Skip to content

Commit

Permalink
Automatic GitHub action updates (#40)
Browse files Browse the repository at this point in the history
Many different script updates along the way as well.
  • Loading branch information
philiptaron authored Apr 10, 2024
2 parents 44d5e2e + 815a4ec commit 6d09601
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 27 deletions.
22 changes: 21 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
- main

jobs:
check:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -14,3 +14,23 @@ jobs:

- name: build
run: nix-build -A ci

test-update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: cachix/install-nix-action@v26

- name: test update script
run: |
nix-build -A autoPrUpdate
{
result/bin/auto-pr-update .
echo ""
echo '```diff'
git diff
echo '```'
} > $GITHUB_STEP_SUMMARY
env:
GH_TOKEN: ${{ github.token }}
2 changes: 2 additions & 0 deletions .github/workflows/update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ jobs:
run: |
nix-build repo -A autoPrUpdate
result/bin/auto-pr-update repo > body
env:
GH_TOKEN: ${{ github.token }}

- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
Expand Down
87 changes: 61 additions & 26 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ let
config = {};
overlays = [];
};
inherit (pkgs) lib;

runtimeExprPath = ./src/eval.nix;
testNixpkgsPath = ./tests/mock-nixpkgs.nix;
Expand All @@ -32,8 +33,12 @@ let


results = {
build = pkgs.callPackage ./package.nix {
inherit nixpkgsLibPath initNix runtimeExprPath testNixpkgsPath;
# We're using this value as the root result. By default, derivations expose all of their
# internal attributes, which is very messy. We prevent this using lib.lazyDerivation
build = lib.lazyDerivation {
derivation = pkgs.callPackage ./package.nix {
inherit nixpkgsLibPath initNix runtimeExprPath testNixpkgsPath;
};
};

shell = pkgs.mkShell {
Expand All @@ -48,31 +53,61 @@ let
};

# Run regularly by CI and turned into a PR
autoPrUpdate = pkgs.writeShellApplication {
name = "auto-pr-update";
runtimeInputs = with pkgs; [
npins
cargo
];
text =
let
commands = {
"npins changes" = ''
npins update --directory "$REPO_ROOT/npins"'';
"cargo changes" = ''
cargo update --manifest-path "$REPO_ROOT/Cargo.toml"'';
autoPrUpdate =
let
updateScripts = {
npins = pkgs.writeShellApplication {
name = "update-npins";
runtimeInputs = with pkgs; [
npins
];
text = ''
echo "<details><summary>npins changes</summary>"
# Needed because GitHub's rendering of the first body line breaks down otherwise
echo ""
echo '```'
npins update --directory "$1/npins" 2>&1
echo '```'
echo "</details>"
'';
};
in
''
REPO_ROOT=$1
echo "Run automated updates"
''
+ pkgs.lib.concatStrings (pkgs.lib.mapAttrsToList (title: command: ''
echo -e '<details><summary>${title}</summary>\n\n```'
${command} 2>&1
echo -e '```\n</details>'
'') commands);
};
cargo = pkgs.writeShellApplication {
name = "update-cargo";
runtimeInputs = with pkgs; [
cargo
];
text = ''
echo "<details><summary>cargo changes</summary>"
# Needed because GitHub's rendering of the first body line breaks down otherwise
echo ""
echo '```'
cargo update --manifest-path "$1/Cargo.toml" 2>&1
echo '```'
echo "</details>"
'';
};
githubActions = pkgs.writeShellApplication {
name = "update-github-actions";
runtimeInputs = with pkgs; [
dependabot-cli
jq
github-cli
];
text = builtins.readFile ./scripts/update-github-actions.sh;
};
};
in
pkgs.writeShellApplication {
name = "auto-pr-update";
text = ''
# Prevent impurities
unset PATH
${lib.concatMapStringsSep "\n" (script: ''
echo >&2 "Running ${script}"
${lib.getExe script} "$1"
'') (lib.attrValues updateScripts)}
'';
};

# Tests the tool on the pinned Nixpkgs tree, this is a good sanity check
nixpkgsCheck = pkgs.runCommand "test-nixpkgs-check-by-name" {
Expand Down
56 changes: 56 additions & 0 deletions scripts/update-github-actions.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

# 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.

set -euo pipefail

REPO_ROOT=$1

echo -e "<details><summary>GitHub Action updates</summary>\n\n"

# 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>"

0 comments on commit 6d09601

Please sign in to comment.