Add support for suspending image scans.

Co-authored-by: Michael Bridgen <mikeb@squaremobius.net>
This commit is contained in:
Kevin McDermott 2020-08-26 21:31:38 +01:00
parent 0882b33903
commit 4c0f6d69be
4 changed files with 76 additions and 4 deletions

View File

@ -33,6 +33,11 @@ type ImageRepositorySpec struct {
// scans of the image repository. // scans of the image repository.
// +optional // +optional
ScanInterval *metav1.Duration `json:"scanInterval,omitempty"` ScanInterval *metav1.Duration `json:"scanInterval,omitempty"`
// This flag tells the controller to suspend subsequent image scans.
// It does not apply to already started scans. Defaults to false.
// +optional
Suspend bool `json:"suspend,omitempty"`
} }
type ScanResult struct { type ScanResult struct {

View File

@ -51,6 +51,11 @@ spec:
description: ScanInterval is the (minimum) length of time to wait description: ScanInterval is the (minimum) length of time to wait
between scans of the image repository. between scans of the image repository.
type: string type: string
suspend:
description: This flag tells the controller to suspend
scanning of the image repository.
Defaults to false.
type: boolean
type: object type: object
status: status:
description: ImageRepositoryStatus defines the observed state of ImageRepository description: ImageRepositoryStatus defines the observed state of ImageRepository

View File

@ -77,6 +77,22 @@ func (r *ImageRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er
log := r.Log.WithValues("controller", strings.ToLower(imagev1alpha1.ImageRepositoryKind), "request", req.NamespacedName) log := r.Log.WithValues("controller", strings.ToLower(imagev1alpha1.ImageRepositoryKind), "request", req.NamespacedName)
if imageRepo.Spec.Suspend {
msg := "ImageRepository is suspended, skipping reconciliation"
status := imagev1alpha1.SetImageRepositoryReadiness(
imageRepo,
corev1.ConditionFalse,
imagev1alpha1.SuspendedReason,
msg,
)
if err := r.Status().Update(ctx, &status); err != nil {
log.Error(err, "unable to update status")
return ctrl.Result{Requeue: true}, err
}
log.Info(msg)
return ctrl.Result{}, nil
}
ref, err := name.ParseReference(imageRepo.Spec.Image) ref, err := name.ParseReference(imageRepo.Spec.Image)
if err != nil { if err != nil {
status := imagev1alpha1.SetImageRepositoryReadiness( status := imagev1alpha1.SetImageRepositoryReadiness(

View File

@ -36,17 +36,24 @@ import (
// has an example of loading a test registry with a random image. // has an example of loading a test registry with a random image.
var _ = Describe("ImageRepository controller", func() { var _ = Describe("ImageRepository controller", func() {
const imageName = "alpine-image"
var repo imagev1alpha1.ImageRepository
AfterEach(func() {
Expect(k8sClient.Delete(context.Background(), &repo)).To(Succeed())
})
It("expands the canonical image name", func() { It("expands the canonical image name", func() {
// would be good to test this without needing to do the scanning, since // would be good to test this without needing to do the scanning, since
// 1. better to not rely on external services being available // 1. better to not rely on external services being available
// 2. probably going to want to have several test cases // 2. probably going to want to have several test cases
repo := imagev1alpha1.ImageRepository{ repo = imagev1alpha1.ImageRepository{
Spec: imagev1alpha1.ImageRepositorySpec{ Spec: imagev1alpha1.ImageRepositorySpec{
Image: "alpine", Image: "alpine",
}, },
} }
imageRepoName := types.NamespacedName{ imageRepoName := types.NamespacedName{
Name: "alpine-image", Name: imageName,
Namespace: "default", Namespace: "default",
} }
@ -65,7 +72,7 @@ var _ = Describe("ImageRepository controller", func() {
err := r.Get(context.Background(), imageRepoName, &repoAfter) err := r.Get(context.Background(), imageRepoName, &repoAfter)
return err == nil && repoAfter.Status.CanonicalImageName != "" return err == nil && repoAfter.Status.CanonicalImageName != ""
}, timeout, interval).Should(BeTrue()) }, timeout, interval).Should(BeTrue())
Expect(repoAfter.Name).To(Equal("alpine-image")) Expect(repoAfter.Name).To(Equal(imageName))
Expect(repoAfter.Namespace).To(Equal("default")) Expect(repoAfter.Namespace).To(Equal("default"))
Expect(repoAfter.Status.CanonicalImageName).To(Equal("index.docker.io/library/alpine")) Expect(repoAfter.Status.CanonicalImageName).To(Equal("index.docker.io/library/alpine"))
}) })
@ -74,7 +81,7 @@ var _ = Describe("ImageRepository controller", func() {
versions := []string{"0.1.0", "0.1.1", "0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"} versions := []string{"0.1.0", "0.1.1", "0.2.0", "1.0.0", "1.0.1", "1.0.2", "1.1.0-alpha"}
imgRepo := loadImages("test-fetch", versions) imgRepo := loadImages("test-fetch", versions)
repo := imagev1alpha1.ImageRepository{ repo = imagev1alpha1.ImageRepository{
Spec: imagev1alpha1.ImageRepositorySpec{ Spec: imagev1alpha1.ImageRepositorySpec{
Image: imgRepo, Image: imgRepo,
}, },
@ -101,6 +108,45 @@ var _ = Describe("ImageRepository controller", func() {
Expect(repoAfter.Status.CanonicalImageName).To(Equal(imgRepo)) Expect(repoAfter.Status.CanonicalImageName).To(Equal(imgRepo))
Expect(repoAfter.Status.LastScanResult.TagCount).To(Equal(len(versions))) Expect(repoAfter.Status.LastScanResult.TagCount).To(Equal(len(versions)))
}) })
Context("when the ImageRepository is suspended", func() {
It("does not process the image", func() {
repo = imagev1alpha1.ImageRepository{
Spec: imagev1alpha1.ImageRepositorySpec{
Image: "alpine",
Suspend: true,
},
}
imageRepoName := types.NamespacedName{
Name: imageName,
Namespace: "default",
}
repo.Name = imageRepoName.Name
repo.Namespace = imageRepoName.Namespace
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
r := imageRepoReconciler
err := r.Create(ctx, &repo)
Expect(err).ToNot(HaveOccurred())
var repoAfter imagev1alpha1.ImageRepository
Eventually(func() bool {
err := r.Get(ctx, imageRepoName, &repoAfter)
return err == nil && len(repoAfter.Status.Conditions) > 0
}, timeout, interval).Should(BeTrue())
Expect(repoAfter.Status.CanonicalImageName).To(Equal(""))
cond := repoAfter.Status.Conditions[0]
Expect(cond.Message).To(
Equal("ImageRepository is suspended, skipping reconciliation"))
Expect(cond.Reason).To(
Equal(imagev1alpha1.SuspendedReason))
})
})
}) })
// loadImages uploads images to the local registry, and returns the // loadImages uploads images to the local registry, and returns the