mirror of
https://github.com/outbackdingo/cozystack.git
synced 2026-01-27 10:18:39 +00:00
255 lines
9.0 KiB
YAML
255 lines
9.0 KiB
YAML
name: Releasing PR
|
|
|
|
on:
|
|
pull_request:
|
|
types: [labeled, opened, synchronize, reopened, closed]
|
|
|
|
concurrency:
|
|
group: pull-requests-release-${{ github.workflow }}-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
verify:
|
|
name: Test Release
|
|
runs-on: [self-hosted]
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
|
|
if: |
|
|
contains(github.event.pull_request.labels.*.name, 'release') &&
|
|
github.event.action != 'closed'
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
fetch-tags: true
|
|
|
|
- name: Login to GitHub Container Registry
|
|
uses: docker/login-action@v3
|
|
with:
|
|
username: ${{ github.repository_owner }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
registry: ghcr.io
|
|
|
|
- name: Extract tag from PR branch
|
|
id: get_tag
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const branch = context.payload.pull_request.head.ref;
|
|
const m = branch.match(/^release-(\d+\.\d+\.\d+(?:[-\w\.]+)?)$/);
|
|
if (!m) {
|
|
core.setFailed(`❌ Branch '${branch}' does not match 'release-X.Y.Z[-suffix]'`);
|
|
return;
|
|
}
|
|
const tag = `v${m[1]}`;
|
|
core.setOutput('tag', tag);
|
|
|
|
- name: Find draft release and get asset IDs
|
|
id: fetch_assets
|
|
uses: actions/github-script@v7
|
|
with:
|
|
github-token: ${{ secrets.GH_PAT }}
|
|
script: |
|
|
const tag = '${{ steps.get_tag.outputs.tag }}';
|
|
const releases = await github.rest.repos.listReleases({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
per_page: 100
|
|
});
|
|
const draft = releases.data.find(r => r.tag_name === tag && r.draft);
|
|
if (!draft) {
|
|
core.setFailed(`Draft release '${tag}' not found`);
|
|
return;
|
|
}
|
|
const findAssetId = (name) =>
|
|
draft.assets.find(a => a.name === name)?.id;
|
|
const installerId = findAssetId("cozystack-installer.yaml");
|
|
const diskId = findAssetId("nocloud-amd64.raw.xz");
|
|
if (!installerId || !diskId) {
|
|
core.setFailed("Missing required assets");
|
|
return;
|
|
}
|
|
core.setOutput("installer_id", installerId);
|
|
core.setOutput("disk_id", diskId);
|
|
|
|
- name: Download assets from GitHub API
|
|
run: |
|
|
mkdir -p _out/assets
|
|
curl -sSL \
|
|
-H "Authorization: token ${GH_PAT}" \
|
|
-H "Accept: application/octet-stream" \
|
|
-o _out/assets/cozystack-installer.yaml \
|
|
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ steps.fetch_assets.outputs.installer_id }}"
|
|
curl -sSL \
|
|
-H "Authorization: token ${GH_PAT}" \
|
|
-H "Accept: application/octet-stream" \
|
|
-o _out/assets/nocloud-amd64.raw.xz \
|
|
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ steps.fetch_assets.outputs.disk_id }}"
|
|
env:
|
|
GH_PAT: ${{ secrets.GH_PAT }}
|
|
|
|
- name: Run tests
|
|
run: make test
|
|
|
|
finalize:
|
|
name: Finalize Release
|
|
runs-on: [self-hosted]
|
|
permissions:
|
|
contents: write
|
|
|
|
if: |
|
|
github.event.pull_request.merged == true &&
|
|
contains(github.event.pull_request.labels.*.name, 'release')
|
|
|
|
steps:
|
|
# Extract tag from branch name (branch = release-X.Y.Z*)
|
|
- name: Extract tag from branch name
|
|
id: get_tag
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const branch = context.payload.pull_request.head.ref;
|
|
const m = branch.match(/^release-(\d+\.\d+\.\d+(?:[-\w\.]+)?)$/);
|
|
if (!m) {
|
|
core.setFailed(`Branch '${branch}' does not match 'release-X.Y.Z[-suffix]'`);
|
|
return;
|
|
}
|
|
const tag = `v${m[1]}`;
|
|
core.setOutput('tag', tag);
|
|
console.log(`✅ Tag to publish: ${tag}`);
|
|
|
|
# Checkout repo & create / push annotated tag
|
|
- name: Checkout repo
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Create tag on merge commit
|
|
run: |
|
|
git tag -f ${{ steps.get_tag.outputs.tag }} ${{ github.sha }}
|
|
git push -f origin ${{ steps.get_tag.outputs.tag }}
|
|
|
|
# Ensure maintenance branch release-X.Y
|
|
- name: Ensure maintenance branch release-X.Y
|
|
uses: actions/github-script@v7
|
|
with:
|
|
github-token: ${{ secrets.GH_PAT }}
|
|
script: |
|
|
const tag = '${{ steps.get_tag.outputs.tag }}'; // e.g. v0.1.3 or v0.1.3-rc3
|
|
const match = tag.match(/^v(\d+)\.(\d+)\.\d+(?:[-\w\.]+)?$/);
|
|
if (!match) {
|
|
core.setFailed(`❌ tag '${tag}' must match 'vX.Y.Z' or 'vX.Y.Z-suffix'`);
|
|
return;
|
|
}
|
|
const line = `${match[1]}.${match[2]}`;
|
|
const branch = `release-${line}`;
|
|
|
|
// Get main branch commit for the tag
|
|
const ref = await github.rest.git.getRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: `tags/${tag}`
|
|
});
|
|
|
|
const commitSha = ref.data.object.sha;
|
|
|
|
try {
|
|
await github.rest.repos.getBranch({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
branch
|
|
});
|
|
|
|
await github.rest.git.updateRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: `heads/${branch}`,
|
|
sha: commitSha,
|
|
force: true
|
|
});
|
|
console.log(`🔁 Force-updated '${branch}' to ${commitSha}`);
|
|
} catch (err) {
|
|
if (err.status === 404) {
|
|
await github.rest.git.createRef({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
ref: `refs/heads/${branch}`,
|
|
sha: commitSha
|
|
});
|
|
console.log(`✅ Created branch '${branch}' at ${commitSha}`);
|
|
} else {
|
|
console.error('Unexpected error --', err);
|
|
core.setFailed(`Unexpected error creating/updating branch: ${err.message}`);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
# Get the latest published release
|
|
- name: Get the latest published release
|
|
id: latest_release
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
try {
|
|
const rel = await github.rest.repos.getLatestRelease({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo
|
|
});
|
|
core.setOutput('tag', rel.data.tag_name);
|
|
} catch (_) {
|
|
core.setOutput('tag', '');
|
|
}
|
|
|
|
# Compare current tag vs latest using semver-utils
|
|
- name: Semver compare
|
|
id: semver
|
|
uses: madhead/semver-utils@v4.3.0
|
|
with:
|
|
version: ${{ steps.get_tag.outputs.tag }}
|
|
compare-to: ${{ steps.latest_release.outputs.tag }}
|
|
|
|
# Derive flags: prerelease? make_latest?
|
|
- name: Calculate publish flags
|
|
id: flags
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const tag = '${{ steps.get_tag.outputs.tag }}'; // v0.31.5-rc.1
|
|
const m = tag.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/);
|
|
if (!m) {
|
|
core.setFailed(`❌ tag '${tag}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`);
|
|
return;
|
|
}
|
|
const version = m[1] + (m[2] ?? ''); // 0.31.5-rc.1
|
|
const isRc = Boolean(m[2]);
|
|
core.setOutput('is_rc', isRc);
|
|
const outdated = '${{ steps.semver.outputs.comparison-result }}' === '<';
|
|
core.setOutput('make_latest', isRc || outdated ? 'false' : 'legacy');
|
|
|
|
# Publish draft release with correct flags
|
|
- name: Publish draft release
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const tag = '${{ steps.get_tag.outputs.tag }}';
|
|
const releases = await github.rest.repos.listReleases({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo
|
|
});
|
|
const draft = releases.data.find(r => r.tag_name === tag && r.draft);
|
|
if (!draft) throw new Error(`Draft release for ${tag} not found`);
|
|
await github.rest.repos.updateRelease({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
release_id: draft.id,
|
|
draft: false,
|
|
prerelease: ${{ steps.flags.outputs.is_rc }},
|
|
make_latest: '${{ steps.flags.outputs.make_latest }}'
|
|
});
|
|
|
|
console.log(`🚀 Published release for ${tag}`);
|