|
37 | 37 | _manual_steps: list[str] = [] # pylint: disable=invalid-name |
38 | 38 |
|
39 | 39 |
|
| 40 | +_BLACK_MIGRATION_WORKFLOW = ( |
| 41 | + """\ |
| 42 | +# Automatic black formatting migration for Dependabot PRs |
| 43 | +# |
| 44 | +# When Dependabot upgrades black, this workflow installs the new version |
| 45 | +# and runs `black .` so the PR already contains any formatting changes |
| 46 | +# introduced by the upgrade, while leaving the PR open for review. |
| 47 | +# |
| 48 | +# Black uses calendar versioning. Only the first release of a new calendar |
| 49 | +# year may introduce formatting changes (major bump in Dependabot's terms). |
| 50 | +# Minor and patch updates within a year keep formatting stable, so they stay |
| 51 | +# in the regular Dependabot groups and are auto-merged normally. |
| 52 | +# |
| 53 | +# The companion auto-dependabot workflow skips major black PRs so they're |
| 54 | +# handled exclusively by this migration workflow. |
| 55 | +# |
| 56 | +# XXX: !!! SECURITY WARNING !!! |
| 57 | +# pull_request_target has write access to the repo, and can read secrets. |
| 58 | +# This is required because Dependabot PRs are treated as fork PRs: the |
| 59 | +# GITHUB_TOKEN is read-only and secrets are unavailable with a plain |
| 60 | +# pull_request trigger. The action mitigates the risk by: |
| 61 | +# - Never executing code from the PR (the migration script is embedded |
| 62 | +# in this workflow file on the base branch, not taken from the PR). |
| 63 | +# - Gating migration steps on github.actor == 'dependabot[bot]'. |
| 64 | +# - Running checkout with persist-credentials: false and isolating |
| 65 | +# push credentials from the migration script environment. |
| 66 | +# For more details read: |
| 67 | +# https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ |
| 68 | +
|
| 69 | +name: Black Migration |
| 70 | +
|
| 71 | +on: |
| 72 | + merge_group: # To allow using this as a required check for merging |
| 73 | + pull_request_target: |
| 74 | + types: [opened, synchronize, reopened, labeled, unlabeled] |
| 75 | +
|
| 76 | +permissions: |
| 77 | + # Commit reformatted files back to the PR branch. |
| 78 | + contents: write |
| 79 | + # Create and normalize migration state labels. |
| 80 | + issues: write |
| 81 | + # Read/update pull request metadata and comments. |
| 82 | + pull-requests: write |
| 83 | +
|
| 84 | +jobs: |
| 85 | + black-migration: |
| 86 | + name: Migrate Black |
| 87 | + # Skip if it was triggered by the merge queue. We only need the workflow to |
| 88 | + # be executed to meet the "Required check" condition for merging, but we |
| 89 | + # don't need to actually run the job, having the job present as Skipped is |
| 90 | + # enough. |
| 91 | + if: | |
| 92 | + github.event_name == 'pull_request_target' && |
| 93 | + github.actor == 'dependabot[bot]' && |
| 94 | + contains(github.event.pull_request.title, 'Bump black from ') |
| 95 | + runs-on: ubuntu-24.04 |
| 96 | + steps: |
| 97 | + - name: Generate token |
| 98 | + id: create-app-token |
| 99 | + uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0 |
| 100 | + with: |
| 101 | + app-id: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_ID }} |
| 102 | + private-key: ${{ secrets.FREQUENZ_AUTO_DEPENDABOT_APP_PRIVATE_KEY }} |
| 103 | + # Push reformatted files to the PR branch. |
| 104 | + permission-contents: write |
| 105 | + # Create and normalize migration state labels. |
| 106 | + permission-issues: write |
| 107 | + # Read/update pull request metadata and labels. |
| 108 | + permission-pull-requests: write |
| 109 | + - name: Migrate |
| 110 | + uses: frequenz-floss/gh-action-dependabot-migrate@""" |
| 111 | + # Broken just to avoid flake8 maximum line length check |
| 112 | + """b389f72f9282346920150a67495efbae450ac07b # v1.1.0" |
| 113 | + with: |
| 114 | + migration-script: | |
| 115 | + import os |
| 116 | + import subprocess |
| 117 | + import sys |
| 118 | +
|
| 119 | + version = os.environ["MIGRATION_VERSION"].lstrip("v") |
| 120 | + subprocess.run( |
| 121 | + [sys.executable, "-Im", "pip", "install", f"black=={version}"], |
| 122 | + check=True, |
| 123 | + ) |
| 124 | + subprocess.run([sys.executable, "-Im", "black", "."], check=True) |
| 125 | + token: ${{ steps.create-app-token.outputs.token }} |
| 126 | + auto-merge-on-changes: "false" |
| 127 | + sign-commits: "true" |
| 128 | + auto-merged-label: "tool:auto-merged" |
| 129 | + migrated-label: "tool:black:migration:executed" |
| 130 | + intervention-pending-label: "tool:black:migration:intervention-pending" |
| 131 | + intervention-done-label: "tool:black:migration:intervention-done" |
| 132 | +""" |
| 133 | +) |
| 134 | + |
| 135 | + |
40 | 136 | _RELEASE_NOTES_CHECK_REPLACEMENTS = [ |
41 | 137 | ( |
42 | 138 | " permissions:\n pull-requests: read\n", |
@@ -65,6 +161,9 @@ def main() -> None: |
65 | 161 | print("Updating generated Dependabot workflows...") |
66 | 162 | migrate_dependabot_workflows() |
67 | 163 | print("=" * 72) |
| 164 | + print("Creating black migration workflow...") |
| 165 | + _migrate_black_migration_workflow() |
| 166 | + print("=" * 72) |
68 | 167 | print("Updating auxiliary GitHub workflows...") |
69 | 168 | migrate_auxiliary_workflows() |
70 | 169 | print("=" * 72) |
@@ -279,6 +378,16 @@ def migrate_dependabot_workflows() -> None: |
279 | 378 | " # Approve PRs, add labels, and enable auto-merge.\n" |
280 | 379 | " permission-pull-requests: write\n", |
281 | 380 | ), |
| 381 | + ( |
| 382 | + " !contains(github.event.pull_request.title, " |
| 383 | + "'the repo-config group')\n" |
| 384 | + " runs-on:", |
| 385 | + " !contains(github.event.pull_request.title, " |
| 386 | + "'the repo-config group') &&\n" |
| 387 | + " !contains(github.event.pull_request.title, " |
| 388 | + "'Bump black from ')\n" |
| 389 | + " runs-on:", |
| 390 | + ), |
282 | 391 | ], |
283 | 392 | description="updated Dependabot auto-merge workflow", |
284 | 393 | ) |
@@ -358,6 +467,14 @@ def migrate_auxiliary_workflows() -> None: |
358 | 467 | ) |
359 | 468 |
|
360 | 469 |
|
| 470 | +def _migrate_black_migration_workflow() -> None: |
| 471 | + """Create or replace the black formatting migration workflow.""" |
| 472 | + filepath = Path(".github/workflows/black-migration.yaml") |
| 473 | + action = "Updated" if filepath.exists() else "Created" |
| 474 | + replace_file_atomically(filepath, _BLACK_MIGRATION_WORKFLOW) |
| 475 | + print(f" {action} {filepath}: black formatting migration workflow") |
| 476 | + |
| 477 | + |
361 | 478 | _WORKFLOW_ACTION_PINS: dict[str, str] = { |
362 | 479 | "frequenz-floss/gh-action-setup-git": ( |
363 | 480 | "16952aac3ccc01d27412fe0dea3ea946530dcace # v1.0.0" |
|
0 commit comments