From bb1127c7679e71eef34a20a750c04156b46a8efb Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 17 Jul 2026 14:20:56 +0400 Subject: [PATCH 1/5] test(sdn): drop the serviceexposure chainsaw suite left after the feature removal Commit 3dbc7d366 removed ExposureClass/ServiceExposure (api/network, the controller, and the CRDs shipped in cozystack-controller/definitions) in favor of the native LoadBalancer path, but the chainsaw port that landed the next day (dd2f89d20) brought the serviceexposure suite along. The suite creates network.cozystack.io/v1alpha1 fixtures against an API that no longer exists, so every full-suite run (any PR touching core paths, and every release cut) fails its two tests with "no matches for network.cozystack.io/v1alpha1". Remove the dead suite and its wiring: the suite_to_source case and its core-platform bats test in select-install (the "-" mechanism stays for the next core-platform-owned suite), and the README suite list. Co-Authored-By: Claude Fable 5 Signed-off-by: Andrey Kolkov --- hack/e2e-chainsaw/README.md | 2 +- .../serviceexposure/chainsaw-test.yaml | 120 ------------------ .../serviceexposure/serviceexposure.yaml | 37 ------ hack/select-install.sh | 7 +- hack/select-install_test.bats | 8 -- 5 files changed, 4 insertions(+), 170 deletions(-) delete mode 100644 hack/e2e-chainsaw/serviceexposure/chainsaw-test.yaml delete mode 100644 hack/e2e-chainsaw/serviceexposure/serviceexposure.yaml diff --git a/hack/e2e-chainsaw/README.md b/hack/e2e-chainsaw/README.md index 6827716b3..0e7ac4729 100644 --- a/hack/e2e-chainsaw/README.md +++ b/hack/e2e-chainsaw/README.md @@ -18,7 +18,7 @@ hack/e2e-chainsaw/ └── ... ``` -Suites: `postgres`, `bucket`, `mariadb`, `mongodb`, `redis`, `qdrant`, `clickhouse`, `kafka`, `etcd`, `openbao`, `harbor`, `foundationdb`, `external-dns`, `kuberture`, `vminstance`, `gateway`, `kubernetes-latest`, `kubernetes-previous`, `kubernetes-oidc-system`, `kubernetes-oidc-customconfig`, `securitygroup`, `serviceexposure`. +Suites: `postgres`, `bucket`, `mariadb`, `mongodb`, `redis`, `qdrant`, `clickhouse`, `kafka`, `etcd`, `openbao`, `harbor`, `foundationdb`, `external-dns`, `kuberture`, `vminstance`, `gateway`, `kubernetes-latest`, `kubernetes-previous`, `kubernetes-oidc-system`, `kubernetes-oidc-customconfig`, `securitygroup`. ## What Chainsaw buys over the BATS suite diff --git a/hack/e2e-chainsaw/serviceexposure/chainsaw-test.yaml b/hack/e2e-chainsaw/serviceexposure/chainsaw-test.yaml deleted file mode 100644 index 888501110..000000000 --- a/hack/e2e-chainsaw/serviceexposure/chainsaw-test.yaml +++ /dev/null @@ -1,120 +0,0 @@ -# Chainsaw port of hack/e2e-apps/serviceexposure.bats. -# -# Smoke test for the ServiceExposure controller (network.cozystack.io): the -# CRDs are installed, the controller resolves a ServiceExposure to its -# ExposureClass, renders NO foreign backend CRs for the externalIPs backend, -# and reports status. See serviceexposure.yaml for why externalIPs. -# -# Both Tests reuse the same fixture names — safe under the global -# execution.parallel: 1 (the first Test's auto-cleanup completes before the -# second starts). -apiVersion: chainsaw.kyverno.io/v1alpha1 -kind: Test -metadata: - name: serviceexposure-externalips -spec: - catch: - - events: {} - - describe: - apiVersion: network.cozystack.io/v1alpha1 - kind: ServiceExposure - name: se-e2e - - get: - apiVersion: network.cozystack.io/v1alpha1 - kind: ExposureClass - name: se-e2e-externalips - - podLogs: - namespace: cozy-system - selector: app=cozystack-controller - tail: 100 - steps: - - name: create-fixtures - try: - - apply: - file: serviceexposure.yaml - - - name: resolved-backend - try: - # The controller stamps status.resolvedBackend once it has reconciled. - - assert: - timeout: 2m - resource: - apiVersion: network.cozystack.io/v1alpha1 - kind: ServiceExposure - metadata: - name: se-e2e - status: - resolvedBackend: externalIPs - - - name: no-backend-pool - try: - # externalIPs backend must not create a MetalLB pool - # (backend.PoolName = cozystack-). resolvedBackend above already - # proved the reconcile ran, so a short non-existence poll is race-free — - # same ordering as the bats. - - error: - timeout: 30s - resource: - apiVersion: metallb.io/v1beta1 - kind: IPAddressPool - metadata: - name: cozystack-se-e2e-externalips - namespace: cozy-metallb - - - name: ready-and-assigned-ips - try: - # The pinned externalIPs make the exposure Ready. - - assert: - timeout: 1m - resource: - apiVersion: network.cozystack.io/v1alpha1 - kind: ServiceExposure - metadata: - name: se-e2e - status: - (conditions[?type == 'Ready']): - - status: "True" - (assignedIPs[0]): 203.0.113.10 ---- -apiVersion: chainsaw.kyverno.io/v1alpha1 -kind: Test -metadata: - name: serviceexposure-delete -spec: - catch: - - events: {} - - describe: - apiVersion: network.cozystack.io/v1alpha1 - kind: ServiceExposure - name: se-e2e - - podLogs: - namespace: cozy-system - selector: app=cozystack-controller - tail: 100 - steps: - - name: create-fixtures - try: - - apply: - file: serviceexposure.yaml - - assert: - timeout: 2m - resource: - apiVersion: network.cozystack.io/v1alpha1 - kind: ServiceExposure - metadata: - name: se-e2e - status: - resolvedBackend: externalIPs - - - name: delete-completes-despite-finalizer - try: - # Deletion must complete despite the cleanup finalizer (nothing to GC for - # the externalIPs backend, so the finalizer is removed promptly). The - # chainsaw delete op waits for the object to be fully gone — the delete - # IS the test. 3m covers the bats' 2m delete + 1m wait-for-delete. - - delete: - timeout: 3m - ref: - apiVersion: network.cozystack.io/v1alpha1 - kind: ServiceExposure - name: se-e2e diff --git a/hack/e2e-chainsaw/serviceexposure/serviceexposure.yaml b/hack/e2e-chainsaw/serviceexposure/serviceexposure.yaml deleted file mode 100644 index a97f85b30..000000000 --- a/hack/e2e-chainsaw/serviceexposure/serviceexposure.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# The externalIPs path is chosen deliberately so the test creates no -# MetalLB/Cilium pool — it cannot collide with the cluster's existing -# LoadBalancer address pools or depend on real L2 reachability -# (203.0.113.10 is TEST-NET-3), keeping it deterministic. The metallb and -# cilium rendering paths (pool + announcer CRs, garbage collection, take-over -# guard) are covered exhaustively by the Go unit tests in -# internal/controller/serviceexposure. -# -# The ExposureClass is cluster-scoped; Chainsaw tracks cluster-scoped applies -# too, so auto-cleanup reclaims all three objects in reverse apply order. -apiVersion: network.cozystack.io/v1alpha1 -kind: ExposureClass -metadata: - name: se-e2e-externalips -spec: - backend: externalIPs ---- -apiVersion: v1 -kind: Service -metadata: - name: se-e2e-svc -spec: - type: ClusterIP - externalIPs: - - 203.0.113.10 - ports: - - port: 80 - targetPort: 80 ---- -apiVersion: network.cozystack.io/v1alpha1 -kind: ServiceExposure -metadata: - name: se-e2e -spec: - serviceRef: - name: se-e2e-svc - exposureClassName: se-e2e-externalips diff --git a/hack/select-install.sh b/hack/select-install.sh index a9579b1c7..1222bad20 100755 --- a/hack/select-install.sh +++ b/hack/select-install.sh @@ -61,9 +61,9 @@ fi # - a "cozystack.*" name the suite's primary install target # - "-" the suite targets a core-platform feature that # has no separately-enabled package (always -# present via the base install), e.g. -# serviceexposure (ExposureClass/ServiceExposure -# live in packages/core/platform) +# present via the base install); no current +# suite maps here, kept for the next +# packages/core/platform-owned suite # - nothing no mapping is known (caller fails closed) # Most suites follow the -application convention; a bare- fallback # and the explicit cases cover suites whose source is named differently. Kept in @@ -82,7 +82,6 @@ suite_to_source() { echo cozystack.kubernetes-application ; return ;; vminstance) echo cozystack.vm-instance-application ; return ;; securitygroup) echo cozystack.securitygroup-controller ; return ;; - serviceexposure) echo - ; return ;; esac for cand in "cozystack.$1-application" "cozystack.$1"; do if echo "$NODES" | grep -Fxq "$cand"; then echo "$cand"; return; fi diff --git a/hack/select-install_test.bats b/hack/select-install_test.bats index 74289caf4..da01f9042 100644 --- a/hack/select-install_test.bats +++ b/hack/select-install_test.bats @@ -101,14 +101,6 @@ echo "$output" | grep -wq cozystack.gateway-api-crds } -@test "core-platform suite contributes no package and emits no warning" { - # serviceexposure is a core-platform CRD; nothing to enable, no warning. - err=$(hack/select-install.sh "serviceexposure" 2>&1 1>/dev/null) - [ -z "$err" ] - output=$(hack/select-install.sh "serviceexposure" 2>/dev/null) - [ -z "$output" ] -} - @test "validate passes on the real source graph and suite mapping" { hack/select-install.sh --validate } From 4e9a40d811dbfb4b4f8811d643e67b6d50dab147 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 17 Jul 2026 14:30:21 +0400 Subject: [PATCH 2/5] test(sdn): drop the now-unreachable "-" mapping from suite_to_source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With the serviceexposure case gone no suite returns "-", and the only executable coverage of that branch was the bats test removed alongside it. Keeping an untestable dead branch for a hypothetical future suite is not worth it — re-adding the three lines with the next core-platform suite is cheaper, and validate/closure both simplify. Co-Authored-By: Claude Fable 5 Signed-off-by: Andrey Kolkov --- hack/select-install.sh | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/hack/select-install.sh b/hack/select-install.sh index 1222bad20..2a4d6a72d 100755 --- a/hack/select-install.sh +++ b/hack/select-install.sh @@ -59,11 +59,6 @@ fi # suite name -> owning PackageSource. Prints: # - a "cozystack.*" name the suite's primary install target -# - "-" the suite targets a core-platform feature that -# has no separately-enabled package (always -# present via the base install); no current -# suite maps here, kept for the next -# packages/core/platform-owned suite # - nothing no mapping is known (caller fails closed) # Most suites follow the -application convention; a bare- fallback # and the explicit cases cover suites whose source is named differently. Kept in @@ -148,8 +143,8 @@ if [ "$MODE" = "validate" ]; then done done - # 3. Suite mapping: every Chainsaw suite dir must resolve to a real source or - # "-". The suite universe is auto-discovered while suite_to_source() is + # 3. Suite mapping: every Chainsaw suite dir must resolve to a real source. + # The suite universe is auto-discovered while suite_to_source() is # hand-maintained, so this is the part that actually drifts. A missing # suites dir is itself a failure — otherwise a moved/misspelled path would # make discovery yield nothing and silently pass this whole check. @@ -162,7 +157,7 @@ if [ "$MODE" = "validate" ]; then if [ -z "$src" ]; then echo "select-install: suite '$suite' has no PackageSource mapping (add it to suite_to_source)" >&2 rc=1 - elif [ "$src" != "-" ] && ! echo "$NODES" | grep -Fxq "$src"; then + elif ! echo "$NODES" | grep -Fxq "$src"; then echo "select-install: suite '$suite' maps to '$src', not a PackageSource in $SOURCES_DIR" >&2 rc=1 fi @@ -180,7 +175,6 @@ for suite in $SUITES; do [ -z "$suite" ] && continue src="$(suite_to_source "$suite")" case "$src" in - "-") continue ;; # core-platform feature — nothing separate to enable "") # Fail closed: a suite that maps to nothing must abort, not silently emit # an empty set. Collect every bad suite so one run reports them all. From f1627354b4fa1e63f08e2488acbe1a169e4e51c3 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 17 Jul 2026 14:35:40 +0400 Subject: [PATCH 3/5] docs(contributing): point the ansible migration note at loadBalancerClass The downstream-coordination checklist still directed contributors at an "externalIPs to exposureClass" migration; exposureClass was removed in 3dbc7d366 and the native replacement key is publishing.loadBalancerClass. Last leftover of the ExposureClass removal this PR sweeps. Co-Authored-By: Claude Fable 5 Signed-off-by: Andrey Kolkov --- docs/agents/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/agents/contributing.md b/docs/agents/contributing.md index 47c630c02..9f34d4495 100644 --- a/docs/agents/contributing.md +++ b/docs/agents/contributing.md @@ -195,7 +195,7 @@ The provider is hand-written: every Kind carries a hand-maintained Terraform sch - Add a required key to `packages/core/installer/values.yaml`, or rename one the role passes (`cozystackOperator.variant`, `cozystack.apiServerHost`, `cozystack.apiServerPort`) → the chart values block in the role's `main.yml` task, and its `defaults/main.yml`. - Change the `cozy-system` namespace or the PSA pre-install hook contract → the role carries namespace-adoption logic written against the current behaviour of `packages/core/installer/templates/`. - Add or rename a platform variant → the variant list is hardcoded in Go, in `cmd/cozystack-operator/main.go`; over there it is `cozystack_platform_variant` in `defaults/main.yml` and the README table. -- Change a `packages/core/platform/values.yaml` key the role sets — `networking.podCIDR`, `networking.podGateway`, `networking.serviceCIDR`, `networking.joinCIDR`, `publishing.host`, `publishing.apiServerEndpoint`, `publishing.externalIPs` → the role's platform-package template. The `externalIPs` to `exposureClass` migration in particular needs a coordinated PR there. +- Change a `packages/core/platform/values.yaml` key the role sets — `networking.podCIDR`, `networking.podGateway`, `networking.serviceCIDR`, `networking.joinCIDR`, `publishing.host`, `publishing.apiServerEndpoint`, `publishing.externalIPs` → the role's platform-package template. The `externalIPs` to `publishing.loadBalancerClass` migration in particular needs a coordinated PR there. - Rename an object the role waits on — `deployment/cozystack-operator`, `crd/packages.cozystack.io`, the root `Tenant` — or change the `.` naming scheme of the Package it applies → the role's tasks and its own CI assertions. - Change node prerequisites in `hack/e2e-prepare-cluster.bats` — the kernel modules a node must load, the containerd settings, the LVM `global_filter` → those are mirrored by hand into that repo's `examples/ubuntu`, `examples/rhel` and `examples/suse` prepare playbooks. Its CI tests some of them, but never against this repo, so a kernel module added here and not there surfaces as a broken storage layer on a generic cluster. - Change what Cozystack requires from the Kubernetes distribution (CNI, kube-proxy, cluster domain, max pods) → the k3s flags, which live only in that repo's inventories. From bbc01039494a5230853df763cccb53df932d3e97 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 17 Jul 2026 14:41:13 +0400 Subject: [PATCH 4/5] test(sdn): pin the fail-closed suite-mapping contract in validate Scrub the last "-" mention from the --validate usage doc (the escape no longer exists, so the header asserted the opposite of the behavior), and add the negative bats case the tightened branch was missing: a suite whose hardcoded mapping names a PackageSource absent from the graph must fail validation with the "maps to ..., not a PackageSource" error. Co-Authored-By: Claude Fable 5 Signed-off-by: Andrey Kolkov --- hack/select-install.sh | 2 +- hack/select-install_test.bats | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/hack/select-install.sh b/hack/select-install.sh index 2a4d6a72d..ae8b6e1a6 100755 --- a/hack/select-install.sh +++ b/hack/select-install.sh @@ -24,7 +24,7 @@ # --validate: check the whole graph AND the suite mapping — every dependsOn # target resolves to a real PackageSource, there are no dependency cycles, and # every Chainsaw suite under resolves via suite_to_source() to a -# real PackageSource or to "-". Exit non-zero on any failure. The graph lives +# real PackageSource. Exit non-zero on any failure. The graph lives # next to the packages and cannot drift from the suite directories; the # hand-maintained suite mapping is the only part that can, so it is guarded here. # diff --git a/hack/select-install_test.bats b/hack/select-install_test.bats index da01f9042..71571de65 100644 --- a/hack/select-install_test.bats +++ b/hack/select-install_test.bats @@ -172,6 +172,31 @@ YAML fi } +@test "validate detects a suite mapped to a source absent from the graph" { + # suite_to_source() hardcodes securitygroup -> cozystack.securitygroup-controller; + # a sources dir without that PackageSource must fail validation (fail closed), + # pinning the contract that EVERY mapping resolves to a real source — there is + # no bypass value (the old "-" escape is gone). + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + mkdir -p "$tmp/sources" "$tmp/suites/securitygroup" + : > "$tmp/suites/securitygroup/chainsaw-test.yaml" + cat > "$tmp/sources/standalone.yaml" <<'YAML' +apiVersion: cozystack.io/v1alpha1 +kind: PackageSource +metadata: + name: cozystack.standalone +spec: + variants: + - name: default +YAML + err=$(hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" 2>&1 1>/dev/null) && { + echo "expected validation to fail on a mapping to a missing source" >&2 + exit 1 + } + echo "$err" | grep -q "maps to 'cozystack.securitygroup-controller', not a PackageSource" +} + @test "validate fails when the suites dir does not exist" { tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT From 8282c17d08b99e7443a3062bb2c12a4e46581a67 Mon Sep 17 00:00:00 2001 From: Andrey Kolkov Date: Fri, 17 Jul 2026 14:45:58 +0400 Subject: [PATCH 5/5] test(sdn): scope the new validate test's comment to what it proves Co-Authored-By: Claude Fable 5 Signed-off-by: Andrey Kolkov --- hack/select-install_test.bats | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/select-install_test.bats b/hack/select-install_test.bats index 71571de65..82d685238 100644 --- a/hack/select-install_test.bats +++ b/hack/select-install_test.bats @@ -175,8 +175,8 @@ YAML @test "validate detects a suite mapped to a source absent from the graph" { # suite_to_source() hardcodes securitygroup -> cozystack.securitygroup-controller; # a sources dir without that PackageSource must fail validation (fail closed), - # pinning the contract that EVERY mapping resolves to a real source — there is - # no bypass value (the old "-" escape is gone). + # pinning that the elif validates every mapping, including hardcoded ones, + # against the graph. tmp=$(mktemp -d) trap 'rm -rf "$tmp"' EXIT mkdir -p "$tmp/sources" "$tmp/suites/securitygroup"