mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
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>
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 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"
|
|
}
|