mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 06:53:12 +00:00
Some checks failed
Deploy dev content / Deploy shoc-frontend-new-dev through Terraform (push) Has been cancelled
* ci(cd): convert SPA hosting to handbook HCP and GitHub content CD Give HCP the bucket and CloudFront with an empty origin path. GitHub owns bucket-root sync and invalidation so merge-to-main and a human staging tag can deploy without creating HCP runs. G13 fails PRs that mix terraform/ with deployable application files. * ci: run Frontend checks and Terraform CI on PRs to main and dev Match backend 148 so a PR targeting origin/dev still gets the required checks. Push remains main only. * refactor(terraform): keep live/dev and live/staging as HCP roots Leave the adopted working directories in place so this CD PR does not retarget two live HCP workspaces. Flattening stays a later change. * style: prettier terraform-validate.mjs * fix(terraform): pin githubdeploy assume-role policy in import checker Reject controlled role updates whose trust document is not the rendered GitHub OIDC policy, matching the bucket-policy pin.
236 lines
7.8 KiB
Bash
Executable file
236 lines
7.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Stubbed aws/curl tests for scripts/verify-cloudfront-release.sh.
|
|
set -euo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
VERIFY="${ROOT}/scripts/verify-cloudfront-release.sh"
|
|
NEW_HASH="1111111111111111111111111111111111111111111111111111111111111111"
|
|
INDEX_HTML=$'<!doctype html><html><head><script type="module" src="/assets/app.js"></script></head><body></body></html>\n'
|
|
INDEX_HASH="$(printf '%s' "${INDEX_HTML}" | python3 -c 'import hashlib,sys; print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())')"
|
|
|
|
failures=0
|
|
assert_exit() {
|
|
local name="$1" expected="$2" got="$3" log="$4"
|
|
if [[ "${got}" != "${expected}" ]]; then
|
|
echo "FAIL: ${name}: expected exit ${expected}, got ${got}" >&2
|
|
sed -n '1,80p' "${log}" >&2
|
|
failures=$((failures + 1))
|
|
else
|
|
echo "PASS: ${name}"
|
|
fi
|
|
}
|
|
|
|
make_stubs() {
|
|
local bin="$1"
|
|
mkdir -p "${bin}"
|
|
cat > "${bin}/aws" << 'AWS'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
cat "${STUB_STATE}/distribution.json"
|
|
AWS
|
|
cat > "${bin}/curl" << 'CURL'
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
state_dir="${STUB_STATE}"
|
|
method="GET"
|
|
url=""
|
|
dump=""
|
|
output=""
|
|
write_out=""
|
|
args=("$@")
|
|
i=0
|
|
while [[ $i -lt ${#args[@]} ]]; do
|
|
arg="${args[$i]}"
|
|
case "${arg}" in
|
|
-X) i=$((i + 1)); method="${args[$i]}" ;;
|
|
-D) i=$((i + 1)); dump="${args[$i]}" ;;
|
|
-o) i=$((i + 1)); output="${args[$i]}" ;;
|
|
-w) i=$((i + 1)); write_out="${args[$i]}" ;;
|
|
-H|--max-time|-s|-S|-f|-fsS|-sS) ;;
|
|
http*) url="${arg}" ;;
|
|
esac
|
|
i=$((i + 1))
|
|
done
|
|
if [[ "${method}" == "OPTIONS" ]]; then
|
|
[[ -n "${dump}" ]] && printf 'HTTP/1.1 204 No Content\nAccess-Control-Allow-Origin: https://dev.seahaven.com\n\n' > "${dump}"
|
|
[[ -n "${write_out}" ]] && printf '204'
|
|
exit 0
|
|
fi
|
|
if [[ "${url}" == *"/assets/"* ]]; then
|
|
[[ -n "${dump}" ]] && printf 'HTTP/1.1 200 OK\nCache-Control: public,max-age=31536000,immutable\n\n' > "${dump}"
|
|
if [[ -f "${state_dir}/asset.js" ]]; then
|
|
body="$(cat "${state_dir}/asset.js")"
|
|
else
|
|
body='const api="https://api.dev.seahaven.com/api";'
|
|
fi
|
|
[[ -n "${output}" ]] && printf '%s' "${body}" > "${output}"
|
|
[[ -z "${output}" ]] && printf '%s' "${body}"
|
|
exit 0
|
|
fi
|
|
[[ -n "${dump}" ]] && printf 'HTTP/1.1 200 OK\nCache-Control: no-cache,no-store,must-revalidate\n\n' > "${dump}"
|
|
if [[ -n "${output}" ]]; then
|
|
cat "${state_dir}/index.html" > "${output}"
|
|
else
|
|
cat "${state_dir}/index.html"
|
|
fi
|
|
exit 0
|
|
CURL
|
|
chmod +x "${bin}/aws" "${bin}/curl"
|
|
}
|
|
|
|
dist_json() {
|
|
local status="$1" current_path="$2"
|
|
python3 -c 'import json,sys
|
|
status, path = sys.argv[1], sys.argv[2]
|
|
print(json.dumps({
|
|
"Distribution": {
|
|
"Status": status,
|
|
"DistributionConfig": {
|
|
"Origins": {"Items": [
|
|
{"Id": "current", "OriginPath": path}
|
|
]}
|
|
}
|
|
}
|
|
}))' "${status}" "${current_path}"
|
|
}
|
|
|
|
# 1. Empty origin, then propagates (InProgress -> Deployed, hash already matches).
|
|
{
|
|
dir="$(mktemp -d)"
|
|
make_stubs "${dir}/bin"
|
|
printf '%s' "${INDEX_HTML}" > "${dir}/index.html"
|
|
printf 'InProgress\n' > "${dir}/status"
|
|
cat > "${dir}/bin/aws" << AWS
|
|
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
status="\$(cat "${dir}/status")"
|
|
python3 -c 'import json,sys; print(json.dumps({"Distribution":{"Status":sys.argv[1],"DistributionConfig":{"Origins":{"Items":[{"Id":"current","OriginPath":""}]}}}}))' "\${status}"
|
|
echo Deployed > "${dir}/status"
|
|
AWS
|
|
chmod +x "${dir}/bin/aws"
|
|
export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}"
|
|
export DISTRIBUTION_ID="E2CWLM1AFB964P"
|
|
export EXPECTED_INDEX_SHA256="${INDEX_HASH}"
|
|
export SITE_URL="https://dev.seahaven.com"
|
|
export API_URL="https://api.dev.seahaven.com/api"
|
|
export BUDGET=5 INTERVAL=0
|
|
set +e
|
|
bash "${VERIFY}" > "${dir}/log.txt" 2>&1
|
|
code=$?
|
|
set -e
|
|
assert_exit "empty-origin-then-propagates" 0 "${code}" "${dir}/log.txt"
|
|
rm -rf "${dir}"
|
|
}
|
|
|
|
# 2. Empty origin never propagates (Deployed, stale hash).
|
|
{
|
|
dir="$(mktemp -d)"
|
|
make_stubs "${dir}/bin"
|
|
dist_json "Deployed" "" > "${dir}/distribution.json"
|
|
printf 'stale' > "${dir}/index.html"
|
|
export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}"
|
|
export DISTRIBUTION_ID="E2CWLM1AFB964P"
|
|
export EXPECTED_INDEX_SHA256="${NEW_HASH}"
|
|
export SITE_URL="https://dev.seahaven.com"
|
|
export API_URL="https://api.dev.seahaven.com/api"
|
|
export BUDGET=2 INTERVAL=0
|
|
set +e
|
|
bash "${VERIFY}" > "${dir}/log.txt" 2>&1
|
|
code=$?
|
|
set -e
|
|
assert_exit "empty-origin-never-propagates" 1 "${code}" "${dir}/log.txt"
|
|
grep -q "last observed" "${dir}/log.txt" || { echo "FAIL: timeout missing last observed state" >&2; failures=$((failures + 1)); }
|
|
rm -rf "${dir}"
|
|
}
|
|
|
|
# 3. Non-empty origin path fails fast.
|
|
{
|
|
dir="$(mktemp -d)"
|
|
make_stubs "${dir}/bin"
|
|
dist_json "Deployed" "/releases/deadbeef" > "${dir}/distribution.json"
|
|
printf '%s' "${INDEX_HTML}" > "${dir}/index.html"
|
|
export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}"
|
|
export DISTRIBUTION_ID="E2CWLM1AFB964P"
|
|
export EXPECTED_INDEX_SHA256="${NEW_HASH}"
|
|
export SITE_URL="https://dev.seahaven.com"
|
|
export API_URL="https://api.dev.seahaven.com/api"
|
|
export BUDGET=2 INTERVAL=0
|
|
set +e
|
|
bash "${VERIFY}" > "${dir}/log.txt" 2>&1
|
|
code=$?
|
|
set -e
|
|
assert_exit "nonempty-origin-path" 1 "${code}" "${dir}/log.txt"
|
|
grep -q "origin_path" "${dir}/log.txt" || { echo "FAIL: nonempty origin path did not name origin_path" >&2; failures=$((failures + 1)); }
|
|
rm -rf "${dir}"
|
|
}
|
|
|
|
# 4. Never Deployed.
|
|
{
|
|
dir="$(mktemp -d)"
|
|
make_stubs "${dir}/bin"
|
|
dist_json "InProgress" "" > "${dir}/distribution.json"
|
|
printf '%s' "${INDEX_HTML}" > "${dir}/index.html"
|
|
export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}"
|
|
export DISTRIBUTION_ID="E2CWLM1AFB964P"
|
|
export EXPECTED_INDEX_SHA256="${NEW_HASH}"
|
|
export SITE_URL="https://dev.seahaven.com"
|
|
export API_URL="https://api.dev.seahaven.com/api"
|
|
export BUDGET=2 INTERVAL=0
|
|
set +e
|
|
bash "${VERIFY}" > "${dir}/log.txt" 2>&1
|
|
code=$?
|
|
set -e
|
|
assert_exit "never-deployed" 1 "${code}" "${dir}/log.txt"
|
|
grep -q "last observed" "${dir}/log.txt" || { echo "FAIL: never-deployed missing last observed state" >&2; failures=$((failures + 1)); }
|
|
rm -rf "${dir}"
|
|
}
|
|
|
|
# 5. Hash-matched Deployed release whose JS assets omit the baked API URL.
|
|
{
|
|
dir="$(mktemp -d)"
|
|
make_stubs "${dir}/bin"
|
|
dist_json "Deployed" "" > "${dir}/distribution.json"
|
|
printf '%s' "${INDEX_HTML}" > "${dir}/index.html"
|
|
printf 'const x=1;' > "${dir}/asset.js"
|
|
export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}"
|
|
export DISTRIBUTION_ID="E2CWLM1AFB964P"
|
|
export EXPECTED_INDEX_SHA256="${INDEX_HASH}"
|
|
export SITE_URL="https://dev.seahaven.com"
|
|
export API_URL="https://api.dev.seahaven.com/api"
|
|
export BUDGET=2 INTERVAL=0
|
|
set +e
|
|
bash "${VERIFY}" > "${dir}/log.txt" 2>&1
|
|
code=$?
|
|
set -e
|
|
assert_exit "missing-baked-api-url" 1 "${code}" "${dir}/log.txt"
|
|
grep -q "baked API URL" "${dir}/log.txt" || { echo "FAIL: missing API URL did not name baked API URL" >&2; failures=$((failures + 1)); }
|
|
rm -rf "${dir}"
|
|
}
|
|
|
|
# 6. Hash-matched Deployed release whose JS assets contain the staging API URL.
|
|
{
|
|
dir="$(mktemp -d)"
|
|
make_stubs "${dir}/bin"
|
|
dist_json "Deployed" "" > "${dir}/distribution.json"
|
|
printf '%s' "${INDEX_HTML}" > "${dir}/index.html"
|
|
printf 'const api="https://api.staging.seahaven.com/api";' > "${dir}/asset.js"
|
|
export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}"
|
|
export DISTRIBUTION_ID="E2CWLM1AFB964P"
|
|
export EXPECTED_INDEX_SHA256="${INDEX_HASH}"
|
|
export SITE_URL="https://dev.seahaven.com"
|
|
export API_URL="https://api.dev.seahaven.com/api"
|
|
export BUDGET=2 INTERVAL=0
|
|
set +e
|
|
bash "${VERIFY}" > "${dir}/log.txt" 2>&1
|
|
code=$?
|
|
set -e
|
|
assert_exit "forbidden-staging-api-url" 1 "${code}" "${dir}/log.txt"
|
|
grep -q "forbidden URL api.staging.seahaven.com" "${dir}/log.txt" || { echo "FAIL: staging API URL did not name forbidden URL" >&2; failures=$((failures + 1)); }
|
|
rm -rf "${dir}"
|
|
}
|
|
|
|
if [[ "${failures}" -ne 0 ]]; then
|
|
echo "FAIL: ${failures} verify-cloudfront-release cases failed" >&2
|
|
exit 1
|
|
fi
|
|
echo "PASS: CloudFront release verify checks"
|