diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e0c899d --- /dev/null +++ b/.github/workflows/release.yml @@ -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/` 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 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"