d5623bdf9c
The PR creation curl immediately follows branch creation via the Contents API, but Gitea may not have fully indexed the new branch for pull request operations yet. This causes intermittent HTTP errors on the first run. - Add sleep 3 before PR creation to allow Gitea to process the new branch - Use --retry-all-errors so curl retries on HTTP 4xx/5xx (not just connection failures) - Capture and display the actual HTTP error code and response body on failure for easier debugging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
341 lines
13 KiB
YAML
341 lines
13 KiB
YAML
name: Unbound Release
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
tag_only:
|
|
description: 'Set to true to only create tags without full releases'
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
|
|
concurrency:
|
|
group: release-${{ github.repository }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
GITEA_URL: http://gitea-http.gitea.svc.cluster.local:3000
|
|
RELEASE_TOKEN_FILE: /runner-secrets/release-token
|
|
GIT_CLIFF_VERSION: "2.12.0"
|
|
|
|
jobs:
|
|
preconditions:
|
|
name: Check Preconditions
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Validate token
|
|
run: |
|
|
if [ ! -r "${RELEASE_TOKEN_FILE}" ]; then
|
|
echo "Release token file not found at ${RELEASE_TOKEN_FILE}"
|
|
echo "This workflow requires the runner to have RELEASE_TOKEN configured."
|
|
exit 1
|
|
fi
|
|
if [ ! -s "${RELEASE_TOKEN_FILE}" ]; then
|
|
echo "Release token file is empty"
|
|
exit 1
|
|
fi
|
|
echo "Release token found"
|
|
|
|
changelog-and-pr:
|
|
name: Generate Changelog and Handle PR
|
|
runs-on: ubuntu-latest
|
|
needs: preconditions
|
|
if: github.ref_type == 'branch' && github.ref_name == github.event.repository.default_branch
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install git-cliff
|
|
run: |
|
|
curl -sSfL "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz
|
|
sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/
|
|
git-cliff --version
|
|
|
|
- name: Generate changelog
|
|
run: |
|
|
git-cliff --bump --unreleased --strip header > CHANGES.md
|
|
git-cliff --bump | sed "s/[[:space:]]\+$//" > CHANGELOG.md
|
|
|
|
- name: Get bumped version
|
|
id: version
|
|
run: |
|
|
VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "")
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
echo "${VERSION}" > VERSION
|
|
|
|
- name: Check for changes
|
|
id: check
|
|
run: |
|
|
LATEST=$(cat .version 2>/dev/null | jq -r '.version' 2>/dev/null || git describe --abbrev=0 --tags 2>/dev/null || echo '')
|
|
VERSION=$(cat VERSION)
|
|
if [ -n "${LATEST}" ] && [ "${VERSION}" = "${LATEST}" ]; then
|
|
echo "has_changes=false" >> $GITHUB_OUTPUT
|
|
else
|
|
echo "has_changes=true" >> $GITHUB_OUTPUT
|
|
fi
|
|
|
|
- name: Create or update release PR
|
|
if: steps.check.outputs.has_changes == 'true'
|
|
env:
|
|
REPOSITORY: ${{ github.repository }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
run: |
|
|
TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
|
|
VERSION=$(cat VERSION)
|
|
OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1)
|
|
REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2)
|
|
API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
|
|
|
|
# Fallback to main if DEFAULT_BRANCH is empty
|
|
BASE_BRANCH="${DEFAULT_BRANCH:-main}"
|
|
echo "Using base branch: ${BASE_BRANCH}"
|
|
|
|
TITLE="chore(release): prepare for ${VERSION}"
|
|
# Read CHANGES.md content and add note (jq --arg will handle JSON escaping)
|
|
CHANGES_CONTENT=$(cat CHANGES.md)
|
|
PR_NOTE="**Note:** Please use **Squash Merge** when merging this PR."
|
|
DESCRIPTION="${CHANGES_CONTENT}"$'\n\n---\n\n'"${PR_NOTE}"
|
|
|
|
# Delete existing next-release branch to start fresh (auto-closes any open PR)
|
|
echo "Checking for existing next-release branch..."
|
|
BRANCH_CHECK=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused -w "%{http_code}" -o /dev/null \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
"${API_URL}/branches/next-release")
|
|
if [ "${BRANCH_CHECK}" = "200" ]; then
|
|
echo "Deleting existing next-release branch..."
|
|
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X DELETE \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
"${API_URL}/branches/next-release"
|
|
echo "Branch deleted"
|
|
fi
|
|
|
|
# Prepare CHANGELOG.md content
|
|
CHANGELOG_CONTENT=$(base64 -w0 < CHANGELOG.md)
|
|
|
|
# Prepare .version content
|
|
VERSION_JSON=$(jq -n --arg v "${VERSION}" '{"version":$v}')
|
|
VERSION_CONTENT=$(echo "${VERSION_JSON}" | base64 -w0)
|
|
|
|
echo "Creating new next-release branch from ${BASE_BRANCH}..."
|
|
|
|
# Check if CHANGELOG.md exists on base branch to determine create vs update
|
|
CHANGELOG_SHA=$(curl -sf --retry 3 --retry-delay 2 --retry-connrefused \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
"${API_URL}/contents/CHANGELOG.md?ref=${BASE_BRANCH}" | jq -r '.sha // empty')
|
|
|
|
if [ -n "${CHANGELOG_SHA}" ]; then
|
|
echo "Updating CHANGELOG.md (exists on ${BASE_BRANCH}) on new branch..."
|
|
RESPONSE=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused -w "\n%{http_code}" -X PUT \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg content "${CHANGELOG_CONTENT}" \
|
|
--arg sha "${CHANGELOG_SHA}" \
|
|
--arg message "${TITLE}" \
|
|
--arg branch "${BASE_BRANCH}" \
|
|
--arg new_branch "next-release" \
|
|
'{content: $content, sha: $sha, message: $message, branch: $branch, new_branch: $new_branch}')" \
|
|
"${API_URL}/contents/CHANGELOG.md")
|
|
else
|
|
echo "Creating CHANGELOG.md on new branch..."
|
|
RESPONSE=$(curl -s --retry 3 --retry-delay 2 --retry-connrefused -w "\n%{http_code}" -X POST \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg content "${CHANGELOG_CONTENT}" \
|
|
--arg message "${TITLE}" \
|
|
--arg branch "${BASE_BRANCH}" \
|
|
--arg new_branch "next-release" \
|
|
'{content: $content, message: $message, branch: $branch, new_branch: $new_branch}')" \
|
|
"${API_URL}/contents/CHANGELOG.md")
|
|
fi
|
|
HTTP_CODE=$(echo "${RESPONSE}" | tail -1)
|
|
BODY=$(echo "${RESPONSE}" | sed '$d')
|
|
if [ "${HTTP_CODE}" -ge 400 ]; then
|
|
echo "Error with CHANGELOG.md: ${BODY}"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if .version exists on base branch
|
|
VERSION_SHA=$(curl -sf --retry 3 --retry-delay 2 --retry-connrefused \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
"${API_URL}/contents/.version?ref=${BASE_BRANCH}" | jq -r '.sha // empty')
|
|
|
|
if [ -n "${VERSION_SHA}" ]; then
|
|
echo "Updating .version on next-release branch..."
|
|
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X PUT \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg content "${VERSION_CONTENT}" \
|
|
--arg sha "${VERSION_SHA}" \
|
|
--arg message "${TITLE}" \
|
|
--arg branch "next-release" \
|
|
'{content: $content, sha: $sha, message: $message, branch: $branch}')" \
|
|
"${API_URL}/contents/.version"
|
|
else
|
|
echo "Creating .version on next-release branch..."
|
|
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X POST \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg content "${VERSION_CONTENT}" \
|
|
--arg message "${TITLE}" \
|
|
--arg branch "next-release" \
|
|
'{content: $content, message: $message, branch: $branch}')" \
|
|
"${API_URL}/contents/.version"
|
|
fi
|
|
|
|
echo "Creating new PR..."
|
|
echo "Waiting for branch to be ready..."
|
|
sleep 3
|
|
|
|
RESPONSE=$(curl -s --retry 3 --retry-delay 3 --retry-all-errors --retry-connrefused \
|
|
-w "\n%{http_code}" -X POST \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg title "${TITLE}" \
|
|
--arg body "${DESCRIPTION}" \
|
|
--arg head "next-release" \
|
|
--arg base "${DEFAULT_BRANCH}" \
|
|
'{title: $title, body: $body, head: $head, base: $base}')" \
|
|
"${API_URL}/pulls")
|
|
HTTP_CODE=$(echo "${RESPONSE}" | tail -1)
|
|
BODY=$(echo "${RESPONSE}" | sed '$d')
|
|
if [ "${HTTP_CODE}" -ge 400 ]; then
|
|
echo "Error creating PR (HTTP ${HTTP_CODE}): ${BODY}"
|
|
exit 1
|
|
fi
|
|
echo "PR created successfully"
|
|
|
|
create-release:
|
|
name: Create Release
|
|
runs-on: ubuntu-latest
|
|
needs: preconditions
|
|
if: |
|
|
github.ref_type == 'branch' &&
|
|
github.ref_name == github.event.repository.default_branch &&
|
|
inputs.tag_only != true
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install git-cliff
|
|
run: |
|
|
curl -sSfL "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz
|
|
sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/
|
|
git-cliff --version
|
|
|
|
- name: Generate changelog
|
|
run: git-cliff --bump --unreleased --strip header > CHANGES.md
|
|
|
|
- name: Get version
|
|
id: version
|
|
run: |
|
|
VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "")
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Create release
|
|
env:
|
|
REPOSITORY: ${{ github.repository }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
|
|
if [ ! -r .version ]; then
|
|
echo "Version file not found"
|
|
exit 0
|
|
fi
|
|
|
|
CURRENT_VERSION=$(cat .version 2>/dev/null | jq -r '.version')
|
|
LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '')
|
|
|
|
if [ -n "${LATEST}" ] && [ "${CURRENT_VERSION}" = "${LATEST}" ]; then
|
|
echo "Version ${CURRENT_VERSION} already exists"
|
|
exit 0
|
|
fi
|
|
|
|
OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1)
|
|
REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2)
|
|
API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
|
|
|
|
MESSAGE=$(cat CHANGES.md)
|
|
|
|
echo "Creating release ${VERSION}..."
|
|
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X POST \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg tag_name "${VERSION}" \
|
|
--arg name "${VERSION}" \
|
|
--arg body "${MESSAGE}" \
|
|
--arg target "${DEFAULT_BRANCH}" \
|
|
'{tag_name: $tag_name, name: $name, body: $body, target_commitish: $target}')" \
|
|
"${API_URL}/releases"
|
|
|
|
create-tag:
|
|
name: Create Tag
|
|
runs-on: ubuntu-latest
|
|
needs: preconditions
|
|
if: |
|
|
github.ref_type == 'branch' &&
|
|
github.ref_name == github.event.repository.default_branch &&
|
|
inputs.tag_only == true
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install git-cliff
|
|
run: |
|
|
curl -sSfL "https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" | tar xz
|
|
sudo mv "git-cliff-${GIT_CLIFF_VERSION}/git-cliff" /usr/local/bin/
|
|
git-cliff --version
|
|
|
|
- name: Get version
|
|
id: version
|
|
run: |
|
|
VERSION=$(git-cliff --bumped-version 2>/dev/null || echo "")
|
|
echo "version=${VERSION}" >> $GITHUB_OUTPUT
|
|
|
|
- name: Create tag
|
|
env:
|
|
REPOSITORY: ${{ github.repository }}
|
|
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
TOKEN=$(cat "${RELEASE_TOKEN_FILE}")
|
|
if [ ! -r .version ]; then
|
|
echo "Version file not found"
|
|
exit 0
|
|
fi
|
|
|
|
CURRENT_VERSION=$(cat .version 2>/dev/null | jq -r '.version')
|
|
LATEST=$(git describe --abbrev=0 --tags 2>/dev/null || echo '')
|
|
|
|
if [ -n "${LATEST}" ] && [ "${CURRENT_VERSION}" = "${LATEST}" ]; then
|
|
echo "Version ${CURRENT_VERSION} already exists"
|
|
exit 0
|
|
fi
|
|
|
|
OWNER=$(echo "${REPOSITORY}" | cut -d'/' -f1)
|
|
REPO=$(echo "${REPOSITORY}" | cut -d'/' -f2)
|
|
API_URL="${GITEA_URL}/api/v1/repos/${OWNER}/${REPO}"
|
|
|
|
echo "Creating tag ${VERSION}..."
|
|
curl -sf --retry 3 --retry-delay 2 --retry-connrefused -X POST \
|
|
-H "Authorization: token ${TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
--data "$(jq -n \
|
|
--arg tag_name "${VERSION}" \
|
|
--arg target "${DEFAULT_BRANCH}" \
|
|
--arg message "${VERSION}" \
|
|
'{tag_name: $tag_name, target: $target, message: $message}')" \
|
|
"${API_URL}/tags"
|