From 5f616499ed0a8b2d4981a55c362c51ac628d2212 Mon Sep 17 00:00:00 2001 From: Aurel Canciu Date: Tue, 17 Nov 2020 21:32:55 +0200 Subject: [PATCH 1/2] Bump pkg/apis/meta to v0.4.0 Signed-off-by: Aurel Canciu --- api/go.mod | 2 +- api/go.sum | 4 ++-- go.mod | 2 +- go.sum | 2 ++ 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/api/go.mod b/api/go.mod index 88614bf5..818d22b4 100644 --- a/api/go.mod +++ b/api/go.mod @@ -3,7 +3,7 @@ module github.com/fluxcd/image-reflector-controller/api go 1.15 require ( - github.com/fluxcd/pkg/apis/meta v0.3.0 + github.com/fluxcd/pkg/apis/meta v0.4.0 k8s.io/api v0.19.3 k8s.io/apimachinery v0.19.3 sigs.k8s.io/controller-runtime v0.6.3 diff --git a/api/go.sum b/api/go.sum index 7d884f60..f75e1b76 100644 --- a/api/go.sum +++ b/api/go.sum @@ -61,8 +61,8 @@ github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLi github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fluxcd/pkg/apis/meta v0.3.0 h1:o2YkfGgf0j8sKeZs8cBmmmMKLA7kEoS1qYViOial1Ds= -github.com/fluxcd/pkg/apis/meta v0.3.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0= +github.com/fluxcd/pkg/apis/meta v0.4.0 h1:JChqB9GGgorW9HWKxirTVV0rzrcLyzBaVjinmqZ0iHA= +github.com/fluxcd/pkg/apis/meta v0.4.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= diff --git a/go.mod b/go.mod index 1a209898..5ba53b2a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/fluxcd/image-reflector-controller/api => ./api require ( github.com/Masterminds/semver/v3 v3.1.0 github.com/fluxcd/image-reflector-controller/api v0.0.0-00010101000000-000000000000 - github.com/fluxcd/pkg/apis/meta v0.3.0 + github.com/fluxcd/pkg/apis/meta v0.4.0 github.com/fluxcd/pkg/recorder v0.0.5 github.com/fluxcd/pkg/runtime v0.3.0 github.com/go-logr/logr v0.2.1 diff --git a/go.sum b/go.sum index 7bbadf2e..a8a5cb04 100644 --- a/go.sum +++ b/go.sum @@ -203,6 +203,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fluxcd/pkg/apis/meta v0.3.0 h1:o2YkfGgf0j8sKeZs8cBmmmMKLA7kEoS1qYViOial1Ds= github.com/fluxcd/pkg/apis/meta v0.3.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0= +github.com/fluxcd/pkg/apis/meta v0.4.0 h1:JChqB9GGgorW9HWKxirTVV0rzrcLyzBaVjinmqZ0iHA= +github.com/fluxcd/pkg/apis/meta v0.4.0/go.mod h1:wOzQQx8CdtUQCGaLzqGu4QgnNxYkI6/wvdvlovxWhF0= github.com/fluxcd/pkg/recorder v0.0.5 h1:D8qfupahIvh6ncCMn2yTHsrzG91S05sp4zdpsbKWeaU= github.com/fluxcd/pkg/recorder v0.0.5/go.mod h1:2UG6EroZ6ZbqmqoL8k/cQMe09e6A36WyH4t4UDUGyuU= github.com/fluxcd/pkg/runtime v0.3.0 h1:WpeTmDT2meIe4NsU081I8zmUGgTYs3bIMRgs9F3Lj90= From 5eec08d232038719dd915e5a19b94f4e7d0dc5b2 Mon Sep 17 00:00:00 2001 From: Aurel Canciu Date: Fri, 20 Nov 2020 17:38:43 +0200 Subject: [PATCH 2/2] Refactor and align with stable components patterns This is an attempt to bring the api and controller logic closer to what the other controller components already have set as patterns. 1. Adopt the k8s standard Condition type. 2. Rename `ScanInterval` to `Interval` to be consistent with the `Interval` attribute other Spec types have defined, translating to reconciliation interval. This attribute is now required. 3. Add `ScanTime` attribute to the `ScanResult` type, enabling keeping track of the last successful scan execution. Use this value for scan frequency throttling. 4. Add optional `Timeout` attribute to allow custom scan timeout handling. The default value is equal to that of the `Interval` attr. Signed-off-by: Aurel Canciu --- api/v1alpha1/imagerepository_types.go | 48 +++++----- api/v1alpha1/zz_generated.deepcopy.go | 11 ++- ...e.toolkit.fluxcd.io_imagerepositories.yaml | 12 ++- controllers/imagerepository_controller.go | 90 ++++++++++--------- controllers/policy_test.go | 15 ++-- controllers/scan_test.go | 47 +++++----- controllers/suite_test.go | 7 +- 7 files changed, 128 insertions(+), 102 deletions(-) diff --git a/api/v1alpha1/imagerepository_types.go b/api/v1alpha1/imagerepository_types.go index 84e50ad2..7095e676 100644 --- a/api/v1alpha1/imagerepository_types.go +++ b/api/v1alpha1/imagerepository_types.go @@ -17,6 +17,8 @@ limitations under the License. package v1alpha1 import ( + "time" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,10 +33,15 @@ type ImageRepositorySpec struct { // Image is the name of the image repository // +required Image string `json:"image,omitempty"` - // ScanInterval is the (minimum) length of time to wait between + // Interval is the length of time to wait between // scans of the image repository. + // +required + Interval metav1.Duration `json:"interval,omitempty"` + + // Timeout for image scanning. + // Defaults to 'Interval' duration. // +optional - ScanInterval *metav1.Duration `json:"scanInterval,omitempty"` + Timeout *metav1.Duration `json:"timeout,omitempty"` // SecretRef can be given the name of a secret containing // credentials to use for the image registry. The secret should be @@ -49,7 +56,8 @@ type ImageRepositorySpec struct { } type ScanResult struct { - TagCount int `json:"tagCount"` + TagCount int `json:"tagCount"` + ScanTime *metav1.Time `json:"scanTime,omitempty"` } // ImageRepositoryStatus defines the observed state of ImageRepository @@ -75,28 +83,26 @@ type ImageRepositoryStatus struct { } // SetImageRepositoryReadiness sets the ready condition with the given status, reason and message. -func SetImageRepositoryReadiness(ir ImageRepository, status metav1.ConditionStatus, reason, message string) ImageRepository { - ir.Status.Conditions = []metav1.Condition{ - { - Type: meta.ReadyCondition, - Status: status, - LastTransitionTime: metav1.Now(), - Reason: reason, - Message: message, - }, - } +func SetImageRepositoryReadiness(ir *ImageRepository, status metav1.ConditionStatus, reason, message string) { ir.Status.ObservedGeneration = ir.ObjectMeta.Generation - return ir + meta.SetResourceCondition(ir, meta.ReadyCondition, status, reason, message) } -func GetLastTransitionTime(ir ImageRepository) *metav1.Time { - for _, condition := range ir.Status.Conditions { - if condition.Type == meta.ReadyCondition { - return &condition.LastTransitionTime - } - } +// GetStatusConditions returns a pointer to the Status.Conditions slice +func (in *ImageRepository) GetStatusConditions() *[]metav1.Condition { + return &in.Status.Conditions +} - return nil +// GetTimeout returns the timeout with default. +func (in ImageRepository) GetTimeout() time.Duration { + duration := in.Spec.Interval.Duration + if in.Spec.Timeout != nil { + duration = in.Spec.Timeout.Duration + } + if duration < time.Second { + return time.Second + } + return duration } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 9b2f4fe4..26a00276 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -199,8 +199,9 @@ func (in *ImageRepositoryList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ImageRepositorySpec) DeepCopyInto(out *ImageRepositorySpec) { *out = *in - if in.ScanInterval != nil { - in, out := &in.ScanInterval, &out.ScanInterval + out.Interval = in.Interval + if in.Timeout != nil { + in, out := &in.Timeout, &out.Timeout *out = new(v1.Duration) **out = **in } @@ -231,7 +232,7 @@ func (in *ImageRepositoryStatus) DeepCopyInto(out *ImageRepositoryStatus) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - out.LastScanResult = in.LastScanResult + in.LastScanResult.DeepCopyInto(&out.LastScanResult) out.ReconcileRequestStatus = in.ReconcileRequestStatus } @@ -248,6 +249,10 @@ func (in *ImageRepositoryStatus) DeepCopy() *ImageRepositoryStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScanResult) DeepCopyInto(out *ScanResult) { *out = *in + if in.ScanTime != nil { + in, out := &in.ScanTime, &out.ScanTime + *out = (*in).DeepCopy() + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanResult. diff --git a/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml b/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml index 15a4cb9b..dd76f01c 100644 --- a/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml +++ b/config/crd/bases/image.toolkit.fluxcd.io_imagerepositories.yaml @@ -47,9 +47,9 @@ spec: image: description: Image is the name of the image repository type: string - scanInterval: - description: ScanInterval is the (minimum) length of time to wait - between scans of the image repository. + interval: + description: Interval is the length of time to wait between scans + of the image repository. type: string secretRef: description: SecretRef can be given the name of a secret containing @@ -66,6 +66,9 @@ spec: image scans. It does not apply to already started scans. Defaults to false. type: boolean + timeout: + description: Timeout for image scanning. Defaults to 'Interval' duration. + type: string type: object status: description: ImageRepositoryStatus defines the observed state of ImageRepository @@ -151,6 +154,9 @@ spec: lastScanResult: description: LastScanResult contains the number of fetched tags. properties: + scanTime: + format: date-time + type: string tagCount: type: integer required: diff --git a/controllers/imagerepository_controller.go b/controllers/imagerepository_controller.go index d938aad9..86d7f80c 100644 --- a/controllers/imagerepository_controller.go +++ b/controllers/imagerepository_controller.go @@ -43,11 +43,6 @@ import ( imagev1alpha1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" ) -const ( - scanTimeout = 10 * time.Second - defaultScanInterval = 10 * time.Minute -) - type DatabaseWriter interface { SetTags(repo string, tags []string) } @@ -70,6 +65,7 @@ type ImageRepositoryReconciler struct { func (r *ImageRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { ctx := context.Background() + reconcileStart := time.Now() // NB: In general, if an error is returned then controller-runtime // will requeue the request with back-off. In the following this @@ -78,7 +74,6 @@ func (r *ImageRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er var imageRepo imagev1alpha1.ImageRepository if err := r.Get(ctx, req.NamespacedName, &imageRepo); err != nil { - // _Might_ get requeued return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -86,13 +81,13 @@ func (r *ImageRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er if imageRepo.Spec.Suspend { msg := "ImageRepository is suspended, skipping reconciliation" - status := imagev1alpha1.SetImageRepositoryReadiness( - imageRepo, + imagev1alpha1.SetImageRepositoryReadiness( + &imageRepo, metav1.ConditionFalse, meta.SuspendedReason, msg, ) - if err := r.Status().Update(ctx, &status); err != nil { + if err := r.Status().Update(ctx, &imageRepo); err != nil { log.Error(err, "unable to update status") return ctrl.Result{Requeue: true}, err } @@ -102,47 +97,51 @@ func (r *ImageRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, er ref, err := name.ParseReference(imageRepo.Spec.Image) if err != nil { - status := imagev1alpha1.SetImageRepositoryReadiness( - imageRepo, + imagev1alpha1.SetImageRepositoryReadiness( + &imageRepo, metav1.ConditionFalse, imagev1alpha1.ImageURLInvalidReason, err.Error(), ) - if err := r.Status().Update(ctx, &status); err != nil { + if err := r.Status().Update(ctx, &imageRepo); err != nil { return ctrl.Result{Requeue: true}, err } log.Error(err, "Unable to parse image name", "imageName", imageRepo.Spec.Image) return ctrl.Result{Requeue: true}, err } - imageRepo.Status.CanonicalImageName = ref.Context().String() + // Set CanonicalImageName based on the parsed reference + if c := ref.Context().String(); imageRepo.Status.CanonicalImageName != c { + imageRepo.Status.CanonicalImageName = c + if err = r.Status().Update(ctx, &imageRepo); err != nil { + return ctrl.Result{Requeue: true}, err + } + } - now := time.Now() - ok, when := r.shouldScan(imageRepo, now) + // Throttle scans based on spec Interval + ok, when := r.shouldScan(imageRepo, reconcileStart) if ok { - ctx, cancel := context.WithTimeout(ctx, scanTimeout) - defer cancel() - - reconciledRepo, reconcileErr := r.scan(ctx, imageRepo, ref) - if err = r.Status().Update(ctx, &reconciledRepo); err != nil { + reconcileErr := r.scan(ctx, &imageRepo, ref) + if err = r.Status().Update(ctx, &imageRepo); err != nil { return ctrl.Result{Requeue: true}, err } - if reconcileErr != nil { return ctrl.Result{Requeue: true}, reconcileErr - } else { - log.Info(fmt.Sprintf("reconciliation finished in %s, next run in %s", - time.Now().Sub(now).String(), - when), - ) } } + log.Info(fmt.Sprintf("reconciliation finished in %s, next run in %s", + time.Now().Sub(reconcileStart).String(), + when.String(), + )) + return ctrl.Result{RequeueAfter: when}, nil } -func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo imagev1alpha1.ImageRepository, ref name.Reference) (imagev1alpha1.ImageRepository, error) { - canonicalName := ref.Context().String() +func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo *imagev1alpha1.ImageRepository, ref name.Reference) error { + timeout := imageRepo.GetTimeout() + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() var options []remote.Option if imageRepo.Spec.SecretRef != nil { @@ -151,39 +150,45 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo imagev1a Namespace: imageRepo.GetNamespace(), Name: imageRepo.Spec.SecretRef.Name, }, &secret); err != nil { - return imagev1alpha1.SetImageRepositoryReadiness( + imagev1alpha1.SetImageRepositoryReadiness( imageRepo, metav1.ConditionFalse, meta.ReconciliationFailedReason, err.Error(), - ), err + ) + return err } auth, err := authFromSecret(secret, ref.Context().RegistryStr()) if err != nil { - return imagev1alpha1.SetImageRepositoryReadiness( + imagev1alpha1.SetImageRepositoryReadiness( imageRepo, metav1.ConditionFalse, meta.ReconciliationFailedReason, err.Error(), - ), err + ) + return err } options = append(options, remote.WithAuth(auth)) } tags, err := remote.ListWithContext(ctx, ref.Context(), options...) if err != nil { - return imagev1alpha1.SetImageRepositoryReadiness( + imagev1alpha1.SetImageRepositoryReadiness( imageRepo, metav1.ConditionFalse, meta.ReconciliationFailedReason, err.Error(), - ), err + ) + return err } + canonicalName := ref.Context().String() // TODO: add context and error handling to database ops r.Database.SetTags(canonicalName, tags) + scanTime := metav1.Now() imageRepo.Status.LastScanResult.TagCount = len(tags) + imageRepo.Status.LastScanResult.ScanTime = &scanTime // if the reconcile request annotation was set, consider it // handled (NB it doesn't matter here if it was changed since last @@ -192,26 +197,25 @@ func (r *ImageRepositoryReconciler) scan(ctx context.Context, imageRepo imagev1a imageRepo.Status.SetLastHandledReconcileRequest(token) } - return imagev1alpha1.SetImageRepositoryReadiness( + imagev1alpha1.SetImageRepositoryReadiness( imageRepo, metav1.ConditionTrue, meta.ReconciliationSucceededReason, fmt.Sprintf("successful scan, found %v tags", len(tags)), - ), nil + ) + + return nil } // shouldScan takes an image repo and the time now, and says whether // the repository should be scanned now, and how long to wait for the // next scan. func (r *ImageRepositoryReconciler) shouldScan(repo imagev1alpha1.ImageRepository, now time.Time) (bool, time.Duration) { - scanInterval := defaultScanInterval - if repo.Spec.ScanInterval != nil { - scanInterval = repo.Spec.ScanInterval.Duration - } + scanInterval := repo.Spec.Interval.Duration // never scanned; do it now - lastTransitionTime := imagev1alpha1.GetLastTransitionTime(repo) - if lastTransitionTime == nil { + lastScanTime := repo.Status.LastScanResult.ScanTime + if lastScanTime == nil { return true, scanInterval } @@ -235,7 +239,7 @@ func (r *ImageRepositoryReconciler) shouldScan(repo imagev1alpha1.ImageRepositor return true, scanInterval } - when := scanInterval - now.Sub(lastTransitionTime.Time) + when := scanInterval - now.Sub(lastScanTime.Time) if when < time.Second { return true, scanInterval } diff --git a/controllers/policy_test.go b/controllers/policy_test.go index e4eb0863..91996488 100644 --- a/controllers/policy_test.go +++ b/controllers/policy_test.go @@ -19,11 +19,11 @@ package controllers import ( "context" "net/http/httptest" - "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" imagev1alpha1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" @@ -51,7 +51,8 @@ var _ = Describe("ImagePolicy controller", func() { repo := imagev1alpha1.ImageRepository{ Spec: imagev1alpha1.ImageRepositorySpec{ - Image: imgRepo, + Interval: metav1.Duration{Duration: reconciliationInterval}, + Image: imgRepo, }, } imageObjectName := types.NamespacedName{ @@ -61,7 +62,7 @@ var _ = Describe("ImagePolicy controller", func() { repo.Name = imageObjectName.Name repo.Namespace = imageObjectName.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() r := imageRepoReconciler @@ -69,8 +70,8 @@ var _ = Describe("ImagePolicy controller", func() { var repoAfter imagev1alpha1.ImageRepository Eventually(func() bool { - err := r.Get(context.Background(), imageObjectName, &repoAfter) - return err == nil && repoAfter.Status.CanonicalImageName != "" + err := r.Get(ctx, imageObjectName, &repoAfter) + return err == nil && repoAfter.Status.LastScanResult.ScanTime != nil }, timeout, interval).Should(BeTrue()) Expect(repoAfter.Status.CanonicalImageName).To(Equal(imgRepo)) Expect(repoAfter.Status.LastScanResult.TagCount).To(Equal(len(versions))) @@ -94,14 +95,14 @@ var _ = Describe("ImagePolicy controller", func() { pol.Namespace = polName.Namespace pol.Name = polName.Name - ctx, cancel = context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel = context.WithTimeout(context.Background(), contextTimeout) defer cancel() Expect(r.Create(ctx, &pol)).To(Succeed()) var polAfter imagev1alpha1.ImagePolicy Eventually(func() bool { - err := r.Get(context.Background(), polName, &polAfter) + err := r.Get(ctx, polName, &polAfter) return err == nil && polAfter.Status.LatestImage != "" }, timeout, interval).Should(BeTrue()) Expect(polAfter.Status.LatestImage).To(Equal(imgRepo + ":1.0.2")) diff --git a/controllers/scan_test.go b/controllers/scan_test.go index f4e58e55..48e8d45c 100644 --- a/controllers/scan_test.go +++ b/controllers/scan_test.go @@ -20,13 +20,13 @@ import ( "context" "fmt" "net/http/httptest" - "time" "github.com/google/go-containerregistry/pkg/authn" "github.com/google/go-containerregistry/pkg/v1/remote" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" imagev1alpha1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1" @@ -55,7 +55,8 @@ var _ = Describe("ImageRepository controller", func() { // 2. probably going to want to have several test cases repo = imagev1alpha1.ImageRepository{ Spec: imagev1alpha1.ImageRepositorySpec{ - Image: "alpine", + Interval: metav1.Duration{Duration: reconciliationInterval}, + Image: "alpine", }, } imageRepoName := types.NamespacedName{ @@ -66,7 +67,7 @@ var _ = Describe("ImageRepository controller", func() { repo.Name = imageRepoName.Name repo.Namespace = imageRepoName.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() r := imageRepoReconciler @@ -76,7 +77,7 @@ var _ = Describe("ImageRepository controller", func() { var repoAfter imagev1alpha1.ImageRepository Eventually(func() bool { err := r.Get(context.Background(), imageRepoName, &repoAfter) - return err == nil && repoAfter.Status.CanonicalImageName != "" + return err == nil && repoAfter.Status.LastScanResult.ScanTime != nil }, timeout, interval).Should(BeTrue()) Expect(repoAfter.Name).To(Equal(imageName)) Expect(repoAfter.Namespace).To(Equal("default")) @@ -89,7 +90,8 @@ var _ = Describe("ImageRepository controller", func() { repo = imagev1alpha1.ImageRepository{ Spec: imagev1alpha1.ImageRepositorySpec{ - Image: imgRepo, + Interval: metav1.Duration{Duration: reconciliationInterval}, + Image: imgRepo, }, } objectName := types.NamespacedName{ @@ -100,7 +102,7 @@ var _ = Describe("ImageRepository controller", func() { repo.Name = objectName.Name repo.Namespace = objectName.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() r := imageRepoReconciler @@ -109,7 +111,7 @@ var _ = Describe("ImageRepository controller", func() { var repoAfter imagev1alpha1.ImageRepository Eventually(func() bool { err := r.Get(context.Background(), objectName, &repoAfter) - return err == nil && repoAfter.Status.CanonicalImageName != "" + return err == nil && repoAfter.Status.LastScanResult.ScanTime != nil }, timeout, interval).Should(BeTrue()) Expect(repoAfter.Status.CanonicalImageName).To(Equal(imgRepo)) Expect(repoAfter.Status.LastScanResult.TagCount).To(Equal(len(versions))) @@ -119,8 +121,9 @@ var _ = Describe("ImageRepository controller", func() { It("does not process the image", func() { repo = imagev1alpha1.ImageRepository{ Spec: imagev1alpha1.ImageRepositorySpec{ - Image: "alpine", - Suspend: true, + Interval: metav1.Duration{Duration: reconciliationInterval}, + Image: "alpine", + Suspend: true, }, } imageRepoName := types.NamespacedName{ @@ -131,7 +134,7 @@ var _ = Describe("ImageRepository controller", func() { repo.Name = imageRepoName.Name repo.Namespace = imageRepoName.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() r := imageRepoReconciler @@ -159,7 +162,8 @@ var _ = Describe("ImageRepository controller", func() { repo = imagev1alpha1.ImageRepository{ Spec: imagev1alpha1.ImageRepositorySpec{ - Image: imgRepo, + Interval: metav1.Duration{Duration: reconciliationInterval}, + Image: imgRepo, }, } objectName := types.NamespacedName{ @@ -170,7 +174,7 @@ var _ = Describe("ImageRepository controller", func() { repo.Name = objectName.Name repo.Namespace = objectName.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() r := imageRepoReconciler @@ -180,22 +184,20 @@ var _ = Describe("ImageRepository controller", func() { // It'll get scanned on creation var repoAfter imagev1alpha1.ImageRepository Eventually(func() bool { - err := r.Get(context.Background(), objectName, &repoAfter) - return err == nil && repoAfter.Status.CanonicalImageName != "" + err := r.Get(ctx, objectName, &repoAfter) + return err == nil && repoAfter.Status.LastScanResult.ScanTime != nil }, timeout, interval).Should(BeTrue()) - lastScan := imagev1alpha1.GetLastTransitionTime(repoAfter) - Expect(lastScan).ToNot(BeNil()) - requestToken := "this can be anything, so long as it's a change" + lastScanTime := repoAfter.Status.LastScanResult.ScanTime repoAfter.Annotations = map[string]string{ meta.ReconcileAtAnnotation: requestToken, } Expect(r.Update(ctx, &repoAfter)).To(Succeed()) Eventually(func() bool { - err := r.Get(context.Background(), objectName, &repoAfter) - return err == nil && imagev1alpha1.GetLastTransitionTime(repoAfter).After(lastScan.Time) + err := r.Get(ctx, objectName, &repoAfter) + return err == nil && repoAfter.Status.LastScanResult.ScanTime.After(lastScanTime.Time) }, timeout, interval).Should(BeTrue()) Expect(repoAfter.Status.LastHandledReconcileAt).To(Equal(requestToken)) }) @@ -249,7 +251,8 @@ var _ = Describe("ImageRepository controller", func() { repo = imagev1alpha1.ImageRepository{ Spec: imagev1alpha1.ImageRepositorySpec{ - Image: imgRepo, + Interval: metav1.Duration{Duration: reconciliationInterval}, + Image: imgRepo, SecretRef: &corev1.LocalObjectReference{ Name: "docker", }, @@ -263,7 +266,7 @@ var _ = Describe("ImageRepository controller", func() { repo.Name = objectName.Name repo.Namespace = objectName.Namespace - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) defer cancel() r := imageRepoReconciler @@ -272,7 +275,7 @@ var _ = Describe("ImageRepository controller", func() { var repoAfter imagev1alpha1.ImageRepository Eventually(func() bool { err := r.Get(context.Background(), objectName, &repoAfter) - return err == nil && repoAfter.Status.CanonicalImageName != "" + return err == nil && repoAfter.Status.LastScanResult.ScanTime != nil }, timeout, interval).Should(BeTrue()) Expect(repoAfter.Status.CanonicalImageName).To(Equal(imgRepo)) Expect(repoAfter.Status.LastScanResult.TagCount).To(Equal(len(versions))) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 4094bac0..57af2687 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -38,9 +38,10 @@ import ( // for Eventually const ( - timeout = time.Second * 30 - interval = time.Second * 1 - // indexInterval = time.Second * 1 + timeout = time.Second * 30 + contextTimeout = time.Second * 5 + interval = time.Second * 1 + reconciliationInterval = time.Second * 5 ) // These tests use Ginkgo (BDD-style Go testing framework). Refer to