mirror of
https://github.com/lukaszraczylo/shared-actions.git
synced 2026-10-12 04:54:48 +00:00
scripts/check-actions.sh runs actionlint and checks that every external uses: reference resolves. The validate workflow runs it, then runs the go-test and node-build actions and the node-pr workflow against small fixtures, so a bump of an action they use is exercised before it merges. renovate.json bumps the actions this repository uses. Minor and patch bumps merge themselves once validate is green. Major bumps wait for a person, because every consumer calls these actions at @main. The same script runs as a Renovate post-upgrade task and marks a failing branch so no PR opens.
116 lines
4.4 KiB
Bash
Executable File
116 lines
4.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Static checks for the workflows and composite actions in this repository:
|
|
# 1. actionlint (schema, expressions, and shell steps through shellcheck when present)
|
|
# 2. every external `uses:` reference resolves to a real tag or branch
|
|
#
|
|
# Usage: scripts/check-actions.sh [--ci | --renovate]
|
|
# --ci exit 1 when a check fails (default)
|
|
# --renovate exit 0 and write .validation-failed when a check fails, so the
|
|
# Renovate runner deletes the branch before a PR can open
|
|
#
|
|
# Needs: bash, git, curl, tar, sha256sum or shasum. No other tools: actionlint is
|
|
# downloaded at a pinned version and checked against a pinned SHA-256.
|
|
set -u
|
|
|
|
mode=--ci
|
|
case "${1:-}" in
|
|
"" | --ci) mode=--ci ;;
|
|
--renovate) mode=--renovate ;;
|
|
*) echo "usage: $0 [--ci | --renovate]" >&2; exit 2 ;;
|
|
esac
|
|
|
|
ACTIONLINT_VERSION=1.7.12
|
|
ACTIONLINT_SHA256_AMD64=8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
|
|
ACTIONLINT_SHA256_ARM64=325e971b6ba9bfa504672e29be93c24981eeb1c07576d730e9f7c8805afff0c6
|
|
marker=.validation-failed
|
|
|
|
cd "$(git rev-parse --show-toplevel)" || exit 2
|
|
|
|
fail=0
|
|
problem() {
|
|
echo "::error::$*" >&2
|
|
fail=1
|
|
}
|
|
|
|
sha256_of() {
|
|
if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1; else shasum -a 256 "$1" | cut -d' ' -f1; fi
|
|
}
|
|
|
|
install_actionlint() {
|
|
local os arch want url tmp
|
|
case "$(uname -s)" in Linux) os=linux ;; Darwin) os=darwin ;; *) return 1 ;; esac
|
|
case "$(uname -m)" in
|
|
x86_64 | amd64) arch=amd64; want=$ACTIONLINT_SHA256_AMD64 ;;
|
|
aarch64 | arm64) arch=arm64; want=$ACTIONLINT_SHA256_ARM64 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
# The pinned checksums are for the linux builds only.
|
|
[ "$os" = linux ] || return 1
|
|
tmp=$(mktemp -d)
|
|
url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_${os}_${arch}.tar.gz"
|
|
curl -fsSL --retry 3 -o "$tmp/a.tgz" "$url" || return 1
|
|
[ "$(sha256_of "$tmp/a.tgz")" = "$want" ] || { echo "actionlint checksum mismatch" >&2; return 1; }
|
|
tar -xzf "$tmp/a.tgz" -C "$tmp" actionlint || return 1
|
|
echo "$tmp/actionlint"
|
|
}
|
|
|
|
run_actionlint() {
|
|
local bin
|
|
if command -v actionlint >/dev/null 2>&1; then
|
|
bin=$(command -v actionlint)
|
|
else
|
|
bin=$(install_actionlint) || { problem "cannot install actionlint ${ACTIONLINT_VERSION}"; return; }
|
|
fi
|
|
# -shellcheck= disables the shellcheck pass when the binary is missing.
|
|
local opts=()
|
|
command -v shellcheck >/dev/null 2>&1 || opts+=(-shellcheck=)
|
|
# Info and style findings from shellcheck (for example SC2086 on intentional word
|
|
# splitting) do not fail the gate. Warnings and errors do. Composite actions are
|
|
# not linted by actionlint 1.7; workflows are.
|
|
opts+=(-ignore 'shellcheck reported issue.+SC[0-9]+:(info|style):')
|
|
if ! "$bin" "${opts[@]}" -color .github/workflows/*.y*ml; then
|
|
problem "actionlint reported problems"
|
|
fi
|
|
}
|
|
|
|
# Resolves every `uses: owner/repo[/path]@ref` that points outside this repository.
|
|
check_refs() {
|
|
local files uses ref repo path checked=""
|
|
files=$(find .github -type f \( -name '*.yml' -o -name '*.yaml' \))
|
|
uses=$(grep -hoE '^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*[^[:space:]#]+' $files | sed -E 's/^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*//' | sort -u)
|
|
while IFS= read -r u; do
|
|
[ -n "$u" ] || continue
|
|
case "$u" in ./* | docker://*) continue ;; esac
|
|
repo=${u%%@*}
|
|
ref=${u#*@}
|
|
[ "$repo" != "$u" ] || { problem "no @ref in 'uses: $u'"; continue; }
|
|
# owner/repo/sub/path -> owner/repo
|
|
path=$(echo "$repo" | cut -d/ -f1,2)
|
|
# This repository's own composite actions are tracked at @main by design.
|
|
[ "$path" = lukaszraczylo/shared-actions ] && continue
|
|
case " $checked " in *" $path@$ref "*) continue ;; esac
|
|
checked="$checked $path@$ref"
|
|
if ! git ls-remote --exit-code "https://github.com/$path" "refs/tags/$ref" "refs/heads/$ref" >/dev/null 2>&1; then
|
|
# A full commit SHA is not listed by ls-remote; accept it when the repository answers at all.
|
|
if echo "$ref" | grep -qE '^[0-9a-f]{40}$' && git ls-remote --exit-code "https://github.com/$path" HEAD >/dev/null 2>&1; then
|
|
continue
|
|
fi
|
|
problem "'uses: $u' does not resolve: no tag or branch $ref in $path"
|
|
fi
|
|
done <<<"$uses"
|
|
}
|
|
|
|
rm -f "$marker"
|
|
run_actionlint
|
|
check_refs
|
|
|
|
if [ "$fail" -ne 0 ]; then
|
|
if [ "$mode" = --renovate ]; then
|
|
echo "validation failed; writing $marker" >&2
|
|
printf 'actions validation failed\n' >"$marker"
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
fi
|
|
echo "actions validation passed"
|