Fix flake update workflow (#184)

This commit is contained in:
Patrick Stevens
2024-07-12 17:07:31 +01:00
committed by GitHub
parent 9f8459a7d3
commit ffaa373da9
4 changed files with 190 additions and 20 deletions

166
.github/workflows/commit.py vendored Executable file
View File

@@ -0,0 +1,166 @@
import datetime
import time
import subprocess
import requests
import os
import base64
from typing import Literal, TypedDict
class TreeEntry(TypedDict):
path: str
mode: Literal["100644", "100755", "120000"]
type: Literal["blob", "tree"]
sha: str
# GitHub API configuration
GITHUB_API_URL = "https://api.github.com"
GITHUB_TOKEN = os.environ.get("BEARER_TOKEN")
if not GITHUB_TOKEN:
raise Exception("Supply BEARER_TOKEN env var")
REPO = os.environ.get("GITHUB_REPOSITORY")
if not REPO:
raise Exception("Supply GITHUB_REPOSITORY env var")
GITHUB_BASE_REF = os.environ.get("GITHUB_BASE_REF") or ""
if not GITHUB_BASE_REF:
raise Exception("Supply GITHUB_BASE_REF env var")
GITHUB_OUTPUT = os.environ.get("GITHUB_OUTPUT") or ""
if not GITHUB_OUTPUT:
raise Exception("Supply GITHUB_OUTPUT env var")
headers = {
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {GITHUB_TOKEN}",
"X-GitHub-Api-Version": "2022-11-28"
}
def get_git_diff():
"""Get the files which have changed in the current repository."""
return subprocess.check_output(["git", "diff", "--name-only"]).decode("utf-8").splitlines()
def create_blob(content, encoding="utf-8"):
"""Create a blob in the GitHub repository."""
url = f"{GITHUB_API_URL}/repos/{REPO}/git/blobs"
data = {
"content": content,
"encoding": encoding
}
response = requests.post(url, headers=headers, json=data)
if not response.ok:
raise Exception(f"bad response: {response}")
print(f"Blob response: {response.text}")
return response.json()["sha"]
def create_tree(base_tree: str, changes: list[TreeEntry]) -> str:
"""Create a new tree with the given changes."""
url = f"{GITHUB_API_URL}/repos/{REPO}/git/trees"
data = {
"base_tree": base_tree,
"tree": changes
}
response = requests.post(url, headers=headers, json=data)
if not response.ok:
raise Exception(f"bad response: {response}")
print(f"Tree response: {response.text}")
return response.json()["sha"]
def create_commit(tree_sha: str, parent_sha: str, message: str):
"""Create a new commit."""
url = f"{GITHUB_API_URL}/repos/{REPO}/git/commits"
data = {
"message": message,
"tree": tree_sha,
"parents": [parent_sha]
}
print(f"Commit request body: {data}")
response = requests.post(url, headers=headers, json=data)
if not response.ok:
raise Exception(f"bad response: {response}")
print(f"Commit response: {response.text}")
json = response.json()
print(f"Commit: {json}")
return json["sha"]
def is_executable(filepath: str):
return os.path.isfile(filepath) and os.access(filepath, os.X_OK)
def get_current_commit() -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("utf-8").strip()
def get_current_tree() -> str:
return [line for line in subprocess.check_output(["git", "cat-file", "-p", "HEAD"]).decode("utf-8").splitlines() if line.startswith('tree ')][0][5:]
def create_branch(branch_name: str, commit_sha: str) -> None:
url = f"{GITHUB_API_URL}/repos/{REPO}/git/refs"
data = {
"ref": f"refs/heads/{branch_name}",
"sha": commit_sha
}
print(f"Branch creation request body: {data}")
response = requests.post(url, headers=headers, json=data)
if not response.ok:
raise Exception(f"bad response: {response}")
print(f"Branch creation response: {response.text}")
def create_pull_request(title: str, branch_name: str, base_branch: str) -> tuple[str, int]:
"""Returns the URL of the new PR."""
url = f"{GITHUB_API_URL}/repos/{REPO}/pulls"
data = {
"title": title,
"head": branch_name,
"base": base_branch,
"body": "Automated pull request.",
"maintainer_can_modify": True
}
print(f"PR creation request body: {data}")
response = requests.post(url, headers=headers, json=data)
if not response.ok:
raise Exception(f"bad response: {response}")
print(f"PR creation response: {response.text}")
json = response.json()
return json["url"], json["number"]
def main():
changed_files = get_git_diff()
# Create blobs and prepare tree changes
tree_changes = []
for file_path in changed_files:
with open(file_path, "rb") as file:
contents = base64.b64encode(file.read()).decode('ascii')
blob_sha = create_blob(contents, encoding="base64")
if is_executable(file_path):
mode = "100755"
else:
mode = "100644"
tree_changes.append(TreeEntry({
"path": file_path,
"mode": mode,
"type": "blob",
"sha": blob_sha
}))
base_tree = get_current_tree()
# Create a new tree
new_tree_sha = create_tree(base_tree, tree_changes)
print(f"Tree: {new_tree_sha}")
# Create a new commit
commit_message = "Automated commit"
new_commit_sha = create_commit(new_tree_sha, get_current_commit(), commit_message)
print(f"New commit created: {new_commit_sha}")
branch_name = f"flake_update" + datetime.datetime.fromtimestamp(time.time()).strftime('%Y_%m_%d-%H_%M_%S_%f')
create_branch(branch_name, new_commit_sha)
print(f"Branch created: {branch_name}")
url, pr_num = create_pull_request(title="Upgrade Nix flake and deps", branch_name=branch_name, base_branch=GITHUB_BASE_REF)
print(f"See PR at: {url}")
with open(GITHUB_OUTPUT, "a") as output_file:
output_file.write(f"pull-request-number={pr_num}")
if __name__ == "__main__":
main()

View File

@@ -14,43 +14,45 @@ jobs:
uses: actions/checkout@v4
- name: Install Nix
uses: cachix/install-nix-action@V27
uses: DeterminateSystems/nix-installer-action@main
with:
extra_nix_config: |
access-tokens = github.com=${{ secrets.GITHUB_TOKEN }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Update Nix flake
run: 'nix flake update'
- name: Build passthru
run: 'nix build .#default.passthru.fetch-deps'
run: 'nix build ".#default.passthru.fetch-deps"'
- name: Run passthru
run: 'cp "$(./result | grep "Successfully wrote " | cut -d / -f 2-)" nix/deps.nix'
run: |
set -o pipefail
./result | tee /tmp/passthru.txt
cp /"$(cat /tmp/passthru.txt | grep " wrote lockfile to " | cut -d / -f 2-)" nix/deps.nix
- name: Format
run: 'nix develop .#ci --command alejandra .'
run: 'nix develop --command alejandra .'
- name: Create Pull Request
id: cpr
uses: peter-evans/create-pull-request@v6.1.0
- name: Create token
id: generate-token
uses: actions/create-github-app-token@v1
with:
commit-message: Update Nix flake
title: Weekly Nix flake update
body: |
This is an automated pull request to update the Nix flake.
# https://github.com/actions/create-github-app-token/issues/136
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
Changes:
```
${{ steps.create-diff.outputs.diff }}
```
branch: nix-flake-update
delete-branch: true
- name: Prepare to create commit
run: python -m venv /tmp/venv && /tmp/venv/bin/python -m pip install -r .github/workflows/requirements.txt
- name: Create pull request
env:
BEARER_TOKEN: ${{ steps.generate-token.outputs.token }}
run: /tmp/venv/bin/python .github/workflows/commit.py
- name: Enable Pull Request Automerge
if: steps.cpr.outputs.pull-request-operation == 'created'
uses: peter-evans/enable-pull-request-automerge@v3
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ steps.generate-token.outputs.token }}
pull-request-number: ${{ steps.cpr.outputs.pull-request-number }}
merge-method: squash

1
.github/workflows/requirements.txt vendored Normal file
View File

@@ -0,0 +1 @@
requests

1
.gitignore vendored
View File

@@ -10,3 +10,4 @@ result
.analyzerpackages/
analysis.sarif
.direnv/
.venv/