From 0350f6a5c13663ec6f11f2b9c1f65c72d1b096a5 Mon Sep 17 00:00:00 2001 From: Paul Kroeher Date: Tue, 5 May 2026 10:51:09 +0200 Subject: [PATCH] ci: implementation of flake bump auto approve and merge Add a GitHub workflow to automatically validate and merge pull requests that only bump flake.lock. This reduces manual review overhead for routine dependency updates while keeping the merge path constrained and auditable. The workflow uses a dedicated gitlint config and custom rule to ensure each eligible commit changes exactly flake.lock. Documentation is added for the required GitHub App, secrets, permissions, and branch-ruleset bypass setup. Signed-off-by: Paul Kroeher On-behalf-of: SAP paul.kroeher@sap.com --- .github/workflows/flake-bump.yaml | 56 +++++++++++++++++++ .gitlint_auto_approve | 15 +++++ ci/README.auto.approve.md | 43 +++++++++++++++ ci/gitlint/rules_auto_approve/only-flake.py | 61 +++++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 .github/workflows/flake-bump.yaml create mode 100644 .gitlint_auto_approve create mode 100644 ci/README.auto.approve.md create mode 100644 ci/gitlint/rules_auto_approve/only-flake.py diff --git a/.github/workflows/flake-bump.yaml b/.github/workflows/flake-bump.yaml new file mode 100644 index 0000000000..4e706be49e --- /dev/null +++ b/.github/workflows/flake-bump.yaml @@ -0,0 +1,56 @@ +name: Flake bump +on: + pull_request: + paths: + - 'flake.lock' + branches: + - gardenlinux + +jobs: + gitlint: + name: Check flake bump requirements + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - name: Set up Python 3.11 + uses: actions/setup-python@v6 + with: + python-version: "3.11" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install --upgrade gitlint + - name: Lint git commit messages + run: | + gitlint --commits origin/$GITHUB_BASE_REF.. -C .gitlint_auto_approve + + gitmerge: + name: Merge flake bump + needs: gitlint # hard dependency on this check job + runs-on: ubuntu-latest + steps: + - name: Generate token + id: generate_token + uses: actions/create-github-app-token@v2 + with: + app-id: ${{ secrets.GH_AUTO_APPROVE_APP_ID }} + private-key: ${{ secrets.GH_AUTO_APPROVE_APP_PRIVATE_KEY }} + owner: daedalus-ca + repositories: test-auto-approve + - name: Merge Pull request + shell: bash + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + run: | + # GitHub CLI api + # https://cli.github.com/manual/gh_api + gh api \ + --method PUT \ + -H "Accept: application/vnd.github+json" \ + -H "X-GitHub-Api-Version: 2026-03-10" \ + /repos/${GITHUB_REPOSITORY}/pulls/${{ github.event.number }}/merge \ + -f 'merge_method=rebase' diff --git a/.gitlint_auto_approve b/.gitlint_auto_approve new file mode 100644 index 0000000000..d79e9b21f5 --- /dev/null +++ b/.gitlint_auto_approve @@ -0,0 +1,15 @@ +[general] +extra-path=ci/gitlint/rules_auto_approve +regex-style-search=true +ignore=body-is-missing,body-max-line-length + +# default 72 +[title-max-length] +line-length=72 + +# Empty bodies are fine +[body-min-length] +min-length=0 + +[UC-flake] +filepath=flake.lock diff --git a/ci/README.auto.approve.md b/ci/README.auto.approve.md new file mode 100644 index 0000000000..a38d3e6e7e --- /dev/null +++ b/ci/README.auto.approve.md @@ -0,0 +1,43 @@ +# Flake bump auto approve + +## Description + +We add a github workflow `Flake bump`. +First job of this workflow checks if a merge request contains only one commit which updates the `flake.lock` file. +If this condition is met the second job approve this merge request and automatically merge it. +The approval is done with a dedicated GitHubApp. + +## Install + +* Follow this guide: https://docs.github.com/en/apps/creating-github-apps/registering-a-github-app/registering-a-github-app +* Create a GitHub app `auto-approve-app` in your GH organization + * github.com/github-organization/ -> Settings -> Developer Settings -> GitHub Apps -> New GitHub App + * Add a name and Homepage URL + * Add Repository Permissions + * Actions: RO + * Contents: RW + * Metadata: RO + * Pull Requests: RW + * Workflows: RW + +* Install this app into your organization + * github.com/github-organization/ -> Settings -> Developer Settings -> GitHub Apps -> Select `auto-approve-app` -> Install App + * Only select repositories: + * repository-name + +* Find app_id + * github.com/github-organization/ -> Settings -> Developer Settings -> GitHub Apps -> Select `auto-approve-app` + * you find the app_id in the `General` section + +* Create app client secret + * github.com/github-organization/ -> Settings -> Developer Settings -> GitHub Apps -> Select `auto-approve-app` -> Client secrets + * The private key will be downloaded using your browser + * Save it in 1Password or vault + +* Create two organization secrets: + * GH_AUTO_APPROVE_APP_ID + * GH_AUTO_APPROVE_APP_PRIVATE_KEY + +* Add Github App `auto-approve-app` to your branch ruleset. + * github.com/github-organization/repository -> Settings -> Rules -> Rulesets -> rule name -> Bypass list -> Add bypass + * This allows the Github App `auto-approve-app` to merge the MRs even if other conditions of the ruleset are not met. diff --git a/ci/gitlint/rules_auto_approve/only-flake.py b/ci/gitlint/rules_auto_approve/only-flake.py new file mode 100644 index 0000000000..567323869f --- /dev/null +++ b/ci/gitlint/rules_auto_approve/only-flake.py @@ -0,0 +1,61 @@ +# Copyright © 2026 Cyberus Technology GmbH +# +# SPDX-License-Identifier: Apache-2.0 +# +from gitlint.options import ListOption, StrOption +from gitlint.rules import CommitRule, RuleViolation + +class SingleSpecificFile(CommitRule): + """Reject commits which modifies files other than those specified""" + id = "UC-flake" + name = "body-require-single-specific-file" + description = "Commit must change exactly one specific file" + target = None # Applies to entire commit + options_spec = [ + StrOption( + "filepath", + "flake.lock", + "The file path to check" + ) + ] + + def validate(self, commit): + changed_files = getattr(commit, "changed_files", None) + if changed_files is None: + # Newer gitlint commit objects expose the touched paths directly via + # `changed_files`. Older variants may only expose + # `changed_files_stats`, a mapping keyed by changed path, so we fall + # back to its keys when `changed_files` is unavailable. + changed_files_stats = getattr(commit, "changed_files_stats", {}) + changed_files = list(changed_files_stats.keys()) + + if len(changed_files) != 1: + return [RuleViolation("commit-changes-multiple-files-or-none", f"Commit changes {len(changed_files)} files, expected exactly 1: {', '.join(changed_files)}")] + + filepath = self.options["filepath"].value + if changed_files[0] != filepath: + return [RuleViolation("commit-wrong-file", f"Commit changes '{changed_files[0]}', expected only '{filepath}'")] + +#################### +# Usage of this rule +#################### +# +# .gitlint_auto_approve file +# [general] +# extra-path=ci/gitlint/rules_auto_approve +# regex-style-search=true +# ignore=body-is-missing,body-max-line-length + +# # default 72 +# [title-max-length] +# line-length=72 + +# # Empty bodies are fine +# [body-min-length] +# min-length=0 + +# [UC-flake] +# filepath=flake.lock + +## run with +# nix run nixpkgs#gitlint -- --commits origin/main.. -C .gitlint_auto_approve