Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify GitHub action update script #42

Merged
merged 1 commit into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ let
dependabot-cli
jq
github-cli
coreutils
];
text = builtins.readFile ./scripts/update-github-actions.sh;
};
Expand Down
85 changes: 43 additions & 42 deletions scripts/update-github-actions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,55 @@
# This script calls the dependabot CLI (https://github.com/dependabot/cli)
# to determine updates to GitHub Action dependencies in the local repository.
# It then also applies the updates and outputs the results to standard output.
# Hopefully there's built-in support for this in the future, see
# https://github.com/dependabot/cli/issues/301

set -euo pipefail

REPO_ROOT=$1

echo -e "<details><summary>GitHub Action updates</summary>\n\n"
echo "<details><summary>GitHub Action updates</summary>"
# Needed because GitHub's rendering of the first body line breaks down otherwise
echo ""

# CI sets the GH_TOKEN env var, which `gh auth token` defaults to if set
githubToken=$(gh auth token)

# Each dependabot update call tries to update all dependencies,
# but it outputs individual results for each dependency,
# with the intention of creating a PR for each.
#
# We want to have all changes together though,
# so we'd need to merge updates of the same files together,
# which could cause merge conflicts, no good.
#
# Instead, we run dependabot repeatedly,
# each time only taking the first dependency update and updating the files with it,
# such that the next iteration takes into account the previous updates.
# We do this until there's no more dependencies to be updated,
# at which point --exit-status will make jq return with a non-zero exit code.
#
# This does mean that dependabot internally needs to perform O(n^2) updates,
# but this isn't a problem in practice, since we run these updates regularly,
# so n is low.
while
# Unused argument would be the remote GitHub repo, which is not used if we pass --local
create_pull_request=$(LOCAL_GITHUB_ACCESS_TOKEN="$githubToken" \
dependabot update github_actions this-argument-is-unused --local "$REPO_ROOT" \
| jq --exit-status --compact-output --slurp 'map(select(.type == "create_pull_request")) | .[0].data')
do
title=$(jq --exit-status --raw-output '."pr-title"' <<< "$create_pull_request")
echo "<details><summary>$title</summary>"

# Needed because GitHub's rendering of the first body line breaks down otherwise
echo ""

jq --exit-status --raw-output '."pr-body"' <<< "$create_pull_request"
echo '</details>'

jq --compact-output '."updated-dependency-files"[]' <<< "$create_pull_request" \
| while read -r fileUpdate; do
file=$(jq --exit-status --raw-output '.name' <<< "$fileUpdate")
# --join-output makes sure to not output a trailing newline
jq --exit-status --raw-output --join-output '.content' <<< "$fileUpdate" > "$REPO_ROOT/$file"
done
done

echo -e "</details>"
tmp=$(mktemp -d)
trap 'rm -rf "$tmp"' exit

# Use dependency groups to update all dependencies together
# Note that repo is not used because we pass `--local` on the CLI
cat <<EOF > "$tmp/input.yml"
job:
package-manager: "github_actions"
allowed-updates:
- update-type: all
source:
directory: "/"
provider: github
repo: not/used
dependency-groups:
- name: actions
rules:
patterns:
- "*"
EOF

if ! create_pull_request=$(LOCAL_GITHUB_ACCESS_TOKEN="$githubToken" \
dependabot update --file "$tmp/input.yml" --local "$REPO_ROOT" \
| jq --exit-status 'select(.type == "create_pull_request").data'); then
# Nothing to update
exit 0
fi

jq --exit-status --raw-output '."pr-body"' <<< "$create_pull_request"

echo '</details>'

jq --compact-output '."updated-dependency-files"[]' <<< "$create_pull_request" \
| while read -r fileUpdate; do
file=$(jq --exit-status --raw-output '.name' <<< "$fileUpdate")
# --join-output makes sure to not output a trailing newline
jq --exit-status --raw-output --join-output '.content' <<< "$fileUpdate" > "$REPO_ROOT/$file"
done