Release Pipeline #36
Workflow file for this run
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
name: Release Pipeline | |
on: | |
workflow_dispatch: | |
inputs: | |
version: | |
description: "Release version tag (required format: v[mayor].[minor].[bugfix], e.g. v1.3.0)" | |
type: string | |
required: true | |
draft: | |
description: Should a release draft be created? (Otherwise the release will be published immediately) | |
type: boolean | |
default: true | |
required: true | |
artifact: | |
description: Add artifacts to the release. All files from /docs are added to the release. | |
type: boolean | |
default: true | |
required: false | |
force: | |
description: "If force is true, already published releases can be overwritten. Caution: This action deletes already published releases and can **not** be undone!" | |
type: boolean | |
required: false | |
jobs: | |
release: | |
runs-on: ubuntu-latest | |
permissions: | |
contents: write | |
pull-requests: write | |
steps: | |
- name: Validate input parameters and check user permission | |
id: param_check | |
run: | | |
VERSION_REGEX="^v[0-9]+\.[0-9]+\.[0-9]+$" | |
version=${{ github.event.inputs.version }} | |
if [[ ! "$version" =~ $VERSION_REGEX ]]; then | |
echo "Invalid version format: $version . Please provide a version matching the pattern 'v[number].[number].[number]'." | |
exit 1 | |
fi | |
if [[ "${{ github.actor }}" != "dkoeni" ]]; then | |
echo "You have no permissons to start the release action." | |
exit 1 | |
fi | |
- name: Checkout repository | |
uses: actions/checkout@v3 | |
with: | |
fetch-depth: -1 # this causes a full repo check out, keep in mind that this propcess needs probably a logner time on big repos | |
- name: Checkout Wiki | |
uses: actions/checkout@v3 | |
with: | |
repository: ${{github.repository}}.wiki | |
path: wiki | |
- name: Extract variables for Release # Adjust names only here | |
id: var | |
run: | | |
version=$(echo ${{ github.event.inputs.version }} | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') # alternative: '[[:digit:]]+\.[[:digit:]]+\.[[:digit:]]' | |
repo_name=$( echo ${{ github.repository }} | sed -E "s/^.*\///" ) | |
echo "VERSION"=$version >> $GITHUB_OUTPUT | |
echo "RELEASE_NAME=$repo_name Release $version" >> $GITHUB_OUTPUT | |
echo "RELEASE_TAG=v$version" >> $GITHUB_OUTPUT | |
echo "RELEASE_NOTES=RELEASE.md" >> $GITHUB_OUTPUT | |
echo "RELEASE_ASSETS_FOLDER=docs/" >> $GITHUB_OUTPUT | |
echo "RELEASE_ASSETS_NAME=artifact" >> $GITHUB_OUTPUT | |
- name: Get release note content from wiki | |
id: release_note_body | |
run: | | |
regex="### Release ${{ steps.var.outputs.RELEASE_TAG }}[[:space:]]*[[:print:]]*[[:cntrl:]]{2}([[:print:]]+[[:space:]])*" | |
content=$(grep -ozE "$regex" wiki/Release-Roadmap.md | tr "\0" "\n" | tail -n +3) | |
# abort if content is empty --> wiki page must be present before release | |
if [ -z "$content" ]; then | |
echo "Found no data for ${{ steps.var.outputs.RELEASE_NAME }} at wiki/Release Roadmap (${{ github.repository }}). Please create a section <<Release ${{ steps.var.outputs.RELEASE_TAG }}>> and fill in details for the release by following the instructions at the .github Wiki. A template can be found at https://github.com/swissfintechinnovations/.github/wiki/Release-Roadmap-Example." | |
exit 1 | |
fi | |
body="$content"$'\n\n# \n\n' | |
echo "BODY=${body//$'\n'/'\n'}" >> $GITHUB_OUTPUT | |
- name: Check file version | |
id: check_version | |
run: | | |
release_version=${{ steps.var.outputs.VERSION }} | |
versions=$(grep -Eo 'version: [0-9]+\.[0-9]+\.[0-9]+' *.yaml | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') | |
versions_outdated='false' | |
for version in $versions; do | |
if [[ $version != $release_version ]]; then | |
versions_outdated='true' | |
break | |
fi | |
done | |
echo "VERSIONS_OUTDATED=$versions_outdated" >> $GITHUB_OUTPUT | |
- name: Switch to release branch | |
id: create_release_branch | |
if: steps.check_version.outputs.VERSIONS_OUTDATED == 'true' | |
run: | | |
BRANCH_NAME="release/${{ steps.var.outputs.VERSION }}" | |
git config --global user.name "GitHub Action" | |
git config --global user.email "dominik.koenig@fort-it.ch" | |
git checkout $BRANCH_NAME 2>/dev/null || git checkout -b $BRANCH_NAME # checkout branch or create new one if not exists | |
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_OUTPUT | |
- name: Update version in files | |
id: update_version | |
if: steps.check_version.outputs.VERSIONS_OUTDATED == 'true' | |
run: | | |
VERSION="${{ steps.var.outputs.VERSION }}" | |
RELEASE_NAME="${{ steps.var.outputs.RELEASE_NAME }}" | |
BRANCH_NAME="${{ steps.create_release_branch.outputs.BRANCH_NAME }}" | |
echo "Prepare Release $RELEASE_NAME: update version number in yaml files" | |
sed -E -i "s/version: [0-9]+\.[0-9]+\.[0-9]+/version: $VERSION/" *.yaml | |
git add -u . | |
# commit only if there was changes in the yaml files | |
git diff --staged --quiet || git commit -m "Automated version update" | |
git commit --quiet --allow-empty -m '[skip-workflow]' | |
git push --quiet --set-upstream origin $BRANCH_NAME | |
- name: "Rollback: Delete Branch" | |
if: failure() && steps.update_version.outcome == 'failure' | |
run: | | |
git checkout main | |
git branch -d ${{ steps.create_release_branch.outputs.BRANCH_NAME }} | |
git push origin --delete ${{ steps.create_release_branch.outputs.BRANCH_NAME }} | |
- name: Create and merge Pull Request | |
id: create_pr | |
if: steps.check_version.outputs.VERSIONS_OUTDATED == 'true' | |
run: | | |
response=$(gh pr create -B main -H ${{ steps.create_release_branch.outputs.BRANCH_NAME }} --title 'Automated version update to version ${{ steps.var.outputs.VERSION }}' --body 'Created by Github action (release workflow)') | |
number=$(echo "$response" | grep -oE '[0-9]+$') # parse GitHub PR link to extract PR number | |
echo "PR_NUMBER=$number" >> $GITHUB_OUTPUT | |
sleep 2 | |
# gh pr review $number --approve # can not approve own PR | |
gh pr merge $number --admin --merge --delete-branch --body "Automated version update" # force merge since PR is not approved | |
git checkout main | |
env: | |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
# - name: "Rollback: Delete PR" | |
# if: failure() && steps.create_pr.outcome == 'failure' | |
# run: | | |
# gh pr close ${{ steps.create_pr.outputs.PR_NUMBER }} -c "Couldn't merge PR automatically" --delete-branch | |
# env: | |
# GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
- name: Delete release (draft) if already exists | |
id: delete_release_draft | |
run: | | |
ACCESS_TOKEN=${{ secrets.GITHUB_TOKEN }} | |
REPO_NAME=${{ github.repository }} | |
TAG=${{ steps.var.outputs.RELEASE_TAG }} | |
RELEASE_NAME="${{ steps.var.outputs.RELEASE_NAME }}" | |
# Get the release ID belonging to the release tag | |
RELEASE_ID=$(curl -sS -X GET -H "Authorization: Bearer $ACCESS_TOKEN" "https://api.github.com/repos/$REPO_NAME/releases" | jq -r ".[] | select(.name == \"$RELEASE_NAME\") | .id") | |
# RELEASE_ID var contains 0 or 1 IDs | |
if [[ ! $RELEASE_ID =~ ^[0-9]*$ ]]; then | |
echo "Found more than one release with name \"$RELEASE_ID\"." | |
exit 1 | |
fi | |
is_draft=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://api.github.com/repos/$REPO_NAME/releases/$RELEASE_ID | jq '.draft') | |
if [[ "$is_draft" == 'false' ]]; then | |
echo "Release is published." | |
if [[ ${{ github.event.inputs.force }} == true ]]; then | |
commit_id=$(git rev-list -n 1 "refs/tags/$TAG") | |
echo $commit_id | |
git tag -d $TAG | |
git push origin ":refs/tags/$TAG" | |
echo "Force Flag is set! Deleted tag $TAG at commit ID $commit_id." | |
fi | |
if [[ ${{ github.event.inputs.force }} == false ]]; then | |
echo "Release $TAG already exists. Please verify that the version entered is correct. Set the force flag to overwrite the release." | |
exit 1 | |
fi | |
fi | |
if [[ -n "$RELEASE_ID" ]]; then | |
# Delete the existing release draft | |
curl -sS -X DELETE -H "Authorization: Bearer $ACCESS_TOKEN" "https://api.github.com/repos/$REPO_NAME/releases/$RELEASE_ID" | |
echo "Deleted release \"$RELEASE_ID\" with ID $RELEASE_ID" | |
fi | |
- name: Create release draft | |
id: create_release_draft | |
run: | | |
ACCESS_TOKEN=${{ secrets.GITHUB_TOKEN }} | |
REPO_NAME=${{ github.repository }} | |
RESPONSE=$(curl -sS -i -X POST \ | |
-H "Authorization: Bearer $ACCESS_TOKEN" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"https://api.github.com/repos/$REPO_NAME/releases" \ | |
-d '{ | |
"tag_name": "'"${{ steps.var.outputs.RELEASE_TAG }}"'", | |
"name": "'"${{ steps.var.outputs.RELEASE_NAME }}"'", | |
"body": "'"${{ steps.release_note_body.outputs.BODY }}"'", | |
"generate_release_notes": true, | |
"draft": true | |
}') | |
if [[ $(echo $RESPONSE | head -n 1 | cut -d$' ' -f2 ) -ne 201 ]]; then | |
echo "Failed to create release draft. Received response from GitHub API:" | |
echo "" | |
echo "$RESPONSE" | |
exit 1 | |
fi | |
echo "RELEASE_ID=$(echo $RESPONSE | grep -o -z '\{.*\}' | jq -r '.id')" >> $GITHUB_OUTPUT | |
- name: Upload artifacts | |
id: upload_artifact | |
# ensure assert folder exsits and not empty otherwise skip step | |
if: github.event.inputs.artifact == 'true' && (hashFiles(steps.var.outputs.RELEASE_ASSETS_FOLDER) != '') | |
run: | | |
ACCESS_TOKEN=${{ secrets.GITHUB_TOKEN }} | |
REPO_NAME=${{ github.repository }} | |
RELEASE_ID=${{ steps.create_release_draft.outputs.RELEASE_ID }} | |
RELEASE_ASSETS_FOLDER=${{ steps.var.outputs.RELEASE_ASSETS_FOLDER }} | |
RELEASE_ASSETS_NAME=${{ steps.var.outputs.RELEASE_ASSETS_NAME }} | |
RELEASE_ASSETS_ZIP=$RELEASE_ASSETS_NAME.zip | |
RELEASE_ASSETS_TARGZ=$RELEASE_ASSETS_NAME.tar.gz | |
zip -qr $RELEASE_ASSETS_ZIP $RELEASE_ASSETS_FOLDER | |
RESPONSE=$(curl -sS -i -X POST \ | |
-H "Authorization: Bearer $ACCESS_TOKEN" \ | |
-H "Content-Type: application/octet-stream" \ | |
"https://uploads.github.com/repos/$REPO_NAME/releases/$RELEASE_ID/assets?name=$RELEASE_ASSETS_ZIP" \ | |
--data-binary "@$RELEASE_ASSETS_ZIP" ) | |
if [[ $(echo $RESPONSE | head -n 1 | cut -d$' ' -f2 ) -ne 201 ]]; then | |
echo "Failed to upload release asset $RELEASE_ASSETS_ZIP. Received response from GitHub API:" | |
echo "" | |
echo "$RESPONSE" | |
exit 1 | |
fi | |
tar -czf $RELEASE_ASSETS_TARGZ $RELEASE_ASSETS_FOLDER | |
RESPONSE=$(curl -sS -i -X POST -H "Authorization: Bearer $ACCESS_TOKEN" -H "Content-Type: application/octet-stream" \ | |
--data-binary "@$RELEASE_ASSETS_TARGZ" \ | |
"https://uploads.github.com/repos/$REPO_NAME/releases/$RELEASE_ID/assets?name=$RELEASE_ASSETS_TARGZ") | |
if [[ $(echo $RESPONSE | head -n 1 | cut -d$' ' -f2 ) -ne 201 ]]; then | |
echo "Failed to upload release asset $RELEASE_ASSETS_TARGZ. Received response from GitHub API:" | |
echo "" | |
echo "$RESPONSE" | |
exit 1 | |
fi | |
- name: Publish release | |
if: github.event.inputs.draft == 'false' | |
id: publish_release | |
run: | | |
ACCESS_TOKEN=${{ secrets.GITHUB_TOKEN }} | |
REPO_NAME=${{ github.repository }} | |
RELEASE_ID=${{ steps.create_release_draft.outputs.RELEASE_ID }} | |
RESPONSE=$(curl -sS -i -X PATCH \ | |
-H "Authorization: Bearer $ACCESS_TOKEN" \ | |
-H "Accept: application/vnd.github.v3+json" \ | |
"https://api.github.com/repos/$REPO_NAME/releases/$RELEASE_ID" \ | |
-d '{ | |
"prerelease": false, | |
"draft": false, | |
"make_latest": "true" | |
}') | |
if [[ $(echo $RESPONSE | head -n 1 | cut -d$' ' -f2 ) -ne 200 ]]; then | |
echo "Failed to publish release. Received response from GitHub API:" | |
echo "" | |
echo "$RESPONSE" | |
exit 1 | |
fi |