Skip to content

Commit efbbe69

Browse files
SONARIAC-2095 Add "Release Jira Version" Github Action (#8)
* add 'Release Jira Version' action * fix script name in action * update README
1 parent 08d7ae4 commit efbbe69

File tree

6 files changed

+285
-1
lines changed

6 files changed

+285
-1
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ A centralized collection of reusable GitHub Actions designed to streamline and a
66

77
* [**Create Jira Release Ticket**](create-jira-release-ticket/README.md): Automates the creation of an "Ask for release" ticket in Jira.
88
* [**Check Releasability Status**](check-releasability-status/README.md): Checks the releasability status of the master branch and extracts the version if successful.
9-
* [**Update Release ticket Status**](update-release-ticket-status/README.md): Updates the status of a Jira release ticket and can change its assignee.
9+
* [**Update Release ticket Status**](update-release-ticket-status/README.md): Updates the status of a Jira release ticket and can change its assignee.
10+
* [**Release Jira Version**](release-jira-version/README.md): Releases a Jira version and creates the next one.

check-releasability-status/action.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
name: 'Check releasability status'
22
description: 'Gets the status of the latest releasability check on the master branch and returns the version if it succeeded.'
3+
author: 'SonarSource'
34

45
inputs:
56
check-name:

release-jira-version/README.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# Release Jira Version Action
2+
3+
This GitHub Action automates releasing a version in Jira and then creating a new, subsequent version. It is useful for end-of-release-cycle workflows.
4+
5+
## How It Works
6+
7+
1. **Finds a Version**: It searches for a Jira version matching the `jira_release_name` input within the specified `project_key`.
8+
2. **Releases It**: It marks that version as "released" in Jira, setting the release date to the current day.
9+
3. **Creates the Next Version**:
10+
- If you provide a `new_version_name`, it creates a new version with that exact name.
11+
- If you don't, it attempts to increment the last number of the `jira_release_name` (e.g., `1.5.2` becomes `1.5.3`) and creates a new version with the incremented name.
12+
13+
## Prerequisites
14+
15+
The action requires that the repository has the `development/kv/data/jira` token configured in vault.
16+
This can be done using the SPEED self-service portal ([more info](https://xtranet-sonarsource.atlassian.net/wiki/spaces/Platform/pages/3553787989/Manage+Vault+Policy+-+SPEED)).
17+
18+
The [Jira API user](https://sonarsource.atlassian.net/jira/people/712020:9dcffe4d-55ee-4d69-b5d1-535c6dbd9cc4) must have the project role `Administrators` for the target project to manage releases.
19+
20+
## Inputs
21+
22+
| Input | Description | Required | Default |
23+
|---------------------|-----------------------------------------------------------------------------------------------------|----------|---------|
24+
| `jira_user` | The Jira user (email) for authentication. | `true` | |
25+
| `jira_token` | The Jira API token for authentication. **Store as a secret!** | `true` | |
26+
| `project_key` | The project key in Jira (e.g., `SONARIAC`). | `true` | |
27+
| `jira_release_name` | The exact name of the Jira version you want to release (e.g., `1.2.3`). | `true` | |
28+
| `new_version_name` | The name for the next version. If omitted, the action will auto-increment from `jira_release_name`. | `false` | `''` |
29+
| `use_sandbox` | Set to `false` to use the production Jira server. Recommended to test with `true` first. | `false` | `true` |
30+
31+
## Outputs
32+
33+
| Output | Description |
34+
|--------------------|-----------------------------------------------|
35+
| `new_version_name` | The name of the new version that was created. |
36+
37+
## Example Usage
38+
39+
This example demonstrates a manually triggered workflow that releases the provided version and creates a new one in the `SONARIAC` project.
40+
41+
```yaml
42+
name: Release Jira Version
43+
44+
on:
45+
workflow_dispatch:
46+
inputs:
47+
version_to_release:
48+
description: "Jira version to release"
49+
required: true
50+
next_version:
51+
description: "Next Jira version to create"
52+
required: false
53+
54+
jobs:
55+
release_in_jira:
56+
name: Release Jira Version
57+
runs-on: ubuntu-latest
58+
permissions:
59+
contents: read
60+
id-token: write
61+
62+
steps:
63+
- name: Get Jira Credentials from Vault
64+
id: secrets
65+
uses: SonarSource/vault-action-wrapper@v3
66+
with:
67+
secrets: |
68+
development/kv/data/jira user | JIRA_USER;
69+
development/kv/data/jira token | JIRA_TOKEN;
70+
71+
- name: Release and Create Next Version
72+
id: jira_release
73+
uses: SonarSource/release-github-actions/release-jira-version@master
74+
with:
75+
jira_user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
76+
jira_token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
77+
project_key: 'SONARIAC'
78+
jira_release_name: ${{ github.event.inputs.version_to_release }}
79+
new_version_name: ${{ github.event.inputs.next_version }}
80+
use_sandbox: true
81+
82+
- name: Echo Output
83+
run: |
84+
echo "Created new version: ${{ steps.jira_release.outputs.new_version_name }}"
85+
```

release-jira-version/action.yml

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: 'Release Jira Version'
2+
description: 'Releases a specified Jira version and creates a new one.'
3+
author: 'SonarSource'
4+
5+
inputs:
6+
jira_user:
7+
description: 'Jira user for authentication (e.g., email address).'
8+
required: true
9+
jira_token:
10+
description: 'Jira API token for authentication.'
11+
required: true
12+
project_key:
13+
description: 'The key of the Jira project (e.g., SONARIAC).'
14+
required: true
15+
jira_release_name:
16+
description: 'The name of the Jira version to release (e.g., 1.2.3).'
17+
required: true
18+
new_version_name:
19+
description: 'The name for the next version. If not provided, it will increment the minor version of the released version.'
20+
required: false
21+
default: ''
22+
use_sandbox:
23+
description: "Use the sandbox Jira server instead of production."
24+
required: false
25+
default: 'true'
26+
27+
outputs:
28+
new_version_name:
29+
description: 'The name of the new version that was created.'
30+
value: ${{ steps.run_python_script.outputs.new_version_name }}
31+
32+
runs:
33+
using: "composite"
34+
steps:
35+
- name: Set up Python
36+
uses: actions/setup-python@v5
37+
with:
38+
python-version: '3.8'
39+
40+
- name: Install dependencies
41+
shell: bash
42+
run: pip install -r ${{ github.action_path }}/requirements.txt
43+
44+
- name: Run Python Script to Release and Create Version
45+
id: run_python_script
46+
shell: bash
47+
run: |
48+
SANDBOX_FLAG=""
49+
if [[ "${{ inputs.use_sandbox }}" == "true" ]]; then
50+
SANDBOX_FLAG="--use-sandbox"
51+
fi
52+
53+
NEW_VERSION_FLAG=""
54+
if [[ -n "${{ inputs.new_version_name }}" ]]; then
55+
NEW_VERSION_FLAG="--new-version-name=${{ inputs.new_version_name }}"
56+
fi
57+
58+
python ${{ github.action_path }}/release_and_create_jira_version.py \
59+
--project-key="${{ inputs.project_key }}" \
60+
--jira-release-name="${{ inputs.jira_release_name }}" \
61+
${SANDBOX_FLAG} \
62+
${NEW_VERSION_FLAG} >> $GITHUB_OUTPUT
63+
env:
64+
JIRA_USER: ${{ inputs.jira_user }}
65+
JIRA_TOKEN: ${{ inputs.jira_token }}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
This script automates releasing a version in Jira and creating the next one.
6+
"""
7+
8+
import argparse
9+
import os
10+
import sys
11+
import datetime
12+
from jira import JIRA
13+
from jira.exceptions import JIRAError
14+
15+
# Jira server URLs
16+
JIRA_SANDBOX_URL = "https://sonarsource-sandbox-608.atlassian.net/"
17+
JIRA_PROD_URL = "https://sonarsource.atlassian.net/"
18+
19+
def eprint(*args, **kwargs):
20+
"""Prints messages to the standard error stream (stderr) for logging."""
21+
print(*args, file=sys.stderr, **kwargs)
22+
23+
24+
# noinspection DuplicatedCode
25+
def get_jira_instance(use_sandbox=False):
26+
"""
27+
Initializes and returns a JIRA client instance.
28+
Authentication is handled via environment variables.
29+
"""
30+
jira_user = os.environ.get('JIRA_USER')
31+
jira_token = os.environ.get('JIRA_TOKEN')
32+
33+
if not jira_user or not jira_token:
34+
eprint("Error: JIRA_USER and JIRA_TOKEN environment variables must be set.")
35+
sys.exit(1)
36+
37+
jira_url = JIRA_SANDBOX_URL if use_sandbox else JIRA_PROD_URL
38+
39+
eprint(f"Connecting to JIRA server at: {jira_url}")
40+
try:
41+
jira_client = JIRA(jira_url, basic_auth=(jira_user, jira_token))
42+
# Verify connection
43+
jira_client.server_info()
44+
eprint("JIRA authentication successful.")
45+
return jira_client
46+
except JIRAError as e:
47+
eprint(f"Error: JIRA authentication failed. Status: {e.status_code}")
48+
eprint(f"Response text: {e.text}")
49+
sys.exit(1)
50+
except Exception as e:
51+
eprint(f"An unexpected error occurred during JIRA connection: {e}")
52+
sys.exit(1)
53+
54+
def increment_version_string(version_name):
55+
"""
56+
Increments the last component of a version string (e.g., '1.2.3' -> '1.2.4').
57+
"""
58+
parts = version_name.split('.')
59+
try:
60+
parts[-1] = str(int(parts[-1]) + 1)
61+
return ".".join(parts)
62+
except (ValueError, IndexError):
63+
eprint(f"Error: Could not auto-increment version '{version_name}'. It does not seem to follow a standard x.y.z format.")
64+
sys.exit(1)
65+
66+
def main():
67+
"""Main function to orchestrate the release and creation process."""
68+
parser = argparse.ArgumentParser(
69+
description="Releases a Jira version and creates the next one.",
70+
formatter_class=argparse.ArgumentDefaultsHelpFormatter
71+
)
72+
parser.add_argument("--project-key", required=True, help="The key of the Jira project (e.g., SONARIAC).")
73+
parser.add_argument("--jira-release-name", required=True, help="The name of the version to release.")
74+
parser.add_argument("--new-version-name", default="", help="The name for the next version.")
75+
parser.add_argument('--use-sandbox', action='store_true', help="Use the sandbox Jira server.")
76+
args = parser.parse_args()
77+
78+
jira = get_jira_instance(args.use_sandbox)
79+
80+
eprint(f"Searching for version '{args.jira_release_name}' in project '{args.project_key}'...")
81+
try:
82+
versions = jira.project_versions(args.project_key)
83+
except JIRAError as e:
84+
eprint(f"Error: Could not fetch versions for project '{args.project_key}'. Status: {e.status_code}")
85+
sys.exit(1)
86+
87+
version_to_release = None
88+
for v in versions:
89+
if v.name == args.jira_release_name:
90+
version_to_release = v
91+
break
92+
93+
if not version_to_release:
94+
eprint(f"Error: Version '{args.jira_release_name}' not found in project '{args.project_key}'.")
95+
sys.exit(1)
96+
97+
if version_to_release.released:
98+
eprint(f"Warning: Version '{version_to_release.name}' is already released. Skipping release step.")
99+
else:
100+
eprint(f"Found version '{version_to_release.name}'. Releasing it now...")
101+
try:
102+
today = datetime.date.today().strftime('%Y-%m-%d')
103+
version_to_release.update(released=True, releaseDate=today)
104+
eprint(f"✅ Successfully released version '{version_to_release.name}'.")
105+
except JIRAError as e:
106+
eprint(f"Error: Failed to release version. Status: {e.status_code}, Text: {e.text}")
107+
sys.exit(1)
108+
109+
110+
if args.new_version_name:
111+
new_name = args.new_version_name
112+
eprint(f"Using provided name for new version: '{new_name}'.")
113+
else:
114+
new_name = increment_version_string(args.jira_release_name)
115+
eprint(f"Auto-incremented version name to: '{new_name}'.")
116+
117+
eprint(f"Creating new version '{new_name}'...")
118+
try:
119+
new_version = jira.create_version(name=new_name, project=args.project_key)
120+
eprint(f"✅ Successfully created new version '{new_version.name}'.")
121+
except JIRAError as e:
122+
if "A version with this name already exists" in e.text:
123+
eprint(f"Warning: Version '{new_name}' already exists. Skipping creation.")
124+
else:
125+
eprint(f"Error: Failed to create new version. Status: {e.status_code}, Text: {e.text}")
126+
sys.exit(1)
127+
128+
print(f"new_version_name={new_name}")
129+
130+
if __name__ == "__main__":
131+
main()

release-jira-version/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jira==3.8.0

0 commit comments

Comments
 (0)