Guidance for AI coding assistants working in fluxcd/image-reflector-controller. Read this file before making changes.
These rules come from fluxcd/flux2/CONTRIBUTING.md and apply to every Flux repository.
- Do not add
Signed-off-byorCo-authored-bytrailers with your agent name. Only a human can legally certify the DCO. - Disclose AI assistance with an
Assisted-bytrailer naming your agent and model:Thegit commit -s -m "Add support for X" --trailer "Assisted-by: <agent-name>/<model-id>"
-sflag adds the human'sSigned-off-byfrom their git config — do not remove it. - Commit message format: Subject in imperative mood ("Add feature X" instead of "Adding feature X"), capitalized, no trailing period, ≤50 characters. Body wrapped at 72 columns, explaining what and why. No
@mentionsor#123issue references in the commit — put those in the PR description. - Trim verbiage: in PR descriptions, commit messages, and code comments. No marketing prose, no restating the diff, no emojis.
- Rebase, don't merge: Never merge
maininto the feature branch; rebase onto the latestmainand push with--force-with-lease. Squash before merge when asked. - Pre-PR gate:
make tidy fmt vet && make testmust pass and the working tree must be clean after codegen. Commit regenerated files in the same PR. - Flux is GA: Backward compatibility is mandatory. Breaking changes to CRD fields, status, CLI flags, metrics, or observable behavior will be rejected. Design additive changes and keep older API versions round-tripping.
- Copyright: All new
.gofiles must begin with the boilerplate fromhack/boilerplate.go.txt(Apache 2.0). Update the year to the current year when copying. - Spec docs: New features and API changes must be documented in
docs/spec/v1/—imagepolicies.mdandimagerepositories.md. Update the relevant file in the same PR that introduces the change. - Tests: New features, improvements and fixes must have test coverage. Add unit tests in
internal/controller/*_test.goand otherinternal/*packages as appropriate. Follow the existing patterns for test organization, fixtures, and assertions. Run tests locally before pushing.
Before submitting code, review your changes for the following:
- No secrets in logs or events. Never surface registry credentials, cloud provider tokens, or pull-secret contents in error messages, conditions, events, or log lines.
- No unchecked I/O. Close HTTP response bodies, file handles, and registry connections in
deferstatements. Check and propagate errors from I/O operations. - No unbounded reads. Use
io.LimitReaderwhen reading from network sources. Repositories with very large tag sets (tens of thousands) consume significant memory; respect existing limits. - No direct Badger imports from reconcilers. Database access goes through the
DatabaseReader/DatabaseWriterinterfaces ininternal/controller/database.go. That boundary is load-bearing for testability. - Registry rate limits. New scan paths must go through the existing reconcile rate limiter and the
TokenCache. Do not callremote.Listin tight loops — public registries (Docker Hub in particular) rate-limit aggressively. - Auth through
AuthOptionsGetter. Registry auth resolution lives ininternal/registry/options.go. Add new auth knobs there, not inline in the reconciler. Cloud provider tokens must use the sharedTokenCache. - Error handling. Wrap errors with
%wfor chain inspection. Do not swallow errors silently. Return actionable error messages that help users diagnose the issue without leaking internal state. - Resource cleanup. Ensure temporary files and directories are cleaned up on all code paths (success and error). Use
deferandt.TempDir()in tests. - Concurrency safety. Do not introduce shared mutable state without synchronization. Reconcilers run concurrently; per-object work must be isolated. Leader election is required because BadgerDB does not support concurrent access from multiple replicas.
- No panics. Never use
panicin runtime code paths. Return errors and let the reconciler handle them gracefully. - Minimal surface. Keep new exported APIs, flags, and environment variables to the minimum needed. The
api/module is consumed by image-automation-controller — every exported change is a cross-repo contract change.
image-reflector-controller is a component of the Flux GitOps Toolkit. It reconciles two CRDs under image.toolkit.fluxcd.io/v1:
ImageRepository— scans an OCI image repository at a fixed interval, lists the tags via the registry API, and persists them.ImagePolicy— references anImageRepository, filters its tag set (optional regex), and elects a "latest" tag using one of three policies:semver(Masterminds/semver ranges),alphabetical, ornumerical. It can also reflect the image digest for the elected tag (digestReflectionPolicy: Never | IfNotPresent | Always).
Scan results are stored in an on-disk BadgerDB database at --storage-path (default /data). The elected latest image ref is written to the ImagePolicy status and consumed by image-automation-controller, which patches that ref into Git. This controller never writes to Git. Tag listing uses github.com/google/go-containerregistry (remote.List). Auth resolves via spec.secretRef, spec.serviceAccountName (with image pull secrets), spec.certSecretRef, spec.proxySecretRef, and the fluxcd/pkg/auth providers for ECR, GCR/AR, and ACR (including object-level workload identity when the feature gate is enabled).
main.go— controller entrypoint: flag parsing, BadgerDB open, manager setup, reconciler wiring, feature gates, token cache, leader election.api/v1/— CRD Go types (imagerepository_types.go,imagepolicy_types.go,condition_types.go,groupversion_info.go). Its own Go module, imported viareplacefrom the rootgo.mod; consumed by image-automation-controller.api/v1beta1/andapi/v1beta2/exist for conversion and must not be broken.zz_generated.deepcopy.gois generated.internal/controller/—ImageRepositoryReconciler,ImagePolicyReconciler, plus theDatabaseReader/DatabaseWriterinterfaces they depend on. Envtest-based suite insuite_test.go.internal/database/— BadgerDB-backed tag store (badger.go) and a periodic GC runnable (badger_gc.go) plugged into the manager.internal/policy/— policy evaluation:semver.go,alphabetical.go,numerical.go,filter.go(regex pre-filter with replace),factory.go(PolicerFromSpec),policer.go(thePolicerinterface).internal/registry/—AuthOptionsGetterthat turns anImageRepositoryinto go-containerregistryremote.Options (secrets, TLS, proxy, cloud provider auth, token cache).helper.gohas shared registry helpers.internal/features/— feature gate registration (workload identity, secret caching).internal/test/— in-process test registry, TLS server, and proxy used by the unit/envtest suite.config/— Kustomize manifests:crd/bases/(generated CRDs),manager/,rbac/,default/,samples/.docs/spec/— human-written CRD spec docs.docs/api/v1/— generated API reference.hack/— codegen boilerplate and api-docs templates.tests/integration/— cloud provider e2e tests (AWS/Azure/GCP) using terraform + tftestenv. Separate Go module.
- Group/version:
image.toolkit.fluxcd.io/v1. Kinds:ImageRepository,ImagePolicy. Types inapi/v1/. - CRDs under
config/crd/bases/and API reference docs underdocs/api/v1/image-reflector.mdare generated — do not hand-edit. Regenerate withmake manifestsandmake api-docs. - The
apimodule is imported by image-automation-controller. Any change to exported API types is a cross-repo contract change. Additive only; breaking changes will be rejected.
All targets in the root Makefile. Tool versions pinned via CONTROLLER_GEN_VERSION and GEN_API_REF_DOCS_VERSION.
make tidy— tidy the root,api/, andtests/integration/modules.make fmt/make vet— run in root andapi/.make generate—controller-gen objectagainstapi/(deepcopy).make manifests— regenerate CRDs and RBAC intoconfig/crd/basesandconfig/rbac.make api-docs— regeneratedocs/api/v1/image-reflector.md.make manager—go build -o bin/manager main.go.make test— runstidy generate fmt vet manifests api-docs install-envtest, thengo test ./... -coverprofile cover.outat the root and inapi/. Envtest assets are downloaded viasetup-envtestand exposed throughKUBEBUILDER_ASSETS. HonorsGO_TEST_ARGSfor extra flags (e.g.-run).make run— runs the controller against the cluster in~/.kube/configusing--storage-path=./data.make install/make deploy— apply CRDs / full manager via kustomize.make docker-build— buildx image build. Platforms viaBUILD_PLATFORMS.
Cloud e2e tests live in tests/integration/ and have their own Makefile (make test-aws, make test-azure, make test-gcp). They require real cloud credentials and provision infrastructure via terraform; do not run them by default.
Check go.mod and the Makefile for current dependency and tool versions. After changing API types or kubebuilder markers, regenerate and commit the results:
make generate manifests api-docsGenerated files (never hand-edit):
api/v1/zz_generated.deepcopy.goconfig/crd/bases/*.yamlconfig/rbac/role.yamldocs/api/v1/image-reflector.md
No load-bearing replace directives beyond the standard api/ local replace.
Bump fluxcd/pkg/* modules as a set. Run make tidy after any bump. Use make <target> so the pinned tool versions are installed rather than invoking controller-gen from elsewhere.
- Standard
gofmt,go vet. All exported names need doc comments; non-trivial unexported types and functions should also have them. Match the style of the file you are editing. - Reconcilers use
fluxcd/pkg/runtime/patch+conditionsfor status updates. Do not mutate status in place thenclient.Status().Update; use the patch helper. - Events go through
fluxcd/pkg/runtime/events.Recorder; metrics throughfluxcd/pkg/runtime/metricsvia the sharedhelper.Metrics. - Rate limiting on reconciles is wired via
helper.GetRateLimiterand theRateLimiterOptionsflags; do not add your own workqueue rate limiter. - Honor
spec.provider,spec.serviceAccountName,spec.secretRef,spec.certSecretRef,spec.proxySecretRefwhen wiring auth options. - Cloud provider tokens are cached in a
pkg/cache.TokenCachekeyed per involved object; respect that cache in new auth paths. - Tag filtering: the optional
spec.filterTagsregex runs before the policer; the policer only sees tags that matched and were optionally rewritten via the replace pattern. Seeinternal/policy/filter.go. - Policy selection is dispatched in
internal/policy/factory.go(PolicerFromSpec); add new policy types there and implement thePolicerinterface. - The
latestTagsCountconstant inimagerepository_controller.gocontrols how many recent tags are surfaced onImageRepositorystatus — do not change it casually.
- Unit and envtest suites live next to the code they test (
internal/controller/*_test.go,internal/policy/*_test.go,internal/database/*_test.go,internal/registry/options_test.go). - The controller suite (
internal/controller/suite_test.go) boots a controller-runtime envtest environment;KUBEBUILDER_ASSETSmust point at an installed kube-apiserver/etcd.make install-envtestinstalls them;make testwires the variable for you. - Registry-touching tests use the in-process helpers in
internal/test/(plain registry, TLS registry, proxy) backed bygo-containerregistry/pkg/registry. Prefer these over network calls. - Fixture images are created in-memory with
go-containerregistry/pkg/v1/randomand pushed to the test registry. - Run a single test:
make test GO_TEST_ARGS='-run TestImagePolicyReconciler_Reconcile'. tests/integration/is a separate module and is not exercised bymake test; do not add unit tests there.
- The Badger database at
--storage-pathis the source of truth for cached tag lists and is assumed to be on a persistent volume in production. Two controller replicas against the same volume is not safe; leader election is enabled for a reason. --storage-value-log-file-size(default1<<28) caps Badger's mmap'd value log. Effective memory is roughly 2× that. Repositories with very large tag sets (tens of thousands) push this. Do not raise the default casually.--gc-interval(minutes) drives theBadgerGarbageCollectorrunnable registered on the manager.0disables GC. Tests should not depend on GC running.- Public registry rate limits (Docker Hub in particular) are a real source of flakes.
- Semver ranges use Masterminds/semver v3 semantics (
>=1.0.0 <2.0.0,~1.2,^1.2). Not Cargo, not npm. Prereleases are excluded unless the range explicitly allows them. - The
filterTags.extractreplace pattern rewrites the tag string the policer sees; the original tag is still what gets published aslatestRef.tag. Keep that distinction when adding new policies. DigestReflectionPolicy: Alwaysis the only mode whereImagePolicy.spec.intervalis meaningful; the CEL validation rules on the CRD enforce that pairing. Do not loosen them without a very good reason.v1beta1andv1beta2API packages exist for conversion. Removing fields or kinds from those is a breaking change for existing clusters.- Cloud auth feature gate:
ObjectLevelWorkloadIdentitychanges howspec.serviceAccountNameis resolved for ECR/GCR/ACR. Changes to auth code paths must consider both gate-on and gate-off behavior, andauth.InconsistentObjectLevelConfiguration.