-
Notifications
You must be signed in to change notification settings - Fork 1.2k
feat(ci): re-introduce pre-commit fixer workflow but limit autofixes for now #4162
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,246 @@ | ||
| name: Pre-commit Fix | ||
| run-name: Apply a subset of pre-commit fixes | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| pr-number: | ||
| description: Pull request number to update | ||
| required: true | ||
|
|
||
| permissions: | ||
| contents: write | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| autofix: | ||
| name: Run pre-commit and push fixes | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - name: Resolve pull request metadata | ||
| id: pr | ||
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
| with: | ||
| result-encoding: string | ||
| script: | | ||
| const prNumber = Number(core.getInput('pr_number', { required: true })); | ||
| const { data: pr } = await github.rest.pulls.get({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: prNumber, | ||
| }); | ||
|
|
||
| if (pr.state !== 'open') { | ||
| core.setFailed(`Pull request #${prNumber} is not open.`); | ||
| return; | ||
| } | ||
|
|
||
| return JSON.stringify({ | ||
| number: prNumber, | ||
| headRef: pr.head.ref, | ||
| headRepo: pr.head.repo.full_name, | ||
| baseRef: pr.base.ref, | ||
| isFork: pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`, | ||
| maintainerCanModify: pr.maintainer_can_modify ? 'true' : 'false', | ||
| author: pr.user.login, | ||
| }); | ||
| pr_number: ${{ github.event.inputs.pr-number }} | ||
|
|
||
| - name: Verify push permissions | ||
| run: | | ||
| pr_info='${{ steps.pr.outputs.result }}' | ||
| head_repo=$(echo "$pr_info" | jq -r '.headRepo') | ||
| maintainer_can_modify=$(echo "$pr_info" | jq -r '.maintainerCanModify') | ||
| author=$(echo "$pr_info" | jq -r '.author') | ||
|
|
||
| if [ "$head_repo" != "${{ github.repository }}" ] && [ "$maintainer_can_modify" != "true" ] && [ "${{ github.actor }}" != "$author" ]; then | ||
| echo "::error::This workflow cannot push to $head_repo because 'Allow edits from maintainers' is disabled." | ||
| echo "Ask the PR author to enable the setting or run the workflow from a fork with sufficient permissions." | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Check out pull request branch | ||
| uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | ||
| with: | ||
| repository: ${{ fromJSON(steps.pr.outputs.result).headRepo }} | ||
| ref: ${{ fromJSON(steps.pr.outputs.result).headRef }} | ||
| token: ${{ secrets.GITHUB_TOKEN }} | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Retrieve trusted pre-commit config | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Security guardrail #2 (but also limitation): don't trust the pre commit config from the PR |
||
| id: trusted-config | ||
| run: | | ||
| set -euo pipefail | ||
| pr_info='${{ steps.pr.outputs.result }}' | ||
| base_ref=$(echo "$pr_info" | jq -r '.baseRef') | ||
| git fetch "https://github.com/${{ github.repository }}.git" "$base_ref:refs/remotes/upstream/$base_ref" | ||
| git show "upstream/$base_ref:.pre-commit-config.yaml" > .pre-commit-config.trusted.yaml | ||
|
|
||
| - name: Set up Python | ||
| uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 | ||
| with: | ||
| python-version: '3.12' | ||
| cache: pip | ||
| cache-dependency-path: | | ||
| **/requirements*.txt | ||
| .pre-commit-config.yaml | ||
| .pre-commit-config.trusted.yaml | ||
|
|
||
| - name: Install pre-commit tooling | ||
| run: | | ||
| python -m pip install 'pre-commit>=4.4.0' 'uv>=0.4.27' | ||
| env: | ||
| GITHUB_TOKEN: '' | ||
|
|
||
| # Spin up a temporary worktree for the base branch and ask pre-commit to | ||
| # execute the trusted codegen scripts from there while pointing their | ||
| # outputs back at the contributor's checkout. | ||
| - name: Run trusted codegen hooks | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| pr_info='${{ steps.pr.outputs.result }}' | ||
| base_ref=$(echo "$pr_info" | jq -r '.baseRef') | ||
| head_ref=$(echo "$pr_info" | jq -r '.headRef') | ||
|
|
||
| mkdir -p .trusted | ||
| git worktree add --force --detach .trusted/base "upstream/$base_ref" | ||
|
|
||
| cleanup() { | ||
| rm -f .trusted-codegen.yaml | ||
| git worktree remove --force .trusted/base 2>/dev/null || true | ||
| } | ||
| trap cleanup EXIT | ||
|
|
||
| cat <<'YAML' > .trusted-codegen.yaml | ||
| repos: | ||
| - repo: local | ||
| hooks: | ||
| - id: trusted-uv-lock | ||
| name: Trusted uv lock | ||
| entry: bash | ||
| args: | ||
| - -c | ||
| - '"$TRUSTED_HOOK_ROOT/scripts/uv-run-with-index.sh" lock' | ||
| language: system | ||
| pass_filenames: false | ||
| always_run: true | ||
| - id: trusted-distro-codegen | ||
| name: Trusted distribution codegen | ||
| entry: bash | ||
| args: | ||
| - -c | ||
| - | | ||
| "$TRUSTED_HOOK_ROOT/scripts/uv-run-with-index.sh" run --group codegen \ | ||
| "$TRUSTED_HOOK_ROOT/scripts/distro_codegen.py" --repo-root "$TRUSTED_TARGET_ROOT" | ||
| language: system | ||
| pass_filenames: false | ||
| always_run: true | ||
| - id: trusted-provider-codegen | ||
| name: Trusted provider codegen | ||
| entry: bash | ||
| args: | ||
| - -c | ||
| - | | ||
| "$TRUSTED_HOOK_ROOT/scripts/uv-run-with-index.sh" run --group codegen \ | ||
| "$TRUSTED_HOOK_ROOT/scripts/provider_codegen.py" --repo-root "$TRUSTED_TARGET_ROOT" | ||
| language: system | ||
| pass_filenames: false | ||
| always_run: true | ||
| - id: trusted-openapi-generator | ||
| name: Trusted OpenAPI generator | ||
| entry: bash | ||
| args: | ||
| - -c | ||
| - | | ||
| "$TRUSTED_HOOK_ROOT/scripts/uv-run-with-index.sh" run \ | ||
| "$TRUSTED_HOOK_ROOT/docs/openapi_generator/run_openapi_generator.sh" --target-root "$TRUSTED_TARGET_ROOT" | ||
| language: system | ||
| pass_filenames: false | ||
| always_run: true | ||
| YAML | ||
|
|
||
| export TRUSTED_HOOK_ROOT="$PWD/.trusted/base" | ||
| export TRUSTED_TARGET_ROOT="$PWD" | ||
| export PYTHONPATH="$TRUSTED_TARGET_ROOT:$TRUSTED_TARGET_ROOT/src:${PYTHONPATH:-}" | ||
| export GITHUB_BASE_REF="$base_ref" | ||
| export GITHUB_REF="refs/heads/$head_ref" | ||
|
|
||
| pre-commit run --all-files --config .trusted-codegen.yaml | ||
| env: | ||
| GITHUB_TOKEN: '' | ||
|
|
||
| - name: Run trusted pre-commit subset | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guardrail #3: only run a small subset of pre-commit hooks. Things like "npm ci" have vulnerabilities. We should likely include more of our custom things here but I think like above, should use "main" as the source of truth of the hook. |
||
| id: precommit | ||
| run: | | ||
| set -euo pipefail | ||
| echo "Running trusted pre-commit hooks from base branch" | ||
| hooks=( | ||
| trailing-whitespace | ||
| end-of-file-fixer | ||
| mixed-line-ending | ||
| insert-license | ||
| blacken-docs | ||
| ruff | ||
| ruff-format | ||
| ) | ||
| status=0 | ||
| for hook in "${hooks[@]}"; do | ||
| echo "::group::Running $hook" | ||
| if ! pre-commit run "$hook" --show-diff-on-failure --color=always --all-files --config .pre-commit-config.trusted.yaml; then | ||
| status=$? | ||
| fi | ||
| echo "::endgroup::" | ||
| done | ||
| exit "$status" | ||
| env: | ||
| RUFF_OUTPUT_FORMAT: github | ||
| GITHUB_TOKEN: '' | ||
|
|
||
| # These hooks come from the repository's base branch configuration, so | ||
| # contributors cannot smuggle new hook definitions or tweak pinned | ||
| # versions through the pull request. The selected set intentionally | ||
| # excludes repo-local entries and tooling that would execute scripts from | ||
| # the PR itself, trading coverage for a safer runner profile. It now | ||
| # focuses further on hooks that perform automatic fixes so the run only | ||
| # attempts changes that can be committed back to the branch. | ||
| - name: Configure git user | ||
| run: | | ||
| git config user.name "github-actions[bot]" | ||
| git config user.email "github-actions[bot]@users.noreply.github.com" | ||
|
|
||
| - name: Commit and push changes | ||
| id: push | ||
| run: | | ||
| set -e | ||
| branch='${{ fromJSON(steps.pr.outputs.result).headRef }}' | ||
| if [ -n "$(git status --porcelain)" ]; then | ||
| git add -A | ||
| git commit -m "Apply pre-commit fixes" | ||
| git push "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ fromJSON(steps.pr.outputs.result).headRepo }}.git" "HEAD:$branch" | ||
| echo "pushed=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "No changes to commit" | ||
| echo "pushed=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Comment on pull request | ||
| uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 | ||
| with: | ||
| script: | | ||
| const prNumber = Number(core.getInput('pr_number', { required: true })); | ||
| const pushed = core.getInput('pushed'); | ||
| const runUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; | ||
| const messages = { | ||
| true: `✅ Applied trusted pre-commit fixes in [workflow run](${runUrl}).`, | ||
| false: `ℹ️ Trusted pre-commit workflow completed with no changes. See [workflow run](${runUrl}) for details.`, | ||
| }; | ||
| await github.rest.issues.createComment({ | ||
| ...context.repo, | ||
| issue_number: prNumber, | ||
| body: messages[pushed === 'true' ? 'true' : 'false'], | ||
| }); | ||
| pr_number: ${{ github.event.inputs.pr-number }} | ||
| pushed: ${{ steps.push.outputs.pushed }} | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
security guardrail #1: manual workflow dispatch