Files
cozystack/hack/select-install_test.bats
T
Myasnikov Daniil 9a782f697a fix(hack): keep the backup stack in the vminstance selective install
The vminstance Chainsaw suite carries the VMInstance and VMDisk Velero
round-trips. Both build the cozy-default BackupStorageLocation and the
platform bucket from the backupstrategy-controller package, but the
selective install retained that package only for the database and
broker suites. A selection of vminstance alone therefore installed a
platform with no Bucket, no BucketClaim and no BSL, and both
round-trips timed out on the BSL wait before running a step of the
example scripts.

Every pull request whose selection is exactly vminstance hits this,
and that includes each edit to examples/backups/vmi and
examples/backups/vmdisk, which select that suite on purpose.

Assisted-by: LLM
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
2026-10-21 15:04:39 +05:00

751 lines
30 KiB
Bash

#!/usr/bin/env bats
# -----------------------------------------------------------------------------
# Unit tests for hack/select-install.sh
#
# CI runs this file under Bats through `make bats-unit-tests`. It also remains
# compatible with the legacy `hack/cozytest.sh` translator, whose awk parser
# recognizes only @test blocks and a bare `}` on its own line. The tests avoid
# `run`, `$status`, setup(), and teardown() for that compatibility path. Cleanup
# stays at the last reachable line rather than in an EXIT trap, which would
# replace Bats' bookkeeping trap and hide a failing TAP result.
#
# Tests that only READ the production sources call the script directly (it
# defaults sources-dir to packages/core/platform/sources); only the tests that
# build a synthetic graph or suite tree use a scratch dir.
#
# Run with: bats hack/select-install_test.bats
# -----------------------------------------------------------------------------
load test_helper
assert_contains_package() {
printf '%s\n' "$1" | tr ' ' '\n' | grep -Fxq "$2"
}
assert_output_graph_error() {
mode=$1
sources=$2
expected=$3
requested_suite=${4:-postgres}
out=$(mktemp)
err=$(mktemp)
if [ "$mode" = disabled ]; then
if hack/select-install.sh --disabled "$requested_suite" "$sources" >"$out" 2>"$err"; then
echo "expected disabled mode to reject invalid graph $sources" >&2
exit 1
fi
elif hack/select-install.sh "$requested_suite" "$sources" >"$out" 2>"$err"; then
echo "expected closure mode to reject invalid graph $sources" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "$expected" "$err"
rm -f "$out" "$err"
}
@test "single app selects its forward dependency closure" {
output=$(hack/select-install.sh "postgres")
assert_contains_package "$output" cozystack.postgres-application
assert_contains_package "$output" cozystack.postgres-operator
assert_contains_package "$output" cozystack.networking
# engine edge is KEPT on the install walk (unlike select-e2e.sh)
assert_contains_package "$output" cozystack.cozystack-engine
}
@test "closure is transitive (deps of deps are pulled in)" {
output=$(hack/select-install.sh "postgres")
# cert-manager is a 2-hop dep (via postgres-operator and via the engine)
assert_contains_package "$output" cozystack.cert-manager
# gateway-api-crds is a 3-hop dep (postgres-application -> networking -> gateway-api-crds)
assert_contains_package "$output" cozystack.gateway-api-crds
}
@test "app pulls its direct operator dependencies" {
output=$(hack/select-install.sh "harbor")
assert_contains_package "$output" cozystack.harbor-application
assert_contains_package "$output" cozystack.postgres-operator
assert_contains_package "$output" cozystack.redis-operator
assert_contains_package "$output" cozystack.objectstorage-controller
}
@test "kubernetes suites keep the cluster and worker pool APIs" {
for suite in kubernetes-latest kubernetes-previous; do
output=$(hack/select-install.sh "$suite")
assert_contains_package "$output" cozystack.kubernetes-application
assert_contains_package "$output" cozystack.kubernetes-nodes-application
done
}
@test "mongodb keeps the operator for its PerconaServerMongoDB resources" {
output=$(hack/select-install.sh mongodb)
assert_contains_package "$output" cozystack.mongodb-application
assert_contains_package "$output" cozystack.mongodb-operator
}
@test "root SeaweedFS keeps COSI for suites without backup steps" {
output=$(hack/select-install.sh kuberture)
assert_contains_package "$output" cozystack.seaweedfs-application
assert_contains_package "$output" cozystack.objectstorage-controller
# A backup suite would hide a lost SeaweedFS edge by retaining COSI itself.
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.objectstorage-controller"]' \
"$tmp/sources/seaweedfs-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"runtime baseline dependency closure is missing: cozystack.objectstorage-controller" kuberture
done
rm -rf "$tmp"
}
@test "vminstance keeps both application owners" {
output=$(hack/select-install.sh "vminstance")
printf '%s\n' "$output" | tr ' ' '\n' | grep -Fxq cozystack.vm-instance-application
printf '%s\n' "$output" | tr ' ' '\n' | grep -Fxq cozystack.vm-disk-application
printf '%s\n' "$output" | tr ' ' '\n' | grep -Fxq cozystack.kubevirt-cdi
}
@test "multiple suites union their closures" {
output=$(hack/select-install.sh "postgres kafka")
assert_contains_package "$output" cozystack.postgres-application
assert_contains_package "$output" cozystack.kafka-application
}
@test "suite list can be read from stdin with -" {
output=$(printf '%s\n' "postgres kafka" | hack/select-install.sh -)
assert_contains_package "$output" cozystack.postgres-application
assert_contains_package "$output" cozystack.kafka-application
assert_contains_package "$output" cozystack.cozystack-engine
}
@test "empty suites select nothing" {
output=$(hack/select-install.sh "")
[ -z "$output" ]
}
@test "unmapped suites are a hard error, all reported, no partial output" {
# Fail closed: a suite that maps to nothing must abort, not silently emit an
# empty set (which would install nothing and let the run pass vacuously).
# Also lock the contract that EVERY bad suite is reported in one run (not
# just the first) and that no partial closure leaks to stdout.
out=$(mktemp); err=$(mktemp)
if hack/select-install.sh "bad-one postgres bad-two" >"$out" 2>"$err"; then
echo "expected a non-zero exit for unmapped suites" >&2
exit 1
fi
grep -q "bad-one" "$err"
grep -q "bad-two" "$err"
[ ! -s "$out" ]
rm -f "$out" "$err"
}
@test "suite whose source omits the -application suffix resolves via fallback" {
# kuberture's PackageSource is cozystack.kuberture (no -application suffix)
output=$(hack/select-install.sh "kuberture")
assert_contains_package "$output" cozystack.kuberture
}
@test "securitygroup closure includes the engine that serves sdn.cozystack.io" {
# Regression for the under-selection bug: the securitygroup suite applies
# sdn.cozystack.io/v1alpha1, served by the aggregated apiserver
# (a cozystack-engine component). The forward walk from the controller can't
# reach the engine, so it must be seeded. Assert the controller AND the
# engine AND the engine's stable prerequisites — asserting only the
# controller (a single member) is what let the bug through.
output=$(hack/select-install.sh "securitygroup")
assert_contains_package "$output" cozystack.securitygroup-controller
assert_contains_package "$output" cozystack.cozystack-engine
assert_contains_package "$output" cozystack.cert-manager
assert_contains_package "$output" cozystack.networking
assert_contains_package "$output" cozystack.gateway-api-crds
}
@test "runtime baseline rejects a lost etcd operator dependency edge" {
# etcd-application is also a baseline seed, so merely selecting etcd cannot
# prove that its operator edge is necessary.
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
hack/select-install.sh postgres "$tmp/sources" >/dev/null
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.etcd-operator"]' \
"$tmp/sources/etcd-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"runtime baseline dependency closure is missing: cozystack.etcd-operator"
done
rm -rf "$tmp"
}
@test "backup suites keep the APIs used by their roundtrip examples" {
for suite in clickhouse etcd kafka kafka-metadata mariadb mongodb postgres rabbitmq redis valkey vminstance; do
keep=$(hack/select-install.sh "$suite")
drop=$(hack/select-install.sh --disabled "$suite")
for required in cozystack.backupstrategy-controller cozystack.backup-controller \
cozystack.bucket-application cozystack.objectstorage-controller cozystack.velero; do
assert_contains_package "$keep" "$required"
case " $drop " in
*" $required "*) echo "suite '$suite' disables backup prerequisite '$required'" >&2; exit 1 ;;
esac
done
done
# Suites without backup steps must not acquire the backup stack as baseline.
drop=$(hack/select-install.sh --disabled kuberture)
assert_contains_package "$drop" cozystack.backupstrategy-controller
}
@test "gateway keeps the pod admission webhook used by its churn test" {
output=$(hack/select-install.sh gateway)
assert_contains_package "$output" cozystack.kubeovn-webhook
}
@test "suite runtime requirements must resolve in every selector mode" {
for pair in "postgres backupstrategy-controller" "gateway kubeovn-webhook"; do
set -- $pair
suite=$1
requirement=$2
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
mkdir -p "$tmp/suites/$suite"
: > "$tmp/suites/$suite/chainsaw-test.yaml"
hack/select-install.sh --validate "$tmp/sources" "$tmp/suites"
mv "$tmp/sources/$requirement.yaml" "$tmp/$requirement.yaml"
hack/select-install.sh kuberture "$tmp/sources" >/dev/null
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"'cozystack.$requirement' is not a PackageSource" "$suite"
done
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" >"$tmp/out" 2>"$tmp/err"; then
echo "expected validation to reject a missing suite runtime requirement" >&2
exit 1
fi
[ ! -s "$tmp/out" ]
grep -Fq "maps to 'cozystack.$requirement', not a PackageSource" "$tmp/err"
rm -rf "$tmp"
done
}
@test "validate passes on the real source graph and suite mapping" {
hack/select-install.sh --validate
}
@test "validate detects a dangling dependency" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites/foo"
: > "$tmp/suites/foo/chainsaw-test.yaml"
cat > "$tmp/sources/foo-application.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.foo-application
spec:
variants:
- name: default
dependsOn:
- cozystack.does-not-exist
YAML
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" >"$out" 2>"$err"; then
echo "expected validation to fail on a dangling dependency" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "dangling dependency 'cozystack.does-not-exist'" "$err"
if grep -Fq "found no chainsaw-test.yaml" "$err"; then
echo "positive-control suite discovery failed instead of graph validation" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate detects a dependency cycle" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources" "$tmp/suites/a"
: > "$tmp/suites/a/chainsaw-test.yaml"
cat > "$tmp/sources/a.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.a
spec:
variants:
- name: default
dependsOn:
- cozystack.b
YAML
cat > "$tmp/sources/b.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.b
spec:
variants:
- name: default
dependsOn:
- cozystack.a
YAML
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh --validate "$tmp/sources" "$tmp/suites" >"$out" 2>"$err"; then
echo "expected validation to fail on a dependency cycle" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "dependency cycle detected involving 'cozystack.a'" "$err"
if grep -Fq "found no chainsaw-test.yaml" "$err"; then
echo "positive-control suite discovery failed instead of graph validation" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "output modes reject a removed runtime source" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
mv "$tmp/sources/reloader.yaml" "$tmp/reloader.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"dangling dependency 'cozystack.reloader'"
done
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.reloader"]' \
"$tmp/sources/linstor.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"required runtime PackageSource(s) not found in $tmp/sources: cozystack.reloader"
done
rm -rf "$tmp"
}
@test "output modes reject a dangling dependency in an unselected package" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '.spec.variants[0].dependsOn += ["cozystack.does-not-exist"]' \
"$tmp/sources/kafka-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/sources" \
"dangling dependency 'cozystack.does-not-exist' (referenced by: cozystack.kafka-application)"
done
rm -rf "$tmp"
}
@test "output modes reject selected and unselected dependency cycles" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/selected"
cp -r packages/core/platform/sources "$tmp/unselected"
yq -i '.spec.variants[0].dependsOn += ["cozystack.postgres-application"]' \
"$tmp/selected/postgres-application.yaml"
yq -i '.spec.variants[0].dependsOn += ["cozystack.kafka-application"]' \
"$tmp/unselected/kafka-application.yaml"
for mode in closure disabled; do
assert_output_graph_error "$mode" "$tmp/selected" \
"dependency cycle detected involving 'cozystack.postgres-application'"
assert_output_graph_error "$mode" "$tmp/unselected" \
"dependency cycle detected involving 'cozystack.kafka-application'"
done
rm -rf "$tmp"
}
@test "validate detects a suite directory with no source mapping" {
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/suites/totally-unmapped-suite/chainsaw-test.yaml"
# real graph (so graph checks pass); the only failure is the unmapped suite
if hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>/dev/null; then
echo "expected validation to fail on an unmapped suite dir" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate sees a suite written as chainsaw-test.yml" {
# Chainsaw runs a chainsaw-test.yml suite, so the mapping check has to see
# one too, or an unmapped suite spelled that way passes validation.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/suites/totally-unmapped-suite/chainsaw-test.yml"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped .yml suite" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
}
@test "validate sees a suite made only of numbered step files" {
# Chainsaw runs a directory of 01-*.yaml step files as a suite when it holds
# no chainsaw-test.*, so the mapping check has to see that layout too. The
# step file is a symlink because Chainsaw counts one.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite"
: > "$tmp/install.yaml"
ln -s "$tmp/install.yaml" "$tmp/suites/totally-unmapped-suite/00-install.yaml"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped step-file suite" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
}
@test "validate ignores what Chainsaw does not load as a step file" {
# Chainsaw skips a step-file name when the entry is a directory, and a name
# with no leading number is not a step file, so neither makes a suite that
# needs a mapping.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/postgres" "$tmp/suites/totally-unmapped-suite/01-x.yaml"
: > "$tmp/suites/totally-unmapped-suite/-install.yaml"
: > "$tmp/suites/postgres/chainsaw-test.yaml"
hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites"
rm -rf "$tmp"
}
@test "validate sees a directory named chainsaw-test.yaml or .yml" {
# Chainsaw stats chainsaw-test.yaml or .yml without checking its type, so a
# directory of that name fails discovery loudly. Listing its suite keeps
# that failure where a run reaches it instead of leaving the suite out.
for name in chainsaw-test.yaml chainsaw-test.yml; do
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite/$name"
: > "$tmp/suites/totally-unmapped-suite/$name/x"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped suite with a $name directory" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
done
}
@test "validate sees a suite nested below a top-level directory" {
# Chainsaw walks every directory under the one it is handed, so a test in a
# subdirectory runs as part of its top-level directory, which then needs a
# mapping like any other suite.
tmp=$(mktemp -d)
mkdir -p "$tmp/suites/totally-unmapped-suite/cases"
: > "$tmp/suites/totally-unmapped-suite/cases/chainsaw-test.yaml"
err=$(hack/select-install.sh --validate packages/core/platform/sources "$tmp/suites" 2>&1 1>/dev/null) && {
echo "expected validation to fail on an unmapped nested suite" >&2
exit 1
}
echo "$err" | grep -q "suite 'totally-unmapped-suite' has no PackageSource mapping"
rm -rf "$tmp"
}
@test "validate fails when the suite listing itself fails" {
# A failed find and an empty suites dir used to read the same: the error
# went to /dev/null and the pipeline reported sort's status, so the mapping
# check validated nothing and passed.
tmp=$(mktemp -d)
mkdir "$tmp/bin"
printf '#!/bin/sh\necho "find: broken fixture" >&2\nexit 1\n' > "$tmp/bin/find"
chmod +x "$tmp/bin/find"
err=$(PATH="$tmp/bin:$PATH" hack/select-install.sh --validate 2>&1 1>/dev/null) && {
echo "expected validate to fail when find fails" >&2
exit 1
}
echo "$err" | grep -q "select-install: find failed listing the suites"
rm -rf "$tmp"
}
@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 that the elif validates every mapping, including hardcoded ones,
# against the graph.
tmp=$(mktemp -d)
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"
rm -rf "$tmp"
}
@test "validate fails when the suites dir does not exist" {
tmp=$(mktemp -d)
# real graph passes the graph checks; a missing suites dir must still FAIL
# (find would yield nothing and silently pass the mapping check otherwise).
if hack/select-install.sh --validate packages/core/platform/sources "$tmp/does-not-exist" 2>/dev/null; then
echo "expected validate to fail on a missing suites dir" >&2
exit 1
fi
rm -rf "$tmp"
}
@test "validate fails when suite discovery fails" {
tmp=$(mktemp -d)
mkdir -p "$tmp/bin" "$tmp/suites/one"
: > "$tmp/suites/one/chainsaw-test.yaml"
cat > "$tmp/bin/find" <<'SH'
#!/bin/sh
exit 42
SH
chmod +x "$tmp/bin/find"
out=$(mktemp); err=$(mktemp)
if PATH="$tmp/bin:$PATH" hack/select-install.sh --validate \
packages/core/platform/sources "$tmp/suites" >"$out" 2>"$err"; then
echo "expected validate to fail when find fails" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "find failed listing the suites" "$err"
rm -rf "$tmp"; rm -f "$out" "$err"
}
@test "yq producer failures cannot become a successful partial graph" {
tmp=$(mktemp -d)
mkdir -p "$tmp/bin"
real_yq=$(command -v yq)
cat > "$tmp/bin/yq" <<'SH'
#!/bin/sh
case "${FAIL_YQ:-}" in
names)
case "$*" in
*metadata.name*) echo cozystack.partial; exit 42 ;;
esac
;;
dependencies)
case "$*" in
*spec.variants*) printf 'cozystack.partial\tcozystack.missing\n'; exit 42 ;;
esac
;;
esac
exec "$REAL_YQ" "$@"
SH
chmod +x "$tmp/bin/yq"
for stage in names dependencies; do
out="$tmp/$stage.out"; err="$tmp/$stage.err"
if REAL_YQ="$real_yq" FAIL_YQ="$stage" PATH="$tmp/bin:$PATH" \
hack/select-install.sh postgres >"$out" 2>"$err"; then
echo "expected the $stage yq failure to propagate" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "yq failed while reading PackageSource" "$err"
done
rm -rf "$tmp"
}
@test "closure fails closed when the engine PackageSource is absent" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources"
# a lone app source, with no cozystack.cozystack-engine in the graph
cat > "$tmp/sources/foo-application.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.foo-application
spec:
variants:
- name: default
YAML
if hack/select-install.sh "foo" "$tmp/sources" 2>/dev/null; then
echo "expected a non-zero exit when the engine source is missing" >&2
exit 1
fi
rm -rf "$tmp"
}
# --disabled emits the complement of the closure: what bundles.disabledPackages
# must hold so an ordinary platform variant installs only what the suites need.
@test "disabled mode: closure and complement partition the source list" {
# Count names only after yq itself has succeeded; dash has no pipefail.
names=$(yq -rN '.metadata.name | select(. != null and . != "")' \
packages/core/platform/sources/*.yaml)
total=$(printf '%s\n' "$names" | sort -u | wc -l | tr -d ' ')
keep_output=$(hack/select-install.sh "postgres")
drop_output=$(hack/select-install.sh --disabled "postgres")
keep=$(printf '%s\n' "$keep_output" | wc -w | tr -d ' ')
drop=$(printf '%s\n' "$drop_output" | wc -w | tr -d ' ')
sum=$((keep + drop))
if [ "$sum" -ne "$total" ]; then
echo "closure ($keep) + complement ($drop) = $sum, but there are $total PackageSources" >&2
exit 1
fi
inventory=$(printf '%s\n' "$names" | sort -u)
partition=$(printf '%s\n%s\n' "$keep_output" "$drop_output" | tr ' ' '\n' | sort -u)
[ "$partition" = "$inventory" ]
}
@test "disabled mode accepts a valid inventory with an empty complement" {
tmp=$(mktemp -d)
mkdir -p "$tmp/sources"
keep=$(hack/select-install.sh postgres)
for source in $keep; do
name=${source#cozystack.}
cp "packages/core/platform/sources/$name.yaml" "$tmp/sources/"
done
output=$(hack/select-install.sh --disabled postgres "$tmp/sources")
[ -z "$output" ]
rm -rf "$tmp"
}
@test "disabled mode: never lists a package the closure keeps" {
keep=$(hack/select-install.sh "postgres kafka")
drop=$(hack/select-install.sh --disabled "postgres kafka")
for k in $keep; do
case " $drop " in
*" $k "*) echo "package '$k' is both kept and disabled" >&2; exit 1 ;;
esac
done
}
# A declared dependency of something kept can never land in the complement.
# Assert the property over every suite instead of citing an unreachable pair.
@test "disabled mode: no kept package has a dependency in the complement" {
deps=$(mktemp)
yq -rN '.metadata.name as $n | .spec.variants[]?.dependsOn[]? | select(. != null and . != "") | $n + " " + .' \
packages/core/platform/sources/*.yaml > "$deps"
for suite in $(find hack/e2e-chainsaw -mindepth 2 -maxdepth 2 -name chainsaw-test.yaml \
| sed -e 's,^hack/e2e-chainsaw/,,' -e 's,/chainsaw-test\.yaml$,,'); do
keep=$(hack/select-install.sh "$suite")
drop=$(hack/select-install.sh --disabled "$suite")
for k in $keep; do
for d in $(awk -v owner="$k" '$1 == owner {print $2}' "$deps"); do
case " $drop " in
*" $d "*)
echo "suite '$suite': kept '$k' depends on '$d', which is in the disable list" >&2
rm -f "$deps"; exit 1 ;;
esac
done
done
done
rm -f "$deps"
}
# Direct seeds correspond to unconditional install steps, even when another
# baseline source also reaches them through dependsOn.
@test "disabled mode: never disables the direct runtime baseline" {
drop=$(hack/select-install.sh --disabled "postgres")
for b in cozystack.cozystack-engine cozystack.flux-shard-operator cozystack.cozystack-basics \
cozystack.tenant-application cozystack.etcd-application \
cozystack.ingress-application cozystack.monitoring-application \
cozystack.seaweedfs-application cozystack.linstor \
cozystack.kubevirt-cdi cozystack.metallb cozystack.capi-operator \
cozystack.capi-provider-bootstrap-kubeadm \
cozystack.capi-provider-core cozystack.capi-provider-cp-kamaji \
cozystack.capi-provider-infra-kubevirt cozystack.keycloak \
cozystack.keycloak-operator cozystack.multus \
cozystack.cozystack-scheduler cozystack.linstor-scheduler \
cozystack.flux-plunger cozystack.cozy-proxy cozystack.ouroboros; do
case " $drop " in
*" $b "*) echo "baseline package '$b' is in the disable list" >&2; exit 1 ;;
esac
done
}
@test "runtime baseline reaches every derived controller and CRD prerequisite" {
keep=$(hack/select-install.sh "postgres")
for required in cozystack.cert-manager cozystack.networking \
cozystack.gateway-api-crds cozystack.etcd-operator \
cozystack.grafana-operator cozystack.postgres-operator \
cozystack.victoria-metrics-operator cozystack.prometheus-operator-crds \
cozystack.vertical-pod-autoscaler cozystack.reloader \
cozystack.snapshot-controller cozystack.gateway-application \
cozystack.info-application cozystack.kamaji cozystack.kubevirt; do
if ! printf '%s\n' "$keep" | tr ' ' '\n' | grep -Fxq "$required"; then
echo "runtime baseline is missing derived prerequisite '$required'" >&2
exit 1
fi
done
}
@test "runtime baseline fails closed when a provider prerequisite edge is lost" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.kamaji"]' \
"$tmp/sources/capi-provider-cp-kamaji.yaml"
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh postgres "$tmp/sources" >"$out" 2>"$err"; then
echo "expected a missing Kamaji prerequisite to fail closed" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "runtime baseline dependency closure is missing: cozystack.kamaji" "$err"
cp packages/core/platform/sources/capi-provider-cp-kamaji.yaml \
"$tmp/sources/capi-provider-cp-kamaji.yaml"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.kubevirt"]' \
"$tmp/sources/capi-provider-infra-kubevirt.yaml"
if hack/select-install.sh postgres "$tmp/sources" >"$out" 2>"$err"; then
echo "expected a missing KubeVirt prerequisite to fail closed" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "runtime baseline dependency closure is missing: cozystack.kubevirt" "$err"
rm -rf "$tmp"
}
@test "runtime baseline fails closed when storage prerequisite edges are lost" {
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
yq -i '(.spec.variants[].dependsOn) -= ["cozystack.reloader", "cozystack.snapshot-controller"]' \
"$tmp/sources/linstor.yaml"
out="$tmp/out"; err="$tmp/err"
if hack/select-install.sh postgres "$tmp/sources" >"$out" 2>"$err"; then
echo "expected missing LINSTOR prerequisites to fail closed" >&2
exit 1
fi
[ ! -s "$out" ]
grep -Fq "cozystack.reloader" "$err"
grep -Fq "cozystack.snapshot-controller" "$err"
rm -rf "$tmp"
}
@test "generated Helm values fixture matches the current disable list" {
generated_raw=$(hack/select-install.sh --disabled postgres)
generated=$(printf '%s\n' "$generated_raw" | tr ' ' '\n' | sort)
fixture_raw=$(yq -r '.bundles.disabledPackages[]' \
packages/core/platform/tests/fixtures/selective-install-postgres-values.yaml)
fixture=$(printf '%s\n' "$fixture_raw" | sort)
if [ "$generated" != "$fixture" ]; then
echo "selective-install-postgres-values.yaml is stale; regenerate it from select-install.sh" >&2
exit 1
fi
tmp=$(mktemp -d)
cp -r packages/core/platform/sources "$tmp/sources"
cat > "$tmp/sources/fixture-drift.yaml" <<'YAML'
apiVersion: cozystack.io/v1alpha1
kind: PackageSource
metadata:
name: cozystack.fixture-drift
spec:
variants:
- name: default
YAML
drifted_raw=$(hack/select-install.sh --disabled postgres "$tmp/sources")
drifted=$(printf '%s\n' "$drifted_raw" | tr ' ' '\n' | sort)
if [ "$drifted" = "$fixture" ]; then
echo "a new PackageSource did not invalidate the generated fixture" >&2
exit 1
fi
printf '%s\n' "$drifted" | grep -Fxq cozystack.fixture-drift
rm -rf "$tmp"
}
# An empty closure would make the complement the entire platform. As an install
# instruction that is a platform with no packages, so it must refuse rather than
# emit it.
@test "disabled mode: refuses an empty selection" {
out=$(mktemp); err=$(mktemp)
if hack/select-install.sh --disabled "" >"$out" 2>"$err"; then
echo "expected a non-zero exit for an empty selection, got: $(cat "$out")" >&2
rm -f "$out" "$err"; exit 1
fi
grep -q "refusing to emit a disable list" "$err" \
|| { echo "expected the refusal message, got: $(cat "$err")" >&2; rm -f "$out" "$err"; exit 1; }
rm -f "$out" "$err"
}