Guidance for AI coding assistants working in fluxcd/source-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/— one file per CRD:gitrepositories.md,ocirepositories.md,helmrepositories.md,helmcharts.md,buckets.md,externalartifacts.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 auth tokens, passwords, or credential URLs in error messages, conditions, events, or log lines. Use
fluxcd/pkg/masktokenand theinternal/errorsanitizers. - No unchecked I/O. Close HTTP response bodies, file handles, and archive readers in
deferstatements. Check and propagate errors fromio.Copy,os.Remove,os.Rename. - No path traversal. Validate and sanitize file paths extracted from archives or user input. Use
securejointo ensure paths stay within the expected root. Neverfilepath.Joinwith untrusted components without validation. - No unbounded reads. Use
io.LimitReaderwhen reading from network or archive sources. Respect existing size limits; do not introduce new reads without bounds. - No command injection. Do not shell out via
os/exec. Use Go libraries for git, helm, OCI, and cloud operations. - No hardcoded defaults for security settings. TLS verification must remain enabled by default; proxy and auth settings come from user-provided secrets, not environment variables.
- 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, directories, and cloned repos 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. Respect the existing
Storage.LockForpattern. - 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. Every export is a backward-compatibility commitment.
source-controller is a core component of the Flux GitOps Toolkit. It reconciles five custom resources in the source.toolkit.fluxcd.io API group — GitRepository, OCIRepository, HelmRepository, HelmChart, and Bucket — by fetching upstream content, verifying it (PGP, Cosign, Notation), and packaging it into an immutable Artifact (tarball or chart). Artifacts are written to a local filesystem rooted at --storage-path and served over HTTP from --storage-addr so downstream controllers (kustomize-controller, helm-controller, source-watcher) can consume them. Status conditions, events, and the artifact URL/revision are what other Flux controllers key off.
main.go— manager wiring: flags, scheme registration,Storageinit, feature gates, setup of the five reconcilers, and the artifact file server.api/— separate Go module (github.com/fluxcd/source-controller/api) holding the CRD types. The root module pulls it via areplacedirective.api/v1/— current storage version. Per-kind*_types.go, sharedartifact_types.go,condition_types.go,source.go,sts_types.go,ociverification_types.go,groupversion_info.go, and generatedzz_generated.deepcopy.go.api/v1beta1/,api/v1beta2/— older versions kept for conversion/compat.
config/— Kustomize overlays.config/crd/bases/holds generated CRDs (one YAML per kind); do not hand-edit.config/default/,config/manager/,config/rbac/,config/samples/,config/testdata/cover install, manager Deployment, RBAC, samples, and fixtures.internal/— controller implementation (not importable by other modules).controller/— the five reconcilers,Storage(artifact lock, archive, GC, serving),artifact.go,source_predicate.go, envtest suite (suite_test.go), and per-kind*_test.gointegration tests.reconcile/— shared reconcile loop primitives;reconcile/summarize/collapses sub-results into terminal status and patches.helm/— Helm logic split intochart/(local/remote chart builders, dependency manager,secureloader),repository/(HTTP and OCIChartRepository),getter/,registry/(OCI client + auth),common/.oci/— OCI auth plusverifier.go;cosign/andnotation/implement the twoOCIRepositoryverification providers.index/— digest indexing for chart repositories.cache/— in-memory TTL cache for Helm index files with Prometheus metrics.digest/— canonical digest algorithm selection (sha256/384/512, blake3) and a hashing writer.predicates/,features/,fs/,util/,tls/,transport/,object/,error/,mock/— small helpers; names match their responsibilities.
pkg/— importable provider clients consumed byBucketReconciler:azure/(Azure Blob),gcp/(GCS),minio/(S3). These are semi-public API.hack/—boilerplate.go.txtlicense header,api-docs/templates,ci/e2e.sh.tests/—listener/,proxy/,registry/harnesses used by integration tests.docs/—spec/(user-facing API docs per version),api/(generated reference),internal/release.md,diagrams/.
- Group:
source.toolkit.fluxcd.io. Storage version:v1.v1beta1andv1beta2remain for compatibility. - Kinds:
GitRepository,OCIRepository,HelmRepository,HelmChart,Bucket. SharedArtifacttype inapi/v1/artifact_types.go; shared conditions inapi/v1/condition_types.go;Sourceinterface inapi/v1/source.go. - CRD manifests under
config/crd/bases/source.toolkit.fluxcd.io_*.yamlare generated from kubebuilder markers. Never edit them by hand — update the types and runmake manifests. api/v1*/zz_generated.deepcopy.gois generated — update types and runmake generate.api/is a distinct Go module so external projects can depend on the types without pulling controller deps. The root module usesreplace github.com/fluxcd/source-controller/api => ./api.
All targets live in the top-level Makefile. Extra go test flags go via GO_TEST_ARGS (default -race). Tool binaries install into build/gobin/ on first use.
make tidy— tidy both the root andapi/modules.make fmt/make vet— run in both modules.make generate—controller-gen objectagainstapi/(deepcopy).make manifests— regenerate CRDs and RBAC from+kubebuildermarkers.make api-docs— regeneratedocs/api/v1/source.md.make manager— static build ofbuild/bin/manager.make test— chainsinstall-envtest+test-api+go test ./...with coverage andKUBEBUILDER_ASSETSset to envtest binaries.make test-api— unit tests insideapi/.make test-ctrl GO_TEST_PREFIX=<name>— run one reconciler suite underinternal/controller.make install/make uninstall/make run/make deploy/make dev-deploy/make docker-build/make docker-push— cluster workflows.make verify— runsfmt vet manifests api-docs tidyand fails on a dirty tree. CI uses this.make e2e— shells out tohack/ci/e2e.sh.
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/*.yamldocs/api/v1/source.md
Load-bearing replace directives in go.mod — do not remove:
Masterminds/semver/v3pinned (see issue #1738).opencontainers/go-digestpinned to a master snapshot for BLAKE3 support.
Bump fluxcd/pkg/* modules as a set — version skew breaks go.sum. Run make tidy after any bump.
- Standard
gofmt. Every exported name needs a doc comment; non-trivial unexported declarations should have one too. - Reconcilers follow the Flux sub-reconciler pattern: the top-level
Reconcileinvokes an ordered slice of step functions, thensummarize.Processor(ininternal/reconcile/summarize/) collapses theirreconcile.Result+ errors into terminal conditions and patches status. Don't set conditions directly from step code. - Status writes go through the patch helper with
metav1.Conditionvalues fromapi/v1/condition_types.goandfluxcd/pkg/apis/meta(Ready,Reconciling,Stalled, kind-specificArtifactInStorage,SourceVerified,FetchFailed, etc.). Usefluxcd/pkg/runtime/conditions. - Events:
EventRecorderwired tofluxcd/pkg/runtime/events.Recorder. Event reasons match condition reasons. - Artifacts: create via
Storage.NewArtifactFor, persist withStorage.Archive/Storage.Copyinside the per-object lock (Storage.LockFor), publish viaStorage.SetArtifactURL, and runStorage.GarbageCollectafter a successful write honoring--artifact-retention-ttland--artifact-retention-records. - TLS/transport: build
http.Transportviainternal/transportand TLS configs viainternal/tls. Proxy, HTTP/2, and keepalive settings must stay consistent. - Feature gates go through
internal/featuresplusfluxcd/pkg/runtime/features. Define the constant infeatures.gowith its default; check withfeatures.Enabled(...). - Digests default to
internal/digest.Canonicaland can be overridden by--artifact-digest-algo. Hash through the writer ininternal/digest— never importcrypto/sha256directly.
- Integration tests live next to the reconcilers in
internal/controller/*_test.go.suite_test.gospins uptestenv.Environment(fluxcd/pkg/runtime/testenv), a localtestserver.ArtifactServer, an in-memory distribution registry, andfoxcpp/go-mockdns. make install-envtestdownloads kube-apiserver/etcd binaries intobuild/testbin/.make testsetsKUBEBUILDER_ASSETSto that path. On macOS the Makefile forcesENVTEST_ARCH=amd64.- Plain Go + Gomega (
gomega.NewWithT(t)); no Ginkgo. Reuse the package-levelk8sClient,testEnv,testStorage, andtestServerfromsuite_test.go. - Tests unset
HTTP_PROXY/HTTPS_PROXYand setGIT_CONFIG_GLOBAL=/dev/nullandGIT_CONFIG_NOSYSTEM=trueto isolate from the developer's environment. Do the same for new git-shelling tests. - Run a single test:
make test GO_TEST_ARGS="-v -run TestGitRepositoryReconciler_reconcileSource". - Run one reconciler suite:
make test-ctrl GO_TEST_PREFIX=TestHelmChart. - Fixtures:
internal/controller/testdata,internal/helm/testdata,internal/fs/testdata. Reuse; don't add new large binaries.
- Two Go modules: root and
api/.make tidy,fmt,vet,testiterate both. A change toapi/types requires runningmake generateandmake manifestsand committing the regenerated files in the same PR. make verifyis the CI gate — a dirty diff means you forgot to run codegen or tidy.- The
replacedirectives ingo.mod(semver and go-digest) exist for correctness. Leave them alone. Storageserializes writes per object viafluxcd/pkg/lockedfileand expects--storage-pathto be a real local directory. Never write artifacts outsideStorage.BasePath— the file server exposes that path verbatim at--storage-addr.- Workload Identity is feature-gated by
auth.FeatureGateObjectLevelWorkloadIdentity(fromfluxcd/pkg/auth). Token caching is opt-in via--token-cache-max-size. CacheSecretsAndConfigMaps(ininternal/features) is off by default;Secret/ConfigMaplookups bypass the cache and hit the API server directly. Mind that before adding new secret reads to a hot path.- The controller watches a label-selected subset of its CRs — see
Cache.ByObjectinmustSetupManager. Adding a new kind requires updating bothmain.goand the scheme. pkg/azure,pkg/gcp,pkg/minioare importable by external consumers. Treat their exported surface as semi-public API.