#!/usr/bin/env bash set -euo pipefail repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" tmp="$(mktemp -d "${repo_root}/.luci-deployment-test.XXXXXX")" trap 'rm -rf "${tmp}"' EXIT fail() { echo "test failed: $*" >&2 exit 1 } rendered_quadlet="${tmp}/quadlets" mkdir "${rendered_quadlet}" cp "${repo_root}/modules/klops/generated/luci.container" "${rendered_quadlet}/luci.container" QUADLET_UNIT_DIRS="${rendered_quadlet}" /usr/lib/systemd/system-generators/podman-system-generator --user --dryrun context="${tmp}/context" mkdir -p "${context}" git init -q "${context}" git -C "${context}" config user.email test@example.invalid git -C "${context}" config user.name Test printf 'FROM scratch\n' >"${context}/Containerfile" git -C "${context}" add Containerfile git -C "${context}" commit -qm initial printf 'package main\n' >"${context}/untracked.go" if output="$("${repo_root}/scripts/local/build-remote-image.sh" example.invalid 22 ci localhost/test:latest "${context}" 2>&1)"; then fail "untracked build context was accepted" fi [[ "${output}" == *"image context has untracked files"* && "${output}" == *"untracked.go"* ]] || fail "untracked build-context rejection was incomplete" if LUCI_IMAGE=localhost/other:latest "${repo_root}/scripts/local/load-luci-image.sh" example.invalid 22 >/dev/null 2>&1; then fail "LUCI_IMAGE override was accepted" fi key_program="$(sed -n "/^awk '\$/,/^' \/root\/\.ssh\/authorized_keys/p" "${repo_root}/scripts/remote/setup-ci-user.sh" | sed '1d;$d')" source_key='from="192.0.2.0/24",expiry-time="20300101",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITest test@example.invalid' [[ "$(printf '%s\n' "${source_key}" | awk "${key_program}")" == "${source_key}" ]] || fail "authorized-key restrictions were not preserved" mkdir -p "${tmp}/bin" "${tmp}/data/ci/ssh" "${tmp}/run/sshd" cat >"${tmp}/bin/install" <<'SCRIPT' #!/usr/bin/env bash exit 0 SCRIPT cat >"${tmp}/bin/sshd" <<'SCRIPT' #!/usr/bin/env bash for ((i = 1; i <= $#; i++)); do if [[ "${!i}" == -f ]]; then next=$((i + 1)) cp "${!next}" "${TEST_SSHD_CONFIG}" fi done [[ " $* " == *" -t "* ]] && exit 0 [[ " $* " == *" -D "* ]] && exit 0 exit 1 SCRIPT cat >"${tmp}/bin/luci" <<'SCRIPT' #!/usr/bin/env bash printf '%s\n' "$*" >>"${TEST_LUCI_LOG}" [[ "$1" == env-check ]] && exit 0 [[ "$1" == serve ]] && sleep 30 SCRIPT chmod 0755 "${tmp}/bin/"* sed \ -e "s|/usr/local/bin/luci|${tmp}/bin/luci|g" \ -e "s|/usr/sbin/sshd|${tmp}/bin/sshd|g" \ -e "s|/data/ci|${tmp}/data/ci|g" \ -e "s|/run/luci-sshd.conf|${tmp}/run/luci-sshd.conf|g" \ -e "s|/run/luci-ssh-command|${tmp}/run/luci-ssh-command|g" \ -e "s|/run/sshd|${tmp}/run/sshd|g" \ "${repo_root}/services/luci/entrypoint.sh" >"${tmp}/entrypoint.sh" chmod 0755 "${tmp}/entrypoint.sh" PATH="${tmp}/bin:${PATH}" \ TEST_SSHD_CONFIG="${tmp}/sshd_config" \ TEST_LUCI_LOG="${tmp}/luci.log" \ LUCI_DATA_DIR="${tmp}/data/ci" \ LUCI_REPO_ROOTS=/repos \ LUCI_HTTP_ADDR=0.0.0.0:8080 \ LUCI_BASE_URL=https://deployment.invalid \ LUCI_INBOX_DIR="${tmp}/data/ci/inbox" \ LUCI_PODMAN_SOCKET=unix:///podman.sock \ LUCI_SSH_ADDR=0.0.0.0:2223 \ LUCI_SSH_AUTHORIZED_KEYS="${tmp}/data/ci/authorized_keys" \ LUCI_THEME_CSS="${tmp}/data/ci/nugu.css" \ LUCI_DEFAULT_TIMEOUT=41m \ LUCI_DEFAULT_MEMORY=3g \ LUCI_DEFAULT_CPUS=4 \ LUCI_CACHE_MAX_VERSIONS=5 \ LUCI_CACHE_MAX_BYTES=3GiB \ LUCI_LOG_LEVEL=debug \ LUCI_PUBLISH_REGISTRY_AUTH_FILE=/run/secrets/zot-auth.json \ LUCI_PUBLISH_PKG_ROOT=/data/pkg \ LUCI_PUBLISH_SITE_ROOT=/data/sites \ "${tmp}/entrypoint.sh" sshd -t -f "${tmp}/sshd_config" grep -qx 'SetEnv LUCI_BASE_URL="https://deployment.invalid"' "${tmp}/sshd_config" || fail "runtime base URL was not forwarded to SSH" grep -qx 'SetEnv LUCI_DEFAULT_TIMEOUT="41m"' "${tmp}/sshd_config" || fail "runtime timeout was not forwarded to SSH" for name in LUCI_DATA_DIR LUCI_REPO_ROOTS LUCI_HTTP_ADDR LUCI_BASE_URL LUCI_INBOX_DIR LUCI_PODMAN_SOCKET LUCI_SSH_ADDR LUCI_SSH_AUTHORIZED_KEYS LUCI_THEME_CSS LUCI_DEFAULT_TIMEOUT LUCI_DEFAULT_MEMORY LUCI_DEFAULT_CPUS LUCI_CACHE_MAX_VERSIONS LUCI_CACHE_MAX_BYTES LUCI_LOG_LEVEL LUCI_PUBLISH_REGISTRY_AUTH_FILE LUCI_PUBLISH_PKG_ROOT LUCI_PUBLISH_SITE_ROOT; do grep -q "^SetEnv ${name}=" "${tmp}/sshd_config" || fail "${name} was not forwarded to SSH" done grep -qx 'env-check' "${tmp}/luci.log" || fail "entrypoint did not validate Luci configuration" grep -qx 'serve' "${tmp}/luci.log" || fail "entrypoint did not start Luci" grep -qx 'export LUCI_REPO_ROOTS=/repos' "${tmp}/run/luci-ssh-command" || fail "forced SSH command did not receive runtime environment" grep -qx "exec ${tmp}/bin/luci ssh" "${tmp}/run/luci-ssh-command" || fail "forced SSH command did not dispatch Luci" cat >"${tmp}/bin/id" <<'SCRIPT' #!/usr/bin/env bash printf '1000\n' SCRIPT cat >"${tmp}/bin/runuser" <<'SCRIPT' #!/usr/bin/env bash for ((i = 1; i <= $#; i++)); do if [[ "${!i}" == -lc ]]; then next=$((i + 1)) exec bash -c "${!next}" fi done exit 1 SCRIPT cat >"${tmp}/bin/podman" <<'SCRIPT' #!/usr/bin/env bash printf '%s\n' "$*" >>"${TEST_PODMAN_LOG}" case "$1 $2" in 'image exists') exit 0 ;; 'port luci') printf '0.0.0.0:8080\n'; exit 0 ;; esac [[ "$1" == tag ]] && exit 0 exit 1 SCRIPT cat >"${tmp}/bin/systemctl" <<'SCRIPT' #!/usr/bin/env bash if [[ "${TEST_SYSTEMCTL_FAIL_RESTART:-}" == 1 && ( " $* " == *" restart "* || " $* " == *" start "* ) ]]; then count=0 [[ -f "${TEST_SYSTEMCTL_STATE}" ]] && count="$(<"${TEST_SYSTEMCTL_STATE}")" count=$((count + 1)) printf '%s\n' "${count}" >"${TEST_SYSTEMCTL_STATE}" (( count <= 2 )) && exit 1 fi exit 0 SCRIPT cat >"${tmp}/bin/curl" <<'SCRIPT' #!/usr/bin/env bash exit 1 SCRIPT cat >"${tmp}/bin/sleep" <<'SCRIPT' #!/usr/bin/env bash exit 0 SCRIPT chmod 0755 "${tmp}/bin/"* mkdir -p "${tmp}/home" "${tmp}/data/ci" sed \ -e "s|home_dir=\"/home/\\\${user}\"|home_dir=\"${tmp}/home\"|" \ -e "s|/data/ci|${tmp}/data/ci|g" \ "${repo_root}/scripts/remote/start-luci.sh" >"${tmp}/start-luci.sh" chmod 0755 "${tmp}/start-luci.sh" PATH="${tmp}/bin:${PATH}" TEST_PODMAN_LOG="${tmp}/podman.log" "${tmp}/start-luci.sh" backup grep -qx 'tag localhost/bugabinga-luci:latest localhost/bugabinga-luci:rollback' "${tmp}/podman.log" || fail "previous image was not retained for rollback" mkdir -p "${tmp}/home/.config/containers/systemd" : >"${tmp}/home/.config/containers/systemd/luci.container" : >"${tmp}/data/ci/authorized_keys" : >"${tmp}/podman.log" if PATH="${tmp}/bin:${PATH}" TEST_PODMAN_LOG="${tmp}/podman.log" "${tmp}/start-luci.sh" deploy; then fail "unhealthy Luci candidate was accepted" fi grep -qx 'tag localhost/bugabinga-luci:rollback localhost/bugabinga-luci:latest' "${tmp}/podman.log" || fail "unhealthy candidate was not rolled back" printf 'concurrent-key\n' >"${tmp}/data/ci/authorized_keys" : >"${tmp}/podman.log" if PATH="${tmp}/bin:${PATH}" TEST_PODMAN_LOG="${tmp}/podman.log" TEST_SYSTEMCTL_FAIL_RESTART=1 TEST_SYSTEMCTL_STATE="${tmp}/systemctl-state" "${tmp}/start-luci.sh" deploy; then fail "restart-failed Luci candidate was accepted" fi grep -qx 'tag localhost/bugabinga-luci:rollback localhost/bugabinga-luci:latest' "${tmp}/podman.log" || fail "restart-failed candidate was not rolled back" grep -qx 'concurrent-key' "${tmp}/data/ci/authorized_keys" || fail "temporary health key truncated existing keys" [[ "$(wc -l <"${tmp}/data/ci/authorized_keys")" -eq 1 ]] || fail "temporary health key was not removed" printf 'ok: Luci deployment checks\n'