fix(kubernetes): make the default md0 node group removable - #2936
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe ChangesNodeGroups default refactor and md0 migration
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Summary of ChangesHello, 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 refactors the Kubernetes application chart to make the default Highlights
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. Using Gemini Code AssistThe 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
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 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
|
There was a problem hiding this comment.
Code Review
This pull request updates the Kubernetes application chart to change the default value of nodeGroups to an empty map ({}). The default md0 node group is now dynamically applied in the templates via a new helper (kubernetes.nodeGroups) only when no user-defined node groups are provided, preventing Helm merge issues. To support existing clusters and prevent their md0 node groups from being pruned on upgrade, a new platform migration script (Migration 44 -> 45) has been added to explicitly pin md0 on active clusters. The review feedback suggests minor shell script improvements in the migration script, specifically using the --patch flag for kubectl patch instead of piping to /dev/stdin, and using the more idiomatic -f - flag for kubectl apply.
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.
| if kubectl_err=$(printf '%s' "$patch" | kubectl patch kuberneteses.apps.cozystack.io \ | ||
| --namespace "$ns" "$name" --type merge --patch-file /dev/stdin 2>&1 >/dev/null); then |
There was a problem hiding this comment.
Using --patch "$patch" is more standard and robust than piping the patch via stdin and using /dev/stdin, which might not be available or behave consistently in all minimal container environments.
| if kubectl_err=$(printf '%s' "$patch" | kubectl patch kuberneteses.apps.cozystack.io \ | |
| --namespace "$ns" "$name" --type merge --patch-file /dev/stdin 2>&1 >/dev/null); then | |
| if kubectl_err=$(kubectl patch kuberneteses.apps.cozystack.io \ | |
| --namespace "$ns" "$name" --type merge --patch "$patch" 2>&1 >/dev/null); then |
| kubectl create configmap --namespace cozy-system cozystack-version \ | ||
| --from-literal version=45 --dry-run=client --output yaml \ | ||
| | kubectl apply --filename - |
There was a problem hiding this comment.
Using -f - is more idiomatic and consistent with the rest of the codebase than --filename -.
| kubectl create configmap --namespace cozy-system cozystack-version \ | |
| --from-literal version=45 --dry-run=client --output yaml \ | |
| | kubectl apply --filename - | |
| kubectl create configmap --namespace cozy-system cozystack-version \ | |
| --from-literal version=45 --dry-run=client --output yaml \ | |
| | kubectl apply -f - |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/platform/images/migrations/migrations/44`:
- Line 65: The current jq filter in the patch variable assignment generates a
merge patch containing the entire pre-read .spec.nodeGroups map, which can
overwrite concurrent edits to other node groups made by other actors between the
read and patch operations. Modify the jq filter to extract and patch only the
md0 node group from .spec.nodeGroups instead of the entire nodeGroups map. This
ensures the patch operation is minimal and only adds or updates md0 without
risking loss of concurrent modifications to other node groups.
- Around line 55-56: The current kubectl command silently suppresses all errors
with 2>/dev/null and || true, which means transient API or RBAC failures are
indistinguishable from a successfully deleted object. This allows the migration
to continue to version 45 without properly pinning md0 when the actual failure
cause was a transient issue. You need to separate the kubectl exit code from the
output check: capture the kubectl output in a variable while preserving its exit
code, then explicitly check whether the command succeeded before deciding to
skip the object. Only increment SKIPPED and return 0 if kubectl succeeded and
returned empty output (meaning the object was genuinely deleted), otherwise
propagate the error so the migration stops and alerts the operator to the
API/RBAC problem.
- Line 1: Change the shebang from `#!/bin/bash` to `#!/bin/sh` and rewrite the
migration script using POSIX-compatible syntax. Specifically, replace the
Bash-only syntax constructs: replace the tab character escape `$'\t'` at line 85
with a literal tab character (or printf equivalent), and replace the here-string
operator `<<<` at line 88 with a here-document or pipe approach. Additionally,
modify the error handling around lines 55-56 where `kubectl get` errors are
silently suppressed with `2>/dev/null` to distinguish between a missing CRD
(which is safe to skip) and actual API call failures (which should trigger a
retry or explicit error). Finally, at line 65, replace the merge patch operation
on the entire `.spec.nodeGroups` object with a strategic merge patch or
individual field patches to avoid overwriting concurrent edits to other node
groups made by users or controllers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 2d23c207-802a-4ced-a104-662b65f1be75
📒 Files selected for processing (13)
api/apps/v1alpha1/kubernetes/types.gohack/e2e-apps/run-kubernetes.shpackages/apps/kubernetes/README.mdpackages/apps/kubernetes/templates/_helpers.tplpackages/apps/kubernetes/templates/cluster.yamlpackages/apps/kubernetes/templates/dashboard-resourcemap.yamlpackages/apps/kubernetes/tests/values-ci.yamlpackages/apps/kubernetes/tests/values/common.yamlpackages/apps/kubernetes/values.schema.jsonpackages/apps/kubernetes/values.yamlpackages/core/platform/images/migrations/migrations/44packages/core/platform/values.yamlpackages/system/kubernetes-rd/cozyrds/kubernetes.yaml
d73d1d9 to
fb0d301
Compare
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
NOT LGTM (REQUEST_CHANGES)
Business context
The kubernetes app baked a default md0 node group into values.yaml, which Helm merged into every cluster and could not be removed; this PR moves the default into a kubernetes.nodeGroups helper applied only when nodeGroups is empty, and adds migration 44 to pin md0 explicitly on existing clusters so the chart change does not prune their live MachineDeployment.
Migration activation is correct: migrations.targetVersion is bumped 44 → 45, which activates /migrations/44 (not dead code). Evidence: packages/core/platform/values.yaml:9. The helper change is also complete — every template that iterated nodeGroups now routes through the helper, so no template renders an empty worker set. Evidence: packages/apps/kubernetes/templates/cluster.yaml:240, packages/apps/kubernetes/templates/dashboard-resourcemap.yaml:33.
Blockers
B1 — Version stamp drops the platform.cozystack.io/no-delete label
- File:
packages/core/platform/images/migrations/migrations/44:102-104 - Evidence: the stamp is
kubectl create configmap --namespace cozy-system cozystack-version --from-literal version=45 --dry-run=client --output yaml | kubectl apply --filename -— a label-less manifest. Migrations 42 and 43 both stamp via an inline manifest carryinglabels: { platform.cozystack.io/no-delete: "true" }, andmigrations/43documents exactly this case: "A label-less apply by the same field manager ... would strip theplatform.cozystack.io/no-deletelabel." - Impact: applied by the same field manager, the label-less manifest strips
no-deletefrom thecozystack-versionConfigMap, removing it from the deletion guardrail. Upgrade-path only, so E2E does not exercise it. - Fix: stamp version 45 with the labeled inline manifest used by
migrations/43.
B2 — Fail-open on patch/read failure lets the destructive chart change proceed
- File:
packages/core/platform/images/migrations/migrations/44:55-56,97-100 - Evidence: the per-object read uses
kubectl get ... 2>/dev/null || true, turning any non-NotFound error into a SKIP (lines 55-56); and onFAILURES > 0the script logs a WARN andexit 0(lines 97-100), so the pre-upgrade hook succeeds even when some clusters were not pinned. - Impact: with the hook green, the platform upgrade completes and the new kubernetes chart rolls out to tenant clusters. An unpinned cluster (CR still without
md0) then has its livemd0MachineDeployment pruned (→ CAPI Machine/node deletion) — the exact outcome this migration exists to prevent. Leaving the stamp at 44 for retry does not help, because the chart has already rolled out before the next upgrade. - Fix: treat non-NotFound read errors as failures rather than skips, and make the failure path block the chart change (fail the hook) instead of completing the upgrade with clusters left unpinned.
Non-blocking
- No test covers the empty-
nodeGroups→md0default. Evidence:packages/apps/kubernetes/tests/values-ci.yamlchanged fromnodeGroups: nullto an explicitmd0, and notests/*_test.yamlasserts that an emptynodeGroupsrenders the defaultmd0MachineDeployment. The new helper's core behavior is therefore unverified by helm-unittest.
|
Aleksei Sviridkin (@lexfrei) addressed in
|
1252378 to
933ab5d
Compare
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
NOT LGTM — both prior blockers are genuinely fixed and well-tested; one gap remains: the fail-closed safety branch (which is what prevents data loss) has no automated test, and it is testable with a pattern this repo already uses.
Credit where due — the fixes are solid and verified:
- B1 (version stamp dropped the no-delete label): fixed. Migration 45 stamps via the shared
stamp_cozystack_versionhelper (:131), which emits the ConfigMap carryingplatform.cozystack.io/no-delete, and thecozystack-version-stamp.batssuite structurally enforces that every migration stamps through that helper, so it can't regress to a label-less apply. - B2 (fail-open let the destructive chart change proceed): fixed. The migration is now fail-closed —
set -euo pipefail(:23), per-object failures counted instead of skipped (:73,:103), list errors abort (:108), andFAILURES > 0exits 1 before the version stamp (:121-123), so the upgrade halts and Helm never rolls the new chart against clusters whose md0 isn't pinned. - The empty-
nodeGroups->md0default is now pinned bytests/nodegroups_default_test.yaml, run undermake unit-tests.
Blocker
B1: the fail-closed abort path has no regression test
The FAILURES > 0 -> exit 1 before the stamp branch is the entire safety mechanism of this migration — if it ever regresses to fail-open, the destructive chart change proceeds against un-pinned clusters and their live md0 MachineDeployment is pruned (the exact data loss this PR exists to prevent). That branch is currently verified by reading only. It is testable with the kubectl-mock bats pattern this repo already uses in cozystack-version-stamp.bats.
Fix: add a bats case that mocks a kubectl get/patch failure and asserts the migration exits non-zero before the version stamp is written (i.e. CURRENT_VERSION is not advanced). A negative helm-unittest (a custom nodeGroups without md0 renders no *-md0 MachineDeployment) would also be worth adding to pin the removability branch directly, but the fail-closed test is the one that guards against data loss.
933ab5d to
f3bfe6c
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
hack/kubernetes-md0-migration.bats (1)
85-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a dry-run regression case too.
This suite never exercises
MIGRATION_DRY_RUN=1, so the success-on-read-failure path in the migration script slips through. A singleMOCK_FAIL=readdry-run test would lock that branch down once the exit ordering is fixed.Possible addition
+@test "migration 47 dry-run still exits non-zero when a read fails" { + tmp=$(mktemp -d) + export KLOG="$tmp/kubectl.log" + : > "$KLOG" + write_fake_kubectl "$tmp" "$KLOG" + + if MIGRATION_DRY_RUN=1 KLOG="$KLOG" MOCK_FAIL=read PATH="$tmp:$PATH" "$MIGRATION" >"$tmp/out" 2>&1; then + echo "expected dry-run migration to exit non-zero on a non-NotFound read failure; output:" >&2 + cat "$tmp/out" >&2 + rm -rf "$tmp" + exit 1 + fi + + if grep -q apply "$KLOG"; then + echo "dry-run must not stamp the version" >&2 + cat "$KLOG" >&2 + rm -rf "$tmp" + exit 1 + fi + rm -rf "$tmp" +}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hack/kubernetes-md0-migration.bats` around lines 85 - 154, Add a regression test for the dry-run path in the kubernetes migration suite, since `MIGRATION_DRY_RUN=1` is currently untested. Extend the `migration 47` cases in `hack/kubernetes-md0-migration.bats` with a `MOCK_FAIL=read` dry-run scenario using `write_fake_kubectl` and the `MIGRATION` runner, and assert the script still exits appropriately without advancing the version stamp. Make sure the new test specifically covers the success-on-read-failure branch so the dry-run behavior stays locked down.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@hack/e2e-apps/run-kubernetes.sh`:
- Around line 154-170: The new kubectl wait calls in run-kubernetes.sh can fail
early with NotFound if the target resources are not created yet, so add the same
existence backstop pattern used earlier in the script before each wait. Update
the sections around kubernetes-${test_name},
kubernetes-${test_name}-cluster-autoscaler/kccm/kcsi-controller, and
kubernetes-${test_name}-md0 to first wait for object existence, then run the
existing condition waits. Keep the fix aligned with the existing event-driven
check style already present in the script.
- Around line 730-761: The server-side dry-run fallback in run-kubernetes.sh
writes stderr to a predictable /tmp path, which can be tampered with by other
processes. Replace the hardcoded /tmp/sc-fallback-render.err usage in the helm
install block with a unique tempfile created via mktemp, store it in a variable,
and use that variable consistently for both the redirected stderr and the later
cat/readback. Make sure the cleanup path removes the tempfile after use and keep
the existing rc/raw handling intact.
In `@packages/core/platform/images/migrations/migrations/47`:
- Around line 116-123: The dry-run path in the migration script is returning
success before checking whether any reads or patches failed, so non-NotFound
errors can be hidden. Update the logic around the DRY_RUN and FAILURES checks in
the migration script for version 47 so dry-run still reports failures from
patch_md0() and only exits 0 when FAILURES is zero; keep the existing
version-stamp gating behavior intact.
---
Nitpick comments:
In `@hack/kubernetes-md0-migration.bats`:
- Around line 85-154: Add a regression test for the dry-run path in the
kubernetes migration suite, since `MIGRATION_DRY_RUN=1` is currently untested.
Extend the `migration 47` cases in `hack/kubernetes-md0-migration.bats` with a
`MOCK_FAIL=read` dry-run scenario using `write_fake_kubectl` and the `MIGRATION`
runner, and assert the script still exits appropriately without advancing the
version stamp. Make sure the new test specifically covers the
success-on-read-failure branch so the dry-run behavior stays locked down.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 51afc051-e43c-48c9-9788-9331a7c3c922
📒 Files selected for processing (14)
api/apps/v1alpha1/kubernetes/types.gohack/e2e-apps/run-kubernetes.shhack/kubernetes-md0-migration.batspackages/apps/kubernetes/README.mdpackages/apps/kubernetes/templates/_helpers.tplpackages/apps/kubernetes/templates/cluster.yamlpackages/apps/kubernetes/templates/dashboard-resourcemap.yamlpackages/apps/kubernetes/tests/nodegroups_default_test.yamlpackages/apps/kubernetes/tests/values-ci.yamlpackages/apps/kubernetes/tests/values/common.yamlpackages/apps/kubernetes/values.schema.jsonpackages/apps/kubernetes/values.yamlpackages/core/platform/images/migrations/migrations/47packages/core/platform/values.yaml
✅ Files skipped from review due to trivial changes (2)
- packages/apps/kubernetes/README.md
- packages/apps/kubernetes/tests/values-ci.yaml
🚧 Files skipped from review as they are similar to previous changes (6)
- packages/apps/kubernetes/templates/dashboard-resourcemap.yaml
- packages/apps/kubernetes/values.yaml
- packages/apps/kubernetes/tests/values/common.yaml
- packages/apps/kubernetes/values.schema.json
- packages/apps/kubernetes/templates/_helpers.tpl
- api/apps/v1alpha1/kubernetes/types.go
f3bfe6c to
d5b2e33
Compare
… empty
The chart shipped a default nodeGroups.md0 in values.yaml. Helm deep-merges
values, so the default md0 was re-added on top of any user-supplied
nodeGroups; combined with Kubernetes stripping null values, md0 could never
be removed by configuration. Move the default into the template
("kubernetes.nodeGroups" helper) so it is applied only when nodeGroups is
empty. User-supplied node groups now fully replace the default, letting users
name and omit groups (including md0) freely.
Test fixtures that relied on the chart-default md0 to supply diskSize via
values merge now set it explicitly.
Signed-off-by: mattia-eleuteri <mattia@hidora.io>
After the kubernetes chart stops force-merging a default md0 node group, clusters that relied on the implicit md0 (defined other nodeGroups without md0) would have their live md0 MachineDeployment pruned by Helm on the next reconcile. Migration 47 makes md0 explicit on those clusters to preserve the current node topology; it is idempotent, treats a missing CRD as a no-op, and skips clusters that already define md0. Signed-off-by: mattia-eleuteri <mattia@hidora.io>
…e label Migration 47 pins the implicit default md0 node group before the kubernetes chart upgrade so the live md0 MachineDeployment is not pruned. Two correctness fixes: - Stamp the cozystack-version ConfigMap via the shared stamp_cozystack_version helper instead of `kubectl create configmap | kubectl apply`. The label-less apply by the same field manager stripped the platform.cozystack.io/no-delete label migration 42 added, dropping cozystack-version out of the cozystack-no-delete-guardrail policy; the helper re-emits the label and is enforced for every migration >= 42 by hack/cozystack-version-stamp.bats. - Fail closed. A non-NotFound read error is now counted as a failure rather than silently skipped, and any failure exits 1 so the pre-upgrade hook aborts before Helm rolls out the new chart against clusters whose md0 is not yet pinned. The version stamp stays at 47 and the migration retries next upgrade. Also narrow the merge patch to only md0 so a concurrent edit to a sibling node group between the read and the patch cannot be reverted. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assert that an empty nodeGroups renders the built-in md0 group via the kubernetes.nodeGroups helper else-branch (the behaviour that makes md0 removable): the default md0 MachineDeployment is emitted and the default 20Gi system disk flows through. A minimal VirtualMachineClusterInstancetype plus dummy CAPI objects are registered so the per-group instanceType lookup and the old-template preservation LISTs resolve under helm-unittest. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The existing case pins the empty-nodeGroups -> default md0 path (the helper's else-branch). This adds the complementary if-branch: when a user supplies their own nodeGroups, they are authoritative and the built-in md0 is NOT merged in, so md0 is removable. The assertion selects every rendered MachineDeployment and requires it to be the user's own group, so a regressed md0 leak surfaces as a second MachineDeployment named <release>-md0 that fails the assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Migration 47's safety property is that any per-object read or patch failure aborts the migration (exit 1) before the cozystack-version stamp advances, so the pre-upgrade hook fails and Helm never rolls the new kubernetes chart against an un-pinned cluster (whose live md0 MachineDeployment would otherwise be pruned). That branch was verified by reading only. Add hack/kubernetes-md0-migration.bats, mirroring the kubectl-mock style of the plain-shell cozytest.sh runner: a fake kubectl on PATH simulates a non-NotFound read failure and a patch failure, and the tests assert the migration exits non-zero and never reaches the version stamp (no `kubectl apply` of cozystack-version is logged). A positive-control case asserts a clean run does reach the stamp, so the "no stamp" assertions are meaningful rather than vacuous. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
d5b2e33 to
4c5a8c8
Compare
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — the remaining blocker is resolved and the migration's correctness re-verified after the rebase to /47.
The fail-closed abort path now has a regression test: hack/kubernetes-md0-migration.bats puts a fake kubectl on PATH and asserts that a read failure and a patch failure each abort non-zero WITHOUT the version stamp ever running, with a clean-run positive control so the "no stamp" assertions aren't vacuous. It is a unit bats (not e2e-prefixed), so BATS_UNIT_FILES picks it up under make unit-tests, and it respects the cozytest.sh constraints (no run/$status, no column-0 }, inline cleanup).
Re-verified after the renumber to /47:
- Activation is correct: targetVersion 47→48 activates migration /47, and main is at 46/47 so there is no /47 collision.
- Fail-closed survived the rebase:
set -euo pipefail, per-object failures counted rather than skipped, list errors abort, andFAILURES>0 -> exit 1before the stamp; the stamp goes throughstamp_cozystack_version 48, so the cozystack-version ConfigMap keeps theplatform.cozystack.io/no-deletelabel. - patch_md0 is data-safe: it distinguishes NotFound (skip) from other read errors (failure), and patches only
{spec:{nodeGroups:{md0}}}, so a concurrent edit to sibling node groups is not clobbered. - No live drift: the injected MD0_JSON is byte-identical to both the new
kubernetes.nodeGroupshelper default and the previous baked values.yaml md0, so pinning an existing cluster leaves its md0 MachineDeployment unchanged.
|
Backport failed for Please cherry-pick the changes locally and resolve any conflicts. git fetch origin release-1.5
git worktree add -d .worktree/backport-2936-to-release-1.5 origin/release-1.5
cd .worktree/backport-2936-to-release-1.5
git switch --create backport-2936-to-release-1.5
git cherry-pick -x 7e3c5f99dbed00cc0459cdf4a61869bfe844f434 602b8d06fdf3a8f0844276e9dd686a63ad3a27d8 b9749cec803bb25b31c29ee9e17aeb5f312c84c0 ab55ff192407079ef33eccffcf9583ab4d09f986 c818ece25b20eb7e4ce7315b079d9627362557c8 4c5a8c830c41ec58e3ff62659d1b1fbfe29fb07a |
|
Not backporting this to This carries an The change also cherry-picks with conflicts in four files, including Removing the |
…roup (#3535) ## What this PR does Fixes #3504. The chart has two loops over worker node groups and they read different sources. `templates/cluster.yaml` iterates the effective set through the `kubernetes.nodeGroups` helper, whose else-branch supplies the built-in `md0` group when the user declares none — that is how #2936 made `md0` removable without a Helm values merge re-adding it. `templates/talos/talos-reconcile-job.yaml` iterated the raw `.Values.nodeGroups` map instead, which is empty in exactly that case. The consequence is a dangling reference. The chart cannot render the `TalosConfigTemplate` itself — it needs the apiserver Service ClusterIP, the Talos CA and the Kubernetes CA, none of which exist while Helm is executing the template — so `cluster.yaml` deliberately stopped rendering it and the Job became its only producer, while the `MachineDeployment` still names it in `spec.template.spec.bootstrap.configRef`. On a cluster with the default `nodeGroups`, `MachineDeployment/<release>-md0` renders and zero Jobs render, so `TalosConfigTemplate/<release>-md0` is never created and CAPI blocks every Machine that ever joins the group. The same Job also patches `KamajiControlPlane.spec.network.certSANs` with the live Service ClusterIP, so that is skipped too. Nothing fails at install time, because the built-in group carries `minReplicas: 0`. The failure waits for the first scale-up, which is the documented path rather than a corner: `values.yaml` tells operators that enabling the ingress-nginx addon on a cluster with default `nodeGroups` means waiting for the cluster-autoscaler to bring `md0` up in response to the controller Pods becoming Pending, and the autoscaler is deployed unconditionally whenever the tenant has an etcd DataStore. The fix is to range over the helper so the Job set tracks the MachineDeployment set exactly. A cluster that declares its own groups renders byte-identically — which is why the pinned content-hash fixtures in `tests/talos_templates_test.yaml` are untouched — and `md0` stays removable, because the fix defers to the helper rather than merging `md0` in unconditionally. On upgrade it is additive: the Job appears where there was none, applies the `TalosConfigTemplate` the MachineDeployment already expects, and the content-hash name suffix means Helm creates a fresh Job rather than attempting to patch an immutable one. ### How this survived Every helm-unittest fixture and every e2e suite declares `md0` explicitly. `tests/nodegroups_default_test.yaml` does render the empty-`nodeGroups` case, but lists only `templates/cluster.yaml`, so it never looked at a Job. The two OIDC chainsaw lanes do install with an empty map, but assert that the HelmRelease exists rather than that it becomes Ready, so a worker that never boots is invisible to them. Offline `helm template` cannot reach this path at all: `cluster.yaml`'s instanceType validator needs a live `lookup` for `u1.medium`. `tests/talos_reconcile_nodegroups_test.yaml` closes the gap and pins both halves of the helper contract — an empty map yields exactly one `md0` Job carrying `GROUP_NAME=md0`, and a two-group map yields exactly two Jobs with no `md0` among them, so a future fix that merges `md0` in unconditionally fails here. Reverting the template change fails the first case on the document count, so it guards the behaviour rather than restating it. ### Screenshots Not a UI change. ### Downstream repositories Walked the trigger map in `docs/agents/contributing.md` against the diff: it is one Helm template loop and one new helm-unittest suite inside `packages/apps/kubernetes`. No package is added, renamed or removed; no `values.yaml`, `values.schema.json`, `ApplicationDefinition`, version enum or default changes; nothing under `hack/`, no namespace, variant, label, annotation or metric renamed. The behaviour now matches what `values.yaml` already documents, so no reference page drifts. - [x] No downstream repository is affected by this change - [ ] [cozystack/website](https://github.com/cozystack/website) - follow-up: - [ ] [cozystack/terraform-provider-cozystack](https://github.com/cozystack/terraform-provider-cozystack) - follow-up: - [ ] [cozystack/ansible-cozystack](https://github.com/cozystack/ansible-cozystack) - follow-up: - [ ] [cozystack/ccp](https://github.com/cozystack/ccp) - follow-up: - [ ] [cozystack/talm](https://github.com/cozystack/talm) - follow-up: - [ ] [cozystack/cozyhr](https://github.com/cozystack/cozyhr) - follow-up: - [ ] [cozystack/cozy-proxy](https://github.com/cozystack/cozy-proxy) - follow-up: - [ ] [cozystack/cozystack-telemetry-server](https://github.com/cozystack/cozystack-telemetry-server) - follow-up: - [ ] [cozystack/external-apps-example](https://github.com/cozystack/external-apps-example) - follow-up: - [ ] [cozystack/examples](https://github.com/cozystack/examples) - follow-up: ### Release note ```release-note fix(kubernetes): a tenant Kubernetes cluster created without an explicit `nodeGroups` map now gets a talos-reconcile Job for the built-in `md0` node group. Previously that Job rendered only for explicitly declared groups, so the default `md0` MachineDeployment referenced a TalosConfigTemplate nothing ever created: every worker the cluster-autoscaler added to `md0` stayed stuck waiting for its bootstrap config, and the KamajiControlPlane certSANs patch the same Job performs never ran. Clusters that declare their own node groups were unaffected. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Ensured the built-in `md0` node group receives its Talos reconciliation Job when no node groups are configured. * Prevented duplicate reconciliation Jobs when custom node groups are specified. * Preserved correct group-specific settings and control-plane configuration in generated Jobs. * **Tests** * Added regression coverage for default and custom node-group scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Same-repo mirror of #2753 by mattia-eleuteri, opened so the
BuildCI job can run. Fork PRs skip the OCIR registry login (if: !github.event.pull_request.head.repo.forkinpull-requests.yaml) and so can't push the per-PR images the build needs — meaning a fork PR can never passBuild, regardless of the change. The commits here are unchanged and authored by mattia-eleuteri; full description and discussion in #2753.This includes migration 44 (renumbered from 43 after the seaweedfs-db migration landed on
main) and bumpstargetVersionto 45.Supersedes #2753.
Summary by CodeRabbit
New Features
nodeGroupssettings now automatically provision a defaultmd0worker group.Bug Fixes
Documentation
Tests