Skip to content

Commit

Permalink
Change CI release logic
Browse files Browse the repository at this point in the history
* add a workflow `create.yml` to create release branches and tags in a
  controlled manner, using a GitHub app that will have exceptions in the
  rulesets
* change the `release.yml` workflow to be directly called by the main CI
  workflow
* change `release.yml` to not validate the ref anymore, but instead rely on
  rulesets and environments
* add support for prereleases
* document workflow in `docs/maintainer.md`
  • Loading branch information
jgiannuzzi committed Oct 16, 2023
1 parent a3ee68c commit 3055474
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 65 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -342,3 +342,14 @@ jobs:
skopeo copy --all oci-archive:fasttrackml-oci.tar:$tag docker://ghcr.io/${{ steps.repo.outputs.name }}:$tag
echo "::endgroup::"
done
release:
name: Release
needs: all-required-checks-done
if: ${{ !github.event.repository.fork && github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') }}
permissions:
contents: write
pages: write
id-token: write
secrets: inherit
uses: ./.github/workflows/release.yml
96 changes: 96 additions & 0 deletions .github/workflows/create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Create release branch or tag

on:
workflow_dispatch:
inputs:
type:
description: "What to create (branch or tag)"
required: true
type: choice
options:
- "branch"
- "tag"
version:
description: "Version - major and minor for a branch (e.g. 0.3), semver for a tag (e.g. 0.3.1 or 0.3.1-rc.1)"
required: true

jobs:
create-release:
name: Create release branch from main
if: github.event.inputs.type == 'branch'
environment: create-release
runs-on: ubuntu-latest
steps:
- name: Check version
id: version
uses: actions/github-script@v6
with:
script: |
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
const version = context.payload.inputs.version;
const match = version.match(semver);
if (match === null) {
core.setFailed('Invalid version format. Expected "MAJOR.MINOR".');
} else {
core.setOutput('branch', `release/${version}`);
}
- name: Generate an app token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: main
token: ${{ steps.app-token.outputs.token }}

- name: Push
run: |
branch=${{ steps.version.outputs.branch }}
git checkout -b $branch
git push origin $branch
create-tag:
name: Create release tag from release branch
if: github.event.inputs.type == 'tag'
environment: create-release
runs-on: ubuntu-latest
steps:
# Regex comes from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
- name: Check version
id: version
uses: actions/github-script@v6
with:
script: |
const semver = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
const version = context.payload.inputs.version;
const match = version.match(semver);
if (match === null) {
core.setFailed('Invalid version format. Expected semver compliant version.');
} else {
core.setOutput('tag', `v${version}`);
core.setOutput('branch', `release/${match[1]}.${match[2]}`);
}
- name: Generate an app token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}

- name: Checkout release branch
uses: actions/checkout@v4
with:
ref: ${{ steps.version.outputs.branch }}
token: ${{ steps.app-token.outputs.token }}

- name: Push
run: |
tag=${{ steps.version.outputs.tag }}
git tag $tag
git push origin $tag
97 changes: 32 additions & 65 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,96 +1,55 @@
name: Release

on:
workflow_run:
types: [completed]
workflows: [CI]
branches:
- main
- v*
workflow_call:

permissions:
contents: read

jobs:
validate:
name: Validate ref
if: github.event.workflow_run.event == 'push' && github.event.workflow_run.conclusion == 'success' && !github.event.repository.fork
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

# The given ref should belong to the main branch.
# If it's main, it shouldn't be more than 2 commits away (in case another push happened in the meantime).
# If it starts with 'v', it should be a tag and belong to the main branch.
# Anything else is invalid.
- name: Validate ref
run: |
ref='${{ github.event.workflow_run.head_branch }}'
sha='${{ github.event.workflow_run.head_sha }}'
case $ref in
main)
[ $(git branch --contains=$sha main | wc -l) -eq 1 ] &&
[ $(git rev-list --count $sha..main) -le 2 ]
;;
v?*)
[[ $ref =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] &&
[ $(git tag --points-at $sha | grep -E "^$ref\$" | wc -l) -eq 1 ] &&
[ $(git branch --contains=$sha main | wc -l) -eq 1 ]
;;
*)
false
;;
esac
if [ $? -ne 0 ]; then
echo "::error ::Invalid ref $ref $sha"
exit 1
fi
pypi-publish:
name: upload release to PyPI
needs: validate
if: github.event.workflow_run.head_branch != 'main'
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
steps:
- name: Download artifact
run: gh run download ${{ github.event.workflow_run.id }} --repo ${{ github.event.workflow_run.repository.full_name }} --name fasttrackml-wheels --dir wheelhouse
env:
GH_TOKEN: ${{ github.token }}
uses: actions/download-artifact@v3
with:
name: fasttrackml-wheels
path: wheelhouse

- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: wheelhouse/
packages-dir: wheelhouse

github-release:
name: Publish GitHub release
needs: validate
if: github.event.workflow_run.head_branch != 'main'
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download artifact
run: gh run download ${{ github.event.workflow_run.id }} --repo ${{ github.event.workflow_run.repository.full_name }} --name fasttrackml-archives --dir dist
env:
GH_TOKEN: ${{ github.token }}
uses: actions/download-artifact@v3
with:
name: fasttrackml-archives
path: dist

- name: Create release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
files: dist/*
tag_name: ${{ github.event.workflow_run.head_branch }}
prerelease: ${{ contains(github.ref, '-') }}

update-website:
name: Update website
needs: github-release
if: ${{ !contains(github.ref, '-') }}
permissions:
contents: read
pages: write
Expand All @@ -99,35 +58,43 @@ jobs:

docker-release:
name: Publish container image to DockerHub
needs: validate
if: startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: release
steps:
# We need to checkout the repo in order to determine the latest tag.
- name: Checkout
if: startsWith(github.ref, 'refs/tags/v')
uses: actions/checkout@v4
with:
fetch-depth: 0

# The main branch is tagged as "main" and "edge".
# Tags are named after the version, e.g. "v0.1.0" -> "0.1.0".
# The latest non-prerelease version is also tagged as "latest".
# This is achieved by sorting the tags by version number, then filtering
# out prereleases and taking the last tag.
- name: Compute tags
id: tags
run: |
ref='${{ github.event.workflow_run.head_branch }}'
ref='${{ github.ref }}'
case $ref in
main)
refs/heads/main)
tags=("main" "edge")
;;
v*)
tags=("${ref#v}")
if [ $(git describe --tags --abbrev=0) == $ref ]; then
refs/tags/v*)
tags=("${ref#refs/tags/v}")
if [ "$(git -c 'versionsort.suffix=-' for-each-ref --sort=version:refname --format='%(refname)' 'refs/tags/v*' | grep -v -- - | tail -n1)" == "$ref" ]; then
tags+=("latest")
fi
esac
echo "ref=${ref#refs/*/}" >> $GITHUB_OUTPUT
echo "tags=${tags[@]}" >> $GITHUB_OUTPUT
- name: Download artifact
run: gh run download ${{ github.event.workflow_run.id }} --name fasttrackml-oci-image
env:
GH_TOKEN: ${{ github.token }}
uses: actions/download-artifact@v3
with:
name: fasttrackml-oci-image

- name: Login to Docker Hub
uses: docker/login-action@v3
Expand All @@ -140,6 +107,6 @@ jobs:
for tag in ${{ steps.tags.outputs.tags }}
do
echo "::group::Pushing image to ${{ vars.DOCKER_REPO }}:$tag"
skopeo copy --all oci-archive:fasttrackml-oci.tar:${{ github.event.workflow_run.head_branch }} docker://${{ vars.DOCKER_REPO }}:$tag
skopeo copy --all oci-archive:fasttrackml-oci.tar:${{ steps.tags.outputs.ref }} docker://${{ vars.DOCKER_REPO }}:$tag
echo "::endgroup::"
done
Loading

0 comments on commit 3055474

Please sign in to comment.