Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
pull_request:
permissions:
id-token: write
contents: read
contents: write
Comment thread
julien-carsique-sonarsource marked this conversation as resolved.
jobs:
pre-commit:
runs-on: warp-custom-ubuntu-24-04
Expand Down
32 changes: 15 additions & 17 deletions .github/workflows/test-build-number.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: warp-custom-ubuntu-24-04
permissions:
id-token: write
contents: read
contents: write
Comment thread
julien-carsique-sonarsource marked this conversation as resolved.
outputs:
BUILD_NUMBER: ${{ steps.get_build_number.outputs.BUILD_NUMBER }}
steps:
Expand Down Expand Up @@ -46,12 +46,12 @@ jobs:
exit 1
fi

test-build-number-reuse-from-cache:
test-build-number-reuse-same-run:
needs: test-build-number-generation
runs-on: warp-custom-ubuntu-24-04
permissions:
id-token: write
contents: read
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand All @@ -61,18 +61,17 @@ jobs:
run: |
echo "BUILD_NUMBER: ${BUILD_NUMBER}"
if [[ "${BUILD_NUMBER}" != "${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}" ]]; then
echo -e "::error title=test-build-number-reuse::Build number '${BUILD_NUMBER}' does not match the previous job build number" \
"'${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}' despite it is the same workflow run.\n" \
"Prefer using the output from SonarSource/ci-github-actions/get-build-number instead of calling it from distinct jobs."
# exit 1 # flaky test
echo "::error title=test-build-number-reuse::Build number '${BUILD_NUMBER}' does not match the previous job build number" \
"'${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}' despite it is the same workflow run."
exit 1
fi

test-build-number-reuse-from-cache-windows:
test-build-number-reuse-same-run-windows:
needs: test-build-number-generation
runs-on: github-windows-latest-s
permissions:
id-token: write
contents: read
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand All @@ -83,18 +82,17 @@ jobs:
run: |
echo "BUILD_NUMBER: ${BUILD_NUMBER}"
if [[ "${BUILD_NUMBER}" != "${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}" ]]; then
echo -e "::error title=test-build-number-reuse-from-cache-windows::Build number '${BUILD_NUMBER}' does not match the previous" \
"job build number '${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}' despite it is the same workflow run.\n" \
"Prefer using the output from SonarSource/ci-github-actions/get-build-number instead of calling it from distinct jobs."
# exit 1 # flaky test
echo "::error title=test-build-number-reuse-same-run-windows::Build number '${BUILD_NUMBER}' does not match the previous" \
"job build number '${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}' despite it is the same workflow run."
exit 1
fi

test-build-number-reuse-from-env:
needs: test-build-number-generation
runs-on: warp-custom-ubuntu-24-04
permissions:
id-token: write
contents: read
contents: write
env:
BUILD_NUMBER: ${{ needs.test-build-number-generation.outputs.BUILD_NUMBER }}
steps:
Expand All @@ -119,13 +117,13 @@ jobs:
if: always()
needs:
- test-build-number-generation
- test-build-number-reuse-from-cache
- test-build-number-reuse-from-cache-windows
- test-build-number-reuse-same-run
- test-build-number-reuse-same-run-windows
- test-build-number-reuse-from-env
runs-on: warp-custom-ubuntu-24-04
permissions:
id-token: write
contents: read
contents: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: ./config-npm
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-shell-scripts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: warp-custom-ubuntu-24-04
permissions:
id-token: write
contents: read
contents: write
Comment thread
julien-carsique-sonarsource marked this conversation as resolved.
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-update-release-channel.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: sonar-xs
permissions:
id-token: write
contents: read
contents: write
Comment thread
julien-carsique-sonarsource marked this conversation as resolved.
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Update release channel (dry-run, happy path)
Expand Down
82 changes: 61 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,31 +70,33 @@ These badges show the status of workflows in dummy repositories that use (or sho

## `get-build-number`

Manage the build number in GitHub Actions.
Get a unique, strictly increasing build number for a repository, reusing one already claimed by the current workflow run when applicable.
It sets `BUILD_NUMBER` as both an environment variable and a GitHub Actions output. Safe to call from multiple jobs in the same workflow
run - only one of them claims a number, the others wait for it and reuse it - and from concurrent workflow runs (e.g. several GitHub
Stacked PRs opened at once), where no two runs will ever get the same number. A job waiting for another job's claim fails after a bounded
timeout (a few minutes) if that claim never completes, rather than claiming an independent number - see [Git References](#git-references)
below for how this is coordinated.

The build number is stored in the GitHub repository property named `build_number`. This action will reuse or increment the build number,
and set it as an environment variable named `BUILD_NUMBER`, and as a GitHub Actions output variable also named `BUILD_NUMBER`.

The build number is unique per workflow run ID. It is not incremented on workflow reruns.

During execution the action temporarily writes `.build_number.txt` at the repository root (for
`actions/cache`); the file is removed before the action completes. Do not track a file named
`.build_number.txt` in your repository.

The action authenticates `gh` with a Vault-issued GitHub token. It sets both `GITHUB_TOKEN` and
`GH_TOKEN` for that step so a workflow-exported `GH_TOKEN` cannot shadow the Vault credential
(`gh` prefers `GH_TOKEN` over `GITHUB_TOKEN`).
During execution the action temporarily writes `.build_number.txt` at the repository root; the file is removed before the action
completes. Do not track a file named `.build_number.txt` in your repository.

### Requirements

#### Required GitHub Permissions

- `id-token: write`
- `contents: read`
- `contents: write`
Comment thread
julien-carsique-sonarsource marked this conversation as resolved.

> **Breaking change:** this action used to require only `contents: read`. Claiming now needs `contents: write` to create the
> [Git references](#git-references) below - `contents: read` alone will fail with a 403 on every claim except the narrow case where this
> exact workflow run already has a marker to reuse (e.g. certain reruns), since that path is read-only. There is no working
> read-only/rerun-only mode: any run that needs a genuinely new number will fail until the caller's `permissions:` block is updated.
Comment thread
julien-carsique-sonarsource marked this conversation as resolved.

#### Required Vault Permissions

- `build-number`: GitHub preset to read and write the build number property. This is built-in to the Vault `auth.github` permission.
- `build-number`: GitHub preset used to read the legacy `build_number` repository property, needed only for repositories that predate this
action's current design. Built-in to the Vault `auth.github` permission. This dependency will be dropped once no repository needs it
anymore.

### Usage

Expand All @@ -104,7 +106,7 @@ jobs:
runs-on: sonar-xs
permissions:
id-token: write
contents: read
contents: write
steps:
- uses: SonarSource/ci-github-actions/get-build-number@v1
```
Expand All @@ -131,6 +133,44 @@ No inputs are required for this action.
|----------------------|--------------------------|
| `BUILD_NUMBER` | The current build number |

### Git References

This action coordinates purely through Git references on the repository - no external state, cache, or database. Each reference is created
pointing at `$GITHUB_SHA` (the commit that triggered the claim); that target is never read back - only the reference's existence matters
(for `build-number` and `build-number-lock`), or its name, which encodes the claimed number (for `build-runs`).

| Reference | Lifetime | Purpose |
| ------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `refs/build-number/<number>` | Deleted once superseded by the next claim | The atomic claim itself and the sole source of truth for uniqueness. Deleting the superseded ref is only safe because it happens while holding the exclusive `build-number-lock` below - without that, a claim stalled in flight for an older number could resurrect it after deletion, republishing a number already used elsewhere (a real bug found in review before this lock existed). At most one ref (rarely a couple, if a past deletion failed and self-heals on the next claim) exists at a time. |
| `refs/build-runs/<run_id>/<number>` | Not automatically deleted - see [Inspecting current refs](#inspecting-current-refs) below | Marker recording which number a workflow run claimed. Checked first, and lock-free, so a rerun or another job in the same run reuses it without ever touching `build-number-lock`. A completed run can still be re-run much later, so "the run finished" does not make its marker safe to delete - only the run's own record being gone from GitHub (confirmed via a 404, never inferred from age) does, since that's what makes re-running it impossible. |
| `refs/build-number-lock/global` | Seconds: created, used, and deleted again within a single claim, not a persistent artifact | Exclusive, repository-wide lock serializing every new claim (not just those within one run) - what makes deleting a superseded `build-number` ref above safe. Assumes claims are infrequent and each is fast enough that the resulting wait queue stays short; every other claimant waits for either this lock or the marker above instead of racing independently. |

### Known limitations

- `refs/build-runs/<run_id>/<number>` markers are never automatically deleted, so they accumulate for the lifetime of the
repository - see [Inspecting current refs](#inspecting-current-refs) below to check what currently exists. Not expected to
matter in practice for a long time; no automated cleanup is implemented today.

### Inspecting current refs

These refs aren't pulled by a normal `git fetch`/`git clone` (they live outside the default `refs/heads/*`/`refs/tags/*`
namespaces), but can be listed directly with `git ls-remote` or the GitHub API.

Current build number - only one `refs/build-number/*` ref exists at a time, since each new claim deletes the one it supersedes:

```shell
$ git ls-remote origin 'refs/build-number/*'
7345fe785a3fc8705e3d8204a28ca4f909628e7e refs/build-number/12106
```

History - mapping a workflow run to the build number it claimed or reused, one `refs/build-runs/<run_id>/<number>` ref per run:

```shell
$ git ls-remote origin 'refs/build-runs/*'
3cfd2386fdd965247c0e465ff4774239036894ec refs/build-runs/32867023550/12093
002e0fb9a88da75c89316635b1f0a62a6ad9aff4 refs/build-runs/32868621162/12095
```

---

## `config-maven`
Expand Down Expand Up @@ -177,7 +217,7 @@ By default, Maven caches `~/.m2/repository`. You can customize this behavior:
#### Required GitHub Permissions

- `id-token: write`
- `contents: read`
- `contents: write`

#### Required Vault Permissions

Expand Down Expand Up @@ -1182,7 +1222,7 @@ This action configures pip to pull packages from the internal JFrog Artifactory
#### Required GitHub Permissions

- `id-token: write`
- `contents: read`
- `contents: write`

#### Required Vault Permissions

Expand All @@ -1193,7 +1233,7 @@ This action configures pip to pull packages from the internal JFrog Artifactory
```yaml
permissions:
id-token: write
contents: read
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: SonarSource/ci-github-actions/config-pip@v1
Expand Down Expand Up @@ -1292,7 +1332,7 @@ default = true
#### Required GitHub Permissions

- `id-token: write`
- `contents: read`
- `contents: write`

#### Required Vault Permissions

Expand All @@ -1307,7 +1347,7 @@ The `uv` tool must be pre-installed. Use of `mise` is recommended.
```yaml
permissions:
id-token: write
contents: read
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4.0.1
Expand Down
47 changes: 17 additions & 30 deletions get-build-number/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Get build number
description: GitHub Action to get the build number for a repository
outputs:
BUILD_NUMBER:
description: The build number, incremented or reused if already cached
description: The build number, newly claimed or reused if this workflow run already claimed one
value: ${{ steps.export.outputs.BUILD_NUMBER }}
inputs:
host-actions-root:
Expand Down Expand Up @@ -38,36 +38,31 @@ runs:
if: env.BUILD_NUMBER != ''
shell: bash
run: |
echo "BUILD_NUMBER ${BUILD_NUMBER} provided from environment, skipping both increment and save to cache."
echo "BUILD_NUMBER ${BUILD_NUMBER} provided from environment, skipping increment."
echo "${BUILD_NUMBER}" > "$BUILD_NUMBER_FILE"
echo "skip=true" >> $GITHUB_OUTPUT

# Reuse current build number in case of rerun
- name: Get cached build number
if: steps.from-env.outputs.skip != 'true'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
id: current-build-number
with:
path: ${{ env.BUILD_NUMBER_FILE }}
key: build-number-${{ github.run_id }}
enableCrossOsArchive: true

# Otherwise, increment the build number
# Vault is only needed to read the legacy build_number property during migration to refs/build-number/* (see README); fetched
# unconditionally for simplicity, since this whole dependency is temporary and slated for removal once migration is complete.
# continue-on-error: a repository with no build_number history (or no {REPO_OWNER_NAME_DASH}-build-number preset configured
# yet) has nothing to migrate - a failure here must not block claiming, only fall through to get_build_number.sh's own
# no-migration-token warning path (an empty LEGACY_PROPERTY_TOKEN below, same as if this step is skipped entirely).
- uses: SonarSource/vault-action-wrapper@881045d830534a70ec3c7c275fa3714412c8ff6e # 3.6.1
id: secrets
if: steps.from-env.outputs.skip != 'true' && steps.current-build-number.outputs.cache-hit != 'true'
if: steps.from-env.outputs.skip != 'true'
continue-on-error: true
with:
secrets: development/github/token/{REPO_OWNER_NAME_DASH}-build-number token | github_token;
- name: Get new build number
if: steps.from-env.outputs.skip != 'true' && steps.current-build-number.outputs.cache-hit != 'true'

# Reuses this run's own claim if one already exists (a rerun, or another job in the same run); otherwise claims a new one under
# an exclusive, repository-wide lock, released as soon as it's no longer needed - see get_build_number.sh.
- name: Get build number
if: steps.from-env.outputs.skip != 'true'
shell: bash
env:
# gh prefers GH_TOKEN over GITHUB_TOKEN. Set both so a workflow-exported
# GH_TOKEN (e.g. a bot token) cannot shadow the Vault build-number token.
GITHUB_TOKEN: ${{ steps.current-build-number.outputs.cache-hit != 'true' &&
steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).github_token || '' }}
GH_TOKEN: ${{ steps.current-build-number.outputs.cache-hit != 'true' &&
steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).github_token || '' }}
GITHUB_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ github.token }}
LEGACY_PROPERTY_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).github_token || '' }}
run: ${ACTION_PATH_GET_BUILD_NUMBER}/get_build_number.sh

- name: Export build number
Expand All @@ -83,14 +78,6 @@ runs:
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> "$GITHUB_ENV"
echo "BUILD_NUMBER=${BUILD_NUMBER}" >> "$GITHUB_OUTPUT"

- name: Save build number to cache
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
if: steps.from-env.outputs.skip != 'true' && steps.current-build-number.outputs.cache-hit != 'true'
with:
path: ${{ env.BUILD_NUMBER_FILE }}
key: build-number-${{ github.run_id }}
enableCrossOsArchive: true

- name: Remove build number file from workspace
if: always()
shell: bash
Expand Down
Loading
Loading