Skip to content

Commit d48e5bf

Browse files
Merge pull request #4576 from agullon/USHIFT-5377
USHIFT-5377: add script to generate a advisory, cves and jira tickets report
2 parents bb4e343 + 50dc326 commit d48e5bf

File tree

4 files changed

+232
-0
lines changed

4 files changed

+232
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# OCP Advisory Publication Report
2+
3+
## Description
4+
5+
Each week for every minor OCP release, the MicroShift team decides if a new MicroShift version should be publish.
6+
The decision is based on if there are important changes/fixes to MicroShift or the OCP images it depends on.
7+
This script will generate a report with advisories, CVEs and jira tickets relevant to making the decision.
8+
9+
### Steps
10+
1. Find advisory ids for a OCP version from [ocp-build-data repository](https://github.com/openshift-eng/ocp-build-data)
11+
- For example, for release `4.17` check [`releases.yml`](https://github.com/openshift-eng/ocp-build-data/blob/openshift-4.17/releases.yml)
12+
2. Get the list of CVEs from a Red Hat advisory using [Red Hat errata API](https://errata.devel.redhat.com/documentation/developer-guide/api-http-api.html#get-cveshowerrata_id.json)
13+
- Only available behind the Red Hat VPN
14+
- Example for [`144556` advisory](https://errata.devel.redhat.com/cve/show/144556.json)
15+
3. Query Jira to find if there are any MicroShift tickets to address a CVE fix
16+
- For example [`summary ~ "CVE-2024-21626" AND component = MicroShift and (affectedVersion = 4.17 or affectedVersion = 4.17.z)`](https://issues.redhat.com/issues/?jql=summary%20%20~%20%22CVE-2024-21626%22%20AND%20component%20%3D%20MicroShift%20and%20(affectedVersion%20%3D%204.17%20or%20affectedVersion%20%3D%204.17.z)) query
17+
18+
## Requisites
19+
20+
### Jira API token
21+
22+
Visit [the Profile page on the Jira
23+
Server](https://issues.redhat.com/secure/ViewProfile.jspa?selectedTab=com.atlassian.pats.pats-plugin:jira-user-personal-access-tokens) and create a token.
24+
25+
Set the `JIRA_API_TOKEN` in your env:
26+
27+
```
28+
export JIRA_API_TOKEN="TOKEN_VALUE"
29+
```
30+
31+
### Connect to Red Hat VPN
32+
33+
Red Hat VPN connection is mandatory to get info from Red Hat errata tool.
34+
35+
## Generate report
36+
37+
Run `./scripts/advisory_publication/advisory_publication_report.sh X.Y.Z` to generate json report.
38+
39+
`X.Y.Z` is the target OCP version, for example `4.17.12`
40+
41+
Output format:
42+
43+
```
44+
{
45+
"RHSA-YYYY:NNNNN": {
46+
"type": "extras",
47+
"url": "https://errata.devel.redhat.com/advisory/XXXXX",
48+
"cves": {
49+
"CVE-YYYY-NNNN": {},
50+
"CVE-YYYY-NNNN": {}
51+
}
52+
},
53+
"RHSA-2025:0364": {
54+
"type": "image",
55+
"url": "https://errata.devel.redhat.com/advisory/YYYYY",
56+
"cves": {
57+
"CVE-YYYY-NNNN": {},
58+
"CVE-YYYY-NNNN": {
59+
"jira_ticket": {
60+
"id": "OCPBUGS-MMMMM",
61+
"summary": "CVE-YYYY-NNNN title of the jira ticket",
62+
"status": "Closed",
63+
"resolution": "Not a Bug"
64+
}
65+
}
66+
}
67+
},
68+
"RHSA-YYYY:NNNNN": {
69+
"type": "metadata",
70+
"url": "https://errata.devel.redhat.com/advisory/ZZZZZ",
71+
"cves": {}
72+
}
73+
}
74+
75+
```
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env python3
2+
3+
import os
4+
import sys
5+
import jira.client
6+
import requests
7+
import urllib3
8+
import json
9+
import jira
10+
import yaml
11+
12+
SERVER_URL = 'https://issues.redhat.com/'
13+
JIRA_API_TOKEN = os.environ.get('JIRA_API_TOKEN')
14+
15+
16+
def usage():
17+
print("""\
18+
usage: advisory_publication_report.py OCP_VERSION
19+
20+
arguments:
21+
OCP_VERSION: The OCP versions to analyse if MicroShift version should be published. Format: "4.X.Z"\
22+
""")
23+
24+
25+
def get_advisories(ocp_version: str) -> dict[str, int]:
26+
"""
27+
Get a list of advisory ids for a OCP version from github.com/openshift-eng/ocp-build-data repository
28+
Parameters:
29+
ocp_version (str): OCP version with format: "X.Y.Z"
30+
Returns:
31+
(dict): advisory dict with type and id
32+
"""
33+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
34+
35+
try:
36+
microshift_xy_version = '.'.join(ocp_version.split('.')[:2])
37+
request = requests.get(f'https://raw.githubusercontent.com/openshift-eng/ocp-build-data/refs/heads/openshift-{microshift_xy_version}/releases.yml', verify=False)
38+
request.raise_for_status()
39+
except requests.exceptions.HTTPError as err:
40+
raise SystemExit(err)
41+
releases_dict = yaml.load(str(request.text), Loader=yaml.SafeLoader)
42+
43+
if ocp_version in releases_dict['releases']:
44+
return releases_dict['releases'][ocp_version]['assembly']['group']['advisories']
45+
else:
46+
raise KeyError(f"{ocp_version} OCP version does NOT exist")
47+
48+
49+
def get_advisory_info(advisory_id: int) -> dict[str, str]:
50+
"""
51+
Get a list of strings with the CVEs ids for an advisory
52+
Parameters:
53+
advisory_id (int): advisory id
54+
Returns:
55+
(list): list of strings with CVE ids
56+
"""
57+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
58+
59+
try:
60+
request = requests.get(f'https://errata.devel.redhat.com/cve/show/{advisory_id}.json', verify=False)
61+
request.raise_for_status()
62+
except requests.exceptions.HTTPError as err:
63+
raise SystemExit(err)
64+
advisory_info = json.loads(request.text)
65+
66+
if advisory_info is None:
67+
raise ValueError
68+
if not isinstance(advisory_info, dict):
69+
raise TypeError
70+
return advisory_info
71+
72+
73+
def search_microshift_tickets(affects_version: str, cve_id: str) -> jira.client.ResultList:
74+
"""
75+
Query Jira for MicroShift ticket with CVE id and MicroShift version
76+
Parameters:
77+
affects_version (str): MicroShift affected version with format: "X.Y"
78+
cve_id (str): the CVE id with format: "CVE-YYYY-NNNNN"
79+
Returns:
80+
(jira.client.ResultList): a list with all the Jira tickets matching the query
81+
"""
82+
server = jira.JIRA(server=SERVER_URL, token_auth=JIRA_API_TOKEN)
83+
jira_tickets = server.search_issues(f'''
84+
summary ~ "{cve_id}" and component = MicroShift and (affectedVersion = {affects_version} or affectedVersion = {affects_version}.z)
85+
''')
86+
87+
if not isinstance(jira_tickets, jira.client.ResultList):
88+
raise TypeError
89+
return jira_tickets
90+
91+
92+
def get_report(ocp_version: str) -> dict[str, dict]:
93+
"""
94+
Get a json object with all the advisories, CVEs and jira tickets linked
95+
Parameters:
96+
ocp_version (str): OCP version with format: "X.Y.Z"
97+
Returns:
98+
(dict): json object with all the advisories, CVEs and jira tickets linked
99+
"""
100+
result_json = dict()
101+
advisories = get_advisories(ocp_version)
102+
for advisory_type, advisory_id in advisories.items():
103+
advisory_info = get_advisory_info(advisory_id)
104+
cve_list = advisory_info['cve']
105+
advisory_dict = dict()
106+
advisory_dict['type'] = advisory_type
107+
advisory_dict['url'] = f'https://errata.devel.redhat.com/advisory/{advisory_id}'
108+
advisory_dict['cves'] = dict()
109+
for cve in cve_list:
110+
jira_tickets = search_microshift_tickets(".".join(ocp_version.split(".")[:2]), cve)
111+
advisory_dict['cves'][cve] = dict()
112+
for ticket in jira_tickets:
113+
jira_ticket_dict = dict()
114+
jira_ticket_dict['id'] = ticket.key
115+
jira_ticket_dict['summary'] = ticket.fields.summary
116+
jira_ticket_dict['status'] = ticket.fields.status.name
117+
jira_ticket_dict['resolution'] = ticket.fields.resolution.name
118+
advisory_dict['cves'][cve]['jira_ticket'] = jira_ticket_dict
119+
result_json[advisory_info['advisory']] = advisory_dict
120+
return result_json
121+
122+
123+
def main():
124+
if len(sys.argv) != 2:
125+
usage()
126+
raise ValueError('Invalid number of arguments')
127+
128+
if JIRA_API_TOKEN is None:
129+
raise ValueError('JIRA_API_TOKEN var not found in the env')
130+
131+
ocp_version = str(sys.argv[1])
132+
result_json = get_report(ocp_version)
133+
print(f"{json.dumps(result_json, indent=4)}")
134+
135+
136+
if __name__ == '__main__':
137+
main()
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
SCRIPTDIR="$(dirname "${BASH_SOURCE[0]}")"
6+
REPOROOT="$(git rev-parse --show-toplevel)"
7+
OUTPUT_DIR="${REPOROOT}/_output"
8+
ENVDIR="${OUTPUT_DIR}/advisory_publication"
9+
10+
if [ ! -d "${ENVDIR}" ]; then
11+
echo "Setting up required tools..."
12+
mkdir -p "${OUTPUT_DIR}"
13+
python3 -m venv "${ENVDIR}"
14+
"${ENVDIR}/bin/pip3" install -r "${SCRIPTDIR}/requirements.txt"
15+
fi
16+
17+
"${ENVDIR}/bin/python3" "${SCRIPTDIR}/advisory_publication_report.py" "$@"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
requests>=2.32.2
2+
jira>=3.5.0
3+
PyYAML>=6.0.2

0 commit comments

Comments
 (0)