Merge ~sylvain-pineau/hwcert-jenkins-tools:tf-checklist-links-to-summary-v2 into hwcert-jenkins-tools:master

Proposed by Sylvain Pineau
Status: Merged
Approved by: Sylvain Pineau
Approved revision: 30f4a6da7248585b5c272ae636a13c50d4126d02
Merged at revision: 85d18ed5c7fc4a1011aac041ac5c82d0db60b974
Proposed branch: ~sylvain-pineau/hwcert-jenkins-tools:tf-checklist-links-to-summary-v2
Merge into: hwcert-jenkins-tools:master
Diff against target: 437 lines (+59/-173)
2 files modified
trello-board-updater-desktop.py (+37/-99)
trello-board-updater-image-testing.py (+22/-74)
Reviewer Review Type Date Requested Status
Paul Larson Approve
Review via email: mp+385822@code.launchpad.net

Description of the change

Refactor of the two new trello board updater scripts:

trello-board-updater-desktop.py
trello-board-updater-image-testing.py

Both now share common code with the original trello-board-updater.py and finally get the comment links in checklists.

Tested here:

https://trello.com/b/pmU4fQgz/kernel-deb-update-verification-sylvains-sandbox
https://trello.com/b/ZibLQPmC/certification-image-testing-sylvains-sandbox

To post a comment you must log in.
Revision history for this message
Paul Larson (pwlars) wrote :

great!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/trello-board-updater-desktop.py b/trello-board-updater-desktop.py
2index 48fb316..d04a990 100755
3--- a/trello-board-updater-desktop.py
4+++ b/trello-board-updater-desktop.py
5@@ -6,6 +6,7 @@
6 #
7 # Written by:
8 # Taihsiang Ho <taihsiang.ho@canonical.com>
9+# Sylvain Pineau <sylvain.pineau@canonical.com>
10 #
11 #
12 # The script uses py-trello package 0.9.0. You may want to fetch it from
13@@ -15,6 +16,7 @@
14 # SUTs
15 #
16 import argparse
17+import importlib
18 import os
19 import re
20 import requests
21@@ -34,89 +36,12 @@ from trello.exceptions import ResourceUnavailable
22
23 from unittest.mock import MagicMock
24
25+tbu = importlib.import_module("trello-board-updater")
26+
27 format_str = "[ %(funcName)s() ] %(message)s"
28 logging.basicConfig(level=logging.INFO, format=format_str)
29
30
31-def environ_or_required(key):
32- if os.environ.get(key):
33- return {'default': os.environ.get(key)}
34- else:
35- return {'required': True}
36-
37-
38-def search_card(board, query, card_filter="open"):
39- for card in board.get_cards(card_filter=card_filter):
40- if re.match(query, card.name):
41- return card
42-
43-
44-def find_or_create_checklist(card, checklist_name, items=[]):
45- existing_checklists = card.fetch_checklists()
46- checklist = None
47- for c in existing_checklists:
48- if c.name == checklist_name:
49- checklist = c
50- break
51- if not checklist:
52- checklist = card.add_checklist(checklist_name, [])
53- for item in items:
54- item_msg = item + ' (NO RESULTS)'
55- checklist.add_checklist_item(item_msg)
56- return checklist
57-
58-
59-def change_checklist_item(checklist, name, checked=False):
60- # keep the trailing space so that we don't match the wrong thing later
61- r = re.match('\[*(.*? )\(', name)
62- if r:
63- debname = r.group(1)
64- for item in checklist.items:
65- if debname in item.get('name'):
66- checklist.rename_checklist_item(item.get('name'), name)
67- checklist.set_checklist_item(name, checked)
68- return True
69- else:
70- return False
71- else:
72- print('WARNING: Invalid name specified', name)
73-
74-
75-def no_new_fails_or_skips(summary_data):
76- """Check summary data for new fails or skips
77-
78- Return True if there are no new fails or skips detected and if all
79- tests passed
80- """
81- return ("No new failed or skipped tests" in summary_data and
82- "All tests passed" in summary_data)
83-
84-
85-def load_config(configfile):
86- if not configfile:
87- return []
88- try:
89- data = yaml.safe_load(configfile)
90- except (yaml.parser.ParserError, yaml.scanner.ScannerError):
91- print('ERROR: Error parsing', configfile.name)
92- sys.exit(1)
93- return data
94-
95-
96-def attach_labels(board, card, label_list):
97- for labelstr in label_list:
98- for label in board.get_labels():
99- if label.name == labelstr:
100- labels = card.list_labels or []
101- if label not in labels:
102- # Avoid crash if checking labels fails to find it
103- try:
104- card.add_label(label)
105- except ResourceUnavailable:
106- pass
107- break
108-
109-
110 def run(args, board, c3_link, jenkins_link):
111
112 kernel_stack = args.series
113@@ -203,8 +128,8 @@ def run(args, board, c3_link, jenkins_link):
114 re.escape(kernel_stack),
115 re.escape(deb_kernel_image),
116 deb_version)
117- card = search_card(board, pattern)
118- config = load_config(args.config)
119+ card = tbu.search_card(board, pattern)
120+ config = tbu.load_config(args.config, None)
121 expected_tests = config.get(kernel_stack, {}).get('expected_tests', [])
122
123 logging.info('SRU type: {}'.format(args.sru_type))
124@@ -241,10 +166,11 @@ def run(args, board, c3_link, jenkins_link):
125 summary_data = args.summary.read()
126 summary += summary_data
127 summary += '\n```\n'
128- card.comment(summary)
129+ comment = card.comment(summary)
130+ comment_link = "{}#comment-{}".format(card.url, comment['id'])
131 else:
132 summary_data = ""
133- checklist = find_or_create_checklist(card, 'Testflinger', expected_tests)
134+ checklist = tbu.find_or_create_checklist(card, 'Testflinger', expected_tests)
135 job_name = args.name.split('-')
136 cid = job_name[-2] + '-' + job_name[-1]
137
138@@ -265,30 +191,32 @@ def run(args, board, c3_link, jenkins_link):
139 print('Detected oem xenial run SUT: {}'.format(sut))
140
141 if args.cardonly:
142- item_name = "{} ({})".format(sut, 'In progress')
143+ item_content = "{} ({})".format(args.name, 'In progress')
144 else:
145- item_name = "{} ({})".format(sut, datetime.utcnow().isoformat())
146+ item_content = "[{}]({}) ({})".format(
147+ args.name, comment_link, datetime.utcnow().isoformat())
148 if jenkins_link:
149- item_name += " [[JENKINS]({})]".format(jenkins_link)
150+ item_content += " [[JENKINS]({})]".format(jenkins_link)
151 if c3_link:
152- item_name += " [[C3]({})]".format(c3_link)
153+ item_content += " [[C3]({})]".format(c3_link)
154 elif not args.cardonly:
155 # If there was no c3_link, it's because the submission failed
156- attach_labels(board, card, ['TESTFLINGER CRASH'])
157+ tbu.attach_labels(board, card, ['TESTFLINGER CRASH'])
158
159 # debug message
160 logging.info('checklist: {}'.format(checklist))
161- logging.info('item_name: {}'.format(item_name))
162- if not change_checklist_item(
163- checklist, item_name, checked=no_new_fails_or_skips(summary_data)):
164- checklist.add_checklist_item(item_name)
165+ logging.info('item_content: {}'.format(item_content))
166+ if not tbu.change_checklist_item(
167+ checklist, args.name, item_content,
168+ checked=tbu.no_new_fails_or_skips(summary_data)):
169+ checklist.add_checklist_item(item_content)
170
171 if not [c for c in card.fetch_checklists() if c.name == 'Sign-Off']:
172- checklist = find_or_create_checklist(card, 'Sign-Off')
173+ checklist = tbu.find_or_create_checklist(card, 'Sign-Off')
174 checklist.add_checklist_item('Ready for ' + lanes[0], True)
175 checklist.add_checklist_item('Ready for ' + lanes[1])
176 checklist.add_checklist_item('Can be Archived')
177- checklist = find_or_create_checklist(card, 'Revisions')
178+ checklist = tbu.find_or_create_checklist(card, 'Revisions')
179 rev = '{} ({})'.format(deb_version, args.arch)
180 if rev not in [item['name'] for item in checklist.items]:
181 checklist.add_checklist_item(rev)
182@@ -313,11 +241,11 @@ def run(args, board, c3_link, jenkins_link):
183 def main():
184 parser = argparse.ArgumentParser()
185 parser.add_argument('--key', help="Trello API key",
186- **environ_or_required('TRELLO_API_KEY'))
187+ **tbu.environ_or_required('TRELLO_API_KEY'))
188 parser.add_argument('--token', help="Trello OAuth token",
189- **environ_or_required('TRELLO_TOKEN'))
190+ **tbu.environ_or_required('TRELLO_TOKEN'))
191 parser.add_argument('--board', help="Trello board identifier",
192- **environ_or_required('TRELLO_BOARD'))
193+ **tbu.environ_or_required('TRELLO_BOARD'))
194 parser.add_argument('--config', help="Pool configuration",
195 type=argparse.FileType())
196 parser.add_argument('-a', '--arch', help="deb architecture",
197@@ -363,7 +291,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
198
199 def _mock_factory(self, jenkins_job_template, card_name, packages_info):
200 parser = argparse.ArgumentParser()
201- args = parser.parse_args()
202+ args = parser.parse_args([])
203 args.__dict__.update(jenkins_job_template)
204
205 self.args = args
206@@ -398,6 +326,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
207
208 def test_stock_xenial_4_4_kernel_stack(self):
209 jenkins_job_template = {
210+ 'cardonly': False,
211 'name': 'xenial-desktop-201606-22344',
212 'arch': 'amd64',
213 'kernel': 'linux-generic',
214@@ -418,6 +347,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
215
216 def test_stock_xenial_4_15_kernel_stack(self):
217 jenkins_job_template = {
218+ 'cardonly': False,
219 'name': 'xenial-hwe-desktop-201606-22344',
220 'arch': 'amd64',
221 'kernel': 'linux-generic-hwe-16_04',
222@@ -439,6 +369,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
223
224 def test_stock_bionic_4_15_kernel_stack(self):
225 jenkins_job_template = {
226+ 'cardonly': False,
227 'name': 'bionic-desktop-201606-22344',
228 'arch': 'amd64',
229 'kernel': 'linux-generic',
230@@ -459,6 +390,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
231
232 def test_oem_xenial_4_4_kernel_stack(self):
233 jenkins_job_template = {
234+ 'cardonly': False,
235 'name': 'xenial-desktop-201610-25144',
236 'arch': 'amd64',
237 'kernel': 'linux-generic',
238@@ -479,6 +411,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
239
240 def test_oem_xenial_4_15_kernel_stack(self):
241 jenkins_job_template = {
242+ 'cardonly': False,
243 'name': 'xenial-hwe-desktop-201802-26107',
244 'arch': 'amd64',
245 'kernel': 'linux-generic-hwe-16_04',
246@@ -500,6 +433,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
247
248 def test_oem_bionic_4_15_kernel_stack(self):
249 jenkins_job_template = {
250+ 'cardonly': False,
251 'name': 'bionic-desktop-201802-26107',
252 'arch': 'amd64',
253 'kernel': 'linux-oem',
254@@ -520,6 +454,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
255
256 def test_oem_osp1_bionic_4_15_kernel_stack(self):
257 jenkins_job_template = {
258+ 'cardonly': False,
259 'name': 'bionic-desktop-201906-27089',
260 'arch': 'amd64',
261 'kernel': 'linux-oem-osp1',
262@@ -541,6 +476,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
263
264 def test_argos_dgx_station_xenial_4_4(self):
265 jenkins_job_template = {
266+ 'cardonly': False,
267 'name': 'xenial-desktop-201711-25989',
268 'arch': 'amd64',
269 'kernel': 'linux-generic',
270@@ -567,6 +503,7 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
271
272 def test_argos_dgx_1_xenial_4_4(self):
273 jenkins_job_template = {
274+ 'cardonly': False,
275 'name': 'xenial-server-201802-26098',
276 'arch': 'amd64',
277 'kernel': 'linux-generic',
278@@ -593,10 +530,11 @@ class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
279
280 def test_argos_dgx_1_xenial_hwe(self):
281 jenkins_job_template = {
282+ 'cardonly': False,
283 'name': 'xenial-hwe-server-201802-26098',
284 'arch': 'amd64',
285 'kernel': 'linux-generic-hwe-16_04',
286- 'series': 'xenial-hwe',
287+ 'series': 'xenial',
288 'sru_type': 'oem',
289 'queue': 'argos-201802-26098',
290 'config': self.debs_yaml_stream,
291diff --git a/trello-board-updater-image-testing.py b/trello-board-updater-image-testing.py
292index ed1485b..ca97b92 100755
293--- a/trello-board-updater-image-testing.py
294+++ b/trello-board-updater-image-testing.py
295@@ -1,85 +1,31 @@
296 #!/usr/bin/env python3
297-# Copyright 2019 Canonical Ltd.
298+# Copyright 2019-2020 Canonical Ltd.
299 # All rights reserved.
300 #
301 # Written by:
302 # Paul Larson <paul.larson@canonical.com>
303+# Sylvain Pineau <sylvain.pineau@canonical.com>
304
305 import argparse
306+import importlib
307 import os
308 import re
309
310 from datetime import datetime
311 from trello import TrelloClient
312
313-
314-def environ_or_required(key):
315- if os.environ.get(key):
316- return {'default': os.environ.get(key)}
317- else:
318- return {'required': True}
319-
320-
321-def search_card(board, query, card_filter="open"):
322- for card in board.get_cards(card_filter=card_filter):
323- if re.match(query, card.name):
324- return card
325-
326-
327-def archive_card(card):
328- print('Archiving old card:', card)
329- card.set_closed(True)
330-
331-
332-def find_or_create_checklist(card, checklist_name, items=None):
333- existing_checklists = card.fetch_checklists()
334- checklist = None
335- for c in existing_checklists:
336- if c.name == checklist_name:
337- checklist = c
338- break
339- if not checklist:
340- checklist = card.add_checklist(checklist_name, [])
341- if items:
342- for item in items:
343- checklist.add_checklist_item(item + ' (NO RESULTS)')
344- return checklist
345-
346-
347-def change_checklist_item(checklist, name, checked=False):
348- # keep the trailing space so that we don't match the wrong thing later
349- r = re.match('\[*(.* )\(', name)
350- if r:
351- device_name = r.group(1)
352- for item in checklist.items:
353- if device_name in item.get('name'):
354- checklist.rename_checklist_item(item.get('name'), name)
355- checklist.set_checklist_item(name, checked)
356- return True
357- else:
358- return False
359- else:
360- print('WARNING: Invalid name specified', name)
361-
362-
363-def no_new_fails_or_skips(summary_data):
364- """Check summary data for new fails or skips
365-
366- Return True if there are no new fails or skips detected and if all
367- tests passed
368- """
369- return ("No new failed or skipped tests" in summary_data and
370- "All tests passed" in summary_data)
371+tbu = importlib.import_module("trello-board-updater")
372+tbm = importlib.import_module("trello-board-manager")
373
374
375 def main():
376 parser = argparse.ArgumentParser()
377 parser.add_argument('--key', help="Trello API key",
378- **environ_or_required('TRELLO_API_KEY'))
379+ **tbu.environ_or_required('TRELLO_API_KEY'))
380 parser.add_argument('--token', help="Trello OAuth token",
381- **environ_or_required('TRELLO_TOKEN'))
382+ **tbu.environ_or_required('TRELLO_TOKEN'))
383 parser.add_argument('--board', help="Trello board identifier",
384- **environ_or_required('TRELLO_BOARD'))
385+ **tbu.environ_or_required('TRELLO_BOARD'))
386 parser.add_argument('-n', '--name', help="SUT device name", required=True)
387 parser.add_argument('-i', '--image', help="image name", required=True)
388 parser.add_argument('-c', '--channel', help="image name", default="stable")
389@@ -96,14 +42,14 @@ def main():
390 re.escape(args.channel),
391 re.escape(args.version))
392 # First, see if this exact card already exists
393- card = search_card(board, pattern)
394+ card = tbu.search_card(board, pattern)
395
396 # If not, see if there's an older one for this image
397 if not card:
398 pattern = "{} - {} - .*".format(re.escape(args.image), args.channel)
399- card = search_card(board, pattern)
400+ card = tbu.search_card(board, pattern)
401 if card:
402- archive_card(card)
403+ tbm.archive_card(card)
404 # If we get here, then either we just archived the old card, or
405 # it didn't exist. We need to create it either way
406 lane = None
407@@ -122,19 +68,21 @@ def main():
408 summary_data = args.summary.read()
409 summary += summary_data
410 summary += '\n```\n'
411- card.comment(summary)
412- checklist = find_or_create_checklist(card, 'Testflinger')
413- item_name = "{} ({})".format(args.name, datetime.utcnow().isoformat())
414+ comment = card.comment(summary)
415+ comment_link = "{}#comment-{}".format(card.url, comment['id'])
416+ checklist = tbu.find_or_create_checklist(card, 'Testflinger')
417+ item_content = "[{}]({}) ({})".format(
418+ args.name, comment_link, datetime.utcnow().isoformat())
419 if jenkins_link:
420- item_name += " [[JENKINS]({})]".format(jenkins_link)
421+ item_content += " [[JENKINS]({})]".format(jenkins_link)
422 if c3_link:
423- item_name += " [[C3]({})]".format(c3_link)
424+ item_content += " [[C3]({})]".format(c3_link)
425
426- if not change_checklist_item(
427- checklist, item_name,
428- checked=no_new_fails_or_skips(summary_data)):
429+ if not tbu.change_checklist_item(
430+ checklist, args.name, item_content,
431+ checked=tbu.no_new_fails_or_skips(summary_data)):
432 checklist.add_checklist_item(
433- item_name, checked=no_new_fails_or_skips(summary_data))
434+ item_content, checked=tbu.no_new_fails_or_skips(summary_data))
435
436
437 if __name__ == "__main__":

Subscribers

People subscribed via source and target branches