mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-26 13:07:04 +09:00
The Redis backup demo now takes APP_KIND=Valkey, which switches the app kind and the chart prefix the helpers derive sentinel and Secret names from. The demo BackupClass routes Valkey to the same demo strategy. The valkey suite gains valkey-backup-roundtrip, which drives that demo end to end: seed a marker, back it up, restore it in place and into a copy. It writes to tenant-root's seaweedfs through seaweedfs-s3, so a seaweedfs change now selects the valkey suite too, and since the round-trip runs examples/backups/redis as its harness, an edit there selects both the redis and the valkey suites. The reduced E2E install keeps the backup stack for the valkey suite, as it does for the other backup round-trip suites. Signed-off-by: Andrey Kolkov <androndo@gmail.com> Assisted-By: LLM
751 lines
30 KiB
Bash
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; 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"
|
|
}
|