Add secret scanning, layout checks, and a --changed mode

gitleaks runs before each commit and in CI, with known values in a redacted baseline. The layout check rejects empty readmes and main.yml role entrypoints. --changed lints only the YAML files that differ from HEAD.
This commit is contained in:
tanshu committed 2026-10-18 21:53:29 +05:30
1 parent f3845bde2b
commit 2aab1aec1e
8 files changed
+478 -19

No files matched your search

+20
View File
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# Reject placeholder commit subjects. pre-commit passes the message file.
set -euo pipefail
message_file="$1"
subject=$(head -n 1 "$message_file")
if [ -z "${subject//[[:space:]]/}" ]; then
echo "commit subject is empty" >&2
exit 1
fi
case "${subject,,}" in
misc|misc.|wip|wip.|update|updates|fix|fixes|change|changes|stuff|temp|tmp)
echo "commit subject is a placeholder: '$subject'" >&2
exit 1
;;
esac
exit 0
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
# Scan for secrets. Known values are listed in the redacted baseline.
# Usage: scripts/check-secrets.sh [--staged]
set -euo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/.."
version="8.30.1"
bin="$PWD/.scratch/bin/gitleaks"
if ! command -v gitleaks >/dev/null 2>&1; then
if [ ! -x "$bin" ]; then
case "$(uname -m)" in
x86_64|amd64) arch=x64 ;;
aarch64|arm64) arch=arm64 ;;
*)
echo "unsupported architecture: $(uname -m)" >&2
exit 1
;;
esac
echo "installing gitleaks $version into .scratch/bin" >&2
mkdir -p .scratch/bin
curl -fsSL -o .scratch/bin/gitleaks.tar.gz \
"https://github.com/gitleaks/gitleaks/releases/download/v${version}/gitleaks_${version}_linux_${arch}.tar.gz"
tar -xzf .scratch/bin/gitleaks.tar.gz -C .scratch/bin gitleaks
fi
PATH="$PWD/.scratch/bin:$PATH"
fi
if [ "${1:-}" = "--staged" ]; then
gitleaks git --pre-commit --staged --redact --verbose \
--baseline-path .gitleaks-baseline.json
else
gitleaks git --baseline-path .gitleaks-baseline.json --redact --no-banner
fi
+105 -13
View File
@@ -1,18 +1,35 @@
#!/usr/bin/env bash
# Run every check this repo has. CI and pre-commit call the same script.
# Usage: scripts/check.sh [--changed]
set -euo pipefail
shopt -s nullglob
cd "$(dirname "${BASH_SOURCE[0]}")/.."
# Ansible and uv both want a writable cache. Keep them in the checkout so the
# script works when $HOME is read-only, which is the case in the agent sandbox.
changed=0
for arg in "$@"; do
case "$arg" in
--changed) changed=1 ;;
*)
echo "unknown argument: $arg" >&2
echo "usage: scripts/check.sh [--changed]" >&2
exit 2
;;
esac
done
# Ansible and uv want writable caches. Keep them in the checkout so the script
# works when $HOME is read-only, which is the case in the agent sandbox.
export ANSIBLE_CONFIG="${ANSIBLE_CONFIG:-$PWD/ansible.cfg}"
export ANSIBLE_HOME="${ANSIBLE_HOME:-$PWD/.ansible/home}"
export ANSIBLE_LOCAL_TEMP="${ANSIBLE_LOCAL_TEMP:-$PWD/.ansible/tmp}"
export ANSIBLE_GALAXY_CACHE_DIR="${ANSIBLE_GALAXY_CACHE_DIR:-$PWD/.ansible/galaxy_cache}"
export UV_CACHE_DIR="${UV_CACHE_DIR:-$PWD/.ansible/uv-cache}"
export UV_TOOL_DIR="${UV_TOOL_DIR:-$PWD/.ansible/uv-tools}"
export ANSIBLE_GALAXY_CACHE_DIR="${ANSIBLE_GALAXY_CACHE_DIR:-$PWD/.ansible/galaxy_cache}"
mkdir -p "$ANSIBLE_LOCAL_TEMP" "$UV_CACHE_DIR" "$UV_TOOL_DIR" "$ANSIBLE_GALAXY_CACHE_DIR" "$ANSIBLE_HOME"
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$PWD/.scratch/cache}"
export PIP_CACHE_DIR="${PIP_CACHE_DIR:-$PWD/.scratch/cache/pip}"
mkdir -p "$ANSIBLE_LOCAL_TEMP" "$UV_CACHE_DIR" "$UV_TOOL_DIR" \
"$ANSIBLE_GALAXY_CACHE_DIR" "$ANSIBLE_HOME" "$XDG_CACHE_HOME" "$PIP_CACHE_DIR"
if [ -f requirements.yml ]; then
echo "== ansible-galaxy collection install =="
@@ -29,15 +46,90 @@ run() {
fi
}
echo "== yamllint =="
run yamllint .
# YAML files that differ from HEAD, including new files that are not staged yet.
yaml_files=()
if [ "$changed" = 1 ]; then
while IFS= read -r file; do
[ -n "$file" ] && yaml_files+=("$file")
done < <(
git diff --name-only --diff-filter=ACMR HEAD -- '*.yml' '*.yaml'
git ls-files --others --exclude-standard -- '*.yml' '*.yaml'
)
fi
echo "== ansible-lint =="
run ansible-lint
# ansible-lint only understands Ansible files, so keep the changed files that
# live under a service with a playbook.
ansible_files=()
if [ "$changed" = 1 ]; then
for file in "${yaml_files[@]}"; do
service="${file%%/*}"
if [ -f "$service/playbook.yml" ]; then
ansible_files+=("$file")
fi
done
fi
if [ "$changed" = 1 ] && [ "${#yaml_files[@]}" -eq 0 ]; then
echo "== changed mode: no YAML files changed =="
else
echo "== yamllint =="
if [ "$changed" = 1 ]; then
run yamllint "${yaml_files[@]}"
else
run yamllint .
fi
echo "== ansible-lint =="
if [ "$changed" = 1 ] && [ "${#ansible_files[@]}" -eq 0 ]; then
echo "no changed Ansible files"
elif [ "$changed" = 1 ]; then
run ansible-lint "${ansible_files[@]}"
else
run ansible-lint
fi
fi
echo "== layout =="
layout_ok=1
for playbook in */playbook.yml; do
service="${playbook%/playbook.yml}"
if [ ! -s "$service/readme.md" ]; then
echo "missing or empty $service/readme.md" >&2
layout_ok=0
fi
done
for entrypoint in */roles/*/tasks/main.yml */roles/*/handlers/main.yml; do
echo "role entrypoint must use .yaml: $entrypoint" >&2
layout_ok=0
done
if [ "$layout_ok" -ne 1 ]; then
exit 1
fi
echo "== gitleaks =="
./scripts/check-secrets.sh
echo "== ansible-playbook --syntax-check =="
shopt -s nullglob
for playbook in */playbook.yml; do
echo "-- $playbook"
ansible-playbook --syntax-check "$playbook"
done
playbooks=()
if [ "$changed" = 1 ]; then
for file in "${yaml_files[@]}"; do
service="${file%%/*}"
if [ -f "$service/playbook.yml" ]; then
playbooks+=("$service/playbook.yml")
fi
done
if [ "${#playbooks[@]}" -gt 0 ]; then
mapfile -t playbooks < <(printf '%s\n' "${playbooks[@]}" | sort -u)
fi
else
playbooks=(*/playbook.yml)
fi
if [ "${#playbooks[@]}" -eq 0 ]; then
echo "no playbooks to check"
else
for playbook in "${playbooks[@]}"; do
echo "-- $playbook"
ansible-playbook --syntax-check "$playbook"
done
fi