Skip to content

fix(installer): bootstrap cozy-system via --create-namespace + label hook - #2508

Merged
myasnikovdaniil merged 3 commits into
mainfrom
fix/installer-namespace-bootstrap
May 5, 2026
Merged

myasnikovdaniil merged 3 commits into
mainfrom
fix/installer-namespace-bootstrap

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor

Why

helm v3 has a chicken-and-egg with charts that ship their own Namespace resource:

  • With --create-namespace on the install: helm pre-creates the namespace via plain kubectl-create (no helm meta annotations); the chart's own Namespace apply then fails with already exists.
  • Without --create-namespace: helm fails immediately because it can't write its release-secret to a non-existent namespace.

The cozy-installer chart shipped a Namespace cozy-system resource since the chart's introduction (December 2025). Every cold install since has hit this conflict. It was masked by the 3× retry on Install Cozystack in .github/workflows/pull-requests.yaml: attempt 1 fails with the conflict, attempt 2 sees the existing failed release and takes the upgrade code path which patch-merges instead of strict-create.

Surfaced when retries on the install step were dropped.

What

  • Remove Namespace cozy-system from packages/core/installer/templates/cozystack-operator.yaml.
  • Add packages/core/installer/templates/cozy-system-labels.yaml: a pre-install/pre-upgrade hook (ServiceAccount + ClusterRole + ClusterRoleBinding + Job) that patches cozystack.io/system=true and pod-security.kubernetes.io/enforce=privileged onto the namespace.
  • Hook RBAC is scoped to verbs: [get, patch] on resourceNames: [cozy-system] only.
  • hook-delete-policy: before-hook-creation,hook-succeeded so the hook surface only exists during install/upgrade.

Install commands now use helm upgrade --install --namespace cozy-system --create-namespace … — standard pattern, matches kube-prometheus-stack / argo-cd / cert-manager.

Verification

End-to-end on a kind cluster:

  • Cold install: 3.3s clean, no retries needed
  • Upgrade (re-run): 3.0s, idempotent, namespace stays labeled
  • Helm uninstall + re-install: clean

Test plan

  • CI on this PR passes E2E
  • Existing clusters: namespace already labeled correctly, hook re-asserts on next upgrade — no migration needed
fix(installer): cold installs no longer fail on the chart's Namespace resource colliding with `helm --create-namespace`; cozy-system is now bootstrapped via a pre-install hook.

Summary by CodeRabbit

  • New Features

    • Added an automated pre-install/pre-upgrade step that applies system identity and pod-security labels to the system namespace during deployment, run in a hardened one-shot task before normal workloads start.
  • Chores

    • Separated namespace initialization from the operator install flow so the operator no longer renders or retains the system namespace directly, improving installation sequencing.

@dosubot dosubot Bot added the size/XS This PR changes 0-9 lines, ignoring generated files label Apr 28, 2026
@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 Apr 28, 2026
@coderabbitai

coderabbitai Bot commented Apr 28, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Adds a conditional cozy-system Namespace rendering path plus a Helm pre-install/pre-upgrade hook that provisions RBAC and runs a one-shot Job to label the cozy-system namespace; removes the previously rendered Namespace from the operator template and updates manifests/Makefile and e2e test to reflect the change.

Changes

Namespace rendering & labeler flow

Layer / File(s) Summary
Values / Feature Gate
packages/core/installer/values.yaml
Adds bareNamespace: false Helm value to gate rendering of a standalone Namespace for kubectl-apply use.
Namespace Template
packages/core/installer/templates/cozy-system-namespace.yaml
New template that conditionally renders Namespace cozy-system with cozystack.io/system and pod-security.kubernetes.io/enforce: privileged when bareNamespace=true, and includes helm.sh/resource-policy: keep.
Makefile / Manifests Output
Makefile
manifests target now sets --set bareNamespace=true for talos/generic/hosted variants and renders templates/cozy-system-namespace.yaml alongside templates/cozystack-operator.yaml for kubectl-apply artifacts; adds explanatory comments.
Operator Template Adjustment
packages/core/installer/templates/cozystack-operator.yaml
Removes the rendered Namespace object for cozy-system; operator manifests now start at the ServiceAccount/RBAC level.
Namespace Labeling Hook: RBAC & SA
packages/core/installer/templates/cozy-system-labels.yaml
Adds Helm-hooked ServiceAccount kube-system/cozy-system-labeler, ClusterRole limited to get/patch on namespaces resourceName cozy-system, and ClusterRoleBinding with hook annotations and weights.
Namespace Labeling Hook: Job Implementation
packages/core/installer/templates/cozy-system-labels.yaml
Adds Helm-hooked Job cozy-system-labeler running pinned alpine/k8s image; hardened pod/container securityContext, conditional hostNetwork and tolerations for non-hosted variants, and command kubectl label --overwrite namespace cozy-system cozystack.io/system=true pod-security.kubernetes.io/enforce=privileged.
Namespace Labeling Hook: Env Wiring
packages/core/installer/templates/cozy-system-labels.yaml
Conditionally sets KUBERNETES_SERVICE_HOST/PORT for talos (localhost:7445) and generic (from cozystack.apiServerHost/apiServerPort); hosted variant leaves env: [].
Tests
hack/e2e-install-cozystack.bats
E2E install test now asserts that cozy-system namespace labels pod-security.kubernetes.io/enforce=privileged and cozystack.io/system=true are present immediately after Helm install.

Sequence Diagram(s)

sequenceDiagram
    participant Helm
    participant KubeAPI as Kubernetes API
    participant Scheduler as K8s Scheduler
    participant JobPod as Labeler Pod

    Helm->>KubeAPI: Apply SA, ClusterRole, ClusterRoleBinding, Job (pre-install hook)
    KubeAPI-->>Scheduler: Admit Job -> create Pod
    Scheduler->>KubeAPI: Create Pod (with hostNetwork/tolerations as applicable)
    JobPod->>KubeAPI: kubectl label --overwrite namespace cozy-system (apply labels)
    KubeAPI-->>JobPod: 200 OK (labels updated)
    JobPod->>KubeAPI: Exit/succeed (Job completes)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped in quick, a tiny job to run,
A label here, a security bit done.
I wore no capes, I dropped them all away,
Stamped cozy marks at the break of day,
A quiet hop — the cluster's set, I'm done!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing the installer to bootstrap cozy-system namespace using Helm's --create-namespace flag with a label hook, which directly addresses the core issue.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/installer-namespace-bootstrap

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 and usage tips.

@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 refactors how the cozy-system namespace is managed to avoid conflicts between Helm's native namespace creation and the chart's own resource definitions. By removing the static Namespace resource and replacing it with a dynamic labeling hook, the installation process becomes more robust and compatible with standard Helm deployment patterns.

Highlights

  • Namespace Management: Removed the explicit Namespace resource from the chart to resolve conflicts with Helm's --create-namespace functionality.
  • Labeling Hook: Introduced a pre-install/pre-upgrade Helm hook that uses a Job to apply necessary labels to the cozy-system namespace.

🧠 New Feature in Public Preview: You can now enable Memory 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 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 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 counter productive. 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 static Namespace resource for cozy-system and introduces a pre-install/upgrade hook to label the namespace, addressing a bootstrap issue where Helm v3 cannot adopt pre-created namespaces. Feedback was provided regarding the new cozy-system-labeler Job, which lacks mandatory resource requests, limits, and a securityContext as required by the repository style guide. Additionally, the PR description is missing the required release-note block.

Comment on lines +68 to +82
spec:
serviceAccountName: cozy-system-labeler
restartPolicy: OnFailure
containers:
- name: kubectl
image: alpine/k8s:1.32.0
imagePullPolicy: IfNotPresent
command:
- kubectl
- label
- --overwrite
- namespace
- cozy-system
- cozystack.io/system=true
- pod-security.kubernetes.io/enforce=privileged

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.

security-medium medium

The Job is missing resource requests/limits and a proper securityContext. According to the repository style guide (lines 82 and 99), new workloads must have resource limits and should not run as root without justification. Adding these ensures better cluster security and stability. Additionally, consider moving the hardcoded image alpine/k8s:1.32.0 to values.yaml to follow the project's image management pattern.

    spec:
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        seccompProfile:
          type: RuntimeDefault
      serviceAccountName: cozy-system-labeler
      restartPolicy: OnFailure
      containers:
      - name: kubectl
        image: alpine/k8s:1.32.0
        imagePullPolicy: IfNotPresent
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop:
            - ALL
        resources:
          requests:
            cpu: 10m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 128Mi
        command:
        - kubectl
        - label
        - --overwrite
        - namespace
        - cozy-system
        - cozystack.io/system=true
        - pod-security.kubernetes.io/enforce=privileged
References
  1. Flag missing securityContext and containers running as root without an explicit reason. (link)
  2. Missing resource requests/limits on new workloads. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 85ced0d:

  • securityContext: capabilities.drop: [ALL], allowPrivilegeEscalation: false, pod-level seccompProfile: RuntimeDefault.
  • resources: requests 10m / 32Mi, limits 100m / 128Mi.
  • Image pinned by digest (alpine/k8s:1.32.0@sha256:4c28b0f0c6accfa07fa449493ea936291e4ff1270500571d73f75bb406676c3b). Keeping it inline rather than promoting to values.yaml — this is a one-shot bootstrap Job that auto-deletes after success, so a values knob would be noise nobody touches.

Skipped runAsNonRoot / runAsUser: 65534 and readOnlyRootFilesystem from your suggestion: upstream alpine/k8s runs as root and kubectl writes its discovery cache under $HOME, so both would require either an image rebase or extra emptyDir mounts for a Job that lives ~1s and is deleted on success. Not worth the surface area here.

@@ -0,0 +1,82 @@
{{/*

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

The pull request description is missing the required release-note block. According to the repository style guide (line 51), every PR must include a block in the following format:

```release-note
type(scope): human-readable changelog entry

<details>
<summary>References</summary>

1. PR body must contain a release note block.
</details>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the release-note block to the PR description.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
packages/core/installer/templates/cozy-system-labels.yaml (1)

22-22: Clean up hook resources on failure as well.

Current hook delete policy leaves temporary SA/RBAC/Job objects behind when the hook fails. Consider adding hook-failed to avoid lingering cluster-scoped permissions after aborted installs.

♻️ Suggested change
-    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
+    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed

Apply the same change to all four hook resources in this file.

Also applies to: 31-31, 45-45, 63-63

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/installer/templates/cozy-system-labels.yaml` at line 22, The
helm hook delete policy currently only uses
"before-hook-creation,hook-succeeded" which leaves ServiceAccount/RBAC/Job
resources when a hook fails; update each hook resource's
helm.sh/hook-delete-policy annotation (the lines with
"helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded") to include
"hook-failed" so it becomes "before-hook-creation,hook-succeeded,hook-failed"
for all four hook resources in this template.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/core/installer/templates/cozy-system-labels.yaml`:
- Line 22: The helm hook delete policy currently only uses
"before-hook-creation,hook-succeeded" which leaves ServiceAccount/RBAC/Job
resources when a hook fails; update each hook resource's
helm.sh/hook-delete-policy annotation (the lines with
"helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded") to include
"hook-failed" so it becomes "before-hook-creation,hook-succeeded,hook-failed"
for all four hook resources in this template.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0249f0cd-aaa1-4ca4-9551-bf22df075e1c

📥 Commits

Reviewing files that changed from the base of the PR and between b46151a and c0b76b1.

📒 Files selected for processing (1)
  • packages/core/installer/templates/cozy-system-labels.yaml

IvanHunters
IvanHunters previously approved these changes Apr 29, 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.

The fix is correct — replacing the shipped Namespace resource with --create-namespace + a label hook is the standard pattern, and the root cause diagnosis (Helm pre-creates the namespace without ownership annotations, causing the chart's Namespace apply to fail) is accurate. Verified against the CI install script (e2e-install-cozystack.bats) — it already uses --create-namespace. RBAC is minimal (get/patch on named resource only), hook weights and delete policies look right, and the scheduling config (hostNetwork: true + tolerations) correctly mirrors cozystack-operator.yaml.

Two non-blocking notes:

  1. Missing explicit hosted variant case in the hook's env section — cozystack-operator.yaml handles all three variants explicitly, including {{- else if eq .Values.cozystackOperator.variant "hosted" }} with a # Hosted: use in-cluster service account, no env override needed comment. The hook silently falls through to "no env block" for hosted, which is functionally correct (hosted cluster has working CNI and ClusterIP routing), but adding the explicit case would keep it consistent with the operator and document the intent.

  2. alpine/k8s:1.32.0 is not pinned by digest — the operator image uses a @sha256:… pin. Given that this hook runs at install time with namespace-patch RBAC, pinning the image digest would be consistent with the rest of the chart.

LGTM

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label Apr 29, 2026
@myasnikovdaniil
myasnikovdaniil marked this pull request as draft April 30, 2026 09:56
…hook

The chart used to ship `Namespace cozy-system` directly, which trips a
helm v3 chicken-and-egg whenever the install command uses
`--create-namespace`:

  - WITH `--create-namespace`, helm pre-creates the namespace via plain
    kubectl-create (no helm meta annotations); the chart's own Namespace
    apply then fails with `already exists`.
  - WITHOUT `--create-namespace`, helm fails immediately because it
    cannot write its release-secret to a non-existent namespace.

The bug was hidden by the 3x retry on the install step in
`.github/workflows/pull-requests.yaml`: attempt 1 fails with the
conflict, attempt 2 sees the failed release and takes the upgrade code
path (patch-merge instead of strict-create). Surfaces cleanly when the
retry is dropped; reproducible on every cold install.

Fix has two pieces:

1. Drop the chart-side `Namespace cozy-system` resource. Install
   commands now use the standard `helm upgrade --install --namespace
   cozy-system --create-namespace` (matches kube-prometheus-stack /
   argo-cd / cert-manager).

2. Add a pre-install/pre-upgrade Job hook (`cozy-system-labeler`) that
   patches `cozystack.io/system=true` and
   `pod-security.kubernetes.io/enforce=privileged` onto the namespace
   so the cozystack-operator pod (hostNetwork=true) is admitted.

The hook runs in `kube-system`, not `cozy-system`. Reason: the operator
pod needs hostNetwork=true (no CNI yet at install time), which violates
PodSecurity baseline. cozy-system therefore needs enforce=privileged
*before* the operator pod is admitted. A labeler pod inside cozy-system
would itself need hostNetwork=true and hit the same admission denial:

    pods "cozy-system-labeler-..." is forbidden: violates PodSecurity
    "baseline:latest": host namespaces (hostNetwork=true)

Job admission denial means no pod is ever created, .status.failed never
increments and backoffLimit never trips. helm's --wait waits for
Complete=True forever and surfaces only the generic "timed out waiting
for the condition" at its budget.

`kube-system` is PSA-exempt on Talos
(`defaults.exemptions.namespaces` in the apiserver
PodSecurityConfiguration) and unlabeled on vanilla kubeadm/kind/k3s, so
hostNetwork pods are admitted there. The hook's ServiceAccount is bound
to a ClusterRole scoped via `resourceNames: ["cozy-system"]` to the one
target namespace, keeping the cluster permission grant minimal.
`hook-delete-policy: before-hook-creation,hook-succeeded` ensures the
SA, RBAC, and Job are cleaned up after a successful install/upgrade.

Verified end-to-end in CI on the parallel consolidation branch
feat/e2e-optimization (PR #2500): cold install passes admission, the
operator pod comes up, all downstream E2E tests pass.

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@myasnikovdaniil
myasnikovdaniil force-pushed the fix/installer-namespace-bootstrap branch from c0b76b1 to 4527320 Compare May 2, 2026 12:48
@github-actions github-actions Bot added size/L This PR changes 100-499 lines, ignoring generated files and removed size/XS This PR changes 0-9 lines, ignoring generated files labels May 2, 2026
myasnikovdaniil added a commit that referenced this pull request May 2, 2026
Re-introduces the pre-install/pre-upgrade Job hook (cozy-system-labeler)
that was added by 3a87485/4754f57f and dropped by be0a668, but moves
it into kube-system to dodge the PSA admission denial that broke the
in-namespace approach:

    pods "cozy-system-labeler-..." is forbidden: violates PodSecurity
    "baseline:latest": host namespaces (hostNetwork=true)

The operator pod needs hostNetwork=true (no CNI yet at install time),
which violates PodSecurity baseline. cozy-system therefore needs
enforce=privileged before the operator pod can be admitted. A labeler
pod inside cozy-system would itself need hostNetwork=true and hit the
same admission denial — Job admission denial means no pod is ever
created, .status.failed never increments and backoffLimit never trips,
helm waits for Complete=True until its budget elapses.

`kube-system` is PSA-exempt on Talos (defaults.exemptions.namespaces in
the apiserver PodSecurityConfiguration) and unlabeled on vanilla
kubeadm/kind/k3s, so hostNetwork pods are admitted there. The hook's
ServiceAccount is bound to a ClusterRole scoped via
`resourceNames: ["cozy-system"]` to the one target namespace, keeping
the cluster permission grant minimal.

Also revert the e2e bats workaround (ddc429c) that pre-applied
cozy-system manually before helm install: the labeler hook handles
the labels in-chart now, so the bats can go back to plain
`helm upgrade --install --namespace cozy-system --create-namespace ...`.

Mirrors the corresponding fix on PR #2508
(fix/installer-namespace-bootstrap) so the two branches converge on
the same resolution.

Verified end-to-end in CI on this branch: cold install passes admission,
operator pod comes up, all downstream E2E tests pass.

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 3, 2026
The previous run on d3e3677 raced PR #2508's concurrent build for
`backupstrategy-controller:latest` and lost:

    ERROR: failed to build: unknown: Conflicted with another upload of
    the same manifest.

Unrelated to the chart change at HEAD — pure shared-registry collision
on :latest. Empty commit to retrigger the workflow.

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
Address review feedback on PR #2508:

- Pin alpine/k8s by digest (matches operator image pinning style).
- Add resources requests/limits and a minimal securityContext to the
  labeler Job: capabilities drop ALL, allowPrivilegeEscalation: false,
  seccompProfile RuntimeDefault.
- Include hook-failed in helm.sh/hook-delete-policy across all four
  hook resources so a failed install doesn't leave SA / ClusterRole /
  ClusterRoleBinding / Job objects behind.
- Add explicit hosted variant case in the env block, mirroring the
  pattern in cozystack-operator.yaml.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

IvanHunters thanks for the review. Both follow-ups from your notes are folded into 85ced0d — explicit hosted variant case in the hook env block, and alpine/k8s:1.32.0 pinned by digest.

@myasnikovdaniil
myasnikovdaniil marked this pull request as ready for review May 4, 2026 06:10
@dosubot dosubot Bot added size:XS and removed size/L This PR changes 100-499 lines, ignoring generated files labels May 4, 2026

@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 replaces the static cozy-system namespace definition with a pre-install/upgrade hook to label the namespace, resolving a circular dependency with PodSecurity admission. Feedback focuses on improving troubleshooting by retaining hook resources on failure and moving the hardcoded image digest to values.yaml to comply with the project's style guide.

annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-200"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed

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

Including hook-failed in the helm.sh/hook-delete-policy will cause the ServiceAccount to be deleted even if the hook fails. This can make troubleshooting more difficult as the associated Job's environment is partially cleaned up. Since before-hook-creation is already present, previous failed attempts will still be cleaned up on the next run.

    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See #2508 (comment) — same reasoning applies; cleanliness on failed installs outweighs the debug-window for the RBAC objects.

annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-180"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed

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

Including hook-failed in the helm.sh/hook-delete-policy for the ClusterRole hinders debugging if the hook fails. before-hook-creation is sufficient to ensure a clean state for subsequent attempts.

    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See #2508 (comment) — same reasoning applies; cleanliness on failed installs outweighs the debug-window for the RBAC objects.

annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-160"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed

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

Including hook-failed in the helm.sh/hook-delete-policy for the ClusterRoleBinding hinders debugging if the hook fails. before-hook-creation is sufficient to ensure a clean state for subsequent attempts.

    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See #2508 (comment) — same reasoning applies; cleanliness on failed installs outweighs the debug-window for the RBAC objects.

annotations:
helm.sh/hook: pre-install,pre-upgrade
helm.sh/hook-weight: "-100"
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded,hook-failed

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

Including hook-failed in the helm.sh/hook-delete-policy for the Job will cause it to be deleted immediately upon failure, making it impossible to inspect the pod logs for troubleshooting. Removing hook-failed allows the failed pod to persist for inspection, while before-hook-creation ensures it is cleaned up before the next installation attempt.

    helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping hook-failed on the Job. Trade-off acknowledged — a failed install loses the labeler pod's logs. But the Job is kubectl label namespace cozy-system ...; the failure modes are almost always (a) apiserver unreachable from a kube-system host-network pod or (b) RBAC mismatch — both surface in helm install's output, not in the pod logs. The previous reviewer asked for hook-failed precisely to avoid leaving SA / RBAC / Job objects behind on a failed install, and that argument wins for me here.

type: RuntimeDefault
containers:
- name: kubectl
image: alpine/k8s:1.32.0@sha256:4c28b0f0c6accfa07fa449493ea936291e4ff1270500571d73f75bb406676c3b

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

The image and its digest are hardcoded directly in the template. Following the project's established pattern (as seen in cozystack-operator.yaml) and the repository style guide (line 31), images and digests should ideally be defined in values.yaml. This facilitates centralized management and allows CI to update digests automatically.

References
  1. Image digest values in values.yaml — set by CI via make image, not by humans. (link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same call as on the line-139 thread: the labeler image is third-party (alpine/k8s), not managed by make image's CI digest-bump, and the Job is a one-shot pre-install hook that auto-deletes on success. A values.yaml knob here would be config nobody touches. Pinned by digest in the template covers the supply-chain concern.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (1)
packages/core/installer/templates/cozy-system-labels.yaml (1)

86-86: ⚡ Quick win

Set dnsPolicy for the hostNetwork hook.

Kubernetes recommends ClusterFirstWithHostNet for Pods that run with hostNetwork: true; without it, DNS falls back to Default, which makes the generic API-server endpoint path more fragile if cozystack.apiServerHost is a hostname. (kubernetes.io)

Suggested fix
       hostNetwork: true
+      dnsPolicy: ClusterFirstWithHostNet
       tolerations:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/installer/templates/cozy-system-labels.yaml` at line 86, Add
the recommended dnsPolicy when using hostNetwork: true in the pod spec to avoid
DNS falling back to Default; specifically update the block containing
hostNetwork: true to also set dnsPolicy: ClusterFirstWithHostNet so Kubernetes
uses ClusterFirstWithHostNet for DNS resolution when cozystack.apiServerHost may
be a hostname.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/core/installer/templates/cozy-system-labels.yaml`:
- Line 86: Add the recommended dnsPolicy when using hostNetwork: true in the pod
spec to avoid DNS falling back to Default; specifically update the block
containing hostNetwork: true to also set dnsPolicy: ClusterFirstWithHostNet so
Kubernetes uses ClusterFirstWithHostNet for DNS resolution when
cozystack.apiServerHost may be a hostname.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: be7f72b8-ac43-4d78-adb4-e84ff655325d

📥 Commits

Reviewing files that changed from the base of the PR and between c0b76b1 and 85ced0d.

📒 Files selected for processing (2)
  • packages/core/installer/templates/cozy-system-labels.yaml
  • packages/core/installer/templates/cozystack-operator.yaml
💤 Files with no reviewable changes (1)
  • packages/core/installer/templates/cozystack-operator.yaml

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.

Solid analysis and the approach is correct: replacing the chart-shipped Namespace cozy-system with --create-namespace + a kube-system pre-install hook is the standard pattern (mirrors kube-prometheus-stack / argo-cd / cert-manager), and the chicken-and-egg root cause is real (the conflict is masked today by the 3× retry on Install Cozystack in .github/workflows/pull-requests.yaml; hack/e2e-install-cozystack.bats already drives the --create-namespace path end-to-end). Hook hygiene is good: RBAC scoped via resourceNames: [cozy-system] to get,patch only, ordered hook weights, digest-pinned image, hardened pod (drop ALL caps, no privilege escalation, RuntimeDefault seccomp, requests/limits set), all three operator variants covered. Existing-cluster migration is safe — the live cozy-system carries helm.sh/resource-policy: keep, so the upgrade keeps it and the hook re-asserts labels.

One blocker before merge:

1. alpine/k8s:1.32.0 — image is from an EOL Kubernetes minor.

Kubernetes 1.32 went EOL on 2026-02-28 (per endoflife.date); supported minors as of today are 1.33 (EOL 2026-06-28), 1.34 (EOL 2026-10-27), 1.35 (current). Two additional concerns:

  • The pin is 1.32.0 — the very first patch. 13 patch releases (1.32.1 .. 1.32.13) shipped before EOL, all skipped. Image 1.32.0 was last updated 2024-12-15 (~17 months stale), so neither the kubectl binary nor the alpine base receives security backports.
  • kubectl is supported within ±1 minor of the apiserver. On clusters running 1.34/1.35 (typical: current Talos, hosted EKS/GKE/AKS) the 1.32 client is outside the formally-supported skew window. kubectl label is simple enough that it usually works, but the supply-chain and CVE arguments still apply because this Job runs at install time with namespace-patch RBAC.
  • Other in-tree hooks pin alpine/k8s:1.33.4 (packages/system/cozystack-api/templates/hook.yaml, packages/system/seaweedfs/templates/hook.yaml, packages/apps/vm-disk/templates/pvc-resize-hook.yaml) — itself not the freshest patch (1.33.11 last_updated 2026-04-19), but at least in a supported minor. This PR introduces unnecessary version drift from that convention.

Minimum: bump to alpine/k8s:1.33.11@sha256:… (matches in-tree convention, supported until 2026-06-28). Better: 1.34.7 or 1.35.4 (both last_updated 2026-04-19) — aligns with the upstream support window and avoids needing another bump in 2 months.

Non-blocking follow-ups (consider for this PR or a fast-follow):

  1. hosted variant on managed clusters with strict-PSA kube-system. Template comment claims kube-system is PSA-exempt on Talos and unlabeled on vanilla kubeadm/kind/k3s, but says nothing about EKS/GKE/AKS/OpenShift. On a managed cluster where kube-system carries pod-security.kubernetes.io/enforce: baseline (or stricter), the Job is rejected because hostNetwork: true violates baseline. In hosted mode the CNI is already provided by the platform, so the hostNetwork bootstrap need does not apply — a cleaner split would have the hosted branch run without hostNetwork and without the not-ready/CNI tolerations. At minimum, expand the docstring to acknowledge the assumption.

  2. dnsPolicy on the hostNetwork pod (CodeRabbit's nitpick). Current paths are DNS-free (localhost:7445, kubelet-injected KUBERNETES_SERVICE_HOST/PORT, IP-only apiServerHost), so this is cosmetic today. Note that on cold install the cluster DNS does not exist yet, so ClusterFirstWithHostNet would not actually help — keeping Default is defensible. If kept, a one-line comment explaining why would help future readers.

  3. E2E assertion for the PSA label. No helm-unittest fixtures for packages/core/installer/ (consistent with prior chart state — no precedent). Worth a one-line bats assertion in e2e-install-cozystack.bats after the helm install so the contract is explicit and silent hook regressions surface:

kubectl get ns cozy-system -o jsonpath='{.metadata.labels.pod-security\.kubernetes\.io/enforce}' | grep -qx privileged
  1. make manifests static-render path is incomplete. Makefile:45-58 renders only templates/cozystack-operator.yaml via --show-only and hack/upload-assets.sh:7-9 uploads those as release artifacts for users on a kubectl-apply path. The new hook is excluded. If kubectl-apply remains a supported install path on Talos (where namespace PSA enforce matters), users on that path won't get the hook and the operator pod won't be admitted. Either fold cozy-system-labels.yaml into the rendered manifest (multi --show-only or drop --show-only and post-filter), or document that kubectl-apply path is deprecated in favor of helm.

  2. PR description nit. "shipped its own Namespace resource since the chart's introduction (Jan 2026)" — chart's introduction is c43db3b7 2025-12-25 (December 2025). Doesn't change the analysis.

@dosubot dosubot Bot removed the lgtm This PR has been approved by a maintainer label May 4, 2026
- Bump alpine/k8s to 1.33.4 (was 1.32.0, EOL); matches in-tree pin used
  by cozystack-api, seaweedfs, vm-disk, and vm-instance hooks.
- Skip hostNetwork=true and not-ready/CNI tolerations on the hosted
  variant: managed clusters provide CNI before install, and kube-system
  may carry strict PSA there that would reject hostNetwork pods.
- Add packages/core/installer/templates/cozy-system-namespace.yaml gated
  on .Values.bareNamespace (default false). The top-level Makefile's
  manifests target now passes --set bareNamespace=true and renders
  Namespace + operator into _out/assets/cozystack-operator-*.yaml so
  the kubectl-apply install path stays self-contained. Helm
  install/upgrade callers keep the default and bootstrap via
  --create-namespace + the pre-install labeler hook.
- Add a bats assertion in hack/e2e-install-cozystack.bats checking
  cozy-system carries pod-security.kubernetes.io/enforce=privileged and
  cozystack.io/system=true after helm install.
- Inline comment near hostNetwork=true documenting why dnsPolicy left
  at Default (cluster DNS not up at install time;
  ClusterFirstWithHostNet would not help).

Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@dosubot dosubot Bot added size:S and removed size:XS labels May 4, 2026
@github-actions github-actions Bot added the size/L This PR changes 100-499 lines, ignoring generated files label May 4, 2026
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Addressed everything actionable in 730037c.

1. Image (blocker) — bumped to docker.io/alpine/k8s:1.33.4@sha256:b0523f0a244ddc4c8e055aa335c040d3d78b3ead5528f4544395f7f9f69c7b68, matching the in-tree convention (cozystack-api, seaweedfs, vm-disk, vm-instance all pin the same minor). Kept the digest pin on top of the version tag. Picked 1.33.4 over 1.33.11/1.34.7/1.35.4 for tree-wide consistency; refreshing the rest of the tree to a fresher patch is separate work.

2. Hosted variant — wrapped hostNetwork: true and the four CNI-not-ready tolerations in {{- if ne .Values.cozystackOperator.variant "hosted" }}. Docstring at the top of cozy-system-labels.yaml now calls out the EKS/GKE/AKS/OpenShift assumption explicitly (managed clusters provide CNI; kube-system may carry strict PSA that would reject hostNetwork pods).

3. dnsPolicy — kept Default, added an inline comment explaining the trade-off and why ClusterFirstWithHostNet would not help (cluster DNS isn't up at install time).

4. Bats assertion — added two kubectl get ns cozy-system -o jsonpath=... checks in hack/e2e-install-cozystack.bats after the helm install, asserting both pod-security.kubernetes.io/enforce=privileged and cozystack.io/system=true.

5. Manifests render path — new packages/core/installer/templates/cozy-system-namespace.yaml gated on .Values.bareNamespace (default false). Top-level Makefile's manifests target sets it true and uses multi---show-only so kubectl-apply artifacts (_out/assets/cozystack-operator-*.yaml) get both Namespace and operator. Helm install/upgrade callers leave it default and use --create-namespace + the labeler hook. make manifests verified for talos / generic / hosted.

6. PR description nit — fixed; chart was introduced 2025-12-25 (December 2025), not January.

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. All findings from the prior review are addressed in 730037c85:

  • Blocker (alpine/k8s:1.32.0 EOL'd): bumped to docker.io/alpine/k8s:1.33.4@sha256:b0523f0a244ddc4c8e055aa335c040d3d78b3ead5528f4544395f7f9f69c7b68. Aligns with the in-tree convention (other hooks: cozystack-api, seaweedfs, vm-disk pvc-resize) and lands inside a supported Kubernetes minor (1.33 EOL 2026-06-28, ~7 weeks of cushion). The digest pin is preserved.
  • Non-blocking #1 (hosted variant on strict-PSA kube-system): the hostNetwork: true block and the not-ready/CNI tolerations are now gated {{- if ne .Values.cozystackOperator.variant "hosted" }}. Verified locally: helm template ... --set cozystackOperator.variant=hosted --show-only templates/cozy-system-labels.yaml | grep hostNetwork → empty. On managed clusters (EKS/GKE/AKS/OpenShift) where kube-system can carry pod-security.kubernetes.io/enforce: baseline, the labeler is now admissible. The docstring at the top of the file explains the asymmetry clearly.
  • Non-blocking #2 (dnsPolicy): inline comment explains why the default ClusterFirst is correct for the cold-install scenario (kubectl reaches the apiserver via KubePrism/explicit IP/kubelet-injected env, no DNS lookups happen, and ClusterFirstWithHostNet would not help because cluster DNS is not up yet). No code change needed; the rationale is preserved for future readers.
  • Non-blocking #4 (E2E assertion for PSA label): hack/e2e-install-cozystack.bats:19-25 now asserts kubectl get ns cozy-system -o jsonpath='{.metadata.labels.pod-security\.kubernetes\.io/enforce}' | grep -qx privileged and the same for cozystack.io/system=true. Exact one-liner I called out. E2E Tests is green on this HEAD — the hook is actually stamping the labels in the real install path, not just rendering correctly in unit tests.
  • Non-blocking #5 (make manifests kubectl-apply path): this is the cleaner solution than what I proposed. A new templates/cozy-system-namespace.yaml is gated on .Values.bareNamespace (default false), and the top-level Makefile passes --set bareNamespace=true together with multi --show-only for the rendered release artifacts. Helm install/upgrade callers leave bareNamespace=false and the pre-install hook handles namespace labeling after --create-namespace creates it bare. kubectl-apply users get the Namespace resource alongside the operator. Two paths cleanly separated without the helm install --create-namespace chicken-and-egg coming back through the manifest render. The doc comment in the new template makes the contract explicit.

Verification:

  • helm template exercise of all three variants (default talos / generic / hosted) renders cleanly. Hosted variant correctly omits hostNetwork + tolerations; talos/generic preserve the original block.
  • CI green: pre-commit, Build, E2E Tests, CodeRabbit, Verify generated code is up to date.
  • Migration safety preserved: existing clusters' cozy-system namespace still carries helm.sh/resource-policy: keep from the prior chart, so the upgrade keeps it; the relabeled hook re-asserts the PSA + identity labels and the new bats assertion catches a future regression where the hook silently fails.

Pre-existing repo-level note from the prior review (still non-blocking, not introduced by this PR): the PR-body description nit "Jan 2026" → actual chart introduction is c43db3b7 2025-12-25 (December). Doesn't change the analysis.

@dosubot dosubot Bot added the lgtm This PR has been approved by a maintainer label May 5, 2026
@myasnikovdaniil
myasnikovdaniil merged commit 086e9bb into main May 5, 2026
16 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the fix/installer-namespace-bootstrap branch May 5, 2026 09:25
myasnikovdaniil added a commit that referenced this pull request May 7, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 8, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
@github-actions github-actions Bot added the size/S This PR changes 10-29 lines, ignoring generated files label May 11, 2026
myasnikovdaniil added a commit that referenced this pull request May 19, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 25, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 26, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 27, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 27, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 28, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request May 29, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request Jun 5, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
myasnikovdaniil added a commit that referenced this pull request Jun 5, 2026
Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry
loop never recovered a flake, only stretched deterministic failures
and tripled diagnostic wall-time. Same data shape on `Install
Cozystack`. Drop both.

`Prepare environment` keeps its 3x retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network)
where transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide`
and `kubectl get events -A` under a collapsible group so triage
starts with the actual broken-state snapshot.

Depends on:
- #2508 — installer namespace bootstrap (Helm namespace-ownership conflict)
- #2509 — operator HelmRelease config knobs (`seaweedfs-system` 2-min wait race)
- #2528 — harbor bucket-secret + BucketInfo gating (harbor ValuesError)
- #2529 — objectstorage-controller BucketAccess conflict retry
- the daniil/split-vminstance PR (vminstance disk race + VM IP/ready timeouts)
- the daniil/split-event-driven PR (existence backstops surfacing real errors)

Until those land, dropping the retry will fail CI for unrelated PRs
that hit the seaweedfs / harbor / installer / vminstance races.

Signed-off-by: Myasnikov Daniil <[email protected]>
Assisted-By: Claude <[email protected]>
Aleksei Sviridkin (lexfrei) added a commit that referenced this pull request Jun 9, 2026
…ay.bats tenant teardown (#2558)

## What this PR does

Drops the 3× retry loop on `Run E2E tests` and `Install Cozystack into
sandbox`. `Prepare environment` keeps its 3× retry — that step is pure
infrastructure (Talos image download, sandbox VM boot, network) where
transient runner hiccups warrant a retry.

On failure, the test step now captures `kubectl get hr -A -o wide` and
`kubectl get events -A` under a collapsible group so triage starts with
the actual broken-state snapshot.

> [!NOTE]
> An earlier revision of this PR also doubled every bats timeout. That
commit was dropped in a rebase and is intentionally **not restored**:
the timeout class that actually matters (per-app HR-Ready waits) has
since been standardized at 5m on `main` (7b9f286), making a blanket 2×
redundant.

**Fixes gateway.bats teardown leakage.** The nested-tenant tests deleted
tenants fire-and-forget, parent and child back-to-back. The leftover
uninstalls (each blocked on a cleanup Job, parents wedged on
still-terminating child namespaces) plus one mid-install child HR
occupied exactly 5 workers on the `--concurrent=5` tenants
helm-controller shard, starving whichever app test ran next — observed
as the harbor HR sitting unreconciled for its whole 5m HR-Ready budget
in [run
27020081550](https://github.com/cozystack/cozystack/actions/runs/27020081550),
surfaced by this PR's own retry removal + diagnostics dump. Teardown now
deletes child→parent with hard `wait hr --for=delete` between, so a
wedged tenant uninstall fails gateway.bats itself, not an innocent
neighbor.

## Why

Audit of 30 successful PR runs found that across 5 sampled failure
attempts, **25/25 retries** on `Run E2E tests` failed — the retry loop
never recovered a flake, only stretched deterministic failures and
tripled diagnostic wall-time. Same data shape on `Install Cozystack`.

Beyond wasted CI time, the retry was hiding ~10 deterministic bugs (Helm
namespace-ownership conflict, seaweedfs HR timeout, harbor BucketInfo
wiring, vminstance disk race, etc.). Each failure looked like a "flake"
because the retry sometimes coincided with whatever transient state had
cleared — the retry never fixed the bug, just delayed surfacing.

## Dependencies

The deterministic bugs the retry was masking are now fixed on `main`:
- ✅ **#2508** — installer namespace bootstrap (Helm namespace-ownership
conflict on cold install) — merged
- ✅ **#2509** — operator HelmRelease config knobs (`seaweedfs-system`
2-min wait race within Flux's 5-min reconcile windows) — merged
- ✅ **#2528** — harbor bucket-secret + BucketInfo gating (harbor
`ValuesError` on first install) — merged
- ✅ **#2529** — objectstorage-controller BucketAccess conflict retry —
merged

Companion PRs in the #2619 split (independent of this PR,
ordering-wise):
- **#2602** — Flux v2.8.0 + chart fixes
- **#2601** — seaweedfs-system split

This PR does NOT depend on #2602/#2601 — it now touches only the
workflow file and gateway.bats teardown, both on top of fresh `main`.

Surfaced from #2500.

### Release note

```release-note
NONE
```


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

## Summary by CodeRabbit

* **Chores**
* CI prepare-environment step now reports plain attempt counts with
clear success/failure messages.
* Install and per-app test steps no longer retry; each runs once and
fails immediately on error. Failed apps log diagnostics and job proceeds
to remaining apps while overall job fails.
* **Tests**
* End-to-end tests and install/prepare flows use longer, more tolerant
timeouts and added existence polling to reduce flakiness and improve
diagnostics.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2558?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- 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/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/api-change Categorizes issue or PR as related to adding, removing, or otherwise changing an API kind/bug Categorizes issue or PR as related to a bug lgtm This PR has been approved by a maintainer size/L This PR changes 100-499 lines, ignoring generated files size/S This PR changes 10-29 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants