Skip to content

Commit 234f272

Browse files
SONARIAC-2156 Add "Update Integration Tickets" Github Action (#10)
* SONARIAC-2156 add 'Update Integration Tickets' Github Action * fix typo in readme example * fix review issues * put back project key inputs
1 parent b4a4a80 commit 234f272

File tree

5 files changed

+270
-0
lines changed

5 files changed

+270
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ A centralized collection of reusable GitHub Actions designed to streamline and a
99
* [**Update Release ticket Status**](update-release-ticket-status/README.md): Updates the status of a Jira release ticket and can change its assignee.
1010
* [**Publish GitHub Release**](publish-github-release/README.md): Publishes a GitHub Release with notes fetched from Jira or provided directly.
1111
* [**Release Jira Version**](release-jira-version/README.md): Releases a Jira version and creates the next one.
12+
* [**Update integration tickets**](update-integration-tickets/README.md): Finds and optionally updates SQS and SC integration tickets.
13+

update-integration-tickets/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Update integrations tickets Action
2+
3+
This GitHub Action automates the process of finding and updating Jira integration tickets.
4+
5+
The action finds both a SonarQube (SQS) integration ticket and a SonarCloud (SC) integration ticket by searching for tickets linked to a specified release ticket. It can then optionally update the `fixVersions` field of the found SQS ticket. If updating the `fixVersions` fails (e.g., due to a non-existent version), it will issue a warning without failing the action.
6+
7+
## Prerequisites
8+
9+
The action requires that the repository has the `development/kv/data/jira` token configured in vault.
10+
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)).
11+
12+
## Inputs
13+
14+
| Input | Description | Required | Default |
15+
|----------------------|----------------------------------------------------------------------------------------------------------------|----------|---------|
16+
| `jira_user` | The Jira user (email) for authentication. | `true` | |
17+
| `jira_token` | The Jira API token for authentication. | `true` | |
18+
| `release_ticket_key` | The key of the release ticket (e.g., `REL-1234`) to find the linked integration tickets from. | `true` | |
19+
| `sqs_project_key` | The Jira project key to search for the linked SQS integration ticket. | `false` | `SONAR` |
20+
| `sc_project_key` | The Jira project key to search for the linked SC integration ticket. | `false` | `SC` |
21+
| `sqs_fix_versions` | A comma-separated list of fix versions to set on the SQS integration ticket (e.g., `"sqs-2025.4, sqcb-25.7"`). | `false` | |
22+
| `use_sandbox` | Set to `false` to use the Jira production server. | `false` | `true` |
23+
24+
## Outputs
25+
26+
| Output | Description |
27+
|------------------|--------------------------------------------------------------------|
28+
| `sqs_ticket_key` | The key of the SQS integration ticket that was found. |
29+
| `sc_ticket_key` | The key of the SC integration ticket that was found. |
30+
31+
## Example Usage
32+
33+
Here is an example of how to use this action in a workflow. This job will be triggered manually, find the linked SQS and SC tickets, and update the SQS ticket's fix versions.
34+
35+
```yaml
36+
name: Update Integration Tickets
37+
38+
on:
39+
workflow_dispatch:
40+
inputs:
41+
release_ticket:
42+
description: 'Release Ticket Key (e.g., REL-1234)'
43+
required: true
44+
fix_versions:
45+
description: 'Optional comma-separated fix versions for the SONAR ticket'
46+
required: false
47+
48+
jobs:
49+
update-integration-tickets:
50+
name: Find and Update Integration Tickets
51+
runs-on: ubuntu-latest
52+
permissions:
53+
contents: read
54+
id-token: write
55+
56+
steps:
57+
- name: Get Jira Credentials from Vault
58+
id: secrets
59+
uses: SonarSource/vault-action-wrapper@v3
60+
with:
61+
secrets: |
62+
development/kv/data/jira user | JIRA_USER;
63+
development/kv/data/jira token | JIRA_TOKEN;
64+
65+
- name: Find and Update Tickets
66+
id: integration_update
67+
uses: SonarSource/release-github-actions/update-integration-tickets@master
68+
with:
69+
jira_user: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_USER }}
70+
jira_token: ${{ fromJSON(steps.secrets.outputs.vault).JIRA_TOKEN }}
71+
release_ticket_key: ${{ github.event.inputs.release_ticket }}
72+
sqs_fix_versions: ${{ github.event.inputs.fix_versions }}
73+
74+
- name: Echo Found Ticket Keys
75+
run: |
76+
echo "Found SQS integration ticket: ${{ steps.integration_update.outputs.sqs_ticket_key }}"
77+
echo "Found SC integration ticket: ${{ steps.integration_update.outputs.sc_ticket_key }}"

update-integration-tickets/action.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: 'Update integrations tickets'
2+
description: 'Finds SQS and SC integration tickets linked to a release ticket and optionally updates the SQS ticket fix versions.'
3+
author: 'SonarSource'
4+
5+
inputs:
6+
jira_user:
7+
description: 'Jira user (email) for authentication.'
8+
required: true
9+
jira_token:
10+
description: 'Jira API token for authentication.'
11+
required: true
12+
release_ticket_key:
13+
description: 'The key of the release ticket (e.g., REL-1234) to find the linked integration tickets from.'
14+
required: true
15+
sqs_project_key:
16+
description: 'The Jira project key to search for the linked SQS integration ticket.'
17+
required: false
18+
default: 'SONAR'
19+
sc_project_key:
20+
description: 'The Jira project key to search for the linked SC integration ticket.'
21+
required: false
22+
default: 'SC'
23+
sqs_fix_versions:
24+
description: 'Comma-separated list of fix versions to set on the SQS integration ticket (e.g., "10.1, 10.2").'
25+
required: false
26+
use_sandbox:
27+
description: "Set to 'false' to use the production Jira server."
28+
required: false
29+
default: 'true'
30+
31+
outputs:
32+
sqs_ticket_key:
33+
description: 'The key of the found SQS integration ticket.'
34+
value: ${{ steps.run_python_script.outputs.sqs_ticket_key }}
35+
sc_ticket_key:
36+
description: 'The key of the found SC integration ticket.'
37+
value: ${{ steps.run_python_script.outputs.sc_ticket_key }}
38+
39+
runs:
40+
using: "composite"
41+
steps:
42+
- name: Set up Python
43+
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
44+
with:
45+
python-version: '3.8'
46+
47+
- name: Install dependencies
48+
shell: bash
49+
run: pip install -r ${{ github.action_path }}/requirements.txt
50+
51+
- name: Run Python Script to Find Tickets
52+
id: run_python_script
53+
shell: bash
54+
run: |
55+
SANDBOX_FLAG=""
56+
if [[ "${{ inputs.use_sandbox }}" == "true" ]]; then
57+
SANDBOX_FLAG="--use-sandbox"
58+
fi
59+
60+
OPTS=""
61+
if [[ -n "${{ inputs.sqs_fix_versions }}" ]]; then
62+
OPTS="$OPTS --sqs-fix-versions=${{ inputs.sqs_fix_versions }}"
63+
fi
64+
65+
python ${{ github.action_path }}/update_integration_tickets.py \
66+
--release-ticket-key="${{ inputs.release_ticket_key }}" \
67+
--sqs-project-key="${{ inputs.sqs_project_key }}" \
68+
--sc-project-key="${{ inputs.sc_project_key }}" \
69+
$SANDBOX_FLAG \
70+
$OPTS >> $GITHUB_OUTPUT
71+
env:
72+
JIRA_USER: ${{ inputs.jira_user }}
73+
JIRA_TOKEN: ${{ inputs.jira_token }}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jira==3.8.0
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
"""
5+
This script finds SQS and SC integration tickets linked to a given release ticket,
6+
optionally updates the SQS ticket's fix versions, and returns both keys.
7+
"""
8+
9+
import argparse
10+
import os
11+
import sys
12+
from jira import JIRA
13+
from jira.exceptions import JIRAError
14+
15+
JIRA_SANDBOX_URL = "https://sonarsource-sandbox-608.atlassian.net/"
16+
JIRA_PROD_URL = "https://sonarsource.atlassian.net/"
17+
18+
19+
def eprint(*args, **kwargs):
20+
"""Prints messages to the standard error stream."""
21+
print(*args, file=sys.stderr, **kwargs)
22+
23+
24+
# noinspection DuplicatedCode
25+
def get_jira_instance(use_sandbox=False):
26+
"""Initializes and returns a JIRA client instance."""
27+
jira_user = os.environ.get('JIRA_USER')
28+
jira_token = os.environ.get('JIRA_TOKEN')
29+
30+
if not jira_user or not jira_token:
31+
eprint("Error: JIRA_USER and JIRA_TOKEN environment variables must be set.")
32+
sys.exit(1)
33+
34+
jira_url = JIRA_SANDBOX_URL if use_sandbox else JIRA_PROD_URL
35+
eprint(f"Connecting to JIRA server at: {jira_url}")
36+
37+
try:
38+
jira_client = JIRA(jira_url, basic_auth=(jira_user, jira_token))
39+
return jira_client
40+
except JIRAError as e:
41+
eprint(f"Error: JIRA authentication failed. Status: {e.status_code}, Response: {e.text}")
42+
sys.exit(1)
43+
44+
45+
def find_linked_ticket(release_issue, target_project_key):
46+
"""Finds a linked ticket from a specific project."""
47+
eprint(f"Searching for linked ticket in project '{target_project_key}'...")
48+
49+
linked_tickets = []
50+
for link in release_issue.fields.issuelinks:
51+
linked_issue = getattr(link, 'outwardIssue', getattr(link, 'inwardIssue', None))
52+
if linked_issue and linked_issue.key.startswith(f"{target_project_key}-"):
53+
linked_tickets.append(linked_issue.key)
54+
55+
if len(linked_tickets) == 0:
56+
eprint(f"Error: No linked ticket found in project '{target_project_key}' for ticket '{release_issue.key}'.")
57+
sys.exit(1)
58+
59+
if len(linked_tickets) > 1:
60+
eprint(
61+
f"Error: Found multiple linked tickets in project '{target_project_key}': {', '.join(linked_tickets)}. Please ensure only one is linked.")
62+
sys.exit(1)
63+
64+
found_key = linked_tickets[0]
65+
eprint(f"✅ Found linked ticket: {found_key}")
66+
return found_key
67+
68+
69+
def update_sqs_fix_versions(jira, ticket_key, fix_versions_str):
70+
"""Attempts to update the fixVersions of the SQS ticket."""
71+
if not fix_versions_str:
72+
return
73+
74+
eprint(f"Attempting to set fix versions for SQS ticket {ticket_key} to: '{fix_versions_str}'")
75+
versions_list = [{'name': v.strip()} for v in fix_versions_str.split(',')]
76+
77+
try:
78+
issue = jira.issue(ticket_key)
79+
issue.update(fields={'fixVersions': versions_list})
80+
eprint("✅ Successfully updated fix versions for SQS ticket.")
81+
except JIRAError as e:
82+
eprint(
83+
f"##[warning]Could not update fix versions for {ticket_key}. Jira API failed with status {e.status_code}: {e.text}")
84+
85+
86+
def main():
87+
parser = argparse.ArgumentParser(description="Finds linked Jira tickets and optionally updates one.")
88+
parser.add_argument("--release-ticket-key", required=True, help="The key of the release ticket.")
89+
parser.add_argument("--sqs-project-key", default="SONAR", help="The project key for the SQS ticket.")
90+
parser.add_argument("--sc-project-key", default="SC", help="The project key for the SC ticket.")
91+
parser.add_argument("--sqs-fix-versions", help="Comma-separated list of fix versions for the SQS ticket.")
92+
parser.add_argument('--use-sandbox', action='store_true', help="Use the sandbox Jira server.")
93+
94+
args = parser.parse_args()
95+
96+
jira = get_jira_instance(args.use_sandbox)
97+
98+
try:
99+
eprint(f"Fetching release ticket '{args.release_ticket_key}' to find linked issues...")
100+
release_issue = jira.issue(args.release_ticket_key, fields='issuelinks')
101+
except JIRAError as e:
102+
eprint(
103+
f"Error: Could not retrieve issue '{args.release_ticket_key}'. Status: {e.status_code}, Response: {e.text}")
104+
sys.exit(1)
105+
106+
sqs_ticket_key = find_linked_ticket(release_issue, args.sqs_project_key)
107+
sc_ticket_key = find_linked_ticket(release_issue, args.sc_project_key)
108+
109+
update_sqs_fix_versions(jira, sqs_ticket_key, args.sqs_fix_versions)
110+
111+
# Output for the GitHub Action
112+
print(f"sqs_ticket_key={sqs_ticket_key}")
113+
print(f"sc_ticket_key={sc_ticket_key}")
114+
115+
116+
if __name__ == "__main__":
117+
main()

0 commit comments

Comments
 (0)