rpki/deploy/docker-installer/tests/upgrade_env_contract_test.sh

207 lines
7.4 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
INSTALLER_SOURCE="$(cd "$SCRIPT_DIR/.." && pwd)"
TEST_ROOT="$(mktemp -d "${TMPDIR:-/tmp}/ours-rp-installer-env-contract.XXXXXX")"
cleanup() {
rm -rf "$TEST_ROOT"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$*" >&2
exit 1
}
assert_equals() {
local expected="$1"
local actual="$2"
local message="$3"
[[ "$actual" == "$expected" ]] || fail "$message (expected=$expected actual=$actual)"
}
assert_not_contains() {
local needle="$1"
local path="$2"
! grep -Fq "$needle" "$path" || fail "unexpected value in $path: $needle"
}
expect_exit() {
local expected_status="$1"
local output_path="$2"
shift 2
local actual_status
set +e
"$@" >"$output_path" 2>&1
actual_status=$?
set -e
[[ "$actual_status" == "$expected_status" ]] || {
cat "$output_path" >&2 || true
fail "unexpected exit status (expected=$expected_status actual=$actual_status)"
}
}
host_arch() {
case "$(uname -m)" in
x86_64) printf 'amd64\n' ;;
aarch64) printf 'arm64\n' ;;
*) fail "unsupported test host architecture: $(uname -m)" ;;
esac
}
env_value() {
local env_path="$1"
local key="$2"
(
# shellcheck disable=SC1090
source "$INSTALLER_SOURCE/scripts/common.sh"
env_file_value "$env_path" "$key"
)
}
create_fixture() {
local name="$1"
local fixture="$TEST_ROOT/$name"
local arch
arch="$(host_arch)"
mkdir -p "$fixture"
cp -a "$INSTALLER_SOURCE"/. "$fixture"/
rm -rf "$fixture/tests"
mkdir -p "$fixture/images" "$fixture/fake-bin"
sed -i \
-e "s|__PACKAGE_ARCH__|$arch|g" \
-e "s|__PACKAGE_PLATFORM__|linux/$arch|g" \
-e "s|__RUNTIME_IMAGE__|ours-rp-runtime-$arch:new|g" \
-e "s|__METRICS_IMAGE__|ours-rp-metrics-$arch:new|g" \
-e "s|__HOST_DATA_DIR__|$fixture/data|g" \
"$fixture/.env.example"
cat > "$fixture/PACKAGE-MANIFEST.env" <<EOF
PACKAGE_ARCH=$arch
PACKAGE_PLATFORM=linux/$arch
EOF
cat > "$fixture/fake-bin/docker" <<'EOF'
#!/usr/bin/env bash
printf '%s\n' "$*" >> "$FAKE_DOCKER_LOG"
if [[ "${1:-}" == "compose" && "${2:-}" == "version" ]]; then
exit 0
fi
exit 99
EOF
chmod +x "$fixture/fake-bin/docker"
for command in jq rsync curl gzip tar; do
cat > "$fixture/fake-bin/$command" <<'EOF'
#!/usr/bin/env bash
exit 0
EOF
chmod +x "$fixture/fake-bin/$command"
done
printf '%s\n' "$fixture"
}
run_upgrade() {
local fixture="$1"
local old_env="$2"
shift 2
(
cd "$fixture"
export PATH="$fixture/fake-bin:$PATH"
export FAKE_DOCKER_LOG="$fixture/fake-docker.log"
"$@" ./scripts/upgrade.sh --reuse-env-from "$old_env"
)
}
run_upgrade_without_reuse() {
local fixture="$1"
(
cd "$fixture"
export PATH="$fixture/fake-bin:$PATH"
export FAKE_DOCKER_LOG="$fixture/fake-docker.log"
./scripts/upgrade.sh
)
}
new_fixture_with_old_env() {
local name="$1"
local fixture
fixture="$(create_fixture "$name")"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=120
RPKI_IMAGE=ours-rp-runtime-old:old
REMOVED_LEGACY_KEY=legacy-value
EOF
printf '%s\n' "$fixture"
}
fixture="$(create_fixture template-defaults)"
expect_exit 99 "$fixture/template.log" run_upgrade_without_reuse "$fixture"
assert_equals "600" "$(env_value "$fixture/.env" INTERVAL_SECS)" "template default must be retained without reuse"
fixture="$(new_fixture_with_old_env default-old)"
expect_exit 99 "$fixture/default.log" run_upgrade "$fixture" "$fixture/old.env"
assert_equals "120" "$(env_value "$fixture/.env" INTERVAL_SECS)" "old value must override template default"
assert_equals "ours-rp-runtime-$(host_arch):new" "$(env_value "$fixture/.env" RPKI_IMAGE)" "standard upgrade must retain package image"
assert_equals "10" "$(env_value "$fixture/.env" METRICS_POLL_SECS)" "template default must fill a key absent from old environment"
! grep -q '^REMOVED_LEGACY_KEY=' "$fixture/.env" || fail "removed old key was copied into target environment"
grep -q 'reused_keys=INTERVAL_SECS' "$fixture/default.log" || fail "reused key summary missing"
grep -q 'ignored_removed_old_keys=REMOVED_LEGACY_KEY' "$fixture/default.log" || fail "removed key summary missing"
grep -q 'template_only_keys=' "$fixture/default.log" || fail "template key summary missing"
fixture="$(new_fixture_with_old_env invocation-overrides)"
expect_exit 99 "$fixture/override.log" run_upgrade "$fixture" "$fixture/old.env" env INTERVAL_SECS=300 GRAFANA_ADMIN_PASSWORD='secret with space'
assert_equals "300" "$(env_value "$fixture/.env" INTERVAL_SECS)" "invocation value must override old value"
assert_equals "secret with space" "$(env_value "$fixture/.env" GRAFANA_ADMIN_PASSWORD)" "invocation value must preserve shell-sensitive content"
grep -q 'command_override_keys=INTERVAL_SECS,GRAFANA_ADMIN_PASSWORD' "$fixture/override.log" || fail "override key summary missing"
assert_not_contains 'secret with space' "$fixture/override.log"
fixture="$(new_fixture_with_old_env invocation-special-characters)"
special_password="O'Reilly"
special_password+=' $literal `tick` slash\'
expect_exit 99 "$fixture/special.log" run_upgrade "$fixture" "$fixture/old.env" env GRAFANA_ADMIN_PASSWORD="$special_password"
assert_equals "$special_password" "$(env_value "$fixture/.env" GRAFANA_ADMIN_PASSWORD)" "invocation value with dotenv-sensitive characters must round-trip"
assert_not_contains "$special_password" "$fixture/special.log"
fixture="$(create_fixture existing-target-env)"
cp "$fixture/.env.example" "$fixture/.env"
sed -i 's/^INTERVAL_SECS=.*/INTERVAL_SECS=180/' "$fixture/.env"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=120
EOF
expect_exit 99 "$fixture/existing.log" run_upgrade "$fixture" "$fixture/old.env" env INTERVAL_SECS=300
assert_equals "300" "$(env_value "$fixture/.env" INTERVAL_SECS)" "invocation value must override an existing target environment"
fixture="$(new_fixture_with_old_env explicit-empty)"
expect_exit 1 "$fixture/empty.log" run_upgrade "$fixture" "$fixture/old.env" env INTERVAL_SECS=
[[ ! -s "$fixture/fake-docker.log" ]] || fail "explicit empty value reached Docker"
grep -q 'INTERVAL_SECS' "$fixture/empty.log" || fail "empty-value error did not identify key"
fixture="$(create_fixture empty-old-value)"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=
EOF
expect_exit 1 "$fixture/old-empty.log" run_upgrade "$fixture" "$fixture/old.env"
[[ ! -s "$fixture/fake-docker.log" ]] || fail "empty reused value reached Docker"
grep -q 'INTERVAL_SECS' "$fixture/old-empty.log" || fail "empty reused value error did not identify key"
fixture="$(create_fixture empty-template)"
sed -i 's/^INTERVAL_SECS=.*/INTERVAL_SECS=/' "$fixture/.env.example"
cat > "$fixture/old.env" <<'EOF'
INTERVAL_SECS=120
EOF
expect_exit 1 "$fixture/template-empty.log" run_upgrade "$fixture" "$fixture/old.env"
[[ ! -s "$fixture/fake-docker.log" ]] || fail "empty template default reached Docker"
grep -q 'INTERVAL_SECS' "$fixture/template-empty.log" || fail "empty template error did not identify key"
fixture="$(new_fixture_with_old_env package-guard)"
arch="$(host_arch)"
other_arch="amd64"
[[ "$arch" == "amd64" ]] && other_arch="arm64"
expect_exit 1 "$fixture/arch.log" run_upgrade "$fixture" "$fixture/old.env" env PACKAGE_ARCH="$other_arch"
[[ ! -s "$fixture/fake-docker.log" ]] || fail "package architecture mismatch reached Docker"
grep -q 'mismatches manifest PACKAGE_ARCH' "$fixture/arch.log" || fail "package architecture guard did not reject override"
printf 'PASS: Docker installer upgrade environment contract\n'