Skip to content

Sync Flux objects with their whole source chain, like flux reconcile --with-source - #2110

Merged
nadaverell merged 6 commits into
mainfrom
feature/flux-sync-with-generated-chart
Oct 14, 2026
Merged

nadaverell merged 6 commits into
mainfrom
feature/flux-sync-with-generated-chart

Conversation

@nadaverell

@nadaverell nadaverell commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

"Sync with source" on a Flux object is meant to do what flux reconcile <kind> --with-source does: refresh what the object builds from, then the object. On main it didn't:

  • On a HelmRelease using spec.chartRef it failed with "no source reference found", although the button is offered.
  • On an inline spec.chart HelmRelease it refreshed the HelmRepository but not the HelmChart Flux generates from it. Refreshing the index alone doesn't re-resolve the chart version; the chart reconcile does.
  • A Kustomization backed by a Bucket failed with "unknown source kind: Bucket", and HelmChart and Bucket couldn't be reconciled, suspended or resumed at all.
  • Every Flux operation patched a hard-coded API version, so on Flux releases that serve an older version (OCIRepository is v1beta2 before Flux 2.6, Bucket before 2.4) the patch failed with "the server could not find the requested resource".
  • A suspended source got the reconcile request anyway, and Radar reported success for a sync its controller ignores.

What changed

The reconcile chain follows the Flux CLI. Objects are reconciled upstream first, then the consumer:

Consumer Reconciled, in order
Kustomization its spec.sourceRef (GitRepository, OCIRepository, Bucket), then the Kustomization
HelmRelease, inline spec.chart the chart's source (HelmRepository, GitRepository, Bucket), the generated HelmChart <release namespace>-<release name> in the source's namespace, then the release
HelmRelease, spec.chartRef to a HelmChart the chart's own source, the chart, then the release
HelmRelease, spec.chartRef to an OCIRepository the OCIRepository, then the release
any, ExternalArtifact source only the consumer; another controller produces the artifact, and the response says so

The generated chart's name comes from the current spec, as the CLI computes it, not from status.helmChart: after a source change that field still names the previous chart until helm-controller replaces it.

Every link is read and checked before anything is written. A suspended (spec.suspend) or Terminating object anywhere in the chain fails the request with 409 and names that object, instead of annotating objects whose controller won't act. A link the caller can't read fails with that error (403, 404, or the read failure), since there is then no way to check it or to follow a chart to its source. Unlike the CLI, Radar requests the reconciles without waiting for each step to finish; the response lists the chain it requested.

Flux objects are patched at the version the cluster serves. gitops.FluxGVRResolver resolves a Flux kind through API discovery (the most stable served version). Once the discovery cache expires, one refresh runs in the background, so an action never waits on discovery; actions use the cached version until that refresh succeeds, which is when a Flux upgrade that retired a version is picked up; Reconcile, Suspend/Resume, Sync with source, the MCP manage_gitops tool and the GitOps destination and write-evidence reads all use it. Without a resolver the static table still applies.

HelmChart and Bucket are valid targets for Reconcile and Suspend/Resume through the API and MCP. No UI button offers them yet.

The success toast shows the server's message, so the user sees what was reconciled, or that an ExternalArtifact source was skipped.

pkg/gitops.SyncFluxWithSource takes an optional resolver as a trailing variadic argument, so existing callers compile unchanged and keep the static versions; FluxKindEntry.Served applies a resolver to the entry passed to ReconcileFlux and SetFluxSuspend.

Testing

Unit tests cover each row of the table, including the order of the patches; a stale status.helmChart pointing at another chart; a missing generated chart (404, nothing patched); a suspended release, chart and repository (409, nothing patched); a chart or repository that can't be read (403 or timeout, nothing patched); discovery that changes version after its cache expires, and a stalled discovery refresh that must neither delay the action nor start more than one refresh; and patching a v1beta2-only OCIRepository through the resolver, where the static version fails. Removing the chart's-source step, the suspension check, the read check, the discovery refresh, the resolver or the spec-derived chart name each fails a test.

Live, on a kind cluster running Flux whose OCIRepository API is v1beta2, each case was checked against status.lastHandledReconcileAt on every object in the chain:

Case main this PR
HelmRelease, chartRef to an OCIRepository 500 "no source reference found" OCIRepository, then release
Kustomization from a Bucket 500 "unknown source kind: Bucket" Bucket, then Kustomization
HelmRelease, inline chart HelmRepository and release; chart untouched HelmRepository, HelmChart, then release
Kustomization from a GitRepository GitRepository, then Kustomization same
Reconcile / Suspend / Resume a HelmChart or Bucket 400 200; spec.suspend toggled
Inline release whose HelmRepository is suspended (not checked) 409 naming the repository; nothing patched

Not covered live: an ExternalArtifact source (this Flux version has no ExternalArtifact API), and the toast in the browser (the message is checked at the API).

@nadaverell
nadaverell requested a review from hisco as a code owner October 14, 2026 10:57
Base automatically changed from feature/relationship-flux-sources to main October 14, 2026 11:06
"Sync with source" read only spec.chart.spec.sourceRef. On a HelmRelease
using spec.chartRef it failed with "no source reference found", and on an
inline release it refreshed the repository instead of the generated
HelmChart, which is what re-resolves the chart version. Kustomizations
backed by a Bucket failed too: Bucket and HelmChart weren't known kinds.

Sources now come from the shared Flux source parser and follow
`flux reconcile --with-source`: an inline release reconciles its generated
HelmChart (status.helmChart, or Flux's generated name before it is
recorded), a chartRef release reconciles the referenced HelmChart or
OCIRepository, and an ExternalArtifact, which another controller produces,
leaves only the release to reconcile.
@nadaverell
nadaverell force-pushed the feature/flux-sync-with-generated-chart branch from 6e36f5c to 650f89f Compare October 14, 2026 11:11
â€Ķed version

Sync with source on a HelmRelease now reconciles the chart's own source before
the chart, as `flux reconcile hr --with-source` does, so a new chart version in
the repository is picked up. The generated chart is named from the current spec
rather than status.helmChart, which names the previous chart while the
controller replaces it. A suspended or Terminating object anywhere in the chain
fails the request with 409 before anything is written.

Flux operations resolve each kind to the version the cluster serves through
discovery, so OCIRepository (v1beta2 before Flux 2.6), Bucket and the Helm
source kinds work on older Flux installs. The success toast shows the server's
message, which names what was reconciled and when an ExternalArtifact was
skipped.
A source that can't be read (Forbidden, timeout, missing) now fails Sync with
source before anything is written: without the object there is no suspend or
deletion state to check and no chart sourceRef to follow. ServedGVR refreshes
expired discovery before resolving, so a Flux CRD upgrade that retires a
version is picked up by write paths on their own. The resolver argument to
SyncFluxWithSource is optional, so existing callers keep compiling.
@nadaverell nadaverell changed the title Sync a HelmRelease with its chart, like flux reconcile --with-source Sync Flux objects with their whole source chain, like flux reconcile --with-source Oct 14, 2026
â€Ķsions

A slow discovery endpoint for an unrelated API could otherwise use up a Flux
action's deadline before it reached the healthy Flux endpoint. The caller gets
the cached version; only the first action after the cache expires can still see
a version a CRD upgrade just retired.
Calls arriving while a refresh is in flight return the cached version without
starting another, so a stalled discovery endpoint doesn't accumulate a waiting
goroutine per call. The refresh tests no longer race their own background
refresh against the fake's response.
@nadaverell
nadaverell merged commit 980d73c into main Oct 14, 2026
9 checks passed
@nadaverell
nadaverell deleted the feature/flux-sync-with-generated-chart branch October 14, 2026 17:23
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.

1 participant