Update repo_health.yml

This commit is contained in:
2025-12-30 16:10:36 -06:00
parent 810585acff
commit 28dd56e822

View File

@@ -11,7 +11,7 @@
# REPO: https://github.com/mokoconsulting-tech/MokoStandards # REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/repo_health.yml # PATH: /.github/workflows/repo_health.yml
# VERSION: 03.05.00 # VERSION: 03.05.00
# BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts using MokoStandards definition files. # BRIEF: Enforces repository guardrails by validating release configuration, scripts governance, tooling availability, and core repository health artifacts.
# NOTE: Field is user-managed. # NOTE: Field is user-managed.
# ============================================================================ # ============================================================================
@@ -44,12 +44,35 @@ on:
- scripts/** - scripts/**
- docs/** - docs/**
- dev/** - dev/**
push:
branches:
- main
paths:
- .github/workflows/**
- scripts/**
- docs/**
- dev/**
permissions: permissions:
contents: read contents: read
env: env:
GUARDRAILS_DEFINITION_URL: ${{ vars.MOKOSTANDARDS_GUARDRAILS_URL || 'https://raw.githubusercontent.com/mokoconsulting-tech/MokoStandards/main/repo-guardrails.definition.json' }} # Global policy variables baked into workflow
ALLOWED_SFTP_PROTOCOLS: sftp
RELEASE_REQUIRED_VARS: FTP_HOST,FTP_USER,FTP_PATH
RELEASE_OPTIONAL_VARS: FTP_KEY,FTP_PASSWORD,FTP_PROTOCOL,FTP_PORT,FTP_PATH_SUFFIX
SCRIPTS_RECOMMENDED_DIRS: scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
SCRIPTS_ALLOWED_DIRS: scripts,scripts/fix,scripts/lib,scripts/release,scripts/run,scripts/validate
REPO_REQUIRED_FILES: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,docs/docs-index.md
REPO_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore
REPO_REQUIRED_PATHS: .github/workflows,scripts,docs,dev
REPO_DISALLOWED_DIRS: src
REPO_DISALLOWED_FILES: TODO.md,todo.md
# Operational toggles
SFTP_VERBOSE: "false"
jobs: jobs:
access_check: access_check:
@@ -115,56 +138,6 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Load guardrails definition
run: |
set -euo pipefail
url="${GUARDRAILS_DEFINITION_URL}"
{
printf '%s\n' '### Guardrails policy source'
printf '%s\n' "${url}"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if ! curl -fsSL "${url}" -o /tmp/repo_guardrails.definition.json; then
printf '%s\n' 'Warning: Unable to fetch guardrails definition. Falling back to workflow defaults.' >> "${GITHUB_STEP_SUMMARY}"
printf '%s\n' 'GUARDRAILS_LOADED=false' >> "${GITHUB_ENV}"
exit 0
fi
python3 - <<'PY'
import json
import os
import uuid
path = "/tmp/repo_guardrails.definition.json"
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
env_path = os.environ.get("GITHUB_ENV")
if not env_path:
raise SystemExit("GITHUB_ENV not set")
def put_multiline(key: str, values):
vals = [str(v) for v in (values or []) if str(v).strip()]
marker = f"EOF_{uuid.uuid4().hex}"
with open(env_path, "a", encoding="utf-8") as w:
w.write(f"{key}<<{marker}\n")
for v in vals:
w.write(v + "\n")
w.write(f"{marker}\n\n")
put_multiline("GUARDRAILS_RELEASE_REQUIRED_SECRETS", data.get("release", {}).get("required_secrets"))
put_multiline("GUARDRAILS_RELEASE_OPTIONAL_SECRETS", data.get("release", {}).get("optional_secrets"))
put_multiline("GUARDRAILS_RELEASE_OPTIONAL_VARS", data.get("release", {}).get("optional_vars"))
put_multiline("GUARDRAILS_RELEASE_PROTOCOL_ALLOWED", data.get("release", {}).get("protocol", {}).get("allowed"))
with open(env_path, "a", encoding="utf-8") as w:
w.write("GUARDRAILS_LOADED=true\n")
print("Guardrails definition loaded")
PY
- name: Guardrails release secrets and vars - name: Guardrails release secrets and vars
env: env:
PROFILE_RAW: ${{ github.event.inputs.profile }} PROFILE_RAW: ${{ github.event.inputs.profile }}
@@ -193,25 +166,9 @@ jobs:
exit 0 exit 0
fi fi
required=("FTP_HOST" "FTP_USER" "FTP_PATH") IFS=',' read -r -a required <<< "${RELEASE_REQUIRED_VARS}"
optional=("FTP_KEY" "FTP_PASSWORD" "FTP_PROTOCOL" "FTP_PORT" "FTP_PATH_SUFFIX") IFS=',' read -r -a optional <<< "${RELEASE_OPTIONAL_VARS}"
IFS=',' read -r -a allowed_proto <<< "${ALLOWED_SFTP_PROTOCOLS}"
if [ "${GUARDRAILS_LOADED:-false}" = 'true' ]; then
if [ -n "${GUARDRAILS_RELEASE_REQUIRED_SECRETS:-}" ]; then
mapfile -t required < <(printf '%s\n' "${GUARDRAILS_RELEASE_REQUIRED_SECRETS}" | sed '/^$/d')
fi
opt=()
if [ -n "${GUARDRAILS_RELEASE_OPTIONAL_SECRETS:-}" ]; then
while IFS= read -r v; do [ -n "${v}" ] && opt+=("${v}"); done < <(printf '%s\n' "${GUARDRAILS_RELEASE_OPTIONAL_SECRETS}" | sed '/^$/d')
fi
if [ -n "${GUARDRAILS_RELEASE_OPTIONAL_VARS:-}" ]; then
while IFS= read -r v; do [ -n "${v}" ] && opt+=("${v}"); done < <(printf '%s\n' "${GUARDRAILS_RELEASE_OPTIONAL_VARS}" | sed '/^$/d')
fi
if [ "${#opt[@]}" -gt 0 ]; then
optional=("${opt[@]}")
fi
fi
missing=() missing=()
missing_optional=() missing_optional=()
@@ -228,11 +185,6 @@ jobs:
proto="${FTP_PROTOCOL:-sftp}" proto="${FTP_PROTOCOL:-sftp}"
allowed_proto=("sftp")
if [ "${GUARDRAILS_LOADED:-false}" = 'true' ] && [ -n "${GUARDRAILS_RELEASE_PROTOCOL_ALLOWED:-}" ]; then
mapfile -t allowed_proto < <(printf '%s\n' "${GUARDRAILS_RELEASE_PROTOCOL_ALLOWED}" | sed '/^$/d')
fi
if [ -n "${FTP_PROTOCOL:-}" ]; then if [ -n "${FTP_PROTOCOL:-}" ]; then
ok=false ok=false
for ap in "${allowed_proto[@]}"; do for ap in "${allowed_proto[@]}"; do
@@ -311,6 +263,12 @@ jobs:
port="${FTP_PORT:-22}" port="${FTP_PORT:-22}"
sftp_verbose="${SFTP_VERBOSE:-false}"
sftp_v_opt=()
if [ "${sftp_verbose}" = 'true' ]; then
sftp_v_opt=(-vv)
fi
{ {
printf '%s\n' '### SFTP connectivity test' printf '%s\n' '### SFTP connectivity test'
printf '%s\n' 'Attempting non-destructive SFTP session' printf '%s\n' 'Attempting non-destructive SFTP session'
@@ -319,10 +277,12 @@ jobs:
set +e set +e
if [ "${use_key}" = true ]; then if [ "${use_key}" = true ]; then
printf 'pwd\nbye\n' | sftp -vv -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1 printf 'pwd
bye\n' | sftp "${sftp_v_opt[@]}" -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
elif [ -n "${FTP_PASSWORD:-}" ]; then elif [ -n "${FTP_PASSWORD:-}" ]; then
sudo apt-get update -qq && sudo apt-get install -y sshpass >/dev/null command -v sshpass >/dev/null 2>&1 || (sudo apt-get update -qq && sudo apt-get install -y sshpass >/dev/null)
printf 'pwd\nbye\n' | sshpass -p "${FTP_PASSWORD}" sftp -vv -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1 printf 'pwd
bye\n' | sshpass -p "${FTP_PASSWORD}" sftp "${sftp_v_opt[@]}" -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
else else
printf '%s\n' 'ERROR: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.' >> "${GITHUB_STEP_SUMMARY}" printf '%s\n' 'ERROR: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.' >> "${GITHUB_STEP_SUMMARY}"
exit 1 exit 1
@@ -359,55 +319,6 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Load guardrails definition
run: |
set -euo pipefail
url="${GUARDRAILS_DEFINITION_URL}"
{
printf '%s\n' '### Guardrails policy source'
printf '%s\n' "${url}"
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
if ! curl -fsSL "${url}" -o /tmp/repo_guardrails.definition.json; then
printf '%s\n' 'Warning: Unable to fetch guardrails definition. Falling back to workflow defaults.' >> "${GITHUB_STEP_SUMMARY}"
printf '%s\n' 'GUARDRAILS_LOADED=false' >> "${GITHUB_ENV}"
exit 0
fi
python3 - <<'PY'
import json
import os
import uuid
path = "/tmp/repo_guardrails.definition.json"
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
env_path = os.environ.get("GITHUB_ENV")
if not env_path:
raise SystemExit("GITHUB_ENV not set")
def put_multiline(key: str, values):
vals = [str(v) for v in (values or []) if str(v).strip()]
marker = f"EOF_{uuid.uuid4().hex}"
with open(env_path, "a", encoding="utf-8") as w:
w.write(f"{key}<<{marker}\n")
for v in vals:
w.write(v + "\n")
w.write(f"{marker}\n\n")
put_multiline("GUARDRAILS_SCRIPTS_ALLOWED_DIRS", data.get("scripts", {}).get("allowed_top_level_dirs"))
put_multiline("GUARDRAILS_SCRIPTS_RECOMMENDED_DIRS", data.get("scripts", {}).get("recommended_dirs"))
put_multiline("GUARDRAILS_SCRIPTS_REQUIRED_VALIDATE_FILES", data.get("scripts", {}).get("required_validate_files_when_present"))
with open(env_path, "a", encoding="utf-8") as w:
w.write("GUARDRAILS_LOADED=true\n")
print("Guardrails definition loaded")
PY
- name: Scripts folder checks - name: Scripts folder checks
env: env:
PROFILE_RAW: ${{ github.event.inputs.profile }} PROFILE_RAW: ${{ github.event.inputs.profile }}
@@ -437,17 +348,8 @@ jobs:
exit 0 exit 0
fi fi
recommended_dirs=("scripts/fix" "scripts/lib" "scripts/release" "scripts/run" "scripts/validate") IFS=',' read -r -a recommended_dirs <<< "${SCRIPTS_RECOMMENDED_DIRS}"
allowed_dirs=("scripts" "scripts/fix" "scripts/lib" "scripts/release" "scripts/run" "scripts/validate") IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
if [ "${GUARDRAILS_LOADED:-false}" = 'true' ]; then
if [ -n "${GUARDRAILS_SCRIPTS_RECOMMENDED_DIRS:-}" ]; then
mapfile -t recommended_dirs < <(printf '%s\n' "${GUARDRAILS_SCRIPTS_RECOMMENDED_DIRS}" | sed '/^$/d')
fi
if [ -n "${GUARDRAILS_SCRIPTS_ALLOWED_DIRS:-}" ]; then
mapfile -t allowed_dirs < <(printf '%s\n' "${GUARDRAILS_SCRIPTS_ALLOWED_DIRS}" | sed '/^$/d')
fi
fi
missing_dirs=() missing_dirs=()
unapproved_dirs=() unapproved_dirs=()
@@ -530,40 +432,11 @@ jobs:
exit 0 exit 0
fi fi
# NOTE: File and path requirements are enforced locally in this script. IFS=',' read -r -a required_files <<< "${REPO_REQUIRED_FILES}"
# Do not source required/optional file lists from external definition files. IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
required_files=( IFS=',' read -r -a required_paths <<< "${REPO_REQUIRED_PATHS}"
README.md IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
LICENSE IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
CHANGELOG.md
CONTRIBUTING.md
CODE_OF_CONDUCT.md
docs/docs-index.md
)
optional_files=(
SECURITY.md
GOVERNANCE.md
.editorconfig
.gitattributes
.gitignore
)
required_paths=(
.github/workflows
scripts
docs
dev
)
disallowed_dirs=(
src
)
disallowed_files=(
TODO.md
todo.md
)
missing_required=() missing_required=()
missing_optional=() missing_optional=()
@@ -634,25 +507,25 @@ jobs:
export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")" export CONTENT_WARNINGS="$(printf '%s\n' "${content_warnings[@]:-}")"
report_json="$(python3 - <<'PY' report_json="$(python3 - <<'PY'
import json import json
import os import os
profile = os.environ.get('PROFILE_RAW') or 'all' profile = os.environ.get('PROFILE_RAW') or 'all'
missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else [] missing_required = os.environ.get('MISSING_REQUIRED', '').splitlines() if os.environ.get('MISSING_REQUIRED') else []
missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else [] missing_optional = os.environ.get('MISSING_OPTIONAL', '').splitlines() if os.environ.get('MISSING_OPTIONAL') else []
content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else [] content_warnings = os.environ.get('CONTENT_WARNINGS', '').splitlines() if os.environ.get('CONTENT_WARNINGS') else []
out = { out = {
'profile': profile, 'profile': profile,
'missing_required': [x for x in missing_required if x], 'missing_required': [x for x in missing_required if x],
'missing_optional': [x for x in missing_optional if x], 'missing_optional': [x for x in missing_optional if x],
'content_warnings': [x for x in content_warnings if x], 'content_warnings': [x for x in content_warnings if x],
} }
print(json.dumps(out, indent=2)) print(json.dumps(out, indent=2))
PY PY
)" )"
{ {
printf '%s\n' '### Guardrails repository health' printf '%s\n' '### Guardrails repository health'
@@ -690,3 +563,5 @@ jobs:
fi fi
printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}" printf '%s\n' 'Repository health guardrails passed.' >> "${GITHUB_STEP_SUMMARY}"
# EOF