Skip to content

feat(kubernetes)!: remove FluxCD addon - #3379

Merged
Aleksei Sviridkin (lexfrei) merged 2 commits into
mainfrom
fix/devendor-flux-operator-agpl
Aug 20, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 2 commits into
mainfrom
fix/devendor-flux-operator-agpl

Conversation

@kvaps

@kvaps Andrei Kvapil (kvaps) commented Jul 20, 2026 •

Copy link
Copy Markdown
Member

What this PR does

The Kubernetes app's optional addons.fluxcd toggle deployed a Flux Operator + FluxInstance into tenant clusters, served by two vendored Helm charts (packages/system/fluxcd-operator, packages/system/fluxcd). Those charts are AGPL-3.0 — the last AGPL content in this Apache-2.0 repository and a governance blocker for CNCF Incubation.

The management cluster does not depend on them: it runs classic Flux via internal/fluxinstall, and migration 21 already removed the Flux Operator from it. The addon defaulted to enabled: false. Rather than maintain an AGPL payload for an unused feature, this PR removes it entirely as a breaking change:

  • Delete both system packages and their platform source entries.
  • Remove the addons.fluxcd HelmReleases, values, Go types and generated schema/README from the Kubernetes app.
  • Add migration 54: for any tenant that had the addon enabled, the FluxCD HelmReleases in the management cluster are suspended, stripped of their Flux finalizer and deleted, so the delete does not trigger a Helm uninstall inside the tenant cluster. The Flux release already running inside the tenant keeps running untouched (mirrors the orphan idiom of migration 29). It runs as a pre-upgrade hook, before the addon-less Kubernetes chart reaches the tenant HelmReleases.

After the change the only affero/AGPL matches under packages/ are comments in packages/system/monitoring explaining why Grafana is not rebuilt — no AGPL payload ships.

Verification: the Kubernetes app helm-unittests (181) and platform tests (88) pass; make generate was run with the CI-pinned cozyvalues-gen v1.6.0 (schema, README, Go types, CRD); migrations-target-check and the cozystack-version-stamp bats pass; the api/apps/v1alpha1 module builds.

Downstream repositories

This removes a user-facing addon and drops the addons.fluxcd field from the Kubernetes app's values.schema.json. Boxes are left unticked pending follow-ups (not opened from this PR):

  • terraform-provider-cozystack — models the Kubernetes app schema; the removed addons.fluxcd field should be checked/dropped there.
  • website — any hand-written mention of the FluxCD addon in the tenant Kubernetes docs should be removed (the managed-app reference regenerates from the README).

Release note

feat(kubernetes)!: remove the FluxCD addon (`addons.fluxcd`). It relied on AGPL-3.0 vendored charts and was disabled by default; tenant clusters that had enabled it keep their running Flux release via an automatic upgrade migration. To remove that orphaned Flux from such a tenant, uninstall its `flux-operator`/`flux-instance` Helm releases inside the tenant cluster by hand.

Summary by CodeRabbit

  • Breaking Changes

    • Removed FluxCD addon support from tenant cluster configuration, including the Kubernetes tenant CRD schema, chart values schema, defaults, and published “Cluster Addons” parameters.
    • FluxCD-related Helm manifests are no longer rendered as part of the Kubernetes package.
    • FluxCD configuration is no longer accepted for new or updated tenant clusters.
  • Migration

    • During upgrade, existing FluxCD Helm releases are cleaned up automatically by suspending them, clearing finalizers, and deleting the management-side releases.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Removes the FluxCD addon from Kubernetes API types, chart values, schemas, platform sources, and system package references. Adds migration 54→55 cleanup for existing FluxCD HelmReleases and advances the migration target.

Changes

FluxCD addon removal

Layer / File(s) Summary
API and values contract removal
api/apps/v1alpha1/kubernetes/*, packages/apps/kubernetes/*, packages/system/kubernetes-rd/cozyrds/kubernetes.yaml
Removes the FluxCD addon type and deepcopy methods, configuration defaults, schema entries, documentation, and application schema ordering.
Platform component and migration updates
packages/core/platform/...
Stops packaging FluxCD components, adds migration 54→55 cleanup for existing FluxCD HelmReleases, and advances the migration target.
System package cleanup
packages/system/fluxcd*/*, packages/system/kamaji/Makefile
Removes FluxCD documentation references and narrows a related patch comment.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested reviewers: lexfrei

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main breaking change: removal of the Kubernetes FluxCD addon.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/devendor-flux-operator-agpl

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 area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/cleanup Categorizes issue or PR as related to cleanup of code, process, or technical debt size/M This PR changes 30-99 lines, ignoring generated files labels Jul 20, 2026
@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch from 85f2765 to 42a316b Compare July 20, 2026 17:52
@kvaps Andrei Kvapil (kvaps) changed the title refactor(fluxcd): de-vendor AGPL flux-operator and flux-instance charts feat(kubernetes)!: remove FluxCD addon Jul 20, 2026
@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files and removed size/M This PR changes 30-99 lines, ignoring generated files labels Jul 20, 2026
@kvaps
Andrei Kvapil (kvaps) marked this pull request as ready for review July 20, 2026 18:45
@dosubot dosubot Bot added area/kubernetes Issues or PRs related to the tenant Kubernetes app kind/breaking-change Indicates the change introduces a breaking API or behaviour change labels Jul 20, 2026
@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 removes the optional FluxCD addon from the Kubernetes application to eliminate AGPL-3.0 licensed components, addressing a key governance requirement for CNCF Incubation. The change includes a migration script to ensure that existing tenant clusters with the addon enabled maintain their current Flux installation by orphaning the management-side HelmReleases, preventing unintended uninstalls.

Highlights

  • Removal of FluxCD Addon: Removed the optional addons.fluxcd toggle and all associated configurations from the Kubernetes application.
  • License Compliance: Deleted the packages/system/fluxcd-operator and packages/system/fluxcd packages to remove AGPL-3.0 licensed content, facilitating CNCF Incubation.
  • Migration 54: Introduced a new migration script to safely orphan existing FluxCD HelmReleases for tenant clusters that previously had the addon enabled.
  • Cleanup: Updated documentation, schema files, and platform source entries to reflect the removal of the FluxCD component.
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 (3)
    • packages/system/fluxcd-operator/patches/kubernetesEnvs.diff
    • packages/system/fluxcd-operator/patches/networkPolicy.diff
    • packages/system/fluxcd/patches/guard-distribution-artifact.diff
  • Ignored by pattern: **/charts/** (32)
    • packages/system/fluxcd-operator/charts/flux-operator/.helmignore
    • packages/system/fluxcd-operator/charts/flux-operator/Chart.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/README.md
    • packages/system/fluxcd-operator/charts/flux-operator/templates/NOTES.txt
    • packages/system/fluxcd-operator/charts/flux-operator/templates/_helpers.tpl
    • packages/system/fluxcd-operator/charts/flux-operator/templates/admin-clusterrole.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/aggregate-clusterrole.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/crds.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/deployment.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/flowschema.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/httproute.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/ingress.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/network-policy.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/networkpolicy.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/service.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/serviceaccount.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/servicemonitor.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/web-clusterrole.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/web-secret.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/templates/web-standard-roles.yaml
    • packages/system/fluxcd-operator/charts/flux-operator/values.schema.json
    • packages/system/fluxcd-operator/charts/flux-operator/values.yaml
    • packages/system/fluxcd/charts/flux-instance/.helmignore
    • packages/system/fluxcd/charts/flux-instance/Chart.yaml
    • packages/system/fluxcd/charts/flux-instance/README.md
    • packages/system/fluxcd/charts/flux-instance/templates/NOTES.txt
    • packages/system/fluxcd/charts/flux-instance/templates/_helpers.tpl
    • packages/system/fluxcd/charts/flux-instance/templates/healthcheck.yaml
    • packages/system/fluxcd/charts/flux-instance/templates/instance.yaml
    • packages/system/fluxcd/charts/flux-instance/templates/serviceaccount.yaml
    • packages/system/fluxcd/charts/flux-instance/values.schema.json
    • packages/system/fluxcd/charts/flux-instance/values.yaml
  • Ignored by pattern: **/zz_generated.*.go (1)
    • api/apps/v1alpha1/kubernetes/zz_generated.deepcopy.go
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 removes the FluxCD addon from the Kubernetes application as a breaking change, deleting the associated packages, templates, schemas, and documentation. To handle existing installations, a new migration script (54 -> 55) is introduced to orphan the FluxCD HelmReleases in tenant clusters during upgrade. Feedback on the migration script highlights three issues: first, the use of pipefail in a #!/bin/sh script is a bashism that will cause failures on POSIX-compliant shells like dash; second, the JSON patch to remove /metadata/finalizers can fail if the field is already absent, so a merge patch setting it to null is recommended; and third, using printf instead of echo when piping the list of HelmReleases prevents an unnecessary loop execution when no releases are found.

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.

# reaches the tenant HelmReleases, so the finalizer is removed before any
# pruning can fire.

set -euo pipefail

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.

high

The shell script uses set -euo pipefail with a #!/bin/sh shebang. However, pipefail is a non-standard shell option (a bashism) and is not supported by POSIX-compliant shells like dash (which is the default /bin/sh on Debian and Ubuntu). Running this script in an environment where /bin/sh points to dash will cause it to fail immediately with set: pipefail: bad option, blocking the platform upgrade. Since the pipeline exit status is already safely handled by the || true guard in the command substitution, we can safely remove pipefail and use set -eu.

Suggested change
set -euo pipefail
set -eu

Comment on lines +41 to +42
kubectl -n "$ns" patch helmrelease "$name" --type=json \
-p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true

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

Using a JSON patch with the remove operation on /metadata/finalizers will fail if the finalizers field is already absent or null on the HelmRelease object. Although the error is ignored via || true, it can clutter the migration logs with unnecessary errors. Using a merge patch to set metadata.finalizers to null is safer, idempotent, and will always succeed regardless of whether the finalizers field currently exists.

Suggested change
kubectl -n "$ns" patch helmrelease "$name" --type=json \
-p='[{"op":"remove","path":"/metadata/finalizers"}]' 2>/dev/null || true
kubectl -n "$ns" patch helmrelease "$name" --type=merge \
-p '{"metadata":{"finalizers":null}}' 2>/dev/null || true

or .spec.chartRef.name == "cozystack-kubernetes-application-kubevirt-kubernetes-fluxcd-operator")
| "\(.metadata.namespace) \(.metadata.name)"' || true)

echo "$hrs" | while read -r ns name; do

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.

low

If hrs is empty, echo "$hrs" will output a blank line, causing the while read loop to execute once with empty variables (which is then skipped by the [ -n "$ns" ] check). Using printf '%s' instead of echo avoids generating this extra blank line when hrs is empty, preventing the loop from executing unnecessarily.

Suggested change
echo "$hrs" | while read -r ns name; do
printf '%s' "$hrs" | while read -r ns name; do

@kvaps

Copy link
Copy Markdown
Member Author

Kingdon Barrett (@kingdonb) FYI — alongside this removal we have asked CNCF for guidance on a Debian-style "contrib" repository (ServiceDesk ticket CNCFSD-3698): Apache-2.0 integration charts under project governance, with non-Apache components such as ControlPlane's flux-operator fetched by the user's cluster from vendor sources at deploy time.

That would give the flux-operator-based delivery of Flux a proper home — including satisfying the platform's Flux dependency from ControlPlane's enterprise distribution as an opt-in alternative, with vanilla Apache-2.0 Flux staying the self-contained default in main (Debian's alternatives model). We would be glad to have you maintain that integration if you are interested; we will follow up once CNCF responds.

Andrei Kvapil (kvaps) added a commit that referenced this pull request Jul 20, 2026
The fluxcd addon pin was a holdover from the #3150-era model where app
charts rendered nested HelmReleases into the target cluster and needed an
in-cluster helm-controller. Consumers now keep their releases on the
management cluster and remote-apply via spec.kubeConfig, so the management
Flux resolves charts and applies manifests — nothing inside the ComputePlane
reconciles HelmReleases. This also decouples the module from the FluxCD
addon removal (#3379). certManager and ingressNginx stay pinned: Certificate
and Ingress objects land inside the cluster and need their controllers
there.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
@kingdonb

Copy link
Copy Markdown
Member

I was worried that AGPL would be a blocker somewhere before too long - Flux distributes the operator as AGPL to prevent hyperscalers from bundling it as a closed source product, not to prevent GPL or Apache projects from integrating. I think I need to review the implications of that exclusion, but it sounds like there is a plan.

@kingdonb

Copy link
Copy Markdown
Member

Yes, please sign me up - I'll be glad to help part to maintain the contrib repo of external or differently licensed projects 🎉

@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch from 42a316b to 1e2d87d Compare July 21, 2026 15:45
@github-actions github-actions Bot added the kind/feature Categorizes issue or PR as related to a new feature label Jul 21, 2026

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed with the cozy-review methodology, focusing on the breaking-change aspects. Verdict: LGTM with non-blocking notes. No blocking findings. The FluxCD addon removal is complete and consistent, and the upgrade path for existing clusters is safe.

Breaking-change review

  • Migration 54 removes the finalizer and deletes the child tenant HelmReleases by two hardcoded chartRef.name values that match the removed packages/apps/kubernetes/templates/helmreleases/fluxcd.yaml template byte-for-byte.
  • Ordering is guaranteed: migration-hook.yaml:34 uses helm.sh/hook: pre-upgrade,pre-install, so the migration runs before the platform re-applies the fluxcd-less ExternalArtifacts.
  • targetVersion bumped from 54 to 55 via the shared version-stamp helper. No rebase collision (max on main is 53).
  • Upgrade does not hard-fail: the generated openAPISchema in kubernetes-rd/cozyrds/kubernetes.yaml does not set additionalProperties: false, so a leftover addons.fluxcd field in a stored CR is not rejected at upgrade time and is pruned on the next manual apply.
  • make generate is up to date (schema / types.go / README), Go builds, no dangling references.
  • Both bootstrap signals are false positives: the removed-identifiers enum is FluxInstance.spec.cluster.size inside the fully-removed vendored CRD (not a cozystack values enum); the charts-direct-edit files are all deleted in full (status D), so there is no in-place edit to be reverted by make update.

Non-blocking notes

  • No test for migration 54 (a destructive operation across all tenant namespaces). A bats/e2e test is recommended.
  • The || true on the kubectl calls in the migration masks a possible finalizer-removal failure (low likelihood, since an apiserver failure would fail the whole run).

Andrei Kvapil (kvaps) added a commit that referenced this pull request Jul 23, 2026
The fluxcd addon pin was a holdover from the #3150-era model where app
charts rendered nested HelmReleases into the target cluster and needed an
in-cluster helm-controller. Consumers now keep their releases on the
management cluster and remote-apply via spec.kubeConfig, so the management
Flux resolves charts and applies manifests — nothing inside the ComputePlane
reconciles HelmReleases. This also decouples the module from the FluxCD
addon removal (#3379). certManager and ingressNginx stay pinned: Certificate
and Ingress objects land inside the cluster and need their controllers
there.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
Andrei Kvapil (kvaps) added a commit that referenced this pull request Jul 28, 2026
The fluxcd addon pin was a holdover from the #3150-era model where app
charts rendered nested HelmReleases into the target cluster and needed an
in-cluster helm-controller. Consumers now keep their releases on the
management cluster and remote-apply via spec.kubeConfig, so the management
Flux resolves charts and applies manifests — nothing inside the ComputePlane
reconciles HelmReleases. This also decouples the module from the FluxCD
addon removal (#3379). certManager and ingressNginx stay pinned: Certificate
and Ingress objects land inside the cluster and need their controllers
there.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>

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.

NOT LGTM. The removal itself is clean and the ordering is right, but migration 54 swallows the one failure it exists to prevent, and it ships without the test the other migrations in this tree have.

Blockers

Migration 54 deletes the HelmRelease even when the finalizer patch failed

The migration's whole point is stated in its own header: drop the Flux finalizer so the delete does not trigger a Helm uninstall inside the tenant. Both steps carry || true, and the delete is unconditional, so a finalizer patch that fails for any reason (webhook, RBAC, conflict, transient apiserver error) is followed by a delete that sets deletionTimestamp on an object that still has the finalizer. helm-controller then does exactly the uninstall the migration was written to avoid, and the tenant loses its Flux.

The suspend patch is not a second line of defence here. It is the finalizer, and only the finalizer, that keeps the delete inert.

Same shape one level up. hrs=$(kubectl get ... | jq ... || true) with set -eu and no pipefail means a failed kubectl get produces an empty list, the loop runs zero times, and stamp_cozystack_version 55 runs anyway. Migrations never re-run, so those tenants keep their finalizers, and the next reconcile of the addon-less Kubernetes chart prunes the HelmReleases through the normal path with the finalizer intact. Same uninstall, just later and with nothing in the logs pointing at it. An earlier review read this as low risk because an apiserver failure would fail the whole run. Without pipefail, and with || true on the assignment, it does not.

Every migration from 42 through 53 uses set -euo pipefail, and none of them uses || true except where the swallowed case is named in a comment. 54 is the only one on set -eu.

Verify the finalizer is gone before deleting, and let the migration fail if it is not. A migration that stops is recoverable; one that stamps 55 on a half-done fleet is not.

No test, and this tree tests its migrations

hack/migration-50-etcd-adopt.bats, hack/migration-seaweedfs-db-adopt.bats, hack/monitoring-pvc-backfill-migration.bats and hack/kubernetes-md0-migration.bats all drive the real migration scripts against a fake kubectl inside the migrations image's own base. The seaweedfs one pins fail-closed behaviour as an explicit property, with the reasoning spelled out in its header: a swallowed error permanently leaves at-risk tenants exposed, because migrations never re-run. That is the property 54 gets wrong, and the harness for testing it already exists.

Two cases are enough: the ordering (suspend, then finalizer, then delete, with the delete gated on the finalizer being gone) and the selection by chartRef.name across namespaces.

Non-blocking

.github/CODEOWNERS:78-79 still owns /packages/system/fluxcd/ and /packages/system/fluxcd-operator/, both deleted here.

The PR body says grep -riE 'affero|AGPL' packages/ returns nothing after the change. On the branch it returns two hits in packages/system/monitoring — comments explaining why Grafana is not rebuilt. No AGPL payload, but the claim as written does not hold.

A tenant that had the addon keeps a Flux the platform no longer manages and no longer exposes a toggle for. enabled: false used to uninstall it. Now nothing does, and neither the release note nor any doc says how an operator removes it by hand. One line in the release note would cover it.

@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch from d34670a to e8aa0fa Compare August 6, 2026 08:38
@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files and removed size/L This PR changes 100-499 lines, ignoring generated files labels Aug 6, 2026
@kvaps

Copy link
Copy Markdown
Member Author

Thanks — both blockers addressed.

B1 (migration 54 deleted even when the finalizer patch failed): rewrote it fail-closed — set -euo pipefail, dropped || true from the list and the suspend/finalizer patches, and gated the delete on verifying the finalizer is actually gone (fails loudly otherwise). The loop is heredoc-fed rather than piped, so an exit 1 aborts the migration instead of only its subshell.

B2 (no test): added hack/migration-54-fluxcd-orphan.bats driving the real script through the same docker/busybox-ash + fake-kubectl harness as the seaweedfs test — covers the ordering (suspend → finalizer → gated delete) + chartRef.name selection, and the fail-closed case (finalizer stuck → no delete, non-zero exit, no stamp). Picked up by make unit-tests via the existing hack/*.bats wildcard.

Non-blocking: dropped the removed fluxcd entries from CODEOWNERS; corrected the AGPL-grep claim in the description (the two matches are monitoring comments, no payload); added a release-note line on removing a leftover tenant Flux by hand.

Rebased on main.

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.

NOT LGTM. Two of the three round-1 items are properly closed, but the fail-closed rewrite has one || true left in it, on exactly the line the whole revision exists to protect.

Business context: Removes the last AGPL-3.0 vendored content (the default-off tenant FluxCD addon) as a breaking change for CNCF governance; tenants that had it enabled keep their running Flux via an orphaning migration.

Blockers

The verification read is fail-open

The gate added in this revision reads the finalizer back with 2>/dev/null || true (packages/core/platform/images/migrations/migrations/54:63-64). A read that fails yields an empty remaining, and empty passes the gate: delete and stamp proceed on an object whose state was never checked. Reproduced against the real script inside the pinned alpine base with a verify read that exits 1: exit code 0, the delete ran, version stamped to 55. The header says "no || true" and "the delete is gated on the finalizer actually being gone"; line 64 disagrees with both.

On blast radius, to be fair: helm-controller v1.5.0's reconcileDelete skips the uninstall for suspended objects and drops the finalizer itself, so on the shipped controller even the worst case does not uninstall the tenant's Flux. But that fence lives in the controller, not in this script, and the header sells the finalizer gate as the guarantee.

--ignore-not-found closes it in one line: a concurrently-deleted object still reads as empty with exit 0, a real API error aborts under set -e.

remaining=$(kubectl -n "$ns" get helmrelease "$name" --ignore-not-found \
  -o jsonpath='{.metadata.finalizers}')

While in the file: the header's claim that removing the finalizer is the ONLY thing keeping the delete inert is not accurate for this controller version, suspend alone protects too. Worth rewording so the next reader does not re-derive it.

The failure knobs exist and nothing uses them

The fake (hack/testdata/migration-54-fluxcd/kubectl:13-15) plumbs FAKE_LIST_FAIL, FAKE_PATCH_FAIL and FAKE_DELETE_FAIL, and its own header says a fake that can only succeed cannot test fail-closed. Neither test uses any of them, and there is no knob for the verify read failing, which is how the blocker above stayed invisible. Two tests close this: a verify-read-fail knob pinning the fix above (read fails, migration aborts before delete and before stamping), and a FAKE_LIST_FAIL case (scan fails, abort, no stamp); that one also pins the busybox-ash pipefail behavior the bats header calls load-bearing. The seaweedfs sibling this file imitates pins exactly these shapes.

The e2e harness still submits the removed addon

hack/e2e-chainsaw/_lib/run-kubernetes.sh:290-292 still puts fluxcd: {enabled: false, valuesOverride: {}} into the Kubernetes CR it applies. It no-ops silently since the schema is not strict, but it references a field this PR removes. Delete the three lines.

Non-blocking

The patch, get and delete calls use bare helmrelease while the fleet scan uses the fully-qualified helmreleases.helm.toolkit.fluxcd.io. Qualifying all of them keeps the migration immune to short-name collisions.

Everything else from round 1 checks out. The ordering holds: the migration is a pre-upgrade hook and the manifests that update the tenant-consumed artifacts apply only after hooks succeed, so a failed migration blocks the upgrade with the old chart still in force. The chartRef selection matches the removed template literally and that form shipped unchanged since before v1.0.0. The metadata-only finalizer strip triggers no reconcile on this controller (the watch predicate ignores it), so there is no re-add race. Leftover addons.fluxcd in stored tenant values is inert. CODEOWNERS is fixed, the AGPL grep claim in the body now holds, and the release note documents manual removal.

# still carries it, and deleting now would set a deletionTimestamp that makes
# helm-controller run the very uninstall this migration exists to avoid.
remaining=$(kubectl -n "$ns" get helmrelease "$name" \
-o jsonpath='{.metadata.finalizers}' 2>/dev/null || true)

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.

A read that fails yields an empty remaining and empty passes the gate, so the delete and the stamp proceed unverified. --ignore-not-found instead of 2>/dev/null || true keeps the concurrently-deleted case working while a real API error aborts under set -e.

# rows with any other chartRef must be listed but ignored.
# FAKE_LIST_FAIL non-empty => the -A fleet scan exits 1 with this stderr
# FAKE_PATCH_FAIL non-empty => every `patch` exits 1 with this stderr
# FAKE_DELETE_FAIL non-empty => every `delete` exits 1 with this stderr

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.

These three knobs are unused by the tests, and there is no knob for the verify read failing. A verify-read-fail case pins the fail-open fix; a FAKE_LIST_FAIL case pins the pipefail behavior the bats header calls load-bearing.

@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch from 875f8ef to 4f91ced Compare August 7, 2026 10:04
@kvaps

Copy link
Copy Markdown
Member Author

Round 2 addressed.

Fail-open verify read (blocker): the finalizer verify now uses --ignore-not-found instead of 2>/dev/null || true, so a failed read aborts under set -e rather than passing the gate on an unchecked object; a concurrently-deleted object still reads empty with rc 0. All helmrelease calls are fully-qualified. Reworded the header — on helm-controller v1.5.0 the suspend also fences the uninstall, but the script does not rely on that.

Unused failure knobs (blocker): added a FAKE_VERIFY_READ_FAIL knob and two tests — verify-read-fails (aborts before delete and before stamping) and FAKE_LIST_FAIL (aborts before stamping, pinning the busybox-ash pipefail). The fake had also fallen out of sync with the qualified calls, which was silently reddening the baseline; fixed.

Removed addon in the harness (blocker): dropped the addons.fluxcd block from run-kubernetes.sh.

Non-blocking: qualified all the calls.

Rebased on main.

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.

NOT LGTM. The migration is now right; the tests that pin it are not. Every negated assertion in the new bats file is a no-op, so the half of the fail-closed contract that says "and it must not have deleted or stamped" is not actually enforced.

Blockers

The ! assertions never fail the test

cozytest.sh runs each test body under set -e and injects return 0 at the closing brace, and POSIX set -e ignores failures of commands negated with !. So ! grep -qE -- "^DELETE " "$FAKE_CMDLOG" does nothing when the DELETE line IS there: grep succeeds, the negation yields rc 1, set -e skips it by design, and the body falls through to the injected return 0. Verified on the harness itself: a scratch test whose log contains a DELETE line and whose body is exactly that negation reports Test OK.

Concretely: the finalizer-sticks and verify-read-fail tests enforce only [ "$rc" -ne 0 ], so a regression that deletes or stamps before aborting still passes. The list-fail test's "nothing touched, nothing stamped" clauses are all vacuous, leaving the rc check as its only real assertion. Test 1's ! grep some-other-app is backstopped by the exact -eq 4 counts, so selection survives. The comments say "and it deleted nothing and stamped nothing"; the checks behind those words do not exist.

The fix is mechanical, per line:

if grep -qE -- "^DELETE " "$FAKE_CMDLOG"; then echo "unexpected DELETE" >&2; return 1; fi

An explicit return 1 propagates fine; only fall-through hits the injected return 0. For the record, the same ! pattern predates this PR in the seaweedfs and etcd adopt suites; that is tracked separately and is not this PR's to fix.

Round provenance in committed text

The comment above the verify-read-fail test says "Round 2 of this migration replaced 2>/dev/null || true on that read with --ignore-not-found", and the last commit's message says "pinning the round-2 fix". A reader of the tree or git log has no round 2. Describe the property instead: the read uses --ignore-not-found rather than 2>/dev/null || true so a failed read aborts instead of reading as finalizer-gone. Same reword for the commit message.

Non-blocking

The testdata realignment sits in the last commit, so at the previous commit the suite is red (its own message says the verify read matched the fleet-scan branch). This repo merges with merge commits, so that intermediate lands in history and breaks bisect over the range; you are rewriting these commits anyway, fold it in.

What held up: the fail-open verify read is properly closed (--ignore-not-found, no || true left in code, header now matches the code); all kubectl calls are fully qualified; the e2e harness block is gone; the fake's matching order is right (the verify read is matched before the fleet scan by the jsonpath token) and the verify-read-fail pin itself is non-vacuous, since reintroducing || true makes the enforced [ "$rc" -ne 0 ] fail; all four tests are green on a linux docker host. A stale addons.fluxcd left in a parent's values passes schema validation (the schema sets no additionalProperties: false), so no parent-values cleanup step is needed in the migration. The red "Unit & controller tests" on this PR is inherited: main's own run-kubernetes-talos-diagnostics_test.bats is missing from the frozen EXIT-trap list in hack/cozyreport.bats, and both files are byte-identical between this branch and main. Not this diff's doing, but it needs a one-line fix on main before either of the open migration PRs can merge green.

Comment thread hack/migration-54-fluxcd-orphan.bats Outdated
grep -qE -- "^FINALIZER-PATCH tenant-a kubevirt-kubernetes-fluxcd$" "$FAKE_CMDLOG"
grep -qF -- "refusing to delete" "$WORK/out"
# ...and it deleted nothing and stamped nothing.
! grep -qE -- "^DELETE " "$FAKE_CMDLOG"

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.

This negation never fails the test: under set -e a !-negated command's failure is ignored by POSIX rule, and cozytest.sh injects return 0 at the closing brace, so when a DELETE line IS present the body still falls through green. Verified with a scratch test on the harness. if grep -qE -- "^DELETE " "$FAKE_CMDLOG"; then echo "unexpected DELETE" >&2; return 1; fi enforces it; same for every ! grep in this file.

rm -rf "$WORK"
}

# The verify READ that gates the delete errors out (RBAC, conflict, a transient

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.

"Round 2 of this migration replaced..." is review-process context a reader of this file cannot resolve. Describe the property instead: the read uses --ignore-not-found rather than 2>/dev/null || true so a failed read aborts instead of reading as finalizer-gone.

@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch from 231c9d6 to 130701b Compare August 9, 2026 20:40
@kvaps

Copy link
Copy Markdown
Member Author

Round 3 addressed.

No-op ! assertions (blocker): converted every negated assertion in migration-54-fluxcd-orphan.bats to explicit if grep …; then echo … >&2; return 1; fi. Sanity-checked that they now bite — injecting a fail-path delete or stamp turns the test red (unexpected DELETE/STAMP after stuck finalizer). The positive count-asserts are unchanged.

Round provenance (blocker): removed the "Round 2 …" comment — it now describes the property (the verify read uses --ignore-not-found rather than 2>/dev/null || true, so a failed read aborts instead of reading as finalizer-gone). No "round" text remains in any commit message.

Fold (NB): rebased on main and reconstructed into two clean commits — the addon removal, then migration 54 with its bats test + fake kubectl together — so the fake realignment is never in a commit after the migration, no red intermediate over the range.

The inherited "Unit & controller tests" red is resolved by the rebase: main removed the talos-diagnostics EXIT traps (6cfeadd / 575b41e), so there is nothing left to record in a frozen list.

Rebased on main.

@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch 3 times, most recently from 82add2b to 5103215 Compare August 13, 2026 17:14
@kvaps
Andrei Kvapil (kvaps) force-pushed the fix/devendor-flux-operator-agpl branch 2 times, most recently from 2c2328e to 8126088 Compare August 19, 2026 08:15

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. Both blockers are gone: every negation is now if grep ...; then ...; return 1; fi, and the round wording is out of both the test comment and the commit message. The testdata realignment is folded in, so the two commits stand on their own.

I checked the load-bearing parts of the migration rather than just the wording. The chartRef selector matches the two names the removed template actually emitted, the migration hook is pre-upgrade,pre-install so the finalizer drop really does precede any pruning, targetVersion: 55 lines up with stamp_cozystack_version 55, and the new bats file is picked up by the hack/*.bats wildcard in bats-unit-tests. Nothing named addons.fluxcd survives outside the changelogs. Migration 53 also bumped targetVersion without touching the migrations image digest, so leaving that pin alone matches precedent.

E2E is red on the install flake, not on this diff: tenant-root/monitoring and seaweedfs time out behind vlstorage-db-vlstorage-generic-0 failing autoplace with "Not enough available nodes", with a lineage.cozystack.io webhook deadline alongside. The API owner gate is the other red check, since api/apps/v1alpha1/kubernetes loses a field. The two downstream items in the description need issues, otherwise they are a paragraph in a merged PR.

The optional `addons.fluxcd` toggle of the Kubernetes app deployed a
Flux Operator + FluxInstance into a tenant cluster. It was served by two
vendored charts, packages/system/fluxcd-operator and packages/system/
fluxcd, which were the last AGPL-3.0 content in this Apache-2.0
repository and a governance blocker for CNCF Incubation.

The management cluster does not use these charts (it runs classic Flux
via internal/fluxinstall; migration 21 already removed the operator from
it), and the addon defaulted to disabled. Rather than maintain an AGPL
payload for an unused feature, drop it entirely:

- delete both system packages and their platform source entries
- remove the addons.fluxcd HelmReleases, values and generated schema
  from the Kubernetes app
- drop the addons.fluxcd block still submitted by the e2e harness
- drop the removed fluxcd package entries from CODEOWNERS

BREAKING CHANGE: the addons.fluxcd option of the Kubernetes app is
removed. Tenant clusters that had it enabled keep their running Flux
release; see the companion migration for the upgrade path.

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
Add migration 54 for the removed addons.fluxcd feature. For every tenant
that had it enabled, the FluxCD HelmReleases in the management cluster
are suspended, stripped of their Flux finalizer and deleted, so the
delete does not trigger a Helm uninstall inside the tenant cluster. The
Flux release already running in the tenant keeps running untouched.

Runs as a pre-upgrade hook, before the addon-less Kubernetes chart
reaches the tenant HelmReleases. Mirrors the orphan idiom of migration
29. Bumps migrations.targetVersion to 55.

Fail closed. Dropping the finalizer is the only thing that keeps the
delete inert, and migrations never re-run, so the script runs under
`set -euo pipefail` with no `|| true` on the critical path and gates the
delete on the finalizer actually being gone. The verify read uses
`--ignore-not-found` rather than `2>/dev/null || true`, so a failed read
aborts instead of reading as an empty finalizer list that passes the
gate on an object whose state was never checked. A migration that stops
is recoverable; one that half-orphans a fleet is not.

Cover it with a docker + fake-kubectl unit test running the real script
by path inside the migrations image's own alpine base (busybox ash, jq
baked on), so it exercises the shipped interpreter and load-bearing
pipefail. The suite pins ordering and selection (only the addon
HelmReleases are touched, each suspend -> finalizer-drop -> delete in
order) and the fail-closed paths (a surviving finalizer, a failing
verify read, and a failing fleet scan each abort before deleting and
before stamping).

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>

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. Rebased this onto main myself after #3406 landed, since both PRs claimed migration 54.

What the rebase changed, nothing else: the migration file is renumbered 54 to 55, its header and stamp_cozystack_version now read 56, targetVersion goes 55 to 56, and hack/migration-54-fluxcd-orphan.bats plus its testdata directory are renamed to 55 with the numbers inside them updated. The redis migration that came in with #3406 stays at 54, untouched.

Worth knowing for the next collision: migrations/54 conflicted add/add and was loud, but values.yaml merged clean on its own because both branches wrote the same 55. Resolving only what git points at would have left targetVersion: 55 next to a migration 55 that stamps 56, and the runner loops seq 54 54, so the migration would never run. make migrations-target-check catches exactly that; it reports targetVersion=56 >= 56 (latest migration 55) now, and the platform suite is 138/138.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 6db7260 into main Aug 20, 2026
45 of 59 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the fix/devendor-flux-operator-agpl branch August 20, 2026 10:39
Andrei Kvapil (kvaps) pushed a commit that referenced this pull request Sep 7, 2026
## What this PR does

The Kubernetes app's optional `addons.fluxcd` toggle deployed a Flux
Operator + FluxInstance into tenant clusters, served by two vendored
Helm charts (`packages/system/fluxcd-operator`,
`packages/system/fluxcd`). Those charts are **AGPL-3.0** — the last AGPL
content in this Apache-2.0 repository and a governance blocker for CNCF
Incubation.

The management cluster does not depend on them: it runs classic Flux via
`internal/fluxinstall`, and migration 21 already removed the Flux
Operator from it. The addon defaulted to `enabled: false`. Rather than
maintain an AGPL payload for an unused feature, this PR removes it
entirely as a breaking change:

- Delete both system packages and their `platform` source entries.
- Remove the `addons.fluxcd` HelmReleases, values, Go types and
generated schema/README from the Kubernetes app.
- Add **migration 54**: for any tenant that had the addon enabled, the
FluxCD HelmReleases in the management cluster are **suspended, stripped
of their Flux finalizer and deleted**, so the delete does not trigger a
Helm uninstall inside the tenant cluster. The Flux release already
running inside the tenant keeps running untouched (mirrors the orphan
idiom of migration 29). It runs as a `pre-upgrade` hook, before the
addon-less Kubernetes chart reaches the tenant HelmReleases.

After the change the only `affero`/`AGPL` matches under `packages/` are
comments in `packages/system/monitoring` explaining why Grafana is not
rebuilt — no AGPL payload ships.

**Verification:** the Kubernetes app helm-unittests (181) and platform
tests (88) pass; `make generate` was run with the CI-pinned
`cozyvalues-gen v1.6.0` (schema, README, Go types, CRD);
`migrations-target-check` and the `cozystack-version-stamp` bats pass;
the `api/apps/v1alpha1` module builds.

### Downstream repositories

This removes a user-facing addon and drops the `addons.fluxcd` field
from the Kubernetes app's `values.schema.json`. Boxes are left unticked
pending follow-ups (not opened from this PR):

- **terraform-provider-cozystack** — models the Kubernetes app schema;
the removed `addons.fluxcd` field should be checked/dropped there.
- **website** — any hand-written mention of the FluxCD addon in the
tenant Kubernetes docs should be removed (the managed-app reference
regenerates from the README).

### Release note

```release-note
feat(kubernetes)!: remove the FluxCD addon (`addons.fluxcd`). It relied on AGPL-3.0 vendored charts and was disabled by default; tenant clusters that had enabled it keep their running Flux release via an automatic upgrade migration. To remove that orphaned Flux from such a tenant, uninstall its `flux-operator`/`flux-instance` Helm releases inside the tenant cluster by hand.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Breaking Changes**
* Removed FluxCD addon support from tenant cluster configuration,
including the Kubernetes tenant CRD schema, chart values schema,
defaults, and published “Cluster Addons” parameters.
* FluxCD-related Helm manifests are no longer rendered as part of the
Kubernetes package.
* FluxCD configuration is no longer accepted for new or updated tenant
clusters.

* **Migration**
* During upgrade, existing FluxCD Helm releases are cleaned up
automatically by suspending them, clearing finalizers, and deleting the
management-side releases.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kubernetes Issues or PRs related to the tenant Kubernetes app area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/breaking-change Indicates the change introduces a breaking API or behaviour change kind/cleanup Categorizes issue or PR as related to cleanup of code, process, or technical debt kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants