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
98 changes: 94 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,39 +1,129 @@
name: release

on:
push:
tags: ['v*']
workflow_run:
workflows: [Tests]
types: [completed]
branches: [main]
workflow_dispatch:

permissions:
actions: read
contents: write

concurrency:
group: provider-release
cancel-in-progress: false

jobs:
goreleaser:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.event == 'push' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.head_branch == 'main')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
ref: ${{ github.event.workflow_run.head_sha || 'main' }}
- uses: actions/setup-go@v5
with:
go-version: '1.26.5'
cache: true
- name: Verify tag is the current main commit
- name: Resolve release version
id: release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
WORKFLOW_RUN_SHA: ${{ github.event.workflow_run.head_sha }}
run: |
git fetch --no-tags origin main
set -euo pipefail
version="$(tr -d '[:space:]' < VERSION)"
if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "VERSION must contain a semantic version such as 0.2.2" >&2
exit 1
fi
grep -Fqx "## ${version}" CHANGELOG.md

tag="v${version}"
git fetch --force --tags origin main
test "$(git rev-parse HEAD)" = "$(git rev-parse origin/main)"
if [ -n "${WORKFLOW_RUN_SHA}" ]; then
test "${WORKFLOW_RUN_SHA}" = "$(git rev-parse HEAD)"
else
ci_sha="$(gh run list \
--repo "${GITHUB_REPOSITORY}" \
--workflow test.yml \
--branch main \
--commit "$(git rev-parse HEAD)" \
--event push \
--status success \
--limit 1 \
--json headSha \
--jq '.[0].headSha // ""')"
test "${ci_sha}" = "$(git rev-parse HEAD)"
fi
if git rev-parse --verify --quiet "refs/tags/${tag}^{commit}" >/dev/null; then
tag_exists=true
else
tag_exists=false
fi

release_status="$(curl --silent --show-error --output /dev/null --write-out '%{http_code}' \
--header 'Accept: application/vnd.github+json' \
--header "Authorization: Bearer ${GITHUB_TOKEN}" \
--header 'X-GitHub-Api-Version: 2022-11-28' \
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases/tags/${tag}")"
case "${release_status}" in
200) release_exists=true ;;
404) release_exists=false ;;
*)
echo "GitHub release lookup failed with HTTP ${release_status}" >&2
exit 1
;;
esac
if [ "${release_exists}" = "true" ]; then
test "${tag_exists}" = "true"
elif [ "${tag_exists}" = "true" ]; then
test "$(git rev-parse "refs/tags/${tag}^{commit}")" = "$(git rev-parse HEAD)"
fi

printf 'tag=%s\n' "${tag}" >> "${GITHUB_OUTPUT}"
printf 'commit=%s\n' "$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
printf 'tag_exists=%s\n' "${tag_exists}" >> "${GITHUB_OUTPUT}"
printf 'release_exists=%s\n' "${release_exists}" >> "${GITHUB_OUTPUT}"
- name: Verify source and tests
if: steps.release.outputs.release_exists != 'true'
run: |
set -euo pipefail
go mod tidy
git diff --exit-code -- go.mod go.sum
go build ./...
go test ./...
- name: Import GPG key
if: steps.release.outputs.release_exists != 'true'
uses: crazy-max/ghaction-import-gpg@v6
id: import_gpg
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
- name: Create release tag
if: steps.release.outputs.release_exists != 'true' && steps.release.outputs.tag_exists != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_SHA: ${{ steps.release.outputs.commit }}
RELEASE_TAG: ${{ steps.release.outputs.tag }}
run: |
set -euo pipefail
git tag "${RELEASE_TAG}" "${RELEASE_SHA}"
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/git/refs" \
--raw-field "ref=refs/tags/${RELEASE_TAG}" \
--raw-field "sha=${RELEASE_SHA}" >/dev/null
- name: Run GoReleaser
if: steps.release.outputs.release_exists != 'true'
uses: goreleaser/goreleaser-action@v6
with:
version: latest
Expand Down
2 changes: 2 additions & 0 deletions .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ signs:
- "${artifact}"

release:
use_existing_draft: true
replace_existing_artifacts: true
extra_files:
- glob: 'terraform-registry-manifest.json'
name_template: '{{ .ProjectName }}_{{ .Version }}_manifest.json'
Expand Down
11 changes: 7 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ Keep implementation, documentation, examples, tests, commits, and releases gener
and a generic example.
3. Regenerate documentation after every schema or example change.
4. Update `docs/api-coverage.md` and `CHANGELOG.md` when coverage or behaviour changes.
5. Run the smallest relevant checks, then the complete release gate before tagging.
5. Run the smallest relevant checks, then the complete release gate before changing
`VERSION` for a release.

```sh
gofmt -w <changed-go-files>
Expand All @@ -56,8 +57,10 @@ run `make generate`. Never bypass a generated-doc diff.

## Releases

- Use semantic version tags from a tested commit on `main`.
- Update the changelog before tagging.
- Let the tag-triggered GitHub Actions workflow run GoReleaser and create signed assets.
- Keep the exact semantic version in `VERSION` and add the matching changelog heading.
- Merge the tested `VERSION` change to `main`; do not push release tags manually.
- After the exact `main` test workflow succeeds, the release workflow verifies the
commit and version, creates the matching tag, and runs GoReleaser. A manual dispatch
safely retries an interrupted current-`main` release without moving an existing tag.
- Verify the test workflow, release workflow, checksums, signature, manifest, and latest
published version before considering a release complete.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,9 @@ make fmt # gofmt
Read-only acceptance tests require `AIRLOCK_URL`, `AIRLOCK_API_KEY`, and `TF_ACC=1`. Mutation acceptance tests additionally require `AIRLOCK_ACC_MUTATION=1` and should only be run against an isolated Airlock environment with disposable `tf-acc-*` objects. Never commit live Airlock URLs, API keys, hostnames, user details, group names, or response fixtures.

See [AGENTS.md](./AGENTS.md) for architecture, safety, validation, and release conventions.
Releases use the exact semantic version in [`VERSION`](./VERSION). After the exact `main`
test workflow succeeds, gated GitHub Actions creates the matching tag and signed
GoReleaser assets when that version has not already been published.

## Contributing

Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.2.2
Loading