Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added support for release automation #849

Merged
merged 6 commits into from
Jul 15, 2024
Merged
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
12 changes: 12 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
changelog:
exclude:
labels: ['wontfix', 'question', 'duplicate', 'invalid']
categories:
- title: 'Enhancements'
labels: ['enhancement']
- title: 'Bug Fixes'
labels: ['bug']
- title: 'Documentation'
labels: ['documentation']
- title: 'Dependency Upgrades'
labels: ['dependencies']
79 changes: 79 additions & 0 deletions .github/scripts/check-pr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
const axios = require('axios');

const githubToken = process.env.GITHUB_TOKEN;
const { GITHUB_REPOSITORY, GITHUB_PR_NUMBER } = process.env;

const [owner, repo] = GITHUB_REPOSITORY.split('/');

async function getPRDetails() {
const url = `https://api.github.com/repos/${owner}/${repo}/pulls/${GITHUB_PR_NUMBER}`;
const response = await axios.get(url, {
headers: {
Authorization: `token ${githubToken}`,
},
});
return response.data;
}

async function getIssueDetails(issueNumber) {
try {
const url = `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`;
const response = await axios.get(url, {
headers: {
Authorization: `token ${githubToken}`,
},
});
return response.data;
} catch (error) {
if (error.response && error.response.status === 404) {
console.log(`Issue #${issueNumber} not found, skipping...`);
return null;
} else {
throw error;
}
}
}

async function run() {
try {
const pr = await getPRDetails();
const { labels: prLabels, milestone: prMilestone, body: prBody } = pr;

if (prLabels.length === 0) {
throw new Error('The PR has no labels.');
}
if (!prMilestone) {
throw new Error('The PR has no milestone.');
}

const issueNumberMatches = prBody.match(/#(\d+)/g);

if (!issueNumberMatches) {
console.log('No associated issues found in PR description.');
} else {
for (const match of issueNumberMatches) {
const issueNumber = match.replace('#', '');
const issue = await getIssueDetails(issueNumber);
if (issue) {
const { labels: issueLabels, milestone: issueMilestone } = issue;

if (issueLabels.length === 0) {
throw new Error(`Associated issue #${issueNumber} has no labels.`);
}
if (!issueMilestone) {
throw new Error(
`Associated issue #${issueNumber} has no milestone.`
);
}
}
}
}

console.log('PR and all associated issues have labels and milestones.');
} catch (error) {
console.error(error.message);
process.exit(1);
}
}

run();
27 changes: 27 additions & 0 deletions .github/workflows/pr-label-milestone-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: PR Label and Milestone Check

on:
pull_request:
types: [opened, edited, labeled, unlabeled, synchronize]

jobs:
check_pr:
runs-on: [self-hosted, Linux, medium, ephemeral]

steps:
- name: Checkout code
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7

- name: Set up Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
with:
node-version: '20'

- name: Install dependencies
run: npm install axios

- name: Check PR labels and milestones
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PR_NUMBER: ${{ github.event.number }}
run: node .github/scripts/check-pr.js
182 changes: 182 additions & 0 deletions .github/workflows/release-automation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
name: Release Branch Automation

on:
workflow_dispatch:
inputs:
version:
description: 'Release Version (semver ie. 0.9.0):'
type: string
required: true

jobs:
branch_bump_tag:
runs-on: [self-hosted, Linux, medium, ephemeral]
env:
RELEASE_NOTES_FILENAME: release_notes
outputs:
create_pr: ${{ env.CREATE_PR }}
next_version_snapshot: ${{ env.NEXT_VERSION_SNAPSHOT }}
pr_title: ${{ env.PR_TITLE }}
release_branch: ${{ env.RELEASE_BRANCH }}

steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit

- name: Parse Version
id: version_parser
uses: step-security/semver-utils@f437161847710e2a9e7b99f75442cbad9aa9ec14 # v1.0.0
with:
lenient: false
version: ${{ github.event.inputs.version }}

- name: Set Release Environment Variables
run: |
PREMINOR_VERSION=${{ steps.version_parser.outputs.inc-preminor }}
NEXT_VERSION_SNAPSHOT=${PREMINOR_VERSION//-0/-SNAPSHOT}
RELEASE_BRANCH="release/${{ steps.version_parser.outputs.major }}.${{ steps.version_parser.outputs.minor }}"
[[ -z "${{ steps.version_parser.outputs.prerelease }}" ]] && \
VERSION=${{ steps.version_parser.outputs.release }} || \
VERSION="${{ steps.version_parser.outputs.release }}-${{ steps.version_parser.outputs.prerelease }}"
RELEASE_TAG="v${VERSION}"
cat >> $GITHUB_ENV <<EOF
NEXT_VERSION_SNAPSHOT=$NEXT_VERSION_SNAPSHOT
RELEASE_BRANCH=$RELEASE_BRANCH
RELEASE_TAG=$RELEASE_TAG
VERSION=$VERSION
EOF

- name: Checkout repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 0
ref: main
token: ${{ secrets.GH_ACCESS_TOKEN }}

- name: Import GPG Key
id: gpg_importer
uses: step-security/ghaction-import-gpg@a7c87df2279f2bf2e69ba8289dfbf35fe05a4e08 # v1.0.0
with:
git_commit_gpgsign: true
git_tag_gpgsign: true
git_user_signingkey: true
gpg_private_key: ${{ secrets.GPG_KEY_CONTENTS }}
passphrase: ${{ secrets.GPG_KEY_PASSPHRASE }}

- name: Create and Switch to Release Branch
run: |
if ! git ls-remote --exit-code --heads --quiet origin refs/heads/${RELEASE_BRANCH}; then
git checkout -b ${RELEASE_BRANCH}
git push -u origin ${RELEASE_BRANCH}

# create a PR to bump main branch to the next snapshot version
echo "CREATE_PR=true" >> $GITHUB_ENV
echo "PR_TITLE=Bump versions for v$NEXT_VERSION_SNAPSHOT" >> $GITHUB_ENV
else
git checkout ${RELEASE_BRANCH}
fi

- name: Set up Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: '20'

- name: Install make
run: sudo apt-get update; sudo apt-get install build-essential -y

- name: Install dependencies
run: npm ci

- name: Bump Versions
run: npm version ${{ env.VERSION }}

- name: Create Release Notes
if: ${{ steps.milestone.outputs.milestone_id != '' }}
uses: Decathlon/release-notes-generator-action@98423db7024696a339f3988ac8a2b051c5860741 # v3.1.6
env:
FILENAME: ${{ env.RELEASE_NOTES_FILENAME }}
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
MILESTONE_NUMBER: ${{ steps.milestone.outputs.milestone_id }}

- name: Commit and Tag
uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
with:
commit_author: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
commit_message: Bump versions for ${{ env.RELEASE_TAG }}
commit_options: '--no-verify --signoff'
commit_user_name: ${{ steps.gpg_importer.outputs.name }}
commit_user_email: ${{ steps.gpg_importer.outputs.email }}
tagging_message: ${{ env.RELEASE_TAG }}

- name: Create Github Release
uses: ncipollo/release-action@2c591bcc8ecdcd2db72b97d6147f871fcd833ba5 # v1.14.0
with:
bodyFile: ${{ env.RELEASE_NOTES_FILENAME }}.md
commit: ${{ env.RELEASE_BRANCH }}
draft: true
name: ${{ env.RELEASE_TAG }}
omitBody: ${{ steps.milestone.outputs.milestone_id == '' }}
prerelease: ${{ steps.version_parser.outputs.prerelease != '' }}
tag: ${{ env.RELEASE_TAG }}
token: ${{ secrets.GH_ACCESS_TOKEN }}

create_snapshot_pr:
name: Create snapshot PR
runs-on: [self-hosted, Linux, medium, ephemeral]
needs: branch_bump_tag
if: ${{ needs.branch_bump_tag.outputs.create_pr == 'true' }}
env:
NEXT_VERSION_SNAPSHOT: ${{ needs.branch_bump_tag.outputs.next_version_snapshot }}
RELEASE_BRANCH: ${{ needs.branch_bump_tag.outputs.release_branch }}

steps:
- name: Harden Runner
uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0
with:
egress-policy: audit

- name: Checkout Repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6
with:
fetch-depth: 0
ref: main
token: ${{ secrets.GH_ACCESS_TOKEN }}

- name: Import GPG Key
id: gpg_importer
uses: step-security/ghaction-import-gpg@a7c87df2279f2bf2e69ba8289dfbf35fe05a4e08 # v1.0.0
with:
git_commit_gpgsign: true
git_tag_gpgsign: true
git_user_signingkey: true
gpg_private_key: ${{ secrets.GPG_KEY_CONTENTS }}
passphrase: ${{ secrets.GPG_KEY_PASSPHRASE }}

- name: Set up Node.js
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
with:
node-version: '20'

- name: Install make
run: sudo apt-get update; sudo apt-get install build-essential -y

- name: Install dependencies
run: npm ci

- name: Bump Versions
run: npm version ${{ env.NEXT_VERSION_SNAPSHOT }}

- name: Create Pull Request
uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e # v6.0.5
with:
body: ''
branch: create-pull-request/${{ env.NEXT_VERSION_SNAPSHOT }}
commit-message: Bump versions for v${{ env.NEXT_VERSION_SNAPSHOT }}
committer: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
author: ${{ steps.gpg_importer.outputs.name }} <${{ steps.gpg_importer.outputs.email }}>
delete-branch: true
signoff: true
title: ${{ needs.branch_bump_tag.outputs.pr_title }}
token: ${{ secrets.GH_ACCESS_TOKEN }}
Loading