release #73
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright 2023 The Authors (see AUTHORS file) | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
name: 'release' | |
on: | |
workflow_run: | |
workflows: | |
- 'ci' | |
types: | |
- 'completed' | |
branches: | |
- 'main' | |
env: | |
SOURCE_DOCKER_REPO: 'us-docker.pkg.dev/github-metrics-aggreg-i-64d426/ci-images' | |
TARGET_DOCKER_REPO: 'us-docker.pkg.dev/abcxyz-artifacts/docker-images' | |
IMAGE_NAME: 'github-metrics-aggregator' | |
SOURCE_TAG: '${{ github.event.workflow_run.head_sha }}' | |
# Don't cancel in progress since we don't want to have half-baked releases. | |
concurrency: | |
group: '${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }}' | |
cancel-in-progress: false | |
jobs: | |
create-release: | |
# trigger only when ci workflow completes successfully and the head commit message starts with 'Release: v' | |
if: |- | |
${{ github.event.workflow_run.conclusion == 'success' && startsWith(github.event.workflow_run.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' | |
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: |- | |
{ | |
"scope": "release", | |
"repositories": ["${{ github.event.repository.name }}"], | |
"permissions": { | |
"contents": "write" | |
} | |
} | |
- uses: 'actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea' # ratchet:actions/github-script@v7 | |
id: 'create-release' | |
env: | |
EXPECTED_EMAIL: '${{ vars.TOKEN_MINTER_GITHUB_EMAIL }}' | |
with: | |
github-token: '${{ steps.mint-token.outputs.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.workflow_run.head_commit; | |
// Ensure the commit is signed. | |
const commitResult = await github.rest.repos.getCommit({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
ref: headCommit.id, | |
}) | |
// 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) | |
// 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; | |
} | |
// 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; | |
} | |
// Compute prerelease. | |
const prerelease = ['-', 'pre', 'alpha', 'beta', 'preview'].some((v) => version.includes(v)); | |
// 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}`); | |
publish-release: | |
runs-on: 'ubuntu-latest' | |
environment: 'production' | |
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: |- | |
{ | |
"scope": "release", | |
"repositories": ["${{ github.event.repository.name }}"], | |
"permissions": { | |
"contents": "write" | |
} | |
} | |
- name: 'Publish GitHub 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 | |
- id: 'auth' | |
name: 'Authenticate to Google Cloud' | |
uses: 'google-github-actions/auth@55bd3a7c6e2ae7cf1877fd1ccb9d54c0503c457c' # ratchet:google-github-actions/auth@v2 | |
with: | |
workload_identity_provider: '${{ vars.WIF_PROVIDER }}' | |
service_account: '${{ vars.WIF_SERVICE_ACCOUNT }}' | |
token_format: 'access_token' | |
- name: 'Authenticate to Artifact Registry' | |
uses: 'docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20' # ratchet:docker/login-action@v3 | |
with: | |
username: 'oauth2accesstoken' | |
password: '${{ steps.auth.outputs.access_token }}' | |
registry: 'us-docker.pkg.dev' | |
- name: 'Copy images to Release registry' | |
env: | |
SOURCE_SERVER_IMAGE: '${{ env.IMAGE_NAME }}:${{ env.SOURCE_TAG }}' | |
TARGET_SERVER_IMAGE: '${{ env.IMAGE_NAME }}:v${{ needs.create-release.outputs.version }}' | |
run: |- | |
# GMA Server Images | |
gcloud container images add-tag --quiet ${{ env.SOURCE_DOCKER_REPO }}/${{ env.SOURCE_SERVER_IMAGE }}-amd64 ${{ env.TARGET_DOCKER_REPO }}/${{ env.TARGET_SERVER_IMAGE }}-amd64 | |
gcloud container images add-tag --quiet ${{ env.SOURCE_DOCKER_REPO }}/${{ env.SOURCE_SERVER_IMAGE }}-arm64 ${{ env.TARGET_DOCKER_REPO }}/${{ env.TARGET_SERVER_IMAGE }}-arm64 | |
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: |- | |
{ | |
"scope": "release", | |
"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 |