Update guardrails.yml
This commit is contained in:
332
.github/workflows/guardrails.yml
vendored
332
.github/workflows/guardrails.yml
vendored
@@ -51,21 +51,16 @@ permissions:
|
|||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
guardrails:
|
release_config:
|
||||||
name: Setting Guardrails
|
name: Release configuration
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Guardrails: release secrets and vars
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- name: Validate required repository secrets and variables
|
|
||||||
env:
|
env:
|
||||||
PROFILE: ${{ github.event.inputs.profile || 'all' }}
|
PROFILE: ${{ github.event.inputs.profile || 'all' }}
|
||||||
|
|
||||||
# Release pipeline SFTP configuration (secrets + vars)
|
|
||||||
FTP_HOST: ${{ secrets.FTP_HOST }}
|
FTP_HOST: ${{ secrets.FTP_HOST }}
|
||||||
FTP_USER: ${{ secrets.FTP_USER }}
|
FTP_USER: ${{ secrets.FTP_USER }}
|
||||||
FTP_KEY: ${{ secrets.FTP_KEY }}
|
FTP_KEY: ${{ secrets.FTP_KEY }}
|
||||||
@@ -74,28 +69,169 @@ jobs:
|
|||||||
FTP_PROTOCOL: ${{ secrets.FTP_PROTOCOL }}
|
FTP_PROTOCOL: ${{ secrets.FTP_PROTOCOL }}
|
||||||
FTP_PORT: ${{ secrets.FTP_PORT }}
|
FTP_PORT: ${{ secrets.FTP_PORT }}
|
||||||
FTP_PATH_SUFFIX: ${{ vars.FTP_PATH_SUFFIX }}
|
FTP_PATH_SUFFIX: ${{ vars.FTP_PATH_SUFFIX }}
|
||||||
|
|
||||||
run: |
|
run: |
|
||||||
set -euxo pipefail
|
set -euxo pipefail
|
||||||
|
|
||||||
profile="${PROFILE}"
|
profile="${PROFILE}"
|
||||||
|
if [ "${profile}" != "all" ] && [ "${profile}" != "release" ] && [ "${profile}" != "scripts" ]; then
|
||||||
|
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Centralized checklist.
|
if [ "${profile}" = "scripts" ]; then
|
||||||
required_release_secrets=(
|
echo "Profile scripts selected. Skipping release configuration checks." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
required=(
|
||||||
"FTP_HOST"
|
"FTP_HOST"
|
||||||
"FTP_USER"
|
"FTP_USER"
|
||||||
"FTP_KEY"
|
"FTP_KEY"
|
||||||
"FTP_PATH"
|
"FTP_PATH"
|
||||||
)
|
)
|
||||||
|
|
||||||
optional_release=(
|
optional=(
|
||||||
"FTP_PASSWORD" # Only needed for encrypted PPK conversion
|
"FTP_PASSWORD"
|
||||||
"FTP_PROTOCOL" # Defaults to sftp in pipelines
|
"FTP_PROTOCOL"
|
||||||
"FTP_PORT" # Defaults to provider default
|
"FTP_PORT"
|
||||||
"FTP_PATH_SUFFIX" # Variable, optional
|
"FTP_PATH_SUFFIX"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
missing=()
|
||||||
|
missing_optional=()
|
||||||
|
|
||||||
|
for k in "${required[@]}"; do
|
||||||
|
v="${!k:-}"
|
||||||
|
if [ -z "${v}" ]; then
|
||||||
|
missing+=("${k}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for k in "${optional[@]}"; do
|
||||||
|
v="${!k:-}"
|
||||||
|
if [ -z "${v}" ]; then
|
||||||
|
missing_optional+=("${k}")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
proto="${FTP_PROTOCOL:-sftp}"
|
||||||
|
if [ -n "${FTP_PROTOCOL:-}" ] && [ "${proto}" != "sftp" ]; then
|
||||||
|
missing+=("FTP_PROTOCOL_INVALID")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Key format guardrail (do not print key material).
|
||||||
|
if [ -n "${FTP_KEY:-}" ]; then
|
||||||
|
first_line="$(printf '%s' "${FTP_KEY}" | head -n 1 || true)"
|
||||||
|
if printf '%s' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then
|
||||||
|
key_format="ppk"
|
||||||
|
elif printf '%s' "${first_line}" | grep -q '^-----BEGIN '; then
|
||||||
|
key_format="openssh"
|
||||||
|
else
|
||||||
|
key_format="unknown"
|
||||||
|
missing+=("FTP_KEY_FORMAT")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
key_format="missing"
|
||||||
|
fi
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "### Guardrails: release configuration"
|
||||||
|
echo "KEY_FORMAT=${key_format}"
|
||||||
|
echo ""
|
||||||
|
echo "### Guardrails report (JSON)"
|
||||||
|
echo "```json"
|
||||||
|
printf '{"profile":"%s","checked":{"required":[' "${profile}"
|
||||||
|
sep=""
|
||||||
|
for c in "${required[@]}"; do
|
||||||
|
printf '%s"%s"' "${sep}" "${c}"
|
||||||
|
sep=",";
|
||||||
|
done
|
||||||
|
printf '],"optional":['
|
||||||
|
sep=""
|
||||||
|
for c in "${optional[@]}"; do
|
||||||
|
printf '%s"%s"' "${sep}" "${c}"
|
||||||
|
sep=",";
|
||||||
|
done
|
||||||
|
printf ']},"missing_required":['
|
||||||
|
sep=""
|
||||||
|
for m in "${missing[@]}"; do
|
||||||
|
printf '%s"%s"' "${sep}" "${m}"
|
||||||
|
sep=",";
|
||||||
|
done
|
||||||
|
printf '],"missing_optional":['
|
||||||
|
sep=""
|
||||||
|
for m in "${missing_optional[@]}"; do
|
||||||
|
printf '%s"%s"' "${sep}" "${m}"
|
||||||
|
sep=",";
|
||||||
|
done
|
||||||
|
printf ']}
|
||||||
|
'
|
||||||
|
echo "```"
|
||||||
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
|
if [ "${#missing[@]}" -gt 0 ]; then
|
||||||
|
echo "### Missing required release configuration" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${missing[@]}"; do
|
||||||
|
echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
||||||
|
echo "### Missing optional release configuration" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
for m in "${missing_optional[@]}"; do
|
||||||
|
echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${#missing[@]}" -gt 0 ]; then
|
||||||
|
echo "MISSING_REQUIRED: ${missing[*]}" >&2
|
||||||
|
echo "ERROR: Guardrails failed. Missing required release configuration." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
scripts_config:
|
||||||
|
name: Scripts and tooling
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Guardrails: script files and toolchain
|
||||||
|
env:
|
||||||
|
PROFILE: ${{ github.event.inputs.profile || 'all' }}
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
profile="${PROFILE}"
|
||||||
|
if [ "${profile}" != "all" ] && [ "${profile}" != "release" ] && [ "${profile}" != "scripts" ]; then
|
||||||
|
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${profile}" = "release" ]; then
|
||||||
|
echo "Profile release selected. Skipping scripts checks." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
required_script_files=(
|
required_script_files=(
|
||||||
|
"scripts/validate/manifest.sh"
|
||||||
|
"scripts/validate/xml_wellformed.sh"
|
||||||
|
"scripts/validate/changelog.sh"
|
||||||
|
"scripts/validate/tabs.sh"
|
||||||
|
"scripts/validate/paths.sh"
|
||||||
|
"scripts/validate/version_alignment.sh"
|
||||||
|
"scripts/validate/language_structure.sh"
|
||||||
|
"scripts/validate/php_syntax.sh"
|
||||||
|
"scripts/validate/no_secrets.sh"
|
||||||
|
"scripts/validate/license_headers.sh"
|
||||||
|
)
|
||||||
|
|
||||||
|
legacy_script_files=(
|
||||||
"scripts/validate_manifest.sh"
|
"scripts/validate_manifest.sh"
|
||||||
"scripts/validate_xml_wellformed.sh"
|
"scripts/validate_xml_wellformed.sh"
|
||||||
"scripts/validate_changelog.sh"
|
"scripts/validate_changelog.sh"
|
||||||
@@ -108,163 +244,49 @@ jobs:
|
|||||||
"scripts/validate_license_headers.sh"
|
"scripts/validate_license_headers.sh"
|
||||||
)
|
)
|
||||||
|
|
||||||
missing=()
|
fi
|
||||||
missing_optional=()
|
done
|
||||||
missing_files=()
|
|
||||||
|
|
||||||
check_release() {
|
# Report legacy scripts if present so teams can clean up.
|
||||||
for k in "${required_release_secrets[@]}"; do
|
for f in "${legacy_script_files[@]}"; do
|
||||||
v="${!k:-}"
|
if [ -f "${f}" ]; then
|
||||||
if [ -z "${v}" ]; then
|
legacy_present+=("${f}")
|
||||||
missing+=("${k}")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Optional configuration.
|
|
||||||
for k in "${optional_release[@]}"; do
|
|
||||||
v="${!k:-}"
|
|
||||||
if [ -z "${v}" ]; then
|
|
||||||
missing_optional+=("${k}")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Protocol sanity if provided.
|
|
||||||
proto="${FTP_PROTOCOL:-sftp}"
|
|
||||||
if [ -n "${FTP_PROTOCOL:-}" ] && [ "${proto}" != "sftp" ]; then
|
|
||||||
missing+=("FTP_PROTOCOL_INVALID")
|
|
||||||
fi
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
# Key format guardrail (do not print key).
|
tools_to_install=()
|
||||||
if [ -n "${FTP_KEY:-}" ]; then
|
command -v php >/dev/null 2>&1 || tools_to_install+=("php-cli")
|
||||||
first_line="$(printf '%s' "${FTP_KEY}" | head -n 1 || true)"
|
command -v xmllint >/dev/null 2>&1 || tools_to_install+=("libxml2-utils")
|
||||||
if printf '%s' "${first_line}" | grep -q '^PuTTY-User-Key-File-'; then
|
|
||||||
key_format="ppk"
|
|
||||||
elif printf '%s' "${first_line}" | grep -q '^-----BEGIN '; then
|
|
||||||
key_format="openssh"
|
|
||||||
else
|
|
||||||
key_format="unknown"
|
|
||||||
missing+=("FTP_KEY_FORMAT")
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
key_format="missing"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "KEY_FORMAT=${key_format}" >> "${GITHUB_STEP_SUMMARY}"
|
if [ "${#tools_to_install[@]}" -gt 0 ]; then
|
||||||
}
|
echo "Installing missing tools: ${tools_to_install[*]}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
sudo apt-get update -y
|
||||||
check_scripts() {
|
ntf '%s"%s"' "${sep}" "${c}"
|
||||||
for f in "${required_script_files[@]}"; do
|
|
||||||
if [ ! -f "${f}" ]; then
|
|
||||||
missing_files+=("${f}")
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# Tooling expectations: scripts may rely on php and xmllint.
|
|
||||||
# Do not fail on tooling here; CI runners can install dependencies.
|
|
||||||
# Still report as guardrail visibility.
|
|
||||||
tool_missing=()
|
|
||||||
command -v php >/dev/null 2>&1 || tool_missing+=("php")
|
|
||||||
command -v xmllint >/dev/null 2>&1 || tool_missing+=("xmllint")
|
|
||||||
|
|
||||||
if [ "${#tool_missing[@]}" -gt 0 ]; then
|
|
||||||
echo "WARN: Missing tools on runner (install in workflow when required): ${tool_missing[*]}" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
case "${profile}" in
|
|
||||||
release)
|
|
||||||
check_release
|
|
||||||
;;
|
|
||||||
scripts)
|
|
||||||
check_scripts
|
|
||||||
;;
|
|
||||||
all)
|
|
||||||
check_release
|
|
||||||
check_scripts
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "ERROR: Unknown profile: ${profile}" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Emit report.
|
|
||||||
{
|
|
||||||
echo "### Guardrails report (JSON)"
|
|
||||||
echo "```json"
|
|
||||||
printf '{"repository":"%s","profile":"%s","checked":{' "${GITHUB_REPOSITORY}" "${profile}"
|
|
||||||
|
|
||||||
printf '"release_required":['
|
|
||||||
sep=""
|
|
||||||
for c in "${required_release_secrets[@]}"; do
|
|
||||||
printf '%s"%s"' "${sep}" "${c}"
|
|
||||||
sep=",";
|
sep=",";
|
||||||
done
|
done
|
||||||
|
printf ']},"missing_script_files":['
|
||||||
printf '],"release_optional":['
|
|
||||||
sep=""
|
|
||||||
for c in "${optional_release[@]}"; do
|
|
||||||
printf '%s"%s"' "${sep}" "${c}"
|
|
||||||
sep=",";
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '],"script_files":['
|
|
||||||
sep=""
|
|
||||||
for c in "${required_script_files[@]}"; do
|
|
||||||
printf '%s"%s"' "${sep}" "${c}"
|
|
||||||
sep=",";
|
|
||||||
done
|
|
||||||
printf ']},'
|
|
||||||
|
|
||||||
printf '"missing_required":['
|
|
||||||
sep=""
|
|
||||||
for m in "${missing[@]}"; do
|
|
||||||
printf '%s"%s"' "${sep}" "${m}"
|
|
||||||
sep=",";
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '],"missing_optional":['
|
|
||||||
sep=""
|
|
||||||
for m in "${missing_optional[@]}"; do
|
|
||||||
printf '%s"%s"' "${sep}" "${m}"
|
|
||||||
sep=",";
|
|
||||||
done
|
|
||||||
|
|
||||||
printf '],"missing_script_files":['
|
|
||||||
sep=""
|
sep=""
|
||||||
for m in "${missing_files[@]}"; do
|
for m in "${missing_files[@]}"; do
|
||||||
printf '%s"%s"' "${sep}" "${m}"
|
printf '%s"%s"' "${sep}" "${m}"
|
||||||
sep=",";
|
sep=",";
|
||||||
done
|
done
|
||||||
|
printf '],"legacy_present":['
|
||||||
|
sep=""
|
||||||
|
for m in "${legacy_present[@]}"; do
|
||||||
|
printf '%s"%s"' "${sep}" "${m}"
|
||||||
|
sep=",";
|
||||||
|
done
|
||||||
printf ']}'
|
printf ']}'
|
||||||
echo
|
echo
|
||||||
echo "```"
|
echo "```"
|
||||||
} >> "${GITHUB_STEP_SUMMARY}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
|
|
||||||
# Human-readable missing items (in addition to JSON)
|
|
||||||
if [ "${#missing[@]}" -gt 0 ]; then
|
|
||||||
echo "### Missing required configuration" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
for m in "${missing[@]}"; do
|
|
||||||
echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${#missing_optional[@]}" -gt 0 ]; then
|
|
||||||
echo "### Missing optional configuration" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
for m in "${missing_optional[@]}"; do
|
|
||||||
echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "${#missing_files[@]}" -gt 0 ]; then
|
if [ "${#missing_files[@]}" -gt 0 ]; then
|
||||||
echo "### Missing script files" >> "${GITHUB_STEP_SUMMARY}"
|
echo "### Missing script files" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
for m in "${missing_files[@]}"; do
|
for m in "${missing_files[@]}"; do
|
||||||
echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"
|
echo "- ${m}" >> "${GITHUB_STEP_SUMMARY}"
|
||||||
done
|
done
|
||||||
fi
|
echo "MISSING_SCRIPT_FILES: ${missing_files[*]}" >&2
|
||||||
|
echo "ERROR: Guardrails failed. Missing required script files." >> "${GITHUB_STEP_SUMMARY}"
|
||||||
# Fail the workflow if required items are missing.
|
|
||||||
if [ "${#missing[@]}" -gt 0 ] || [ "${#missing_files[@]}" -gt 0 ]; then
|
|
||||||
echo "ERROR: Guardrails failed. Missing required configuration or script files." >> "${GITHUB_STEP_SUMMARY}"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user