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

Automatic GitHub action updates #40

Merged
merged 4 commits 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
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
philiptaron marked this conversation as resolved.
Show resolved Hide resolved
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 {
philiptaron marked this conversation as resolved.
Show resolved Hide resolved
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>"
philiptaron marked this conversation as resolved.
Show resolved Hide resolved

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