Skip to content

fix(fluxcd): omit empty distribution.artifact in FluxInstance for guest clusters - #3284

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
cozystack:mainfrom
IvanHunters:fix/guest-fluxcd-distribution-artifact
Jul 14, 2026
Merged

fix(fluxcd): omit empty distribution.artifact in FluxInstance for guest clusters#3284
Aleksei Sviridkin (lexfrei) merged 1 commit into
cozystack:mainfrom
IvanHunters:fix/guest-fluxcd-distribution-artifact

Conversation

@IvanHunters

@IvanHunters IvanHunters commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Guest Kubernetes clusters (apps/kubernetes) with the fluxcd addon enabled never became
Ready. The flux-instance template renders spec.distribution.artifact unconditionally,
unlike the sibling optional fields (artifactPullSecret, imagePullSecret, variant, which
are all {{- if }}-guarded). The umbrella default is empty (artifact: "") so the operator
uses its embedded manifests, which is the intended air-gapped default (#964, and the upstream
recommendation). But an empty value renders as YAML null, and the flux-operator v0.50.0 CRD
types distribution.artifact as a non-nullable string (pattern ^oci://.*$), so the API
rejects the FluxInstance.

Result: the addon's HelmRelease looped install/uninstall (remediation.retries: -1), the
parent kubernetes release stayed InProgress, and the cluster CR was stuck Ready=Unknown.

This guards the field so an empty value is omitted entirely. Behaviour:

  • empty (default) -> field omitted -> operator falls back to embedded manifests: air-gapped
    default preserved
    , and the CRD accepts it (artifact is not in distribution.required);
  • non-empty -> renders as before; valuesOverride still applies.

The template is a vendored subchart (make update runs helm pull), so the change is carried
as patches/guard-distribution-artifact.diff and applied by the update target, matching the
convention of the sibling fluxcd-operator package (patch -p1 < patches/*.diff).

Verified with helm template packages/system/fluxcd:

  • default values -> no artifact: line (before this PR: artifact: null);
  • --set flux-instance.instance.distribution.artifact=oci://example.com/x:1 -> renders.

The affected path is off by default (addons.fluxcd.enabled: false) and not exercised by the
in-repo CI values, which is why default e2e stayed green.

Closes #3283

Release note

fix(fluxcd): guest Kubernetes clusters with the fluxcd addon enabled no longer fail to install; an empty distribution artifact is now omitted so the operator uses its embedded manifests (air-gapped default preserved) instead of rendering an invalid null

Summary by CodeRabbit

  • Bug Fixes
    • Prevented Flux instance configuration from including an empty distribution artifact value, which could cause validation failures.
    • Improved the generated FluxInstance manifest so the artifact field is omitted entirely when no distribution artifact is provided.

@github-actions github-actions Bot added area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/bug Categorizes issue or PR as related to a bug labels Jul 14, 2026
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The FluxCD template now conditionally renders spec.distribution.artifact, and the chart update workflow applies the matching patch after unpacking the chart.

Changes

FluxCD artifact rendering

Layer / File(s) Summary
Guard distribution artifact template
packages/system/fluxcd/patches/guard-distribution-artifact.diff, packages/system/fluxcd/charts/flux-instance/templates/instance.yaml, packages/system/fluxcd/Makefile
The artifact field is emitted only when configured, and the update target applies the corresponding patch to the unpacked chart.

Estimated code review effort: 1 (Trivial) | ~5 minutes

Suggested reviewers: androndo, kvaps, lllamnyp, myasnikovdaniil, sircthulhu

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The patch matches issue #3283 by guarding spec.distribution.artifact so empty values are omitted and valid non-empty values still render.
Out of Scope Changes check ✅ Passed The changes are scoped to the FluxCD artifact rendering fix and the patch application needed to vendor it.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: omitting an empty FluxInstance distribution.artifact for guest clusters.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size/XS This PR changes 0-9 lines, ignoring generated files label Jul 14, 2026
@IvanHunters IvanHunters added the backport Should change be backported on previous release label Jul 14, 2026
@IvanHunters
IvanHunters force-pushed the fix/guest-fluxcd-distribution-artifact branch from cc3324b to 8a5ac8c Compare July 14, 2026 09:02
@IvanHunters IvanHunters changed the title fix(fluxcd): set default distribution.artifact for guest clusters fix(fluxcd): omit empty distribution.artifact in FluxInstance for guest clusters Jul 14, 2026
@github-actions github-actions Bot added size/S This PR changes 10-29 lines, ignoring generated files and removed size/XS This PR changes 0-9 lines, ignoring generated files labels Jul 14, 2026
@IvanHunters
IvanHunters force-pushed the fix/guest-fluxcd-distribution-artifact branch 2 times, most recently from 2dfaa08 to 9bd727e Compare July 14, 2026 09:14
@IvanHunters
IvanHunters marked this pull request as ready for review July 14, 2026 09:36
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a deployment failure in guest Kubernetes clusters where the fluxcd addon would remain in an unready state. By conditionally omitting the distribution artifact field, the operator is now able to correctly fall back to its embedded manifests, satisfying CRD validation requirements while preserving air-gapped default behavior.

Highlights

  • FluxInstance Configuration: Guarded the spec.distribution.artifact field in the FluxInstance template to prevent it from rendering as an invalid null value when empty.
  • Build Automation: Updated the Makefile to automatically apply the new patch during the update process, ensuring the fix is consistently maintained.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Ignored Files
  • Ignored by pattern: **/*.diff (1)
    • packages/system/fluxcd/patches/guard-distribution-artifact.diff
  • Ignored by pattern: **/charts/** (1)
    • packages/system/fluxcd/charts/flux-instance/templates/instance.yaml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request updates the packages/system/fluxcd/Makefile to apply a patch (patches/guard-distribution-artifact.diff) during the update target. Feedback suggests removing the --no-backup-if-mismatch option from the patch command, as it is a GNU extension that is not supported by the default BSD patch utility on macOS, which would cause the build to fail on that platform.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

update:
rm -rf charts
helm pull oci://ghcr.io/controlplaneio-fluxcd/charts/flux-instance --untar --untardir charts
patch --no-backup-if-mismatch -p1 < patches/guard-distribution-artifact.diff

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The --no-backup-if-mismatch option is a GNU patch extension and is not supported by the default BSD patch utility on macOS. This will cause make update to fail on macOS with patch: unrecognized option '--no-backup-if-mismatch'.

To ensure portability across development environments and to align with the convention used in other packages (like fluxcd-operator), we should omit this option and use standard patch -p1.

	patch -p1 < patches/guard-distribution-artifact.diff

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping --no-backup-if-mismatch here: it is the convention across all 12 vendored-patch Makefiles under packages/system/*, including the sibling fluxcd-operator (patch --no-backup-if-mismatch -p1 < patches/*.diff). Dropping it only from this package would make it the sole outlier, and make update in this repo runs under GNU patch. A repo-wide move to BSD-portable flags would be better as a separate PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the diagnosis holds under checking and the fix does what it claims.

Business context: guest Kubernetes clusters with the fluxcd addon enabled never reach Ready, because the vendored flux-instance chart renders spec.distribution.artifact unconditionally and the umbrella's empty default turns into YAML null, which the CRD rejects (#3283).

I verified each load-bearing claim rather than taking the PR body's word for it:

  • The render. Templating packages/system/fluxcd with default values on main emits a bare artifact: (YAML null); on this branch the key is absent entirely. The guard does exactly what it says.
  • The CRD. flux-operator v0.50.0 types distribution.artifact as type: string with pattern: ^oci://.*$ and no nullable, and distribution.required is [registry, version]. So null is genuinely rejected and omitting the key is genuinely valid — both halves of the diagnosis are sound.
  • The blast radius. packages/system/fluxcd is referenced only from packages/core/platform/sources/kubernetes-application.yaml (as kubernetes-fluxcd) and appears in no management-cluster bundle. The management cluster never renders this chart, so it really is unaffected — which is the obvious "then why isn't the management cluster broken too?" objection, and it survives.
  • The patch mechanics. The diff applies at -p1 from the package directory, both the vendored template and the patch file are committed, and the update target matches the sibling fluxcd-operator package's convention verbatim.

Non-blocking follow-ups

  1. The root cause is upstream, and nothing here points at it. controlplaneio-fluxcd/charts/flux-instance renders artifact: unconditionally while guarding every sibling optional field (artifactPullSecret, imagePullSecret, variant) — that is an upstream template bug, and there is no way to express "no artifact" through the chart as shipped. Carrying it as a local patch is the right immediate move, since guest clusters are hard-broken today. But no upstream issue or PR is referenced anywhere in this change, which makes the patch permanent by default. Please file it upstream and link it from the patch file or the Makefile, so the next person to bump this chart knows when the hunk can be dropped rather than re-resolved.

  2. Nothing pins the rendered shape, which is the entire content of this fix. packages/system/fluxcd has no tests/ directory and no test: target, so there is zero automated coverage here — and the PR itself notes the affected path is off by default and unexercised by CI, while E2E did not run at all on this fork PR. Two helm-unittest assertions cover it: notExists: spec.distribution.artifact under default values, and equal when instance.distribution.artifact is set. That also guards the patch: if a future make update silently loses the hunk, a test catches it, whereas today nothing would.

  3. helm pull in the update target pins no --version, so make update always fetches the latest flux-instance. Pre-existing, but it enlarges the new patch's exposure — a hunk against an unpinned moving target. Worth pinning while this Makefile is open.

On CI: the red Build Talos is a fork-PR artifact, not a code failure. The image built fine; only the registry push was denied (denied: Anonymous users are only allowed read access on public repos), because fork PRs don't receive the registry credentials. It does not gate this review.

update:
rm -rf charts
helm pull oci://ghcr.io/controlplaneio-fluxcd/charts/flux-instance --untar --untardir charts
patch --no-backup-if-mismatch -p1 < patches/guard-distribution-artifact.diff

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two things worth doing while this line is being added.

Point the patch at its upstream. The bug being patched is upstream's: flux-instance's instance.yaml renders artifact: unconditionally while guarding artifactPullSecret, imagePullSecret and variant, so there is no way to express "no artifact" through the chart. Carrying the fix locally is right for now — guest clusters are broken today — but with no upstream issue or PR referenced anywhere, this patch is permanent by default. A link in the patch file or here tells whoever next bumps the chart whether to drop the hunk or re-resolve it.

helm pull above pins no --version, so make update always fetches the latest chart. That was already true, but a hunk against an unpinned moving target is a more fragile thing than a hunk against a pinned one. Worth pinning the version while you are here.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the check. Addressed both:

  • Pinned helm pull --version 0.50.0 (matches the vendored Chart.yaml), so the hunk targets a stable chart version instead of latest.
  • Added an upstream-reference header to the patch (chart source + "drop this hunk once upstream guards the field").

Not filed upstream yet — can open a one-line guard PR against controlplaneio-fluxcd/charts to retire this patch on a future bump.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified both: the pin matches the vendored Chart.yaml (0.50.0), and the header explains the bug well.

On --no-backup-if-mismatch — agreed, keep it. It is used in 13 of the vendored-patch Makefiles, including the sibling fluxcd-operator, so dropping it only here would make this package the sole outlier for no gain. A BSD-portable sweep is worth doing, but as its own PR across all of them.

Please do open the upstream guard PR. Right now the header says "not yet reported upstream", which makes the patch permanent by default: nothing tells a future bump that the hunk can go away, so it will be carried forever by inertia. A one-line guard against controlplaneio-fluxcd/charts, with its link in the header, turns this into a temporary carry with a retire condition someone can actually check. That is the shape we want for every vendored patch.

The flux-instance template rendered spec.distribution.artifact
unconditionally, unlike the sibling optional fields (artifactPullSecret,
imagePullSecret, variant). The umbrella default is empty to use the
operator's embedded manifests (the air-gapped default from cozystack#964), but an
empty value renders as YAML null. In the flux-operator v0.50.0 CRD the
field is optional yet typed 'string' with pattern ^oci://.*$ and is not
nullable, so the rendered null (like an empty string) fails schema
validation and the API rejects the FluxInstance.

Guest Kubernetes clusters with the fluxcd addon enabled could therefore
never install it: the sub-release looped install/uninstall, the parent
kubernetes release stayed InProgress, and the cluster never reached Ready.

Guard the field so an empty value is omitted (the operator falls back to
its embedded manifests, preserving the air-gapped default) while a
non-empty artifact still renders. The template lives in the vendored
subchart, so the change is carried as a patch applied by 'make update'.

Signed-off-by: Ivan Okhotnikov <xorokhotnikov@gmail.com>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — re-reviewing after the force-push (which dismissed my earlier approval). The delta addresses two of the three follow-ups, and I verified both actually work rather than taking the diff at face value.

What changed since my last pass (9bd727e5da47b70e): the helm pull in the update target is now pinned to --version 0.50.0, and the patch file gained a header explaining the upstream bug and stating the hunk should be dropped once upstream guards the field.

Both of those are exactly the class of change that can silently break make update, so I replayed the target end-to-end rather than eyeballing it:

  • patch still applies. The new #-prefixed header sits ahead of the diff --git line; patch skips leading garbage, so it applies cleanly — exit 0, no .rej, no .orig.
  • The pin matches reality. --version 0.50.0 is the same version already vendored under charts/flux-instance (Chart.yaml: version: 0.50.0), so the pin freezes the current state rather than silently moving the chart on the next update.
  • No vendored drift. I regenerated charts/flux-instance/templates/instance.yaml from a fresh helm pull 0.50.0 + the patch, and it is byte-identical to the file committed here. What is in the tree is what make update produces.

Everything I verified last time still holds: the render omits artifact under default values, the CRD types it as a non-nullable string and does not list it in distribution.required, and the chart ships only to guest clusters (absent from every management-cluster bundle).

Remaining follow-up (non-blocking, unchanged)

Nothing pins the rendered shape. packages/system/fluxcd still has no tests/ directory and no test: target, so the fix — which is a render-shape change — has zero automated coverage, on a path the PR itself notes is off by default and unexercised by CI. Two helm-unittest assertions cover it: notExists: spec.distribution.artifact under default values, and equal when instance.distribution.artifact is set. That would also guard the patch itself: if a future make update loses the hunk, a test catches it; today nothing would.

The patch header is a good compromise on the upstream point — it tells the next maintainer what the hunk is for and when to drop it. Filing it upstream is still worth doing so the hunk can actually go away.

On CI: Build Talos remains red for the fork-PR reason (image builds, registry push is denied without secrets), not for anything in this diff.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 5d881f1 into cozystack:main Jul 14, 2026
16 of 27 checks passed
@github-actions

Copy link
Copy Markdown

myasnikovdaniil added a commit that referenced this pull request Jul 15, 2026
…in FluxInstance for guest clusters (#3292)

# Description
Backport of #3284 to `release-1.5`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) backport Should change be backported on previous release kind/bug Categorizes issue or PR as related to a bug size/S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Guest Kubernetes fluxcd addon fails: FluxInstance rendered with null spec.distribution.artifact

2 participants