Skip to content
Merged
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
183 changes: 183 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
name: Release automation

# End-to-end docs-release pipeline for salt-install-guide.
# Polls saltstack/salt every 10 min. On a new point release (X.Y where Y > 0):
# 1. Open `topic/release/<version>` PR with version-bump edits
# 2. Wait for required PR checks
# 3. Merge
# 4. Push annotated tag (minor-bumped) so build-sphinx-docs.yml can build
#
# `.0` (LTS-bump) releases are skipped — they require manually-curated edits
# to lifecycle dates, downloads, etc.
#
# Caveat: PRs created with the default GITHUB_TOKEN do not trigger workflows
# that run on `pull_request`. If you want PR-level CI to run, replace
# GH_TOKEN with a PAT or GitHub App token stored as a secret.

on:
schedule:
- cron: '*/10 * * * *'
workflow_dispatch:
inputs:
salt_version:
description: 'Override salt version (e.g. 3008.1). Blank = auto-detect.'
required: false
type: string

permissions:
contents: write
pull-requests: write

concurrency:
group: release-automation
cancel-in-progress: false

jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Detect target salt version
id: detect
env:
GH_TOKEN: ${{ github.token }}
INPUT_VERSION: ${{ inputs.salt_version }}
run: |
set -euo pipefail
if [[ -n "${INPUT_VERSION:-}" ]]; then
v="$INPUT_VERSION"
else
v=$(gh api repos/saltstack/salt/releases/latest --jq '.tag_name')
fi
v=${v#v}
IFS='.' read -r maj min <<<"$v"
if [[ "${min:-0}" -eq 0 ]]; then
echo "::notice::skipping $v — .0 LTS bumps require manual editing"
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "salt_version=$v" >> "$GITHUB_OUTPUT"
echo "branch=topic/release/$v" >> "$GITHUB_OUTPUT"
echo "detected $v"
fi

- name: Look up existing PR for this version
id: lookup
if: steps.detect.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ steps.detect.outputs.branch }}
run: |
set -euo pipefail
pr=$(gh pr list --state all --head "$BRANCH" \
--json number,state,mergedAt --jq '.[0] // empty')
if [[ -n "$pr" ]]; then
echo "pr_number=$(echo "$pr" | jq -r '.number')" >> "$GITHUB_OUTPUT"
echo "pr_state=$(echo "$pr" | jq -r '.state')" >> "$GITHUB_OUTPUT"
[[ "$(echo "$pr" | jq -r '.mergedAt // empty')" != "" ]] \
&& echo "merged=true" >> "$GITHUB_OUTPUT"
fi
echo "lookup: $pr"

- name: Check if release already complete
id: done_check
if: steps.detect.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
SALT: ${{ steps.detect.outputs.salt_version }}
run: |
set -euo pipefail
# Canonical end-state marker: the repo's tag annotation convention is
# "v<salt_version> release" (e.g. "v3008.0 release"). If the latest
# tag already carries this annotation for our salt_version, the
# release is done and downstream steps short-circuit.
latest=$(gh api "repos/$REPO/tags" --jq '.[0].name // empty')
if [[ -n "$latest" ]]; then
obj=$(gh api "repos/$REPO/git/refs/tags/$latest" --jq '.object')
if [[ "$(echo "$obj" | jq -r '.type')" == "tag" ]]; then
sha=$(echo "$obj" | jq -r '.sha')
msg=$(gh api "repos/$REPO/git/tags/$sha" --jq '.message')
if [[ "$msg" == *"v${SALT} release"* ]]; then
echo "::notice::latest tag $latest annotated for Salt $SALT — done"
echo "done=true" >> "$GITHUB_OUTPUT"
fi
fi
fi

- name: Checkout main
if: steps.detect.outputs.skip != 'true' && steps.lookup.outputs.pr_number == ''
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0

- name: Generate edits and open PR
if: steps.detect.outputs.skip != 'true' && steps.lookup.outputs.pr_number == ''
id: create
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ steps.detect.outputs.branch }}
SALT: ${{ steps.detect.outputs.salt_version }}
run: |
set -euo pipefail
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git checkout -b "$BRANCH"

old=$(jq -r '.versions | to_entries[]
| select(.value.default == true).key' \
tools/supported-versions.json)
new="$SALT"
echo "renaming key $old -> $new in tools/supported-versions.json"
sed -i "s/\"${old//./\\.}\":/\"${new}\":/" tools/supported-versions.json

if git diff --quiet; then
echo "::error::no changes produced for $SALT — supported-versions.json may already be at $SALT"
exit 1
fi
git add -A
git commit -m "Update to Salt $SALT"
git push -u origin "$BRANCH"
gh pr create --base main --head "$BRANCH" \
--title "Update to Salt $SALT" \
--body "Automated update of supported-versions.json for Salt $SALT point release."

- name: Wait for required PR checks
if: steps.detect.outputs.skip != 'true' && steps.lookup.outputs.merged != 'true' && steps.done_check.outputs.done != 'true'
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ steps.detect.outputs.branch }}
run: |
set -euo pipefail
sleep 15 # let any checks register
gh pr checks "$BRANCH" --watch --required || true

- name: Merge PR
if: steps.detect.outputs.skip != 'true' && steps.lookup.outputs.merged != 'true' && steps.done_check.outputs.done != 'true'
env:
GH_TOKEN: ${{ github.token }}
BRANCH: ${{ steps.detect.outputs.branch }}
run: |
set -euo pipefail
gh pr merge "$BRANCH" --merge

- name: Push annotated release tag
if: steps.detect.outputs.skip != 'true' && steps.done_check.outputs.done != 'true'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
SALT: ${{ steps.detect.outputs.salt_version }}
run: |
set -euo pipefail
latest=$(gh api "repos/$REPO/tags" --jq '.[0].name')
v=${latest#v}
IFS='.' read -r maj min _ <<<"$v"
next="v${maj}.$((min + 1)).0"
sleep 5
sha=$(gh api "repos/$REPO/commits/main" --jq '.sha')
tag_sha=$(gh api -X POST "repos/$REPO/git/tags" \
-f tag="$next" \
-f message="v${SALT} release" \
-f object="$sha" -f type=commit --jq '.sha')
gh api -X POST "repos/$REPO/git/refs" \
-f ref="refs/tags/$next" -f sha="$tag_sha" >/dev/null
echo "pushed $next -> $sha"
Loading