Guidance for AI coding assistants working in fluxcd/notification-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/under the current version —v1/receivers.mdfor Receiver,v1beta3/alerts.mdandv1beta3/providers.mdfor Alert and Provider. 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. Anything derived from a Secret (tokens, passwords, webhook URLs with embedded secrets) must be scrubbed via
fluxcd/pkg/masktokenbefore logging or surfacing in conditions. Never log the receiver webhook path at info level — it is effectively a secret. - No unchecked I/O. Close HTTP response bodies and file handles in
deferstatements. Check and propagate errors from I/O operations. - Bounded request bodies. Both HTTP servers enforce
maxRequestSizeBytes(3 MiB). Always read throughio.LimitReaderwhen extending handlers. Do not introduce new readers without bounds. - Single HTTP client for notifiers.
internal/notifier/client.gois the only sanctioned way to make outbound HTTP calls from a notifier. It handles proxies, TLS, retries, and response validation. Do not add your own HTTP client or retry loops. - No hardcoded defaults for security settings. TLS verification must remain enabled by default; proxy settings come from user-provided secrets. Receiver signature verification must never be short-circuited.
- 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. - No duplicate rate limiting. The event server already applies a token-bucket rate limiter keyed by event fingerprint. Do not add a second layer of deduplication.
- Concurrency safety. Do not introduce shared mutable state without synchronization. Both HTTP servers and the reconcilers run concurrently.
- No panics. Never use
panicin runtime code paths. Return errors and let the reconciler or handler handle them gracefully. - Minimal surface. Keep new exported APIs, flags, and environment variables to the minimum needed. Every export is a backward-compatibility commitment.
notification-controller is the eventing edge of the Flux GitOps Toolkit. It reconciles three custom resources under notification.toolkit.fluxcd.io — Provider, Alert, and Receiver — and runs two HTTP servers alongside the controller manager:
- An inbound event sink (default
:9090) that ingestseventv1.Eventpayloads from the other Flux controllers and dispatches them to external notifier backends (Slack, MS Teams, PagerDuty, Git commit status, …) according to matchingAlertresources. - An inbound webhook receiver (default
:9292) that translates third-party webhooks (GitHub, GitLab, Harbor, image registries, CDEvents, …) into reconcile requests on Flux objects.
It does not reconcile source or workload state itself.
main.go— manager entrypoint. Registers the three reconcilers, startsserver.NewEventServerandserver.NewReceiverServeras goroutines, wires feature gates, token cache, and workload identity.api/— separate Go module (replaced from rootgo.mod). CRD Go types forv1,v1beta1,v1beta2,v1beta3. Storage versions:v1forReceiver,v1beta3forAlertandProvider.zz_generated.deepcopy.gois generated.internal/controller/— reconcilers:provider_controller.go,alert_controller.go,receiver_controller.go, predicates, and the envtestsuite_test.go.internal/server/— the two HTTP servers.event_server.go+event_handlers.goimplement event ingestion, alert matching, filtering, rate limiting (sethvargo/go-limiter) and token masking (fluxcd/pkg/masktoken).receiver_server.go+receiver_handlers.goimplement webhook verification and dispatch.provider_commit_status.goandprovider_change_request.gohandle Git commit-status/MR commenting.internal/notifier/— one file per backend plusnotifier.go(theInterfacewith a singlePost(ctx, event)method),factory.go(provider name → constructor map),client.go(shared retryable HTTP client),util.go,markdown.go. Each backend has a sibling_test.go.internal/features/— feature-gate registration.config/— Kustomize overlays.config/crd/bases/holds generated CRDs;config/manager/,config/default/,config/rbac/,config/samples/,config/testdata/.hack/—boilerplate.go.txtandapi-docs/templates forgen-crd-api-reference-docs.docs/spec/— human-readable specs per API version.docs/api/— generated reference docs.tests/—listener/andproxy/helpers for integration tests.
Mapped in factory.go:
- generic, generic-hmac (the built-in
Forwarder) - slack, discord, rocket, msteams, googlechat, googlepubsub, webex, telegram, lark, matrix, zulip
- sentry, pagerduty, opsgenie, datadog, grafana, alertmanager
- github, githubdispatch, githubpullrequestcomment
- gitlab, gitlabmergerequestcomment
- gitea, giteapullrequestcomment
- bitbucket, bitbucketserver, azuredevops
- azureeventhub, nats, otel
- Group:
notification.toolkit.fluxcd.io. Kinds:Provider,Alert,Receiver. - Types under
api/<version>/{provider,alert,receiver}_types.gowith constants for every provider/receiver type name (e.g.SlackProvider,GitHubReceiver). The notifier factory and the receiver validator switch on these constants — keep them in sync when adding a type. v1beta3is the currentAlert/Providerstorage version;Receivergraduated tov1. Conversion logic is code-generated.- CRD manifests under
config/crd/bases/and reference docs underdocs/api/<version>/notification.mdare generated bymake manifestsandmake api-docs. Do not hand-edit. config/crd/bases/gitrepositories.yamlis pulled at build time from source-controller viamake download-crd-deps(for envtest); do not commit changes to it.
All targets in the root Makefile. Tool versions pinned via CONTROLLER_GEN_VERSION, GEN_API_REF_DOCS_VERSION, SOURCE_VER. Go version tracks go.mod.
make tidy— tidy both the root andapi/modules.make fmt/make vet— run in both modules.make generate—controller-gen objectinapi/to refreshzz_generated.deepcopy.go.make manifests— regenerate CRDs and RBAC underconfig/crd/basesandconfig/rbac.make api-docs— regeneratedocs/api/v1,docs/api/v1beta2,docs/api/v1beta3markdown.make test— full pre-PR gate. Depends ontidy generate fmt vet manifests api-docs download-crd-deps install-envtest, then runsgo test ./...with envtest assets plusgo test ./...inapi/.GO_TEST_ARGSfor extra flags.make manager— buildsbin/manager.make run— runs the controller against~/.kube/config.make install/make uninstall/make deploy/make dev-deploy/make dev-cleanup— cluster workflows (IMGvariable).make docker-build/make docker-push— buildx image build (linux/amd64default viaBUILD_PLATFORMS).
Check go.mod and the Makefile for current dependency and tool versions. After changing API types, kubebuilder markers, or RBAC comments, regenerate and commit the results:
make generate manifests api-docsGenerated files (never hand-edit):
api/*/zz_generated.deepcopy.goconfig/crd/bases/notification.toolkit.fluxcd.io_*.yamlconfig/crd/bases/gitrepositories.yaml(downloaded from source-controller)config/rbac/role.yamldocs/api/**/*.md
No load-bearing replace directives beyond the standard api/ local replace.
Bump fluxcd/pkg/* modules as a set — version skew breaks go.sum. Run make tidy after any bump.
go fmt,go vet. All exported identifiers get doc comments, including provider-type-name constants. Non-trivial unexported helpers should also be documented.- Adding a notifier. Add a constant in
api/v1beta3/provider_types.go, extend the+kubebuilder:validation:Enumlist onProviderSpec.Type, implement the backend ininternal/notifier/<name>.goreturning thenotifier.Interface, and add its entry to thenotifiersmap plus axxxNotifierFuncininternal/notifier/factory.go. Then regenerate CRDs and docs. - Receiver verification.
internal/server/receiver_handlers.go::validateis the single entry point. EachReceiver.Spec.Typehas its own signature scheme — GitHub HMAC-SHA1/256 over the body withX-Hub-Signature, GitLab comparesX-Gitlab-Token, Bitbucket/Harbor/DockerHub/Quay/Nexus/GCR/ACR/CDEvents each have their own rules. Do not short-circuit these checks. Payloads are capped atmaxRequestSizeBytes = 3 * 1024 * 1024(3 MiB); reuse the constant. - Webhook path. Receivers are addressed under
apiv1.ReceiverWebhookPath(/hook/) with a per-resource digest. The path is indexed onReceiver.Status.WebhookPathviaWebhookPathIndexKey— do not bypass the index when looking up receivers. - Event dispatch.
EventServer.handleEventmatches each event against allAlertresources, applies inclusion/exclusion filters and CELeventMetadata, rate-limits duplicates through the memory store, then calls each notifier'sPost(ctx, event)with a 15s context timeout. HTTP-based notifiers inherit retry behavior frominternal/notifier/client.goviago-retryablehttp. - Proxy and TLS. Every HTTP notifier takes an optional
ProxyURLand*tls.ConfigfromnotifierOptions. Respect both. TLS material comes fromProviderSpec.CertSecretRefviapkg/runtime/secrets; never disable verification by default. - Service account / workload identity. Cloud notifiers (Azure Event Hub, Azure DevOps, Google Pub/Sub, GitHub App) use
fluxcd/pkg/authwith the shared token cache passed throughWithTokenCache/WithTokenClient. - Cross-namespace refs. Gated by
--no-cross-namespace-refs(ACL option); both servers honor it when resolvingAlert.Spec.ProviderRefandReceiver.Spec.Resources.
internal/controller/suite_test.gobootstrapsenvtestwith controller-runtime.make install-envtestdownloads the binaries intobuild/testbinviasetup-envtest;make testexportsKUBEBUILDER_ASSETS.- Reconciler tests (
alert_controller_test.go,provider_controller_test.go,receiver_controller_test.go) drive the envtest API server. Usetestdata/fixtures when possible. - Server tests (
event_server_test.go,event_handlers_test.go,receiver_handler_test.go,receiver_resource_filter_test.go,provider_commit_status_test.go) exercise the HTTP handlers directly withhttptest. - Notifier tests: one
<name>_test.goper backend standing up anhttptest.Server(or a fake SDK client for platforms that don't accept a custom URL) and asserting on the captured request. Match this pattern when adding a new notifier.- Run a single test:make test GO_TEST_ARGS='-run TestSlack'. - The
api/submodule has its own tests;make testruns them in a separatego testpass.
- Two Go modules. The root module imports
github.com/fluxcd/notification-controller/apivia a localreplace. Runtidy,fmt,vetin both when you touchapi/— the Makefile targets already do this. - CRD enum drift.
ProviderSpec.TypeandReceiverSpec.Typehave explicit+kubebuilder:validation:Enumlists. Adding a new constant in Go is not enough — extend the enum marker and rerunmake manifests, or the new type will be rejected by the API server. v1beta3is the storage version forAlert/Providerand introduced breaking-ish shape changes fromv1beta2(e.g.intervaldeprecated onProvider). New fields go onv1beta3(andv1forReceiver); do not add fields to older versions.- The
Alertreconciler is intentionally minimal — it exists to migratev1beta3alerts and manage finalizers. Actual dispatch happens ininternal/server/event_handlers.go. Do not move business logic into the reconciler. - The receiver webhook path is derived from a SHA-256 digest of the resource and a token from the referenced Secret; it is written to
Receiver.Status.WebhookPathand indexed withWebhookPathIndexKey. - The event server applies a token-bucket rate limiter keyed by event fingerprint (
eventKeyFunc) usingsethvargo/go-limiter— duplicate events within--rate-limit-interval(default 5m) are dropped before notifier dispatch. --events-addr :9090must match what source-controller, kustomize-controller, and helm-controller post to viaEVENT_ENDPOINT; changing the default breaks the GitOps Toolkit wiring.- Prefer existing
fluxcd/pkghelpers (runtime, secrets, auth, cache, masktoken) before adding new top-level dependencies.