Update release_pipeline.yml

This commit is contained in:
Jonathan Miller
2026-01-01 08:54:38 -06:00
committed by GitHub
parent 622395faac
commit aa80746b50

View File

@@ -56,7 +56,6 @@ defaults:
run: run:
shell: bash shell: bash
# Default permissions are minimized; jobs elevate as needed.
permissions: permissions:
contents: read contents: read
@@ -79,7 +78,6 @@ jobs:
permissions: permissions:
contents: read contents: read
actions: read actions: read
# Required for permissions check via REST API
pull-requests: read pull-requests: read
steps: steps:
@@ -250,7 +248,8 @@ jobs:
printf '"sha":"%s",' "${GITHUB_SHA}" printf '"sha":"%s",' "${GITHUB_SHA}"
printf '"runner_os":"%s",' "${RUNNER_OS}" printf '"runner_os":"%s",' "${RUNNER_OS}"
printf '"runner_name":"%s"' "${RUNNER_NAME}" printf '"runner_name":"%s"' "${RUNNER_NAME}"
printf '}\n' printf '}
'
echo "```" echo "```"
} >> "${GITHUB_STEP_SUMMARY}" } >> "${GITHUB_STEP_SUMMARY}"
@@ -348,7 +347,8 @@ jobs:
printf '"event":"%s",' "${GITHUB_EVENT_NAME}" printf '"event":"%s",' "${GITHUB_EVENT_NAME}"
printf '"ref_name":"%s",' "${GITHUB_REF_NAME}" printf '"ref_name":"%s",' "${GITHUB_REF_NAME}"
printf '"sha":"%s"' "${GITHUB_SHA}" printf '"sha":"%s"' "${GITHUB_SHA}"
printf '}\n' printf '}
'
echo "```" echo "```"
} >> "${GITHUB_STEP_SUMMARY}" } >> "${GITHUB_STEP_SUMMARY}"
@@ -424,7 +424,8 @@ jobs:
{ {
echo "ERROR: Date normalization script not found in approved locations." echo "ERROR: Date normalization script not found in approved locations."
echo "Approved locations:" echo "Approved locations:"
printf '%s\n' "${CANDIDATES[@]}" printf '%s
' "${CANDIDATES[@]}"
echo "Discovered candidates (first 5):" echo "Discovered candidates (first 5):"
echo "${FOUND:-<none>}" echo "${FOUND:-<none>}"
echo "Required action: add scripts/release/update_dates.sh (preferred) to the repo." echo "Required action: add scripts/release/update_dates.sh (preferred) to the repo."
@@ -506,6 +507,7 @@ jobs:
FTP_PORT: ${{ secrets.FTP_PORT }} FTP_PORT: ${{ secrets.FTP_PORT }}
FTP_PATH_SUFFIX: ${{ vars.FTP_PATH_SUFFIX }} FTP_PATH_SUFFIX: ${{ vars.FTP_PATH_SUFFIX }}
CHANNEL: ${{ needs.guard.outputs.channel }} CHANNEL: ${{ needs.guard.outputs.channel }}
DEPLOY_DRY_RUN: ${{ vars.DEPLOY_DRY_RUN }}
run: | run: |
set -euo pipefail set -euo pipefail
@@ -513,7 +515,6 @@ jobs:
[ -n "${FTP_HOST:-}" ] || missing+=("FTP_HOST") [ -n "${FTP_HOST:-}" ] || missing+=("FTP_HOST")
[ -n "${FTP_USER:-}" ] || missing+=("FTP_USER") [ -n "${FTP_USER:-}" ] || missing+=("FTP_USER")
[ -n "${FTP_KEY:-}" ] || missing+=("FTP_KEY")
[ -n "${FTP_PATH:-}" ] || missing+=("FTP_PATH") [ -n "${FTP_PATH:-}" ] || missing+=("FTP_PATH")
proto="${FTP_PROTOCOL:-sftp}" proto="${FTP_PROTOCOL:-sftp}"
@@ -521,18 +522,23 @@ jobs:
missing+=("FTP_PROTOCOL_INVALID") missing+=("FTP_PROTOCOL_INVALID")
fi fi
first_line="$(printf '%s' "${FTP_KEY:-}" | head -n 1 || true)" key_present=false
if [ -n "${FTP_KEY:-}" ]; then if [ -n "${FTP_KEY:-}" ]; then
if printf '%s' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then key_present=true
key_format="ppk"
elif printf '%s' "${first_line}" | grep -q '^-----BEGIN '; then
key_format="openssh"
else
key_format="unknown"
missing+=("FTP_KEY_FORMAT")
fi fi
else
key_format="missing" pw_present=false
if [ -n "${FTP_PASSWORD:-}" ]; then
pw_present=true
fi
auth_mode="password"
if [ "${key_present}" = "true" ]; then
auth_mode="key"
fi
if [ "${auth_mode}" = "password" ] && [ "${pw_present}" != "true" ]; then
missing+=("FTP_PASSWORD_REQUIRED")
fi fi
{ {
@@ -542,9 +548,13 @@ jobs:
sep="" sep=""
for m in "${missing[@]}"; do for m in "${missing[@]}"; do
printf '%s"%s"' "${sep}" "${m}" printf '%s"%s"' "${sep}" "${m}"
sep="," sep=",";
done done
printf '],"key_format":"%s","channel":"%s"}\n' "${key_format}" "${CHANNEL}" printf '],"channel":"%s","deploy_dry_run":"%s","credential_presence":{"FTP_KEY":"%s","FTP_PASSWORD":"%s"}}
' \
"${CHANNEL}" "${DEPLOY_DRY_RUN:-false}" \
"$( [ "${key_present}" = "true" ] && echo present || echo missing )" \
"$( [ "${pw_present}" = "true" ] && echo present || echo missing )"
echo "```" echo "```"
} >> "${GITHUB_STEP_SUMMARY}" } >> "${GITHUB_STEP_SUMMARY}"
@@ -587,9 +597,10 @@ jobs:
sep="" sep=""
for m in "${missing[@]}"; do for m in "${missing[@]}"; do
printf '%s"%s"' "${sep}" "${m}" printf '%s"%s"' "${sep}" "${m}"
sep="," sep=",";
done done
printf ']}\n' printf ']}
'
echo "```" echo "```"
} >> "${GITHUB_STEP_SUMMARY}" } >> "${GITHUB_STEP_SUMMARY}"
exit 1 exit 1
@@ -621,31 +632,32 @@ jobs:
sep="" sep=""
for s in "${required_scripts[@]}"; do for s in "${required_scripts[@]}"; do
printf '%s"%s"' "${sep}" "${s}" printf '%s"%s"' "${sep}" "${s}"
sep="," sep=",";
done done
printf '],"optional":[' printf '],"optional":['
sep="" sep=""
for s in "${optional_scripts[@]}"; do for s in "${optional_scripts[@]}"; do
printf '%s"%s"' "${sep}" "${s}" printf '%s"%s"' "${sep}" "${s}"
sep="," sep=",";
done done
printf '],"ran":[' printf '],"ran":['
sep="" sep=""
for s in "${ran[@]}"; do for s in "${ran[@]}"; do
printf '%s"%s"' "${sep}" "${s}" printf '%s"%s"' "${sep}" "${s}"
sep="," sep=",";
done done
printf '],"skipped_optional":[' printf '],"skipped_optional":['
sep="" sep=""
for s in "${skipped[@]}"; do for s in "${skipped[@]}"; do
printf '%s"%s"' "${sep}" "${s}" printf '%s"%s"' "${sep}" "${s}"
sep="," sep=",";
done done
printf ']}\n' printf ']}
'
echo "```" echo "```"
} >> "${GITHUB_STEP_SUMMARY}" } >> "${GITHUB_STEP_SUMMARY}"
@@ -663,7 +675,6 @@ jobs:
DIST_DIR="${GITHUB_WORKSPACE}/dist" DIST_DIR="${GITHUB_WORKSPACE}/dist"
mkdir -p "${DIST_DIR}" mkdir -p "${DIST_DIR}"
# Detect manifest inside src for type naming only.
MANIFEST="" MANIFEST=""
if [ -f "src/templateDetails.xml" ]; then if [ -f "src/templateDetails.xml" ]; then
MANIFEST="src/templateDetails.xml" MANIFEST="src/templateDetails.xml"
@@ -691,7 +702,6 @@ jobs:
EXT_TYPE="unknown" EXT_TYPE="unknown"
fi fi
# Policy: archive must include ONLY the src directory tree (no repo root files).
ZIP="${REPO_NAME}-${VERSION}-${CHANNEL}-${EXT_TYPE}.zip" ZIP="${REPO_NAME}-${VERSION}-${CHANNEL}-${EXT_TYPE}.zip"
zip -r -X "${DIST_DIR}/${ZIP}" src \ zip -r -X "${DIST_DIR}/${ZIP}" src \
@@ -775,7 +785,6 @@ jobs:
REMOTE_PATH="${FTP_PATH%/}/${CHANNEL}" REMOTE_PATH="${FTP_PATH%/}/${CHANNEL}"
fi fi
# Guardrails: remote path safety.
if [ -z "${REMOTE_PATH}" ] || [ "${REMOTE_PATH}" = "/" ]; then if [ -z "${REMOTE_PATH}" ] || [ "${REMOTE_PATH}" = "/" ]; then
echo "ERROR: Unsafe REMOTE_PATH resolved (${REMOTE_PATH:-<empty>})" >> "${GITHUB_STEP_SUMMARY}" echo "ERROR: Unsafe REMOTE_PATH resolved (${REMOTE_PATH:-<empty>})" >> "${GITHUB_STEP_SUMMARY}"
exit 1 exit 1
@@ -785,18 +794,15 @@ jobs:
exit 1 exit 1
fi fi
AUTH_MODE="" AUTH_MODE="password"
if [ -n "${FTP_KEY:-}" ]; then if [ -n "${FTP_KEY:-}" ]; then
AUTH_MODE="key" AUTH_MODE="key"
else
AUTH_MODE="password"
fi fi
# Credential precedence: key wins when both are present.
PASSWORD_PRESENT="$( [ -n "${FTP_PASSWORD:-}" ] && echo true || echo false )" PASSWORD_PRESENT="$( [ -n "${FTP_PASSWORD:-}" ] && echo true || echo false )"
KEY_PRESENT="$( [ -n "${FTP_KEY:-}" ] && echo true || echo false )" KEY_PRESENT="$( [ -n "${FTP_KEY:-}" ] && echo true || echo false )"
if [ "${AUTH_MODE}" = "password" ] && [ -z "${FTP_PASSWORD:-}" ]; then if [ "${AUTH_MODE}" = "password" ] && [ "${PASSWORD_PRESENT}" != "true" ]; then
echo "ERROR: FTP_PASSWORD required when FTP_KEY is not provided" >> "${GITHUB_STEP_SUMMARY}" echo "ERROR: FTP_PASSWORD required when FTP_KEY is not provided" >> "${GITHUB_STEP_SUMMARY}"
exit 1 exit 1
fi fi
@@ -879,7 +885,6 @@ jobs:
ZIP_BYTES_LOCAL="$(stat -c%s "${DIST_DIR}/${ZIP}")" ZIP_BYTES_LOCAL="$(stat -c%s "${DIST_DIR}/${ZIP}")"
# Preflight: remote collision detection and directory validation.
set +e set +e
preflight_log="$(mktemp)" preflight_log="$(mktemp)"
lftp -d -e "\ lftp -d -e "\
@@ -898,7 +903,12 @@ jobs:
set -e set -e
if [ "${preflight_rc}" -ne 0 ]; then if [ "${preflight_rc}" -ne 0 ]; then
cat "${preflight_log}" >> "${GITHUB_STEP_SUMMARY}" || true {
echo "### SFTP preflight log"
echo "```"
tail -n 400 "${preflight_log}" || true
echo "```"
} >> "${GITHUB_STEP_SUMMARY}" || true
exit "${preflight_rc}" exit "${preflight_rc}"
fi fi
@@ -920,7 +930,6 @@ jobs:
exit 0 exit 0
fi fi
# Upload + verify with failure classification.
set +e set +e
upload_log="$(mktemp)" upload_log="$(mktemp)"
lftp -d -e "\ lftp -d -e "\
@@ -955,7 +964,6 @@ jobs:
fi fi
fi fi
# Always attach upload log to summary (bounded by job log limits, but critical for audit).
{ {
echo "### SFTP session log" echo "### SFTP session log"
echo "```" echo "```"
@@ -973,14 +981,12 @@ jobs:
exit "${rc}" exit "${rc}"
fi fi
# Verification: ensure ZIP appears in directory listing.
if ! grep -F " ${ZIP}" "${upload_log}" >/dev/null 2>&1; then if ! grep -F " ${ZIP}" "${upload_log}" >/dev/null 2>&1; then
echo "ERROR: Upload completed but verification failed. ZIP not visible in remote listing." >> "${GITHUB_STEP_SUMMARY}" echo "ERROR: Upload completed but verification failed. ZIP not visible in remote listing." >> "${GITHUB_STEP_SUMMARY}"
exit 1 exit 1
fi fi
ZIP_BYTES_REMOTE="unknown" ZIP_BYTES_REMOTE="unknown"
# Best-effort size extraction from ls -l output (platform dependent).
ZIP_BYTES_REMOTE="$(awk -v z="${ZIP}" '$NF==z {print $(NF-4)}' "${upload_log}" | tail -n 1 || true)" ZIP_BYTES_REMOTE="$(awk -v z="${ZIP}" '$NF==z {print $(NF-4)}' "${upload_log}" | tail -n 1 || true)"
if [ -z "${ZIP_BYTES_REMOTE}" ]; then if [ -z "${ZIP_BYTES_REMOTE}" ]; then
ZIP_BYTES_REMOTE="unknown" ZIP_BYTES_REMOTE="unknown"