Update repo_health.yml

This commit is contained in:
2025-12-30 16:31:42 -06:00
parent f3c18e3b1a
commit 47f26ed3ff

View File

@@ -59,15 +59,20 @@ permissions:
env:
# Global policy variables baked into workflow
ALLOWED_SFTP_PROTOCOLS: sftp
# Release policy
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 governance policy
# Note: directories listed without a trailing slash.
SCRIPTS_REQUIRED_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 health policy
# Files are listed as-is; directories must end with a trailing slash.
REPO_REQUIRED_ARTIFACTS: README.md,LICENSE,CHANGELOG.md,CONTRIBUTING.md,CODE_OF_CONDUCT.md,docs/docs-index.md,.github/workflows/,scripts/,docs/,dev/
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
@@ -96,25 +101,25 @@ jobs:
const res = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.actor
username: context.actor,
});
const permission = (res?.data?.permission || 'unknown').toLowerCase();
const allowed = permission === 'admin';
const permission = (res?.data?.permission || "unknown").toLowerCase();
const allowed = permission === "admin";
core.setOutput('permission', permission);
core.setOutput('allowed', allowed ? 'true' : 'false');
core.setOutput("permission", permission);
core.setOutput("allowed", allowed ? "true" : "false");
const lines = [];
lines.push('### Access control');
lines.push('');
lines.push("### Access control");
lines.push("");
lines.push(`Actor: ${context.actor}`);
lines.push(`Permission: ${permission}`);
lines.push(`Allowed: ${allowed}`);
lines.push('');
lines.push('Policy: This workflow runs only for users with admin permission on the repository.');
lines.push("");
lines.push("Policy: This workflow runs only for users with admin permission on the repository.");
await core.summary.addRaw(lines.join('\n')).write();
await core.summary.addRaw(lines.join("\n")).write();
- name: Deny execution when not permitted
if: ${{ steps.perm.outputs.allowed != 'true' }}
@@ -138,7 +143,7 @@ jobs:
with:
fetch-depth: 0
- name: Guardrails release secrets and vars
- name: Guardrails release vars
env:
PROFILE_RAW: ${{ github.event.inputs.profile }}
FTP_HOST: ${{ secrets.FTP_HOST }}
@@ -184,7 +189,6 @@ jobs:
done
proto="${FTP_PROTOCOL:-sftp}"
if [ -n "${FTP_PROTOCOL:-}" ]; then
ok=false
for ap in "${allowed_proto[@]}"; do
@@ -224,6 +228,8 @@ jobs:
FTP_KEY: ${{ secrets.FTP_KEY }}
FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
FTP_PORT: ${{ secrets.FTP_PORT }}
FTP_PATH: ${{ secrets.FTP_PATH }}
FTP_PATH_SUFFIX: ${{ vars.FTP_PATH_SUFFIX }}
run: |
set -euo pipefail
@@ -241,15 +247,32 @@ jobs:
exit 0
fi
mkdir -p "$HOME/.ssh"
port="${FTP_PORT:-22}"
key_file="$HOME/.ssh/ci_sftp_key"
use_key=false
target_path="${FTP_PATH}"
if [ -n "${FTP_PATH_SUFFIX:-}" ]; then
target_path="${target_path%/}/${FTP_PATH_SUFFIX#/}"
fi
sftp_verbose="${SFTP_VERBOSE:-false}"
sftp_v_opt=()
[ "${sftp_verbose}" = 'true' ] && sftp_v_opt=(-vv)
{
printf '%s\n' '### SFTP connectivity test'
printf '%s\n' "Target path: ${target_path}"
printf '%s\n' 'Attempting non-destructive SFTP session'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
sftp_cmds="$(printf 'cd %s\npwd\nbye\n' "${target_path}")"
set +e
if [ -n "${FTP_KEY:-}" ]; then
mkdir -p "$HOME/.ssh"
key_file="$HOME/.ssh/ci_sftp_key"
printf '%s\n' "${FTP_KEY}" > "${key_file}"
chmod 600 "${key_file}"
use_key=true
if [ -n "${FTP_PASSWORD:-}" ]; then
first_line="$(head -n 1 "${key_file}" || true)"
@@ -259,33 +282,17 @@ jobs:
fi
ssh-keygen -p -P "${FTP_PASSWORD}" -N '' -f "${key_file}" >/dev/null
fi
fi
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' 'Attempting non-destructive SFTP session'
printf '\n'
} >> "${GITHUB_STEP_SUMMARY}"
set +e
if [ "${use_key}" = true ]; then
printf 'pwd\nbye\n' | sftp "${sftp_v_opt[@]}" -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
printf '%s' "${sftp_cmds}" | sftp "${sftp_v_opt[@]}" -oBatchMode=yes -oStrictHostKeyChecking=no -P "${port}" -i "${key_file}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
sftp_rc=$?
elif [ -n "${FTP_PASSWORD:-}" ]; then
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 "${sftp_v_opt[@]}" -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
printf '%s' "${sftp_cmds}" | sshpass -p "${FTP_PASSWORD}" sftp "${sftp_v_opt[@]}" -oBatchMode=no -oStrictHostKeyChecking=no -P "${port}" "${FTP_USER}@${FTP_HOST}" >/tmp/sftp_check.log 2>&1
sftp_rc=$?
else
printf '%s\n' 'ERROR: No FTP_KEY or FTP_PASSWORD provided for SFTP authentication.' >> "${GITHUB_STEP_SUMMARY}"
exit 1
fi
sftp_rc=$?
set -e
printf '%s\n' '### SFTP connectivity result' >> "${GITHUB_STEP_SUMMARY}"
@@ -298,7 +305,7 @@ jobs:
printf '%s\n' "Status: FAILED (exit code ${sftp_rc})"
printf '\n'
printf '%s\n' 'Last SFTP output'
tail -n 40 /tmp/sftp_check.log || true
tail -n 60 /tmp/sftp_check.log || true
} >> "${GITHUB_STEP_SUMMARY}"
exit 1
@@ -346,28 +353,31 @@ jobs:
exit 0
fi
IFS=',' read -r -a recommended_dirs <<< "${SCRIPTS_RECOMMENDED_DIRS}"
IFS=',' read -r -a required_dirs <<< "${SCRIPTS_REQUIRED_DIRS}"
IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
missing_dirs=()
unapproved_dirs=()
for d in "${recommended_dirs[@]}"; do
[ ! -d "${d}" ] && missing_dirs+=("${d}/")
for d in "${required_dirs[@]}"; do
req="${d%/}"
[ ! -d "${req}" ] && missing_dirs+=("${req}/")
done
while IFS= read -r d; do
allowed=false
for a in "${allowed_dirs[@]}"; do
[ "${d}" = "${a}" ] && allowed=true
a_norm="${a%/}"
[ "${d%/}" = "${a_norm}" ] && allowed=true
done
[ "${allowed}" = false ] && unapproved_dirs+=("${d}/")
[ "${allowed}" = false ] && unapproved_dirs+=("${d%/}/")
done < <(find scripts -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##')
{
printf '%s\n' '### Scripts governance'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' 'Missing recommended script directories:'
printf '%s\n' 'Missing required script directories:'
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
printf '\n'
fi
@@ -381,9 +391,9 @@ jobs:
printf '%s\n' '| Area | Status | Notes |'
printf '%s\n' '|------|--------|-------|'
if [ "${#missing_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Recommended directories | Warning | Missing recommended subfolders |'
printf '%s\n' '| Required directories | Warning | Missing required subfolders |'
else
printf '%s\n' '| Recommended directories | OK | All recommended subfolders present |'
printf '%s\n' '| Required directories | OK | All required subfolders present |'
fi
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
@@ -430,37 +440,34 @@ jobs:
exit 0
fi
IFS=',' read -r -a required_files <<< "${REPO_REQUIRED_FILES}"
IFS=',' read -r -a required_artifacts <<< "${REPO_REQUIRED_ARTIFACTS}"
IFS=',' read -r -a optional_files <<< "${REPO_OPTIONAL_FILES}"
IFS=',' read -r -a required_paths <<< "${REPO_REQUIRED_PATHS}"
IFS=',' read -r -a disallowed_dirs <<< "${REPO_DISALLOWED_DIRS}"
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
missing_required=()
missing_optional=()
for f in "${required_files[@]}"; do
[ ! -f "${f}" ] && missing_required+=("${f}")
for item in "${required_artifacts[@]}"; do
if printf '%s' "${item}" | grep -q '/$'; then
d="${item%/}"
[ ! -d "${d}" ] && missing_required+=("${item}")
else
[ ! -f "${item}" ] && missing_required+=("${item}")
fi
done
for f in "${optional_files[@]}"; do
[ ! -f "${f}" ] && missing_optional+=("${f}")
done
for p in "${required_paths[@]}"; do
[ ! -d "${p}" ] && missing_required+=("${p}/")
done
for d in "${disallowed_dirs[@]}"; do
if [ -d "${d}" ]; then
missing_required+=("${d}/ (disallowed)")
fi
d_norm="${d%/}"
[ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)")
done
for f in "${disallowed_files[@]}"; do
if [ -f "${f}" ]; then
missing_required+=("${f} (disallowed)")
fi
[ -f "${f}" ] && missing_required+=("${f} (disallowed)")
done
git fetch origin --prune