Respect the reconcileAt annotation

The GOTK controllers all use an annotation to trigger syncing outside
of the schedule (e.g., when a webhook is indicates a new
revision). This commit makes the ImageRepository controller respect
the annotation, by checking it when examining an image repo object.

This works a little different to the other controllers, which tend to
assume that any incoming event means a sync is due. The image
reflector controller uses the ChangedPredicate to filter incoming
evetns, as they do, but goes further to check the annotation and
otherwise, the scheduled scan time.
This commit is contained in:
Michael Bridgen 2020-10-19 13:29:34 +01:00
parent b2e4bfed35
commit 4e5c89ae18
5 changed files with 79 additions and 7 deletions

View File

@ -31,7 +31,9 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/recorder"
"github.com/fluxcd/pkg/runtime/predicates"
imagev1alpha1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
)
@ -169,11 +171,21 @@ func (r *ImageRepositoryReconciler) shouldScan(repo imagev1alpha1.ImageRepositor
scanInterval = repo.Spec.ScanInterval.Duration
}
// never scanned; do it now
lastTransitionTime := imagev1alpha1.GetLastTransitionTime(repo)
if lastTransitionTime == nil {
return true, scanInterval
}
// allow for the "reconcile at" annotation
if syncAt, ok := repo.GetAnnotations()[meta.ReconcileAtAnnotation]; ok {
if t, err := time.Parse(time.RFC3339Nano, syncAt); err == nil && t.Before(now) {
return true, scanInterval
} else if err == nil {
return false, t.Sub(now)
}
}
// when recovering, it's possible that the resource has a last
// scan time, but there's no records because the database has been
// dropped and created again.
@ -195,5 +207,6 @@ func (r *ImageRepositoryReconciler) shouldScan(repo imagev1alpha1.ImageRepositor
func (r *ImageRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&imagev1alpha1.ImageRepository{}).
WithEventFilter(predicates.ChangePredicate{}).
Complete(r)
}

View File

@ -29,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/types"
imagev1alpha1 "github.com/fluxcd/image-reflector-controller/api/v1alpha1"
"github.com/fluxcd/pkg/apis/meta"
// +kubebuilder:scaffold:imports
)
@ -147,6 +148,50 @@ var _ = Describe("ImageRepository controller", func() {
})
})
Context("when the ImageRepository gets a 'reconcile at' annotation", func() {
It("scans right away", func() {
imgRepo := loadImages("test-fetch", []string{"1.0.0"})
repo = imagev1alpha1.ImageRepository{
Spec: imagev1alpha1.ImageRepositorySpec{
Image: imgRepo,
},
}
objectName := types.NamespacedName{
Name: "random",
Namespace: "default",
}
repo.Name = objectName.Name
repo.Namespace = objectName.Namespace
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
r := imageRepoReconciler
err := r.Create(ctx, &repo)
Expect(err).ToNot(HaveOccurred())
// 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 != ""
}, timeout, interval).Should(BeTrue())
lastScan := imagev1alpha1.GetLastTransitionTime(repoAfter)
Expect(lastScan).ToNot(BeNil())
repoAfter.Annotations = map[string]string{
meta.ReconcileAtAnnotation: time.Now().Add(-time.Minute).Format(time.RFC3339Nano),
}
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)
}, timeout, interval).Should(BeTrue())
})
})
})
// loadImages uploads images to the local registry, and returns the

View File

@ -83,9 +83,6 @@ var _ = BeforeSuite(func(done Done) {
err = imagev1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = imagev1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sMgr, err = ctrl.NewManager(cfg, ctrl.Options{
@ -140,7 +137,7 @@ var _ = AfterSuite(func() {
// ---
// the go-containerregistry test regsitry implementation does not
// the go-containerregistry test registry implementation does not
// serve /myimage/tags/list. Until it does, I'm adding this handler.
// NB:
// - assumes repo name is a single element

8
go.mod
View File

@ -7,14 +7,16 @@ 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.0.2
github.com/fluxcd/pkg/recorder v0.0.5
github.com/fluxcd/pkg/runtime v0.1.0
github.com/go-logr/logr v0.1.0
github.com/google/go-containerregistry v0.1.1
github.com/onsi/ginkgo v1.12.1
github.com/onsi/gomega v1.10.1
go.uber.org/zap v1.10.0
k8s.io/api v0.18.6
k8s.io/apimachinery v0.18.6
k8s.io/api v0.18.9
k8s.io/apimachinery v0.18.9
k8s.io/client-go v0.18.6
sigs.k8s.io/controller-runtime v0.6.2
sigs.k8s.io/controller-runtime v0.6.3
)

15
go.sum
View File

@ -196,10 +196,17 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M=
github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses=
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/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fluxcd/pkg v0.0.4 h1:fMA6GG3FTSBFDrlB026gQJhxj4xVuvcvwP3rhX/Yqrw=
github.com/fluxcd/pkg/apis/meta v0.0.2 h1:kyA4Y0IzNjf1joBOnFqpWG7aNDHvtLExZcaHQM7qhRI=
github.com/fluxcd/pkg/apis/meta v0.0.2/go.mod h1:nCNps5JJOcEQr3MNDmZqI4o0chjePSUYL6Q2ktDtotU=
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.1.0 h1:mCLj5GlQZqWtK3tvtZTmfgFOLsTUY1iqg3CmEyS1nRs=
github.com/fluxcd/pkg/runtime v0.1.0/go.mod h1:OXkrYtDLw3GhclbzvnzfSktUyxRmC3FFhXj0tVVaIX8=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -415,6 +422,8 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
github.com/hashicorp/go-retryablehttp v0.6.4/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.6 h1:HJunrbHTDDbBb/ay4kxa1n+dLmttUlnP3V9oNE4hmsM=
github.com/hashicorp/go-retryablehttp v0.6.6/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.6.7 h1:8/CAEZt/+F7kR7GevNHulKkUjLht3CPmn7egmhieNKo=
github.com/hashicorp/go-retryablehttp v0.6.7/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
@ -1087,12 +1096,16 @@ k8s.io/api v0.17.4/go.mod h1:5qxx6vjmwUVG2nHQTKGlLts8Tbok8PzHl4vHtVFuZCA=
k8s.io/api v0.18.4/go.mod h1:lOIQAKYgai1+vz9J7YcDZwC26Z0zQewYOGWdyIPUUQ4=
k8s.io/api v0.18.6 h1:osqrAXbOQjkKIWDTjrqxWQ3w0GkKb1KA1XkUGHHYpeE=
k8s.io/api v0.18.6/go.mod h1:eeyxr+cwCjMdLAmr2W3RyDI0VvTawSg/3RFFBEnmZGI=
k8s.io/api v0.18.9 h1:7VDtivqwbvLOf8hmXSd/PDSSbpCBq49MELg84EYBYiQ=
k8s.io/api v0.18.9/go.mod h1:9u/h6sUh6FxfErv7QqetX1EB3yBMIYOBXzdcf0Gf0rc=
k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrqOzaMo=
k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M=
k8s.io/apimachinery v0.17.4/go.mod h1:gxLnyZcGNdZTCLnq3fgzyg2A5BVCHTNDFrw8AmuJ+0g=
k8s.io/apimachinery v0.18.4/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apimachinery v0.18.9 h1:3ZABKQx3F3xPWlsGhCfUl8W+JXRRblV6Wo2A3zn0pvY=
k8s.io/apimachinery v0.18.9/go.mod h1:PF5taHbXgTEJLU+xMypMmYTXTWPJ5LaW8bfsisxnEXk=
k8s.io/apiserver v0.17.4/go.mod h1:5ZDQ6Xr5MNBxyi3iUZXS84QOhZl+W7Oq2us/29c0j9I=
k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg=
k8s.io/client-go v0.17.4/go.mod h1:ouF6o5pz3is8qU0/qYL2RnoxOPqgfuidYLowytyLJmc=
@ -1137,6 +1150,8 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0=
sigs.k8s.io/controller-runtime v0.6.2 h1:jkAnfdTYBpFwlmBn3pS5HFO06SfxvnTZ1p5PeEF/zAA=
sigs.k8s.io/controller-runtime v0.6.2/go.mod h1:vhcq/rlnENJ09SIRp3EveTaZ0yqH526hjf9iJdbUJ/E=
sigs.k8s.io/controller-runtime v0.6.3 h1:SBbr+inLPEKhvlJtrvDcwIpm+uhDvp63Bl72xYJtoOE=
sigs.k8s.io/controller-runtime v0.6.3/go.mod h1:WlZNXcM0++oyaQt4B7C2lEE5JYRs8vJUzRP4N4JpdAY=
sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU=
sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18=