Files
cozystack/.github/workflows/backport.yaml
T
Andrei Kvapil 814a47f23e ci: move remaining workflows off the persistent self-hosted runner
Finishes the runner decommission tracked in #2937. PR builds, the main
cache warmer, nightly, promote-rc and pull-requests-release already run
on the ephemeral pool; this moves the stragglers so no workflow depends
on the persistent runner anymore:

- tags.yaml prepare-release: large ephemeral shape (same as the cache
  warmer), flux CLI bootstrap, and read-only OCIR access so release
  builds warm-start from the shared mode=max build cache while still
  pushing images to GHCR. Without the cache wiring every tag build on
  an ephemeral runner would be 100% cold.
- tags.yaml generate-changelog / update-website-docs, backport.yaml,
  update-releasenotes.yaml: GitHub-hosted runners — these jobs only
  need git and the GitHub API, no docker or repo toolchain.
- pull-requests.yaml: the debug label no longer reroutes jobs to the
  self-hosted runner (that path would queue forever once the runner is
  gone). The label keeps gating the SSH breakpoint on e2e failure,
  which works from ephemeral runners too.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <andrei.kvapil@aenix.io>
2026-07-09 20:50:43 +02:00

147 lines
6.1 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Automatic Backport
on:
pull_request_target:
types: [closed, labeled] # fires when PR is closed (merged) or labeled
concurrency:
group: backport-${{ github.workflow }}-${{ github.event.pull_request.number }}
cancel-in-progress: true
# Top-level token defaults to read-only; jobs request the minimum extra scopes.
permissions:
contents: read
jobs:
# Determine which backports are needed
prepare:
if: |
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
(
contains(github.event.pull_request.labels.*.name, 'backport') ||
contains(github.event.pull_request.labels.*.name, 'backport-previous') ||
(github.event.action == 'labeled' && (github.event.label.name == 'backport' || github.event.label.name == 'backport-previous'))
)
# GitHub API calls only — no docker, no repo toolchain: GitHub-hosted.
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
backport_current: ${{ steps.labels.outputs.backport }}
backport_previous: ${{ steps.labels.outputs.backport_previous }}
current_branch: ${{ steps.branches.outputs.current_branch }}
previous_branch: ${{ steps.branches.outputs.previous_branch }}
steps:
- name: Check which labels are present
id: labels
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const pr = context.payload.pull_request;
const labels = pr.labels.map(l => l.name);
const isBackport = labels.includes('backport');
const isBackportPrevious = labels.includes('backport-previous');
core.setOutput('backport', isBackport ? 'true' : 'false');
core.setOutput('backport_previous', isBackportPrevious ? 'true' : 'false');
console.log(`backport label: ${isBackport}, backport-previous label: ${isBackportPrevious}`);
- name: Determine target branches
id: branches
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
// Get latest release
let latestRelease;
try {
latestRelease = await github.rest.repos.getLatestRelease({
owner: context.repo.owner,
repo: context.repo.repo
});
} catch (e) {
core.setFailed('No existing releases found; cannot determine backport target.');
return;
}
const [maj, min] = latestRelease.data.tag_name.replace(/^v/, '').split('.');
const currentBranch = `release-${maj}.${min}`;
const prevMin = parseInt(min) - 1;
const previousBranch = prevMin >= 0 ? `release-${maj}.${prevMin}` : '';
core.setOutput('current_branch', currentBranch);
core.setOutput('previous_branch', previousBranch);
console.log(`Current branch: ${currentBranch}, Previous branch: ${previousBranch || 'N/A'}`);
// Verify previous branch exists if we need it
if (previousBranch && '${{ steps.labels.outputs.backport_previous }}' === 'true') {
try {
await github.rest.repos.getBranch({
owner: context.repo.owner,
repo: context.repo.repo,
branch: previousBranch
});
console.log(`Previous branch ${previousBranch} exists`);
} catch (e) {
core.setFailed(`Previous branch ${previousBranch} does not exist.`);
return;
}
}
backport:
needs: prepare
if: |
github.event.pull_request.merged == true &&
github.event.pull_request.base.ref == 'main' &&
(needs.prepare.outputs.backport_current == 'true' || needs.prepare.outputs.backport_previous == 'true')
# git cherry-picks via backport-action — no docker, no repo toolchain: GitHub-hosted.
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
strategy:
matrix:
backport_type: [current, previous]
steps:
# 1. Determine target branch based on matrix
- name: Set target branch
id: target
if: |
(matrix.backport_type == 'current' && needs.prepare.outputs.backport_current == 'true') ||
(matrix.backport_type == 'previous' && needs.prepare.outputs.backport_previous == 'true')
run: |
if [ "${{ matrix.backport_type }}" == "current" ]; then
echo "branch=${{ needs.prepare.outputs.current_branch }}" >> $GITHUB_OUTPUT
echo "Target branch: ${{ needs.prepare.outputs.current_branch }}"
else
echo "branch=${{ needs.prepare.outputs.previous_branch }}" >> $GITHUB_OUTPUT
echo "Target branch: ${{ needs.prepare.outputs.previous_branch }}"
fi
# 2. Checkout (required by backport‑action)
- name: Checkout repository
if: steps.target.outcome == 'success'
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
# 3. Create the back‑port pull request
- name: Create back‑port PR
id: backport
if: steps.target.outcome == 'success'
uses: korthout/backport-action@0193454f0c5947491d348f33a275c119f30eb736 # v3.2.1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
label_pattern: '' # don't read labels for targets
target_branches: ${{ steps.target.outputs.branch }}
merge_commits: skip
# conflict_resolution lives inside the experimental JSON input, not as a
# top-level key. Passed top-level it is silently ignored and the action
# falls back to the experimental default ("fail"), which on a conflict
# creates no PR and only comments. Nesting it here makes a conflicting
# backport open a draft PR with the conflict committed for manual fixup.
experimental: |
{
"conflict_resolution": "draft_commit_conflicts"
}