Skip to content

feat(controller): add --min-poll-interval floor - #441

Open
henrysachs wants to merge 6 commits into
masterfrom
feat/min-poll-interval
Open

henrysachs wants to merge 6 commits into
masterfrom
feat/min-poll-interval

Conversation

@henrysachs

@henrysachs henrysachs commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Description of your changes

Fixes #428

Why

A managed resource can set crossplane.io/poll-interval to any value, for example 2s. crossplane-runtime raises the annotation only to the value of managed.WithMinPollInterval. No controller set that option, so the floor was 0. One resource could poll GitLab every 2 seconds and use up the rate limit of its ProviderConfig token.

What changed

  • New flag --min-poll-interval, env MIN_POLL_INTERVAL, default 10s. Annotation values below it are raised to it. --poll is not clamped. This matches --min-poll-interval in Crossplane core.
  • common.ControllerOptions embeds controller.Options and adds MinPollInterval. Every Setup function takes it.
  • common.NewReconciler builds each managed reconciler with the shared poll intervals, logger, event recorder and management-policies flag. The 39 namespaced controllers use it. The cluster copies are regenerated.
  • forbidigo forbids managed.NewReconciler outside pkg/common/controller.go. A new controller cannot skip the floor.
  • common.AddMRStateRecorder adds the state metrics recorder and skips it when MetricOptions or MRStateMetrics is nil. All controllers use it, and forbidigo forbids direct statemetrics.NewMRStateRecorder calls.
  • README documents --poll, --min-poll-interval and --sync.
  • common.NewReconciler also passes MetricOptions.MRMetrics to managed.WithMetricRecorder. main.go registered that recorder, but no controller used it, also on master. So the crossplane_managed_resource_first_time_to_reconcile, first_time_to_readiness, drift and deletion histograms stayed empty. The exists, ready and synced gauges already worked.

Commit 1 is the refactor and does not change behavior. Commit 2 adds the floor with a 30s default. Commit 3 changes the default to 10s.

Scope

  • Behavior change: a resource that sets the annotation below 10s now polls every 10s. No example in this repo sets the annotation. --min-poll-interval=0s restores the old behavior.
  • feat(provider): enforce a minimum for the crossplane.io/poll-interval annotation #428 proposed a 30s default. The maintainer chose 10s. Crossplane core defaults its floor to 1s.
  • Exported Setup* functions now take common.ControllerOptions instead of controller.Options.
  • ProviderConfig controllers take the new type but do not read MinPollInterval.

Merge order

Merge this PR last, after #301, #414, #426, #430, #432, #433, #434, #435, #436, #439, #442 and #443.

After those PRs land, I will rebase this PR and move their controllers to common.NewReconciler and common.ControllerOptions. On the rebased branch, the forbidigo rule fails the lint for any controller that still calls managed.NewReconciler, so none can be missed.

I have:

  • Read and followed Crossplane's contribution process.
  • Run make reviewable test to ensure this PR is ready for review. I ran its steps one by one, see below.

How has this code been tested

Verification

  • TestNewReconciler sets a 10s minimum and checks RequeueAfter. Annotation 2s gives 10s, 45s gives 45s, and no annotation gives PollInterval. With WithMinPollInterval removed from the helper, the 2s case fails with 2s.
  • TestNewReconciler also enables the management-policies feature. A resource with empty policies is then paused and gets no requeue. With WithManagementPolicies removed from the helper, that case fails with 10m0s.
  • A probe file that calls managed.NewReconciler fails forbidigo, and the message names common.NewReconciler.
  • TestNewReconciler checks that a reconcile adds one first_time_to_reconcile sample to the recorder from MetricOptions. With WithMetricRecorder removed from the helper, that case fails with 0. Metric options without a recorder do not panic.
  • Live /metrics check with one project Variable. The master code path showed only exists/ready/synced. With the fix, it showed first_time_to_reconcile 1, first_time_to_readiness 1, drift 1 after a GitLab-side edit, and deletion 2.
  • go test -p 4 ./... and golangci-lint run ./... pass. go generate -tags generate in apis and pkg and go mod tidy leave no diff.

Live test against a GitLab 19.4 test instance, with a kind cluster and the provider run out of cluster with --debug --poll 15s. A scratch project held three Variable resources: namespaced with poll-interval: 2s, namespaced without the annotation, and cluster-scoped with 2s. The interval is the time between two External resource is up to date debug lines for one resource.

Binary Floor Namespaced 2s Namespaced, no annotation Cluster 2s
master none 2.1–2.2s 15.1s 2.1–2.2s
commit 2 default (30s) 30.1–30.2s 15.1–15.2s 30.1–30.2s
commit 2 MIN_POLL_INTERVAL=5s 5.1–5.2s 15.1s 5.1–5.2s

The patched runs logged no reconcile errors. In the 5s run, a value changed in GitLab was reverted within 12s. That reconcile logs an update, not "up to date", so one gap reads 10.4s. Deleting the resources removed the variables from GitLab.

Commit 3 changes only the flag default, the README and the test values. I did not repeat the live run for it.

Not tested: setting the flag through a DeploymentRuntimeConfig in a real Crossplane install.

…iler

Lint forbids direct managed.NewReconciler calls.

Signed-off-by: Henry Sachs <henry.sachs@deutschebahn.com>
…otation

The crossplane.io/poll-interval annotation let any resource poll GitLab
as often as it asked. --min-poll-interval (env MIN_POLL_INTERVAL, default
30s) raises shorter annotation values to the floor. --poll is unchanged.

Closes #428

Signed-off-by: Henry Sachs <henry.sachs@deutschebahn.com>
Signed-off-by: Henry Sachs <henry.sachs@deutschebahn.com>
Signed-off-by: Henry Sachs <henry.sachs@deutschebahn.com>
main.go registers the MR metric recorder, but no reconciler used it, so the first_time_to_reconcile, first_time_to_readiness, drift and deletion histograms were never recorded.

Signed-off-by: Henry Sachs <henry.sachs@deutschebahn.com>
common.AddMRStateRecorder skips the recorder when MetricOptions or
MRStateMetrics is nil. forbidigo forbids direct NewMRStateRecorder.

Signed-off-by: Henry Sachs <henry.sachs@deutschebahn.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(provider): enforce a minimum for the crossplane.io/poll-interval annotation

1 participant