CI / lint (pull_request) Successful in 12s
CI / typecheck (pull_request) Successful in 9s
CI / test (pull_request) Successful in 16s
CI / build (pull_request) Successful in 11s
CI / package (pull_request) Successful in 14s
CI / integration (pull_request) Successful in 7m26s
CI / release (pull_request) Skipped
76 lines
4.8 KiB
Bash
Executable File
76 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Checks that Signage's CI cleans up every stack it starts, even when the runner kills a job (the
|
|
# platform's rule: docs/development/security.md, "Disposable stacks never outlive their job").
|
|
#
|
|
# bash ci/check-cleanup.sh
|
|
#
|
|
# The `integration` job is the one that starts Docker stacks. It must have its own
|
|
# EKKLESIA_CI_JOB_KEY, sweep stale CI stacks before it starts anything, and remove its own in a final
|
|
# `if: always()` step. The pinned platform smoke owns and labels every image and container.
|
|
set -Eeuo pipefail
|
|
|
|
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
workflow="$root/.gitea/workflows/ci.yml"
|
|
failures=0
|
|
check() {
|
|
if eval "$1"; then
|
|
echo "ok $2"
|
|
else
|
|
echo "FAIL $2"
|
|
failures=$((failures + 1))
|
|
fi
|
|
}
|
|
|
|
# The integration job's lines, from its key to the next job (or the end of the file).
|
|
job="$(awk '/^ integration:$/ { inside = 1; print; next } inside && /^ [a-z][a-z-]*:$/ { exit } inside { print }' "$workflow")"
|
|
line_of() { grep -n -F -- "$1" <<<"$job" | head -1 | cut -d: -f1 || true; }
|
|
|
|
stale="$(line_of 'ci-sweep.sh --stale')"
|
|
run="$(line_of 'bash ci/integration.sh')"
|
|
own="$(line_of 'ci-sweep.sh --job')"
|
|
last_step="$(grep -n -- " - name:" <<<"$job" | tail -1 | cut -d: -f1 || true)"
|
|
|
|
check '[[ -n "$job" ]]' "the workflow has an integration job"
|
|
check '! grep -q "if: false" <<<"$job"' "the integration job is enabled"
|
|
check 'grep -q "EKKLESIA_CI_JOB_KEY:" <<<"$job"' "the integration job names its stacks with its own key"
|
|
check '[[ -n "$stale" && -n "$run" ]] && ((stale < run))' "it sweeps stale CI stacks before it starts anything"
|
|
check '[[ -n "$own" && -n "$run" ]] && ((own > run)) && ((own > last_step))' \
|
|
"its last step removes its own stacks"
|
|
check 'sed -n "${last_step},\$p" <<<"$job" | grep -q "if: always()"' "that last step always runs"
|
|
check 'grep -q "EKKLESIA_SMOKE_MODULE_RELEASE_DIR" "$root/ci/integration.sh" && grep -q "bash scripts/smoke-deploy.sh" "$root/ci/integration.sh"' \
|
|
"the integration delegates its external package to the platform smoke"
|
|
check 'grep -Eq "^EKKLESIA_PLATFORM_REF=refs/(pull/[0-9]+/head|heads/main)$" "$root/ci/platform.env" && grep -Eq "^EKKLESIA_PLATFORM_COMMIT=[0-9a-f]{40}$" "$root/ci/platform.env"' \
|
|
"the platform harness is pinned by full ref and commit"
|
|
check 'grep -q "rev-parse refs/remotes/pinned" "$root/ci/fetch-platform.sh" && grep -q "does not resolve to EKKLESIA_PLATFORM_COMMIT" "$root/ci/fetch-platform.sh"' \
|
|
"the fetch verifies that the pinned ref resolves to the pinned commit"
|
|
|
|
# The package job is the only place that builds the canonical archive. Both integration and release
|
|
# receive the same Gitea Actions v3 artifact, and release cannot run until every required job passed.
|
|
package_job="$(awk '/^ package:$/ { inside = 1; print; next } inside && /^ [a-z][a-z-]*:$/ { exit } inside { print }' "$workflow")"
|
|
release_job="$(awk '/^ release:$/ { inside = 1; print; next } inside && /^ [a-z][a-z-]*:$/ { exit } inside { print }' "$workflow")"
|
|
check 'grep -q "tags:" "$workflow" && grep -q '\"v\*\"' "$workflow"' \
|
|
"v tags start the gated workflow"
|
|
check 'grep -q "bash ci/package.sh" <<<"$package_job" && grep -q "actions/upload-artifact@c6a3b2bd78b3985e4b2f15397fec357f0fd808de" <<<"$package_job"' \
|
|
"the package job builds and transfers one canonical artifact"
|
|
check 'grep -q "actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b" <<<"$job" && ! grep -Eq "ci/package.sh|ekklesia-pack-module" <<<"$job"' \
|
|
"integration downloads that artifact and never rebuilds it"
|
|
check 'grep -q "needs.package.outputs.sha256" <<<"$job" && grep -q "GITHUB_OUTPUT" "$root/ci/package.sh"' \
|
|
"integration binds the downloaded bytes to the package job's digest"
|
|
check 'for required in lint typecheck test build package integration; do grep -q " - $required" <<<"$release_job" || exit 1; done' \
|
|
"release waits for every required job"
|
|
check 'grep -q "if: startsWith(github.ref, '\''refs/tags/v'\'')" <<<"$release_job" && grep -q "contents: write" <<<"$release_job"' \
|
|
"only a v tag gives the release job write permission"
|
|
check 'grep -q "actions/download-artifact@ad191675b41f6a5b46da9a048cb6893812da158b" <<<"$release_job" && ! grep -Eq "ci/package.sh|ekklesia-pack-module" <<<"$release_job"' \
|
|
"release downloads the tested artifact and never rebuilds it"
|
|
check 'grep -q "EXPECTED_PACKAGE_SHA256:.*needs.package.outputs.sha256" <<<"$release_job"' \
|
|
"release binds its selected and uploaded bytes to the package job's digest"
|
|
check 'grep -q "ekklesia-corresponding-source verify" <<<"$release_job" && grep -q "release-assets.py upload" <<<"$release_job"' \
|
|
"the redistribution gate runs before the exact assets are uploaded"
|
|
check 'grep -q "GITHUB_TOKEN:.*github.token" <<<"$release_job"' \
|
|
"upload uses Gitea's automatic per-job token"
|
|
|
|
((failures == 0)) || {
|
|
echo "check-cleanup: ${failures} check(s) failed." >&2
|
|
exit 1
|
|
}
|