mirror of
https://github.com/Sea-Haven-Industries/shoc-backend.git
synced 2026-09-30 06:03:12 +00:00
131 lines
4.8 KiB
Bash
Executable file
131 lines
4.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
||
#
|
||
# governance-check.sh — local/CI parity governance gate for the Seahaven backend.
|
||
#
|
||
# Locally this runs G1–G5, G10, G11, promotion-script tests, and live G13.
|
||
# The architecture job in ci.yml sets GOVERNANCE_SKIP_BUILD_TEST=1 so G4/G5
|
||
# run only in the Build and test job. Live G13 runs on pull_request and local
|
||
# invocations; it skips merge_group and push.
|
||
#
|
||
# Usage:
|
||
# bash scripts/governance-check.sh
|
||
# BASE_REF=origin/main bash scripts/governance-check.sh
|
||
# BASE_REF=<base-sha> HEAD_REF=<head-sha> bash scripts/governance-check.sh
|
||
set -euo pipefail
|
||
|
||
SOLUTION="SeaHavenIndustries.sln"
|
||
ARCH_TEST_PROJECT="Api.SeaHavenIndustries.Tests/Api.SeaHavenIndustries.Tests.csproj"
|
||
LIVE_ROOTS=(terraform/live/dev terraform/live/staging)
|
||
|
||
log() { printf '\n\033[1m== %s ==\033[0m\n' "$1"; }
|
||
ok() { printf '\033[32mPASS\033[0m %s\n' "$1"; }
|
||
bad() { printf '\033[31mFAIL\033[0m %s\n' "$1"; }
|
||
|
||
if [[ -n "${DOTNET_BIN:-}" ]]; then
|
||
DOTNET="$DOTNET_BIN"
|
||
elif command -v dotnet >/dev/null 2>&1; then
|
||
DOTNET="$(command -v dotnet)"
|
||
elif [[ -x "$HOME/.dotnet/dotnet" ]]; then
|
||
DOTNET="$HOME/.dotnet/dotnet"
|
||
else
|
||
bad "dotnet is unavailable; set DOTNET_BIN or install the repository SDK."
|
||
exit 1
|
||
fi
|
||
|
||
# Comparison point for changed-file formatting. Default to main locally; CI
|
||
# overrides BASE_REF/HEAD_REF with the PR base/head SHAs.
|
||
BASE_REF="${BASE_REF:-origin/main}"
|
||
HEAD_REF="${HEAD_REF:-HEAD}"
|
||
|
||
# Resolve the base ref before using it for a diff.
|
||
if ! git rev-parse --verify --quiet "${BASE_REF}^{commit}" >/dev/null; then
|
||
bad "G3: BASE_REF '${BASE_REF}' does not resolve to a commit (run: git fetch origin)."
|
||
exit 1
|
||
fi
|
||
|
||
# Diff the change set from the merge base, not from the base tip. A two-dot
|
||
# diff against a moving base reports everything the base gained after the
|
||
# branch point as if this change reverted it, so a branch behind main that
|
||
# touches C# would fail G13 whenever main had merged Terraform in the meantime.
|
||
# The merge queue no longer requires branches to be current, so this matters.
|
||
DIFF_BASE="$(git merge-base "${BASE_REF}" "${HEAD_REF}")" || {
|
||
bad "G3: no merge base between '${BASE_REF}' and '${HEAD_REF}'."
|
||
exit 1
|
||
}
|
||
|
||
log "G1: restore"
|
||
"$DOTNET" restore "$SOLUTION"
|
||
ok "G1: restore"
|
||
|
||
log "G2: architecture boundary tests (dependency direction)"
|
||
"$DOTNET" test "$ARCH_TEST_PROJECT" \
|
||
--no-restore \
|
||
--filter "FullyQualifiedName~ArchitectureTests" \
|
||
--nologo
|
||
ok "G2: ArchitectureTests"
|
||
|
||
log "G3: changed-file formatting (${BASE_REF}...${HEAD_REF}, merge base ${DIFF_BASE:0:7})"
|
||
changed_cs=()
|
||
while IFS= read -r f; do
|
||
changed_cs+=("$f")
|
||
done < <(
|
||
git diff --name-only --diff-filter=ACMR "${DIFF_BASE}" "${HEAD_REF}" -- '*.cs'
|
||
)
|
||
|
||
if (( ${#changed_cs[@]} == 0 )); then
|
||
printf '\033[33mSKIP\033[0m G3: no changed C# files between %s...%s\n' "${BASE_REF}" "${HEAD_REF}"
|
||
else
|
||
printf ' checking %d changed C# file(s)\n' "${#changed_cs[@]}"
|
||
"$DOTNET" format "$SOLUTION" \
|
||
--no-restore \
|
||
--verify-no-changes \
|
||
--include "${changed_cs[@]}"
|
||
ok "G3: changed-file formatting"
|
||
fi
|
||
|
||
if [[ "${GOVERNANCE_SKIP_BUILD_TEST:-}" == "1" ]]; then
|
||
printf '\033[33mSKIP\033[0m G4: Release build (CI Build and test job owns it)\n'
|
||
printf '\033[33mSKIP\033[0m G5: full test suite (CI Build and test job owns it)\n'
|
||
else
|
||
log "G4: Release build"
|
||
"$DOTNET" build "$SOLUTION" -c Release --no-restore --nologo
|
||
ok "G4: Release build"
|
||
|
||
log "G5: full test suite"
|
||
"$DOTNET" test "$SOLUTION" -c Release --no-build --nologo
|
||
ok "G5: full test suite"
|
||
fi
|
||
|
||
log "G10: Terraform import plan safety"
|
||
python scripts/test-terraform-import-plan-check.py
|
||
ok "G10: Terraform import plan safety"
|
||
|
||
log "Release promotion scripts"
|
||
python3 scripts/test_next_release_tag.py
|
||
python3 scripts/test_require_commit_checks.py
|
||
python3 scripts/test_check_app_terraform_isolation.py
|
||
ok "Release promotion scripts"
|
||
|
||
log "G11: Terraform formatting and validation"
|
||
if ! command -v terraform >/dev/null 2>&1; then
|
||
bad "G11: terraform is unavailable; install Terraform or set PATH."
|
||
exit 1
|
||
fi
|
||
terraform fmt -check -recursive terraform
|
||
for live_root in "${LIVE_ROOTS[@]}"; do
|
||
terraform -chdir="${live_root}" init -backend=false -input=false -lockfile=readonly -no-color
|
||
terraform -chdir="${live_root}" validate -no-color
|
||
done
|
||
ok "G11: Terraform formatting and validation"
|
||
|
||
if [[ -n "${GITHUB_EVENT_NAME:-}" && "${GITHUB_EVENT_NAME}" != "pull_request" ]]; then
|
||
printf '\033[33mSKIP\033[0m G13: live isolation is a pull-request property (event: %s)\n' "${GITHUB_EVENT_NAME}"
|
||
else
|
||
log "G13: application and Terraform isolation (${BASE_REF}...${HEAD_REF}, merge base ${DIFF_BASE:0:7})"
|
||
python3 scripts/check_app_terraform_isolation.py < <(
|
||
git diff --name-only --diff-filter=ACMR "${DIFF_BASE}" "${HEAD_REF}"
|
||
)
|
||
ok "G13: application and Terraform isolation"
|
||
fi
|
||
|
||
log "governance-check: all required repository gates passed"
|