Skip to content

Instantly share code, notes, and snippets.

@mborgerson
Created March 27, 2025 22:50
Show Gist options
  • Save mborgerson/721fe7b76e543f67768afba4ced80862 to your computer and use it in GitHub Desktop.
Save mborgerson/721fe7b76e543f67768afba4ced80862 to your computer and use it in GitHub Desktop.
GitHub Actions Version Pinner
#!/bin/bash
#
# Usage: ./pin-gh-workflow-action-versions.sh input.yml > output.yml
#
# Pins GitHub Actions referenced by tags to the associated commit SHA.
# e.g.:
#
# < uses: actions/checkout@v4
# ---
# > uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
#
# GitHub has pretty low unauthenticated API limits, so you may want to create
# and use an API token before running this script. You can create a token under
# GitHub settings > Developer settings > Personal access tokens > Tokens
# (classic). Provide it to this script via the GITHUB_API_TOKEN environment
# variable.
#
# Dependencies: curl, jq
#
#-------------------------------------------------------------------------------
if ! command -v curl &> /dev/null; then
echo "Error: curl is not installed. Please install curl to proceed." >&2
exit 1
fi
if ! command -v jq &> /dev/null; then
echo "Error: jq is not installed. Please install jq to proceed." >&2
exit 1
fi
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <file-with-gh-actions-uses>"
exit 1
fi
INPUT_FILE="$1"
# Build curl options; add auth header if GITHUB_API_TOKEN is set.
curl_opts=("-s")
if [ -n "$GITHUB_API_TOKEN" ]; then
curl_opts+=("-H" "Authorization: token $GITHUB_API_TOKEN")
fi
while IFS= read -r line; do
if [[ $line =~ ^([[:space:]]*-?[[:space:]]*uses:[[:space:]]*)([^/@[:space:]]+)/([^@[:space:]]+)@((v[0-9]+(\.[0-9]+){0,2})) ]]; then
prefix="${BASH_REMATCH[1]}"
owner="${BASH_REMATCH[2]}"
repo="${BASH_REMATCH[3]}"
version="${BASH_REMATCH[4]}"
echo "Pinning $owner/$repo@$version..." >&2
# Query the Git ref API to get the tag object
ref_url="https://api.github.com/repos/${owner}/${repo}/git/ref/tags/${version}"
ref_json=$(curl "${curl_opts[@]}" "$ref_url")
# Extract the object type and SHA from the JSON.
object_type=$(echo "$ref_json" | jq -r '.object.type')
object_sha=$(echo "$ref_json" | jq -r '.object.sha')
if [ "$object_type" = "tag" ]; then
# For annotated tags, dereference the tag object to get the commit SHA.
tag_url="https://api.github.com/repos/${owner}/${repo}/git/tags/${object_sha}"
commit_sha=$(curl "${curl_opts[@]}" "$tag_url" | jq -r '.object.sha')
elif [ "$object_type" = "commit" ]; then
commit_sha="$object_sha"
else
echo "Warning: Unrecognized object type for ${owner}/${repo}:${version}" >&2
echo "$line"
continue
fi
if [ -z "$commit_sha" ] || [ "$commit_sha" = "null" ]; then
echo "Warning: Could not resolve commit SHA for ${owner}/${repo}:${version}" >&2
echo "$line"
continue
fi
echo "${prefix}${owner}/${repo}@${commit_sha} # ${version}"
else
# If the line doesn't match, print it unchanged.
echo "$line"
fi
done < "$INPUT_FILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment