Skip to content

Commit

Permalink
Update to new release process
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo committed Feb 27, 2024
1 parent e234d04 commit faaa2b7
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 72 deletions.
108 changes: 57 additions & 51 deletions .github/actions/create-release/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,84 +72,90 @@ inputs:
type: 'string'
required: true

draft:
description: |-
Create the release as a draft.
type: 'boolean'
required: false
default: false

outputs:
release_version:
version:
description: |-
Version that is being released, without a leading "v".
value: '${{ steps.create-release.outputs.release_version }}'
value: '${{ steps.create-release.outputs.version }}'
tag:
description: |-
Tag name (version with a leading "v").
value: '${{ steps.create-release.outputs.tag }}'
created:
description: |-
Whether the release actually created (false if there was a transient error).
value: '${{ steps.create-release.outputs.created }}'


runs:
using: 'composite'
steps:
- name: 'Create release'
id: 'create-release'
env:
DRAFT: '${{ inputs.draft }}'
EXPECTED_EMAIL: '${{ inputs.expected_email }}'
uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' # ratchet:actions/github-script@v7
with:
github-token: '${{ inputs.github_token }}'
script: |-
// Get the head commit from the API instead of the event, because
// signature status is not available in the webhook.
const headCommit = context.payload.head_commit;
// Ensure the commit is signed.
const commitResult = await github.rest.repos.getCommit({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.head_commit.id,
ref: headCommit.id,
})
if (!commitResult.data.commit.verification.verified) {
core.setFailed(`Commit is not signed`)
// Ensure the commit is a release commit.
const commitMessage = commitResult.data.commit.message;
const matches = commitMessage.match(/Release: v(?<version>[^\s]+)/i);
if (!matches || !matches.groups) {
core.setFailed(`❌ Commit "${commitMessage}" does not match version syntax`);
return;
}
let version = matches.groups.version;
while(version.charAt(0).toLowerCase() === 'v') {
version = version.substr(1);
}
core.info(`👾 Computed version as: "${version}"`)
core.setOutput('version', version)
const expectedEmail = process.env.EXPECTED_EMAIL;
if (commitResult.data.commit.author.email !== expectedEmail) {
core.setFailed(`Commit author is not ${expectedEmail}, got ${commitResult.data.commit.author.email}`);
// Set the tag (which has the leading "v") prefix.
const tag = `v${version}`;
core.info(`👾 Computed tag as: "${tag}"`)
core.setOutput('tag', tag)
// Verify the commit is signed.
if (!commitResult.data.commit.verification.verified) {
core.setFailed(`❌ Commit is not signed`)
return;
}
// Ensure the commit message matches the expected regular
// expression. Part of this is guarded by the conditional
// entrypoint.
const matches = context.payload.head_commit.message.match(/Release: v(?<version>[^\ ]+)/i);
if (!matches || !matches.groups) {
core.setFailed(`Commit message does not contain a version`)
// Verify the email matches the expected committer.
const expectedEmail = process.env.EXPECTED_EMAIL;
const gotEmail = commitResult.data.commit.author.email;
if (gotEmail !== expectedEmail) {
core.setFailed(`❌ Commit author is "${gotEmail}", expected "${expectedEmail}"`);
return;
}
let releaseVersion = matches.groups.version;
while(releaseVersion.charAt(0).toLowerCase() === 'v') {
releaseVersion = releaseVersion.substr(1);
}
// Compute variables.
const tag = `v${releaseVersion}`;
const draft = JSON.parse(process.env.DRAFT);
const prerelease = ['-', 'pre', 'alpha', 'beta', 'preview'].some((v) => tag.includes(v));
// Compute prerelase.
const prerelease = ['-', 'pre', 'alpha', 'beta', 'preview'].some((v) => version.includes(v));
try {
const createReleaseRequest = {
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
target_commitish: context.sha,
name: tag,
generate_release_notes: true,
prerelease: prerelease,
draft: draft,
};
const response = await github.rest.repos.createRelease(createReleaseRequest);
core.setOutput('release_version', releaseVersion);
core.info(
`Created release ${response.data.name} at ${response.data.html_url}`
);
} catch (err) {
core.setFailed(`Failed to create release: ${err}`);
}
// Create the release.
const response = await github.rest.repos.createRelease({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: tag,
target_commitish: headCommit.id,
name: tag,
generate_release_notes: true,
prerelease: prerelease,
draft: true,
make_latest: 'legacy',
});
core.setOutput('created', true);
core.info(`✅ Created release "${response.data.name}" at ${response.data.html_url}`);
4 changes: 1 addition & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@
name: 'ci'

on:
push:
branches:
- 'main'
pull_request:
branches:
- 'main'
- 'release/**/*'

concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
Expand Down
30 changes: 20 additions & 10 deletions .github/workflows/draft-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,16 @@ on:
- 'patch'
- 'prerelease'
required: true
draft:
description: |-
Draft
type: 'boolean'
default: false
required: true

env:
PR_BRANCH: 'automation/draft-release-${{ github.ref_name }}'

jobs:
draft-release:
runs-on: 'ubuntu-latest'
permissions:
contents: 'read'
id-token: 'write'
env:
PR_BRANCH: 'automation/draft-release-${{ github.ref_name }}'
steps:
- name: 'Increment version'
id: 'increment-version'
Expand All @@ -67,14 +62,29 @@ jobs:
}
}
- uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' # ratchet:actions/github-script@v7
id: 'generate-release-notes'
env:
CURRENT_VERSION: '${{ steps.increment-version.outputs.current_version }}'
NEXT_VERSION: '${{ steps.increment-version.outputs.next_version }}'
with:
github-token: '${{ steps.mint-token.outputs.token }}'
script: |-
const releaseNotesResponse = await github.rest.repos.generateReleaseNotes({
owner: context.repo.owner,
repo: context.repo.repo,
tag_name: `v${process.env.NEXT_VERSION}`,
previous_tag_name: `v${process.env.CURRENT_VERSION}`,
});
core.setOutput('release-notes', releaseNotesResponse.data.body)
- name: 'Update Pull Request'
uses: 'abcxyz/pkg/.github/actions/create-pull-request@main' # ratchet:exclude
with:
token: '${{ steps.mint-token.outputs.token }}'
draft: '${{ inputs.draft }}'
base_branch: '${{ github.event.repository.default_branch }}'
head_branch: '${{ env.PR_BRANCH }}'
title: 'Release: v${{ steps.increment-version.outputs.next_version }}'
# TODO: switch to Mike's new solution
body: '${{ steps.generate-release-notes.outputs.release-notes }}'
changed_paths: |-
["VERSION"]
89 changes: 81 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,27 @@
name: 'release'

on:
workflow_run:
workflows:
- 'ci'
types:
- 'completed'
push:
branches:
- 'main'
- 'release/**/*'

concurrency:
group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}'
group: '${{ github.workflow }}-${{ github.event_name}}-${{ github.head_ref || github.ref }}'
cancel-in-progress: false

jobs:
release:
create-release:
if: |-
startsWith(github.event.workflow_run.head_commit.message, 'Release: v')
${{ startsWith(github.event.head_commit.message, 'Release: v') }}
runs-on: 'ubuntu-latest'
permissions:
contents: 'read'
id-token: 'write'
outputs:
created: '${{ steps.create-release.outputs.created || false }}'
tag: '${{ steps.create-release.outputs.tag }}'
version: '${{ steps.create-release.outputs.version }}'
steps:
- name: 'Mint token'
id: 'mint-token'
Expand All @@ -58,3 +59,75 @@ jobs:
with:
github_token: '${{ steps.mint-token.outputs.token }}'
expected_email: '${{ vars.TOKEN_MINTER_GITHUB_EMAIL }}'

publish-release:
runs-on: 'ubuntu-latest'
permissions:
contents: 'read'
id-token: 'write'
needs:
- 'create-release'
steps:
- name: 'Mint token'
id: 'mint-token'
uses: 'abcxyz/github-token-minter/.github/actions/mint-token@main' # ratchet:exclude
with:
wif_provider: '${{ vars.TOKEN_MINTER_WIF_PROVIDER }}'
wif_service_account: '${{ vars.TOKEN_MINTER_WIF_SERVICE_ACCOUNT }}'
service_audience: '${{ vars.TOKEN_MINTER_SERVICE_AUDIENCE }}'
service_url: '${{ vars.TOKEN_MINTER_SERVICE_URL }}'
requested_permissions: |-
{
"repositories": ["${{ github.event.repository.name }}"],
"permissions": {
"contents": "write"
}
}
- name: 'Publish release'
env:
GH_TOKEN: '${{ steps.mint-token.outputs.token }}'
RELEASE_VERSION: 'v${{ needs.create-release.outputs.version }}'
REPO: '${{ github.repository }}'
run: |-
gh release edit "${RELEASE_VERSION}" \
--repo "${REPO}" \
--draft=false
cleanup-failed-release:
if: |-
${{ always() && needs.create-release.outputs.created == 'true' && contains(fromJSON('["failure", "cancelled", "skipped"]'), needs.publish-release.result) }}
runs-on: 'ubuntu-latest'
needs:
- 'create-release'
- 'publish-release'
permissions:
contents: 'read'
id-token: 'write'
steps:
- name: 'Mint token'
id: 'mint-token'
uses: 'abcxyz/github-token-minter/.github/actions/mint-token@main' # ratchet:exclude
with:
wif_provider: '${{ vars.TOKEN_MINTER_WIF_PROVIDER }}'
wif_service_account: '${{ vars.TOKEN_MINTER_WIF_SERVICE_ACCOUNT }}'
service_audience: '${{ vars.TOKEN_MINTER_SERVICE_AUDIENCE }}'
service_url: '${{ vars.TOKEN_MINTER_SERVICE_URL }}'
requested_permissions: |-
{
"repositories": ["${{ github.event.repository.name }}"],
"permissions": {
"contents": "write"
}
}
- name: 'Cleanup failed release'
env:
GH_TOKEN: '${{ steps.mint-token.outputs.token }}'
RELEASE_VERSION: 'v${{ needs.create-release.outputs.version }}'
REPO: '${{ github.repository }}'
run: |-
gh release delete "${RELEASE_VERSION}" \
--repo "${REPO}" \
--cleanup-tag \
--yes || true

0 comments on commit faaa2b7

Please sign in to comment.