Skip to content

ci: consolidated platform & e2e stabilization batch - #2948

Merged
myasnikovdaniil merged 28 commits into
mainfrom
ci-next
Jun 22, 2026
Merged

ci: consolidated platform & e2e stabilization batch#2948
myasnikovdaniil merged 28 commits into
mainfrom
ci-next

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

What

Consolidated landing branch for a set of interdependent platform/CI stabilization fixes. It began as the source PRs in the table below (in dependency order) and has since grown with review-driven fixes and a few production-behavior changes — the full current inventory is under Scope below. Landing them as one unit lets CI run against the real combined state instead of a fragile bottom-up merge train.

Source PRs (dependency order)

# Commit Source PR Addresses
1 fix(capi): startupProbe on capi-controller-manager #2946 capi crashloop during cert provisioning
2 test(e2e): bucket.bats port-forward + S3 client reliability #2944 flaky bucket test
3 fix(seaweedfs): restore -lock BucketClass, s3 svc name, drop volumeSizeLimitMB #2943 missing bucket/access classes
4 test(e2e): in-cluster Cilium endpoint-leak healer (install + apps) #2874 cilium "IP already in use" leak
5 refactor(build): mode=max registry cache + main-only warmer #2938 build cache
6 ci(build): isolate each PR build on its own ephemeral runner VM #2939 shared-buildkit contention
7 fix(e2e): LINSTOR post-install waits on a single 15m deadline #2928 LINSTOR provisioning timeouts
8 test(metallb): assert digest-pinned image form, not version literal #2873 brittle metallb assertion

Why consolidated

These form a dependency DAG (verified from CI logs): e.g. 2943 needs 2946, 2938 needs 2874, 2928 needs 2938/2939, 2873 needs 2928. Tested bottom-up, each lower PR runs with none of the fixes above it and can't go green alone. This branch carries all of them, so CI runs against the real combined state.

Verification status

CI is green on the latest head (5653c30): full-suite E2E passes end-to-end and is reproducible (2 of 3 runs on this SHA green). The one red run was a LINSTOR tie-breaker / DRBD-metadata infra flake on the sandbox, unrelated to the diff — a different environmental subsystem failed each run (details in the comments below). The earlier 3-hour crust-gather snapshot hang is fixed in 5653c30.

Scope beyond the original 8

The branch has grown past the 8 source PRs above with review-driven fixes and a few production-behavior changes. For reviewer transparency, the full set:

Review fixes (Aleksei Sviridkin (@lexfrei) review):

  • B1 (blocker)8f41910 converts the SeaweedFS -lock/-readonly BucketClasses and the s3 service-name override into patches/, wired into make update, so a re-vendor no longer drops them.
  • FU1cc99230 grants the cilium leak-healer delete on pods (both delete remedies were RBAC-forbidden).
  • FU3014de3f adds a unit test for the HelmRelease update conflict-retry path.
  • FU46deddc6 makes the leak-healer refuse a disconnect for any non-terminal owner phase, not just Running.
  • FU7f3914d2 sets persist-credentials: false on the build-cache checkout (SHA-pinning is handled repo-wide in ci: harden workflow token permissions and pin actions by SHA #2849).

Production-behavior changes (not in the table above):

  • VPA updateMode: Auto → Initial for etcd (6e9ff90) and monitoring (0997105), plus vmselect/vmstorage minAllowed floors — stops install-time eviction churn.
  • cozystack-api: retry.RetryOnConflict on the Application→HelmRelease update path (728f676).

E2E robustness follow-ups: per-test crust-gather snapshots (50e5f94), EtcdBackupSchedule wait (763b85e), tenant-node single-deadline wait (9e43d45), harbor BucketClaim 10m budget (d7f244f), tenant API via LoadBalancer (9beda4e), crust-gather pin (95f42aa).

Not addressed (by decision): FU2 — the s3 Service keeps seaweedfs.name-based naming via patch (rendered output unchanged; cozystack renders fullname == seaweedfs, so it matches the componentName/fullname siblings in practice). FU6 — fork PR build push is handled by the existing mirror-to-same-repo-branch workflow.

Relates to

#2946 #2944 #2943 #2874 #2938 #2939 #2928 #2873 — if this lands, those can be closed; otherwise they remain the granular per-PR review path.

Summary by CodeRabbit

  • New Features

    • Added CI build cache warming for main branch builds.
    • Added a best-effort in-cluster Cilium endpoint self-heal watchdog for e2e installs.
    • Extended SeaweedFS COSI with object-lock support plus separate readonly access.
  • Improvements

    • Reduced e2e install flakiness with a shared readiness deadline, safer waits, and improved cleanup/diagnostics capture.
    • Prevented install-time churn by switching VPA update mode to Initial.
    • Added container startup probes and updated SeaweedFS volume sizing.
  • Tests

    • Improved e2e robustness (etcd backup schedule waiting, digest-pinned image checks, and corrected S3 port-forwarding).

myasnikovdaniil and others added 8 commits June 18, 2026 10:40
…t provisioning delay (#2946)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…volumeSizeLimitMB (#2943)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… apps (#2874)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…2928)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…eral (#2873)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@github-actions github-actions Bot added the area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review label Jun 18, 2026
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR centralizes Docker Buildx cache configuration into a cache-args Make macro with a WRITE_CACHE toggle, adds a build-main.yaml workflow to warm the registry cache on main pushes, migrates 40+ image Makefiles to the macro, adds an in-cluster Cilium endpoint leak self-healer for e2e, hardens e2e wait logic with shared deadlines and VPA eviction guards, extends the SeaweedFS COSI template with object-lock and readonly access classes, and integrates crust-gather snapshot collection for failed test runs.

Changes

Centralized Docker Buildx Registry Cache

Layer / File(s) Summary
cache-args macro and WRITE_CACHE toggle
hack/common-envs.mk
Changes CACHE_REGISTRY default to $(REGISTRY), adds WRITE_CACHE ?= 0, and defines cache-args macro emitting --cache-from always and --cache-to mode=max,oci-mediatypes=true,image-manifest=true only when WRITE_CACHE=1.
Main-branch cache-warming workflow and PR ephemeral runner
.github/workflows/build-main.yaml, .github/workflows/pull-requests.yaml
Adds build-main.yaml running make build with WRITE_CACHE=1 on main push using an isolated docker-container Buildx builder and OCIR login. Updates PR workflow to use ephemeral oracle-vm-4cpu-16gb-x86-64 runner by default (self-hosted on debug label) with helm-unittest and flux CLI bootstrap.
Migrate 40+ image Makefiles to cache-args macro
packages/apps/{clickhouse,http-cache,kubernetes,mariadb}/Makefile, packages/core/{installer,platform,talos,testing}/Makefile, packages/extra/monitoring/Makefile, packages/system/{backup-controller,backupstrategy-controller,bucket,cilium,cozystack-api,cozystack-controller,dashboard,flux-plunger,grafana-operator,kamaji,keycloak-operator,kilo,kubeovn-plunger,kubeovn-webhook,lineage-controller-webhook,linstor-gui,linstor,metallb,multus,objectstorage-controller,redis-operator}/Makefile, packages/system/metallb/tests/metallb_test.yaml
Replaces explicit --cache-from/--cache-to inline flags with $(call cache-args,...) across all app and system package Makefiles. Updates metallb chart test to assert image digest via matchRegex pattern instead of exact version+sha256 literal.

Cilium Endpoint Leak Self-Healer for e2e

Layer / File(s) Summary
Cilium endpoint leak healer shell script
hack/e2e-cilium-endpoint-leak-healer.sh
Implements continuous polling loop: watches FailedCreatePodSandBox events for "IP already in use" messages, parses pod/namespace/IP, checks pod freshness, locates cilium-agent on the affected node, queries endpoint registry, performs live-pod ownership safety check, and executes cilium-dbg endpoint disconnect for confirmed orphaned endpoints. Includes strict error handling and configurable POLL_INTERVAL.
Kubernetes RBAC and Job manifest for healer
hack/e2e-cilium-leak-healer.yaml
Defines ServiceAccount, ClusterRole (events/pods/pod/exec), ClusterRoleBinding in kube-system, and long-running Job with host networking, broad tolerations, OnFailure restart, and high backoffLimit. Job mounts healer script from ConfigMap.
e2e test setup integration for healer
hack/e2e-install-cozystack.bats
Adds initial @test that creates cilium-leak-healer ConfigMap from the shell script and applies the Kubernetes manifest to deploy the watchdog ahead of the rest of the test suite; all steps are best-effort and do not fail the suite if the job is missing.

e2e Infrastructure Hardening and Diagnostics

Layer / File(s) Summary
Shared-deadline wait_for() helper
hack/e2e-post-install-prep.sh
Replaces per-step fixed timeouts with shared 15-minute DEADLINE and a wait_for() function that dynamically shrinks per-attempt kubectl wait timeouts from remaining deadline, tolerates resources not-yet-created, and fails once deadline elapses. Routes HelmRelease and linstor-controller deployment waits through helper. Extends LINSTOR nodes-online check to 300s with 2s polling interval.
Bucket e2e test port-forward fix
hack/e2e-apps/bucket.bats
Starts kubectl port-forward directly (no bash -c wrapper), probes readiness on 127.0.0.1, and updates all mc alias set endpoints from localhost to 127.0.0.1.
Etcd test backup schedule resource readiness polling
hack/e2e-apps/etcd.bats
Replaces single kubectl get with timed polling loop (120s, 2s sleep) to wait for EtcdBackupSchedule resource before proceeding, avoiding readiness check/resource materialization timing skew from HelmRelease reconciliation.
Kubernetes cleanup hook and node readiness unified loop
hack/e2e-apps/run-kubernetes.sh, hack/e2e-apps/kubernetes-latest.bats, hack/e2e-apps/kubernetes-previous.bats
Adds file-scope cozy_cleanup() function that deletes all kuberneteses.apps.cozystack.io in tenant-test and waits for deletion. Replaces two-phase node readiness logic with single 12-minute polling loop checking for at least 2 nodes reporting Ready. Moves script sourcing to file scope in both Kubernetes test files so cleanup hook registers in parent shell EXIT trap.
VPA updateMode Auto → Initial to prevent install eviction churn
packages/system/monitoring-agents/templates/vpa.yaml, packages/system/monitoring/templates/vpa.yaml, packages/extra/etcd/templates/vpa.yaml
Changes spec.updatePolicy.updateMode from Auto to Initial across monitoring-agents, monitoring, and etcd VPA resources to avoid pod eviction/restart churn during install that can trigger Cilium IPAM IP-reuse leak. Adds template comments explaining the rationale and safety considerations.
Failed test snapshot capture and crust-gather integration
hack/cozytest.sh, hack/cozyreport.sh, packages/core/testing/images/e2e-sandbox/Dockerfile
Updates cozytest.sh EXIT trap with _cozy_on_exit lifecycle hook that on failed runs captures crust-gather snapshots (excluding Secrets) into per-test directories before cleanup, including nested tenant kubeconfig state. Introduces COZY_REPORT_DIR variable and conditionally invokes user-defined cozy_cleanup function. Updates cozyreport.sh to copy snapshot artifacts into final report directory. Adds CRUST_GATHER_VERSION build ARG to e2e-sandbox Dockerfile and installs crust-gather static binary from upstream install script.

SeaweedFS COSI and Configuration

Layer / File(s) Summary
COSI object-lock and access-policy additions
packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-bucket-class.yaml
Adds BucketClass/<bucketClassName>-lock with deletionPolicy: Retain and object-lock parameters. Adds accessPolicy: readwrite to the main BucketAccessClass and introduces a new -readonly BucketAccessClass with accessPolicy: readonly.
SeaweedFS S3 service name and volume config
packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml, packages/system/seaweedfs/values.yaml
Fixes s3-service.yaml metadata.name to use seaweedfs.name-s3 template. Reduces volumeSizeLimitMB from 30000 to 1000 with expanded comments on volume-placement and multi-bucket test behavior.

Miscellaneous Infrastructure Fixes

Layer / File(s) Summary
startupProbe for CAPI core-components
packages/system/capi-providers-core/files/core-components.yaml
Adds HTTP GET startupProbe on /healthz with failureThreshold: 30 and periodSeconds: 10 to the core cluster API controller manager container.

Sequence Diagrams

sequenceDiagram
    participant GHA as GitHub Actions
    participant Make as make build
    participant Buildx as docker buildx
    participant OCIR as Registry

    rect rgba(70, 130, 180, 0.5)
        Note over GHA,OCIR: main push — cache warm-up
        GHA->>Make: WRITE_CACHE=1, IMAGE_TAG=main
        Make->>Buildx: $(call cache-args) → --cache-from + --cache-to mode=max
        Buildx->>OCIR: pull existing cache
        Buildx->>OCIR: push mode=max cache manifest
    end

    rect rgba(60, 179, 113, 0.5)
        Note over GHA,OCIR: PR build — read-only cache
        GHA->>Make: WRITE_CACHE=0 (default)
        Make->>Buildx: $(call cache-args) → --cache-from only
        Buildx->>OCIR: pull cached layers (no write)
    end
Loading
sequenceDiagram
    participant Healer as cilium-leak-healer Job
    participant K8sAPI as Kubernetes API
    participant CiliumAgent as cilium-agent

    loop every POLL_INTERVAL seconds
        Healer->>K8sAPI: get events reason=FailedCreatePodSandBox
        K8sAPI-->>Healer: event list with "IP already in use"
        Healer->>K8sAPI: get pod phase (freshness check)
        K8sAPI-->>Healer: pod phase
        alt pod Running/Succeeded
            Healer->>Healer: skip
        else pod wedged
            Healer->>CiliumAgent: cilium-dbg endpoint get ipv4:<ip>
            CiliumAgent-->>Healer: endpoint JSON or empty
            alt endpoint absent
                Healer->>Healer: skip (already cleared)
            else endpoint present
                Healer->>K8sAPI: get owning pod IP+phase
                K8sAPI-->>Healer: pod status
                alt live Running pod owns IP
                    Healer->>Healer: REFUSE log
                else confirmed orphan
                    Healer->>CiliumAgent: cilium-dbg endpoint disconnect ipv4:<ip>
                    CiliumAgent-->>Healer: HEAL log
                end
            end
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • cozystack/cozystack#2728: Both PRs touch the same e2e test harness in hack/e2e-apps/kubernetes-latest.bats and hack/e2e-apps/run-kubernetes.sh with changes to sourcing and cleanup hook wiring.
  • cozystack/cozystack#2834: Both PRs modify the SeaweedFS Helm chart package directly in packages/system/seaweedfs/charts/seaweedfs/templates/ and packages/system/seaweedfs/values.yaml.
  • cozystack/cozystack#2855: Both PRs modify the shared build-cache plumbing in hack/common-envs.mk and update image Makefile buildx cache invocations to control cache source/destination behavior.

Suggested labels

size/XXL

Suggested reviewers

  • kvaps
  • lllamnyp
  • androndo
  • sircthulhu
  • IvanHunters
  • lexfrei

Poem

🐇 Hippity-hop, the cache is warm and bright,
No more inline flags cluttering the night!
The Cilium leak? A healer now on guard,
Port-forward to 127 — not localhost barred.
SeaweedFS locks its buckets, read-only too,
One macro to rule them all — hooray, it's true! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci: consolidated platform & e2e stabilization batch' directly summarizes the main change: consolidating multiple interdependent platform and CI stabilization fixes into a single batch PR for unified review.

✏️ 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 ci-next

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.

@github-actions github-actions Bot added the size/XL This PR changes 500-999 lines, ignoring generated files label Jun 18, 2026
@myasnikovdaniil
myasnikovdaniil marked this pull request as ready for review June 18, 2026 07:15
@dosubot dosubot Bot added area/build Issues or PRs related to image build infrastructure, multi-arch support area/ci Issues or PRs related to CI workflows, GitHub Actions, automation area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/testing Issues or PRs related to testing (e2e, bats, unit tests) labels Jun 18, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

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

This pull request consolidates eight critical platform and CI stabilization fixes into a single, dependency-ordered batch. The primary goal is to resolve intermittent CI flakes caused by resource contention, CNI endpoint leaks, and timing issues in the integration test suite. By addressing these issues collectively, the PR ensures a stable and reliable CI environment for the project.

Highlights

  • CI Build Cache Optimization: Refactored the build system to use mode=max registry caching, significantly improving build speeds by caching all stages and enabling shared cache warming for main/release builds.
  • Cilium Endpoint Leak Healer: Introduced an in-cluster watchdog Job that automatically detects and heals 'IP already in use' errors caused by stale Cilium endpoints, preventing CI pipeline failures.
  • E2E Test Stabilization: Improved reliability in bucket tests by fixing port-forwarding logic and updated LINSTOR post-install waits to use a shared deadline, preventing timeouts on resource-constrained CI runners.
  • SeaweedFS Configuration: Reduced volumeSizeLimitMB in SeaweedFS to allow for more parallel bucket collections within the constrained CI environment.
  • CAPI Controller Reliability: Added a startupProbe to the CAPI controller manager to prevent crashloops during initial certificate provisioning.
New Features

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

Ignored Files
  • Ignored by pattern: **/charts/** (2)
    • packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-bucket-class.yaml
    • packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml
  • Ignored by pattern: .github/workflows/** (2)
    • .github/workflows/build-main.yaml
    • .github/workflows/pull-requests.yaml
Using Gemini Code Assist

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

Invoking Gemini

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

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

Customization

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

Limitations & Feedback

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

Footnotes

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

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a unified buildx caching mechanism using a new cache-args macro across all package Makefiles, and adds an in-cluster watchdog Job (cilium-leak-healer) to mitigate Cilium in-memory endpoint leaks during E2E tests. It also improves the robustness of E2E setup scripts, such as using a shared deadline helper in e2e-post-install-prep.sh, fixing port-forwarding in bucket.bats, and adjusting SeaweedFS volume limits. Review feedback suggests refining the Cilium leak healer script: first, by ensuring pods in the Pending phase are not incorrectly treated as stale when checking owner phases, and second, by replacing the inline && with a standard if statement around the while loop for better shell compatibility and readability.

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.

Comment thread hack/e2e-cilium-endpoint-leak-healer.sh Outdated
if [ -n "$epns" ] && [ -n "$eppod" ]; then
owner_ip=$(kubectl get pod -n "$epns" "$eppod" -o jsonpath='{.status.podIP}' 2>/dev/null)
owner_phase=$(kubectl get pod -n "$epns" "$eppod" -o jsonpath='{.status.phase}' 2>/dev/null)
if [ "$owner_ip" = "$ip" ] && [ "$owner_phase" = "Running" ]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Checking [ "$owner_phase" = "Running" ] might incorrectly classify a live pod in the Pending phase (for example, while it is running init containers or pulling images) as stale. If its IPAM allocation is still active, disconnecting its endpoint could disrupt its startup.

Consider refusing the disconnection if the owner pod is in any non-terminal phase (i.e., not Succeeded and not Failed).

Suggested change
if [ "$owner_ip" = "$ip" ] && [ "$owner_phase" = "Running" ]; then
if [ "$owner_ip" = "$ip" ] && [ "$owner_phase" != "Succeeded" ] && [ "$owner_phase" != "Failed" ]; then

Comment on lines +71 to +117
[ -n "$events" ] && while IFS='|' read -r ns pod msg; do
[ -n "$ns" ] && [ -n "$pod" ] || continue
ip=$(printf '%s' "$msg" | grep -oE 'ipv4:[0-9]{1,3}(\.[0-9]{1,3}){3}' | head -1 | cut -d: -f2)
[ -n "$ip" ] || continue

# Only act on a pod that is still wedged (re-checked here = freshness gate).
phase=$(kubectl get pod -n "$ns" "$pod" -o jsonpath='{.status.phase}' 2>/dev/null)
case "$phase" in Running|Succeeded|"") continue ;; esac

node=$(kubectl get pod -n "$ns" "$pod" -o jsonpath='{.spec.nodeName}' 2>/dev/null)
[ -n "$node" ] || continue
agent=$(agent_on_node "$node")
[ -n "$agent" ] || { log "no cilium-agent on node=$node for $ns/$pod ip=$ip"; continue; }

# Does an endpoint in this agent's registry hold the disputed IP at all?
# (The wedged pod itself was never registered — CreateEndpoint rejected it
# before exposing — so any endpoint holding the IP is the stale one.)
ep_json=$(kubectl exec -n "$CILIUM_NS" "$agent" -c cilium-agent -- \
cilium-dbg endpoint get "ipv4:$ip" -o json 2>/dev/null)
[ -n "$ep_json" ] && [ "$ep_json" != "[]" ] || \
{ log "no endpoint holds ip=$ip on node=$node (already cleared?) for $ns/$pod"; continue; }

epid=$(printf '%s' "$ep_json" | jq -r '.[0].id // empty' 2>/dev/null)
epns=$(printf '%s' "$ep_json" | jq -r '.[0].status."external-identifiers"."k8s-namespace" // empty' 2>/dev/null)
eppod=$(printf '%s' "$ep_json" | jq -r '.[0].status."external-identifiers"."k8s-pod-name" // empty' 2>/dev/null)

# If that endpoint claims a pod that is alive on the cluster with this IP,
# it is a LIVE endpoint -> a genuine duplicate-IP, NOT the leak. Refuse and
# shout so the real problem is visible instead of silently broken.
if [ -n "$epns" ] && [ -n "$eppod" ]; then
owner_ip=$(kubectl get pod -n "$epns" "$eppod" -o jsonpath='{.status.podIP}' 2>/dev/null)
owner_phase=$(kubectl get pod -n "$epns" "$eppod" -o jsonpath='{.status.phase}' 2>/dev/null)
if [ "$owner_ip" = "$ip" ] && [ "$owner_phase" = "Running" ]; then
log "REFUSE ip=$ip on node=$node: held by LIVE pod $epns/$eppod (genuine duplicate IP, not the leak) — investigate"
continue
fi
fi

# Confirmed orphan: endpoint $epid holds $ip, backs no live pod, and pod
# $ns/$pod is wedged requesting the same IP. Evict only this endpoint.
log "HEAL node=$node agent=$agent ep=$epid ip=$ip wedged=$ns/$pod -> endpoint disconnect ipv4:$ip"
out=$(kubectl exec -n "$CILIUM_NS" "$agent" -c cilium-agent -- \
cilium-dbg endpoint disconnect "ipv4:$ip" 2>&1)
log " result: ${out:-<none>}"
done <<EOF
$events
EOF

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

Mixing the && operator with a while loop and a here-doc (<<EOF) can be hard to read and may lead to unexpected parsing behaviors in some shell implementations.

Using a standard if statement is more readable and robust.

  if [ -n "$events" ]; then
    while IFS='|' read -r ns pod msg; do
      [ -n "$ns" ] && [ -n "$pod" ] || continue
      ip=$(printf '%s' "$msg" | grep -oE 'ipv4:[0-9]{1,3}(\.[0-9]{1,3}){3}' | head -1 | cut -d: -f2)
      [ -n "$ip" ] || continue

      # Only act on a pod that is still wedged (re-checked here = freshness gate).
      phase=$(kubectl get pod -n "$ns" "$pod" -o jsonpath='{.status.phase}' 2>/dev/null)
      case "$phase" in Running|Succeeded|"") continue ;; esac

      node=$(kubectl get pod -n "$ns" "$pod" -o jsonpath='{.spec.nodeName}' 2>/dev/null)
      [ -n "$node" ] || continue
      agent=$(agent_on_node "$node")
      [ -n "$agent" ] || { log "no cilium-agent on node=$node for $ns/$pod ip=$ip"; continue; }

      # Does an endpoint in this agent's registry hold the disputed IP at all?
      # (The wedged pod itself was never registered — CreateEndpoint rejected it
      # before exposing — so any endpoint holding the IP is the stale one.)
      ep_json=$(kubectl exec -n "$CILIUM_NS" "$agent" -c cilium-agent -- \
                  cilium-dbg endpoint get "ipv4:$ip" -o json 2>/dev/null)
      [ -n "$ep_json" ] && [ "$ep_json" != "[]" ] || \
        { log "no endpoint holds ip=$ip on node=$node (already cleared?) for $ns/$pod"; continue; }

      epid=$(printf '%s' "$ep_json" | jq -r '.[0].id // empty' 2>/dev/null)
      epns=$(printf '%s' "$ep_json" | jq -r '.[0].status."external-identifiers"."k8s-namespace" // empty' 2>/dev/null)
      eppod=$(printf '%s' "$ep_json" | jq -r '.[0].status."external-identifiers"."k8s-pod-name" // empty' 2>/dev/null)

      # If that endpoint claims a pod that is alive on the cluster with this IP,
      # it is a LIVE endpoint -> a genuine duplicate-IP, NOT the leak. Refuse and
      # shout so the real problem is visible instead of silently broken.
      if [ -n "$epns" ] && [ -n "$eppod" ]; then
        owner_ip=$(kubectl get pod -n "$epns" "$eppod" -o jsonpath='{.status.podIP}' 2>/dev/null)
        owner_phase=$(kubectl get pod -n "$epns" "$eppod" -o jsonpath='{.status.phase}' 2>/dev/null)
        if [ "$owner_ip" = "$ip" ] && [ "$owner_phase" != "Succeeded" ] && [ "$owner_phase" != "Failed" ]; then
          log "REFUSE ip=$ip on node=$node: held by LIVE pod $epns/$eppod (genuine duplicate IP, not the leak) — investigate"
          continue
        fi
      fi

      # Confirmed orphan: endpoint $epid holds $ip, backs no live pod, and pod
      # $ns/$pod is wedged requesting the same IP. Evict only this endpoint.
      log "HEAL node=$node agent=$agent ep=$epid ip=$ip wedged=$ns/$pod -> endpoint disconnect ipv4:$ip"
      out=$(kubectl exec -n "$CILIUM_NS" "$agent" -c cilium-agent -- \
              cilium-dbg endpoint disconnect "ipv4:$ip" 2>&1)
      log "  result: ${out:-<none>}"
    done <<EOF
$events
EOF
  fi

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

Actionable comments posted: 6

🤖 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 @.github/workflows/build-main.yaml:
- Around line 44-48: Pin the workflow actions to specific commit SHAs instead of
version tags to prevent tag-retargeting attacks. Replace actions/checkout@v4
with its commit SHA digest, docker/login-action@v3 with its SHA, and
docker/setup-buildx-action@v3 with its SHA. Additionally, in the
actions/checkout step, add persist-credentials: false to the with block to
disable credential persistence and reduce the token exposure surface in this
privileged workflow.

In @.github/workflows/pull-requests.yaml:
- Around line 67-72: The helm plugin installation and flux script installation
commands lack version pinning and checksum verification, creating supply-chain
risks. For the helm plugin install command, add the --version flag to pin a
specific version of helm-unittest. For the flux installation, replace the direct
pipe to sudo bash with a safer approach: download the install script, verify its
checksum against the published checksums.txt file from the flux release, and
then execute it only after verification succeeds. This ensures both
installations are deterministic and verified across CI builds.

In `@hack/e2e-cilium-endpoint-leak-healer.sh`:
- Around line 50-55: The agent_on_node function blindly selects the first pod
from the list using .items[0] in the jsonpath query without verifying it is in a
Running state, which can result in selecting Pending, Terminating, or Failed
pods during DaemonSet rollouts. Modify the function to filter for Running pods
by adding a field selector for status.phase=Running in the kubectl get pod
command or by updating the jsonpath query to select only from pods with running
status, ensuring that a Running pod's name is returned instead of the first pod
regardless of its state.

In `@hack/e2e-cilium-leak-healer.yaml`:
- Around line 76-96: The healer container in the pod specification lacks an
explicit securityContext, which is problematic given that the pod uses
hostNetwork: true and has pods/exec RBAC permissions. Add a securityContext
field to the healer container that restricts unnecessary capabilities by
dropping all capabilities and only adding back those required for the healing
operation, sets readOnlyRootFilesystem to true since the ConfigMap mount is
read-only, ensures the container runs as a non-root user, and prevents privilege
escalation. This hardening will reduce the attack surface if the container is
compromised while remaining compatible with the container's read-only
configuration.
- Around line 31-35: The pods/exec resource permission is currently granted
cluster-wide through the ClusterRole, but the healer script only executes within
the cozy-cilium namespace. To follow least-privilege principle, split the
permissions: keep the read-only cluster-wide discovery rules (events, pods) in
the existing ClusterRole, and create a separate namespace-scoped Role in the
cozy-cilium namespace that contains the pods/exec permission with verbs create,
then create a RoleBinding in cozy-cilium that binds this Role to the same
service account. This restricts exec access to only the cozy-cilium namespace as
the script intends.

In `@hack/e2e-install-cozystack.bats`:
- Around line 18-21: The kubectl commands creating the cilium-leak-healer
ConfigMap and applying the e2e-cilium-leak-healer.yaml manifest are both using
the || true operator to suppress failures. Remove the || true from both the
kubectl create configmap command on line 20 and the kubectl apply command on
line 21. This ensures that if either the ConfigMap creation or the Job manifest
application fails due to missing files, permission issues, or namespace
problems, the script will fail explicitly rather than silently, preventing the
watchdog from running unnoticed.
🪄 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: cfb86fdc-13ec-4134-be94-4c6125dcb6ba

📥 Commits

Reviewing files that changed from the base of the PR and between 8c411d8 and df9dc74.

📒 Files selected for processing (44)
  • .github/workflows/build-main.yaml
  • .github/workflows/pull-requests.yaml
  • hack/common-envs.mk
  • hack/e2e-apps/bucket.bats
  • hack/e2e-cilium-endpoint-leak-healer.sh
  • hack/e2e-cilium-leak-healer.yaml
  • hack/e2e-install-cozystack.bats
  • hack/e2e-post-install-prep.sh
  • packages/apps/clickhouse/Makefile
  • packages/apps/http-cache/Makefile
  • packages/apps/kubernetes/Makefile
  • packages/apps/mariadb/Makefile
  • packages/core/installer/Makefile
  • packages/core/platform/Makefile
  • packages/core/talos/Makefile
  • packages/core/testing/Makefile
  • packages/extra/monitoring/Makefile
  • packages/system/backup-controller/Makefile
  • packages/system/backupstrategy-controller/Makefile
  • packages/system/bucket/Makefile
  • packages/system/capi-providers-core/files/core-components.yaml
  • packages/system/cilium/Makefile
  • packages/system/cozystack-api/Makefile
  • packages/system/cozystack-controller/Makefile
  • packages/system/dashboard/Makefile
  • packages/system/flux-plunger/Makefile
  • packages/system/grafana-operator/Makefile
  • packages/system/kamaji/Makefile
  • packages/system/keycloak-operator/Makefile
  • packages/system/kilo/Makefile
  • packages/system/kubeovn-plunger/Makefile
  • packages/system/kubeovn-webhook/Makefile
  • packages/system/lineage-controller-webhook/Makefile
  • packages/system/linstor-gui/Makefile
  • packages/system/linstor/Makefile
  • packages/system/metallb/Makefile
  • packages/system/metallb/tests/metallb_test.yaml
  • packages/system/monitoring/Makefile
  • packages/system/multus/Makefile
  • packages/system/objectstorage-controller/Makefile
  • packages/system/redis-operator/Makefile
  • packages/system/seaweedfs/charts/seaweedfs/templates/cosi/cosi-bucket-class.yaml
  • packages/system/seaweedfs/charts/seaweedfs/templates/s3/s3-service.yaml
  • packages/system/seaweedfs/values.yaml

Comment on lines +44 to +48
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
fetch-tags: true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/build-main.yaml

Repository: cozystack/cozystack

Length of output: 3864


🌐 Web query:

GitHub Actions security best practices SHA pinning vs version tags 2024 2025

💡 Result:

In GitHub Actions, pinning to a full-length commit SHA is the recommended security best practice, as it provides an immutable reference to the action's code [1][2][3]. Using version tags (e.g., @v4) or branch names (e.g., @main) is inherently risky because these references are mutable; a compromised maintainer or an attacker who gains access to the action's repository can retarget them to point to malicious code without notice [4][3][5]. SHA pinning prevents "tag-retargeting" supply chain attacks [4][6]. Because a commit SHA uniquely identifies a specific state of the repository, it guarantees that your workflow executes the exact code you reviewed and approved, regardless of changes made to tags or branches upstream [2][3]. To implement this effectively: 1. Use full 40-character commit SHAs in your workflow files (e.g., uses: actions/checkout@3df4ab1) [1][7]. 2. Include the original version tag as a comment to maintain human readability and simplify updates (e.g., uses: actions/checkout@3df4ab1 # v4.0.0) [4][6][7]. 3. Automate the update process using tools like Dependabot or Renovate (using specific presets like helpers:pinGitHubActionDigests) to keep SHAs current without manual intervention [4][8][6][7]. 4. Enforce SHA pinning at the organization or repository level via GitHub's policy settings, which can cause workflows using unpinned actions to fail automatically [1][3][9]. While SHA pinning is a critical security control, it is not a complete solution. It does not protect against malicious code that was present in the chosen commit, nor does it necessarily prevent actions from pulling in other mutable dependencies (such as Docker images tagged:latest) at runtime [8][5]. Organizations should combine SHA pinning with other security practices, such as applying the principle of least privilege to GITHUB_TOKEN permissions, utilizing OIDC for cloud authentication, and conducting regular audits of third-party actions [10][11][7].

Citations:


🌐 Web query:

actions/checkout persist-credentials default behavior

💡 Result:

The default behavior for the persist-credentials input in the GitHub actions/checkout action is true [1][2][3]. When set to true (the default), the action configures the authentication token or SSH key used for the checkout within the local git environment [1][3][4]. This allows subsequent steps in your workflow to execute authenticated git commands, such as git fetch, without needing to manually re-authenticate [1][3]. In versions of actions/checkout prior to v6.0.0, these credentials were stored directly in the local.git/config file [1][5][6]. Starting with v6.0.0, the action was updated to store credentials in a separate file under $RUNNER_TEMP to improve security [1][5][6]. Regardless of the storage method, the action ensures that these credentials are removed during the post-job cleanup process [1][3][7]. If you wish to prevent the action from configuring these credentials, you can opt-out by explicitly setting persist-credentials: false in your workflow file [1][3][8].

Citations:


Pin workflow actions to commit SHA and disable checkout credential persistence.

actions/checkout@v4, docker/login-action@v3, and docker/setup-buildx-action@v3 use version tags instead of commit SHAs, allowing tag-retargeting attacks if a dependency is compromised. Additionally, checkout persists credentials by default, exposing an unnecessary token surface in this privileged workflow.

🔧 Proposed hardening
       - name: Checkout code
-        uses: actions/checkout@v4
+        uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
         with:
           fetch-depth: 0
           fetch-tags: true
+          persist-credentials: false

       - name: Login to OCIR
-        uses: docker/login-action@v3
+        uses: docker/login-action@9780b0c56d5d59944534694c45ea9bc1d00c757e # v3.3.0

       - name: Set up Buildx (docker-container driver)
         id: buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@d4c1175270439c506a768202a39eb90a979b35acb # v3.6.0

Replace SHAs with the actual digests for your target versions, or use Dependabot with helpers:pinGitHubActionDigests preset to automate updates.

Also applies to: lines 57, 70

🧰 Tools
🪛 zizmor (1.25.2)

[warning] 44-48: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 45-45: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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 @.github/workflows/build-main.yaml around lines 44 - 48, Pin the workflow
actions to specific commit SHAs instead of version tags to prevent
tag-retargeting attacks. Replace actions/checkout@v4 with its commit SHA digest,
docker/login-action@v3 with its SHA, and docker/setup-buildx-action@v3 with its
SHA. Additionally, in the actions/checkout step, add persist-credentials: false
to the with block to disable credential persistence and reduce the token
exposure surface in this privileged workflow.

Source: Linters/SAST tools

Comment on lines +67 to +72
run: |
helm plugin list 2>/dev/null | grep -q unittest \
|| helm plugin install https://github.com/helm-unittest/helm-unittest
command -v flux >/dev/null \
|| curl -s https://fluxcd.io/install.sh | sudo bash

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/pull-requests.yaml | sed -n '60,80p'

Repository: cozystack/cozystack

Length of output: 1142


🏁 Script executed:

# Check if there are any version constraints or requirements files
find . -type f \( -name "requirements*.txt" -o -name "requirements*.yaml" -o -name "constraints*" -o -name ".tool-versions" \) | head -20

Repository: cozystack/cozystack

Length of output: 45


🏁 Script executed:

# Search for helm plugin install patterns elsewhere to understand versioning convention
rg "helm plugin install" -A 2

Repository: cozystack/cozystack

Length of output: 284


🏁 Script executed:

# Check for flux version references in the repo
rg "flux.*version" -i

Repository: cozystack/cozystack

Length of output: 3264


🌐 Web query:

helm-unittest plugin version pinning command syntax --version

💡 Result:

To pin a specific version of the helm-unittest plugin during installation, use the --version flag with the standard helm plugin install command [1][2]. The command syntax is: helm plugin install https://github.com/helm-unittest/helm-unittest --version <version_number> For example, to install version 1.0.3: helm plugin install https://github.com/helm-unittest/helm-unittest --version 1.0.3 Important considerations: 1. Compatibility with Helm 4: If you are using Helm 4, ensure you omit the.git extension from the repository URL [3]. Additionally, because Helm 4 does not support webhooks GPG verification for plugin installation, you may need to include the --verify=false flag [4][3]: helm plugin install https://github.com/helm-unittest/helm-unittest --version 1.0.3 --verify=false 2. Troubleshooting: If you encounter installation errors, ensure your Helm cache is clean and that you are using a compatible version of Helm [5][6]. The plugin documentation notes that installation issues can sometimes arise if previous installations or cached files conflict with the new version [1][5]. 3. Default behavior: If the --version flag is omitted, the helm plugin install command will install the latest available version by default [4][7].

Citations:


🌐 Web query:

flux fluxcd release checksums.txt GitHub releases

💡 Result:

Flux release checksums are published as a file named flux_checksums.txt within the assets of every release on the fluxcd/flux2 GitHub repository [1][2][3]. These checksum files allow users to verify the integrity of the downloaded artifacts (such as CLI binaries, SBOMs, and manifests) by comparing the file hashes against those listed in the text file [4][5]. In addition to the raw checksum text file, Flux provides cryptographic signatures and certificates for these files to ensure their authenticity [6][7]: - fluxchecksums.txt: Contains the SHA-256 hashes of the release assets [1][4]. - fluxchecksums.txt.sig: The digital signature of the checksums file [1][5]. - flux_checksums.txt.pem: The certificate associated with the signature [1][5]. These files can be verified using the Sigstore Cosign tool [5]. A common command to verify the checksum file is: cosign verify-blob checksums.txt \ --signature checksums.txt.sig \ --certificate checksums.txt.pem \ --certificate-identity="https://github.com/fluxcd/flux2/.github/workflows/release.yml@refs/tags/v" \ --certificate-oidc-issuer="https://token.actions.githubusercontent.com" For automated installation, the official Flux install script (install/flux.sh) automatically fetches the checksum file to verify the downloaded binary [4]. Beyond manual checksum verification, the project also provides SLSA provenance attestations and signed container images for enhanced security [6][8][9].

Citations:


Avoid unpinned remote installer execution with sudo bash.

Lines 69 and 71 execute unpinned plugin and script installations. Piping the flux install script directly to sudo bash bypasses the checksum verification that flux actually provides in its releases, introducing preventable supply-chain risk and nondeterministic CI behavior across builds.

Both can be hardened:

  • helm plugin install supports --version flag for version pinning
  • flux publishes checksums.txt for all releases; the official install script verifies them, but the direct pipe approach does not
🔧 Proposed hardening pattern
-          helm plugin list 2>/dev/null | grep -q unittest \
-            || helm plugin install https://github.com/helm-unittest/helm-unittest
+          helm plugin list 2>/dev/null | grep -q unittest \
+            || helm plugin install https://github.com/helm-unittest/helm-unittest --version v<pin>

-          command -v flux >/dev/null \
-            || curl -s https://fluxcd.io/install.sh | sudo bash
+          command -v flux >/dev/null || {
+            FLUX_VERSION=v<pin>
+            ARCHIVE="flux_${FLUX_VERSION#v}_linux_amd64.tar.gz"
+            curl -fsSLo /tmp/${ARCHIVE} "https://github.com/fluxcd/flux2/releases/download/${FLUX_VERSION}/${ARCHIVE}"
+            curl -fsSLo /tmp/flux_checksums.txt "https://github.com/fluxcd/flux2/releases/download/${FLUX_VERSION}/checksums.txt"
+            grep " ${ARCHIVE}$" /tmp/flux_checksums.txt | sha256sum -c -
+            tar -xzf /tmp/${ARCHIVE} -C /tmp flux
+            sudo install -m 0755 /tmp/flux /usr/local/bin/flux
+          }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
run: |
helm plugin list 2>/dev/null | grep -q unittest \
|| helm plugin install https://github.com/helm-unittest/helm-unittest
command -v flux >/dev/null \
|| curl -s https://fluxcd.io/install.sh | sudo bash
run: |
helm plugin list 2>/dev/null | grep -q unittest \
|| helm plugin install https://github.com/helm-unittest/helm-unittest --version v<pin>
command -v flux >/dev/null || {
FLUX_VERSION=v<pin>
ARCHIVE="flux_${FLUX_VERSION#v}_linux_amd64.tar.gz"
curl -fsSLo /tmp/${ARCHIVE} "https://github.com/fluxcd/flux2/releases/download/${FLUX_VERSION}/${ARCHIVE}"
curl -fsSLo /tmp/flux_checksums.txt "https://github.com/fluxcd/flux2/releases/download/${FLUX_VERSION}/checksums.txt"
grep " ${ARCHIVE}$" /tmp/flux_checksums.txt | sha256sum -c -
tar -xzf /tmp/${ARCHIVE} -C /tmp flux
sudo install -m 0755 /tmp/flux /usr/local/bin/flux
}
🤖 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 @.github/workflows/pull-requests.yaml around lines 67 - 72, The helm plugin
installation and flux script installation commands lack version pinning and
checksum verification, creating supply-chain risks. For the helm plugin install
command, add the --version flag to pin a specific version of helm-unittest. For
the flux installation, replace the direct pipe to sudo bash with a safer
approach: download the install script, verify its checksum against the published
checksums.txt file from the flux release, and then execute it only after
verification succeeds. This ensures both installations are deterministic and
verified across CI builds.

Comment on lines +50 to +55
# cilium-agent pod running on a given node (DaemonSet selector k8s-app=cilium).
agent_on_node() {
kubectl get pod -n "$CILIUM_NS" -l k8s-app=cilium \
--field-selector "spec.nodeName=$1" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null
}

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.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the actual file structure and content
cat -n hack/e2e-cilium-endpoint-leak-healer.sh | head -70

Repository: cozystack/cozystack

Length of output: 4374


🏁 Script executed:

# Search for usages of agent_on_node function
rg "agent_on_node" hack/e2e-cilium-endpoint-leak-healer.sh

Repository: cozystack/cozystack

Length of output: 116


🏁 Script executed:

# Look for kubectl exec calls that might use the output
rg "kubectl exec" hack/e2e-cilium-endpoint-leak-healer.sh -A 2 -B 2

Repository: cozystack/cozystack

Length of output: 759


🏁 Script executed:

# Test JSONPath filtering syntax used in the suggestion
# First, let's check what kubectl version is expected and if JSONPath filters work
grep -E "(kubectl|version)" hack/e2e-cilium-endpoint-leak-healer.sh | head -5

Repository: cozystack/cozystack

Length of output: 436


🏁 Script executed:

# Let's also check if there are any other similar patterns in the codebase for filtering by status
rg "status.phase" --type sh

Repository: cozystack/cozystack

Length of output: 3398


🏁 Script executed:

# Check if the file has any comments about pod selection reliability
rg -B2 -A2 "items\[0\]" hack/e2e-cilium-endpoint-leak-healer.sh

Repository: cozystack/cozystack

Length of output: 218


🏁 Script executed:

# Check the full context of the agent_on_node function usage and how errors are handled
sed -n '50,120p' hack/e2e-cilium-endpoint-leak-healer.sh

Repository: cozystack/cozystack

Length of output: 3940


🏁 Script executed:

# Look for similar pattern with status filtering in foundationdb helpers mentioned in grep results
cat -n examples/backups/foundationdb/00-helpers.sh | grep -B3 -A3 "status.phase=Running"

Repository: cozystack/cozystack

Length of output: 523


🏁 Script executed:

# Verify the kubectl JSONPath filter syntax is valid
# Check if there are any JSONPath range filters in the codebase
rg "range.*items\[" --type sh | head -10

Repository: cozystack/cozystack

Length of output: 1327


🏁 Script executed:

# Also check what kubectl version/features are expected
fd -e yaml | xargs grep -l "kubectl" | head -3 | xargs cat | grep -i "image.*kubectl" | head -3

Repository: cozystack/cozystack

Length of output: 156


🏁 Script executed:

# Verify both approaches work with kubectl's field-selector and JSONPath
# Check if there are examples of both approaches in the codebase

# Approach 1: field-selector with status.phase (simpler)
grep -n "field-selector.*status.phase" hack/e2e-cilium-endpoint-leak-healer.sh examples/backups/foundationdb/*.sh

# Approach 2: JSONPath filtering (what the review suggests)
grep -n "items\[?.*status" hack/*.sh | head -5

Repository: cozystack/cozystack

Length of output: 695


🏁 Script executed:

# Let's verify the actual behavior difference
# Check what happens when agent_on_node returns empty or non-running pod
sed -n '82,92p' hack/e2e-cilium-endpoint-leak-healer.sh

Repository: cozystack/cozystack

Length of output: 720


🏁 Script executed:

# Verify that kubectl exec actually fails on non-Running pods
# Let's check the kubectl documentation pattern in the codebase

# Also verify the condition check at line 89 only checks for empty string
sed -n '87,91p' hack/e2e-cilium-endpoint-leak-healer.sh

Repository: cozystack/cozystack

Length of output: 433


🏁 Script executed:

# Check if there's any error logging when kubectl exec fails silently
# The error is redirected to /dev/null, so failures are not logged
sed -n '100,115p' hack/e2e-cilium-endpoint-leak-healer.sh | cat -n

Repository: cozystack/cozystack

Length of output: 1080


Select a Running cilium-agent pod instead of blindly taking .items[0].

The current code at line 54 can resolve to a Pending, Terminating, or Failed pod during DaemonSet rollouts or restarts. This causes kubectl exec to fail silently (stderr redirected to /dev/null), skipping endpoint healing for that node until the next poll interval.

Suggested patch
 agent_on_node() {
   kubectl get pod -n "$CILIUM_NS" -l k8s-app=cilium \
     --field-selector "spec.nodeName=$1" \
-    -o jsonpath='{.items[0].metadata.name}' 2>/dev/null
+    -o jsonpath='{range .items[?(@.status.phase=="Running")]}{.metadata.name}{"\n"}{end}' 2>/dev/null \
+    | head -n1
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# cilium-agent pod running on a given node (DaemonSet selector k8s-app=cilium).
agent_on_node() {
kubectl get pod -n "$CILIUM_NS" -l k8s-app=cilium \
--field-selector "spec.nodeName=$1" \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null
}
# cilium-agent pod running on a given node (DaemonSet selector k8s-app=cilium).
agent_on_node() {
kubectl get pod -n "$CILIUM_NS" -l k8s-app=cilium \
--field-selector "spec.nodeName=$1" \
-o jsonpath='{range .items[?(@.status.phase=="Running")]}{.metadata.name}{"\n"}{end}' 2>/dev/null \
| head -n1
}
🤖 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/e2e-cilium-endpoint-leak-healer.sh` around lines 50 - 55, The
agent_on_node function blindly selects the first pod from the list using
.items[0] in the jsonpath query without verifying it is in a Running state,
which can result in selecting Pending, Terminating, or Failed pods during
DaemonSet rollouts. Modify the function to filter for Running pods by adding a
field selector for status.phase=Running in the kubectl get pod command or by
updating the jsonpath query to select only from pods with running status,
ensuring that a Running pod's name is returned instead of the first pod
regardless of its state.

Comment on lines +31 to +35
# run `cilium-dbg endpoint ...` inside the owning node's cilium-agent
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate and read the YAML file
find . -name "e2e-cilium-leak-healer.yaml" -type f

Repository: cozystack/cozystack

Length of output: 98


🏁 Script executed:

# Once found, read the file to see the complete RBAC structure
cat -n hack/e2e-cilium-leak-healer.yaml

Repository: cozystack/cozystack

Length of output: 4158


🏁 Script executed:

# Search for references to cozy-cilium and exec in the repository
rg "cozy-cilium" --type yaml -B 2 -A 2

Repository: cozystack/cozystack

Length of output: 5452


🏁 Script executed:

# Search for exec invocations or patterns in shell scripts
rg "exec" hack/ -B 2 -A 2

Repository: cozystack/cozystack

Length of output: 6447


Scope pods/exec to cozy-cilium namespace instead of granting it cluster-wide.

The healer script (hack/e2e-cilium-endpoint-leak-healer.sh) explicitly limits exec calls to the cozy-cilium namespace using -n "$CILIUM_NS", but the current ClusterRole allows exec into any pod cluster-wide. This violates least-privilege: keep the ClusterRole for read-only cluster-wide discovery rules (events, pods), and move pods/exec to a namespace-scoped Role in cozy-cilium.

Suggested patch direction
 # In ClusterRole: keep read-only cluster-wide discovery rules,
 # remove cluster-wide pods/exec.
-  - apiGroups: [""]
-    resources: ["pods/exec"]
-    verbs: ["create"]

+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+  name: cilium-leak-healer-exec
+  namespace: cozy-cilium
+rules:
+  - apiGroups: [""]
+    resources: ["pods/exec"]
+    verbs: ["create"]
+
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: RoleBinding
+metadata:
+  name: cilium-leak-healer-exec
+  namespace: cozy-cilium
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: Role
+  name: cilium-leak-healer-exec
+subjects:
+  - kind: ServiceAccount
+    name: cilium-leak-healer
+    namespace: kube-system
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# run `cilium-dbg endpoint ...` inside the owning node's cilium-agent
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
# run `cilium-dbg endpoint ...` inside the owning node's cilium-agent
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: cilium-leak-healer-exec
namespace: cozy-cilium
rules:
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: cilium-leak-healer-exec
namespace: cozy-cilium
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: cilium-leak-healer-exec
subjects:
- kind: ServiceAccount
name: cilium-leak-healer
namespace: kube-system
🧰 Tools
🪛 Trivy (0.69.3)

[error] 31-33: Exec into Pods

ClusterRole 'cilium-leak-healer' should not have access to resource '["pods/exec"]' for verbs ["create", "update", "patch", "delete", "deletecollection", "impersonate", "*"]

Rule: KSV-0053

Learn more

(IaC/Kubernetes)

🤖 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/e2e-cilium-leak-healer.yaml` around lines 31 - 35, The pods/exec
resource permission is currently granted cluster-wide through the ClusterRole,
but the healer script only executes within the cozy-cilium namespace. To follow
least-privilege principle, split the permissions: keep the read-only
cluster-wide discovery rules (events, pods) in the existing ClusterRole, and
create a separate namespace-scoped Role in the cozy-cilium namespace that
contains the pods/exec permission with verbs create, then create a RoleBinding
in cozy-cilium that binds this Role to the same service account. This restricts
exec access to only the cozy-cilium namespace as the script intends.

Source: Linters/SAST tools

Comment on lines +76 to +96
tolerations:
- operator: Exists
containers:
- name: healer
# Reuse the in-tree, digest-pinned kubectl+jq image already shipped by
# the installer (packages/core/installer/templates/cozy-system-labels.yaml).
image: docker.io/alpine/k8s:1.33.4@sha256:b0523f0a244ddc4c8e055aa335c040d3d78b3ead5528f4544395f7f9f69c7b68
command: ["/bin/sh", "/opt/healer/heal.sh"]
env:
- name: CILIUM_NS
value: cozy-cilium
volumeMounts:
- name: script
mountPath: /opt/healer
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
volumes:

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat hack/e2e-cilium-leak-healer.yaml

Repository: cozystack/cozystack

Length of output: 3465


Add explicit container security context; default settings are unnecessarily permissive with hostNetwork and exec RBAC.

The pod uses hostNetwork: true (required for healing the leak during cluster bootstrap) and grants pods/exec permissions. Without explicit container hardening, a compromise gains elevated capabilities. The ConfigMap-only mount is read-only, making the suggested hardening straightforward to implement.

Suggested patch
       containers:
         - name: healer
           image: docker.io/alpine/k8s:1.33.4@sha256:b0523f0a244ddc4c8e055aa335c040d3d78b3ead5528f4544395f7f9f69c7b68
           command: ["/bin/sh", "/opt/healer/heal.sh"]
+          securityContext:
+            allowPrivilegeEscalation: false
+            readOnlyRootFilesystem: true
+            capabilities:
+              drop: ["ALL"]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
tolerations:
- operator: Exists
containers:
- name: healer
# Reuse the in-tree, digest-pinned kubectl+jq image already shipped by
# the installer (packages/core/installer/templates/cozy-system-labels.yaml).
image: docker.io/alpine/k8s:1.33.4@sha256:b0523f0a244ddc4c8e055aa335c040d3d78b3ead5528f4544395f7f9f69c7b68
command: ["/bin/sh", "/opt/healer/heal.sh"]
env:
- name: CILIUM_NS
value: cozy-cilium
volumeMounts:
- name: script
mountPath: /opt/healer
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
volumes:
tolerations:
- operator: Exists
containers:
- name: healer
# Reuse the in-tree, digest-pinned kubectl+jq image already shipped by
# the installer (packages/core/installer/templates/cozy-system-labels.yaml).
image: docker.io/alpine/k8s:1.33.4@sha256:b0523f0a244ddc4c8e055aa335c040d3d78b3ead5528f4544395f7f9f69c7b68
command: ["/bin/sh", "/opt/healer/heal.sh"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
env:
- name: CILIUM_NS
value: cozy-cilium
volumeMounts:
- name: script
mountPath: /opt/healer
resources:
requests:
cpu: 10m
memory: 32Mi
limits:
memory: 128Mi
volumes:
🧰 Tools
🪛 Trivy (0.69.3)

[error] 55-96: Access to host network

Job 'cilium-leak-healer' should not set 'spec.template.spec.hostNetwork' to true

Rule: KSV-0009

Learn more

(IaC/Kubernetes)


[error] 76-92: Root file system is not read-only

Container 'healer' of Job 'cilium-leak-healer' should set 'securityContext.readOnlyRootFilesystem' to true

Rule: KSV-0014

Learn more

(IaC/Kubernetes)


[error] 76-92: Default security context configured

container cilium-leak-healer in kube-system namespace is using the default security context

Rule: KSV-0118

Learn more

(IaC/Kubernetes)


[error] 64-96: Default security context configured

job cilium-leak-healer in kube-system namespace is using the default security context, which allows root privileges

Rule: KSV-0118

Learn more

(IaC/Kubernetes)

🤖 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/e2e-cilium-leak-healer.yaml` around lines 76 - 96, The healer container
in the pod specification lacks an explicit securityContext, which is problematic
given that the pod uses hostNetwork: true and has pods/exec RBAC permissions.
Add a securityContext field to the healer container that restricts unnecessary
capabilities by dropping all capabilities and only adding back those required
for the healing operation, sets readOnlyRootFilesystem to true since the
ConfigMap mount is read-only, ensures the container runs as a non-root user, and
prevents privilege escalation. This hardening will reduce the attack surface if
the container is compromised while remaining compatible with the container's
read-only configuration.

Source: Linters/SAST tools

Comment thread hack/e2e-install-cozystack.bats
myasnikovdaniil and others added 5 commits June 18, 2026 12:57
…viction churn

In Auto mode the VPA Updater evicts running pods to apply new recommendations. During install that eviction churn recreates VictoriaMetrics pods faster than Cilium releases their endpoint IPs, tripping the cluster-pool IPAM "IP already in use" (putEndpointIdInvalid) leak that wedges a pod and stalls the install. Initial sets requests at pod creation without ever evicting, removing the trigger (the cilium-leak healer remains as defense for non-VPA churn paths).

Also raise vmstorage/vmselect minAllowed floors to 200m/512Mi: under Initial a pod is sized once at creation and never re-sized at runtime, so the floor is the safety net against the heavy query/storage tiers being left under-provisioned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Auto mode evicts running etcd members to apply VPA recommendations, churning the StatefulSet (disruptive to quorum and a Cilium IP-reuse-leak trigger under load). Initial sizes at pod creation without evicting; the existing 250m/256Mi minAllowed floor covers the no-runtime-resize tradeoff. Same eviction-churn class as the monitoring VPA fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…bats

Reconfiguring the Etcd CR triggers a HelmRelease upgrade that renders the EtcdBackupSchedule; hr/etcd and etcdcluster can report ready on the prior revision before the new resource materializes, so the immediate get raced to NotFound. Poll for it (timeout 120s), mirroring the CronJob wait already used later in the same test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…t budgets

kubernetes-latest flaked because the worker-readiness wait was split into 8m-to-join + 3m-to-become-Ready. Under sandbox load a slow KubeVirt VM boot consumes most of the join budget, then the tenant cluster's cilium CNI needs several more minutes to make the freshly-joined nodes Ready, overflowing the fixed 3m Ready window even though the CNI converges fine (its HelmRelease reached True shortly after). Replace with one 12m deadline polling for >=2 Ready nodes, robust to wherever the time goes. Same single-deadline shape as the LINSTOR post-install fix (#2928).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… hook

The e2e runner hack/cozytest.sh converts only @test blocks into functions and runs those; it never invokes bats setup_file/teardown_file. So the healer's setup_file deploy silently never executed (confirmed: the Job was absent from the cozyreport while the same manifest deploys and runs fine on a real Talos cozystack cluster). Move the deploy into a real, first @test so cozytest.sh runs it (with visible set -x trace) before the install churn, and assert the Job landed without ever failing the suite (band-aid).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
kubernetes-{latest,previous} intermittently failed their node-Ready wait
with the tenant API "connection refused" on localhost, while the cluster was
in fact healthy (host snapshot: both CAPI Machines NodeHealthy=True, TCP
Ready 2/2). Root cause: `kubectl port-forward service/kubernetes-<name>`
pins to ONE of the two Kamaji control-plane pods; when that pod's
kube-apiserver container restarted once (observed restartCount=1), the
forward broke and did not recover, so the 12m wait ran against a dead socket
and the failure-path describe/snapshot also got nothing.

Expose the tenant API with a test-scoped LoadBalancer Service selecting the
Kamaji control-plane pods instead. It load-balances across ALL ready apiserver
endpoints, so a single pod's apiserver restart is routed around transparently;
the host runs MetalLB on the sandbox /24 (pool 192.168.123.200-250) so the IP
is directly routable from the test (the in-tenant LB test already curls such
an address). The kubeconfig points at the LB IP with insecure-skip-tls-verify
(the MetalLB IP isn't in the apiserver cert SANs; e2e only) and the CA dropped.

Bonus: the LB endpoint is stable until teardown, so the on-failure tenant
crust-gather snapshot can finally reach the tenant (the port-forward was dead
by snapshot time, which is why every prior tenant snapshot came back empty).

Removes the port-forward supervisor + pidfile machinery. Verified against the
real hack/cozytest.sh under dash (CI's /bin/sh) in ubuntu:22.04: no pipeline
deadlock, the in-subshell snapshot trap fires and captures the tenant, and the
LB Service is cleaned up on both the success path and cozy_cleanup.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@myasnikovdaniil myasnikovdaniil removed the debug Debugging in progress label Jun 20, 2026
myasnikovdaniil and others added 4 commits June 20, 2026 13:34
No code change. The 'debug' PR label was pinning E2E to the contended
self-hosted runner; with it removed this synchronize event re-evaluates
runs-on to the ephemeral oracle-vm pool. Empty commit because the workflow
only triggers on opened/synchronize/reopened (not label changes) and a plain
re-run reuses the original event payload (stale labels).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The leak-healer only refused to disconnect an endpoint whose owning pod was
Running, so a Pending owner that already held a live IPAM allocation
(status.podIP is populated once the sandbox is created, before the pod goes
Running) could have its endpoint evicted mid-startup. Refuse for any
non-terminal phase (!= Succeeded && != Failed); only a terminal owner is
safely past needing its endpoint.

Addresses non-blocking follow-up 4 on #2948.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
REST.Update wraps the HelmRelease write in retry.RetryOnConflict so a stale
resourceVersion (the helm-controller racing the status subresource) is
refreshed and retried instead of surfacing a 409. Add a unit test using a fake
client whose interceptor returns one Conflict on the first Update and succeeds
on the second, asserting Update returns nil and writes exactly twice.

Addresses non-blocking follow-up 3 on #2948.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The build-main cache warmer is a privileged workflow (packages:write + an OCIR
push token) and never pushes git, so set persist-credentials: false to keep the
checkout's GITHUB_TOKEN out of .git/config where a later build step could read
it. (SHA-pinning the actions is handled repo-wide in #2849.)

Addresses non-blocking follow-up 7 on #2948.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@github-actions github-actions Bot added size/XXL This PR changes 1000+ lines, ignoring generated files and removed size/XL This PR changes 500-999 lines, ignoring generated files labels Jun 20, 2026
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Aleksei Sviridkin (@lexfrei) thanks for the thorough review — every blocker and follow-up is now addressed or consciously deferred.

Blocker

  • B18f41910 — the -lock/-readonly BucketClass/BucketAccessClass and the s3 service-name override are now patches/ files (cosi-bucket-class-lock-readonly.patch, s3-service-name.patch) wired into the Makefile update target, alongside the existing s3-tls-main-port / cosi-provisioner-sa-name customizations. A re-vendor no longer drops them — exactly the trap you flagged.

Follow-ups

  • FU1cc99230delete added to the healer's pods rule; both delete remedies (reserved/infra reschedule + the disconnect-failure fallback) were Forbidden before.
  • FU3014de3f — unit test for the conflict-retry: a fake client returns one Conflict on the first HelmRelease Update and succeeds on the second, asserting err == nil and exactly two writes.
  • FU46deddc6 — the refuse now triggers for any non-terminal owner phase (!= Succeeded && != Failed), so a Pending owner that already holds a live IPAM allocation isn't disconnected mid-startup.
  • FU5 ✅ — PR description now has a "Scope beyond the original 8" section listing the production-behavior changes (VPA Auto→Initial for etcd+monitoring, vmselect/vmstorage minAllowed, the cozystack-api conflict-retry) and the e2e follow-ups.
  • FU7f3914d2persist-credentials: false on the build-cache checkout. SHA-pinning the actions is being done repo-wide in ci: harden workflow token permissions and pin actions by SHA #2849 rather than piecemeal here.

Deferred (with reasoning)

  • FU2 — kept the s3 Service on seaweedfs.name-based naming via patch to preserve rendered output. The divergence from the componentName/fullname siblings is latent only: cozystack deploys the chart so fullname == seaweedfs (the global.seaweedfs env vars already hard-code seaweedfs-master/seaweedfs-filer-client), so the Service and its consumers all resolve to seaweedfs-s3. Happy to switch to fullnameOverride: seaweedfs in a follow-up if you'd rather close the latent gap.
  • FU6 — fork PR build push stays as-is; external forks are handled via the existing mirror-to-same-repo-branch workflow.

The per-test failure snapshots (host in cozytest.sh, tenant in run-kubernetes.sh)
ran `crust-gather collect` unbounded. On a contended/degraded cluster the collect
can hang indefinitely — observed wedging the E2E install step for ~2h35m after a
test failure until the job-level cancel, with no output after the capture line.
`|| true` only catches a bad exit code, never a hang.

Wrap both invocations in `timeout -k 30 300`: a 5-minute cap (hard SIGKILL 30s
later if the collect ignores SIGTERM) bounds the hang; a partial snapshot still
beats a multi-hour wedge, and timeout's exit 124 is already swallowed by `|| true`.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

E2E re-run update + root cause of the storage failure

Re-ran E2E on 5653c30. Across runs on this head:

Run E2E Failed at
1 (f3914d2) install — vlclusters/generic never operational: vlselect-generic liveness probe gets connection refused on :9471 while the process is listening (srv2 dataplane), 51 restarts. Plus the 3h crust-gather hang (fixed in 5653c30).
2 (5653c30) ✅ green
3 (5653c30) app tests vminstance + kubernetes-previous — LINSTOR/DRBD on srv1

The same SHA passes and fails on different subsystems each run, so the red E2E is environmental flakiness on the sandbox, not the change — none of these paths are touched by this PR.

srv1 root cause (run 3)

vm-disk-test and nfs-test-pod never get storage because one DRBD resource wedges mid-autoplace:

  • All LINSTOR nodes Online; pools have space (srv1 ZFS data = 109 GiB free). Other DRBD volumes are UpToDate on all three nodes — srv1 is not broken.
  • The failing PVC (pvc-53e09291) is stuck in a contradictory state: srv1 Unknown, srv2 DELETING, srv3 Inconsistent/Connecting.
  • The srv1 satellite loops Aligning zvol → regenerate .res → Failed to create meta-data for DRBD volume … [Report 6A376613-21381-NNN] ~690× in ~5 min, never converging. Upstream trigger: Could not find suitable node to automatically create a tie breaking resource.

Reads as a LINSTOR autoplace / tie-breaker reconcile wedge (orchestration-level), not a disk fault. The exact drbdmeta error couldn't be confirmed from the artifact — see below.

Two cozyreport.sh diagnosability gaps that blocked a conclusive RCA

  1. hack/cozyreport.sh:286talosctl … dmesg --tail=200, but talosctl's --tail is a boolean flag, so every per-node capture dies with invalid argument "200" for "--tail" flag → no node kernel logs collected.
  2. hack/cozyreport.sh:268-270 collects linstor n l / sp l / r l but not linstor error-report list/show → the actual drbdmeta stderr (the one place the root cause lives) is never captured.

Fixing both would make this storage-failure class diagnosable in CI instead of a dead end — happy to fold a fix into this PR if useful.

Re-running E2E once more for another data point.

@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Aleksei Sviridkin (@lexfrei) ready for another look when you have a moment 🙏

  • All review points are addressed — B1 + FU1 landed (kvaps), FU3 / FU4 / FU7 pushed (per-finding mapping in my reply above), plus a bonus fix for the 3h crust-gather snapshot hang.
  • CI is green and reproducible on the latest head (5653c30): 2 of 3 E2E runs on this exact SHA passed end-to-end. The one red was a proven LINSTOR tie-breaker / DRBD-metadata infra flake on the sandbox, unrelated to the diff (root cause in the comment above).

Could you re-review to clear the CHANGES_REQUESTED when you get a chance?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — the SeaweedFS customizations are now carried as patches wired into make update, so a re-vendor no longer drops them. That resolves my earlier blocker, and nothing else in the batch is a merge risk.

Business context: consolidated landing branch for 8 interdependent platform/CI stabilization fixes, reviewable commit-by-commit.

Resolved blocker

The -lock/-readonly BucketClasses and the s3 service-name override now live in patches/cosi-bucket-class-lock-readonly.patch and patches/s3-service-name.patch, both applied from the update target. A future re-vendor re-applies them instead of silently dropping hand-edits.

Production-surface review (no blockers)

  • cozystack-api conflict retry (pkg/registry/apps/application/rest.go): retry.RetryOnConflict re-reads the live HelmRelease, copies its resourceVersion and retries. Last-write-wins is correct here since the HelmRelease spec is fully derived from the Application. Covered by rest_conflict_test.go.
  • volumeSizeLimitMB: 30000 -> 1000 (seaweedfs values.yaml): not a capacity regression. The volume servers run -max 0 (maxVolumes: 0), so SeaweedFS derives the volume count from disk size and total capacity is unchanged — only volume-file granularity changes. 1000 is the upstream chart default, and it also unblocks the default 10Gi PVC config, not just the E2E sandbox.
  • VPA Auto -> Initial (etcd, monitoring, monitoring-agents): production-wide but justified. Auto evicts running pods (etcd quorum disruption plus install-time churn that trips the Cilium IP-reuse leak). Initial together with the raised minAllowed floors is the safer combination for these workloads. The tradeoff (no live resize of already-running pods) is acceptable and documented inline.
  • capi startupProbe: standard ~5-minute startup budget before liveness takes over. Fine.

Non-blocking follow-ups

  1. The ci: type undersells the scope — the batch also changes the aggregated API server, etcd and monitoring behavior. The PR body is transparent about it, so this is only a labeling nit.
  2. The e2e Cilium leak-healer Job (under hack/) runs with hostNetwork plus pods/exec and no explicit container securityContext. It is ephemeral test-cluster tooling and never ships under packages/, but hardening it is cheap if you touch it again.

@myasnikovdaniil myasnikovdaniil changed the title ci: consolidated platform & e2e stabilization batch (8 fixes) ci: consolidated platform & e2e stabilization batch Jun 22, 2026
@myasnikovdaniil
myasnikovdaniil merged commit aeaedca into main Jun 22, 2026
17 of 18 checks passed
@myasnikovdaniil
myasnikovdaniil deleted the ci-next branch June 22, 2026 04:19
myasnikovdaniil added a commit that referenced this pull request Jun 22, 2026
## What this PR does

The `Build cache (main)` workflow (`build-main.yaml`, added in #2948)
failed on
its first run:
https://github.com/cozystack/cozystack/actions/runs/27929277423

The `Set up Buildx` step ran without `DOCKER_CONFIG`, so
`docker/setup-buildx-action` created the docker-container builder under
the
runner's default `~/.docker`. The build step runs with
`DOCKER_CONFIG=$RUNNER_TEMP/.docker` (seeded earlier, holding the OCIR
auth), so
buildx couldn't find the builder and failed immediately:

```
ERROR: no builder "builder-6fb46242-…" found
```

The post-job `docker buildx rm` still succeeded — because the action's
cleanup
runs under the default config where the builder actually lived — which
confirms
the builder existed but in the wrong config store.

This sets `DOCKER_CONFIG` on the `Set up Buildx` step so the builder
instance is
created in the same config the build step reads.

Impact: only the main cache-warmer was broken. PR builds `--cache-from`
this
ref, so a missing cache is a cold build, not a failure — but the
warm-cache
speedup from #2948 wasn't being delivered until this fix.

### Release note

```release-note
NONE
```


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

## Summary by CodeRabbit

* **Chores**
* Improved build pipeline stability by ensuring consistent Docker
configuration across workflow steps. This enhances the reliability of
the container build and deployment process.

<!-- 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/build Issues or PRs related to image build infrastructure, multi-arch support area/ci Issues or PRs related to CI workflows, GitHub Actions, automation area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) area/testing Issues or PRs related to testing (e2e, bats, unit tests) area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants