· 4 years ago · Apr 30, 2021, 01:26 AM
1#!/usr/bin/env python3
2
3# Helpful docsets
4# https://developer.atlassian.com/cloud/jira/platform/rest/v2/api-group-project-versions/#api-rest-api-2-version-id-put
5#
6
7import pprint
8import requests
9from requests.auth import HTTPBasicAuth
10import json
11import getpass
12
13# -----------------------------------------------------------------------------
14#TODO maybe move this out to a lib instead of inlcuding it in each script?
15import keyring
16#import pwd
17import os
18#TODO parm these / update these / improve this
19#JIRA_USERNAME = pwd.getpwuid(os.getuid())[0]
20#JIRA_PASSWORD = keyring.get_password("PYTHON_JIRA", JIRA_USERNAME)
21
22JIRA_USERNAME = getpass.getuser()
23print(f'your username is : "{JIRA_USERNAME}" ')
24JIRA_PASSWORD = keyring.get_password("PYTHON_JIRA", JIRA_USERNAME)
25
26def jira_request_wrapper(request_type, url, payload):
27 """Simple wrapper for Jira REST API, allows for basic error handling and HTTP_BASIC_AUTH in one place (use HTTPS!!)
28 :param request_type: HTTP request type (GET, POST, PUT)
29 :param payload: object to send in the body of the :class:`Request`.
30 :return: :class:`Response <Response>` object
31 :rtype: requests.Response
32 """
33 #TODO add some basic error checks buddy
34 # e.g. GET, PUT, POST); len(url) > 0
35
36 jira_headers = {
37 "Accept": "application/json",
38 "Content-Type": "application/json"
39 }
40 # REMINDER: You will need to be ADMIN on jira prohects to makes changes (like updatind version numbers)
41 response = requests.request(request_type, url, data=payload, headers=jira_headers, auth=(JIRA_USERNAME, JIRA_PASSWORD))
42 #print("<debug>", json.dumps(json.loads(response.text), sort_keys=True, indent=4, separators=(",", ":")))
43
44 #Response return a code: <Response[200]>
45
46 # TODO: could be improved -- for now we want to exit(err) to catch this right away
47 #if response.status_code != requests.codes.ok and response.status_code != requests.codes.created:
48 if response.status_code not in (requests.codes.ok, requests.codes.created):
49 print(f"\n<error> {response.status_code} when attempting to {request_type} {response.request.url}")
50 print(f"<error> {response.request.body}")
51 exit(response.status_code)
52 # TODO: Catching errors here and then EXITing. Safe (e.g. does not cascade across 1st project), but not a clean approach :-/
53 return response
54# -----------------------------------------------------------------------------
55
56def get_project_versions(jira_project_key):
57 """Retrieves array of Jira Version objects for a given projest.
58 :param jira_project_key: a valid jira project key (e.g. FLEX_SDK, EMQATSTING ...).
59 :return: ver_ids object
60 :rtype: list[]
61 """
62
63 # create empty list (will be filled, or remain empty on fail)
64 ver_ids = []
65
66 #e.g. https://jira-engdev.silabs.com/rest/api/2/project/FLEX_SDK/versions
67 # jira-engdev.silabs.com
68 jira_url = f"https://jira-engdev.silabs.com/rest/api/2/project/{jira_project_key}/versions"
69 response = jira_request_wrapper("GET", jira_url, "")
70
71 #Get the response code if ok
72 if response.status_code == requests.codes.ok:
73 ver_ids = json.loads(response.text)
74 print(f"\n<status> {jira_project_key} retrieved {len(ver_ids)} unique versions")
75
76 PP.pprint(ver_ids)
77
78 return ver_ids
79
80def create_version(jira_project_key, project_id, new_version):
81 """Creates a new 'release version' in specified project.
82 :param jira_project_key: a valid jira project key (e.g. FLEX_SDK, EMQATSTING ...).
83 :param project_id: a valid jira project id (e.g. use GET to get one).
84 :param new_version: a 'release verison' dictionaty (name, releaseDate)
85 """
86
87 jira_url = f"https://jira-engdev.silabs.com/rest/api/2/version"
88 payload = json.dumps( {
89 "projectId": str(project_id),
90 "name": new_version["name"],
91 "description": new_version['description'],
92 "startDate": new_version['startDate'],
93 "releaseDate": new_version['releaseDate']
94 } )
95
96 response = jira_request_wrapper("POST", jira_url, payload)
97 if response.status_code == requests.codes.created: #200=OK; 201=CREATED
98 print(f"<status> {jira_project_key} created {new_version['name']}")
99
100 return response.status_code
101
102def diff_keys(key, old, new):
103 """Updates the specified 'release version'.
104 :param key: a dictionary key (e.g. 'releaseDate')
105 :param old_version: a 'project verison' dictionaty (name, releaseDate, ...)
106 :param new_version: a 'release verison' dictionaty (name, releaseDate, ...)
107 :return: value [0|1]
108 :rtype: int
109 """
110
111 #TODO: need to improve this checking
112 #If desired field was in the old key, for exemple the description
113 if key in old.keys():
114 #If the value of the field does not match with the previous one
115 if old[key] != new[key]:
116 #print(f"<debug> '{key}' old:{old[key]} != new{new[key]} so needs an UPDATE")
117 return 1
118 else:
119 #Or update if the field is None
120 if new[key] not in (None, ''):
121 #print(f"<debug> new value of new:{new[key]} so needs an UPDATE")
122 return 1
123
124 #print(f"<debug> no need to UPDATE")
125 return 0
126
127def update_version(jira_project_key, old_version, new_version):
128 """Updates the specified 'release version'.
129 :param jira_project_key: a valid jira project key (e.g. FLEX_SDK, EMQATSTING ...).
130 :param old_version: a 'project verison' dictionaty (name, releaseDate, ...)
131 :param new_version: a 'release verison' dictionaty (name, releaseDate)
132 :return: http status code
133 :rtype: int
134 """
135
136 # some (simple) checks to see if we actually need to update
137 needs_update = 0
138 needs_update += diff_keys('description', old_version, new_version)
139 needs_update += diff_keys('startDate', old_version, new_version)
140 needs_update += diff_keys('releaseDate', old_version, new_version)
141
142 # e.g. https://jira-engdev.silabs.com/projects/FLEX_SDK?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page
143 # e.g. https://jira-engdev.silabs.com/rest/api/2/version/53756
144 if needs_update > 0:
145 version_id = old_version['id'] # TODO: could be improved / checked
146 jira_url = f"https://jira-engdev.silabs.com/rest/api/2/version/{version_id}"
147
148 #Update the fields
149 payload = json.dumps( {
150 "description": new_version["description"],
151 "startDate": new_version['startDate'],
152 "releaseDate": new_version['releaseDate']
153 } )
154
155 response = jira_request_wrapper("PUT", jira_url, payload)
156 if response.status_code == requests.codes.ok: # 200=ok
157 print(f"<status> {jira_project_key} updated {new_version['name']} w/ releaseDate:{new_version['releaseDate']}")
158 return response.status_code
159
160 else:
161 #print(f"<debug> {jira_project_name} {old_version['name']} == {new_version['name']}, no need for update")
162 return requests.codes.bad_request # 400=bad_request
163
164def update_all(jira_project_key, release_versions):
165 """Updates the specified 'release version', creates one if it does not exist
166 :param jira_project_key: a valid jira project key (e.g. FLEX_SDK, EMQATSTING ...).
167 :param release_versions: a list[] of 'release verison' dictionaties (w/ name, releaseDate)
168 """
169
170 jira_version_ids = get_project_versions(jira_project_key)
171 #BUG: this does not handle project with NO existing versions (jira_version_ids=0). Unlikely, but could happen w/ NEW Jira projects
172 # need <for x in dict["foo"]> but can't rememebr syntax ... wow I need to brush up on my Python
173 # so, i guess nested for loops will have to do
174
175 #Release version is the one we are trying to add (new version)
176 for rel_ver in release_versions:
177 found_fix_verison = False
178
179 #Comparing with the one retrieved with the GET (old version)
180 for prj_ver in jira_version_ids:
181
182 #If it already exists
183 if rel_ver['name'] == prj_ver['name']:
184 found_fix_verison = True
185 #print(f"<debug> {jira_project_name} has existing {rel_ver['name']} (id: {prj_ver['id']}). Attempting to updated ...")
186 update_version(jira_project_key, prj_ver, rel_ver)
187
188 #If it does not exist
189 if found_fix_verison is False:
190 #print(f"<debug> {jira_project_name} does not already contain {rel_ver['name']}. Attempting to create ...")
191 #TODO hardcoding for now, but need to harden this as it ASSUMES there is at least 1 RELEASE (version) in the prj
192 project_id = jira_version_ids[0]['projectId']
193 create_version(jira_project_key, project_id, rel_ver)
194
195def main():
196 """main(), used to update | create versions in working set"""
197
198 ## Basic Usage
199 ##############
200 ## name: "20Q2-Patch1" <---------- mandatory (as this script uses it, and not it's 'UUID', as a foreign key)
201 ## startDate: "2020-07-29" || None
202 ## releaseDate: "2020-07-29" || None
203 ## description: "FeatureComplete: TBD FixFreeze: TBD GA: TBD" || "" (empty string, NOT 'None')
204
205 # Our working set of versions (w/ 'name', VALID [YYYY-MM-DD] startDate + releaseDate, and description)
206 release_versions = [] # empty set
207 #release_versions.append({'name':'21Q2-IFC-1', 'startDate':None, 'releaseDate':'2021-01-20', 'description':'Interim Feature Complete 1'})
208 #release_versions.append({'name':'21Q2-IFC-2', 'startDate':None, 'releaseDate':'2021-02-24', 'description':'Interim Feature Complete 2'})
209 #release_versions.append({'name':'21Q2-IFC-3', 'startDate':None, 'releaseDate':'2021-03-24', 'description':'Interim Feature Complete 3'})
210 #release_versions.append({'name':'21Q2-IFC-4', 'startDate':None, 'releaseDate':'2021-04-21', 'description':'Interim Feature Complete 4'})
211 #release_versions.append({'name':'21Q2-FC', 'startDate':'2020-12-15', 'releaseDate':'2021-05-19', 'description':'Feature Complete'})
212 #release_versions.append({'name':'21Q2-PostFC', 'startDate':None, 'releaseDate':'2021-06-02', 'description':'All Features Testing Complete'})
213 #release_versions.append({'name':'21Q2-GA', 'startDate':'2020-12-15', 'releaseDate':'2021-06-16', 'description':'Public Release'})
214 #release_versions.append({'name':'21Q2-NonSDK', 'startDate':'2020-12-15', 'releaseDate':'2021-06-16', 'description':'Non-GSDK work, completed in 21Q2 timeframe'})
215 #release_versions.append({'name':'21Q2-GitHub', 'startDate':'2020-12-15', 'releaseDate':'2021-06-16', 'description':'GitHub work, completed in 21Q2 timeframe'}) # OT only!!
216 #release_versions.append({'name':'20Q4-Patch1', 'startDate':None, 'releaseDate':'2021-01-27', 'description':'FeatureComplete:Jan-13, FixFreeze:Jan-20, GA:Jan-27'})
217 #release_versions.append({'name':'20Q4-Patch2', 'startDate':None, 'releaseDate':None, 'description':'FeatureComplete: TBD FixFreeze: TBD GA: TBD'})
218 #release_versions.append({'name':'20Q4-Patch3', 'startDate':None, 'releaseDate':None, 'description':''})
219 #release_versions.append({'name':'21Q2-Patch1', 'startDate':None, 'releaseDate':'2021-07-21', 'description':'FeatureComplete: 2021-07-07 | FixFreeze: 2021-07-16 | GA: 2021-07-21'})
220 #release_versions.append({'name':'21Q2-Patch2', 'startDate':None, 'releaseDate':'2021-08-18', 'description':'FeatureComplete: 2021-08-04 | FixFreeze: 2021-08-13 | GA: 2021-08-18'})
221 release_versions.append({'name':'LC_TEST', 'startDate':None, 'releaseDate':'2021-08-21', 'description':'FeatureComplete: 2021-08-04 | FixFreeze: 2021-08-13 | GA: 2021-08-18'})
222 release_versions.append({'name':'LC_TEST2', 'startDate':'2021-05-04', 'releaseDate':'2021-08-21', 'description':'FeatureComplete: 2021-08-04 | FixFreeze: 2021-08-13 | GA: 2021-08-18'})
223
224
225 print(f"\n<status> working set of release verisons ({len(release_versions)}):")
226 PP.pprint(release_versions)
227
228 # Our working set of jira projects versions
229 # Oct-20-2020: now based on https://confluence.silabs.com/display/IOTSW/Team+View
230 # (see also https://confluence.silabs.com/pages/viewpage.action?pageId=148958272)
231 # Apr-07-2021: updated below based on latest https://confluence.silabs.com/display/IOTSW/Team+View
232 jira_projects = []
233 #! jira_projects('EMIPSTACK') # EXCLUDE (PoC: Bob MacDonald ) Legacy project, no longer used
234 #! jira_projects('HWT_SW') # EXCLUDE (PoC: ??? ) ???
235 #! jira_projects('IOTREQ') # EXCLUDE (PoC: Sarah Scannell) Used for REQUIREMENTs, do not change
236 #! jira_projects('IOTPIT') # EXCLUDE (PoC: John Loukota ) Used for internal 'process improvemnt projects, no need to change
237 #! jira_projects('MCUW_RADIO_CFG') # EXCLUDE (PoC: Ashkan Sattari) Used by the VALIDATION TEAM (Hardik Patel), not in SW org.
238 # jira_projects.append('BG')
239 # jira_projects.append('BTAPP')
240 # jira_projects.append('SQA_BLE')
241 # jira_projects.append('BTMESH')
242 # jira_projects.append('SQA_BTMESH')
243 # jira_projects.append('CONNECTSTACK')
244 # jira_projects.append('FLEX_SDK')
245 # jira_projects.append('CONNECT_SDK')
246 # jira_projects.append('RAIL_SDK')
247 # jira_projects.append('SQA_CONNECT')
248 # jira_projects.append('EMHAL')
249 # jira_projects.append('PLATFORM_HYD')
250 # jira_projects.append('PLATFORM_MTL')
251 # jira_projects.append('CRDSW')
252 # jira_projects.append('RAIL_LIB')
253 # jira_projects.append('PSEC')
254 # jira_projects.append('SQA_PLATFORM')
255 # jira_projects.append('SQA_RAIL')
256 # jira_projects.append('SQA_SECURITY')
257 # jira_projects.append('OPENTHREAD')
258 # jira_projects.append('SQA_THREAD')
259 # jira_projects.append('EMZIGBEE')
260 # jira_projects.append('SQA_ZIGBEE')
261 # jira_projects.append('SWPROT')
262 # jira_projects.append('ZWAVE_SDK')
263 # jira_projects.append('SQA_ZWAVE')
264 # jira_projects.append('WI_SUN')
265 # jira_projects.append('WISUN_SDK')
266 # jira_projects.append('SQA_WISUN')
267 # jira_projects.append('SQA_DEVOPS')
268 jira_projects.append('IOTREQ')
269
270 jira_projects.sort()
271 print(f"\n<status> working set of jira projects ({len(jira_projects)}):")
272 PP.pprint(jira_projects)
273
274 ## I use this to TEST
275 ## https://jira-engdev.silabs.com/projects/FLEX_SDK?selectedItem=com.atlassian.jira.jira-projects-plugin:release-page&status=all&contains=20Q
276 #release_versions.clear()
277 #release_versions.append({'name':'20Q4-TEST1', 'startDate':None, 'releaseDate':'2020-07-22', 'description':''})
278 #print("\n\n### TEST MODE ###\n\n")
279 #update_all("FLEX_SDK", release_versions) # I use this to test as "I" run this team
280 #exit(0)
281
282 ##! ok, let's GO
283 for prj in jira_projects:
284 update_all(prj, release_versions)
285
286if __name__ == '__main__':
287 # init globals ... i know, i know but quick-and-dirty for now
288 # TODO cleanup this global | or need for ppprint anyway
289 PP = pprint.PrettyPrinter(indent=2, width=160)
290
291 # go go gadget go!
292 main()
293 exit(0)
294