Skip to content
Closed
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
139 changes: 80 additions & 59 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ name: Release

on:
push:
tags: ["v*.*.*"]
branches: [main]
pull_request:
paths: [".github/workflows/release.yml"]
workflow_dispatch:
paths:
- .github/workflows/release.yml
- .release-please-manifest.json
- release-please-config.json

concurrency:
group: release-${{ github.ref }}
Expand All @@ -14,66 +16,77 @@ concurrency:
permissions: {}
cache-mode: none

env:
RELEASE_REF: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}
CHECKSUMS_FILE: decolint_${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.ref_name }}_checksums.txt

# The runner's default shell adds only -e, so an unset variable or a failure
# mid-pipeline would otherwise pass silently.
defaults:
run:
shell: bash -euo pipefail {0}

jobs:
# Opens the release PR, and creates a draft release once it is merged. The
# draft is published only after every asset is attached, since immutable
# releases reject later uploads. If a later job fails, re-run the failed
# jobs: a fresh run would not create the release again.
#
# The job runs on pull requests too, with its steps skipped and outside the
# release environment, so that the jobs after it are not skipped with it.
release-please:
permissions: {}
environment: ${{ github.event_name == 'push' && 'release' || '' }}
runs-on: ubuntu-slim
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
# A GitHub App token, because a release PR opened with GITHUB_TOKEN
# would not trigger CI.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
if: github.event_name == 'push'
id: app-token
with:
client-id: ${{ vars.RELEASE_PLEASE_CLIENT_ID }}
private-key: ${{ secrets.RELEASE_PLEASE_PRIVATE_KEY }}
permission-contents: write
permission-issues: write
permission-pull-requests: write
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
if: github.event_name == 'push'
id: release
with:
token: ${{ steps.app-token.outputs.token }}

prepare:
permissions:
contents: read
permissions: {}
needs: release-please
if: github.event_name == 'pull_request' || needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-slim
outputs:
skip_build: ${{ steps.decide.outputs.skip_build }}
skip_release: ${{ steps.decide.outputs.skip_release }}
release_ref: ${{ steps.decide.outputs.release_ref }}
tag_name: ${{ needs.release-please.outputs.tag_name }}
steps:
- name: Summary
run: |
{
echo "### Release Summary"
echo "- Version: \`$RELEASE_REF\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Decide release actions
- name: Decide release ref
id: decide
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
IS_PULL_REQUEST: ${{ github.event_name == 'pull_request' }}
TAG_NAME: ${{ needs.release-please.outputs.tag_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
is_semver=false
if [[ "$RELEASE_REF" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$ ]]; then
is_semver=true
fi

release_exists=false
if [ "$IS_PULL_REQUEST" != "true" ] && [ "$is_semver" = "true" ] && gh release view "$RELEASE_REF" >/dev/null 2>&1; then
release_exists=true
echo "- Release \`$RELEASE_REF\` already exists. Skipping build." >> "$GITHUB_STEP_SUMMARY"
fi

skip_release=false
if [ "$IS_PULL_REQUEST" = "true" ] || [ "$is_semver" != "true" ]; then
skip_release=true
echo "- Version \`$RELEASE_REF\` is not a release candidate. Skipping sign/attest/release." >> "$GITHUB_STEP_SUMMARY"
elif [ "$release_exists" = "true" ]; then
skip_release=true
echo "### Release Summary" >> "$GITHUB_STEP_SUMMARY"
if [ -n "$TAG_NAME" ]; then
release_ref=$TAG_NAME
else
release_ref=pr-$PR_NUMBER
echo "- Pull request build. Skipping sign/attest/release." >> "$GITHUB_STEP_SUMMARY"
fi

echo "skip_build=$release_exists" >> "$GITHUB_OUTPUT"
echo "skip_release=$skip_release" >> "$GITHUB_OUTPUT"
echo "- Version: \`$release_ref\`" >> "$GITHUB_STEP_SUMMARY"
echo "release_ref=$release_ref" >> "$GITHUB_OUTPUT"

build:
permissions:
contents: read
cache-mode: read
needs: prepare
if: needs.prepare.outputs.skip_build != 'true'
env:
RELEASE_REF: ${{ needs.prepare.outputs.release_ref }}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -131,7 +144,9 @@ jobs:
checksums:
permissions:
contents: read
needs: build
needs: [build, prepare]
env:
CHECKSUMS_FILE: decolint_${{ needs.prepare.outputs.release_ref }}_checksums.txt
runs-on: ubuntu-slim
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand All @@ -155,7 +170,9 @@ jobs:
id-token: write
contents: read
needs: [checksums, prepare]
if: needs.prepare.outputs.skip_release != 'true'
if: needs.prepare.outputs.tag_name != ''
env:
CHECKSUMS_FILE: decolint_${{ needs.prepare.outputs.release_ref }}_checksums.txt
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand All @@ -180,7 +197,9 @@ jobs:
attestations: write
contents: read
needs: [checksums, prepare]
if: needs.prepare.outputs.skip_release != 'true'
if: needs.prepare.outputs.tag_name != ''
env:
CHECKSUMS_FILE: decolint_${{ needs.prepare.outputs.release_ref }}_checksums.txt
runs-on: ubuntu-slim
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand All @@ -198,7 +217,8 @@ jobs:
attestations: write
cache-mode: read
needs: prepare
if: needs.prepare.outputs.skip_build != 'true'
env:
RELEASE_REF: ${{ needs.prepare.outputs.release_ref }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand All @@ -218,11 +238,11 @@ jobs:
images: ghcr.io/bare-devcontainer/decolint
flavor: latest=auto
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern={{version}},value=${{ needs.prepare.outputs.tag_name }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.prepare.outputs.tag_name }}
type=semver,pattern={{major}},value=${{ needs.prepare.outputs.tag_name }}
- name: Log in to GHCR
if: needs.prepare.outputs.skip_release != 'true'
if: needs.prepare.outputs.tag_name != ''
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
Expand All @@ -234,7 +254,7 @@ jobs:
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ needs.prepare.outputs.skip_release != 'true' }}
push: ${{ needs.prepare.outputs.tag_name != '' }}
provenance: mode=max
sbom: true
tags: ${{ steps.meta.outputs.tags }}
Expand All @@ -244,7 +264,7 @@ jobs:
VERSION=${{ env.RELEASE_REF }}
REVISION=${{ steps.revision.outputs.short_sha }}
- name: Attest image provenance
if: needs.prepare.outputs.skip_release != 'true'
if: needs.prepare.outputs.tag_name != ''
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ghcr.io/bare-devcontainer/decolint
Expand All @@ -256,7 +276,9 @@ jobs:
actions: write
contents: write
needs: [checksums, sign, attest, docker, prepare]
if: needs.prepare.outputs.skip_release != 'true'
if: needs.prepare.outputs.tag_name != ''
env:
CHECKSUMS_FILE: decolint_${{ needs.prepare.outputs.release_ref }}_checksums.txt
runs-on: ubuntu-slim
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
Expand All @@ -270,14 +292,13 @@ jobs:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: signature-${{ github.run_id }}
- name: Create GitHub Release
- name: Publish GitHub Release
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
TAG_NAME: ${{ github.ref_name }}
TAG_NAME: ${{ needs.prepare.outputs.tag_name }}
run: |
gh release create "$TAG_NAME" \
dist/* "$CHECKSUMS_FILE" "$CHECKSUMS_FILE.sigstore.json" \
--title "$TAG_NAME" \
--generate-notes
gh release upload "$TAG_NAME" --clobber \
dist/* "$CHECKSUMS_FILE" "$CHECKSUMS_FILE.sigstore.json"
gh release edit "$TAG_NAME" --draft=false
gh workflow run attest-check.yml --ref main
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.8.0"
}
19 changes: 19 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,3 +241,22 @@ PR titles must follow the [Conventional
Commits](https://www.conventionalcommits.org/) format with one of the
types `feat`, `fix`, `ci`, `chore`, `test`, or `docs`, e.g.
`feat(cli): add new feature`. Keep external dependencies to a minimum.
The title becomes the commit message on `main`, and its type decides
the next release: `feat` bumps the minor version, `fix` the patch
version, and the other types do not cut a release. Use `fix` for a
dependency update that ships in the binary or image.

## Releasing

Releases are cut by
[release-please](https://github.com/googleapis/release-please). It keeps
a release PR open that bumps the version and updates `CHANGELOG.md`;
merging it builds, signs and publishes the release. To release a
version other than the computed one, add a `Release-As: x.y.z` footer
to a commit on `main`.

release-please runs as a GitHub App, so that CI runs on the release
PR. The App needs read and write access to contents, issues and pull
requests, with its client ID in the `RELEASE_PLEASE_CLIENT_ID` variable
and a private key in the `RELEASE_PLEASE_PRIVATE_KEY` secret of the
`release` environment.
10 changes: 10 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/v17.6.0/schemas/config.json",
"release-type": "go",
"include-component-in-tag": false,
"draft": true,
"force-tag-creation": true,
"packages": {
".": {}
}
}
18 changes: 17 additions & 1 deletion renovate.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
"extends": [
"config:best-practices",
":semanticCommits",
":semanticCommitTypeAll(chore)"
// fix(deps) for what ships in the binary and image, so release-please cuts a release for it.
":semanticPrefixFixDepsChoreOthers"
],
"minimumReleaseAge": "14 days",
"labels": [
Expand Down Expand Up @@ -60,6 +61,21 @@
}
],
"packageRules": [
{
"description": "dockerflagsgen is a code generator; its dependencies do not ship.",
"matchFileNames": [
"cmd/dockerflagsgen/go.mod"
],
"semanticCommitType": "chore"
},
{
"description": "The Go toolchain builds the binary and image, so its updates ship.",
"matchPackageNames": [
"go",
"golang"
],
"semanticCommitType": "fix"
},
{
"description": "Limit Claude Code updates to one pull request a week, since upstream tags releases several times a day.",
"matchPackageNames": [
Expand Down
Loading