Skip to content

Commit 123a106

Browse files
GHA-174 Add releasability check to automated release workflow (#83)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2a5ddd6 commit 123a106

6 files changed

Lines changed: 159 additions & 14 deletions

File tree

.github/workflows/automated-release.yml

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,11 @@ on:
116116
required: false
117117
type: boolean
118118
default: true
119+
check-releasability:
120+
description: "Check the releasability status after freezing the branch"
121+
required: false
122+
type: boolean
123+
default: true
119124
slack-channel:
120125
description: "Slack channel for notifications"
121126
required: false
@@ -170,19 +175,91 @@ jobs:
170175
echo "- Locked branch pattern \`$BRANCH\` to prevent changes during the release." >> $GITHUB_STEP_SUMMARY
171176
echo "- Notifications sent to Slack channel: \`$SLACK_CHANNEL\`." >> $GITHUB_STEP_SUMMARY
172177
178+
# This job executes the releasability check on the branch to ensure it is ready for release.
179+
# Running this check early prevents unnecessary work (like creating REL tickets) if the release cannot proceed.
180+
check-releasability:
181+
name: Check Releasability
182+
if: ${{ inputs.check-releasability && !cancelled() }}
183+
needs: [ freeze-branch ]
184+
runs-on: ${{ inputs.runner-environment }}
185+
permissions:
186+
id-token: write
187+
contents: read
188+
statuses: read
189+
outputs:
190+
release-version: ${{ steps.get-version.outputs.release-version }}
191+
steps:
192+
- name: Get Release Version
193+
id: get-version
194+
uses: SonarSource/release-github-actions/get-release-version@v1
195+
with:
196+
branch: ${{ inputs.branch }}
197+
198+
- uses: SonarSource/gh-action_releasability@v3
199+
id: releasability
200+
with:
201+
branch: ${{ github.ref_name }}
202+
commit-sha: ${{ github.sha }}
203+
organization: ${{ github.repository_owner }}
204+
repository: ${{ github.event.repository.name }}
205+
version: ${{ steps.get-version.outputs.release-version }}
206+
207+
- name: Summary
208+
if: ${{ always() && inputs.verbose }}
209+
shell: bash
210+
env:
211+
BRANCH: ${{ inputs.branch }}
212+
VERSION: ${{ steps.get-version.outputs.release-version }}
213+
CHECK_OUTCOME: ${{ steps.releasability.outcome }}
214+
CHECK_DEPENDENCIES: ${{ steps.releasability.outputs.releasabilityCheckDependencies }}
215+
CHECK_QA: ${{ steps.releasability.outputs.releasabilityQA }}
216+
CHECK_JIRA: ${{ steps.releasability.outputs.releasabilityJira }}
217+
CHECK_PEACHEE: ${{ steps.releasability.outputs.releasabilityCheckPeacheeLanguagesStatistics }}
218+
CHECK_QUALITY_GATE: ${{ steps.releasability.outputs.releasabilityQualityGate }}
219+
CHECK_PARENT_POM: ${{ steps.releasability.outputs.releasabilityParentPOM }}
220+
CHECK_GITHUB: ${{ steps.releasability.outputs.releasabilityGitHub }}
221+
CHECK_MANIFEST: ${{ steps.releasability.outputs.releasabilityCheckManifestValues }}
222+
CHECK_LICENSES: ${{ steps.releasability.outputs.releasabilityCheckLicenses }}
223+
run: |
224+
if [ "$CHECK_OUTCOME" = "success" ]; then
225+
echo "## ✅ Releasability Check" >> $GITHUB_STEP_SUMMARY
226+
else
227+
echo "## ❌ Releasability Check Failed" >> $GITHUB_STEP_SUMMARY
228+
fi
229+
echo "" >> $GITHUB_STEP_SUMMARY
230+
echo "### What happened" >> $GITHUB_STEP_SUMMARY
231+
echo "- Executed releasability check on branch \`$BRANCH\` for version \`$VERSION\`." >> $GITHUB_STEP_SUMMARY
232+
if [ "$CHECK_OUTCOME" != "success" ]; then
233+
echo "" >> $GITHUB_STEP_SUMMARY
234+
echo "### Failed Checks" >> $GITHUB_STEP_SUMMARY
235+
[ "$CHECK_DEPENDENCIES" = "FAILED" ] && echo "- ❌ CheckDependencies" >> $GITHUB_STEP_SUMMARY
236+
[ "$CHECK_QA" = "FAILED" ] && echo "- ❌ QA" >> $GITHUB_STEP_SUMMARY
237+
[ "$CHECK_JIRA" = "FAILED" ] && echo "- ❌ Jira" >> $GITHUB_STEP_SUMMARY
238+
[ "$CHECK_PEACHEE" = "FAILED" ] && echo "- ❌ CheckPeacheeLanguagesStatistics" >> $GITHUB_STEP_SUMMARY
239+
[ "$CHECK_QUALITY_GATE" = "FAILED" ] && echo "- ❌ QualityGate" >> $GITHUB_STEP_SUMMARY
240+
[ "$CHECK_PARENT_POM" = "FAILED" ] && echo "- ❌ ParentPOM" >> $GITHUB_STEP_SUMMARY
241+
[ "$CHECK_GITHUB" = "FAILED" ] && echo "- ❌ GitHub" >> $GITHUB_STEP_SUMMARY
242+
[ "$CHECK_MANIFEST" = "FAILED" ] && echo "- ❌ CheckManifestValues" >> $GITHUB_STEP_SUMMARY
243+
[ "$CHECK_LICENSES" = "FAILED" ] && echo "- ❌ CheckLicenses" >> $GITHUB_STEP_SUMMARY
244+
fi
245+
173246
# This step determines the release version, Jira version name, and gathers release notes.
174247
# It sets up the necessary outputs for subsequent steps.
175248
# These outputs include the release version, Jira version name, release notes, Jira release notes, and Jira release URL.
176249
prepare-release:
177250
name: Prepare Release
178-
needs: [ freeze-branch ]
251+
needs: [ freeze-branch, check-releasability ]
252+
if: |
253+
!cancelled() &&
254+
(needs.freeze-branch.result == 'success' || needs.freeze-branch.result == 'skipped') &&
255+
(needs.check-releasability.result == 'success' || needs.check-releasability.result == 'skipped')
179256
runs-on: ${{ inputs.runner-environment }}
180257
permissions:
181258
statuses: read
182259
contents: read
183260
id-token: write
184261
outputs:
185-
release-version: ${{ steps.get-release-version.outputs.release-version }}
262+
release-version: ${{ needs.check-releasability.outputs.release-version || steps.get-release-version.outputs.release-version }}
186263
jira-version-name: ${{ steps.get-jira-version.outputs.jira-version-name }}
187264
release-notes: ${{ inputs.release-notes != '' && inputs.release-notes || steps.get-jira-release-notes.outputs.release-notes }}
188265
jira-release-notes: ${{ inputs.release-notes != '' && inputs.release-notes || steps.get-jira-release-notes.outputs.jira-release-notes }}
@@ -191,6 +268,7 @@ jobs:
191268
steps:
192269
- name: Get Release Version
193270
id: get-release-version
271+
if: ${{ needs.check-releasability.result == 'skipped' }}
194272
uses: SonarSource/release-github-actions/get-release-version@v1
195273
with:
196274
branch: ${{ inputs.branch }}
@@ -228,7 +306,7 @@ jobs:
228306
fi
229307
echo "" >> $GITHUB_STEP_SUMMARY
230308
echo "### Results" >> $GITHUB_STEP_SUMMARY
231-
echo "- Release version: \`${{ steps.get-release-version.outputs.release-version }}\`." >> $GITHUB_STEP_SUMMARY
309+
echo "- Release version: \`${{ needs.check-releasability.outputs.release-version || steps.get-release-version.outputs.release-version }}\`." >> $GITHUB_STEP_SUMMARY
232310
echo "- Jira version name: \`${{ steps.get-jira-version.outputs.jira-version-name }}\`." >> $GITHUB_STEP_SUMMARY
233311
234312
# This step creates a Jira release ticket using the prepared release information.
@@ -563,7 +641,7 @@ jobs:
563641
name: Release Results
564642
runs-on: ${{ inputs.runner-environment }}
565643
if: always()
566-
needs: [ prepare-release, publish-github-release, create-release-ticket, release-in-jira, create-integration-tickets, update-analyzers ]
644+
needs: [ check-releasability, prepare-release, publish-github-release, create-release-ticket, release-in-jira, create-integration-tickets, update-analyzers ]
567645
env:
568646
RELEASE_PROCESS: ${{ inputs.release-process != '' && inputs.release-process || 'https://xtranet-sonarsource.atlassian.net/wiki/spaces/CSD/pages/4325048388/Release+Instructions+-+Cloud+Security' }}
569647
steps:
@@ -580,7 +658,7 @@ jobs:
580658
SQS_PR_URL: ${{ needs.update-analyzers.outputs.sqs-pull-request-url || 'not created' }}
581659
SQC_PR_URL: ${{ needs.update-analyzers.outputs.sqc-pull-request-url || 'not created' }}
582660
run: |
583-
ALL_SUCCESS=$(echo '${{ toJson(needs) }}' | jq -r 'to_entries | all(.value.result == "success")')
661+
ALL_SUCCESS=$(echo '${{ toJson(needs) }}' | jq -r 'to_entries | all(.value.result == "success" or .value.result == "skipped")')
584662
585663
if [[ "$ALL_SUCCESS" == "true" ]]; then
586664
echo "# 🎉 Release Successful" >> $GITHUB_STEP_SUMMARY

CLAUDE.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
66

77
This is a collection of reusable GitHub Actions for automating SonarSource analyzer releases. Actions handle Jira integration (tickets, versions, release notes), GitHub releases, cross-repository updates, and Slack notifications.
88

9+
## Jira Project
10+
11+
Related Jira tickets for this project are tracked in the **GHA** (GitHub Automation) project. When available, use the Jira MCP to access ticket details (e.g., `GHA-123`).
12+
913
## Branching
1014

1115
**Important:** Changes must always be made on a feature branch, never directly on `master`.
@@ -14,6 +18,14 @@ This is a collection of reusable GitHub Actions for automating SonarSource analy
1418
- Adapt `<feature-name>` based on the task/prompt (use lowercase, hyphen-separated)
1519
- If already on a feature branch, do not create a new branch—continue working on the current branch
1620

21+
## Documentation
22+
23+
**Important:** When making any code changes, check if the related README or documentation needs to be updated. Each action has its own `README.md`, and workflow documentation is in `docs/`. Keep documentation in sync with code changes.
24+
25+
When creating a new action:
26+
- Add a `README.md` to the action's directory documenting inputs, outputs, and usage
27+
- Update the main `README.md` at the repository root to link to the new action
28+
1729
## Testing
1830

1931
**Important:** When making any code changes, always check if there are related tests that need to be updated. Always run the tests after making changes to ensure nothing is broken.
@@ -95,3 +107,17 @@ env:
95107
INPUT_BRANCH: ${{ inputs.branch }}
96108
run: echo "$INPUT_BRANCH"
97109
```
110+
111+
### Pinning External Actions
112+
113+
**Important:** All GitHub Actions from outside the SonarSource organization must be pinned to a full commit SHA (not a tag). Add a comment with the version number for readability.
114+
115+
```yaml
116+
# Bad - using tag
117+
- uses: actions/checkout@v4
118+
119+
# Good - pinned to commit SHA with version comment
120+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
121+
```
122+
123+
This prevents tag mutation attacks where a malicious actor could change what code a tag points to.

check-releasability-status/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ This action requires the `statuses: read` permission for the GitHub token to acc
2424

2525
## Outputs
2626

27-
This action has no outputs.
27+
| Output | Description |
28+
|----------------------------|--------------------------------------------------------------------------|
29+
| `releasability-state` | The state of the Releasability status (e.g., `success`, `failure`, `pending`) |
30+
| `releasability-description`| The description of the Releasability status |
31+
32+
These outputs are always set (even on failure), allowing subsequent steps to access the releasability information for reporting or debugging purposes.
2833

2934
## Usage
3035

check-releasability-status/action.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,19 @@ inputs:
1616
required: false
1717
default: 'true'
1818

19+
outputs:
20+
releasability-state:
21+
description: 'The state of the Releasability status (e.g., success, failure, pending)'
22+
value: ${{ steps.check.outputs.releasability-state }}
23+
releasability-description:
24+
description: 'The description of the Releasability status'
25+
value: ${{ steps.check.outputs.releasability-description }}
26+
1927
runs:
2028
using: 'composite'
2129
steps:
2230
- name: Check releasability status
31+
id: check
2332
shell: bash
2433
env:
2534
GITHUB_TOKEN: ${{ inputs.github-token }}
@@ -40,6 +49,10 @@ runs:
4049
RELEASABILITY_STATE=$(echo "$STATUS_RESPONSE" | jq -r '.statuses[] | select(.context == "Releasability") | .state')
4150
RELEASABILITY_DESCRIPTION=$(echo "$STATUS_RESPONSE" | jq -r '.statuses[] | select(.context == "Releasability") | .description')
4251
52+
# Output the state and description for use in subsequent steps
53+
echo "releasability-state=$RELEASABILITY_STATE" >> $GITHUB_OUTPUT
54+
echo "releasability-description=$RELEASABILITY_DESCRIPTION" >> $GITHUB_OUTPUT
55+
4356
if [ -z "$RELEASABILITY_STATE" ] || [ "$RELEASABILITY_STATE" = "null" ]; then
4457
echo "❌ Could not find Releasability status on $BRANCH_NAME branch"
4558
exit 1

docs/AUTOMATED_RELEASE.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,21 @@ This reusable GitHub Actions workflow automates the end-to-end release process a
77
The workflow orchestrates these steps:
88

99
1. Optionally freeze (lock) the target branch at the start of the release
10-
2. Determine the release version and Jira version name
11-
3. Optionally generate Jira release notes if not provided
12-
4. Create a Jira release ticket
13-
5. Publish a GitHub release (draft or final)
14-
6. Release the current Jira version and create the next version in Jira
15-
7. Optionally create integration tickets (SLVS, SLVSCODE, SLE, SLI, SQC, SQS)
16-
8. Optionally open analyzer update PRs in SQS and SQC
17-
9. Optionally post per-job and final workflow summaries when `verbose` is enabled
10+
2. Check releasability status on the branch (enabled by default)
11+
3. Determine the release version and Jira version name
12+
4. Optionally generate Jira release notes if not provided
13+
5. Create a Jira release ticket
14+
6. Publish a GitHub release (draft or final)
15+
7. Release the current Jira version and create the next version in Jira
16+
8. Optionally create integration tickets (SLVS, SLVSCODE, SLE, SLI, SQC, SQS)
17+
9. Optionally open analyzer update PRs in SQS and SQC
18+
10. Optionally post per-job and final workflow summaries when `verbose` is enabled
1819

1920
## Dependencies
2021

2122
This workflow composes several actions from this repository:
2223

24+
- `SonarSource/gh-action_releasability/releasability-status` (external action for releasability checks)
2325
- `SonarSource/release-github-actions/get-release-version`
2426
- `SonarSource/release-github-actions/get-jira-version`
2527
- `SonarSource/release-github-actions/get-jira-release-notes`
@@ -60,6 +62,7 @@ This workflow composes several actions from this repository:
6062
| `release-process` | Release process documentation URL | No | General page |
6163
| `verbose` | When `true`, posts per-job summaries and a final run summary | No | `false` |
6264
| `freeze-branch` | When `true`, locks the target branch during the release and unlocks it after publishing | No | `true` |
65+
| `check-releasability` | When `true`, verifies the releasability status on the branch before proceeding | No | `true` |
6366
| `slack-channel` | Slack channel to notify when locking/unlocking the branch | No | - |
6467

6568
## Outputs
@@ -121,6 +124,10 @@ jobs:
121124
122125
## Notes
123126
127+
- When `check-releasability: true` (default), the workflow will:
128+
- Execute a releasability check on the specified branch immediately after freezing (using `SonarSource/gh-action_releasability@v3`)
129+
- Update the commit status with the latest releasability results
130+
- Fail early if the releasability check does not pass, preventing unnecessary work (like creating REL tickets)
124131
- When `freeze-branch: true`, the workflow will:
125132
- Lock the specified branch at the start of the release
126133
- Proceed with the release steps

docs/SETUP_AUTOMATED_RELEASE.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ For detailed workflow documentation (inputs, outputs, behavior), see [AUTOMATED_
88

99
Before the workflow will work, complete these steps:
1010

11+
### Releasability Status
12+
13+
- [ ] **Ensure releasability checks are configured** for your repository. The workflow verifies releasability status on the branch before proceeding with the release. This prevents creating REL tickets and other artifacts if the build is not releasable.
14+
1115
### Jira Configuration
1216

1317
- [ ] **Add `Jira Tech User GitHub` as Administrator** on your Jira project (required to create/release versions)
@@ -296,3 +300,15 @@ Specify a different runner:
296300
```yaml
297301
runner-environment: "sonar-s"
298302
```
303+
304+
### Releasability Check
305+
306+
The workflow checks releasability status by default. To disable or customize:
307+
```yaml
308+
# Disable releasability check entirely
309+
check-releasability: false
310+
311+
# Check releasability but allow failed optional checks
312+
check-releasability: true
313+
with-optional-releasability-checks: false
314+
```

0 commit comments

Comments
 (0)