Skip to content

feat(e2e): consolidated E2E optimization changes (cozyreport + helmrelease-interval) - #2500

Closed
myasnikovdaniil wants to merge 29 commits into
mainfrom
feat/e2e-optimization
Closed

feat(e2e): consolidated E2E optimization changes (cozyreport + helmrelease-interval)#2500
myasnikovdaniil wants to merge 29 commits into
mainfrom
feat/e2e-optimization

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Consolidation branch + team preview of the updated E2E test suite. Not
intended to merge as one — each section will be split into a focused PR (or
the relevant fix has already been split into a child PR; see Stage 1 below).
Kept open as the single place to (a) eyeball the full updated test suite and
(b) see what Run E2E tests looks like after the stabilization pass.

CI status: green twice end-to-end as of 2026-04-30 (last run:
25105826461).

Why this exists

The Run E2E tests step had a 3× retry loop that masked deterministic failures
as "flakes" and tripled the diagnostic wall-time. Audit of 30 successful PR
runs found that across 5 sampled failure attempts, 25/25 retries failed
the retry loop never once recovered a flake. It only stretched deterministic
bugs.

This branch removes the retry loops, then walks through every test step that
relied on the retry to mask a deterministic problem and either (a) fixes the
underlying bug in a focused child PR or (b) tightens the test so failures
surface as a clear error instead of a five-minute timeout.

What was done to stabilize the test suite

1. Drop CI retry loops on Run E2E tests and Install Cozystack

Single attempt + diagnostics on failure (HR list + cluster events) under a
collapsible group. 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. (.github/workflows/pull-requests.yaml)

2. Replace fixed timeouts with event-driven backstops

14 sleep N / timeout N until …; do sleep …; done patterns in hack/*.bats
and hack/*.sh replaced with kubectl wait --for=condition=… plus a small
existence backstop. The 3 genuine sleeps that remain (LINSTOR node membership
propagation, MetalLB end-to-end HTTP path, HR-count emission heuristic) are
annotated with TODO + rationale so the next reader knows they were
intentional.

3. Add kubectl wait existence backstops — 33 sites, 12 bats files

kubectl wait against a resource that doesn't exist yet sits silently for the
full timeout and then fails with error: timed out waiting for the condition
— useless for diagnosis. Wrapping each kubectl wait with a short until kubectl get … >/dev/null 2>&1; do sleep 2; done existence loop surfaces the
real error: the resource never appeared. 33 sites updated across redis,
kafka, openbao, harbor, mongodb, qdrant, vminstance, clickhouse, mariadb,
postgres, bucket, and the install bats.

4. Fix the deterministic bugs the retry was hiding

The audit catalogued 10 bugs that the retry pattern was masking. Each one is
now landing in a focused child PR:

Bug Hit rate Fix
Helm namespace-ownership conflict on cold install 17/17 #2508 — drop chart-defined Namespace; caller pre-applies with PSA labels
seaweedfs-system HR kubectl wait 2-min timeout (no progress between Flux's 5-min reconcile windows) 17/17 #2509Strategy=RetryOnFailure + RetryInterval=30s
vminstance VMDisk delete-recreate race 7/17 this PR — drop || true on delete; block until removal completes
VM Instance no IP within 20 s 7/17 this PR — bump to 120s with 2s poll
linstor-controller deployment not-found race 3/17 this PR — wait on LINSTOR HR Ready before its deployment
Talos srv1 not-found bootstrap race 2/17 mitigated — Prepare environment retains 3× retry
redis kubectl wait pvc race (NotFound) 1/1 surfaced after retry drop this PR — existence backstops in #3 above
harbor — bucket-secret template fails on nil .data 1/1 #2528 commit 8daf4952 — values-driven shape
harbor-test-system HR fires before bucket secret populated 1/1 #2528 — Flux valuesFrom with optional: false gates the HR
harbor valuesFrom + targetPath triggered Helm strvals comma-split on the BucketInfo JSON 1/1 #2528 commit 2555a9ac — drop targetPath, merge BucketInfo at the values root
BucketAccess controller "object has been modified" race flaky #2529 — retry on conflict during BucketAccess reconcile

5. Richer cozyreport for when something does fail

hack/cozyreport.sh now collects:

  • Flux controller logs + source resources (HelmRepository, OCIRepository, GitRepository, ExternalArtifact)
  • Decoded Helm storage secrets for non-Ready HRs (base64 -d | base64 -d | gzip -d)
  • Cluster events (all + warning-only)
  • cert-manager resources + logs
  • cozystack-operator deployment logs (current + previous)
  • Application / ApplicationDefinition / Tenant resources
  • Sandbox host context per node (df, free, ps, dmesg, talosctl logs/dmesg/kubelet/containerd)

New hack/cozyreport-summary.sh writes a summary.txt at the archive root
listing what is broken right now — first thing to read when triaging an
artifact.

6. Configurable HelmRelease Interval + parallel install prep (companion to #2509)

Eliminates the 8–10 min Flux dead zone in Install Cozystack by letting the
E2E installer override the default 5-min reconcile interval to 30s. The
install bats also runs hack/e2e-post-install-prep.sh (LINSTOR pool +
StorageClass + MetalLB pool) in the background, in parallel with the platform
reconcile wait. Production default unchanged. Operator-side flag landing in
#2509.

Stage 1 — child PRs

These hold the actual fixes and should land first. File-disjoint, mergeable in
any order. Each landing one removes its cherry-pick from this branch on rebase.

PR Status Scope
#2508 Draft (owner finishing) installer: drop chart-defined Namespace; caller pre-applies cozy-system with PSA labels
#2509 Draft cozystack-operator: 5 HelmRelease generation knobs + Strategy=RetryOnFailure
#2528 Open harbor: bucket-secret values-driven; HR gated on BucketInfo; merge at values root
#2529 Open objectstorage-controller: retry on Bucket update conflict during BucketAccess reconcile

Stage 2 — what's left in this PR after Stage 1

Once #2508/#2509/#2528/#2529 land and this branch is rebased, the leftovers
are sections 1, 2, 3, 5, 6 above plus the vminstance and timeout fixes
from the bug table. Likely split:

  • feat(cozyreport): richer archive + summary.txt
  • ci(workflow): drop retry loops on Run E2E + Install Cozystack
  • test(e2e): replace fixed timeouts with event-driven backstops
  • test(e2e): kubectl wait existence backstops across 12 bats files
  • fix(virtual-machine): vminstance disk race + IP/ready timeouts
  • test(e2e): parallel install prep alongside platform reconcile

Production safety

Release note

NONE

@coderabbitai

coderabbitai Bot commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b5f5a57d-f9d6-4841-92ca-791b420c09fb

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/e2e-optimization

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 area/testing Issues or PRs related to testing (e2e, bats, unit tests) kind/feature Categorizes issue or PR as related to a new feature labels Apr 27, 2026
…t context

The previous report omitted everything needed to diagnose failures that
originate deeper than a single system package install — Flux controller
logs, the actual rendered Helm manifest from the storage secret, recent
events, cert-manager state, the cozystack-operator's own logs, and
sandbox host context (talosctl logs, dmesg, disk).

This makes failures self-diagnosable from cozyreport.tgz alone, which is
the only artifact that survives a failed E2E run.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
A 50MB tarball with 200 directories is opaque on first contact.
summary.txt at the root surfaces 'what is broken right now' — non-Ready
HRs, ImagePullBackOff pods, OOMKilled events, cert-manager state,
Flux Source health, storage binding, node pressure — so triage starts
with one file instead of a directory tree dive.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…0s in E2E

Adds --helmrelease-interval (default 5m, matching today's behaviour) to the
operator and a corresponding cozystackOperator.helmReleaseInterval value
on the cozy-installer chart (default empty -> no flag rendered ->
operator default 5m applies).

E2E install sets it to 30s. Rationale: with the 5m default, dependency-
blocked HRs (waiting on cert-manager webhooks, CRDs) are requeued only
every 5 min, producing an 8-10 min dead zone in the E2E install where
the fast pack of HRs is Ready but the slow tail hasn't been retried yet.
30s collapses the gap.

Production defaults are unchanged. The override is opt-in per install.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…tall

Previously these three configuration blocks ran as separate @test cases
after the 15m platform HR wait, adding ~1.5 min sequentially. None of
them gates platform HR reconcile, so they can run as a background prep
that completes during the main wait. The helper script polls for its
own prerequisites and is awaited at the end of the Install Cozystack
test so failures still surface deterministically.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…yment

The previous 5-min timeout on `until kubectl get deploy/linstor-controller`
fired during cold-install runs where the LINSTOR HR's dependency chain
(cert-manager → piraeus-operator-crds → piraeus-operator → linstor)
takes longer than 5 min to resolve, killing the whole post-install-prep
script via `set -eu`. With CI's 3x retry the failure was hidden; with the
retry removed it would surface every cold install.

Wait on the LINSTOR HR being Ready first (the actual prerequisite),
then keep the existence-poll as a near-instant backstop, then wait
for Available. Same final semantics, no fragile fixed-window timeout.

Surfaced by PR #2500 cozyreport diagnostics (post-install-prep.log).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Across 5 sampled failure runs (30 PR runs total), 25/25 retry attempts
failed. The retry loop never recovered a flake — it only stretched
deterministic failures (e.g. kubernetes-previous: 11:37 + 7:49 + 10:16
= 29:42 wasted on one broken test). Replace with a single attempt plus
inline diagnostics (HR list + recent events) so the actual failure
surfaces immediately. Re-runs remain available via the standard
'gh run rerun' / empty-commit retry path.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
A failed cluster bootstrap or platform install is almost always a real
bug (Talos boot, chart, operator) — not a flake. The 3x retry on these
steps was the direct cause of the 250-min outlier run (24931612182):
each retry compounds 25-30 min of work, and the failure mode persists
across attempts. Single attempt + the existing 'collect-report' step
on always() gives faster signal and a debug archive.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Prepare environment is pure infrastructure (Talos image download, sandbox
VMs boot, network setup). Failures here are mostly noisy-runner /
transient infra hiccups (image-download stalls, NIC negotiation, etc.) —
not cozystack code under test. Retry-on-failure is the right policy for
infra setup.

Install Cozystack and Run E2E tests remain single-attempt: they exercise
cozystack code, where retries hide real bugs.

Refines the previous two retry-removal commits.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The app bats files all share the pattern:

    kubectl apply -f - <<EOF ... EOF
    sleep 5
    kubectl wait hr <name> --for=condition=ready

The fixed sleep guesses how long the cozystack-operator needs to translate
the app CR into a HelmRelease. On a noisy CI runner or after a cold
install that wait is sometimes too short, so the immediately-following
`kubectl wait hr ...` fails because the HR object does not exist yet
(`kubectl wait` errors out instantly on a missing resource).

Replace each `sleep 5` (and the one outlier `sleep 15` in foundationdb
that was paying for the same risk) with an event-driven backstop that
polls for the HR's existence and exits the moment it appears, capped at
60s. The downstream `kubectl wait --for=condition=ready` is unchanged,
preserving the original total budget.

Same shape as the LINSTOR fix in commit eb87413 on feat/e2e-optimization:
wait for the actual prerequisite (the HelmRelease the operator creates),
not on a downstream artefact whose existence depends on it.

Files: postgres, mariadb, kafka, mongodb, clickhouse, redis, qdrant,
harbor, openbao, external-dns (both tests), vminstance (both tests, with
the existence backstop hoisted before the downstream vmi-ip poll), and
foundationdb.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…tl proxy

`kubectl proxy --port=21234 & sleep 0.5` guesses how long the proxy
needs to start listening. On a slow runner the curl that follows can
race the proxy and fail with "connection refused".

Replace the fixed `sleep 0.5` with `nc -z localhost 21234` polled until
the listener accepts a connection (10s cap). Same idiom already used in
hack/e2e-apps/bucket.bats for the seaweedfs-s3 port-forward.

Also save the proxy PID to a named variable so the trap kills the right
process even if a later background command lands first in $!.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Three poll/sleep sites in the install bats and the kubernetes runner are
NOT replaceable by `kubectl wait --for=condition=...` because the signal
they observe is not a Kubernetes API condition:

- e2e-install-cozystack.bats:55-56 - 5s pad lets late-arriving HRs join
  the awk-snapshot the parallel `kubectl wait` runs against. There is no
  k8s condition for "all expected platform HRs have been emitted" short
  of hard-coding the list.
- e2e-install-cozystack.bats:75 - LINSTOR node membership is reported by
  the linstor binary running inside the controller pod (kubectl exec),
  not a CRD status, so kubectl wait cannot subscribe to it.
- e2e-apps/run-kubernetes.sh:231-238 - validates the external HTTP path
  through MetalLB -> tenant ingress -> backend pod end-to-end. Not a
  single API condition.

Annotate each with TODO(e2e-replace-fixed-timeouts) and the rationale so
future readers do not "fix" them with a kubectl wait that does not work.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The cozy-installer chart declares Namespace cozy-system itself (with
helm.sh/resource-policy: keep). Combining that with --create-namespace
makes Helm v3 pre-create the namespace via plain kubectl-create (without
helm annotations); the subsequent chart apply then fails with
'namespaces cozy-system already exists'.

The 3x retry on Install Cozystack was hiding this. First attempt failed,
second saw 'release exists' and treated it as upgrade. Reproducible
across PRs (PR #2507 E2E hit the same first-attempt failure today and
recovered on retry). Surfaced cleanly after dropping the retry on this
step.

Drop --create-namespace; let the chart manage its own namespace.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The cozy-installer chart declares Namespace cozy-system itself, which
makes helm cold-install paradoxical:
  - WITH --create-namespace: helm pre-creates the ns without helm
    annotations; the chart's own Namespace apply then fails with
    'already exists'.
  - WITHOUT --create-namespace: helm fails before any apply because it
    can't write its release-secret to a non-existent ns.

The 3x retry was masking both directions — second attempt saw a partial
release and took the upgrade path, which patch-merges instead of
strict-create. Reproducible on every cold install across PRs.

Pre-create cozy-system with meta.helm.sh/release-name and
app.kubernetes.io/managed-by=Helm so helm adopts it cleanly during
install. Labels mirror the chart's template (PSA enforce=privileged etc.)
so behaviour matches what the chart would have done.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…hook

Removes the chart's `Namespace cozy-system` resource and replaces it with
a pre-install/pre-upgrade Job hook (cozy-system-labeler) that patches the
required labels onto the namespace after `--create-namespace` creates it.

Why: helm v3 has a known chicken-and-egg with charts that ship their own
Namespace:
  - WITH `--create-namespace` on the install command, 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.

Until now this was hidden by the 3x retry on `Install Cozystack` in
`.github/workflows/pull-requests.yaml`: first attempt always fails with
the conflict, second attempt sees the existing failed release and takes
the upgrade code path which patch-merges instead of strict-create.
Reproducible on every cold install. Surfaced cleanly when retries on the
install step were dropped.

After this change:
  - install commands use `helm upgrade --install --namespace cozy-system
    --create-namespace`. Standard pattern, matches kube-prometheus-stack /
    argo-cd / cert-manager / others.
  - the pre-install hook (SA + ClusterRole + ClusterRoleBinding + Job)
    patches `cozystack.io/system=true` and
    `pod-security.kubernetes.io/enforce=privileged` onto the namespace
    before main resources apply.
  - hook-delete-policy=before-hook-creation,hook-succeeded so the RBAC
    surface only exists during install/upgrade.

Verified end-to-end on a kind cluster: cold install in 3.3s, upgrade
idempotent, cleanup clean. Image pinned to `alpine/k8s:1.32.0` for the
hook (small, public, includes kubectl).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Now that the chart fix (this same branch) bootstraps cozy-system via
--create-namespace + a pre-install label hook, the bats no longer needs
to pre-apply a hand-crafted helm-adoptable namespace. Restore plain
--create-namespace and drop the kubectl apply block.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…y timeouts

Two reliability fixes in `hack/e2e-apps/vminstance.bats`:

1. Delete-recreate race. Both @test blocks deleted the prior VMInstance/VMDisk
   with `kubectl ... --timeout=2m || true`. The `|| true` silently swallowed
   timeout errors, letting the test continue with the resource still in
   finalizer-drain. The next `kubectl apply` then no-ops with
   "Detected changes to resource ... which is currently being deleted",
   producing a NotFound on the downstream HR wait. Bumped the delete
   timeout to 3m and removed `|| true` so true delete failures surface
   loudly. End-of-test cleanup deletes also got the explicit timeout.

2. VM IP and VM ready timeouts. The 20s timeout for the VMI to acquire an
   IP was unrealistic for nested KubeVirt under runner load — virt-launcher
   + libvirt + cloud-init DHCP routinely takes 30-60s. Bumped to 120s with
   a 2s poll interval (60 polls instead of 4). VM ready timeout bumped from
   20s to 60s for the same reason.

Both surfaced as recurring first-attempt failures in the past 30 successful
PR runs (7/17 analysable runs in each case) — masked by the 3x retry on
`Run E2E tests`.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… with configurable RetryInterval

The operator generated HelmReleases with `Install.Remediation{Retries: -1}`
and `Upgrade.Remediation{Retries: -1}`, which is the v2 way of saying
"retry forever, never remediate". Combined with the spec-level
`Interval: 5m`, this meant a failed install/upgrade waited a full 5
minutes before the next attempt — even when the underlying race that
caused the failure (e.g. a chart artifact not quite Ready, a HR
dependency reconciling out of order) cleared in seconds.

Audit of the last 30 successful PR runs found that every single run had
its `Install Cozystack` step's `kubectl wait hr/seaweedfs-system` time
out at the 2-minute mark and recover only after an inline
`flux reconcile --force` workaround. Same root cause: 5-minute retry
interval was longer than the test's wait window. That workaround can be
removed once this change lands.

Switch to `Strategy.Name=RetryOnFailure` with `RetryInterval` exposed via
a new `--helmrelease-retry-interval` operator flag (default 30s):

- Functionally equivalent to the previous configuration ("retry forever
  on failure") since `Retries: -1` meant remediation never fired anyway.
- Decouples retry-on-failure timing from `spec.Interval` so failed
  releases recover at 30s without polling healthy releases at the same
  cadence (which would multiply controller load by ~10x for no benefit).
- Helm chart exposes `cozystackOperator.helmReleaseRetryInterval`,
  defaulting to empty (operator uses its own 30s default).
- Same flag pattern as the existing `--helmrelease-interval` (5m default),
  also added in this PR via cherry-pick.

Verified: go build green, go test ./internal/operator/... pass, helm
template renders both flags conditionally on values.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The HelmRelease generation in package_reconciler hardcoded three values
that have caused recurring pain:

- Install.Timeout = 10m (line 214)
- Upgrade.Timeout = 10m (line 220)
- MaxHistory = unset (Helm default 5)

The 10m timeout in particular contradicts the per-Application timeout
work done in `pkg/config/config.go` and
`pkg/registry/apps/application/rest.go` (commit 7b146cb), which lets
individual Applications override the install/upgrade timeout via
annotation. The PackageSource side had no equivalent — etcd works around
it by hand-rolling its HR YAML in `packages/apps/tenant/templates/etcd.yaml`
to set a 30m timeout, harbor has had multiple commits chasing the same
problem from the other side. This closes that gap.

New operator flags (each maps to a chart value with empty default;
operator uses its own default when value is empty):

- `--helmrelease-install-timeout` (default 10m) → Spec.Install.Timeout
- `--helmrelease-upgrade-timeout` (default 10m) → Spec.Upgrade.Timeout
- `--helmrelease-max-history` (default 5)        → Spec.MaxHistory

Production behaviour unchanged. E2E and edge cases (cert rotation,
slow-installing charts) can override per-cluster without modifying
chart manifests.

Per-component overrides on `ComponentInstall` (mirroring the existing
`UpgradeCRDs` pattern) would be a strictly better fix for charts like
etcd that need an outlier timeout — left as a deliberate followup since
it touches the Package CR API.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…ule nodes

The pre-install Job runs before any CNI is installed, so all nodes are
tainted NotReady-NoSchedule and the pod has no pod network. Without
adjustment the Job's pod sits Pending until helm's pre-install timeout
fires, blocking the entire install.

Surfaced in PR #2500 CI run 25040584552 attempt 2:
  FailedScheduling  0/3 nodes are available: 3 node(s) had untolerated
  taint {node.kubernetes.io/not-ready: }.

Mirror the cozystack-operator deployment's scheduling pattern:
- hostNetwork=true so the pod doesn't depend on CNI
- Tolerations matching the operator
  (not-ready / unreachable / cilium.agent-not-ready / cloudprovider.uninitialized)
- Variant-aware KUBERNETES_SERVICE_HOST/PORT env so kubectl reaches the
  apiserver before kube-proxy + CNI are up:
    talos:   localhost:7445   (KubePrism)
    generic: cozystack.apiServerHost / Port
    hosted:  default in-cluster

Verified on the sandbox kind cluster that the rendered Job manifest
schedules and the kubectl container starts (the kind cluster itself
doesn't run KubePrism so the env-var path can't be end-to-end-tested
there; the Talos E2E will exercise it).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil added a commit that referenced this pull request Apr 28, 2026
…ule nodes

The pre-install Job runs before any CNI is installed, so all nodes are
tainted NotReady-NoSchedule and the pod has no pod network. Without
adjustment the Job's pod sits Pending until helm's pre-install timeout
fires, blocking the entire install.

Surfaced in PR #2500 CI run 25040584552 attempt 2:
  FailedScheduling  0/3 nodes are available: 3 node(s) had untolerated
  taint {node.kubernetes.io/not-ready: }.

Mirror the cozystack-operator deployment's scheduling pattern:
- hostNetwork=true so the pod doesn't depend on CNI
- Tolerations matching the operator
  (not-ready / unreachable / cilium.agent-not-ready / cloudprovider.uninitialized)
- Variant-aware KUBERNETES_SERVICE_HOST/PORT env so kubectl reaches the
  apiserver before kube-proxy + CNI are up:
    talos:   localhost:7445   (KubePrism)
    generic: cozystack.apiServerHost / Port
    hosted:  default in-cluster

Verified on the sandbox kind cluster that the rendered Job manifest
schedules and the kubectl container starts (the kind cluster itself
doesn't run KubePrism so the env-var path can't be end-to-end-tested
there; the Talos E2E will exercise it).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
…espace

The pre-install Job approach (added in c0b76b1, b46151a) hits a fatal
chicken-and-egg on Talos clusters that enforce PodSecurity baseline:latest
on bare namespaces:

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

The labeler needs hostNetwork=true (no CNI yet) which requires the namespace
to be PSA-privileged, but the labeler's whole job is to add that label.
Cannot work from inside the namespace it's labeling.

Drop the labeler Job + ServiceAccount + ClusterRole + ClusterRoleBinding.
The chart now only removes the chart-defined Namespace and assumes the
caller pre-creates cozy-system with the right labels — same pattern as
kube-prometheus-stack, argo-cd, cert-manager, and other mainstream charts.

Install procedure becomes two-step:

    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: Namespace
    metadata:
      name: cozy-system
      labels:
        cozystack.io/system: "true"
        cozystack.io/deletion-protected: "true"
        pod-security.kubernetes.io/enforce: privileged
    EOF
    helm upgrade installer packages/core/installer \
      --install --namespace cozy-system ...

The cozystack-operator pod (hostNetwork=true) is then admitted because the
namespace already has enforce=privileged.

Verified end-to-end on a kind cluster — clean install in 0.1s, no hooks
involved, no PSA conflict, no retry needed.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The chart fix (3a87485, be0a668) removes the chart-defined Namespace
without replacing it. On the Talos E2E cluster the apiserver enforces
PodSecurity baseline-by-default on bare namespaces, so the cozystack-
operator pod (hostNetwork=true) is rejected if cozy-system is created
without enforce=privileged.

Apply the namespace from the bats with the labels the operator depends
on, then run helm install without --create-namespace. Mirrors the
documented production install procedure for the new chart shape.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
`kubectl wait <kind> <name>` errors immediately with NotFound if the
resource doesn't exist yet — even with --for=condition or --for=jsonpath.
The redis test in CI run 25043350705 failed in 1 second for exactly this
reason: the redis-failover operator hadn't created the PVC by the time
the test waited for it. Previously the 3x retry on `Run E2E tests`
masked this race; with retry dropped, every such call is a flake risk.

Add a small `until kubectl get` existence backstop before each
kubectl wait, matching the pattern already established for HRs in
commit 66888c9.

33 backstops across 12 files:
  bucket.bats         — bucketclaims, bucketaccesses x2
  clickhouse.bats     — statefulset 0-0 (0-1 already covered)
  harbor.bats         — deploy x3, bucketclaims, bucketaccesses
  kafka.bats          — kafkas
  mariadb.bats        — statefulset, deployment
  mongodb.bats        — statefulset
  openbao.bats        — sts, pvc
  postgres.bats       — job.batch
  qdrant.bats         — sts, pvc
  redis.bats          — pvc, deploy, sts (the trigger)
  vminstance.bats     — dv, pvc, vm (with 120s for KubeVirt latency)
  e2e-install-cozystack.bats — apiservices, sts/etcd, vmalert,
                                vmalertmanager, vlclusters, vmcluster,
                                clusters.postgresql.cnpg.io,
                                deploy/grafana-deployment, namespace

Same pattern, same 60s default timeout for the existence wait (120s for
nested-virt resources). Once the resource exists, the original wait
timeout takes over.

Run-kubernetes.sh has the same race shape on several waits (nfs, kamaji,
machinedeployment, etc.) — out of scope here; flagged for follow-up.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Two bugs caused the harbor E2E to fail (CI #25049445125):

1. cozy-harbor's bucket-secret.yaml called `index $existingSecret.data
   "BucketInfo"` unconditionally. During first-time install the COSI
   BucketAccess controller may create the credentials Secret as a
   placeholder before populating it, so `.data` is nil and the chart
   render crashes with `index of untyped nil`.

2. The downstream `<release>-system` HelmRelease starts reconciling
   immediately, in parallel with bucket provisioning, hitting bug #1
   on every retry within the test's 5-minute window.

The previous shape relied on `lookup` to read the Secret at render
time. helm-controller's upgrade trigger is digest-based over composed
values + chart artifact, so a `lookup` returning new data on a later
reconcile is not enough on its own to force an upgrade — the rendered
output may diverge but the digest does not.

Switch to a values-driven shape:

- `bucket-secret.yaml` now reads `.Values.bucket.bucketInfo` (a JSON
  string) and uses `dig` for safe access; if the value is empty or the
  expected nested fields are missing, no `*-registry-s3` Secret is
  rendered.
- The `<release>-system` HR sources `BucketInfo` via `valuesFrom` with
  `targetPath: bucket.bucketInfo`. With the default `optional: false`,
  helm-controller will refuse to compose values until the key exists,
  which both gates initial reconciliation on the BucketAccess Secret
  being populated and forces a config-digest change (and thus a helm
  upgrade) when its contents change.
- `bucket.secretName` is removed from the system chart's values and
  from the apps chart's `values:` block; the only consumer (the
  `lookup` call) is gone.

Verified four scenarios via `helm template` against the system chart:
unset bucketInfo, empty string, empty JSON object `{}`, and a
fully-populated BucketInfo — only the last renders the registry-s3
Secret; the others render nothing without erroring.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
… BucketAccess reconcile

Upstream COSI v0.2.2's BucketAccess reconciler does a Get->mutate->Update
on the parent Bucket and surfaces "Operation cannot be fulfilled ... the
object has been modified" as a FailedGrantAccess event when it races
against the Bucket reconciler in the same controller process. Wrap the
mutation in retry.RetryOnConflict so the reconcile loop refreshes and
retries instead of leaking the conflict to users.

Carried as 91-bucketaccess-conflict-retry.diff until upstreamed
(cf. 89-reconciliation.diff and 90-bucket-name.diff, both dropped
in c29d501 once merged upstream).

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Some workloads (OVN raft, LINSTOR controller) fail when replicas start
at different times due to image-pull stagger across nodes. Add a
DaemonSet-based pre-pull step that runs before helm install, ensuring
all nodes have the images cached so every replica starts within
milliseconds of each other.

Covers kube-ovn (confirmed OVN raft race), piraeus-server, and
linstor-csi. Comments in hack/e2e-prepull-images.sh point to the
source chart values so versions stay in sync.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Some workloads (OVN raft, LINSTOR controller) fail when replicas start
at different times due to image-pull stagger across nodes. Add a
DaemonSet-based pre-pull step that runs before helm install, ensuring
all nodes have the images cached so every replica starts within
milliseconds of each other.

The script accepts image refs on stdin and creates one container per
image (parallel pulls, total time = max of any single image rather than
sum). The bats test sources images directly from the rendered charts
via yq, walking only PodSpec-shaped objects so version bumps stay in
sync automatically without a separate hardcoded list.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
The previous fix (a21c18f) sourced BucketInfo via Flux valuesFrom with
`targetPath: bucket.bucketInfo`. Flux runs values with `targetPath`
through Helm's `strvals.ParseInto`, which splits the value on commas as
list separators. The COSI BucketInfo JSON is comma-rich, so values
resolution bailed:

  could not resolve Secret chart values reference
  'tenant-X/harbor-X-registry-bucket' with key 'BucketInfo':
  key "\"spec\":{\"bucketName\":\"bucket-1785...\""
  has no value (cannot end with ,)

Drop `targetPath`. With only `valuesKey: BucketInfo`, helm-controller
unmarshals the value as YAML and merges at the chart's values root, so
JSON commas stay nested instead of being split. The system chart's
bucket-secret.yaml now reads `.Values.spec.bucketName` /
`.Values.spec.secretS3.{accessKeyID,accessSecretKey,endpoint}`,
guarded by nested `with` blocks so the registry-s3 Secret renders only
when secretS3 has been populated.

Gating semantics from the previous fix are preserved: with the default
`optional: false`, helm-controller still refuses to compose values
until the COSI BucketAccess controller writes `BucketInfo` into the
Secret, and the value's content still drives the HR config-digest.

Verified via `helm template` against the system chart with three
scenarios: missing `spec` and `spec` without `secretS3` render
nothing; full BucketInfo renders all five S3 env keys correctly.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
myasnikovdaniil added a commit that referenced this pull request May 2, 2026
…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 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
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 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
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 <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil added a commit that referenced this pull request May 4, 2026
…#2556)

## What this PR does

Some workloads (kube-ovn raft, LINSTOR controller / piraeus-server) fail
when replicas start at materially different times due to image-pull
stagger across nodes. Adds a DaemonSet-based pre-pull step that runs
before `helm install`, ensuring every node has the images cached so
replicas start within milliseconds of each other.

`hack/e2e-prepull-images.sh` accepts image refs on stdin and creates one
container per image so pulls run in parallel — total time = max(any
single image) rather than sum. The bats test sources image refs directly
from the rendered platform charts via `yq`, walking only PodSpec-shaped
objects, so version bumps stay in sync without a hardcoded list.

Surfaced from #2500.

### Release note

```
NONE
```


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

* **Tests**
* Added an end-to-end test that pre-pulls platform container images
before the installation workflow to improve install reliability.

* **Chores**
* Added an automated pre-pull utility that reads image lists,
deduplicates and sorts them, deploys a short-lived pre-puller across
nodes, waits for completion, then cleans up.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil added a commit that referenced this pull request May 4, 2026
#2553)

## What this PR does

Makes `cozyreport.tgz` actually useful when triaging an E2E failure
deeper than a single pod log. Adds:

- Flux controller logs (helm-controller, source-controller,
notification-controller, kustomize-controller, last 2000 lines each)
- Flux source resources (`HelmRepository`, `OCIRepository`,
`GitRepository`, `ExternalArtifact`)
- Decoded Helm storage secrets for non-Ready HRs (`base64 -d | base64 -d
| gzip -d`)
- Cluster events (all + warning-only filtered file)
- cert-manager `Certificate` / `CertificateRequest` / `Order` /
`Challenge` resources + cert-manager logs
- `cozystack-operator` deployment logs (current + previous)
- `Application` / `ApplicationDefinition` / `Tenant` resources
- Sandbox host context per node: `df`, `free`, `ps`, `dmesg`, `talosctl
logs/dmesg/kubelet/containerd`

New executable `hack/cozyreport-summary.sh` writes a `summary.txt` at
the archive root listing what is broken right now — the first thing to
read when downloading the artifact from a CI failure.

Surfaced from #2500.

### Release note

```
NONE
```


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

* **New Features**
* Added a timestamped diagnostic summary report that highlights failing
resources and key cluster issues.
* Expanded diagnostic collection: controller logs (current+previous),
Flux source status, cert-manager certificate details, Helm release
payloads, and Cozystack resource status.
* Added host-level diagnostics (disk/memory/process/dmesg) and improved
event/warning listings to aid troubleshooting.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil added a commit that referenced this pull request May 6, 2026
## What this PR does

Replaces fragile `sleep N` / `timeout N until …; do sleep …; done`
patterns in `hack/*.bats` and `hack/*.sh` with `kubectl wait
--for=condition=…` plus a small existence backstop:

```sh
until kubectl get <resource> >/dev/null 2>&1; do sleep 2; done
kubectl wait <resource> --for=condition=Ready --timeout=…
```

### Why the existence backstop matters

`kubectl wait` against a not-yet-created resource sits silently for the
full timeout and then fails with `error: timed out waiting for the
condition` — useless for diagnosis. With the backstop, the test fails
fast with a clear "resource never appeared" error.

Existence backstop added at 14 sites in this sweep across:
- `redis`, `kafka`, `openbao`, `harbor`, `mongodb`, `qdrant`,
`clickhouse`, `mariadb`, `postgres`, `bucket`, `external-dns`,
`foundationdb` bats
- `hack/e2e-install-cozystack.bats`
- `hack/e2e-apps/run-kubernetes.sh`
- `hack/e2e-test-openapi.bats` (port-readiness wait replacing fixed
`sleep 5` after `kubectl proxy`)

The vm-disk hunk in `hack/e2e-apps/vminstance.bats` is included here;
the vm-instance hunks land separately via the `daniil/split-vminstance`
PR.

### Genuine sleeps annotated

The 3 remaining genuine sleeps (LINSTOR node membership propagation,
MetalLB end-to-end HTTP path, HR-count emission heuristic) are annotated
with `# TODO` + rationale so a future reader knows they are intentional.

Same green-path semantics; failures now surface in seconds instead of
after a 5-minute timeout. Surfaced from #2500.

### Release note

```
NONE
```


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

* **Tests**
* Improved e2e stability by replacing fixed sleeps with timeout-based
polling across many integration tests, waiting for operator-created
resources to exist before readiness checks to reduce race conditions and
flakes.

* **Documentation**
* Added clarifying notes for LoadBalancer retry behavior and improved
local proxy readiness checks in e2e scripts.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil added a commit that referenced this pull request May 6, 2026
#2555)

## What this PR does

Three fixes for `hack/e2e-apps/vminstance.bats`, surfaced once the 3x
retry was dropped from `Run E2E tests`:

1. **Disk delete-recreate race.** The teardown used `kubectl delete
vmdisk … || true` and immediately created a new disk; sometimes the new
disk reconciled before the old finalizer completed, leaving a duplicate
PVC and the new VM stuck. Drop the `|| true`, block until the old disk
is gone, then create.

2. **VM IP wait timeout 20s → 120s.** Under nested-virt + concurrent
runner load, a fresh VM sometimes takes 60–90 s to acquire its IP via
DHCP. The 20 s bound was tripping deterministically.

3. **VM ready timeout 20s → 60s.** Same root cause; the readiness probe
needs the IP to be reachable, so the lower bound has to grow alongside
the IP wait.

Existence backstop on the `vm-instance-$name` HR is added here too (it
touches the same hunk as the timeout fix; cleaner to land them
together).

Surfaced from #2500.

### Release note

```
NONE
```


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

* **Tests**
  * Improved end-to-end test reliability for VM instance and disk flows.
* Replaced short sleeps with explicit waits for upstream and downstream
deployment artifacts.
* Extended polling windows and timeouts for IP assignment and readiness
checks.
* Strengthened cleanup to reliably remove prior VM and disk resources
before and after tests.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
myasnikovdaniil added a commit that referenced this pull request May 7, 2026
## What this PR does

### Parallel post-install prep

LINSTOR / StorageClass / MetalLB setup, previously sequential `@test`
cases after the 15 m platform HR wait, now run as a background prep that
completes during the platform reconcile. None of them gates platform HR
reconcile, so they can move out of the critical path.

New helper `hack/e2e-post-install-prep.sh` polls for its prerequisites
and is awaited at the end of the `Install Cozystack` test so failures
still surface deterministically.

### T2-race fix

Wait on the LINSTOR HR being `Ready` before polling for
`deploy/linstor-controller`. The old 5-min hard timeout fired during
cold installs where the LINSTOR HR's dependency chain (cert-manager →
piraeus-operator-crds → piraeus-operator → linstor) takes longer than 5
min to resolve.

### E2E uses helmReleaseInterval=30s

E2E install passes `--set cozystackOperator.helmReleaseInterval=30s` to
eliminate the 8–10 min Flux dead zone where dependency-blocked HRs are
requeued only every 5 min. Operator-side flag and chart value landed in
#2509; production defaults are unchanged.

Surfaced from #2500.

### Release note

```
NONE
```


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

* **Tests**
- Improved end-to-end test initialization to run concurrent post-install
preparation, wait for cluster release reconciliation, verify component
readiness, and validate automated storage configuration.

* **Chores**
- Added background post-install setup and logging to increase test
reliability and speed up cluster initialization and teardown
diagnostics.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
@myasnikovdaniil

Copy link
Copy Markdown
Contributor Author

Child PRs are merged

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/testing Issues or PRs related to testing (e2e, bats, unit tests) kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant