Files
cozystack/hack/select-e2e.sh
T
IvanHunters e0597e3d99 fix(platform): make *-rd sources depend on cozystack-engine (#3041)
## Problem

On an unlucky Flux reconcile order, a `*-application` PackageSource that
ships a `system/*-rd` component can install before the
`applicationdefinitions.cozystack.io` CRD exists. Every `*-rd` chart
renders an `ApplicationDefinition` (`cozystack.io/v1alpha1`); that CRD
is installed by the `application-definition-crd` component of
`cozystack.cozystack-engine`. When an `*-rd` HelmRelease renders first,
it hard-fails with `no matches for kind "ApplicationDefinition" in
version "cozystack.io/v1alpha1"`, and that cascades into a broad install
timeout — surfacing even on changes that have nothing to do with
ordering, because it is a latent race.

## Fix

Add `- cozystack.cozystack-engine` to the `dependsOn` of every
`*-application` source that ships a `system/*-rd` component (29
sources). `gateway-application` already carried this edge — it was added
earlier for the TenantGateway CRD, the same bug class, but only for that
one source. A new invariant test fails if any source with a
`system/*-rd` component is missing the edge.

## Why

The ordering was previously implicit, relying on Flux happening to
reconcile `cozystack-engine` before the `*-application` sources. Making
the edge explicit removes the race deterministically. No dependency
cycle is introduced: `cozystack-engine` depends only on `networking` and
`cert-manager` (plus keycloak in its oidc variant), none of which reach
back to any `*-application`.

## Test

- New generic guard `hack/check-applicationdefinition-crd-ordering.bats`
(auto-discovered by `make bats-unit-tests`, runs in CI via `make
unit-tests`): parses every source and fails if any PackageSource variant
with a `system/*-rd` component lacks the `cozystack.cozystack-engine`
edge — so future sources are covered, not just today's set.
- `helm unittest packages/core/platform`: 13 suites / 66 tests pass; the
source edits do not disturb existing assertions.


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

## Summary by CodeRabbit

* **Bug Fixes**
* Improved dependency ordering across several platform packages to
ensure related components install in the correct sequence.
* Prevented end-to-end test selection from being overly broad when only
ordering-related dependencies change.

* **Tests**
* Added checks to catch missing dependency requirements and to verify
test selection behavior stays accurate.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-06-25 19:21:50 +03:00

161 lines
5.6 KiB
Bash
Executable File

#!/bin/sh
# Read a list of changed files (one per line) and emit space-separated app
# names whose bats files in hack/e2e-apps/ should run.
#
# Usage: hack/select-e2e.sh <changed-files> [<sources-dir>]
# Defaults: sources-dir = packages/core/platform/sources
#
# Output:
# - empty no E2E impact (docs / dashboards / *.md only)
# - <app names> selected per the PackageSource dependency graph
# - full list any path that affects all tests, OR an unrecognised
# packages/* path, OR a per-app source whose graph has
# no *-application descendants (conservative fallback)
set -eu
CHANGED="${1:?missing changed-files arg}"
SOURCES_DIR="${2:-packages/core/platform/sources}"
# Anything matching this pattern triggers the full bats suite. Per-app bats
# (hack/e2e-apps/<name>.bats) are matched BEFORE this so editing one bats
# file doesn't escalate to the full suite.
full_suite_pattern='^(packages/library/|packages/core/|api/|cmd/|internal/|hack/[^/]+\.sh$|hack/[^/]+\.bats$|hack/e2e-apps/[^/]+\.sh$|Makefile$|\.github/workflows/pull-requests\.yaml$)'
# All known per-app bats files
all_apps=$(ls hack/e2e-apps/*.bats 2>/dev/null | xargs -n1 basename | sed 's/\.bats$//')
# PackageSource name -> bats name(s). Most *-application sources map by
# stripping the suffix; explicit overrides for the few that don't.
app_to_bats() {
case "$1" in
postgres-application) echo postgres ;;
vm-instance-application) echo vminstance ;;
kubernetes-application) echo "kubernetes-latest kubernetes-previous" ;;
external-dns) echo external-dns ;;
*-application) echo "${1%-application}" ;;
*) echo "$1" ;;
esac
}
# yq: path -> PackageSource name
build_owners_index() {
yq -rN '.metadata.name as $n | .spec.variants[]?.components[]?.path | select(. != null and . != "") | . + "\t" + $n' "$SOURCES_DIR"/*.yaml
}
# 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 . != "" and . != "cozystack.cozystack-engine") | . + "\t" + $n' "$SOURCES_DIR"/*.yaml
}
OWNERS=$(build_owners_index | sort -u)
REVERSE=$(build_reverse_deps | sort -u)
trigger_full=0
trigger_any=0
selected_sources=""
selected_apps=""
while IFS= read -r file; do
[ -z "$file" ] && continue
# 1. Skip: docs, dashboards, *.md
if echo "$file" | grep -qE '^(docs/|dashboards/)' || echo "$file" | grep -qE '\.md$'; then
continue
fi
# 2. Per-app bats first — editing one bats file selects only that app.
if echo "$file" | grep -qE '^hack/e2e-apps/[^/]+\.bats$'; then
app=$(basename "$file" .bats)
selected_apps="$selected_apps $app"
trigger_any=1
continue
fi
# 3. Full-suite trigger
if echo "$file" | grep -qE "$full_suite_pattern"; then
trigger_full=1
continue
fi
# 4. Component change: lookup in PackageSource graph
rel=$(echo "$file" | sed -nE 's,^packages/(apps|system|extra)/([^/]+)/.*,\1/\2,p')
if [ -n "$rel" ]; then
src=$(echo "$OWNERS" | awk -v p="$rel" -F'\t' '$1==p {print $2}')
if [ -n "$src" ]; then
selected_sources="$selected_sources $src"
trigger_any=1
else
# Inside packages/ but no graph entry — be conservative.
trigger_full=1
fi
fi
# Anything else (e.g. unrelated workflow files, top-level configs) is
# silently ignored.
done < "$CHANGED"
if [ "$trigger_full" = 1 ]; then
echo "$all_apps" | paste -sd ' ' -
exit 0
fi
if [ "$trigger_any" = 0 ]; then
exit 0 # nothing to run
fi
# Transitive closure: walk reverse-deps from each selected source.
all_sources="$selected_sources"
while :; do
new=""
for s in $all_sources; do
deps=$(echo "$REVERSE" | awk -v src="$s" -F'\t' '$1==src {print $2}')
for d in $deps; do
case " $all_sources " in *" $d "*) ;; *) new="$new $d";; esac
done
done
[ -z "$new" ] && break
all_sources="$all_sources $new"
done
# Filter to *-application sources, then map to bats names.
final=""
for s in $all_sources; do
app=${s#cozystack.}
case "$app" in
*-application) final="$final $(app_to_bats "$app")" ;;
external-dns) final="$final external-dns" ;;
esac
done
# Add directly-selected apps from per-app bats edits.
final="$final $selected_apps"
# Deduplicate; intersect with available bats files.
final_apps=$(echo "$final" | tr ' ' '\n' | sort -u | grep -v '^$' | while read -r app; do
if echo "$all_apps" | grep -Fxq "$app"; then
echo "$app"
fi
done | paste -sd ' ' -)
# Safety net: a system source with no *-application descendants would otherwise
# silently skip E2E. Fall back to full suite so a path inside the graph is
# never silently dropped.
if [ -z "$final_apps" ]; then
echo "$all_apps" | paste -sd ' ' -
exit 0
fi
echo "$final_apps"