From 7644a4bc49495e3626f81864a0303a990ada7f47 Mon Sep 17 00:00:00 2001 From: Patryk Bujna Date: Mon, 20 Jul 2026 11:25:41 +0200 Subject: [PATCH] Add reusable release workflows (prepare-release + release) Implements slice #4 of the Apius Splunk CI standard: the DECIDED commit-back release-PR flow (ADR-0004) plus tag-triggered publish (ADR-0004/ADR-0005). prepare-release.yml (workflow_call; inputs version, app_id): - Stamps app.conf [launcher] version and root pyproject.toml [project].version to the target version via `python -m splunk_app_ci stamp`, sourcing the packaging module the same way app-ci.yml does (checkout splunk-app-ci into .splunk-app-ci, run under PYTHONPATH=.splunk-app-ci). - Commits only the two stamped files onto a release/vX.Y.Z branch and opens a "Release vX.Y.Z" PR against main. Never writes to main directly; opening the PR runs the normal PR gate. Needs contents:write + pull-requests:write. release.yml (workflow_call; inputs app_id, version, cloud_gate=false; secrets SPLUNK_USER/SPLUNK_PASS): - Builds the Splunkbase package at the released version into dist/. - Runs Splunk AppInspect via the AppInspect API (splunk/appinspect-api-action @v3.0.5). Base (non-cloud) checks run with excluded_tags "manual,cloud" and BLOCK on errors (the action hardcodes failOnError=true). Cloud checks run with included_tags cloud in a separate step made ADVISORY via step-level continue-on-error: ${{ !inputs.cloud_gate }}, so cloud findings only block when cloud_gate=true. This two-step split is required because v3 of the action dropped the failOn* inputs. - Publishes a GitHub Release for the pushed tag with dist/*.tar.gz and auto-generated notes (gh release create --generate-notes). Splunkbase upload is out of scope (slice #6); the workflow stops after the Release step. README: document the two workflows and the human release flow (prepare-release -> merge Release PR -> push tag vX.Y.Z -> release publishes), with caller snippets and the "Allow Actions to create PRs" repo-setting note. --- .github/workflows/prepare-release.yml | 92 ++++++++++++++++++ .github/workflows/release.yml | 132 ++++++++++++++++++++++++++ README.md | 70 ++++++++++++++ 3 files changed, 294 insertions(+) create mode 100644 .github/workflows/prepare-release.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml new file mode 100644 index 0000000..2c18fa9 --- /dev/null +++ b/.github/workflows/prepare-release.yml @@ -0,0 +1,92 @@ +name: prepare-release + +# Reusable release-preparation workflow for Apius Splunk apps (ADR-0004: +# commit-back release-PR flow). It stamps the target version into the two +# source-of-truth files -- app.conf [launcher] version and the root +# pyproject.toml [project].version -- on a release/vX.Y.Z branch, then opens a +# "Release vX.Y.Z" PR against main. It never writes to main directly; opening +# the PR runs the normal PR gate (ci.yml). A human reviews and merges the PR so +# main always shows the released version. +# +# Human release flow (see also release.yml): +# 1. Dispatch the caller prepare-release workflow with the target version. +# 2. This workflow opens the "Release vX.Y.Z" PR; review and merge it. +# 3. Push the tag vX.Y.Z on the merged commit. +# 4. The release workflow (release.yml) packages, runs AppInspect and +# publishes the GitHub Release. +on: + workflow_call: + inputs: + version: + description: "Release version X.Y.Z (no leading v)." + required: true + type: string + app_id: + description: "Splunk app id; also the app directory containing default/app.conf." + required: true + type: string + python_version: + description: "Python version used for the stamping tooling." + required: false + type: string + default: "3.9" + +permissions: + contents: write + pull-requests: write + +jobs: + prepare-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + + - name: Fetch packaging tooling + uses: actions/checkout@v4 + with: + repository: apius-tech/splunk-app-ci + ref: main + path: .splunk-app-ci + + # Stamp both source-of-truth files in place so the repo always shows the + # released version once the PR merges (ADR-0004). + - name: Stamp version into app.conf + pyproject.toml + run: > + PYTHONPATH=.splunk-app-ci python -m splunk_app_ci stamp + --version "${{ inputs.version }}" + --app-conf "${{ inputs.app_id }}/default/app.conf" + --pyproject pyproject.toml + + - name: Configure git identity + run: | + git config user.name "Patryk Bujna" + git config user.email "patryk.bujna@apius.pl" + + # Commit only the two stamped files onto a release branch and open the PR. + # The .splunk-app-ci checkout is intentionally not staged. + - name: Commit and open release PR + env: + GH_TOKEN: ${{ github.token }} + run: | + branch="release/v${{ inputs.version }}" + git checkout -b "$branch" + git add "${{ inputs.app_id }}/default/app.conf" pyproject.toml + git commit -m "Release v${{ inputs.version }}: stamp app.conf + pyproject version" + git push -u origin "$branch" + gh pr create \ + --base main \ + --head "$branch" \ + --title "Release v${{ inputs.version }}" \ + --body "Automated release preparation for v${{ inputs.version }} (ADR-0004). + + Stamps to v${{ inputs.version }}: + - \`${{ inputs.app_id }}/default/app.conf\` \`[launcher] version\` + - root \`pyproject.toml\` \`[project].version\` + + After merge, push the tag \`v${{ inputs.version }}\` on the merged commit to trigger the release workflow (package -> AppInspect API -> GitHub Release)." diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..30e29a7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,132 @@ +name: release + +# Reusable release/publish workflow for Apius Splunk apps (ADR-0004 / ADR-0005). +# Triggered by a caller on a pushed tag vX.Y.Z (on the merged release commit), +# it builds the Splunkbase package at the released version, runs Splunk +# AppInspect via the AppInspect API, and publishes a GitHub Release with the +# packaged .tar.gz and auto-generated notes. +# +# AppInspect (ADR-0005): base (non-cloud) errors/failures BLOCK the release; +# cloud findings are ADVISORY by default and only block when cloud_gate=true. +# The base and cloud vettings run as two steps so blocking can be controlled +# per-tag-set: the base step always blocks (the action's failOnError is +# hardcoded true), while the cloud step is made advisory via step-level +# continue-on-error unless cloud_gate is set. +# +# Splunkbase upload is deliberately out of scope here (that is slice #6); this +# workflow stops after the GitHub Release step. +# +# Human release flow (see also prepare-release.yml): +# 1. Dispatch prepare-release with the target version; merge the "Release +# vX.Y.Z" PR so main shows the released version. +# 2. Push the tag vX.Y.Z on the merged commit -> this workflow runs. +on: + workflow_call: + inputs: + app_id: + description: "Splunk app id; also the app directory to package/inspect." + required: true + type: string + version: + description: "Release version X.Y.Z (no leading v; caller strips it from the tag)." + required: true + type: string + cloud_gate: + description: "When true, AppInspect cloud findings block the release; otherwise advisory." + required: false + type: boolean + default: false + python_version: + description: "Python version used for the packaging tooling." + required: false + type: string + default: "3.9" + secrets: + SPLUNK_USER: + description: "splunk.com service-account user for the AppInspect API." + required: true + SPLUNK_PASS: + description: "splunk.com service-account password for the AppInspect API." + required: true + +permissions: + contents: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ inputs.python_version }} + + - name: Fetch packaging tooling + uses: actions/checkout@v4 + with: + repository: apius-tech/splunk-app-ci + ref: main + path: .splunk-app-ci + + # Build the Splunkbase artifact at the released version into dist/. The + # AppInspect API action globs the directory, so dist/ holds only this + # single tarball. + - name: Build package + run: > + PYTHONPATH=.splunk-app-ci python -m splunk_app_ci package + --app-dir "${{ inputs.app_id }}" + --version "${{ inputs.version }}" + --dest dist + + # Base (non-cloud) vetting. failOnError is hardcoded true in the action, + # so any error/failure here BLOCKS the release. + - name: AppInspect API (base checks, blocking) + uses: splunk/appinspect-api-action@v3.0.5 + with: + username: ${{ secrets.SPLUNK_USER }} + password: ${{ secrets.SPLUNK_PASS }} + app_path: dist + excluded_tags: "manual,cloud" + + - name: Upload base AppInspect report + if: always() + uses: actions/upload-artifact@v4 + with: + name: appinspect-base-report + path: AppInspect_response.html + if-no-files-found: warn + + # Cloud vetting. included_tags: cloud runs only the cloud checks. The + # step is advisory (continue-on-error) unless cloud_gate is true, in + # which case cloud findings block the release. + - name: AppInspect API (cloud vetting, advisory unless cloud_gate) + continue-on-error: ${{ !inputs.cloud_gate }} + uses: splunk/appinspect-api-action@v3.0.5 + with: + username: ${{ secrets.SPLUNK_USER }} + password: ${{ secrets.SPLUNK_PASS }} + app_path: dist + included_tags: cloud + + - name: Upload cloud AppInspect report + if: always() + uses: actions/upload-artifact@v4 + with: + name: appinspect-cloud-report + path: AppInspect_response.html + if-no-files-found: warn + + # Publish the GitHub Release for the pushed tag with the packaged + # artifact and auto-generated notes. Reached only when the base checks + # (and, under cloud_gate, cloud checks) passed. + - name: Create GitHub Release + env: + GH_TOKEN: ${{ github.token }} + run: > + gh release create "v${{ inputs.version }}" + dist/*.tar.gz + --title "v${{ inputs.version }}" + --generate-notes diff --git a/README.md b/README.md index c403307..bd12495 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,76 @@ python -m splunk_app_ci stamp --version X.Y.Z \ directory is the app id, with `app.conf` version stamped, vendored `lib/` retained, and tooling/VCS artifacts excluded. +## Release workflows + +Two reusable workflows implement the commit-back release-PR flow (ADR-0004), +each with a thin caller in the app repo: + +- `.github/workflows/prepare-release.yml` (`workflow_call`; inputs `version`, + `app_id`) — stamps `app.conf [launcher] version` and root `pyproject.toml + [project].version` to `version` on a `release/vX.Y.Z` branch and opens a + "Release vX.Y.Z" PR. It never writes to main directly; opening the PR runs + the normal PR gate. +- `.github/workflows/release.yml` (`workflow_call`; inputs `app_id`, `version`, + `cloud_gate` default `false`; secrets `SPLUNK_USER`/`SPLUNK_PASS`) — packages + the app at `version`, runs Splunk AppInspect via the **AppInspect API** + (`splunk/appinspect-api-action`), and publishes a GitHub Release with the + `.tar.gz` and auto-generated notes. Base (non-cloud) errors/failures block; + cloud findings are advisory unless `cloud_gate=true` (ADR-0005). Splunkbase + upload is a separate slice and is not done here. + +### Human release flow + +1. Dispatch the app's `prepare-release` caller with the target version + (`X.Y.Z`). This opens the "Release vX.Y.Z" PR. +2. Review and merge the Release PR — main now shows the released version. +3. Push the tag `vX.Y.Z` on the merged commit + (`git tag vX.Y.Z && git push origin vX.Y.Z`). +4. The `release` workflow runs: package -> AppInspect API -> GitHub Release. + +Caller examples (in an app repo): + +```yaml +# .github/workflows/prepare-release.yml +name: prepare-release +on: + workflow_dispatch: + inputs: + version: { description: "X.Y.Z", required: true, type: string } +jobs: + prepare-release: + uses: apius-tech/splunk-app-ci/.github/workflows/prepare-release.yml@main + with: + version: ${{ inputs.version }} + app_id: +``` + +```yaml +# .github/workflows/release.yml +name: release +on: + push: + tags: ["v*"] +jobs: + version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.strip.outputs.version }} + steps: + - id: strip + run: echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" + release: + needs: version + uses: apius-tech/splunk-app-ci/.github/workflows/release.yml@main + with: + app_id: + version: ${{ needs.version.outputs.version }} + secrets: inherit +``` + +The `prepare-release` job needs the repo setting "Allow GitHub Actions to +create and approve pull requests" enabled so it can open the Release PR. + ## Development ```