-
Notifications
You must be signed in to change notification settings - Fork 502
chore(ci): introduce code freeze flow using milestones #4276
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
Open
kakkoyun
wants to merge
9
commits into
main
Choose a base branch
from
kakkoyun/code_freeze_actions
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
cfe74c5
chore(ci): Add actionlint
kakkoyun 7946221
chore(ci): Fix discovered issues
kakkoyun 2bcfc27
chore: Generated docs and fix minor issues
kakkoyun 6047729
chore(ci): Add code freeze flow
kakkoyun cd8f8b6
chore: Address review comments
kakkoyun adf09be
Merge branch 'main' into kakkoyun/code_freeze_actions
darccio eadc370
Merge branch 'main' into kakkoyun/code_freeze_actions
kakkoyun 9560093
Merge branch 'main' into kakkoyun/code_freeze_actions
kakkoyun 8d93017
Merge branch 'main' into kakkoyun/code_freeze_actions
kakkoyun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| name: PR Status Updater | ||
| description: Updates commit statuses on open PRs for code freeze enforcement | ||
|
|
||
| inputs: | ||
| github_token: | ||
| description: "GitHub token for API access" | ||
| required: true | ||
| state: | ||
| description: "The state to set (success or failure)" | ||
| required: true | ||
| workflow_name: | ||
| description: "Name of the workflow for click-through link" | ||
| required: true | ||
|
|
||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Update PR statuses | ||
| shell: bash | ||
| env: | ||
| GITHUB_TOKEN: ${{ inputs.github_token }} | ||
| STATE: ${{ inputs.state }} | ||
| WORKFLOW_NAME: ${{ inputs.workflow_name }} | ||
| run: | | ||
| set -e | ||
|
|
||
| REPO="${{ github.repository }}" | ||
| CONTEXT="code-freeze" | ||
|
|
||
| if [ "$STATE" = "failure" ]; then | ||
| DESCRIPTION="Code freeze is active - PRs not in Code Freeze/Incident milestones cannot be merged" | ||
| else | ||
| DESCRIPTION="Code freeze has ended - merging allowed" | ||
| fi | ||
|
|
||
| echo "Fetching open PRs..." | ||
|
|
||
| # Get all open PRs | ||
| PRS=$(gh pr list --repo "$REPO" --state open --json number,headRefOid,milestone --limit 1000) | ||
|
|
||
| echo "Found $(echo "$PRS" | jq length) open PRs" | ||
|
|
||
| # Process each PR | ||
| echo "$PRS" | jq -c '.[]' | while read -r pr; do | ||
| PR_NUMBER=$(echo "$pr" | jq -r '.number') | ||
| SHA=$(echo "$pr" | jq -r '.headRefOid') | ||
| MILESTONE_TITLE=$(echo "$pr" | jq -r '.milestone.title // ""') | ||
|
|
||
| # Determine the state for this PR | ||
| PR_STATE="$STATE" | ||
| PR_DESC="$DESCRIPTION" | ||
|
|
||
| # If we're freezing (state=failure), check if PR is in an allowed milestone | ||
| if [ "$STATE" = "failure" ]; then | ||
| if [[ "$MILESTONE_TITLE" == Code\ Freeze* ]] || [[ "$MILESTONE_TITLE" == Incident* ]]; then | ||
| PR_STATE="success" | ||
| PR_DESC="PR is in '$MILESTONE_TITLE' milestone - exempt from code freeze" | ||
| echo "PR #$PR_NUMBER is in milestone '$MILESTONE_TITLE' - marking as exempt" | ||
| else | ||
| echo "PR #$PR_NUMBER is NOT in a code freeze/incident milestone - blocking" | ||
| fi | ||
| fi | ||
|
|
||
| # Set the commit status | ||
| gh api \ | ||
| --method POST \ | ||
| -H "Accept: application/vnd.github+json" \ | ||
| -H "X-GitHub-Api-Version: 2022-11-28" \ | ||
| "/repos/$REPO/statuses/$SHA" \ | ||
| -f state="$PR_STATE" \ | ||
| -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/workflows/$WORKFLOW_NAME" \ | ||
| -f description="$PR_DESC" \ | ||
| -f context="$CONTEXT" | ||
|
|
||
| echo "Set status '$PR_STATE' on PR #$PR_NUMBER (SHA: ${SHA:0:7})" | ||
| done | ||
|
|
||
| echo "Done updating PR statuses" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| name: Code Freeze Check | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, edited, milestoned, demilestoned] | ||
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: read | ||
| statuses: write | ||
| issues: read | ||
|
|
||
| jobs: | ||
| code_freeze_check: | ||
| runs-on: ubuntu-latest | ||
| env: | ||
| GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | ||
|
|
||
| steps: | ||
| - name: Check code freeze status | ||
| id: check | ||
| run: | | ||
| set -e | ||
|
|
||
| REPO="${{ github.repository }}" | ||
| PR_NUMBER="${{ github.event.pull_request.number }}" | ||
|
|
||
| echo "Checking code freeze status for PR #$PR_NUMBER..." | ||
|
|
||
| # Get all open milestones | ||
| MILESTONES=$(gh api \ | ||
| -H "Accept: application/vnd.github+json" \ | ||
| -H "X-GitHub-Api-Version: 2022-11-28" \ | ||
| "/repos/$REPO/milestones?state=open" \ | ||
| --jq '.[] | select(.title | startswith("Code Freeze") or startswith("Incident")) | .title') | ||
|
|
||
| # Check if any code freeze milestones are open | ||
| if [ -z "$MILESTONES" ]; then | ||
| echo "✅ No active code freeze - merging allowed" | ||
| echo "freeze_active=false" >> "$GITHUB_OUTPUT" | ||
| echo "pr_exempt=true" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| echo "🥶 Active code freeze milestones:" | ||
| echo "$MILESTONES" | ||
| echo "freeze_active=true" >> "$GITHUB_OUTPUT" | ||
|
|
||
| # Get the PR's milestone | ||
| PR_MILESTONE=$(gh pr view "$PR_NUMBER" --repo "$REPO" --json milestone --jq '.milestone.title // ""') | ||
|
|
||
| echo "PR milestone: '$PR_MILESTONE'" | ||
|
|
||
| # Check if PR is in an allowed milestone | ||
| if [[ "$PR_MILESTONE" == Code\ Freeze* ]] || [[ "$PR_MILESTONE" == Incident* ]]; then | ||
| echo "✅ PR #$PR_NUMBER is in milestone '$PR_MILESTONE' - exempt from code freeze" | ||
| echo "pr_exempt=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "❌ PR #$PR_NUMBER is NOT in a code freeze/incident milestone" | ||
| echo "pr_exempt=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| - name: Set commit status | ||
| run: | | ||
| SHA="${{ github.event.pull_request.head.sha }}" | ||
| REPO="${{ github.repository }}" | ||
| CONTEXT="code-freeze" | ||
|
|
||
| if [ "${{ steps.check.outputs.freeze_active }}" = "false" ]; then | ||
| STATE="success" | ||
| DESCRIPTION="No active code freeze - merging allowed" | ||
| elif [ "${{ steps.check.outputs.pr_exempt }}" = "true" ]; then | ||
| STATE="success" | ||
| DESCRIPTION="PR is in Code Freeze/Incident milestone - exempt from code freeze" | ||
| else | ||
| STATE="failure" | ||
| DESCRIPTION="Code freeze is active - add PR to Code Freeze/Incident milestone to merge" | ||
| fi | ||
|
|
||
| gh api \ | ||
| --method POST \ | ||
| -H "Accept: application/vnd.github+json" \ | ||
| -H "X-GitHub-Api-Version: 2022-11-28" \ | ||
| "/repos/$REPO/statuses/$SHA" \ | ||
| -f state="$STATE" \ | ||
| -f target_url="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \ | ||
| -f description="$DESCRIPTION" \ | ||
| -f context="$CONTEXT" | ||
|
|
||
| echo "Set status '$STATE' on commit ${SHA:0:7}" | ||
|
|
||
| # Fail the job if PR is blocked | ||
| if [ "$STATE" = "failure" ]; then | ||
| echo "::error::Code freeze is active. Add this PR to a 'Code Freeze' or 'Incident' milestone to allow merging." | ||
| exit 1 | ||
| fi | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| name: End code freeze | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| milestone: | ||
| types: [closed, deleted] | ||
|
|
||
| jobs: | ||
| end_code_freeze: | ||
| # Trigger on: | ||
| # - Manual dispatch (unfreezes all PRs) | ||
| # - Milestone events where title starts with "Code Freeze" or "Incident" and state is closed | ||
| if: | | ||
| github.event_name == 'workflow_dispatch' || | ||
| (github.event_name == 'milestone' && | ||
| (startsWith(github.event.milestone.title, 'Code Freeze') || | ||
| startsWith(github.event.milestone.title, 'Incident'))) | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read # Fetches PRs | ||
| statuses: write # add a commit status check | ||
| issues: read # Required to list milestones | ||
| env: | ||
| GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | ||
|
|
||
| steps: | ||
| - name: Log code freeze end | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | ||
| echo "🌡️ Code freeze ended manually" | ||
| else | ||
| echo "🌡️ Code freeze ended by closing milestone: ${{ github.event.milestone.title }}" | ||
| fi | ||
|
|
||
| - name: Checkout repository | ||
| uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | ||
|
|
||
| - name: Check for remaining freeze milestones | ||
| id: check_remaining | ||
| run: | | ||
| REPO="${{ github.repository }}" | ||
|
|
||
| # Check if any other Code Freeze or Incident milestones are still open | ||
| REMAINING=$(gh api \ | ||
| -H "Accept: application/vnd.github+json" \ | ||
| -H "X-GitHub-Api-Version: 2022-11-28" \ | ||
| "/repos/$REPO/milestones?state=open" \ | ||
| --jq '.[] | select(.title | startswith("Code Freeze") or startswith("Incident")) | .title') | ||
|
|
||
| if [ -z "$REMAINING" ]; then | ||
| echo "✅ No remaining freeze milestones - proceeding with unfreeze" | ||
| echo "should_unfreeze=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "⚠️ Other freeze milestones are still open:" | ||
| echo "$REMAINING" | ||
|
|
||
| # For manual dispatch, warn but allow unfreezing all PRs | ||
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | ||
| echo "🔓 Manual dispatch - unfreezing all PRs despite active freeze milestones" | ||
| echo "should_unfreeze=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "🔒 Skipping unfreeze because other freeze milestones remain open" | ||
| echo "should_unfreeze=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| fi | ||
|
|
||
| - name: Unfreeze PRs | ||
| if: steps.check_remaining.outputs.should_unfreeze == 'true' | ||
| uses: ./.github/actions/pr-status-updater | ||
| with: | ||
| github_token: ${{ secrets.GITHUB_TOKEN }} | ||
| state: success | ||
| workflow_name: code_freeze_end.yml |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| name: Start code freeze | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| milestone_title: | ||
| description: 'Milestone title (must start with "Code Freeze" or "Incident")' | ||
| required: true | ||
| type: string | ||
| milestone: | ||
| types: [opened, created, edited] | ||
|
|
||
| jobs: | ||
| start_code_freeze: | ||
| # Trigger on: | ||
| # - Manual dispatch with a valid milestone title | ||
| # - Milestone events where title starts with "Code Freeze" or "Incident" and state is open | ||
| if: | | ||
| (github.event_name == 'workflow_dispatch' && | ||
| (startsWith(github.event.inputs.milestone_title, 'Code Freeze') || | ||
| startsWith(github.event.inputs.milestone_title, 'Incident'))) || | ||
| (github.event_name == 'milestone' && | ||
| github.event.milestone.state == 'open' && | ||
| (startsWith(github.event.milestone.title, 'Code Freeze') || | ||
| startsWith(github.event.milestone.title, 'Incident'))) | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: read # Fetches PRs | ||
| statuses: write # add a commit status check | ||
| issues: read # Required to query milestone information | ||
| env: | ||
| GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" | ||
|
|
||
| steps: | ||
| - name: Log code freeze trigger | ||
| run: | | ||
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | ||
| echo "🥶 Code freeze triggered manually for milestone: ${{ github.event.inputs.milestone_title }}" | ||
| else | ||
| echo "🥶 Code freeze triggered by milestone: ${{ github.event.milestone.title }}" | ||
| fi | ||
|
|
||
| - name: Checkout repository | ||
| uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 | ||
|
|
||
| - name: Freeze PRs | ||
| uses: ./.github/actions/pr-status-updater | ||
| with: | ||
| github_token: ${{ secrets.GITHUB_TOKEN }} | ||
| state: failure | ||
| workflow_name: code_freeze_start.yml |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This workflow runs on
pull_requestevents but always posts a commit status viagh apito/statuses/$SHA. On forked PRs, GitHub Actions provides a read‑onlyGITHUB_TOKENthat cannot write commit statuses, so this POST returns 403 and the step fails. That means the workflow itself fails even when no code freeze is active, blocking merges for external contributors. Consider skipping the status update for forked PRs or using a safer event/permissions model.Useful? React with 👍 / 👎.