From 56108eca836932f0d15dfd95135ff539e469c855 Mon Sep 17 00:00:00 2001 From: Myasnikov Daniil Date: Wed, 24 Jun 2026 12:07:49 +0500 Subject: [PATCH 01/38] ci(renovate): pin base images and GitHub Actions to digests Enable Renovate's dockerfile and github-actions managers with pinDigests so OpenSSF Scorecard Pinned-Dependencies (currently 1/10) can pass and supply-chain refs stay reproducible. PR #2849 SHA-pinned actions in 13 workflows, but the check also scores container base images -- 58 unpinned FROM lines across 27 distinct images -- and left build-main.yaml / release-e2e.yaml unpinned. Nothing kept the new action SHAs updated either, so they would silently stop receiving security patches. Enabling the managers lets Renovate open the initial pin PR (and pin the two missed workflows) and keep digests fresh thereafter. Digest-only refreshes and the initial pin are batched into a single weekly PR to avoid a trickle of rebuild-triggering PRs on the shared CI runner; real tag bumps stay individual for review. Also pin s3manager's floating tags (golang:1, alpine:latest) to concrete versions first, otherwise digest tracking churns endlessly. Assisted-By: Claude Signed-off-by: Myasnikov Daniil --- .github/renovate.json | 14 +++++++++++++- packages/system/bucket/images/s3manager/Dockerfile | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index 9475acf1b..6c2010250 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,7 +4,7 @@ "config:recommended", ":semanticCommits" ], - "enabledManagers": ["gomod"], + "enabledManagers": ["gomod", "dockerfile", "github-actions"], "postUpdateOptions": ["gomodTidy", "gomodUpdateImportPaths"], "labels": ["automated", "do-not-merge/hold"], "automerge": false, @@ -22,6 +22,18 @@ "matchManagers": ["gomod"], "matchDepTypes": ["golang"], "enabled": false + }, + { + "description": "Pin Dockerfile base images and GitHub Actions to immutable digests so OpenSSF Scorecard Pinned-Dependencies passes and supply-chain refs stay reproducible. The tag is kept in front of the digest so it stays human-readable and trackable.", + "matchManagers": ["dockerfile", "github-actions"], + "pinDigests": true + }, + { + "description": "Batch the initial pin and all digest-only refreshes (same tag, rebuilt upstream) into a single weekly PR. Without this, frequently-rebuilt bases (alpine, debian, golang, distroless) would trickle in one rebuild-triggering PR each, loading the shared CI runner. Real tag bumps (major/minor/patch) are intentionally left out of this group so they get individual review.", + "matchManagers": ["dockerfile", "github-actions"], + "matchUpdateTypes": ["digest", "pinDigest"], + "groupName": "container image & action digests", + "schedule": ["before 6am on monday"] } ] } diff --git a/packages/system/bucket/images/s3manager/Dockerfile b/packages/system/bucket/images/s3manager/Dockerfile index e97adf07b..e9eb55f07 100644 --- a/packages/system/bucket/images/s3manager/Dockerfile +++ b/packages/system/bucket/images/s3manager/Dockerfile @@ -1,6 +1,6 @@ # Source: https://github.com/cloudlena/s3manager/blob/main/Dockerfile -FROM docker.io/library/golang:1 AS builder +FROM docker.io/library/golang:1.26 AS builder ARG TARGETOS ARG TARGETARCH @@ -12,7 +12,7 @@ RUN git apply /cozystack.patch RUN go mod tidy RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build -ldflags="-s -w" -a -installsuffix cgo -o bin/s3manager -FROM docker.io/library/alpine:latest +FROM docker.io/library/alpine:3.24 WORKDIR /usr/src/app RUN addgroup -S s3manager && adduser -S s3manager -G s3manager RUN apk add --no-cache \ From 50344f1ab9bc197b082676ef4321050a3f1d6d79 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 17:11:09 +0300 Subject: [PATCH 02/38] refactor(platform): add shared cozystack-version stamp helper Extract the labeled cozystack-version ConfigMap stamp into a sourced shell helper so migrations cannot drift back to a label-less apply that strips the platform.cozystack.io/no-delete label. render_cozystack_version_manifest emits the manifest on stdout (pure, unit-testable); stamp_cozystack_version renders and applies it. The namespace defaults to cozy-system and honors $NAMESPACE. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/cozystack-version-stamp.bats | 95 +++++++++++++++++++ .../migrations/lib/cozystack-version.sh | 43 +++++++++ 2 files changed, 138 insertions(+) create mode 100644 hack/cozystack-version-stamp.bats create mode 100644 packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh diff --git a/hack/cozystack-version-stamp.bats b/hack/cozystack-version-stamp.bats new file mode 100644 index 000000000..038642c85 --- /dev/null +++ b/hack/cozystack-version-stamp.bats @@ -0,0 +1,95 @@ +#!/usr/bin/env bats +# ----------------------------------------------------------------------------- +# Unit tests for +# packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh +# +# render_cozystack_version_manifest emits the cozystack-version +# ConfigMap manifest on stdout, always carrying the +# platform.cozystack.io/no-delete=true label that the +# cozystack-no-delete-guardrail ValidatingAdmissionPolicy keys on. Migrations +# apply it under the default kubectl field manager; a label-less apply by that +# same manager would strip the label, so the manifest is centralized here and +# every migration sources it instead of copy-pasting a heredoc. +# +# cozytest.sh's awk parser recognizes only @test blocks and a bare `}` on its +# own line; there is no bats `run` or `$status`. Assertions are expressed as +# direct shell tests that exit non-zero on failure. Each test runs in its own +# subshell, so NAMESPACE set/unset in one test does not leak into another. +# +# Run with: hack/cozytest.sh hack/cozystack-version-stamp.bats +# ----------------------------------------------------------------------------- + +@test "renders parseable YAML for a version" { + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + render_cozystack_version_manifest 45 | yq . >/dev/null +} + +@test "carries the no-delete label set to true" { + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + val=$(render_cozystack_version_manifest 45 \ + | yq -r '.metadata.labels."platform.cozystack.io/no-delete"') + [ "$val" = "true" ] +} + +@test "names the ConfigMap cozystack-version" { + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + name=$(render_cozystack_version_manifest 45 | yq -r '.metadata.name') + [ "$name" = "cozystack-version" ] +} + +@test "stamps the requested version as a quoted string" { + # The version MUST render as a quoted YAML string; an unquoted numeric + # scalar (version: 45) would change the ConfigMap data type the rest of + # the platform reads as a string. + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + out=$(render_cozystack_version_manifest 45) + printf '%s\n' "$out" | grep -qx ' version: "45"' + v=$(printf '%s\n' "$out" | yq -r '.data.version') + [ "$v" = "45" ] +} + +@test "defaults namespace to cozy-system when NAMESPACE is unset" { + unset NAMESPACE + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + ns=$(render_cozystack_version_manifest 45 | yq -r '.metadata.namespace') + [ "$ns" = "cozy-system" ] +} + +@test "honors a NAMESPACE override" { + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + out=$(export NAMESPACE=cozy-other; render_cozystack_version_manifest 45) + ns=$(printf '%s\n' "$out" | yq -r '.metadata.namespace') + [ "$ns" = "cozy-other" ] +} + +@test "requires a version argument" { + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + if ( render_cozystack_version_manifest ) 2>/dev/null; then + echo "expected non-zero exit when version arg is missing" >&2 + exit 1 + fi +} + +@test "render output matches the canonical labeled manifest" { + # Golden test: pins the exact bytes migrations 42/43/44 apply today, so the + # refactor that routes them through this helper is provably manifest-stable. + unset NAMESPACE + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + expected=$(cat <<'EOF' +apiVersion: v1 +kind: ConfigMap +metadata: + name: cozystack-version + namespace: cozy-system + labels: + platform.cozystack.io/no-delete: "true" +data: + version: "45" +EOF +) + actual=$(render_cozystack_version_manifest 45) + if [ "$actual" != "$expected" ]; then + printf 'expected:\n%s\n---\nactual:\n%s\n' "$expected" "$actual" >&2 + exit 1 + fi +} diff --git a/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh b/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh new file mode 100644 index 000000000..81b86d622 --- /dev/null +++ b/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh @@ -0,0 +1,43 @@ +# shellcheck shell=sh +# Shared helper for stamping the cozystack-version ConfigMap during migrations. +# +# The ConfigMap MUST carry the platform.cozystack.io/no-delete=true label so the +# cozystack-no-delete-guardrail ValidatingAdmissionPolicy guards it against +# deletion. Migrations apply it under the default kubectl field manager; a +# label-less apply by that same manager would strip the label (the failure mode +# migration 42 first fixed). templates/cozystack-version.yaml renders the +# labeled ConfigMap only on first install (lookup-if-not), so every upgrade-time +# stamp must re-emit the label by hand. +# +# Centralizing the manifest here means migration 45+ cannot drift back to a +# label-less apply: each migration sources this file and calls the helper +# instead of copy-pasting a heredoc. +# +# Sourced, not executed. Each migrations/ script does: +# . "$(dirname "$0")/lib/cozystack-version.sh" +# and run-migrations.sh sources /migrations/lib/cozystack-version.sh. + +# render_cozystack_version_manifest +# Emit the labeled cozystack-version ConfigMap manifest on stdout. Pure (no +# kubectl), so it is unit-testable in isolation. The namespace defaults to +# cozy-system and is overridable via $NAMESPACE. +render_cozystack_version_manifest() { + : "${1:?render_cozystack_version_manifest: version argument required}" + cat < +# Render and apply the labeled manifest under the default kubectl field manager. +stamp_cozystack_version() { + render_cozystack_version_manifest "$1" | kubectl apply --filename - +} From b199b5e438cb08be872b6a8a4b9a1fe0b9728a2f Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 17:14:12 +0300 Subject: [PATCH 03/38] refactor(platform): stamp cozystack-version via shared helper in migrations Migrations 42, 43 and 44 each carried a copy-pasted heredoc that applied the labeled cozystack-version ConfigMap. Source the shared helper and call it instead, so the labeled manifest has a single definition and future migrations cannot reintroduce a label-less apply. Behavior-preserving: the rendered manifest is byte-identical to the previous heredocs. Migration 42 keeps its MIGRATION_DRY_RUN path, now building its manifest from the helper's pure renderer. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../platform/images/migrations/migrations/42 | 25 ++++++------------- .../platform/images/migrations/migrations/43 | 24 +++++------------- .../platform/images/migrations/migrations/44 | 24 +++++------------- 3 files changed, 20 insertions(+), 53 deletions(-) diff --git a/packages/core/platform/images/migrations/migrations/42 b/packages/core/platform/images/migrations/migrations/42 index adc4f0fea..5844f4acc 100755 --- a/packages/core/platform/images/migrations/migrations/42 +++ b/packages/core/platform/images/migrations/migrations/42 @@ -14,29 +14,20 @@ # the next chart-driven apply sees no diff. # # 3-way-merge note: this apply records the no-delete label in the kubectl -# last-applied-configuration annotation under the default field manager. -# Any future migration that re-stamps cozystack-version via the same field -# manager MUST include the label inline (matching the chart template at -# templates/cozystack-version.yaml) — a label-less apply by the same field -# manager would strip it. +# last-applied-configuration annotation under the default field manager. A +# label-less apply by the same field manager would strip it, so the manifest +# is rendered by the shared helper (lib/cozystack-version.sh) that every +# migration sources rather than a hand-copied heredoc. set -euo pipefail +# shellcheck source=lib/cozystack-version.sh +. "$(dirname "$0")/lib/cozystack-version.sh" + NAMESPACE="${NAMESPACE:-cozy-system}" DRY_RUN="${MIGRATION_DRY_RUN:-0}" -MANIFEST=$(cat </dev/null 2>&1; then # No early-close (head) in the pipe: under pipefail a large name list would # SIGPIPE kubectl and abort the migration. @@ -23,21 +26,6 @@ if kubectl get deployment flux-tenants -n cozy-fluxcd >/dev/null 2>&1; then fi fi -# Stamp version. -# Mirror migration 43's labeled manifest. A label-less apply by the same field -# manager would strip the platform.cozystack.io/no-delete label, dropping -# cozystack-version out of the cozystack-no-delete-guardrail -# ValidatingAdmissionPolicy. templates/cozystack-version.yaml only re-renders -# the labeled ConfigMap on first install, so the label must be carried inline -# here. -kubectl apply --filename - < Date: Mon, 22 Jun 2026 17:14:26 +0300 Subject: [PATCH 04/38] fix(platform): stamp the no-delete label on bootstrap cozystack-version The bootstrap path that creates cozystack-version when the ConfigMap is absent applied it without the platform.cozystack.io/no-delete label, leaving it unguarded by the cozystack-no-delete-guardrail ValidatingAdmissionPolicy. Route it through the shared helper so the bootstrap ConfigMap carries the label like every migration-driven stamp. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../core/platform/images/migrations/run-migrations.sh | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/core/platform/images/migrations/run-migrations.sh b/packages/core/platform/images/migrations/run-migrations.sh index 90a72d907..e2143f9b8 100755 --- a/packages/core/platform/images/migrations/run-migrations.sh +++ b/packages/core/platform/images/migrations/run-migrations.sh @@ -1,6 +1,9 @@ #!/bin/sh set -euo pipefail +# shellcheck source=migrations/lib/cozystack-version.sh +. /migrations/lib/cozystack-version.sh + NAMESPACE="${NAMESPACE:-cozy-system}" CURRENT_VERSION="${CURRENT_VERSION:-0}" TARGET_VERSION="${TARGET_VERSION:-0}" @@ -10,9 +13,9 @@ echo "Starting migrations from version $CURRENT_VERSION to $TARGET_VERSION" # Check if ConfigMap exists if ! kubectl get configmap --namespace "$NAMESPACE" cozystack-version >/dev/null 2>&1; then echo "ConfigMap cozystack-version does not exist, creating it with version $TARGET_VERSION" - kubectl create configmap --namespace "$NAMESPACE" cozystack-version \ - --from-literal=version="$TARGET_VERSION" \ - --dry-run=client --output yaml | kubectl apply --filename - + # Stamp via the shared helper so the bootstrap ConfigMap carries the + # platform.cozystack.io/no-delete label like every migration-driven stamp. + stamp_cozystack_version "$TARGET_VERSION" echo "ConfigMap created with version $TARGET_VERSION" exit 0 fi From 985b4b0954853a00169b6edbe5af8337ddb8f8c3 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 20:20:23 +0300 Subject: [PATCH 05/38] fix(platform): guard stamp_cozystack_version against a missing version Mirror render_cozystack_version_manifest's argument guard in stamp_cozystack_version so a missing version aborts before the kubectl pipe. Without it, a render failure in a shell without pipefail would leave the pipeline exit status to kubectl, which would apply empty input. Add a unittest covering the guard. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/cozystack-version-stamp.bats | 10 ++++++++++ .../migrations/migrations/lib/cozystack-version.sh | 3 +++ 2 files changed, 13 insertions(+) diff --git a/hack/cozystack-version-stamp.bats b/hack/cozystack-version-stamp.bats index 038642c85..f8e84a203 100644 --- a/hack/cozystack-version-stamp.bats +++ b/hack/cozystack-version-stamp.bats @@ -70,6 +70,16 @@ fi } +@test "stamp_cozystack_version requires a version argument" { + # The guard fires before the kubectl pipe, so this needs no cluster: a + # missing version must abort rather than apply empty input to kubectl. + . packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh + if ( stamp_cozystack_version ) 2>/dev/null; then + echo "expected non-zero exit when version arg is missing" >&2 + exit 1 + fi +} + @test "render output matches the canonical labeled manifest" { # Golden test: pins the exact bytes migrations 42/43/44 apply today, so the # refactor that routes them through this helper is provably manifest-stable. diff --git a/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh b/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh index 81b86d622..e05f1c170 100644 --- a/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh +++ b/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh @@ -39,5 +39,8 @@ EOF # stamp_cozystack_version # Render and apply the labeled manifest under the default kubectl field manager. stamp_cozystack_version() { + # Guard the version here too, not only in render: without pipefail a render + # failure would not abort the pipeline and kubectl would apply empty input. + : "${1:?stamp_cozystack_version: version argument required}" render_cozystack_version_manifest "$1" | kubectl apply --filename - } From de44ecf15aa078fe2120effd1b6929d382f42b90 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 20:28:16 +0300 Subject: [PATCH 06/38] test(platform): enforce go-forward version stamps route through the helper The helper header and run-migrations.sh claimed every migration-driven stamp routes through the helper, but migrations 1-41 still stamp the cozystack-version ConfigMap label-less and are backfilled by migration 42. Scope the comments to go-forward stamps (migration 42 onward and the bootstrap path), and add a unittest asserting no migration numbered >= 42 nor run-migrations.sh stamps cozystack-version without the helper, so the no-drift invariant is enforced rather than aspirational. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/cozystack-version-stamp.bats | 38 +++++++++++++++++++ .../migrations/lib/cozystack-version.sh | 11 ++++-- .../images/migrations/run-migrations.sh | 4 +- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/hack/cozystack-version-stamp.bats b/hack/cozystack-version-stamp.bats index f8e84a203..85db10d8f 100644 --- a/hack/cozystack-version-stamp.bats +++ b/hack/cozystack-version-stamp.bats @@ -103,3 +103,41 @@ EOF exit 1 fi } + +@test "no go-forward stamp bypasses the helper (migrations >= 42 and run-migrations.sh)" { + # The helper exists so go-forward stamps cannot drift back to a label-less + # apply that drops the no-delete label. Migrations 1-41 are frozen and + # backfilled by 42, so they are exempt; migration 42 onward and the + # bootstrap path must each source the helper, call its API, and never stamp + # cozystack-version directly with kubectl. Blocking only the historical + # 'kubectl create configmap' shape is too weak — an inline 'kubectl apply' + # of a hand-rolled label-less manifest would slip past it. + mig_dir=packages/core/platform/images/migrations/migrations + files="" + for f in "$mig_dir"/*; do + n=$(basename "$f") + case "$n" in *[!0-9]*) continue ;; esac # skip lib/ and other non-numeric + [ "$n" -ge 42 ] || continue + files="$files $f" + done + files="$files packages/core/platform/images/migrations/run-migrations.sh" + + for f in $files; do + if ! grep -Eq 'cozystack-version\.sh' "$f"; then + echo "$f does not source the cozystack-version helper" >&2 + exit 1 + fi + if ! grep -Eq '(render_cozystack_version_manifest|stamp_cozystack_version)' "$f"; then + echo "$f does not call the cozystack-version helper API" >&2 + exit 1 + fi + # Strip full-comment lines first: a comment that explains why a stamp + # routes through the helper must not read as a direct stamp itself. A + # real bypass command never starts with '#', so the guard keeps its + # strength against actual direct stamping. + if grep -v '^[[:space:]]*#' "$f" | grep -Eq 'kubectl[[:space:]].*(create[[:space:]]+configmap|apply).*cozystack-version'; then + echo "$f stamps cozystack-version directly instead of via the helper" >&2 + exit 1 + fi + done +} diff --git a/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh b/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh index e05f1c170..7b665f5f0 100644 --- a/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh +++ b/packages/core/platform/images/migrations/migrations/lib/cozystack-version.sh @@ -10,10 +10,15 @@ # stamp must re-emit the label by hand. # # Centralizing the manifest here means migration 45+ cannot drift back to a -# label-less apply: each migration sources this file and calls the helper -# instead of copy-pasting a heredoc. +# label-less apply: every go-forward stamp (migration 42 onward and the +# bootstrap path in run-migrations.sh) sources this file and calls the helper +# instead of copy-pasting a heredoc. Migrations 1-41 predate the no-delete +# label and stamp label-less on purpose; migration 42 backfills the label on +# any upgrade path through them, so they are left frozen. The +# cozystack-version-stamp.bats suite pins this — no migration numbered >= 42 +# (nor run-migrations.sh) may stamp cozystack-version without the helper. # -# Sourced, not executed. Each migrations/ script does: +# Sourced, not executed. Each migrations/ script (42+) does: # . "$(dirname "$0")/lib/cozystack-version.sh" # and run-migrations.sh sources /migrations/lib/cozystack-version.sh. diff --git a/packages/core/platform/images/migrations/run-migrations.sh b/packages/core/platform/images/migrations/run-migrations.sh index e2143f9b8..aeea5ec44 100755 --- a/packages/core/platform/images/migrations/run-migrations.sh +++ b/packages/core/platform/images/migrations/run-migrations.sh @@ -14,7 +14,9 @@ echo "Starting migrations from version $CURRENT_VERSION to $TARGET_VERSION" if ! kubectl get configmap --namespace "$NAMESPACE" cozystack-version >/dev/null 2>&1; then echo "ConfigMap cozystack-version does not exist, creating it with version $TARGET_VERSION" # Stamp via the shared helper so the bootstrap ConfigMap carries the - # platform.cozystack.io/no-delete label like every migration-driven stamp. + # platform.cozystack.io/no-delete label, matching every go-forward stamp + # (migration 42 onward; migrations 1-41 stamp label-less and are backfilled + # by migration 42, so they are intentionally left as-is). stamp_cozystack_version "$TARGET_VERSION" echo "ConfigMap created with version $TARGET_VERSION" exit 0 From 60b6cf3fdccd093004d63fe039a07de07a58eebf Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 10 Jun 2026 13:53:42 +0300 Subject: [PATCH 07/38] refactor(api): return typed apierrors from TenantNamespace registry helpers The access-check helpers returned plain fmt.Errorf values, which the apiserver coerces to 500 InternalError regardless of the actual failure. A request without user identity now surfaces as 401 Unauthorized, and a RoleBinding list failure as 500 InternalError with a typed Status body. Get also repackages a NotFound from the backing Namespace lookup so the Status body reports the tenantnamespaces resource instead of namespaces. The Watch goroutine already logs access-check failures and skips the event, so no untyped error escapes to the client from that path. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- pkg/registry/core/tenantnamespace/rest.go | 13 +- .../core/tenantnamespace/rest_test.go | 273 +++++++++++++++++- 2 files changed, 278 insertions(+), 8 deletions(-) diff --git a/pkg/registry/core/tenantnamespace/rest.go b/pkg/registry/core/tenantnamespace/rest.go index ee6a938ee..870770157 100644 --- a/pkg/registry/core/tenantnamespace/rest.go +++ b/pkg/registry/core/tenantnamespace/rest.go @@ -139,6 +139,11 @@ func (r *REST) Get( ns := &corev1.Namespace{} err = r.c.Get(ctx, types.NamespacedName{Namespace: "", Name: name}, ns, &client.GetOptions{Raw: opts}) if err != nil { + // Repackage NotFound so the Status reports this resource, not the + // backing Namespace one. + if apierrors.IsNotFound(err) { + return nil, apierrors.NewNotFound(r.gvr.GroupResource(), name) + } return nil, err } @@ -437,7 +442,7 @@ func (r *REST) filterAccessible( ) ([]string, error) { u, ok := request.UserFrom(ctx) if !ok { - return []string{}, fmt.Errorf("user missing in context") + return nil, apierrors.NewUnauthorized("user missing in context") } groups := make(map[string]struct{}) for _, group := range u.GetGroups() { @@ -456,7 +461,7 @@ func (r *REST) filterAccessible( rbs := &rbacv1.RoleBindingList{} err := r.c.List(ctx, rbs) if err != nil { - return []string{}, fmt.Errorf("failed to list rolebindings: %w", err) + return nil, apierrors.NewInternalError(fmt.Errorf("failed to list rolebindings: %w", err)) } allowedNameSet := make(map[string]struct{}) for i := range rbs.Items { @@ -491,7 +496,7 @@ func (r *REST) hasAccessToNamespace( ) (bool, error) { u, ok := request.UserFrom(ctx) if !ok { - return false, fmt.Errorf("user missing in context") + return false, apierrors.NewUnauthorized("user missing in context") } groups := make(map[string]struct{}) for _, group := range u.GetGroups() { @@ -520,7 +525,7 @@ func (r *REST) hasAccessToNamespaceForUser( rbs := &rbacv1.RoleBindingList{} err := r.c.List(ctx, rbs, client.InNamespace(namespace)) if err != nil { - return false, fmt.Errorf("failed to list rolebindings in %s: %w", namespace, err) + return false, apierrors.NewInternalError(fmt.Errorf("failed to list rolebindings in %s: %w", namespace, err)) } // Check if user is in any RoleBinding subjects diff --git a/pkg/registry/core/tenantnamespace/rest_test.go b/pkg/registry/core/tenantnamespace/rest_test.go index b03c96888..199edabf2 100644 --- a/pkg/registry/core/tenantnamespace/rest_test.go +++ b/pkg/registry/core/tenantnamespace/rest_test.go @@ -457,8 +457,9 @@ func TestList_NamespaceListError_Propagates(t *testing.T) { } // TestList_RoleBindingListError_Propagates asserts a failure to list -// RoleBindings during access filtering fails the List instead of returning an -// unfiltered or silently truncated result. +// RoleBindings during access filtering fails the List with a typed +// InternalError instead of returning an unfiltered or silently truncated +// result. func TestList_RoleBindingListError_Propagates(t *testing.T) { wantErr := errors.New("rolebinding list failed") fc := fake.NewClientBuilder(). @@ -482,8 +483,12 @@ func TestList_RoleBindingListError_Propagates(t *testing.T) { u := &user.DefaultInfo{Name: "test-user", Groups: []string{"system:authenticated"}} ctx := request.WithUser(context.Background(), u) - if _, err := r.List(ctx, nil); !errors.Is(err, wantErr) { - t.Fatalf("expected %v, got %v", wantErr, err) + _, err := r.List(ctx, nil) + if err == nil { + t.Fatal("expected error, got nil") + } + if !apierrors.IsInternalError(err) { + t.Fatalf("expected InternalError, got %v", err) } } @@ -798,3 +803,263 @@ func TestGet_NonTenantNamespace(t *testing.T) { t.Errorf("expected NotFound error, got %v", err) } } + +// Typed-error tests: helpers must return apierrors so the apiserver maps +// failures to accurate status codes instead of coercing everything to 500. + +// newRoleBindingListErrorClient returns a fake client whose List fails for +// RoleBindingList only, simulating an RBAC listing outage. +func newRoleBindingListErrorClient(scheme *runtime.Scheme, listErr error, objs ...client.Object) client.Client { + return fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(objs...). + WithInterceptorFuncs(interceptor.Funcs{ + List: func(ctx context.Context, c client.WithWatch, list client.ObjectList, opts ...client.ListOption) error { + if _, ok := list.(*rbacv1.RoleBindingList); ok { + return listErr + } + return c.List(ctx, list, opts...) + }, + }). + Build() +} + +func TestHasAccessToNamespace_NoUserInContext(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + c := fake.NewClientBuilder().WithScheme(scheme).Build() + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + _, err := r.hasAccessToNamespace(context.Background(), "tenant-test") + if err == nil { + t.Fatal("expected error, got nil") + } + if !apierrors.IsUnauthorized(err) { + t.Errorf("expected Unauthorized error, got %v", err) + } +} + +func TestHasAccessToNamespace_RoleBindingListError(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + c := newRoleBindingListErrorClient(scheme, errors.New("etcd is on fire")) + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + u := &user.DefaultInfo{ + Name: "test-user", + Groups: []string{"system:authenticated"}, + } + ctx := request.WithUser(context.Background(), u) + + _, err := r.hasAccessToNamespace(ctx, "tenant-test") + if err == nil { + t.Fatal("expected error, got nil") + } + if !apierrors.IsInternalError(err) { + t.Errorf("expected InternalError, got %v", err) + } + var statusErr *apierrors.StatusError + if !errors.As(err, &statusErr) { + t.Fatalf("expected *apierrors.StatusError, got %T", err) + } + if statusErr.Status().Code != 500 { + t.Errorf("expected status code 500, got %d", statusErr.Status().Code) + } +} + +func TestFilterAccessible_NoUserInContext(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + c := fake.NewClientBuilder().WithScheme(scheme).Build() + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + _, err := r.filterAccessible(context.Background(), []string{"tenant-test"}) + if err == nil { + t.Fatal("expected error, got nil") + } + if !apierrors.IsUnauthorized(err) { + t.Errorf("expected Unauthorized error, got %v", err) + } +} + +func TestFilterAccessible_RoleBindingListError(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + c := newRoleBindingListErrorClient(scheme, errors.New("etcd is on fire")) + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + u := &user.DefaultInfo{ + Name: "test-user", + Groups: []string{"system:authenticated"}, + } + ctx := request.WithUser(context.Background(), u) + + _, err := r.filterAccessible(ctx, []string{"tenant-test"}) + if err == nil { + t.Fatal("expected error, got nil") + } + if !apierrors.IsInternalError(err) { + t.Errorf("expected InternalError, got %v", err) + } +} + +func TestGet_NotFoundReportsTenantNamespaceResource(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + c := fake.NewClientBuilder().WithScheme(scheme).Build() + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + u := &user.DefaultInfo{ + Name: "admin-user", + Groups: []string{"system:masters"}, + } + ctx := request.WithUser(context.Background(), u) + + _, err := r.Get(ctx, "tenant-missing", &metav1.GetOptions{}) + if err == nil { + t.Fatal("expected error, got nil") + } + if !apierrors.IsNotFound(err) { + t.Fatalf("expected NotFound error, got %v", err) + } + var statusErr *apierrors.StatusError + if !errors.As(err, &statusErr) { + t.Fatalf("expected *apierrors.StatusError, got %T", err) + } + details := statusErr.Status().Details + if details == nil { + t.Fatal("expected status details, got nil") + } + if details.Group != "core.cozystack.io" || details.Kind != "tenantnamespaces" { + t.Errorf("expected NotFound for core.cozystack.io/tenantnamespaces, got %s/%s", details.Group, details.Kind) + } +} + +func TestList_RoleBindingListError(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, + } + + c := newRoleBindingListErrorClient(scheme, errors.New("etcd is on fire"), ns) + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + u := &user.DefaultInfo{ + Name: "test-user", + Groups: []string{"system:authenticated"}, + } + ctx := request.WithUser(context.Background(), u) + + obj, err := r.List(ctx, nil) + if err == nil { + t.Fatal("expected error, got nil") + } + if obj != nil { + t.Errorf("expected nil object, got %v", obj) + } + if !apierrors.IsInternalError(err) { + t.Errorf("expected InternalError, got %v", err) + } +} + +func TestGet_NoUserInContext(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, + } + + c := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(ns). + Build() + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + obj, err := r.Get(context.Background(), "tenant-test", &metav1.GetOptions{}) + if err == nil { + t.Fatal("expected error, got nil") + } + if obj != nil { + t.Errorf("expected nil object, got %v", obj) + } + if !apierrors.IsUnauthorized(err) { + t.Errorf("expected Unauthorized error, got %v", err) + } +} + +func TestGet_RoleBindingListError(t *testing.T) { + scheme := runtime.NewScheme() + _ = corev1.AddToScheme(scheme) + _ = rbacv1.AddToScheme(scheme) + + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "tenant-test"}, + } + + c := newRoleBindingListErrorClient(scheme, errors.New("etcd is on fire"), ns) + + r := &REST{ + c: c, + gvr: schema.GroupVersionResource{Group: "core.cozystack.io", Version: "v1alpha1", Resource: "tenantnamespaces"}, + } + + u := &user.DefaultInfo{ + Name: "test-user", + Groups: []string{"system:authenticated"}, + } + ctx := request.WithUser(context.Background(), u) + + obj, err := r.Get(ctx, "tenant-test", &metav1.GetOptions{}) + if err == nil { + t.Fatal("expected error, got nil") + } + if obj != nil { + t.Errorf("expected nil object, got %v", obj) + } + if !apierrors.IsInternalError(err) { + t.Errorf("expected InternalError, got %v", err) + } +} From ed30425b17b1141b46f56718c785115d6618d79e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 16 Jun 2026 08:04:14 +0300 Subject: [PATCH 08/38] fix(tenant): grant watch verb to cleanup Role so pre-delete hook can wait The pre-delete cleanup hook deletes the tenant's HelmReleases with `kubectl delete --wait=true`, which opens a watch on helmreleases to block until finalization. The hook Role granted only get/list/delete, so the watch was rejected with 403, the delete call hung, the Job never finished, and tenant deletion stalled on the hook. Add the watch verb, matching the kubernetes app chart's cleanup Role which already includes it for the same --wait deletes. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../apps/tenant/templates/cleanup-job.yaml | 2 +- .../tenant/tests/cleanup_role_watch_test.yaml | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 packages/apps/tenant/tests/cleanup_role_watch_test.yaml diff --git a/packages/apps/tenant/templates/cleanup-job.yaml b/packages/apps/tenant/templates/cleanup-job.yaml index 0f13daa48..be5729b67 100644 --- a/packages/apps/tenant/templates/cleanup-job.yaml +++ b/packages/apps/tenant/templates/cleanup-job.yaml @@ -21,7 +21,7 @@ metadata: rules: - apiGroups: ["helm.toolkit.fluxcd.io"] resources: ["helmreleases"] - verbs: ["get", "list", "delete"] + verbs: ["get", "list", "watch", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/packages/apps/tenant/tests/cleanup_role_watch_test.yaml b/packages/apps/tenant/tests/cleanup_role_watch_test.yaml new file mode 100644 index 000000000..2c5d83094 --- /dev/null +++ b/packages/apps/tenant/tests/cleanup_role_watch_test.yaml @@ -0,0 +1,29 @@ +suite: tenant cleanup Role can watch HelmReleases + +# The pre-delete cleanup hook deletes the tenant's HelmReleases with +# `kubectl delete --wait=true`, which sets up a watch on helmreleases to block +# until finalization completes. Without the watch verb that watch is rejected +# with HTTP 403, the delete call hangs, the Job never finishes, and tenant +# deletion stalls on the hook. Pin the verb so it cannot regress. + +# Restrict rendering to the cleanup template so the chart's namespace.yaml +# `lookup` (which errors under helm-unittest) is not evaluated. +templates: + - templates/cleanup-job.yaml + +release: + name: tenant-test + namespace: tenant-test + +tests: + - it: cleanup Role grants watch on helmreleases + asserts: + - template: templates/cleanup-job.yaml + documentIndex: 1 + isKind: + of: Role + - template: templates/cleanup-job.yaml + documentIndex: 1 + contains: + path: rules[0].verbs + content: watch From 5e7936c811a5bca331b84e3220337dcc91298adb Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 16 Jun 2026 08:24:54 +0300 Subject: [PATCH 09/38] fix(tenant): replace bitnami/kubectl with maintained clastix image in cleanup hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pre-delete cleanup Job ran kubectl from `bitnami/kubectl:latest`. The bitnami Docker Hub catalog has moved to a legacy/sunset state, so that pull is at risk of breaking — which would stop the cleanup hook from starting at all — and the `:latest` tag leaves the kubectl version unpinned and non-reproducible. Switch to `docker.io/clastix/kubectl:v1.32`, the version-pinned image already used by every other first-party cleanup/delete hook in the repo (kubernetes, keycloak-configure, dashboard, mariadb). That image is Alpine-based and ships only `/bin/sh`, so the hook script (pure POSIX) now invokes `/bin/sh` instead of `/bin/bash` — matching those sibling hooks. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../apps/tenant/templates/cleanup-job.yaml | 4 +-- .../tenant/tests/cleanup_role_watch_test.yaml | 32 ++++++++++++++++++- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/apps/tenant/templates/cleanup-job.yaml b/packages/apps/tenant/templates/cleanup-job.yaml index be5729b67..59cb0544f 100644 --- a/packages/apps/tenant/templates/cleanup-job.yaml +++ b/packages/apps/tenant/templates/cleanup-job.yaml @@ -62,9 +62,9 @@ spec: restartPolicy: OnFailure containers: - name: cleanup - image: bitnami/kubectl:latest + image: docker.io/clastix/kubectl:v1.32 command: - - /bin/bash + - /bin/sh - -c - | set -e diff --git a/packages/apps/tenant/tests/cleanup_role_watch_test.yaml b/packages/apps/tenant/tests/cleanup_role_watch_test.yaml index 2c5d83094..53fcb5920 100644 --- a/packages/apps/tenant/tests/cleanup_role_watch_test.yaml +++ b/packages/apps/tenant/tests/cleanup_role_watch_test.yaml @@ -1,10 +1,16 @@ -suite: tenant cleanup Role can watch HelmReleases +suite: tenant cleanup pre-delete hook # The pre-delete cleanup hook deletes the tenant's HelmReleases with # `kubectl delete --wait=true`, which sets up a watch on helmreleases to block # until finalization completes. Without the watch verb that watch is rejected # with HTTP 403, the delete call hangs, the Job never finishes, and tenant # deletion stalls on the hook. Pin the verb so it cannot regress. +# +# The Job runs kubectl from a maintained, version-pinned image. Pin the image +# so it cannot drift back to an unpinned / sunset base (e.g. bitnami:latest), +# and pin the command interpreter to a shell that image actually ships +# (clastix/kubectl is Alpine — /bin/sh only, no /bin/bash) so the container +# can exec at all. # Restrict rendering to the cleanup template so the chart's namespace.yaml # `lookup` (which errors under helm-unittest) is not evaluated. @@ -27,3 +33,27 @@ tests: contains: path: rules[0].verbs content: watch + + - it: cleanup Job uses the maintained clastix kubectl image + asserts: + - template: templates/cleanup-job.yaml + documentIndex: 3 + isKind: + of: Job + - template: templates/cleanup-job.yaml + documentIndex: 3 + equal: + path: spec.template.spec.containers[0].image + value: docker.io/clastix/kubectl:v1.32 + + - it: cleanup Job invokes a shell the image actually ships + asserts: + - template: templates/cleanup-job.yaml + documentIndex: 3 + isKind: + of: Job + - template: templates/cleanup-job.yaml + documentIndex: 3 + equal: + path: spec.template.spec.containers[0].command[0] + value: /bin/sh From e1f6a9c2aa3591fa54058fda6afd08b53650a95a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 20:39:05 +0300 Subject: [PATCH 10/38] fix(tenant): run the cleanup hook as non-root under a restrictive securityContext The pre-delete cleanup Job ran kubectl as root with no securityContext. Add a pod-level runAsNonRoot + runAsUser 65534 + seccompProfile RuntimeDefault and a container-level allowPrivilegeEscalation: false plus capabilities drop ALL, mirroring the dashboard adopt hook that runs the same clastix/kubectl image non-root. Pin the securityContext in helm-unittest. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../apps/tenant/templates/cleanup-job.yaml | 9 ++++++ .../tenant/tests/cleanup_role_watch_test.yaml | 32 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/packages/apps/tenant/templates/cleanup-job.yaml b/packages/apps/tenant/templates/cleanup-job.yaml index 59cb0544f..a4996ec5d 100644 --- a/packages/apps/tenant/templates/cleanup-job.yaml +++ b/packages/apps/tenant/templates/cleanup-job.yaml @@ -60,9 +60,18 @@ spec: spec: serviceAccountName: {{ include "tenant.name" . }}-cleanup restartPolicy: OnFailure + securityContext: + runAsNonRoot: true + runAsUser: 65534 + seccompProfile: + type: RuntimeDefault containers: - name: cleanup image: docker.io/clastix/kubectl:v1.32 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] command: - /bin/sh - -c diff --git a/packages/apps/tenant/tests/cleanup_role_watch_test.yaml b/packages/apps/tenant/tests/cleanup_role_watch_test.yaml index 53fcb5920..710cc1f9d 100644 --- a/packages/apps/tenant/tests/cleanup_role_watch_test.yaml +++ b/packages/apps/tenant/tests/cleanup_role_watch_test.yaml @@ -57,3 +57,35 @@ tests: equal: path: spec.template.spec.containers[0].command[0] value: /bin/sh + + - it: cleanup Job runs non-root under a restrictive securityContext + asserts: + - template: templates/cleanup-job.yaml + documentIndex: 3 + isKind: + of: Job + - template: templates/cleanup-job.yaml + documentIndex: 3 + equal: + path: spec.template.spec.securityContext.runAsNonRoot + value: true + - template: templates/cleanup-job.yaml + documentIndex: 3 + equal: + path: spec.template.spec.securityContext.runAsUser + value: 65534 + - template: templates/cleanup-job.yaml + documentIndex: 3 + equal: + path: spec.template.spec.securityContext.seccompProfile.type + value: RuntimeDefault + - template: templates/cleanup-job.yaml + documentIndex: 3 + equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - template: templates/cleanup-job.yaml + documentIndex: 3 + contains: + path: spec.template.spec.containers[0].securityContext.capabilities.drop + content: ALL From 0f33e3a60d4494d262bf24174cbd49c3cc931b46 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 17 Jun 2026 13:20:59 +0300 Subject: [PATCH 11/38] chore(deps): bump first-party image Go modules to 1.26.4 The token-proxy, kube-ovn-webhook and kubevirt-csi-driver image modules still pinned go 1.26.2, so their binaries shipped standard-library versions that the 1.26.4 toolchain fixes. Raise the go directive and refresh module dependencies to current releases. token-proxy and kube-ovn-webhook take a full dependency refresh; kube-ovn-webhook also moves k8s.io/* from 0.26.0 to 0.36.2 (only long-stable admission/core API is used, so no code change is needed). kubevirt-csi-driver keeps its existing replace-based k8s.io/* alignment at 0.36.0 and only raises the go directive; a blanket go get -u is avoided there because it pulls transitive bumps that break compilation of the pinned vendored upstream csi-driver source. Also ignore the locally-built binaries under the two image directories so they cannot be committed by accident. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/kubernetes/.gitignore | 1 + .../images/kubevirt-csi-driver/go.mod | 2 +- .../dashboard/images/token-proxy/go.mod | 12 +- .../dashboard/images/token-proxy/go.sum | 20 +- packages/system/kubeovn-webhook/.gitignore | 1 + .../images/kubeovn-webhook/go.mod | 76 +++-- .../images/kubeovn-webhook/go.sum | 284 +++++++----------- 7 files changed, 168 insertions(+), 228 deletions(-) create mode 100644 packages/apps/kubernetes/.gitignore create mode 100644 packages/system/kubeovn-webhook/.gitignore diff --git a/packages/apps/kubernetes/.gitignore b/packages/apps/kubernetes/.gitignore new file mode 100644 index 000000000..59ec0d536 --- /dev/null +++ b/packages/apps/kubernetes/.gitignore @@ -0,0 +1 @@ +images/kubevirt-csi-driver/kubevirt-csi-driver diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod index cef60a7f6..dcf5441f8 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod @@ -1,6 +1,6 @@ module cozystack.io/kubevirt-csi-driver -go 1.26.2 +go 1.26.4 require ( github.com/container-storage-interface/spec v1.10.0 diff --git a/packages/system/dashboard/images/token-proxy/go.mod b/packages/system/dashboard/images/token-proxy/go.mod index 2b045361a..b33ce164b 100644 --- a/packages/system/dashboard/images/token-proxy/go.mod +++ b/packages/system/dashboard/images/token-proxy/go.mod @@ -1,23 +1,23 @@ module token-proxy -go 1.26.2 +go 1.26.4 require ( github.com/gorilla/securecookie v1.1.2 - github.com/lestrrat-go/httprc/v3 v3.0.5 - github.com/lestrrat-go/jwx/v3 v3.1.0 + github.com/lestrrat-go/httprc/v3 v3.0.6 + github.com/lestrrat-go/jwx/v3 v3.1.1 ) require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect - github.com/lestrrat-go/dsig v1.2.1 // indirect + github.com/lestrrat-go/dsig v1.3.0 // indirect github.com/lestrrat-go/dsig-secp256k1 v1.0.0 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/segmentio/asm v1.2.1 // indirect github.com/valyala/fastjson v1.6.10 // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/sys v0.43.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/sys v0.46.0 // indirect ) diff --git a/packages/system/dashboard/images/token-proxy/go.sum b/packages/system/dashboard/images/token-proxy/go.sum index 4c75483c6..ad9542565 100644 --- a/packages/system/dashboard/images/token-proxy/go.sum +++ b/packages/system/dashboard/images/token-proxy/go.sum @@ -11,16 +11,16 @@ github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kX github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw= -github.com/lestrrat-go/dsig v1.2.1 h1:MwxzZhE4+4fguHi+uDALKVlC3Cn+O1QU1Q/F8D7hVIc= -github.com/lestrrat-go/dsig v1.2.1/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= +github.com/lestrrat-go/dsig v1.3.0 h1:phjMOCXvYzhuIgn7Voe2rex8z166vGfxRxmqM25P9/Q= +github.com/lestrrat-go/dsig v1.3.0/go.mod h1:RD2eOaidyPvpc7IJQoO3Qq52RWdy8ZcJs8lrOnoa1Kc= github.com/lestrrat-go/dsig-secp256k1 v1.0.0 h1:JpDe4Aybfl0soBvoVwjqDbp+9S1Y2OM7gcrVVMFPOzY= github.com/lestrrat-go/dsig-secp256k1 v1.0.0/go.mod h1:CxUgAhssb8FToqbL8NjSPoGQlnO4w3LG1P0qPWQm/NU= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= -github.com/lestrrat-go/httprc/v3 v3.0.5 h1:S+Mb4L2I+bM6JGTibLmxExhyTOqnXjqx+zi9MoXw/TM= -github.com/lestrrat-go/httprc/v3 v3.0.5/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= -github.com/lestrrat-go/jwx/v3 v3.1.0 h1:AyyLtxc0QM75F75JroWgt1phwC7X+wOb3XKhH7XBZWw= -github.com/lestrrat-go/jwx/v3 v3.1.0/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU= +github.com/lestrrat-go/httprc/v3 v3.0.6 h1:4FpLQ18KK/ypPbVU3NLWJNRvH3kcYiqKqWfKGqNWxxI= +github.com/lestrrat-go/httprc/v3 v3.0.6/go.mod h1:mSMtkZW92Z98M5YoNNztbRGxbXHql7tSitCvaxvo9l0= +github.com/lestrrat-go/jwx/v3 v3.1.1 h1:yd9AdPmZ4INnQ7k42IrzXYpnEG803+SrQ6hdMvzHJzw= +github.com/lestrrat-go/jwx/v3 v3.1.1/go.mod h1:uw/MN2M/Xiu4FhwcIwH11Zsh9JWx9SWzgALl7/uIEkU= github.com/lestrrat-go/option/v2 v2.0.0 h1:XxrcaJESE1fokHy3FpaQ/cXW8ZsIdWcdFzzLOcID3Ss= github.com/lestrrat-go/option/v2 v2.0.0/go.mod h1:oSySsmzMoR0iRzCDCaUfsCzxQHUEuhOViQObyy7S6Vg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -33,10 +33,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/packages/system/kubeovn-webhook/.gitignore b/packages/system/kubeovn-webhook/.gitignore new file mode 100644 index 000000000..ae3f7df3f --- /dev/null +++ b/packages/system/kubeovn-webhook/.gitignore @@ -0,0 +1 @@ +images/kubeovn-webhook/kube-ovn-webhook diff --git a/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.mod b/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.mod index d8b4a7547..408cfd209 100644 --- a/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.mod +++ b/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.mod @@ -1,46 +1,58 @@ module kube-ovn-webhook -go 1.26.2 +go 1.26.4 require ( github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 - k8s.io/api v0.26.0 - k8s.io/apimachinery v0.26.0 - k8s.io/client-go v0.26.0 + k8s.io/api v0.36.2 + k8s.io/apimachinery v0.36.2 + k8s.io/client-go v0.36.2 ) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/go-logr/logr v1.2.3 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.2 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch v0.5.2 // indirect + github.com/fxamacker/cbor/v2 v2.9.2 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.23.1 // indirect + github.com/go-openapi/jsonreference v0.21.6 // indirect + github.com/go-openapi/swag v0.26.1 // indirect + github.com/go-openapi/swag/cmdutils v0.26.1 // indirect + github.com/go-openapi/swag/conv v0.26.1 // indirect + github.com/go-openapi/swag/fileutils v0.26.1 // indirect + github.com/go-openapi/swag/jsonname v0.26.1 // indirect + github.com/go-openapi/swag/jsonutils v0.26.1 // indirect + github.com/go-openapi/swag/loading v0.26.1 // indirect + github.com/go-openapi/swag/mangling v0.26.1 // indirect + github.com/go-openapi/swag/netutils v0.26.1 // indirect + github.com/go-openapi/swag/stringutils v0.26.1 // indirect + github.com/go-openapi/swag/typeutils v0.26.1 // indirect + github.com/go-openapi/swag/yamlutils v0.26.1 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/kr/text v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - golang.org/x/net v0.53.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect - google.golang.org/protobuf v1.36.11 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/klog/v2 v2.80.1 // indirect - k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect - k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect - sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/klog/v2 v2.140.0 // indirect + k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 // indirect + k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.4.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.sum b/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.sum index 62f9d1759..f0238dbbe 100644 --- a/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.sum +++ b/packages/system/kubeovn-webhook/images/kubeovn-webhook/go.sum @@ -1,209 +1,135 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= -github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.14 h1:gm3vOOXfiuw5i9p5N9xJvfjvuofpyvLA9Wr6QfK5Fng= -github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/fxamacker/cbor/v2 v2.9.2 h1:X4Ksno9+x3cz0TZv69ec1hxP/+tymuR8PXQJyDwfh78= +github.com/fxamacker/cbor/v2 v2.9.2/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= +github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= +github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y= +github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY= +github.com/go-openapi/swag v0.26.1 h1:l5sVEyVpwj+DDYeZyo7wQI/Ebn/mKYIyGB/pFwAfGoQ= +github.com/go-openapi/swag v0.26.1/go.mod h1:yNY38BbIVthxbkDtq1UHBCGasBqjakW3lCR6ANzdBEw= +github.com/go-openapi/swag/cmdutils v0.26.1 h1:f2iE1ijYaJ3nuu5PaEMx3zpEhzhZFgivCJObWEObLIQ= +github.com/go-openapi/swag/cmdutils v0.26.1/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= +github.com/go-openapi/swag/conv v0.26.1 h1:slr5FVkg9Wc3Y5zcwenD8Sd/PQ94b2I/QJI7N7KTBpg= +github.com/go-openapi/swag/conv v0.26.1/go.mod h1:mvQXgPptZk9GTrFgGwWvT4q+dN+zQej9JfmGwnipz1A= +github.com/go-openapi/swag/fileutils v0.26.1 h1:K1XCM2CGhfNsc6YDt6v7Q5+1e59rftYWdcu/isZhvFw= +github.com/go-openapi/swag/fileutils v0.26.1/go.mod h1:mYUgxQAKX4ShS3qvvySx+/9yrlUnDhjiD1CalaQl8lQ= +github.com/go-openapi/swag/jsonname v0.26.1 h1:VReupaV6WxlAsCn0e4DUfgV6bPmINnPpyJDLqSfNPcE= +github.com/go-openapi/swag/jsonname v0.26.1/go.mod h1:OvdW6BoWoj33pTfi7x9vFrgmT+fk7aw0BRwvCE0YOuc= +github.com/go-openapi/swag/jsonutils v0.26.1 h1:2hdBfFkHg+7Wrz2VsCbeyR6hzkRDs7AztnMR2u84yOY= +github.com/go-openapi/swag/jsonutils v0.26.1/go.mod h1:U+RMJH3wa+6BRiphuRtIyI8fW9HPFqFQ4sHk2oRx0UQ= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1 h1:1CD7NiLLb/TXl3tOnFYU4b+mNfb5rtgHkaA+q7RMYYQ= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.1/go.mod h1:ZWafc8nMdYzTE3uYY6W86f0n46+IF0g4uUyRhJw/kXc= +github.com/go-openapi/swag/loading v0.26.1 h1:E9K4wqXeROlhjFQ13K9zMz6ojFGXIggGe+ad1odrK9w= +github.com/go-openapi/swag/loading v0.26.1/go.mod h1:3qvRIlWzWdq1HvmldwmuJ2ohpcAryN6xVt2OTKd0/7E= +github.com/go-openapi/swag/mangling v0.26.1 h1:gpYI4WuPKFJJVjV5cDLGlDVJhFIxYjQc7yN5eEb4CqM= +github.com/go-openapi/swag/mangling v0.26.1/go.mod h1:POETDH01hqAdASXfw7ISEd9bCOE6xBHOt8NHmGZRmYM= +github.com/go-openapi/swag/netutils v0.26.1 h1:BNctoc39WTAUMxyAs355fExOPzMZtPbZ0ZZ1Am2FR5M= +github.com/go-openapi/swag/netutils v0.26.1/go.mod h1:y02vByhZhQPAVwOX+0KipXFZ/hUbk6G/Enhf5rGaOkQ= +github.com/go-openapi/swag/stringutils v0.26.1 h1:f88uYyTso7TnHrKM/bUBsQ5e2wKf37cpgo6pvbzd9yU= +github.com/go-openapi/swag/stringutils v0.26.1/go.mod h1:Sc6d3bU8fgk5AyZR8/8jEQ+Is/Ald+TD/IIggPN8UJk= +github.com/go-openapi/swag/typeutils v0.26.1 h1:yg42FgMzRR6PVQ3M3qHz1s+Y6/P4HoJ3cBarXa3OVnU= +github.com/go-openapi/swag/typeutils v0.26.1/go.mod h1:VfnV+oUtSP2vCSCn2aJgnr8OevUYemyIzzS1VOzS10o= +github.com/go-openapi/swag/yamlutils v0.26.1 h1:0TSLK+lXs9vfIhAWzBeI/lOzEnIoot6WTCO1aAeWFTk= +github.com/go-openapi/swag/yamlutils v0.26.1/go.mod h1:7W5b7PRX9MxwL7TjeG7H8HkyBGRsIDRObhyMWFgBI2M= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk= +github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo= +github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= +github.com/google/gnostic-models v0.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= -github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo= -github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= -github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= -golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= +google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I= -k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg= -k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg= -k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74= -k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8= -k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg= -k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4= -k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E= -k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs= -k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k= -sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +k8s.io/api v0.36.2 h1:TF6YDLIzKfccK7cq9YpTcGX8TJmEkHVRv78DM51fRYY= +k8s.io/api v0.36.2/go.mod h1:F4LbMO4brjZYh7yFkXWhynSvtB7YauxV4c+HHkNRGNg= +k8s.io/apimachinery v0.36.2 h1:0PE/W/WNy1UX61NLbXY5TMbJ6UwLL6E6lAPkYrKFxbQ= +k8s.io/apimachinery v0.36.2/go.mod h1:fvf/HOLXq9RId0rnDIbN1OEBvHXdQbLMM8nu0LcBUf4= +k8s.io/client-go v0.36.2 h1:bfgxmFKc9CgqsgX4xKLAAdmTQlWee7Ob/HlDOrJ5TBI= +k8s.io/client-go v0.36.2/go.mod h1:1vgO4OAlfPnoLcb+Rze2GF5rAr14w8qjrYMoyXJzQj0= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25 h1:mPMaPMpBij2V1Wv/fR+HW124vVGXXvOSS9ver/9yjWs= +k8s.io/kube-openapi v0.0.0-20260603220949-865597e52e25/go.mod h1:V/QaCUYDa+0QpcHhVVc5l99Uz56wEMEXBSj9oCDkNDY= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0 h1:qmp2e3ZfFi1/jJbDGpD4mt3wyp6PE1NfKHCYLqgNQJo= +sigs.k8s.io/structured-merge-diff/v6 v6.4.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= From 9c5fbb69a506ea631397d5d693cfd79b46d2ce06 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 17 Jun 2026 13:21:24 +0300 Subject: [PATCH 12/38] chore(images): modernize Go builder images to 1.26 Several first-party images still built their Go binaries with EOL or behind-latest toolchains (golang 1.20, 1.20.6, 1.23.4, 1.24, 1.25), so a plain rebuild kept shipping standard-library versions that the current 1.26 toolchain fixes. Raise every Go builder image (FROM and the builder_image ARG defaults) to golang:1.26 across cluster-autoscaler, kubevirt-cloud-provider, redis-operator, multus-cni, keycloak-operator, objectstorage, kilo and linstor-csi. This is the build-time counterpart to raising the go directive in the first-party Go modules: the toolchain that compiles the binary, not the go.mod minimum, determines the standard-library version baked into the image. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile | 2 +- .../apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile | 2 +- .../keycloak-operator/images/keycloak-operator/Dockerfile | 2 +- packages/system/kilo/images/kilo/Dockerfile | 2 +- packages/system/linstor/images/linstor-csi/Dockerfile | 2 +- .../objectstorage-controller/images/objectstorage/Dockerfile | 2 +- packages/system/redis-operator/images/redis-operator/Dockerfile | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile b/packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile index 7f9ff8963..4a0acd58e 100644 --- a/packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile +++ b/packages/apps/kubernetes/images/cluster-autoscaler/Dockerfile @@ -1,5 +1,5 @@ # Source: https://raw.githubusercontent.com/kubernetes/autoscaler/refs/heads/master/cluster-autoscaler/Dockerfile.amd64 -ARG builder_image=docker.io/library/golang:1.23.4 +ARG builder_image=docker.io/library/golang:1.26 ARG BASEIMAGE=gcr.io/distroless/static:nonroot-${TARGETARCH} FROM ${builder_image} AS builder diff --git a/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile b/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile index 58a4d0390..37e8a9176 100644 --- a/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile +++ b/packages/apps/kubernetes/images/kubevirt-cloud-provider/Dockerfile @@ -1,5 +1,5 @@ # Source: https://github.com/kubevirt/cloud-provider-kubevirt/blob/main/build/images/kubevirt-cloud-controller-manager/Dockerfile -FROM golang:1.20.6 AS builder +FROM golang:1.26 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/packages/system/keycloak-operator/images/keycloak-operator/Dockerfile b/packages/system/keycloak-operator/images/keycloak-operator/Dockerfile index 8167042f1..7a3e2a2a5 100644 --- a/packages/system/keycloak-operator/images/keycloak-operator/Dockerfile +++ b/packages/system/keycloak-operator/images/keycloak-operator/Dockerfile @@ -11,7 +11,7 @@ RUN curl -sSL https://github.com/epam/edp-keycloak-operator/archive/${COMMIT_REF COPY patches /patches RUN git apply /patches/*.diff -FROM --platform=$BUILDPLATFORM docker.io/golang:1.24 AS builder +FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/packages/system/kilo/images/kilo/Dockerfile b/packages/system/kilo/images/kilo/Dockerfile index e1d5f0094..b20da859c 100644 --- a/packages/system/kilo/images/kilo/Dockerfile +++ b/packages/system/kilo/images/kilo/Dockerfile @@ -1,5 +1,5 @@ # Build the kg binary -FROM --platform=$BUILDPLATFORM golang:1.25 AS builder +FROM --platform=$BUILDPLATFORM golang:1.26 AS builder ARG VERSION=944dff5745d747c06555a964c2137b9a986ff6ec ARG TARGETOS diff --git a/packages/system/linstor/images/linstor-csi/Dockerfile b/packages/system/linstor/images/linstor-csi/Dockerfile index df5d179dd..ac482c563 100644 --- a/packages/system/linstor/images/linstor-csi/Dockerfile +++ b/packages/system/linstor/images/linstor-csi/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25 AS builder +FROM golang:1.26 AS builder ARG VERSION=v1.10.6 ARG LINSTOR_WAIT_UNTIL_VERSION=v0.3.1 diff --git a/packages/system/objectstorage-controller/images/objectstorage/Dockerfile b/packages/system/objectstorage-controller/images/objectstorage/Dockerfile index d71e5566b..abaa0c3c9 100644 --- a/packages/system/objectstorage-controller/images/objectstorage/Dockerfile +++ b/packages/system/objectstorage-controller/images/objectstorage/Dockerfile @@ -11,7 +11,7 @@ RUN curl -sSL https://github.com/kubernetes-sigs/container-object-storage-interf COPY patches /patches RUN git apply /patches/*.diff -FROM --platform=$BUILDPLATFORM docker.io/golang:1.24 AS builder +FROM --platform=$BUILDPLATFORM docker.io/golang:1.26 AS builder ARG TARGETOS ARG TARGETARCH diff --git a/packages/system/redis-operator/images/redis-operator/Dockerfile b/packages/system/redis-operator/images/redis-operator/Dockerfile index c06bb2de0..fa1806c33 100644 --- a/packages/system/redis-operator/images/redis-operator/Dockerfile +++ b/packages/system/redis-operator/images/redis-operator/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20 AS builder +FROM golang:1.26 AS builder ARG VERSION=v1.3.0-rc1 From 3659803dccd819355b4599072fb1e14863726f2e Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 17:57:54 +0300 Subject: [PATCH 13/38] chore(kubeovn-webhook): ignore local build artifacts A local `go build` emits the module-named binary (kube-ovn-webhook), while the Dockerfile builds `-o webhook`. Ignore both names so neither local binary is accidentally committed. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/kubeovn-webhook/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/system/kubeovn-webhook/.gitignore b/packages/system/kubeovn-webhook/.gitignore index ae3f7df3f..31816768e 100644 --- a/packages/system/kubeovn-webhook/.gitignore +++ b/packages/system/kubeovn-webhook/.gitignore @@ -1 +1,2 @@ images/kubeovn-webhook/kube-ovn-webhook +images/kubeovn-webhook/webhook From d09d52e8dc9576e2188db4d568fa49b72ef1ce93 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 18:11:59 +0300 Subject: [PATCH 14/38] chore(kubevirt-csi-driver): bump golang.org/x/net to v0.56.0 Clear the reachable GO-2026-5026 advisory: idna.ToASCII is reached through the KubeVirt client path, and the fix landed in x/net v0.55.0. Bump to v0.56.0, pulling x/sys, x/term and x/text forward as required; govulncheck then reports the module clean. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../images/kubevirt-csi-driver/go.mod | 8 ++++---- .../images/kubevirt-csi-driver/go.sum | 20 +++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod index dcf5441f8..1bf8f3c62 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.mod @@ -44,11 +44,11 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/net v0.56.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/term v0.42.0 // indirect - golang.org/x/text v0.36.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect golang.org/x/time v0.14.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect diff --git a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum index e35cea75d..fa10984f9 100644 --- a/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum +++ b/packages/apps/kubernetes/images/kubevirt-csi-driver/go.sum @@ -282,8 +282,8 @@ golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -340,8 +340,8 @@ golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/telemetry v0.0.0-20250710130107-8d8967aff50b/go.mod h1:4ZwOYna0/zsOKwuR5X/m0QFOJpSZvAxFfkQT+Erd9D4= @@ -368,8 +368,8 @@ golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -391,8 +391,8 @@ golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -420,8 +420,8 @@ golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= -golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= From 192246e8b64ce40fbdebd25340e2b7537355e83b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 17 Jun 2026 12:47:02 +0300 Subject: [PATCH 15/38] chore(kubevirt-operator): update to v1.8.4 Refresh the vendored virt-operator manifest from v1.8.2 to v1.8.4. The release backports validation of the VMI checksum status fields as a uint32 range, required on Kubernetes 1.36 where strict CRD numeric format validation otherwise leaves VMIs stuck in Scheduled. Remaining changes are bug fixes and CVE remediations; the embedded CRDs and RBAC are unchanged, so only the operator image tag moves. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/kubevirt-operator/Makefile | 4 ++-- .../kubevirt-operator/templates/kubevirt-operator.yaml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/system/kubevirt-operator/Makefile b/packages/system/kubevirt-operator/Makefile index 9aeb6de1a..3f3a22649 100644 --- a/packages/system/kubevirt-operator/Makefile +++ b/packages/system/kubevirt-operator/Makefile @@ -6,8 +6,8 @@ include ../../../hack/package.mk update: rm -rf templates mkdir templates - # v1.7.0 blocked by https://github.com/kubevirt/kubevirt/issues/16386 - export RELEASE=v1.8.2 && \ + # Skipping v1.7.x: live-migration regression, https://github.com/kubevirt/kubevirt/issues/16386 + export RELEASE=v1.8.4 && \ wget https://github.com/kubevirt/kubevirt/releases/download/$${RELEASE}/kubevirt-operator.yaml -O templates/kubevirt-operator.yaml && \ sed -i 's/namespace: kubevirt/namespace: $(NAMESPACE)/g' templates/kubevirt-operator.yaml awk -i inplace -v RS="---" '!/kind: Namespace/{printf "%s", $$0 RS}' templates/kubevirt-operator.yaml diff --git a/packages/system/kubevirt-operator/templates/kubevirt-operator.yaml b/packages/system/kubevirt-operator/templates/kubevirt-operator.yaml index 8d73f9822..f6acbc37d 100644 --- a/packages/system/kubevirt-operator/templates/kubevirt-operator.yaml +++ b/packages/system/kubevirt-operator/templates/kubevirt-operator.yaml @@ -8536,14 +8536,14 @@ spec: - virt-operator env: - name: VIRT_OPERATOR_IMAGE - value: quay.io/kubevirt/virt-operator:v1.8.2 + value: quay.io/kubevirt/virt-operator:v1.8.4 - name: WATCH_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.annotations['olm.targetNamespaces'] - name: KUBEVIRT_VERSION - value: v1.8.2 - image: quay.io/kubevirt/virt-operator:v1.8.2 + value: v1.8.4 + image: quay.io/kubevirt/virt-operator:v1.8.4 imagePullPolicy: IfNotPresent livenessProbe: httpGet: From 7e4af79b3151ddfe430ce98b35b971e24c1f232d Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 19:49:16 +0300 Subject: [PATCH 16/38] fix(kamaji): rebuild image to pick up Go stdlib security fixes The shipped kamaji binary was compiled with go1.26.3, which trivy flags for the standard-library CVEs fixed in go1.26.4: crypto/x509 CVE-2026-27145, mime CVE-2026-42504, and net/textproto CVE-2026-42507. The image base is distroless/static, so the remaining OS-package CVEs from the scan do not apply to it. Advance the build source from 26.3.5-edge to 26.3.6-edge, the next patch on the same edge line (identical k8s.io/api v0.35.0 and controller-runtime v0.22.4, staying clear of the k8s 1.36 line that regresses tenant control-plane RBAC). The version change busts the build-layer cache so the release rebuild compiles against the current golang:1.26 toolchain, which resolves to 1.26.4 and closes the stdlib CVEs. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/kamaji/images/kamaji/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/system/kamaji/images/kamaji/Dockerfile b/packages/system/kamaji/images/kamaji/Dockerfile index 4dd0bb3ff..b67f934f0 100644 --- a/packages/system/kamaji/images/kamaji/Dockerfile +++ b/packages/system/kamaji/images/kamaji/Dockerfile @@ -1,7 +1,7 @@ # Build the manager binary FROM golang:1.26 AS builder -ARG VERSION=26.3.5-edge +ARG VERSION=26.3.6-edge ARG TARGETOS ARG TARGETARCH From 13ca721bfda6c6666acbde6a5029607e7f08ba6c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 20:07:46 +0300 Subject: [PATCH 17/38] fix(kamaji): overlay patched x/net, x/crypto and otel/sdk modules The kamaji source up to the 26.6.4-edge tag pins Go modules that carry known advisories compiled into the manager binary: golang.org/x/net below 0.55.0, golang.org/x/crypto below 0.52.0 and go.opentelemetry.io/otel/sdk below 1.43.0. Unlike the distroless OS-package findings, these advisories ship inside the Go binary and are reported against the deployed image. Force the fixed module versions during the build, after applying the source patches and before compiling, so the binary embeds them. No edge tag ships the fixes, and the tags that advance these modules also bring the k8s 1.36 kubeadm change that regresses tenant control-plane RBAC, so a source version bump is not a viable alternative. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/kamaji/images/kamaji/Dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/system/kamaji/images/kamaji/Dockerfile b/packages/system/kamaji/images/kamaji/Dockerfile index b67f934f0..144c67118 100644 --- a/packages/system/kamaji/images/kamaji/Dockerfile +++ b/packages/system/kamaji/images/kamaji/Dockerfile @@ -12,6 +12,13 @@ RUN curl -sSL https://github.com/clastix/kamaji/archive/refs/tags/${VERSION}.tar COPY patches /patches RUN git apply /patches/*.diff +# Overlay patched Go modules. The kamaji edge tags up to 26.6.4 still pin +# vulnerable golang.org/x/net (< 0.55.0), golang.org/x/crypto (< 0.52.0) and +# go.opentelemetry.io/otel/sdk (< 1.43.0). These advisories ship inside the +# compiled binary, so force the fixed versions before building. +RUN go get golang.org/x/net@v0.55.0 golang.org/x/crypto@v0.52.0 go.opentelemetry.io/otel/sdk@v1.43.0 && \ + go mod tidy + RUN GOOS=$TARGETOS GOARCH=$TARGETARCH CGO_ENABLED=0 go build \ -ldflags "-X github.com/clastix/kamaji/internal.GitRepo=$GIT_REPO -X github.com/clastix/kamaji/internal.GitTag=$GIT_LAST_TAG -X github.com/clastix/kamaji/internal.GitCommit=$GIT_HEAD_COMMIT -X github.com/clastix/kamaji/internal.GitDirty=$GIT_MODIFIED -X github.com/clastix/kamaji/internal.BuildTime=$BUILD_DATE" \ -a -o kamaji main.go From c7bd3bcf2b58d7c6c7e770519fdda37c3a0b4481 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 22:10:11 +0300 Subject: [PATCH 18/38] test(e2e): widen etcd convergence budget to 5m in Talos bootstrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Bootstrap Talos cluster" step times out the etcd-members health gate at 180s. A 3-node etcd converges via Talos's serialized learner promotion (one learner admitted at a time), so on a loaded ephemeral runner this deterministic path can approach ~3m wall clock and photo-finish against the 180s ceiling — failing a cluster that is in fact healthy. The wait is already event-driven; only the ceiling was too tight. Widen it to 300s. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-prepare-cluster.bats | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/hack/e2e-prepare-cluster.bats b/hack/e2e-prepare-cluster.bats index 0c639ef30..a57fbab3f 100644 --- a/hack/e2e-prepare-cluster.bats +++ b/hack/e2e-prepare-cluster.bats @@ -261,8 +261,15 @@ EOF # ephemeral runners can take several attempts of ~5s each. timeout 60 sh -ec 'until talosctl bootstrap -n 192.168.123.11 -e 192.168.123.11; do sleep 2; done' - # Wait until etcd is healthy - if ! timeout 180 sh -ec 'until talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 >/dev/null 2>&1; do sleep 1; done'; then + # Wait until etcd is healthy. + # Budget is 5m, not 3m: a 3-node etcd converges via Talos's serialized + # learner promotion (etcd admits one learner at a time), so the third member + # is only promoted after the second is fully caught up. On a loaded ephemeral + # runner this deterministic path can legitimately approach ~3m wall clock — + # a 180s ceiling photo-finishes against it and times out a cluster that is + # actually healthy. The wait itself is already event-driven (poll until + # members respond); only the ceiling needed widening. + if ! timeout 300 sh -ec 'until talosctl etcd members -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 >/dev/null 2>&1; do sleep 1; done'; then talosctl dmesg -n 192.168.123.11,192.168.123.12,192.168.123.13 -e 192.168.123.10 || true exit 1 fi From ee853b00bd046646c7d799bed27fb5cae4eec883 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 22:18:05 +0300 Subject: [PATCH 19/38] fix(platform): order backupstrategy-controller after cozystack-basics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit backupstrategy-controller creates the cozy-backups Bucket into the tenant-root namespace, which cozystack-basics provisions. On a fresh install the Bucket patch can race namespace creation and fail with `namespaces "tenant-root" not found`, which then races the install wait deadline and fails the run — on changes that have nothing to do with backup ordering. Add the cozystack-basics dependsOn edge so the target namespace exists first, pinned with a helm-unittest regression guard. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../sources/backupstrategy-controller.yaml | 8 +++++ .../platform/sources/cozystack-basics.yaml | 7 ++-- ...sources_backupstrategy_dependson_test.yaml | 34 +++++++++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 packages/core/platform/tests/sources_backupstrategy_dependson_test.yaml diff --git a/packages/core/platform/sources/backupstrategy-controller.yaml b/packages/core/platform/sources/backupstrategy-controller.yaml index 2306c755f..d28e59991 100644 --- a/packages/core/platform/sources/backupstrategy-controller.yaml +++ b/packages/core/platform/sources/backupstrategy-controller.yaml @@ -18,8 +18,16 @@ spec: # All three must be installed before the templates render, otherwise # the Helm release fails with "no matches for kind". Order is enforced # via dependsOn so a fresh-cluster bootstrap cannot race the CRDs. + # + # The cozy-backups Bucket is also created INTO the tenant-root namespace, + # which is provisioned by cozystack-basics (the static tenant-root release). + # Without the cozystack-basics edge the Bucket patch can race namespace + # creation on a fresh install and fail with `namespaces "tenant-root" not + # found`, which then races the install script's wait deadline. Depend on + # cozystack-basics so the target namespace exists first. dependsOn: - cozystack.networking + - cozystack.cozystack-basics - cozystack.backup-controller - cozystack.bucket-application - cozystack.objectstorage-controller diff --git a/packages/core/platform/sources/cozystack-basics.yaml b/packages/core/platform/sources/cozystack-basics.yaml index 8a5313ee6..0a71a2461 100644 --- a/packages/core/platform/sources/cozystack-basics.yaml +++ b/packages/core/platform/sources/cozystack-basics.yaml @@ -28,8 +28,11 @@ spec: # Drift detection is off on operator-generated HelmReleases, so a # capability gate in the templates would render the policy out at first # install and never add it back; ordering via dependsOn is the reliable - # fix. cozystack-basics is a dependency-graph leaf (nothing dependsOn it) - # and engine components consume none of its outputs, so this adds no cycle. + # fix. The cozystack-engine and gateway-api-crds packages it depends on + # consume none of cozystack-basics' outputs and do not depend back on it, + # so these edges add no cycle. (Downstream packages such as backupstrategy- + # controller dependsOn cozystack-basics, but those do not feed engine or + # gateway-api-crds, so they cannot close a loop here.) - cozystack.cozystack-engine - cozystack.gateway-api-crds components: diff --git a/packages/core/platform/tests/sources_backupstrategy_dependson_test.yaml b/packages/core/platform/tests/sources_backupstrategy_dependson_test.yaml new file mode 100644 index 000000000..e1234972b --- /dev/null +++ b/packages/core/platform/tests/sources_backupstrategy_dependson_test.yaml @@ -0,0 +1,34 @@ +suite: backupstrategy-controller waits for the tenant-root namespace and its CRDs +# Regression guard for a fresh-install ordering race: backupstrategy-controller +# creates the cozy-backups Bucket INTO the tenant-root namespace, which is +# provisioned by cozystack-basics. Without the cozystack-basics edge the Bucket +# patch races namespace creation and fails with `namespaces "tenant-root" not +# found`, which then races the install script's wait deadline. The CRD edges +# (backup-controller / bucket-application / objectstorage-controller / velero) +# guard the `no matches for kind` race. All edges below must stay. +templates: + - templates/sources.yaml +release: + name: cozystack + namespace: cozy-system +tests: + - it: backupstrategy-controller dependsOn cozystack-basics and the CRD providers + documentSelector: + path: metadata.name + value: cozystack.backupstrategy-controller + asserts: + - contains: + path: spec.variants[0].dependsOn + content: cozystack.cozystack-basics + - contains: + path: spec.variants[0].dependsOn + content: cozystack.backup-controller + - contains: + path: spec.variants[0].dependsOn + content: cozystack.bucket-application + - contains: + path: spec.variants[0].dependsOn + content: cozystack.objectstorage-controller + - contains: + path: spec.variants[0].dependsOn + content: cozystack.velero From a79f8009f4cbf94beccd6f39622e2cc18fde34a7 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 22:30:37 +0300 Subject: [PATCH 20/38] test(foundationdb): widen late-converging status wait budgets The reconciled, hasListenIPsForAllPods, fullReplication and healthy gates polled status fields with a 60s ceiling, while the strictly-earlier health.available gate had 300s. The operator stamps generations.reconciled only after every process holds its role for minimumUptimeSecondsForBounce (60s), making reconciled and the health fields that depend on it the last status fields to converge. The short ceiling closed the gate seconds before the field landed on a healthy, fully-reconciled cluster, causing intermittent jsonpath poll timeouts. Raise those four gates to 300s to match the health.available budget. The waits stay event-driven; only the ceilings widen. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/e2e-apps/foundationdb.bats | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/hack/e2e-apps/foundationdb.bats b/hack/e2e-apps/foundationdb.bats index accfd8e1d..ef48d4df6 100644 --- a/hack/e2e-apps/foundationdb.bats +++ b/hack/e2e-apps/foundationdb.bats @@ -91,15 +91,27 @@ EOF # Validate status.desiredProcessGroups field timeout 60 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.desiredProcessGroups}' | grep -q '^[0-9][0-9]*$'; do sleep 10; done" - # Validate status.generations.reconciled field - timeout 60 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.generations.reconciled}' | grep -q '^[0-9][0-9]*$'; do sleep 10; done" + # Validate status.generations.reconciled field. + # The operator only stamps generations.reconciled after every process has + # held its role for minimumUptimeSecondsForBounce (60s, see + # packages/apps/foundationdb/templates/cluster.yaml), so this is structurally + # the last status field to converge. It needs at least the same budget as the + # earlier health.available gate (300s), not the short 60s ceiling. + timeout 300 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.generations.reconciled}' | grep -q '^[0-9][0-9]*$'; do sleep 10; done" - # Validate status.hasListenIPsForAllPods field - timeout 60 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.hasListenIPsForAllPods}' | grep -q 'true'; do sleep 10; done" + # Validate status.hasListenIPsForAllPods field. A bounce restarts fdbserver + # in-place, so pods and their listen IPs do not churn and this usually settles + # before the reconciled gate. Widened to 300s for a conservative ceiling + # consistent with the surrounding gates; the 300s is headroom, not because + # this field is as late-converging as generations.reconciled. + timeout 300 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.hasListenIPsForAllPods}' | grep -q 'true'; do sleep 10; done" - # Validate comprehensive status.health fields - timeout 60 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.health.fullReplication}' | grep -q 'true'; do sleep 10; done" - timeout 60 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.health.healthy}' | grep -q 'true'; do sleep 10; done" + # Validate comprehensive status.health fields. fullReplication and healthy + # settle only once the cluster is fully reconciled (after the post-bounce + # uptime gate), so they share the same 300s budget rather than the short 60s + # ceiling. + timeout 300 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.health.fullReplication}' | grep -q 'true'; do sleep 10; done" + timeout 300 sh -ec "until kubectl -n tenant-test get foundationdbclusters.apps.foundationdb.org foundationdb-$name -o jsonpath='{.status.health.healthy}' | grep -q 'true'; do sleep 10; done" # Verify security context is applied correctly (non-root user) storage_pod=$(kubectl -n tenant-test get pods -l foundationdb.org/fdb-cluster-name=foundationdb-$name,foundationdb.org/fdb-process-class=storage --no-headers | head -n1 | awk '{print $1}') From fd6e121b285f0a3e24c5e6f8367a4115d1062ffe Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 22:43:56 +0300 Subject: [PATCH 21/38] fix(platform): make *-rd sources depend on cozystack-engine Every *-application PackageSource that ships a system/*-rd component renders an ApplicationDefinition (cozystack.io/v1alpha1). That CRD is installed by the application-definition-crd component of cozystack-engine. Without an explicit dependsOn edge, a fresh install where Flux reconciles a *-application source before cozystack-engine fails to render with `no matches for kind "ApplicationDefinition" in version "cozystack.io/v1alpha1"`, which cascades into a broad install timeout unrelated to whatever change triggered the reconcile. Add `- cozystack.cozystack-engine` to the dependsOn of every source that ships a system/*-rd component; gateway-application already carried the edge for the TenantGateway CRD. Add a generic invariant test that fails if any such source is missing the edge, so future sources cannot reintroduce the race. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- ...ck-applicationdefinition-crd-ordering.bats | 62 +++++++++++++++++++ .../platform/sources/bootbox-application.yaml | 2 + .../platform/sources/bucket-application.yaml | 2 + .../sources/clickhouse-application.yaml | 2 + .../platform/sources/etcd-application.yaml | 2 + .../sources/external-dns-application.yaml | 2 + .../sources/foundationdb-application.yaml | 2 + .../platform/sources/harbor-application.yaml | 2 + .../sources/http-cache-application.yaml | 2 + .../platform/sources/info-application.yaml | 2 + .../platform/sources/ingress-application.yaml | 2 + .../platform/sources/kafka-application.yaml | 2 + .../sources/kubernetes-application.yaml | 2 + .../platform/sources/mariadb-application.yaml | 2 + .../platform/sources/mongodb-application.yaml | 2 + .../sources/monitoring-application.yaml | 2 + .../platform/sources/nats-application.yaml | 2 + .../platform/sources/openbao-application.yaml | 2 + .../sources/opensearch-application.yaml | 2 + .../sources/postgres-application.yaml | 2 + .../platform/sources/qdrant-application.yaml | 2 + .../sources/rabbitmq-application.yaml | 2 + .../platform/sources/redis-application.yaml | 2 + .../sources/seaweedfs-application.yaml | 2 + .../sources/tcp-balancer-application.yaml | 2 + .../platform/sources/tenant-application.yaml | 2 + .../virtualprivatecloud-application.yaml | 2 + .../platform/sources/vm-disk-application.yaml | 2 + .../sources/vm-instance-application.yaml | 2 + .../platform/sources/vpn-application.yaml | 2 + 30 files changed, 120 insertions(+) create mode 100644 hack/check-applicationdefinition-crd-ordering.bats diff --git a/hack/check-applicationdefinition-crd-ordering.bats b/hack/check-applicationdefinition-crd-ordering.bats new file mode 100644 index 000000000..1ff6041f2 --- /dev/null +++ b/hack/check-applicationdefinition-crd-ordering.bats @@ -0,0 +1,62 @@ +#!/usr/bin/env bats +# Chart-wide invariant for packages/core/platform/sources: +# +# Every *-application PackageSource that ships a `system/*-rd` component renders +# an ApplicationDefinition (cozystack.io/v1alpha1). That CRD is installed by the +# application-definition-crd component of cozystack.cozystack-engine. Without an +# explicit `dependsOn: cozystack.cozystack-engine`, a fresh install where Flux +# happens to reconcile the *-application source before cozystack-engine fails to +# render with `no matches for kind "ApplicationDefinition" in version +# "cozystack.io/v1alpha1"`, which cascades into a broad install timeout. +# +# This invariant is generic: any future *-application source that adds a +# system/*-rd component but forgets the cozystack-engine edge fails here, even +# though the individual source files are not enumerated below. +# +# Requires: yq (mikefarah v4+), jq. Both are available on the project's CI +# runners and on the maintainer workstation. + +REPO_ROOT="$(cd "$(dirname "${BATS_TEST_FILENAME:-$0}")/.." && pwd)" +SOURCES_DIR="$REPO_ROOT/packages/core/platform/sources" + +@test "every PackageSource with a system/*-rd component dependsOn cozystack.cozystack-engine" { + command -v yq >/dev/null || { echo "yq (mikefarah v4+) is required" >&2; exit 1; } + command -v jq >/dev/null || { echo "jq is required" >&2; exit 1; } + + # yq streams one JSON object per source document; jq -s slurps the stream so + # every PackageSource variant can be evaluated as a single collection. + json="$(yq --output-format=json eval-all '.' "$SOURCES_DIR"/*.yaml)" + + # A "-rd variant" is any variant whose components include a system/*-rd path. + rd_total="$(printf '%s' "$json" | jq -s ' + [ .[] + | select(.kind == "PackageSource") + | (.spec.variants // [])[] + | select((.components // []) | any(.path // "" | test("^system/[a-z0-9-]+-rd$"))) + ] | length + ')" + + # Guard against a vacuous pass (empty glob / yq breakage): the platform ships + # many -rd sources, so discovering zero means this test itself is broken. + [ "$rd_total" -ge 1 ] || { echo "Discovered zero system/*-rd variants under $SOURCES_DIR - test is misconfigured" >&2; exit 1; } + + offenders="$(printf '%s' "$json" | jq -rs ' + [ .[] + | select(.kind == "PackageSource") + | .metadata.name as $name + | (.spec.variants // [])[] + | select((.components // []) | any(.path // "" | test("^system/[a-z0-9-]+-rd$"))) + | select(((.dependsOn // []) | any(. == "cozystack.cozystack-engine")) | not) + | "\($name) [variant: \(.name)]" + ] | .[] + ')" + + if [ -n "$offenders" ]; then + echo "PackageSources with a system/*-rd component but missing the cozystack.cozystack-engine dependsOn edge:" >&2 + printf '%s\n' "$offenders" >&2 + echo "Fix: add '- cozystack.cozystack-engine' to each listed variant's dependsOn (mirror sources/gateway-application.yaml)." >&2 + exit 1 + fi + + echo "Invariant holds: all $rd_total system/*-rd PackageSource variant(s) dependsOn cozystack.cozystack-engine" +} diff --git a/packages/core/platform/sources/bootbox-application.yaml b/packages/core/platform/sources/bootbox-application.yaml index ee6401769..8aca8fbf2 100644 --- a/packages/core/platform/sources/bootbox-application.yaml +++ b/packages/core/platform/sources/bootbox-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/bucket-application.yaml b/packages/core/platform/sources/bucket-application.yaml index a50c116f1..c69463097 100644 --- a/packages/core/platform/sources/bucket-application.yaml +++ b/packages/core/platform/sources/bucket-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.objectstorage-controller + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/clickhouse-application.yaml b/packages/core/platform/sources/clickhouse-application.yaml index f22a003de..b6ca0f7a8 100644 --- a/packages/core/platform/sources/clickhouse-application.yaml +++ b/packages/core/platform/sources/clickhouse-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.clickhouse-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/etcd-application.yaml b/packages/core/platform/sources/etcd-application.yaml index 1abcec121..9dcbe670c 100644 --- a/packages/core/platform/sources/etcd-application.yaml +++ b/packages/core/platform/sources/etcd-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/external-dns-application.yaml b/packages/core/platform/sources/external-dns-application.yaml index 0a37dc522..cd26bc02d 100644 --- a/packages/core/platform/sources/external-dns-application.yaml +++ b/packages/core/platform/sources/external-dns-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/foundationdb-application.yaml b/packages/core/platform/sources/foundationdb-application.yaml index 4488b0339..23caa037e 100644 --- a/packages/core/platform/sources/foundationdb-application.yaml +++ b/packages/core/platform/sources/foundationdb-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.foundationdb-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/harbor-application.yaml b/packages/core/platform/sources/harbor-application.yaml index c2773bdd9..9121d9c02 100644 --- a/packages/core/platform/sources/harbor-application.yaml +++ b/packages/core/platform/sources/harbor-application.yaml @@ -16,6 +16,8 @@ spec: - cozystack.postgres-operator - cozystack.redis-operator - cozystack.objectstorage-controller + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/http-cache-application.yaml b/packages/core/platform/sources/http-cache-application.yaml index 67537b4f5..4431b94cc 100644 --- a/packages/core/platform/sources/http-cache-application.yaml +++ b/packages/core/platform/sources/http-cache-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/info-application.yaml b/packages/core/platform/sources/info-application.yaml index 25b8b7982..7ab6d708d 100644 --- a/packages/core/platform/sources/info-application.yaml +++ b/packages/core/platform/sources/info-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/ingress-application.yaml b/packages/core/platform/sources/ingress-application.yaml index 638c2f1c4..157fb2f62 100644 --- a/packages/core/platform/sources/ingress-application.yaml +++ b/packages/core/platform/sources/ingress-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/kafka-application.yaml b/packages/core/platform/sources/kafka-application.yaml index 4a5ca1587..3b3c58770 100644 --- a/packages/core/platform/sources/kafka-application.yaml +++ b/packages/core/platform/sources/kafka-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.kafka-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/kubernetes-application.yaml b/packages/core/platform/sources/kubernetes-application.yaml index 551a8191f..94a276a5b 100644 --- a/packages/core/platform/sources/kubernetes-application.yaml +++ b/packages/core/platform/sources/kubernetes-application.yaml @@ -18,6 +18,8 @@ spec: - cozystack.capi-provider-core - cozystack.capi-provider-cp-kamaji - cozystack.capi-provider-infra-kubevirt + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/mariadb-application.yaml b/packages/core/platform/sources/mariadb-application.yaml index 34ff901b7..2f324e14e 100644 --- a/packages/core/platform/sources/mariadb-application.yaml +++ b/packages/core/platform/sources/mariadb-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.mariadb-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/mongodb-application.yaml b/packages/core/platform/sources/mongodb-application.yaml index b6106fe17..67f7f25c6 100644 --- a/packages/core/platform/sources/mongodb-application.yaml +++ b/packages/core/platform/sources/mongodb-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/monitoring-application.yaml b/packages/core/platform/sources/monitoring-application.yaml index 119aaa373..6c6360398 100644 --- a/packages/core/platform/sources/monitoring-application.yaml +++ b/packages/core/platform/sources/monitoring-application.yaml @@ -16,6 +16,8 @@ spec: - cozystack.postgres-operator - cozystack.grafana-operator - cozystack.victoria-metrics-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/nats-application.yaml b/packages/core/platform/sources/nats-application.yaml index b09c5a303..21c75440d 100644 --- a/packages/core/platform/sources/nats-application.yaml +++ b/packages/core/platform/sources/nats-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/openbao-application.yaml b/packages/core/platform/sources/openbao-application.yaml index 438148af2..715bf00c7 100644 --- a/packages/core/platform/sources/openbao-application.yaml +++ b/packages/core/platform/sources/openbao-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/opensearch-application.yaml b/packages/core/platform/sources/opensearch-application.yaml index 19358c210..285fc5451 100644 --- a/packages/core/platform/sources/opensearch-application.yaml +++ b/packages/core/platform/sources/opensearch-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.opensearch-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/postgres-application.yaml b/packages/core/platform/sources/postgres-application.yaml index 5d7b220b8..784dc6334 100644 --- a/packages/core/platform/sources/postgres-application.yaml +++ b/packages/core/platform/sources/postgres-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.postgres-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/qdrant-application.yaml b/packages/core/platform/sources/qdrant-application.yaml index 6477cc37c..4bd16b680 100644 --- a/packages/core/platform/sources/qdrant-application.yaml +++ b/packages/core/platform/sources/qdrant-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/rabbitmq-application.yaml b/packages/core/platform/sources/rabbitmq-application.yaml index 7dc68b315..bf1ed1914 100644 --- a/packages/core/platform/sources/rabbitmq-application.yaml +++ b/packages/core/platform/sources/rabbitmq-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.rabbitmq-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/redis-application.yaml b/packages/core/platform/sources/redis-application.yaml index 41a2a9dc7..6abfacc65 100644 --- a/packages/core/platform/sources/redis-application.yaml +++ b/packages/core/platform/sources/redis-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.redis-operator + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/seaweedfs-application.yaml b/packages/core/platform/sources/seaweedfs-application.yaml index a213ebc86..713551ec6 100644 --- a/packages/core/platform/sources/seaweedfs-application.yaml +++ b/packages/core/platform/sources/seaweedfs-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/tcp-balancer-application.yaml b/packages/core/platform/sources/tcp-balancer-application.yaml index 0ba031e7d..bbee51fe5 100644 --- a/packages/core/platform/sources/tcp-balancer-application.yaml +++ b/packages/core/platform/sources/tcp-balancer-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/tenant-application.yaml b/packages/core/platform/sources/tenant-application.yaml index 6fad2ba64..841c3a3ca 100644 --- a/packages/core/platform/sources/tenant-application.yaml +++ b/packages/core/platform/sources/tenant-application.yaml @@ -19,6 +19,8 @@ spec: - cozystack.info-application - cozystack.monitoring-application - cozystack.etcd-application + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/virtualprivatecloud-application.yaml b/packages/core/platform/sources/virtualprivatecloud-application.yaml index b6a8519b1..4b813881b 100644 --- a/packages/core/platform/sources/virtualprivatecloud-application.yaml +++ b/packages/core/platform/sources/virtualprivatecloud-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.multus + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/vm-disk-application.yaml b/packages/core/platform/sources/vm-disk-application.yaml index a4e98ca95..e15064a0f 100644 --- a/packages/core/platform/sources/vm-disk-application.yaml +++ b/packages/core/platform/sources/vm-disk-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.kubevirt-cdi + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/vm-instance-application.yaml b/packages/core/platform/sources/vm-instance-application.yaml index eaa0c76e8..2fd377e29 100644 --- a/packages/core/platform/sources/vm-instance-application.yaml +++ b/packages/core/platform/sources/vm-instance-application.yaml @@ -14,6 +14,8 @@ spec: dependsOn: - cozystack.networking - cozystack.kubevirt + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib diff --git a/packages/core/platform/sources/vpn-application.yaml b/packages/core/platform/sources/vpn-application.yaml index 0200108fe..5ce29cb2c 100644 --- a/packages/core/platform/sources/vpn-application.yaml +++ b/packages/core/platform/sources/vpn-application.yaml @@ -13,6 +13,8 @@ spec: - name: default dependsOn: - cozystack.networking + # cozystack-engine ships the ApplicationDefinition CRD rendered by the *-rd component below. + - cozystack.cozystack-engine libraries: - name: cozy-lib path: library/cozy-lib From db797b1dd34b92bda71d96ea17fd7d07ddda6c6c Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 22:27:26 +0300 Subject: [PATCH 22/38] fix(opensearch-operator): gate leader election on replica count The operator Deployment ships a single replica but always passes --leader-elect to the manager. On one replica leader election adds no HA value: a transient apiserver/etcd lease-renewal blip makes controller-runtime self-terminate (os.Exit on "leader election lost"), which crashloops the manager and surfaces at install time as a Deployment stuck in Failed. Gate --leader-elect on the replica count via a manager.replicaCount value (default 1). The single-replica install now rides out control-plane latency instead of crashlooping, and scaling to more than one replica re-enables leader election automatically for genuine HA. Also set the Deployment strategy to Recreate on a single replica: with leader election off, a default RollingUpdate would briefly run two managers (maxSurge) during an operator upgrade, both reconciling without lease coordination. Recreate closes that window at zero availability cost, while installs scaled past one replica keep RollingUpdate. The change to the vendored opensearch-operator 2.8.0 chart is carried in patches/leaderElection.diff and re-applied by 'make update' so it survives a chart refresh. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- packages/system/opensearch-operator/Makefile | 1 + ...perator-controller-manager-deployment.yaml | 11 +++- .../patches/leaderElection.diff | 29 ++++++++++ .../tests/leader_election_test.yaml | 57 +++++++++++++++++++ .../system/opensearch-operator/values.yaml | 6 ++ 5 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 packages/system/opensearch-operator/patches/leaderElection.diff create mode 100644 packages/system/opensearch-operator/tests/leader_election_test.yaml diff --git a/packages/system/opensearch-operator/Makefile b/packages/system/opensearch-operator/Makefile index 365224956..ea0e2bf9d 100644 --- a/packages/system/opensearch-operator/Makefile +++ b/packages/system/opensearch-operator/Makefile @@ -8,3 +8,4 @@ update: helm repo add opensearch-operator https://opensearch-project.github.io/opensearch-k8s-operator/ helm repo update opensearch-operator helm pull opensearch-operator/opensearch-operator --version 2.8.0 --untar --untardir charts + patch --no-backup-if-mismatch -p1 < patches/leaderElection.diff diff --git a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml index 114e6b200..fb58d6527 100644 --- a/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml +++ b/packages/system/opensearch-operator/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml @@ -5,7 +5,14 @@ metadata: control-plane: controller-manager name: {{ include "opensearch-operator.fullname" . }}-controller-manager spec: - replicas: 1 + replicas: {{ .Values.manager.replicaCount | default 1 }} + {{- if le (int (.Values.manager.replicaCount | default 1)) 1 }} + # On a single replica a RollingUpdate briefly runs two managers (maxSurge) during + # an upgrade; with leader election gated off they would both reconcile without lease + # coordination. Recreate closes that window at zero availability cost on one replica. + strategy: + type: Recreate + {{- end }} selector: matchLabels: control-plane: controller-manager @@ -49,7 +56,9 @@ spec: - args: - --health-probe-bind-address=:8081 - --metrics-bind-address=127.0.0.1:8080 + {{- if gt (int (.Values.manager.replicaCount | default 1)) 1 }} - --leader-elect + {{- end }} {{- if .Values.manager.watchNamespace }} - --watch-namespace={{ .Values.manager.watchNamespace }} {{- end }} diff --git a/packages/system/opensearch-operator/patches/leaderElection.diff b/packages/system/opensearch-operator/patches/leaderElection.diff new file mode 100644 index 000000000..57dc6cff8 --- /dev/null +++ b/packages/system/opensearch-operator/patches/leaderElection.diff @@ -0,0 +1,29 @@ +diff --git a/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml b/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml +--- a/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml ++++ b/charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml +@@ -5,7 +5,14 @@ + control-plane: controller-manager + name: {{ include "opensearch-operator.fullname" . }}-controller-manager + spec: +- replicas: 1 ++ replicas: {{ .Values.manager.replicaCount | default 1 }} ++ {{- if le (int (.Values.manager.replicaCount | default 1)) 1 }} ++ # On a single replica a RollingUpdate briefly runs two managers (maxSurge) during ++ # an upgrade; with leader election gated off they would both reconcile without lease ++ # coordination. Recreate closes that window at zero availability cost on one replica. ++ strategy: ++ type: Recreate ++ {{- end }} + selector: + matchLabels: + control-plane: controller-manager +@@ -49,7 +56,9 @@ + - args: + - --health-probe-bind-address=:8081 + - --metrics-bind-address=127.0.0.1:8080 ++ {{- if gt (int (.Values.manager.replicaCount | default 1)) 1 }} + - --leader-elect ++ {{- end }} + {{- if .Values.manager.watchNamespace }} + - --watch-namespace={{ .Values.manager.watchNamespace }} + {{- end }} diff --git a/packages/system/opensearch-operator/tests/leader_election_test.yaml b/packages/system/opensearch-operator/tests/leader_election_test.yaml new file mode 100644 index 000000000..663394b75 --- /dev/null +++ b/packages/system/opensearch-operator/tests/leader_election_test.yaml @@ -0,0 +1,57 @@ +suite: opensearch-operator leader election is gated on replica count + +# The operator Deployment ships a single replica. Leader election on one replica +# adds no HA value but lets a transient apiserver/etcd lease-renewal blip make the +# manager self-terminate (controller-runtime os.Exit on "leader election lost"), +# which surfaces at install time as a crashlooping Deployment marked Failed. +# Gate --leader-elect on replica count: off for the default single replica, back +# on automatically when an operator scales to more than one replica for real HA. +release: + name: opensearch-operator + namespace: cozy-opensearch-operator +tests: + - it: default single-replica install drops --leader-elect from the manager + template: charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml + documentSelector: + path: kind + value: Deployment + asserts: + - equal: + path: spec.replicas + value: 1 + # Guard container ordering: kube-rbac-proxy is [0], manager is [1]. + - equal: + path: spec.template.spec.containers[1].name + value: operator-controller-manager + - notContains: + path: spec.template.spec.containers[1].args + content: --leader-elect + # Recreate avoids two uncoordinated managers overlapping during an upgrade + # while leader election is gated off on the single replica. + - equal: + path: spec.strategy.type + value: Recreate + + - it: scaling past one replica re-enables --leader-elect for real HA + template: charts/opensearch-operator/templates/opensearch-operator-controller-manager-deployment.yaml + documentSelector: + path: kind + value: Deployment + set: + opensearch-operator: + manager: + replicaCount: 2 + asserts: + - equal: + path: spec.replicas + value: 2 + - equal: + path: spec.template.spec.containers[1].name + value: operator-controller-manager + - contains: + path: spec.template.spec.containers[1].args + content: --leader-elect + # Multi-replica installs keep the default RollingUpdate (no explicit + # strategy) so rollouts stay available. + - notExists: + path: spec.strategy diff --git a/packages/system/opensearch-operator/values.yaml b/packages/system/opensearch-operator/values.yaml index 137a7f5b3..956cc6894 100644 --- a/packages/system/opensearch-operator/values.yaml +++ b/packages/system/opensearch-operator/values.yaml @@ -1,5 +1,11 @@ opensearch-operator: manager: + # Number of operator replicas. Leader election is enabled automatically only + # when this is greater than 1. On a single replica leader election adds no HA + # value but lets a transient apiserver/etcd lease-renewal blip self-terminate + # the manager (controller-runtime exits on "leader election lost"), which + # surfaces at install time as a crashlooping, Failed Deployment. + replicaCount: 1 # Enable cluster-wide mode to watch all namespaces watchNamespace: "" kubeRbacProxy: From 0427b184fae3b77cbfc448ca060b66d8bebe98ce Mon Sep 17 00:00:00 2001 From: IvanHunters Date: Thu, 23 Apr 2026 18:32:37 +0300 Subject: [PATCH 23/38] feat(operator): support private OCI registry credentials for platform source Added --platform-source-secret flag to cozystack-operator that allows specifying a Secret name for registry authentication. This enables using private OCI registries and Git repositories as platform sources. Changes: - cmd/cozystack-operator/main.go: - Add --platform-source-secret flag - Pass secretName to generateOCIRepository() and generateGitRepository() - Set spec.secretRef in generated resources when secret is specified - packages/core/installer/values.yaml: - Add cozystackOperator.platformSourceSecret parameter - packages/core/installer/templates/cozystack-operator.yaml: - Pass --platform-source-secret flag to operator container Usage: 1. Create registry credentials secret in cozy-system namespace: kubectl create secret docker-registry my-registry-creds \ --namespace cozy-system \ --docker-server=registry.example.com \ --docker-username=user \ --docker-password=token 2. Configure Helm values: cozystackOperator: platformSourceUrl: 'oci://registry.example.com/repo/packages' platformSourceSecret: 'my-registry-creds' Closes #2455 Signed-off-by: IvanHunters --- cmd/cozystack-operator/main.go | 31 ++++++++++--- cmd/cozystack-operator/main_test.go | 44 ++++++++++++++++--- .../templates/cozystack-operator.yaml | 3 ++ packages/core/installer/values.yaml | 9 ++++ 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index 0a252dc7b..e389986d3 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -30,6 +30,7 @@ import ( cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1" helmv2 "github.com/fluxcd/helm-controller/api/v2" + fluxmeta "github.com/fluxcd/pkg/apis/meta" sourcev1 "github.com/fluxcd/source-controller/api/v1" sourcewatcherv1beta1 "github.com/fluxcd/source-watcher/api/v2/v1beta1" corev1 "k8s.io/api/core/v1" @@ -95,6 +96,7 @@ func main() { var platformSourceURL string var platformSourceName string var platformSourceRef string + var platformSourceSecret string flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") @@ -138,6 +140,7 @@ func main() { flag.StringVar(&platformSourceURL, "platform-source-url", "", "Platform source URL (oci:// or https://). If specified, generates OCIRepository or GitRepository resource.") flag.StringVar(&platformSourceName, "platform-source-name", "cozystack-platform", "Name for the generated platform source resource and PackageSource") flag.StringVar(&platformSourceRef, "platform-source-ref", "", "Reference specification as key=value pairs (e.g., 'branch=main' or 'digest=sha256:...,tag=v1.0'). For OCI: digest, semver, semverFilter, tag. For Git: branch, tag, semver, name, commit.") + flag.StringVar(&platformSourceSecret, "platform-source-secret", "", "Name of the Secret in cozy-system namespace containing credentials for the platform source. Sets spec.secretRef on the generated OCIRepository or GitRepository. Secret type depends on the source kind: kubernetes.io/dockerconfigjson for OCI (oci://...); Opaque with username+password for Git over HTTPS; Opaque with identity, identity.pub, known_hosts for Git over SSH.") flag.StringVar(&cozyValuesSecretName, "cozy-values-secret-name", "cozystack-values", "The name of the secret containing cluster-wide configuration values.") flag.StringVar(&cozyValuesSecretNamespace, "cozy-values-secret-namespace", "cozy-system", "The namespace of the secret containing cluster-wide configuration values.") flag.StringVar(&cozyValuesNamespaceSelector, "cozy-values-namespace-selector", "cozystack.io/system=true", "The label selector for namespaces where the cluster-wide configuration values must be replicated.") @@ -256,12 +259,12 @@ func main() { // Generate and install platform source resource if specified if platformSourceURL != "" { - setupLog.Info("Generating platform source resource", "url", platformSourceURL, "name", platformSourceName, "ref", platformSourceRef) + setupLog.Info("Generating platform source resource", "url", platformSourceURL, "name", platformSourceName, "ref", platformSourceRef, "secret", platformSourceSecret) installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute) defer installCancel() // Use direct client for pre-start operations (cache is not ready yet) - if err := installPlatformSourceResource(installCtx, directClient, platformSourceURL, platformSourceName, platformSourceRef); err != nil { + if err := installPlatformSourceResource(installCtx, directClient, platformSourceURL, platformSourceName, platformSourceRef, platformSourceSecret); err != nil { setupLog.Error(err, "failed to install platform source resource") os.Exit(1) } else { @@ -375,7 +378,7 @@ func main() { // installPlatformSourceResource generates and installs a Flux source resource (OCIRepository or GitRepository) // based on the platform source URL -func installPlatformSourceResource(ctx context.Context, k8sClient client.Client, sourceURL, resourceName, refSpec string) error { +func installPlatformSourceResource(ctx context.Context, k8sClient client.Client, sourceURL, resourceName, refSpec, secretName string) error { logger := log.FromContext(ctx) // Parse the source URL to determine type @@ -393,12 +396,12 @@ func installPlatformSourceResource(ctx context.Context, k8sClient client.Client, var obj client.Object switch sourceType { case "oci": - obj, err = generateOCIRepository(resourceName, repoURL, refMap) + obj, err = generateOCIRepository(resourceName, repoURL, refMap, secretName) if err != nil { return fmt.Errorf("failed to generate OCIRepository: %w", err) } case "git": - obj, err = generateGitRepository(resourceName, repoURL, refMap) + obj, err = generateGitRepository(resourceName, repoURL, refMap, secretName) if err != nil { return fmt.Errorf("failed to generate GitRepository: %w", err) } @@ -558,7 +561,7 @@ func validateGitRef(refMap map[string]string) error { } // generateOCIRepository creates an OCIRepository resource -func generateOCIRepository(name, repoURL string, refMap map[string]string) (*sourcev1.OCIRepository, error) { +func generateOCIRepository(name, repoURL string, refMap map[string]string, secretName string) (*sourcev1.OCIRepository, error) { if err := validateOCIRef(refMap); err != nil { return nil, err } @@ -588,11 +591,18 @@ func generateOCIRepository(name, repoURL string, refMap map[string]string) (*sou } } + // Set secretRef if secret name is provided + if secretName != "" { + obj.Spec.SecretRef = &fluxmeta.LocalObjectReference{ + Name: secretName, + } + } + return obj, nil } // generateGitRepository creates a GitRepository resource -func generateGitRepository(name, repoURL string, refMap map[string]string) (*sourcev1.GitRepository, error) { +func generateGitRepository(name, repoURL string, refMap map[string]string, secretName string) (*sourcev1.GitRepository, error) { if err := validateGitRef(refMap); err != nil { return nil, err } @@ -623,6 +633,13 @@ func generateGitRepository(name, repoURL string, refMap map[string]string) (*sou } } + // Set secretRef if secret name is provided + if secretName != "" { + obj.Spec.SecretRef = &fluxmeta.LocalObjectReference{ + Name: secretName, + } + } + return obj, nil } diff --git a/cmd/cozystack-operator/main_test.go b/cmd/cozystack-operator/main_test.go index f13a22d0d..faebfcf12 100644 --- a/cmd/cozystack-operator/main_test.go +++ b/cmd/cozystack-operator/main_test.go @@ -487,7 +487,7 @@ func TestValidateGitRef(t *testing.T) { func TestGenerateOCIRepository(t *testing.T) { refMap := map[string]string{"tag": "v1.0", "digest": "sha256:abc123"} - obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", refMap) + obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", refMap, "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -510,10 +510,13 @@ func TestGenerateOCIRepository(t *testing.T) { if obj.Spec.Reference.Digest != "sha256:abc123" { t.Errorf("expected digest %q, got %q", "sha256:abc123", obj.Spec.Reference.Digest) } + if obj.Spec.SecretRef != nil { + t.Errorf("expected SecretRef to be nil when secretName is empty, got %+v", obj.Spec.SecretRef) + } } func TestGenerateOCIRepository_NoRef(t *testing.T) { - obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{}) + obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{}, "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -523,15 +526,28 @@ func TestGenerateOCIRepository_NoRef(t *testing.T) { } func TestGenerateOCIRepository_InvalidRef(t *testing.T) { - _, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{"branch": "main"}) + _, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{"branch": "main"}, "") if err == nil { t.Fatal("expected error for invalid OCI ref key, got nil") } } +func TestGenerateOCIRepository_WithSecret(t *testing.T) { + obj, err := generateOCIRepository("my-repo", "oci://registry.example.com/repo", map[string]string{"tag": "v1.0"}, "my-registry-creds") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if obj.Spec.SecretRef == nil { + t.Fatal("expected SecretRef to be set when secretName is non-empty") + } + if obj.Spec.SecretRef.Name != "my-registry-creds" { + t.Errorf("expected SecretRef.Name %q, got %q", "my-registry-creds", obj.Spec.SecretRef.Name) + } +} + func TestGenerateGitRepository(t *testing.T) { refMap := map[string]string{"branch": "main", "commit": "abc1234def5678"} - obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", refMap) + obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", refMap, "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -554,10 +570,13 @@ func TestGenerateGitRepository(t *testing.T) { if obj.Spec.Reference.Commit != "abc1234def5678" { t.Errorf("expected commit %q, got %q", "abc1234def5678", obj.Spec.Reference.Commit) } + if obj.Spec.SecretRef != nil { + t.Errorf("expected SecretRef to be nil when secretName is empty, got %+v", obj.Spec.SecretRef) + } } func TestGenerateGitRepository_NoRef(t *testing.T) { - obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{}) + obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{}, "") if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -567,8 +586,21 @@ func TestGenerateGitRepository_NoRef(t *testing.T) { } func TestGenerateGitRepository_InvalidRef(t *testing.T) { - _, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{"digest": "sha256:abc"}) + _, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{"digest": "sha256:abc"}, "") if err == nil { t.Fatal("expected error for invalid Git ref key, got nil") } } + +func TestGenerateGitRepository_WithSecret(t *testing.T) { + obj, err := generateGitRepository("my-repo", "https://github.com/user/repo", map[string]string{"branch": "main"}, "my-git-creds") + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if obj.Spec.SecretRef == nil { + t.Fatal("expected SecretRef to be set when secretName is non-empty") + } + if obj.Spec.SecretRef.Name != "my-git-creds" { + t.Errorf("expected SecretRef.Name %q, got %q", "my-git-creds", obj.Spec.SecretRef.Name) + } +} diff --git a/packages/core/installer/templates/cozystack-operator.yaml b/packages/core/installer/templates/cozystack-operator.yaml index 95e6996da..12b8f1bf3 100644 --- a/packages/core/installer/templates/cozystack-operator.yaml +++ b/packages/core/installer/templates/cozystack-operator.yaml @@ -78,6 +78,9 @@ spec: {{- if .Values.cozystackOperator.platformSourceRef }} - --platform-source-ref={{ .Values.cozystackOperator.platformSourceRef }} {{- end }} + {{- if .Values.cozystackOperator.platformSourceSecret }} + - --platform-source-secret={{ .Values.cozystackOperator.platformSourceSecret }} + {{- end }} {{- if eq .Values.cozystackOperator.variant "talos" }} env: # Talos KubePrism endpoint diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index c0aadf19a..6d0e45d7f 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -29,6 +29,15 @@ cozystackOperator: # (operator default: 5). Number of release revisions Helm keeps per HR. # Use 0 for unlimited; lower values reduce Secret accumulation in test clusters. helmReleaseMaxHistory: "" + # Name of the Secret in cozy-system namespace containing credentials for the + # platform source. When non-empty, sets spec.secretRef on the generated + # OCIRepository or GitRepository resource. Secret type depends on source kind: + # OCI (oci://...): kubernetes.io/dockerconfigjson with registry credentials. + # Git over HTTPS: Opaque/generic secret with `username` + `password` keys. + # Git over SSH: Opaque/generic secret with `identity`, `identity.pub`, + # and `known_hosts` keys. + # See FluxCD source-controller docs for the exact key layout per scheme. + platformSourceSecret: '' # Generic variant configuration (only used when cozystackOperator.variant=generic) cozystack: # Kubernetes API server host (IP only, no protocol/port) From 52c20ad1a2dfd3553449c5cb0a97f014744b52bd Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 23:13:05 +0300 Subject: [PATCH 24/38] fix(linstor): set controller resource requests and relax liveness probe Under node CPU contention the linstor-controller JVM cannot answer its GET /health liveness probe within the piraeus-operator defaults (timeoutSeconds 1, failureThreshold 3). The kubelet then liveness-kills the container; the resulting crash-loop leaves the linstor-controller Service with no ready endpoints, linstor-csi gets EPERM, and RWX-Filesystem DataVolumes never bind, breaking tenant provisioning. The operator-generated controller pod sets no resources (no CPU request, hence no scheduling/CPU guarantee) and ships the tight default probe. Add CPU/memory requests and relax the liveness timing via the controller podTemplate, which piraeus-operator merges into the Deployment as a strategic merge patch. The probe override carries only the timing fields; the httpGet target (GET /health on the api port) is left to the operator to avoid pinning a wrong path or scheme. Requests-only (no limits) keeps the JVM from being throttled or OOM-killed. The knobs are exposed under controller.* for tuning to observed RSS. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../system/linstor/templates/cluster.yaml | 10 +++ .../system/linstor/tests/cluster_test.yaml | 75 +++++++++++++++++++ packages/system/linstor/values.yaml | 19 +++++ 3 files changed, 104 insertions(+) create mode 100644 packages/system/linstor/tests/cluster_test.yaml diff --git a/packages/system/linstor/templates/cluster.yaml b/packages/system/linstor/templates/cluster.yaml index ab349cf5d..4e94c80f9 100644 --- a/packages/system/linstor/templates/cluster.yaml +++ b/packages/system/linstor/templates/cluster.yaml @@ -39,6 +39,16 @@ spec: containers: - name: linstor-controller image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} + # Guarantee CPU/memory so the JVM keeps answering its /health probe + # under node contention. Requests only — see values.yaml controller.*. + resources: + {{- toYaml .Values.controller.resources | nindent 12 }} + # Partial override: piraeus-operator merges podTemplate as a strategic + # merge patch, so only these timing fields change. The operator's + # httpGet (GET /health on the api port) is preserved as-is. + livenessProbe: + timeoutSeconds: {{ .Values.controller.livenessProbe.timeoutSeconds }} + failureThreshold: {{ .Values.controller.livenessProbe.failureThreshold }} - name: plunger image: {{ .Values.piraeusServer.image.repository }}:{{ .Values.piraeusServer.image.tag }} command: diff --git a/packages/system/linstor/tests/cluster_test.yaml b/packages/system/linstor/tests/cluster_test.yaml new file mode 100644 index 000000000..4bf7ba094 --- /dev/null +++ b/packages/system/linstor/tests/cluster_test.yaml @@ -0,0 +1,75 @@ +suite: LinstorCluster controller resources and liveness hardening + +# Pins the controller-pod surface this package adds on top of the +# piraeus-operator defaults. The operator merges spec.controller.podTemplate +# into the generated linstor-controller Deployment as a strategic merge patch +# (containers merged by name, struct fields merged field-by-field), so: +# - resources.requests guarantee the JVM gets scheduled CPU/memory so it can +# answer GET /health under node CPU contention (no limits: avoid throttling +# / OOM-killing the JVM); +# - the livenessProbe override carries ONLY the timing fields (timeoutSeconds, +# failureThreshold) relaxed from the operator defaults (1s / 3) so a JVM GC +# pause or CPU contention does not trip a liveness kill. The httpGet target +# (GET /health on the api port) is intentionally NOT overridden — it is +# supplied by the operator, and re-stating it risks a wrong path/scheme. + +templates: + - templates/cluster.yaml + +tests: + - it: "linstor-controller container carries CPU and memory requests (no limits)" + asserts: + - isKind: + of: LinstorCluster + - equal: + path: spec.controller.podTemplate.spec.containers[0].name + value: linstor-controller + - equal: + path: spec.controller.podTemplate.spec.containers[0].resources.requests.cpu + value: 250m + - equal: + path: spec.controller.podTemplate.spec.containers[0].resources.requests.memory + value: 700Mi + - notExists: + path: spec.controller.podTemplate.spec.containers[0].resources.limits + + - it: "linstor-controller liveness probe relaxes timing but leaves httpGet to the operator" + asserts: + - equal: + path: spec.controller.podTemplate.spec.containers[0].livenessProbe.timeoutSeconds + value: 5 + - equal: + path: spec.controller.podTemplate.spec.containers[0].livenessProbe.failureThreshold + value: 6 + - notExists: + path: spec.controller.podTemplate.spec.containers[0].livenessProbe.httpGet + + - it: "resource and liveness knobs are values-driven" + set: + controller: + resources: + requests: + cpu: 500m + memory: 1Gi + livenessProbe: + timeoutSeconds: 10 + failureThreshold: 9 + asserts: + - equal: + path: spec.controller.podTemplate.spec.containers[0].resources.requests.cpu + value: 500m + - equal: + path: spec.controller.podTemplate.spec.containers[0].resources.requests.memory + value: 1Gi + - equal: + path: spec.controller.podTemplate.spec.containers[0].livenessProbe.timeoutSeconds + value: 10 + - equal: + path: spec.controller.podTemplate.spec.containers[0].livenessProbe.failureThreshold + value: 9 + + - it: "controller image pin is preserved alongside the new fields" + asserts: + - matchRegex: + path: spec.controller.podTemplate.spec.containers[0].image + pattern: "^ghcr\\.io/cozystack/cozystack/piraeus-server:" diff --git a/packages/system/linstor/values.yaml b/packages/system/linstor/values.yaml index 09cf14cda..6ff6b2382 100644 --- a/packages/system/linstor/values.yaml +++ b/packages/system/linstor/values.yaml @@ -14,3 +14,22 @@ linstorCSI: image: repository: ghcr.io/cozystack/cozystack/linstor-csi tag: v1.5.0@sha256:c60519d12aa8d078e4891d6e436c709ff53f2cc96457994390ca9424b6505903 +controller: + # Resource requests for the linstor-controller JVM. Requests (not limits) are + # set on purpose: a CPU request guarantees CPU shares so the JVM can answer its + # GET /health liveness probe under node CPU contention, while omitting limits + # avoids throttling the JVM (CPU limit) or OOM-killing it (memory limit). The + # memory value is a conservative starting estimate for the JVM heap + overhead; + # tune it to observed RSS. + resources: + requests: + cpu: 250m + memory: 700Mi + # Liveness-probe timing for the linstor-controller container. Only these timing + # fields are overridden; the httpGet target (GET /health on the api port) is + # supplied by piraeus-operator and intentionally left untouched. Relaxed from + # the operator defaults (timeoutSeconds 1, failureThreshold 3) so a JVM GC + # pause or CPU contention does not trip a kubelet liveness kill. + livenessProbe: + timeoutSeconds: 5 + failureThreshold: 6 From 7c519ad079cc4b0e358ae2f41cafe63c0d119e79 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 22:49:56 +0300 Subject: [PATCH 25/38] ci(build): route PR base-image pulls through mirror.gcr.io The Pull Request Build job authenticates only to the push registry (OCIR), so every base image (golang, ubuntu, alpine, nginx, ...) is pulled from Docker Hub anonymously. Those pulls intermittently fail at auth.docker.io/token under Docker Hub's anonymous-pull rate limit, leaving the base image unresolved and failing make build. Build on a docker-container buildx builder whose buildkitd routes docker.io through mirror.gcr.io, Google's public pull-through cache for Docker Hub: no auth, far higher limits, transparent fall-through to Docker Hub on a miss. This reuses the builder pattern already proven in build-main.yaml (same make build, same DOCKER_CONFIG auth forwarding); only the committed hack/buildkitd.toml mirror config is new. Fail-safe: if mirror.gcr.io is unreachable, BuildKit falls back to registry-1.docker.io and the build still succeeds, so this strictly reduces build failures without adding a new hard dependency. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .github/workflows/pull-requests.yaml | 22 ++++++++++++++++++++++ hack/buildkitd.toml | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 hack/buildkitd.toml diff --git a/.github/workflows/pull-requests.yaml b/.github/workflows/pull-requests.yaml index 52faceb15..032ff40d7 100644 --- a/.github/workflows/pull-requests.yaml +++ b/.github/workflows/pull-requests.yaml @@ -99,10 +99,32 @@ jobs: env: DOCKER_CONFIG: ${{ runner.temp }}/.docker + # Build on a docker-container buildx builder whose buildkitd routes + # docker.io base-image pulls through mirror.gcr.io (hack/buildkitd.toml). + # The default embedded `docker` driver pulls base images from Docker Hub + # anonymously and intermittently trips its anonymous-pull rate limit + # (auth.docker.io/token failures leaving golang/ubuntu/nginx unresolved); + # the mirror sidesteps that and falls back to Docker Hub on a miss/outage. + # Mirrors build-main.yaml: create the builder under the SAME DOCKER_CONFIG + # the Build step reads (runner.temp/.docker, holding the OCIR auth) so + # buildx forwards push/cache auth to the container builder. + - name: Set up Buildx (docker-container driver, docker.io mirror) + id: buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 + with: + driver: docker-container + buildkitd-config: hack/buildkitd.toml + env: + DOCKER_CONFIG: ${{ runner.temp }}/.docker + - name: Build run: make build env: DOCKER_CONFIG: ${{ runner.temp }}/.docker + # Build on the docker-container builder created above so base-image + # pulls go through the docker.io mirror (BUILDX_ARGS adds + # --builder=$(BUILDER)). + BUILDER: ${{ steps.buildx.outputs.name }} # Unique per (PR, commit) so concurrent PR builds never push the same # tag. PUBLISH_VERSIONED/PUBLISH_FLOATING stay off — PR images are # consumed by digest, and :latest belongs to release builds. diff --git a/hack/buildkitd.toml b/hack/buildkitd.toml new file mode 100644 index 000000000..dc6e2d696 --- /dev/null +++ b/hack/buildkitd.toml @@ -0,0 +1,21 @@ +# BuildKit daemon config for CI builds. +# +# Route docker.io base-image pulls through mirror.gcr.io, Google's public +# pull-through cache for Docker Hub. The Build job authenticates only to the +# push registry (OCIR), so every base image (golang, alpine, ubuntu, nginx, ...) +# is otherwise pulled from Docker Hub ANONYMOUSLY and is subject to Docker Hub's +# anonymous-pull rate limit -- which intermittently fails the build at +# auth.docker.io/token with the base image left unresolved. +# +# mirror.gcr.io needs no auth, carries far higher limits, and transparently +# falls through to Docker Hub on a cache miss. This config only takes effect on +# a buildx builder created with `--config hack/buildkitd.toml` (CI wires this via +# docker/setup-buildx-action `buildkitd-config:`); a plain local `make build` on +# the default builder is unaffected. +# +# FAIL-SAFE: if mirror.gcr.io is unreachable, BuildKit logs "trying next host" +# and falls back to registry-1.docker.io, so the build still succeeds (verified: +# a build pointed at a non-resolving mirror fell back to Docker Hub and exited 0). +# This strictly reduces build failures; it never introduces a new hard dependency. +[registry."docker.io"] + mirrors = ["mirror.gcr.io"] From fff9e827d194c7d255cac8612223508fbe28208f Mon Sep 17 00:00:00 2001 From: Ivan Okhotnikov Date: Wed, 24 Jun 2026 23:22:54 +0300 Subject: [PATCH 26/38] fix(operator): address review feedback on private platform source - Run `go mod tidy` to move github.com/fluxcd/pkg/apis/meta from indirect to direct require: it became a direct import after the SecretRef type fix. - Refine the --platform-source-secret flag docstring and the matching values.yaml comment: drop identity.pub from the SSH required-keys list (Flux source-controller only consumes identity + known_hosts) and mention bearerToken as an alternative to username+password for Git over HTTPS. - Note in values.yaml that the Secret must exist in cozy-system before the operator reconciles; otherwise source-controller reports a failed condition until the Secret is created. Signed-off-by: Ivan Okhotnikov --- cmd/cozystack-operator/main.go | 2 +- go.mod | 2 +- packages/core/installer/values.yaml | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index e389986d3..1da7a011b 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -140,7 +140,7 @@ func main() { flag.StringVar(&platformSourceURL, "platform-source-url", "", "Platform source URL (oci:// or https://). If specified, generates OCIRepository or GitRepository resource.") flag.StringVar(&platformSourceName, "platform-source-name", "cozystack-platform", "Name for the generated platform source resource and PackageSource") flag.StringVar(&platformSourceRef, "platform-source-ref", "", "Reference specification as key=value pairs (e.g., 'branch=main' or 'digest=sha256:...,tag=v1.0'). For OCI: digest, semver, semverFilter, tag. For Git: branch, tag, semver, name, commit.") - flag.StringVar(&platformSourceSecret, "platform-source-secret", "", "Name of the Secret in cozy-system namespace containing credentials for the platform source. Sets spec.secretRef on the generated OCIRepository or GitRepository. Secret type depends on the source kind: kubernetes.io/dockerconfigjson for OCI (oci://...); Opaque with username+password for Git over HTTPS; Opaque with identity, identity.pub, known_hosts for Git over SSH.") + flag.StringVar(&platformSourceSecret, "platform-source-secret", "", "Name of the Secret in cozy-system namespace containing credentials for the platform source. Sets spec.secretRef on the generated OCIRepository or GitRepository. Secret type depends on the source kind: kubernetes.io/dockerconfigjson for OCI (oci://...); Opaque with username+password (or bearerToken) for Git over HTTPS; Opaque with identity (PEM private key) and known_hosts for Git over SSH.") flag.StringVar(&cozyValuesSecretName, "cozy-values-secret-name", "cozystack-values", "The name of the secret containing cluster-wide configuration values.") flag.StringVar(&cozyValuesSecretNamespace, "cozy-values-secret-namespace", "cozy-system", "The namespace of the secret containing cluster-wide configuration values.") flag.StringVar(&cozyValuesNamespaceSelector, "cozy-values-namespace-selector", "cozystack.io/system=true", "The label selector for namespaces where the cluster-wide configuration values must be replicated.") diff --git a/go.mod b/go.mod index 8f4e7133c..6ca35dcf3 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/cozystack/cozystack-scheduler/pkg/apis v0.1.1 github.com/emicklei/dot v1.10.0 github.com/fluxcd/helm-controller/api v1.4.3 + github.com/fluxcd/pkg/apis/meta v1.23.0 github.com/fluxcd/source-controller/api v1.7.4 github.com/fluxcd/source-watcher/api/v2 v2.0.3 github.com/go-logr/logr v1.4.3 @@ -55,7 +56,6 @@ require ( github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fluxcd/pkg/apis/acl v0.9.0 // indirect github.com/fluxcd/pkg/apis/kustomize v1.13.0 // indirect - github.com/fluxcd/pkg/apis/meta v1.23.0 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect diff --git a/packages/core/installer/values.yaml b/packages/core/installer/values.yaml index 6d0e45d7f..90ed698d3 100644 --- a/packages/core/installer/values.yaml +++ b/packages/core/installer/values.yaml @@ -33,9 +33,10 @@ cozystackOperator: # platform source. When non-empty, sets spec.secretRef on the generated # OCIRepository or GitRepository resource. Secret type depends on source kind: # OCI (oci://...): kubernetes.io/dockerconfigjson with registry credentials. - # Git over HTTPS: Opaque/generic secret with `username` + `password` keys. - # Git over SSH: Opaque/generic secret with `identity`, `identity.pub`, - # and `known_hosts` keys. + # Git over HTTPS: Opaque secret with `username`+`password`, or `bearerToken`. + # Git over SSH: Opaque secret with `identity` (PEM private key) and `known_hosts`. + # The Secret must already exist in cozy-system before the operator reconciles; + # otherwise source-controller will report a failed condition until it is created. # See FluxCD source-controller docs for the exact key layout per scheme. platformSourceSecret: '' # Generic variant configuration (only used when cozystackOperator.variant=generic) From 02eeb9e0d5b7cd1a872f8d3602e0244c0c1ecd78 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 23:36:42 +0300 Subject: [PATCH 27/38] ci(e2e): keep the cozystack-engine ordering edge out of test-impact selection Every *-application source declares dependsOn cozystack.cozystack-engine so its *-rd HelmRelease waits for the ApplicationDefinition CRD to register before it reconciles. That is a universal install-ordering edge, not a behavioral one. The test-impact selector reused the same dependsOn graph to pick which e2e app suites to run for a diff. Because the engine transitively depends on postgres-operator, keycloak and cert-manager, a change to any of those fanned out through the engine to every app, defeating test-impact analysis. Exclude cozystack.cozystack-engine as a propagation hub in the reverse-dep walk so the engine stays reachable but no longer pushes selection to its app-dependents. A change to the engine itself still triggers the full suite via the existing no-app-descendants safety net. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/select-e2e.sh | 15 ++++++++++++++- hack/select-e2e_test.bats | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/hack/select-e2e.sh b/hack/select-e2e.sh index eb169b01a..2aed5147f 100755 --- a/hack/select-e2e.sh +++ b/hack/select-e2e.sh @@ -43,8 +43,21 @@ build_owners_index() { } # yq: PackageSource name -> sources that depend on it (reverse of dependsOn) +# +# Exclude cozystack.cozystack-engine as a propagation hub. Every *-application +# source declares `dependsOn: cozystack.cozystack-engine` purely as an INSTALL +# ORDERING edge — the app's *-rd HelmRelease must wait for the engine to +# register the ApplicationDefinition CRD before it can reconcile. That is a +# universal lifecycle dependency, NOT a behavioral one: a change to an engine +# dependency (postgres-operator, keycloak, cert-manager, ...) does not alter any +# app's runtime behavior, so it must not fan test selection out to every app. +# Without this filter, postgres-operator -> keycloak -> cozystack-engine -> EVERY +# app, defeating test-impact analysis. Dropping the engine reverse edges keeps +# the engine reachable but stops it propagating selection downstream. A change to +# the engine itself still triggers the full suite via the no-app-descendants +# safety-net at the bottom of this script. build_reverse_deps() { - yq -rN '.metadata.name as $n | .spec.variants[]?.dependsOn[]? | select(. != null and . != "") | . + "\t" + $n' "$SOURCES_DIR"/*.yaml + yq -rN '.metadata.name as $n | .spec.variants[]?.dependsOn[]? | select(. != null and . != "" and . != "cozystack.cozystack-engine") | . + "\t" + $n' "$SOURCES_DIR"/*.yaml } OWNERS=$(build_owners_index | sort -u) diff --git a/hack/select-e2e_test.bats b/hack/select-e2e_test.bats index f427e8034..0732acd28 100644 --- a/hack/select-e2e_test.bats +++ b/hack/select-e2e_test.bats @@ -37,6 +37,26 @@ fi } +@test "engine-dependency change does not fan out via the ordering edge" { + # cert-manager is a dependency of cozystack-engine, and every app declares + # dependsOn cozystack-engine purely as an INSTALL-ORDERING edge (the app's + # *-rd HelmRelease waits for the ApplicationDefinition CRD). That edge must + # not propagate test selection: a cert-manager change selects only its + # genuine direct dependents (postgres, harbor, ...), never unrelated apps + # like kafka that reach cert-manager solely through the engine. + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + cp -r packages/core/platform/sources "$tmp/sources" + echo "packages/system/cert-manager/values.yaml" > "$tmp/diff" + output=$(hack/select-e2e.sh "$tmp/diff" "$tmp/sources") + echo "$output" | grep -wq postgres + echo "$output" | grep -wq harbor + if echo "$output" | grep -wq kafka; then + echo "cert-manager change must not fan out via engine; got: $output" >&2 + exit 1 + fi +} + @test "networking change triggers full suite" { tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT From 45a273eba57fc0b6dc5e2926811d25ce7b5a458b Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 24 Jun 2026 23:38:38 +0300 Subject: [PATCH 28/38] fix(monitoring-agents): relax fluent-bit probes for loaded installs The fluent-bit DaemonSet crash-loops on a loaded install, timing out its HelmRelease and cascading into velero and backupstrategy-controller DependencyNotReady failures. The subchart default liveness/readiness probes (initialDelaySeconds 0, timeoutSeconds 1, failureThreshold 3) target the HTTP server on :2020, but that server does not bind until AFTER the kubernetes filter's 'testing connectivity with API server' step. Under a concurrent install the kube-API can be unreachable for ~10s, delaying the :2020 bind, while the single-threaded HTTP server can miss a 1s timeout when the engine is busy. The probes then fire into a connection-refused port and liveness-kill fluent-bit into a restart loop. Give both probes load headroom (initialDelaySeconds 15, timeoutSeconds 5, failureThreshold 6) so the API-gated late bind and busy-engine blips are tolerated. The httpGet target is unchanged (path / on port http = :2020). Add a helm-unittest pinning the relaxed timing and the preserved target. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- .../tests/fluent-bit_probes_test.yaml | 53 +++++++++++++++++++ packages/system/monitoring-agents/values.yaml | 19 +++++++ 2 files changed, 72 insertions(+) create mode 100644 packages/system/monitoring-agents/tests/fluent-bit_probes_test.yaml diff --git a/packages/system/monitoring-agents/tests/fluent-bit_probes_test.yaml b/packages/system/monitoring-agents/tests/fluent-bit_probes_test.yaml new file mode 100644 index 000000000..3816091a6 --- /dev/null +++ b/packages/system/monitoring-agents/tests/fluent-bit_probes_test.yaml @@ -0,0 +1,53 @@ +suite: fluent-bit liveness/readiness probes carry load headroom + +# Regression guard for the fluent-bit crash loop on a loaded install. The +# subchart default probes (initialDelaySeconds 0, timeoutSeconds 1, +# failureThreshold 3) fire into a connection-refused :2020 before fluent-bit's +# HTTP server binds — that bind happens only AFTER the kubernetes filter's +# "testing connectivity with API server" step, which the kube-API can gate for +# ~10s under a concurrent install. The aggressive defaults then liveness-kill +# fluent-bit into a restart loop, timing out the HelmRelease. The umbrella +# values relax both probes. Pin the relaxed timing AND the unchanged target +# (httpGet path / on port http = :2020) — a wrong path/port would make the +# probe always fail, strictly worse than the bug it fixes. +release: + name: monitoring-agents + namespace: cozy-monitoring +tests: + - it: liveness probe keeps :2020 target and gains load headroom + template: charts/fluent-bit/templates/daemonset.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.path + value: / + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet.port + value: http + - equal: + path: spec.template.spec.containers[0].livenessProbe.initialDelaySeconds + value: 15 + - equal: + path: spec.template.spec.containers[0].livenessProbe.periodSeconds + value: 10 + - equal: + path: spec.template.spec.containers[0].livenessProbe.timeoutSeconds + value: 5 + - equal: + path: spec.template.spec.containers[0].livenessProbe.failureThreshold + value: 6 + + - it: readiness probe keeps :2020 target and gains load headroom + template: charts/fluent-bit/templates/daemonset.yaml + asserts: + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.path + value: / + - equal: + path: spec.template.spec.containers[0].readinessProbe.httpGet.port + value: http + - equal: + path: spec.template.spec.containers[0].readinessProbe.timeoutSeconds + value: 5 + - equal: + path: spec.template.spec.containers[0].readinessProbe.failureThreshold + value: 6 diff --git a/packages/system/monitoring-agents/values.yaml b/packages/system/monitoring-agents/values.yaml index bcdee0e0b..4357db276 100644 --- a/packages/system/monitoring-agents/values.yaml +++ b/packages/system/monitoring-agents/values.yaml @@ -287,9 +287,28 @@ vmagent: fluent-bit: rbac: eventsAccess: true + # Relax probe timing to survive a loaded install. Fluent-bit's HTTP server on + # :2020 (the probe target) does not bind until AFTER the kubernetes filter's + # "testing connectivity with API server" step; under concurrent install the + # kube-API can be unreachable for ~10s, delaying the :2020 bind, and the + # single-threaded HTTP server can miss a 1s timeout while the engine is busy. + # The subchart defaults (initialDelaySeconds 0, timeoutSeconds 1, + # failureThreshold 3) then liveness-kill fluent-bit into a crash loop. httpGet + # stays on path / port http (:2020) — same target as the subchart defaults. + livenessProbe: + httpGet: + path: / + port: http + initialDelaySeconds: 15 + periodSeconds: 10 + timeoutSeconds: 5 + failureThreshold: 6 readinessProbe: httpGet: path: / + port: http + timeoutSeconds: 5 + failureThreshold: 6 daemonSetVolumes: - name: varlog hostPath: From 079e4079d3ffc7b343a0cf98fda0700bece69e53 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 25 Jun 2026 02:08:26 +0300 Subject: [PATCH 29/38] test(e2e): capture host->pod data-plane on failure The E2E install intermittently fails on a CNI host->local-pod data-plane transient: kubelet on a node reaches a local pod's readiness/startup probe with "connection refused" for several minutes while overlay pod->pod traffic works, then it self-heals. This is rooted in the cilium+kube-ovn chaining config (forced enable-host-legacy-routing, CNI InstallEndpointRoute:false), which delegates host->local-pod routing to kube-ovn/ovn0. The crust-gather failure snapshot records Kubernetes object state but not the node's L3 forwarding state, so the mechanism cannot be root-caused after the fact. On test failure, for each NotReady pod that is scheduled, has a podIP, and is not terminal (Succeeded/Failed excluded), collect the host->pod data-plane state from the pod's node into the crust-gather snapshot dir (so it ships in the uploaded report): - host netns via the kube-ovn cni-server (hostNetwork): ip route get , ip neigh, ip rule, ip addr show ovn0, and the KERNEL conntrack entries for the podIP (conntrack -L, falling back to /proc/net/nf_conntrack). Under enable-host-legacy-routing the host->local-pod path traverses the kernel netfilter stack, not cilium BPF, so this is the authoritative conntrack table and route/rule state for the transient; - cilium-agent on the node: endpoint list, bpf CT entries for the podIP, a bounded monitor --type drop, and hubble dropped verdicts when present (complementary: confirms the path is not handled in BPF); - OVS/OVN on the node: ovs-ofctl dump-flows br-int, the OVN Port_Binding / Logical_Switch_Port for the pod, and a bounded ovn-sbctl lflow-list. Diagnostic-only: it runs after the existing crust-gather collect and only on an already-failed test, never mutates the cluster, and cannot change the pass/fail outcome. Every live capture is time-boxed, every command is || true, the whole run has a wall-clock backstop, node-global captures are deduped per node, and the pod count is capped. It no-ops cleanly when no pods are affected or when a tool/pod is absent. Assisted-By: Claude Signed-off-by: Aleksei Sviridkin --- hack/cozytest.sh | 34 +++-- hack/e2e-capture-dataplane.sh | 248 ++++++++++++++++++++++++++++++++++ 2 files changed, 273 insertions(+), 9 deletions(-) create mode 100755 hack/e2e-capture-dataplane.sh diff --git a/hack/cozytest.sh b/hack/cozytest.sh index 6a7cf9891..28a43a298 100755 --- a/hack/cozytest.sh +++ b/hack/cozytest.sh @@ -95,17 +95,33 @@ TMP_SH=$(mktemp) || { echo "Failed to create temp file" >&2; exit 1; } COZY_REPORT_DIR="${COZY_REPORT_DIR:-_out/cozyreport}" _cozy_on_exit() { _rc=$? - if [ "$_rc" -ne 0 ] && command -v crust-gather >/dev/null 2>&1; then + if [ "$_rc" -ne 0 ]; then _snap="$COZY_REPORT_DIR/snapshots/$(basename "$TEST_FILE" .bats)" mkdir -p "$_snap" 2>/dev/null || true - echo "» capturing crust-gather snapshot of failed $(basename "$TEST_FILE") -> $_snap" - # Bound with a timeout: crust-gather collect has hung indefinitely on a - # contended/degraded cluster (e.g. streaming logs from a crashlooping pod), - # wedging the whole test step for hours until the job-level cancel. 5 min is - # ample for a host snapshot; a partial capture (timeout exits 124, swallowed - # by `|| true`) still beats a multi-hour hang. -k 30 hard-kills if a blocked - # collect ignores the SIGTERM. - timeout -k 30 300 crust-gather collect --exclude-kind Secret -f "$_snap/host" >/dev/null 2>&1 || true + if command -v crust-gather >/dev/null 2>&1; then + echo "» capturing crust-gather snapshot of failed $(basename "$TEST_FILE") -> $_snap" + # Bound with a timeout: crust-gather collect has hung indefinitely on a + # contended/degraded cluster (e.g. streaming logs from a crashlooping pod), + # wedging the whole test step for hours until the job-level cancel. 5 min is + # ample for a host snapshot; a partial capture (timeout exits 124, swallowed + # by `|| true`) still beats a multi-hour hang. -k 30 hard-kills if a blocked + # collect ignores the SIGTERM. + timeout -k 30 300 crust-gather collect --exclude-kind Secret -f "$_snap/host" >/dev/null 2>&1 || true + fi + # Diagnostic-only: capture the host->pod CNI data-plane state for any + # NotReady pod so the recurrent host->local-pod "connection refused" + # transient (rooted in our cilium+kube-ovn chaining: + # enable-host-legacy-routing + CNI InstallEndpointRoute:false, which + # delegates host->local-pod routing to kube-ovn/ovn0) can be root-caused + # from the uploaded artifact. crust-gather captures object state but not the + # node's L3 forwarding state. This NEVER affects the test outcome: every + # capture is time-boxed and `|| true`, and the whole run is wrapped in a + # wall-clock backstop so it cannot stall the job. It no-ops when there are no + # affected pods or when kubectl/the tooling is absent. + if command -v kubectl >/dev/null 2>&1; then + echo "» capturing host->pod data-plane for NotReady pods -> $_snap/dataplane" + timeout -k 30 600 "$(dirname "$0")/e2e-capture-dataplane.sh" "$_snap/dataplane" 2>&1 || true + fi fi if command -v cozy_cleanup >/dev/null 2>&1; then echo "» cozy_cleanup $(basename "$TEST_FILE" .bats)" diff --git a/hack/e2e-capture-dataplane.sh b/hack/e2e-capture-dataplane.sh new file mode 100755 index 000000000..a6b6c57fd --- /dev/null +++ b/hack/e2e-capture-dataplane.sh @@ -0,0 +1,248 @@ +#!/bin/sh +# e2e-capture-dataplane.sh - capture host->pod data-plane state for NotReady pods. +# +# DIAGNOSTIC ONLY. This script collects extra evidence on an already-failed +# test; it never mutates the cluster and never changes the test's pass/fail +# outcome. cozytest.sh invokes it from its on-failure hook, AFTER the +# crust-gather snapshot and only when a test has already failed, writing into +# the same snapshot dir so the output lands in the uploaded cozyreport artifact. +# +# Why this exists: a recurrent install failure is a CNI host->local-pod +# data-plane transient -- kubelet on a node reaches a *local* pod's +# readiness/startup probe with "connection refused" for several minutes while +# overlay (pod->pod) traffic works, then it self-heals. This is rooted in the +# cozystack cilium+kube-ovn chaining config (forced enable-host-legacy-routing, +# CNI InstallEndpointRoute:false -> host->local-pod routing is delegated to +# kube-ovn/ovn0). The standard crust-gather snapshot captures Kubernetes object +# state but NOT the L3 forwarding state on the node, so the mechanism cannot be +# root-caused after the fact. This collects exactly that state from the pod's +# node so the next recurrence is dispositive. +# +# What it captures, per affected pod (NotReady, scheduled, has a podIP): +# - cilium-agent on the node: cilium-dbg endpoint list; bpf ct entries for +# the podIP; a short bounded `cilium-dbg monitor --type drop`; hubble +# dropped-verdict observations (if the hubble CLI is present in the agent). +# NOTE: under enable-host-legacy-routing the host->local-pod path traverses +# the KERNEL netfilter stack, not cilium BPF, so the cilium CT/monitor view +# is complementary -- the authoritative conntrack table for this path is the +# kernel one captured below. Both are kept: cilium's view still covers +# pod->pod and confirms the path is NOT in BPF, which is itself evidence. +# - host netns on the node (via the kube-ovn cni-server, which is +# hostNetwork+NET_ADMIN): `ip route get `, `ip neigh`, `ip rule`, +# `ip addr show ovn0`, and the KERNEL conntrack entries for the podIP +# (`conntrack -L`, falling back to /proc/net/nf_conntrack). These reveal a +# kernel-path misforward / missing route / wrong rule / unresolved neighbor +# -- the actual mechanism behind host->local-pod "connection refused" on the +# legacy-routing path (the crux of the transient). +# - OVS/OVN on the node: `ovs-ofctl dump-flows br-int`, and the OVN +# Port_Binding / Logical_Switch_Port for the pod (LSP name = . in +# kube-ovn) plus a bounded `ovn-sbctl lflow-list`. +# +# Robustness contract (matches docs/agents/e2e-testing.md): pure diagnostics, +# no retries, no behavior change, no traps. Every live capture is time-boxed +# and every command is `|| true`, so a missing tool, an absent pod, or a hung +# exec can never fail or stall the job. A wall-clock backstop wraps the whole +# run at the call site. It no-ops cleanly when there are no affected pods. +set -u + +OUT="${1:?Usage: e2e-capture-dataplane.sh }" + +CILIUM_NS="${COZY_CILIUM_NS:-cozy-cilium}" +KUBEOVN_NS="${COZY_KUBEOVN_NS:-cozy-kubeovn}" +# Cap how many pods we inspect so a fully-wedged cluster cannot explode the +# runtime; the per-command timeouts and the call-site wall-clock wrapper are the +# other two bounds. Node-global captures are deduped per node, so the effective +# work is closer to (#affected-nodes) than (#affected-pods). +MAX_PODS="${COZY_DATAPLANE_MAX_PODS:-12}" + +command -v kubectl >/dev/null 2>&1 || exit 0 +mkdir -p "$OUT" 2>/dev/null || exit 0 + +log() { echo "[capture-dataplane] $*"; } + +# Affected = scheduled (has nodeName), has a podIP (so an endpoint exists to +# inspect), Ready!=True, and not already terminal. That is the superset of the +# "readiness/startup probe failing with connection refused" symptom -- a pod +# that is Running with an IP but whose Ready condition is False is exactly the +# transient's signature. Succeeded/Failed pods (completed Jobs, hook pods) are +# Ready!=True too but are not the symptom, so they are excluded to keep the +# MAX_PODS budget on Running/Pending pods that are actually wedged. +# jsonpath keeps this dependency-free (no jq / go-template reassignment). +affected=$(kubectl get pods -A \ + -o jsonpath='{range .items[*]}{.metadata.namespace}{"|"}{.metadata.name}{"|"}{.status.podIP}{"|"}{.spec.nodeName}{"|"}{.status.conditions[?(@.type=="Ready")].status}{"|"}{.status.phase}{"\n"}{end}' \ + 2>/dev/null | awk -F'|' '$3!="" && $4!="" && $5!="True" && $6!="Succeeded" && $6!="Failed"') + +if [ -z "$affected" ]; then + log "no NotReady pods with a podIP+node -- nothing to capture" + exit 0 +fi + +ncount=$(printf '%s\n' "$affected" | wc -l | tr -d ' ') +log "capturing host->pod data-plane for up to $MAX_PODS of $ncount affected pod(s) -> $OUT" + +# OVN southbound logical-flow dump is cluster-global, so capture it once rather +# than per pod. ovn-central is a Deployment (not per-node); any replica answers. +# --no-leader-only lets a read land on a raft follower instead of erroring. +central=$(kubectl get pod -n "$KUBEOVN_NS" -l app=ovn-central \ + -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) +if [ -n "$central" ]; then + { + echo "=== ovn-sbctl lflow-list (cluster-global, pod=$central) ===" + timeout 30 kubectl exec -n "$KUBEOVN_NS" "$central" -c ovn-central -- \ + ovn-sbctl --no-leader-only lflow-list 2>&1 || true + } > "$OUT/ovn-lflows.txt" 2>&1 || true +else + log "no ovn-central pod in $KUBEOVN_NS -- skipping OVN logical-flow dump" +fi + +# pod_on_node