Update repo_health.yml
This commit is contained in:
131
.github/workflows/repo_health.yml
vendored
131
.github/workflows/repo_health.yml
vendored
@@ -59,15 +59,20 @@ permissions:
|
|||||||
env:
|
env:
|
||||||
# Global policy variables baked into workflow
|
# Global policy variables baked into workflow
|
||||||
ALLOWED_SFTP_PROTOCOLS: sftp
|
ALLOWED_SFTP_PROTOCOLS: sftp
|
||||||
|
|
||||||
|
# Release policy
|
||||||
RELEASE_REQUIRED_VARS: FTP_HOST,FTP_USER,FTP_PATH
|
RELEASE_REQUIRED_VARS: FTP_HOST,FTP_USER,FTP_PATH
|
||||||
RELEASE_OPTIONAL_VARS: FTP_KEY,FTP_PASSWORD,FTP_PROTOCOL,FTP_PORT,FTP_PATH_SUFFIX
|
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
|
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_OPTIONAL_FILES: SECURITY.md,GOVERNANCE.md,.editorconfig,.gitattributes,.gitignore
|
||||||
REPO_REQUIRED_PATHS: .github/workflows,scripts,docs,dev
|
|
||||||
REPO_DISALLOWED_DIRS: src
|
REPO_DISALLOWED_DIRS: src
|
||||||
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
REPO_DISALLOWED_FILES: TODO.md,todo.md
|
||||||
|
|
||||||
@@ -96,25 +101,25 @@ jobs:
|
|||||||
const res = await github.rest.repos.getCollaboratorPermissionLevel({
|
const res = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||||
owner: context.repo.owner,
|
owner: context.repo.owner,
|
||||||
repo: context.repo.repo,
|
repo: context.repo.repo,
|
||||||
username: context.actor
|
username: context.actor,
|
||||||
});
|
});
|
||||||
|
|
||||||
const permission = (res?.data?.permission || 'unknown').toLowerCase();
|
const permission = (res?.data?.permission || "unknown").toLowerCase();
|
||||||
const allowed = permission === 'admin';
|
const allowed = permission === "admin";
|
||||||
|
|
||||||
core.setOutput('permission', permission);
|
core.setOutput("permission", permission);
|
||||||
core.setOutput('allowed', allowed ? 'true' : 'false');
|
core.setOutput("allowed", allowed ? "true" : "false");
|
||||||
|
|
||||||
const lines = [];
|
const lines = [];
|
||||||
lines.push('### Access control');
|
lines.push("### Access control");
|
||||||
lines.push('');
|
lines.push("");
|
||||||
lines.push(`Actor: ${context.actor}`);
|
lines.push(`Actor: ${context.actor}`);
|
||||||
lines.push(`Permission: ${permission}`);
|
lines.push(`Permission: ${permission}`);
|
||||||
lines.push(`Allowed: ${allowed}`);
|
lines.push(`Allowed: ${allowed}`);
|
||||||
lines.push('');
|
lines.push("");
|
||||||
lines.push('Policy: This workflow runs only for users with admin permission on the repository.');
|
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
|
- name: Deny execution when not permitted
|
||||||
if: ${{ steps.perm.outputs.allowed != 'true' }}
|
if: ${{ steps.perm.outputs.allowed != 'true' }}
|
||||||
@@ -138,7 +143,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Guardrails release secrets and vars
|
- name: Guardrails release vars
|
||||||
env:
|
env:
|
||||||
PROFILE_RAW: ${{ github.event.inputs.profile }}
|
PROFILE_RAW: ${{ github.event.inputs.profile }}
|
||||||
FTP_HOST: ${{ secrets.FTP_HOST }}
|
FTP_HOST: ${{ secrets.FTP_HOST }}
|
||||||
@@ -184,7 +189,6 @@ jobs:
|
|||||||
done
|
done
|
||||||
|
|
||||||
proto="${FTP_PROTOCOL:-sftp}"
|
proto="${FTP_PROTOCOL:-sftp}"
|
||||||
|
|
||||||
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
|
||||||
@@ -224,6 +228,8 @@ jobs:
|
|||||||
FTP_KEY: ${{ secrets.FTP_KEY }}
|
FTP_KEY: ${{ secrets.FTP_KEY }}
|
||||||
FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
|
FTP_PASSWORD: ${{ secrets.FTP_PASSWORD }}
|
||||||
FTP_PORT: ${{ secrets.FTP_PORT }}
|
FTP_PORT: ${{ secrets.FTP_PORT }}
|
||||||
|
FTP_PATH: ${{ secrets.FTP_PATH }}
|
||||||
|
FTP_PATH_SUFFIX: ${{ vars.FTP_PATH_SUFFIX }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
@@ -241,15 +247,32 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
mkdir -p "$HOME/.ssh"
|
port="${FTP_PORT:-22}"
|
||||||
|
|
||||||
key_file="$HOME/.ssh/ci_sftp_key"
|
target_path="${FTP_PATH}"
|
||||||
use_key=false
|
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
|
if [ -n "${FTP_KEY:-}" ]; then
|
||||||
|
mkdir -p "$HOME/.ssh"
|
||||||
|
key_file="$HOME/.ssh/ci_sftp_key"
|
||||||
printf '%s\n' "${FTP_KEY}" > "${key_file}"
|
printf '%s\n' "${FTP_KEY}" > "${key_file}"
|
||||||
chmod 600 "${key_file}"
|
chmod 600 "${key_file}"
|
||||||
use_key=true
|
|
||||||
|
|
||||||
if [ -n "${FTP_PASSWORD:-}" ]; then
|
if [ -n "${FTP_PASSWORD:-}" ]; then
|
||||||
first_line="$(head -n 1 "${key_file}" || true)"
|
first_line="$(head -n 1 "${key_file}" || true)"
|
||||||
@@ -259,33 +282,17 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
ssh-keygen -p -P "${FTP_PASSWORD}" -N '' -f "${key_file}" >/dev/null
|
ssh-keygen -p -P "${FTP_PASSWORD}" -N '' -f "${key_file}" >/dev/null
|
||||||
fi
|
fi
|
||||||
fi
|
|
||||||
|
|
||||||
port="${FTP_PORT:-22}"
|
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=$?
|
||||||
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
|
|
||||||
elif [ -n "${FTP_PASSWORD:-}" ]; then
|
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)
|
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
|
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
|
||||||
fi
|
fi
|
||||||
sftp_rc=$?
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
printf '%s\n' '### SFTP connectivity result' >> "${GITHUB_STEP_SUMMARY}"
|
printf '%s\n' '### SFTP connectivity result' >> "${GITHUB_STEP_SUMMARY}"
|
||||||
@@ -298,7 +305,7 @@ jobs:
|
|||||||
printf '%s\n' "Status: FAILED (exit code ${sftp_rc})"
|
printf '%s\n' "Status: FAILED (exit code ${sftp_rc})"
|
||||||
printf '\n'
|
printf '\n'
|
||||||
printf '%s\n' 'Last SFTP output'
|
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}"
|
} >> "${GITHUB_STEP_SUMMARY}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
@@ -346,28 +353,31 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
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}"
|
IFS=',' read -r -a allowed_dirs <<< "${SCRIPTS_ALLOWED_DIRS}"
|
||||||
|
|
||||||
missing_dirs=()
|
missing_dirs=()
|
||||||
unapproved_dirs=()
|
unapproved_dirs=()
|
||||||
|
|
||||||
for d in "${recommended_dirs[@]}"; do
|
for d in "${required_dirs[@]}"; do
|
||||||
[ ! -d "${d}" ] && missing_dirs+=("${d}/")
|
req="${d%/}"
|
||||||
|
[ ! -d "${req}" ] && missing_dirs+=("${req}/")
|
||||||
done
|
done
|
||||||
|
|
||||||
while IFS= read -r d; do
|
while IFS= read -r d; do
|
||||||
allowed=false
|
allowed=false
|
||||||
for a in "${allowed_dirs[@]}"; do
|
for a in "${allowed_dirs[@]}"; do
|
||||||
[ "${d}" = "${a}" ] && allowed=true
|
a_norm="${a%/}"
|
||||||
|
[ "${d%/}" = "${a_norm}" ] && allowed=true
|
||||||
done
|
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#^\./##')
|
done < <(find scripts -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sed 's#^\./##')
|
||||||
|
|
||||||
{
|
{
|
||||||
printf '%s\n' '### Scripts governance'
|
printf '%s\n' '### Scripts governance'
|
||||||
|
|
||||||
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
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
|
for m in "${missing_dirs[@]}"; do printf '%s\n' "- ${m}"; done
|
||||||
printf '\n'
|
printf '\n'
|
||||||
fi
|
fi
|
||||||
@@ -381,9 +391,9 @@ jobs:
|
|||||||
printf '%s\n' '| Area | Status | Notes |'
|
printf '%s\n' '| Area | Status | Notes |'
|
||||||
printf '%s\n' '|------|--------|-------|'
|
printf '%s\n' '|------|--------|-------|'
|
||||||
if [ "${#missing_dirs[@]}" -gt 0 ]; then
|
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
|
else
|
||||||
printf '%s\n' '| Recommended directories | OK | All recommended subfolders present |'
|
printf '%s\n' '| Required directories | OK | All required subfolders present |'
|
||||||
fi
|
fi
|
||||||
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
if [ "${#unapproved_dirs[@]}" -gt 0 ]; then
|
||||||
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
|
printf '%s\n' '| Directory policy | Warning | Unapproved directories detected |'
|
||||||
@@ -430,37 +440,34 @@ jobs:
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
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 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_dirs <<< "${REPO_DISALLOWED_DIRS}"
|
||||||
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
|
IFS=',' read -r -a disallowed_files <<< "${REPO_DISALLOWED_FILES}"
|
||||||
|
|
||||||
missing_required=()
|
missing_required=()
|
||||||
missing_optional=()
|
missing_optional=()
|
||||||
|
|
||||||
for f in "${required_files[@]}"; do
|
for item in "${required_artifacts[@]}"; do
|
||||||
[ ! -f "${f}" ] && missing_required+=("${f}")
|
if printf '%s' "${item}" | grep -q '/$'; then
|
||||||
|
d="${item%/}"
|
||||||
|
[ ! -d "${d}" ] && missing_required+=("${item}")
|
||||||
|
else
|
||||||
|
[ ! -f "${item}" ] && missing_required+=("${item}")
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
for f in "${optional_files[@]}"; do
|
for f in "${optional_files[@]}"; do
|
||||||
[ ! -f "${f}" ] && missing_optional+=("${f}")
|
[ ! -f "${f}" ] && missing_optional+=("${f}")
|
||||||
done
|
done
|
||||||
|
|
||||||
for p in "${required_paths[@]}"; do
|
|
||||||
[ ! -d "${p}" ] && missing_required+=("${p}/")
|
|
||||||
done
|
|
||||||
|
|
||||||
for d in "${disallowed_dirs[@]}"; do
|
for d in "${disallowed_dirs[@]}"; do
|
||||||
if [ -d "${d}" ]; then
|
d_norm="${d%/}"
|
||||||
missing_required+=("${d}/ (disallowed)")
|
[ -d "${d_norm}" ] && missing_required+=("${d_norm}/ (disallowed)")
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
for f in "${disallowed_files[@]}"; do
|
for f in "${disallowed_files[@]}"; do
|
||||||
if [ -f "${f}" ]; then
|
[ -f "${f}" ] && missing_required+=("${f} (disallowed)")
|
||||||
missing_required+=("${f} (disallowed)")
|
|
||||||
fi
|
|
||||||
done
|
done
|
||||||
|
|
||||||
git fetch origin --prune
|
git fetch origin --prune
|
||||||
|
|||||||
Reference in New Issue
Block a user