-
Notifications
You must be signed in to change notification settings - Fork 0
Migrate pipeline from CircleCI to GitHub Actions #231
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,178 @@ | ||
| name: Main | ||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| # Concurrency is JOB-level, on prerelease + release only (shared group), NOT | ||
| # workflow-level: a run waiting on the release approval counts as the group's | ||
| # in-progress occupant, so a workflow-level group would freeze ALL main CI | ||
| # (check/test/prerelease of later pushes) for up to the 30-day approval | ||
| # window. The approval itself lives on the slot-free release-gate job, so a | ||
| # pending approval holds no slot. queue: max keeps every queued publish (the | ||
| # default keeps only the newest pending) so close-together merges serialise | ||
| # FIFO on tag operations instead of cancelling each other. | ||
| # 'queue: max' — GA 2026-05-07: https://github.blog/changelog/2026-05-07-github-actions-concurrency-groups-now-allow-larger-queues/ | ||
| # Gate/slot split pattern: https://github.com/orgs/community/discussions/17401 | ||
|
|
||
| jobs: | ||
| skip-ci-check: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 5 | ||
| outputs: | ||
| should_skip_ci: ${{ steps.skip_ci_check.outputs.should_skip_ci }} | ||
| steps: | ||
| - id: skip_ci_check | ||
| env: | ||
| HEAD_COMMIT_MESSAGE: ${{ github.event.head_commit.message }} | ||
| run: | | ||
| if [[ "$HEAD_COMMIT_MESSAGE" == *"[no ci]"* ]] \ | ||
| || [[ "$HEAD_COMMIT_MESSAGE" == *"[skip ci]"* ]] \ | ||
| || [[ "$HEAD_COMMIT_MESSAGE" == *"[ci skip]"* ]]; then | ||
| echo "should_skip_ci=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "should_skip_ci=false" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| check: | ||
| needs: [skip-ci-check] | ||
| if: needs.skip-ci-check.outputs.should_skip_ci != 'true' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - name: Install tools | ||
| uses: infrablocks/github-actions/asdf_install@v1 | ||
| - name: Check | ||
| run: ./go test:code:check | ||
| - name: Notify Slack | ||
| if: ${{ !cancelled() }} | ||
| continue-on-error: true | ||
| run: ./go "slack:notify[${{ job.status }}]" | ||
| env: | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
|
|
||
| test: | ||
| needs: [skip-ci-check] | ||
| if: needs.skip-ci-check.outputs.should_skip_ci != 'true' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Install tools | ||
| uses: infrablocks/github-actions/asdf_install@v1 | ||
| - name: Ensure docker-compose is available | ||
| # Pinned + checksum-verified: this runs as root on runners that later | ||
| # hold secrets; the checksum is from the release's own .sha256 asset. | ||
| run: | | ||
| if ! command -v docker-compose >/dev/null 2>&1; then | ||
| sudo curl -fsSL \ | ||
| "https://github.com/docker/compose/releases/download/v5.3.1/docker-compose-linux-x86_64" \ | ||
| -o /usr/local/bin/docker-compose | ||
| echo "f9ebc6ebdb19d769b793c245a736caaeb198c62587f13b25c660c13b4987f959 /usr/local/bin/docker-compose" \ | ||
| | sha256sum -c - | ||
| sudo chmod +x /usr/local/bin/docker-compose | ||
| fi | ||
| - name: Test | ||
| run: ./go test:integration | ||
| - name: Notify Slack | ||
| if: ${{ !cancelled() }} | ||
| continue-on-error: true | ||
| run: ./go "slack:notify[${{ job.status }}]" | ||
| env: | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
|
|
||
| prerelease: | ||
| needs: [check, test] | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| concurrency: | ||
| group: main-tags | ||
| cancel-in-progress: false | ||
| queue: max | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
| - name: Install tools | ||
| uses: infrablocks/github-actions/asdf_install@v1 | ||
| - name: Install secrets tools | ||
| run: sudo apt-get update && sudo apt-get install -y git-crypt gnupg | ||
| - name: Unlock git-crypt | ||
| run: ./go git_crypt:unlock_with_encrypted_gpg_key | ||
| env: | ||
| ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} | ||
| - name: Set CI git author | ||
| run: ./go repository:set_ci_author | ||
| - name: Prerelease | ||
| run: ./go "version:bump[rc]" && ./go image:publish | ||
| - name: Notify Slack of release hold | ||
| continue-on-error: true | ||
| run: ./go "slack:notify[success,on_hold]" | ||
| env: | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
| - name: Notify Slack | ||
| if: ${{ !cancelled() }} | ||
| continue-on-error: true | ||
| run: ./go "slack:notify[${{ job.status }}]" | ||
| env: | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
|
|
||
| release-gate: | ||
| # Approval-only job: carries the environment but NO concurrency group, so | ||
| # a pending approval never occupies the main-tags slot. Multiple pushes | ||
| # can await approval independently. | ||
| needs: [prerelease] | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| environment: release | ||
| steps: | ||
| - name: Release approved | ||
| run: "true" | ||
|
|
||
| release: | ||
| needs: [release-gate] | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 30 | ||
| concurrency: | ||
| group: main-tags | ||
| cancel-in-progress: false | ||
| queue: max | ||
| permissions: | ||
| contents: write | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| ref: main | ||
| fetch-depth: 0 | ||
| - name: Pull latest main | ||
|
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. 🔵 Correctness + Safety — plan concern, not a defect of this diff Two independent lenses landed on this step, so recording it together. It is redundant. The step's comment says its purpose is that "approval can land long after the run starts; release publishes main as of approval time" — but the preceding It is ordered before the git author is set. Impact: a redundant step whose stated rationale doesn't hold, contributing latent failure risk to the one job that pushes tags and publishes images. If an identity ever did exist, a non-fast-forward pull would silently create a merge commit that This is the plan's exact YAML (§4.2) and the PR description documents the pull as parity with the old |
||
| # Approval can land long after the run starts; release publishes main | ||
| # as of approval time, not the tested SHA — parity with the old | ||
| # release.sh, which also pulled. prerelease.sh never pulled, so the | ||
| # prerelease job deliberately has no pull. | ||
| run: git pull | ||
| - name: Install tools | ||
| uses: infrablocks/github-actions/asdf_install@v1 | ||
| - name: Install secrets tools | ||
| run: sudo apt-get update && sudo apt-get install -y git-crypt gnupg | ||
| - name: Unlock git-crypt | ||
| run: ./go git_crypt:unlock_with_encrypted_gpg_key | ||
| env: | ||
| ENCRYPTION_PASSPHRASE: ${{ secrets.ENCRYPTION_PASSPHRASE }} | ||
| - name: Set CI git author | ||
| run: ./go repository:set_ci_author | ||
| - name: Release | ||
| run: ./go version:release && ./go image:publish | ||
| - name: Notify Slack | ||
| if: ${{ !cancelled() }} | ||
| continue-on-error: true | ||
| run: ./go "slack:notify[${{ job.status }}]" | ||
| env: | ||
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | ||
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.
🔵 Safety — plan concern, not a defect of this diff
The
main-tagsgroup serialises the publish jobs, and the header comment describes this as making close-together merges "serialise FIFO on tag operations". Worth noting the serialisation is narrower than that reads.version:bumpultimately doesrepo.push('origin', 'main', tags: true)— i.e.git push origin main --tags, which pushes the main branch as well as the tag.actions/checkouton a push event creates localmainat the tested SHA (git checkout --force -B main <github.sha>), and unlikerelease,prereleasedeliberately does not pull. So when two merges land close together, the older run's prerelease pushes a stalemainand git rejects it as non-fast-forward. Becausegit pushupdates refspecs independently (no--atomic), the tag lands anyway while the command exits non-zero — so&& ./go image:publishnever runs.Impact: an rc tag on origin with no image behind it, plus a red prerelease. Same orphan-tag hazard the PR already documents, but reached via a benign cause. Serialising the jobs cannot prevent it, because the staleness comes from the checkout SHA rather than from job overlap.
Nothing to change — the push logic is untouched per D5. Two suggestions for the plan: narrow the concurrency comment to say it serialises tag creation, and log the branch-push-from-a-stale-SHA behaviour as a fleet-wide item (the fix is for
version:bumpto push the tag refspec alone, notmain).