canonical-hw-cert team mailing list archive
-
canonical-hw-cert team
-
Mailing list archive
-
Message #18551
[Merge] ~jocave/hwcert-jenkins-tools:remove-trello-scripts into hwcert-jenkins-tools:master
Jonathan Cave has proposed merging ~jocave/hwcert-jenkins-tools:remove-trello-scripts into hwcert-jenkins-tools:master.
Requested reviews:
Canonical Hardware Certification (canonical-hw-cert)
For more details, see:
https://code.launchpad.net/~jocave/hwcert-jenkins-tools/+git/hwcert-jenkins-tools/+merge/429214
Remove the trello scripts from this repo. They are now hosted in this standalone project: https://github.com/canonical/certification-dashboard-manager
--
Your team Canonical Hardware Certification is requested to review the proposed merge of ~jocave/hwcert-jenkins-tools:remove-trello-scripts into hwcert-jenkins-tools:master.
diff --git a/trello-board-manager-desktop.py b/trello-board-manager-desktop.py
deleted file mode 100755
index 15c9c00..0000000
--- a/trello-board-manager-desktop.py
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2019 Canonical Ltd
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# Written by:
-# Taihsiang Ho <taihsiang.ho@xxxxxxxxxxxxx>
-#
-#
-# The script uses py-trello package 0.9.0. You may want to fetch it from
-# source.
-#
-# This script will move cards between different lanes (-updates and -proposed)
-
-import sys
-import argparse
-import logging
-import os
-import re
-import requests
-import yaml
-
-from urllib.parse import urlparse
-from trello import TrelloClient
-
-
-repositories = ['proposed', 'updates']
-
-repository_promotion_map = {
- # repository -> next-repository
- 'proposed': 'updates'
-}
-
-codename_map = {'xenial': '16.04',
- 'bionic': '18.04',
- 'cosmic': '18.10',
- 'disco': '19.04',
- 'focal': '20.04',
- 'groovy': '20.10',
- 'hirsute': '21.04'}
-
-logger = logging.getLogger("trello-board-manager-desktop")
-
-
-def environ_or_required(key):
- """Mapping for argparse to supply required or default from $ENV."""
- if os.environ.get(key):
- return {'default': os.environ.get(key)}
- else:
- return {'required': True}
-
-
-def archive_card(card):
- logger.info('Archiving old revision: {}'.format(card))
- card.set_closed(True)
-
-
-def move_card(config, lane_name, card):
- """Move trello cards according to the current deb repository."""
- logger.info('Card {} in {}'.format(card.name, lane_name))
- m = re.match(
- r"(?P<stack>.*?)(?:\s+\-\s+)(?P<package>.*?)(?:\s+\-\s+)"
- r"\((?P<version>.*?)\)", card.name)
- if m:
- arch = config[m.group("stack")]["arch"]
- stack = m.group("stack")
- codename = m.group("stack").split('-')[0]
-
- # TODO: you may need to update this mapping in the future when
- # the oem image is based on the other distro
- if codename == 'oem':
- codename = 'bionic'
- elif codename == 'oem_focal':
- codename = 'focal'
-
- logger.debug('arch: {}'.format(arch))
- logger.debug('stack: {}'.format(stack))
- logger.debug('codename: {}'.format(codename))
-
- for repo in repositories:
- logger.debug('Working in repo: {}'.format(repo))
-
- jenkins_link = os.environ.get('BUILD_URL', '')
- uri = urlparse(jenkins_link)
- jenkins_host = '{uri.scheme}://{uri.netloc}/'.format(uri=uri)
- logger.debug('jenkins_link: {}'.format(jenkins_link))
- logger.debug('jenkins_host: {}'.format(jenkins_host))
- # TODO: we could merge main and universe repositories from the source
- # jenkins jobs
- # linux-oem is in universe rather than main
- if 'oem-osp1' in stack and not codename == 'xenial':
- text_template = '{}/job/cert-package-data/lastSuccessfulBuild/artifact/{}-universe-{}-{}.json'
- else:
- # packages of generic kernels
- # projects using these kernels:
- # stock images
- # oem image - xenial
- # oem images - shipped with oem-4.13
- # argos dgx-1/dgx-station images
- text_template = '{}/job/cert-package-data/lastSuccessfulBuild/artifact/{}-main-{}-{}.json'
-
- package_json_url = text_template.format(jenkins_host,
- codename, arch, repo)
- response = requests.get(url=package_json_url)
- pkg_data = response.json()
-
- # stack_version_full, svf, for example 4.4.0.150.158
- if 'oem' in stack and '5.6' in card.name:
- svf = pkg_data['linux-oem-20_04']
- elif 'oem' in stack and '5.10' in card.name:
- svf = pkg_data['linux-oem-20_04b']
- elif 'oem' in stack and '5.13' in card.name:
- svf = pkg_data['linux-oem-20_04c']
- elif 'oem' in stack and '5.14' in card.name:
- svf = pkg_data['linux-oem-20_04d']
- else:
- svf = pkg_data['linux-generic']
-
- if 'hwe' in stack:
- svf = re.match(r'\d+.\d+.\d+.\d+.\d+',
- pkg_data['linux-generic-hwe-' + codename_map[codename].replace('.', '_')]).group(0)
- # I only want 4_4_0-150
- sv = svf[:svf.rfind('.')].replace('.', '-')
- stack_version = sv.replace('-', '_', 2)
- if 'oem-osp1' in stack:
- deb_kernel_image = 'linux-image-' + stack_version + '-oem-osp1'
- elif "oem" in stack:
- deb_kernel_image = 'linux-image-' + stack_version + '-oem'
- else:
- deb_kernel_image = 'linux-image-' + stack_version + '-generic'
-
- logger.debug('stack_version: {}'.format(stack_version))
- logger.debug('deb_kernel_image: {}'.format(deb_kernel_image))
-
- deb_version = pkg_data[deb_kernel_image]
-
- ori = next_repo = repository_promotion_map.get(lane_name)
-
- logger.debug('deb_version: {}'.format(deb_version))
- logger.debug('next_repo: {}'.format(next_repo))
- logger.debug('m.group("stack"): '
- '{} {}'.format(m.group("stack"),
- type(m.group("stack"))))
- logger.debug('m.group("package"): '
- '{} {}'.format(m.group("package"),
- type(m.group("package"))))
- logger.debug('m.group("version"): '
- '{} {}'.format(m.group("version"),
- type(m.group("version"))))
-
- if (repo == lane_name and deb_version != m.group("version")):
- archive_card(card)
- continue
- if (repo == next_repo and
- deb_kernel_image == m.group("package") and
- deb_version == m.group("version")):
- for l in card.board.open_lists():
- if ori.capitalize() == l.name:
- msg = 'Moving the card {} to {}'.format(card.name,
- l.name)
- logger.debug(msg)
- card.change_list(l.id)
- return
-
-
-def load_config(configfile):
- if not configfile:
- return []
- try:
- data = yaml.safe_load(configfile)
- except (yaml.parser.ParserError, yaml.scanner.ScannerError):
- print('ERROR: Error parsing', configfile.name)
- sys.exit(1)
- return data
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--key', help="Trello API key",
- **environ_or_required('TRELLO_API_KEY'))
- parser.add_argument('--token', help="Trello OAuth token",
- **environ_or_required('TRELLO_TOKEN'))
- parser.add_argument('--board', help="Trello board identifier",
- **environ_or_required('TRELLO_BOARD'))
- parser.add_argument('--debug', help="Enable the debug mode",
- action="store_true", default=False)
- parser.add_argument('config', help="snaps configuration",
- type=argparse.FileType())
- args = parser.parse_args()
-
- format_str = "[ %(funcName)s() ] %(message)s"
- if args.debug:
- logging.basicConfig(format=format_str, level=logging.DEBUG)
- else:
- logging.basicConfig(format=format_str)
-
- client = TrelloClient(api_key=args.key, token=args.token)
- board = client.get_board(args.board)
-
- config = load_config(args.config)
-
- for lane in board.list_lists():
- lane_name = lane.name.lower()
- if lane_name in repositories:
- for card in lane.list_cards():
- try:
- move_card(config, lane_name, card)
- except Exception:
- logger.warning("WARNING", exc_info=True)
- continue
-
-
-if __name__ == "__main__":
- main()
diff --git a/trello-board-manager.py b/trello-board-manager.py
deleted file mode 100755
index fa715d9..0000000
--- a/trello-board-manager.py
+++ /dev/null
@@ -1,131 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2017 Canonical Ltd
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# Written by:
-# Sylvain Pineau <sylvain.pineau@xxxxxxxxxxxxx>
-
-import argparse
-import logging
-import os
-import re
-import requests
-
-from trello import TrelloClient
-from yaml import safe_load
-from yaml.parser import ParserError
-
-
-channel_promotion_map = {
- # channel -> next-channel
- 'edge': 'beta',
- 'beta': 'candidate',
- 'candidate': 'stable',
- 'stable': 'stable',
-}
-
-
-def environ_or_required(key):
- """Mapping for argparse to supply required or default from $ENV."""
- if os.environ.get(key):
- return {'default': os.environ.get(key)}
- else:
- return {'required': True}
-
-
-def archive_card(card):
- print('Archiving old revision:', card)
- card.set_closed(True)
-
-
-def move_card(config, lane_name, card):
- """Move trello cards according to the snap current channel."""
- print(card.name)
- m = re.match(
- r"(?P<snap>.*?)(?:\s+\-\s+)(?P<version>.*?)(?:\s+\-\s+)"
- r"\((?P<revision>.*?)\)(?:\s+\-\s+\[(?P<track>.*?)\])?", card.name)
- if m:
- arch = config[m.group("snap")]["arch"]
- headers = {
- 'Snap-Device-Series': '16',
- 'Snap-Device-Architecture': arch,
- 'Snap-Device-Store': config[m.group("snap")]["store"],
- }
- req = requests.get(
- 'https://api.snapcraft.io/v2/'
- 'snaps/info/{}'.format(m.group("snap")),
- headers=headers)
- json_resp = req.json()
- track = m.group("track")
- if not track:
- track = 'latest'
- for channel_info in json_resp["channel-map"]:
- if (channel_info["channel"]["track"] == track and
- channel_info["channel"]["architecture"] == arch):
- risk = channel_info["channel"]["risk"]
- try:
- version = channel_info['version']
- revision = str(channel_info['revision'])
- except KeyError:
- continue
- ori = next_risk = channel_promotion_map[lane_name]
- # If the snap with this name, in this channel is a
- # differet revision, then this is old so archive it
- if (risk == lane_name and
- revision != m.group("revision")):
- archive_card(card)
- continue
- if (
- risk == next_risk and
- version == m.group("version") and
- revision == m.group("revision")
- ):
- for l in card.board.open_lists():
- if ori.capitalize() == l.name:
- card.change_list(l.id)
- return
-
-
-def main():
- logger = logging.getLogger("trello-board-manager")
- parser = argparse.ArgumentParser()
- parser.add_argument('--key', help="Trello API key",
- **environ_or_required('TRELLO_API_KEY'))
- parser.add_argument('--token', help="Trello OAuth token",
- **environ_or_required('TRELLO_TOKEN'))
- parser.add_argument('--board', help="Trello board identifier",
- **environ_or_required('TRELLO_BOARD'))
- parser.add_argument('config', help="snaps configuration",
- type=argparse.FileType())
- args = parser.parse_args()
- client = TrelloClient(api_key=args.key, token=args.token)
- board = client.get_board(args.board)
- try:
- config = safe_load(args.config)
- except ParserError:
- raise SystemExit('Error parsing %s' % args.config.name)
- for lane in board.list_lists():
- lane_name = lane.name.lower()
- if lane_name in channel_promotion_map.keys():
- for card in lane.list_cards():
- try:
- move_card(config, lane_name, card)
- except Exception:
- logger.warn("WARNING", exc_info=True)
- continue
-
-
-if __name__ == "__main__":
- main()
diff --git a/trello-board-updater-desktop.py b/trello-board-updater-desktop.py
deleted file mode 100755
index 719fc92..0000000
--- a/trello-board-updater-desktop.py
+++ /dev/null
@@ -1,623 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#
-# Copyright 2019 Canonical Ltd.
-# All rights reserved.
-#
-# Written by:
-# Taihsiang Ho <taihsiang.ho@xxxxxxxxxxxxx>
-# Sylvain Pineau <sylvain.pineau@xxxxxxxxxxxxx>
-#
-#
-# The script uses py-trello package 0.9.0. You may want to fetch it from
-# source.
-#
-# This script will create or update corresponding cards for each kernel and
-# SUTs
-#
-import argparse
-import importlib
-import os
-import re
-import requests
-import sys
-import yaml
-import logging
-import collections
-
-import unittest
-import json
-import trello
-
-from urllib.parse import urlparse
-from datetime import datetime
-from trello import TrelloClient
-from trello.exceptions import ResourceUnavailable
-
-from unittest.mock import MagicMock
-
-tbu = importlib.import_module("trello-board-updater")
-
-format_str = "[ %(funcName)s() ] %(message)s"
-logging.basicConfig(level=logging.INFO, format=format_str)
-
-
-def run(args, board, c3_link, jenkins_link):
-
- kernel_stack = args.series
- if args.sru_type == 'stock-hwe':
- kernel_stack = args.series + '-hwe'
-
- # The current oem stack
- # For now it is bionic with linux-oem kernel
- # TODO: update the if statement when oem-osp1 is delivered
- if kernel_stack == 'bionic' and 'oem' in args.kernel:
- if 'osp1' in args.kernel:
- kernel_stack = 'oem-osp1'
- else:
- kernel_stack = 'oem_bionic'
- if kernel_stack == 'focal' and 'oem' in args.kernel:
- kernel_stack = 'oem_focal'
-
- uri = urlparse(jenkins_link)
- jenkins_host = '{uri.scheme}://{uri.netloc}/'.format(uri=uri)
-
- # TODO: we could merge main and universe repositories from the source
- # jenkins jobs
- # linux-raspi is in universe for bionic
- if args.series == 'bionic' and 'raspi' in args.kernel:
- package_json_name_template = '{}-universe-{}-proposed.json'
- else:
- # packages of generic kernels
- # projects using these kernels:
- # stock images
- # oem image - xenial
- # oem images - shipped with oem-4.13
- # argos dgx-1/dgx-station images
- package_json_name_template = '{}-main-{}-proposed.json'
-
- package_json_url_template = '{}/job/cert-package-data/'\
- 'lastSuccessfulBuild/artifact/' + \
- package_json_name_template
-
- package_json_url = package_json_url_template.format(jenkins_host,
- args.series,
- args.arch)
- logging.info('package json url: {}'.format(package_json_url))
- response = requests.get(url=package_json_url)
- package_data = response.json()
-
- # Since bionic-oem and bionic-oem-osp1 are retired, we should move oem item to generic card.
- # In order to distinguish them, we add a prefix to the item name.
- if 'oem' in args.kernel and 'oem' in args.sru_type and args.series == 'bionic':
- print(kernel_stack,args.kernel)
- kernel_stack = 'bionic-hwe'
- if 'osp1' in args.kernel:
- args.name = 'oem-osp1-'+ args.name + '-hwe'
- else:
- args.name = 'oem-'+ args.name + '-hwe'
- #since some focal-oem machines will upgrade to 5.8-hwe kernel, therefore we should move those item to focal-hwe card.
- if 'hwe' in args.kernel and 'oem' in args.sru_type and args.series == 'focal':
- print(kernel_stack,args.kernel)
- kernel_stack = 'focal-hwe'
- args.name = 'oem-'+ args.name + '-hwe'
-
- # linux deb version
- # e.g. linux-generic-hwe-16.04 which version is 4.15.0.50.71
- dlv = package_data[args.kernel].split('.')
- dlv_short = dlv[0] + '_' + dlv[1] + '_' + dlv[2] + '-' + dlv[3]
- logging.info("linux deb version: {}".format(dlv))
- logging.info("linux deb version (underscores): {}".format(dlv_short))
- kernel_suffix = kernel_stack.split('_')[0]
- if args.sru_type == 'stock' or args.sru_type == 'stock-hwe':
- # for stock images, it always uses generic kernels
- #
- # GA example:
- # linux-generic -->
- # linux-image-4_15_0-55-generic (4.15.0.55.57)
- #
- # hwe stack example:
- # linux-generic-hwe-18_04 -->
- # linux-image-5_0_0-21-generic (5.0.0-21.22~18.04.1)
- kernel_suffix = 'generic'
- elif args.sru_type == 'oem' and "xenial" in args.series:
- # very special case: oem xenial images
- # oem xenial 4.4 kernel is using generic kernel, besides,
- # some oem images are delivered as xenial + oem-4.13
- # when time goes by, it is updated to be xenial + generic xenial hwe
- #
- # this if condition includes the dgx-1 and dgx-station images
- #
- # TODO: we may need to add more conditions when more oem images
- # is updated to use generic kernel
- kernel_suffix = 'generic'
- elif args.sru_type == 'oem' and "bionic" in args.series:
- # bionic oem image has started using generic kernel.
- # bionic-oem and bionic-oem-osp1 will upgrade to 5.4 kernel.
- kernel_suffix = 'generic'
- elif args.sru_type == 'oem' and "focal" in args.series and 'hwe' in args.kernel:
- # some focal-oem machines will upgrade to 5.8-hwe kernel.
- kernel_suffix = 'generic'
-
- # For raspi kernels
- if 'raspi' in args.kernel:
- if "xenial" in args.series:
- kernel_suffix = 'raspi2'
- if "bionic" in args.series:
- if "hwe" in args.kernel:
- kernel_suffix = 'raspi'
- else:
- kernel_suffix = 'raspi2'
- else:
- kernel_suffix = 'raspi'
- kernel_stack = kernel_stack + '-' + kernel_suffix
-
- logging.info("kernel_suffix: {}".format(kernel_suffix))
-
- deb_kernel_image = 'linux-image-' + dlv_short + '-' + kernel_suffix
-
- deb_version = package_data[deb_kernel_image]
- pattern = "{} - {} - \({}\)".format(
- re.escape(kernel_stack),
- re.escape(deb_kernel_image),
- re.escape(deb_version))
- card = tbu.search_card(board, pattern)
- config = tbu.load_config(args.config, None)
- expected_tests = config.get(kernel_stack, {}).get('expected_tests', [])
-
- logging.info('SRU type: {}'.format(args.sru_type))
- logging.info('series: {}'.format(args.series))
- logging.info("kernel_stack: {}".format(kernel_stack))
- logging.info("deb_kernel_image: {}".format(deb_kernel_image))
- logging.info("deb_version: {}".format(deb_version))
- logging.info("expected_tests and SUTs: {}".format(expected_tests))
-
- lanes = ['Proposed', 'Updates']
-
- if not card:
- lane = None
- for l in board.open_lists():
- # TODO: not a reasonable condition, use better one later
- if l.name == lanes[0] and \
- package_data[deb_kernel_image] == deb_version:
- lane = l
- break
- if lane:
- print("No target card was found. Create an new one...")
- card = lane.add_card('{} - {} - ({})'.format(kernel_stack,
- deb_kernel_image,
- deb_version))
- else:
- print('No target card and lane was found. Give up to create an '
- 'new card.')
- sys.exit(1)
- if not args.cardonly:
- summary = '**[TESTFLINGER] {} {} {} {} ({})**\n---\n\n'.format(
- args.name, args.arch, args.kernel, deb_kernel_image, deb_version)
- summary += '- Jenkins build details: {}\n'.format(jenkins_link)
- summary += '- Full results at: {}\n\n```\n'.format(c3_link)
- summary_data = args.summary.read()
- summary += summary_data
- summary += '\n```\n'
- comment = card.comment(summary)
- comment_link = "{}#comment-{}".format(card.url, comment['id'])
- else:
- summary_data = ""
- checklist = tbu.find_or_create_checklist(card, 'Testflinger', expected_tests)
-
- # Need to test different arch on the same device, separate the results
- # TODO: We should support different arch for all devices, not just raspi
- if 'raspi' in args.kernel:
- args.name = args.name + '-' + args.arch
-
- if args.cardonly:
- item_content = "{} ({})".format(args.name, 'In progress')
- else:
- item_content = "[{}]({}) ({})".format(
- args.name, comment_link, datetime.utcnow().isoformat())
- if jenkins_link:
- item_content += " [[JENKINS]({})]".format(jenkins_link)
- if c3_link:
- item_content += " [[C3]({})]".format(c3_link)
- elif not args.cardonly:
- # If there was no c3_link, it's because the submission failed
- tbu.attach_labels(board, card, ['TESTFLINGER CRASH'])
-
- # debug message
- logging.info('checklist: {}'.format(checklist))
- logging.info('item_content: {}'.format(item_content))
- if not tbu.change_checklist_item(
- checklist, args.name, item_content,
- checked=tbu.no_new_fails_or_skips(summary_data)):
- checklist.add_checklist_item(item_content)
-
- if not [c for c in card.fetch_checklists() if c.name == 'Sign-Off']:
- checklist = tbu.find_or_create_checklist(card, 'Sign-Off')
- checklist.add_checklist_item('Ready for ' + lanes[0], True)
- checklist.add_checklist_item('Ready for ' + lanes[1])
- checklist.add_checklist_item('Can be Archived')
- checklist = tbu.find_or_create_checklist(card, 'Revisions')
- rev = '{} ({})'.format(deb_version, args.arch)
- if rev not in [item['name'] for item in checklist.items]:
- checklist.add_checklist_item(rev)
-
- # a read trello card object, useful for testing
- k_deb_card = collections.namedtuple("KernelDeb", ["kernel_stack",
- "deb_kernel_image",
- "deb_version",
- "expected_tests",
- "sut"])
- # card title
- k_deb_card.kernel_stack = kernel_stack
- k_deb_card.deb_kernel_image = deb_kernel_image
- k_deb_card.deb_version = deb_version
- # card content: SUTs
- k_deb_card.expected_tests = expected_tests
- k_deb_card.sut = args.name
-
- return k_deb_card
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--key', help="Trello API key",
- **tbu.environ_or_required('TRELLO_API_KEY'))
- parser.add_argument('--token', help="Trello OAuth token",
- **tbu.environ_or_required('TRELLO_TOKEN'))
- parser.add_argument('--board', help="Trello board identifier",
- **tbu.environ_or_required('TRELLO_BOARD'))
- parser.add_argument('--config', help="Pool configuration",
- type=argparse.FileType())
- parser.add_argument('-a', '--arch', help="deb architecture",
- required=True)
- parser.add_argument('-t', '--sru-type',
- help="SRU type, stock or oem etc.", required=True)
- parser.add_argument('-s', '--series',
- help="series code name, e.g. xenial etc.",
- required=True)
- parser.add_argument('-n', '--name', help="SUT name", required=True)
- parser.add_argument('-k', '--kernel', help="kernel type", required=True)
- # TODO: for now it is not required because I want to make it backward
- # compatible. If we could batch update the corresponding jenkins jobs then
- # we could make this argument required.
- parser.add_argument('-q', '--queue', help="kernel type", default="")
- parser.add_argument('summary', help="test results summary",
- type=argparse.FileType())
- parser.add_argument("--cardonly", help="Only create an empty card",
- action="store_true")
- args = parser.parse_args()
- client = TrelloClient(api_key=args.key, token=args.token)
- board = client.get_board(args.board)
- c3_link = os.environ.get('C3LINK', '')
- jenkins_link = os.environ.get('BUILD_URL', '')
-
- run(args, board, c3_link, jenkins_link)
-
-
-class TestTrelloUpdaterKernelDebSRU(unittest.TestCase):
-
- def _request_get(self):
- return requests.models.Response()
-
- def _get_package_data(self):
- with open(self.packages_info) as f:
- data = json.load(f)
-
- return data
-
- def _get_cards(self, board, card_id, name):
- card = trello.card.Card(board, card_id, name=name)
- return [card]
-
- def _mock_factory(self, jenkins_job_template, card_name, packages_info):
- parser = argparse.ArgumentParser()
- args = parser.parse_args([])
- args.__dict__.update(jenkins_job_template)
-
- self.args = args
- self.board = trello.board.Board("fake_board")
- self.c3_link = "fake_c3_link"
- self.jenkins_link = "fake_jenkins_link"
- self.packages_info = packages_info
-
- requests.get = MagicMock(return_value=self._request_get())
- requests.models.Response.json = MagicMock(
- side_effect=self._get_package_data)
- trello.board.Board.get_cards = MagicMock(return_value=self._get_cards(
- self.board,
- 9999,
- card_name))
- trello.board.Card.comment = MagicMock(return_value="fake_comment")
- trello.board.Card.fetch_checklists = MagicMock(
- return_value=[])
- mock_checklist = MagicMock()
- mock_checklist.add_checklist_item = print
-
- trello.board.Card.add_checklist = MagicMock(
- return_value=mock_checklist)
-
- def setUp(self):
- self.debs_yaml_stream = open('./data/debs.yaml')
- self.summary_stream = open('./data/raw_summary')
-
- def tearDown(self) -> None:
- self.debs_yaml_stream.close()
- self.summary_stream.close()
-
- def test_stock_xenial_4_4_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-desktop-201606-22344',
- 'arch': 'amd64',
- 'kernel': 'linux-generic',
- 'series': 'xenial',
- 'sru_type': 'stock',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial - linux-image-4_4_0-167-generic - (4.4.0-167.196)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial")
-
- def test_stock_xenial_4_15_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-hwe-desktop-201606-22344',
- 'arch': 'amd64',
- 'kernel': 'linux-generic-hwe-16_04',
- 'series': 'xenial',
- 'sru_type': 'stock',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial-hwe - linux-image-4_15_0-66-generic " \
- "- (4.15.0-66.75~16.04.1)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial-hwe")
-
- def test_stock_bionic_4_15_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'bionic-desktop-201606-22344',
- 'arch': 'amd64',
- 'kernel': 'linux-generic',
- 'series': 'bionic',
- 'sru_type': 'stock',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "bionic - linux-image-4_15_0-67-generic - (4.15.0-67.76)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_bionic-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "bionic")
-
- def test_oem_xenial_4_4_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-desktop-201610-25144',
- 'arch': 'amd64',
- 'kernel': 'linux-generic',
- 'series': 'xenial',
- 'sru_type': 'oem',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial - linux-image-4_4_0-167-generic - (4.4.0-167.196)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial")
-
- def test_oem_xenial_4_15_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-hwe-desktop-201802-26107',
- 'arch': 'amd64',
- 'kernel': 'linux-generic-hwe-16_04',
- 'series': 'xenial',
- 'sru_type': 'oem',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial-hwe - linux-image-4_15_0-66-generic " \
- "- (4.15.0-66.75~16.04.1)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial-hwe")
-
- def test_oem_bionic_4_15_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'bionic-desktop-201802-26107',
- 'arch': 'amd64',
- 'kernel': 'linux-oem',
- 'series': 'bionic',
- 'sru_type': 'oem',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "oem - linux-image-4_15_0-1059-oem - (4.15.0-1059.68)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_bionic-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "oem")
-
- def test_oem_osp1_bionic_4_15_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'bionic-desktop-201906-27089',
- 'arch': 'amd64',
- 'kernel': 'linux-oem-osp1',
- 'series': 'bionic',
- 'sru_type': 'oem',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "oem-osp1 - linux-image-5_0_0-1025-oem-osp1 " \
- "- (5.0.0-1025.28)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_bionic-universe-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "oem-osp1")
-
- def test_argos_dgx_station_xenial_4_4(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-desktop-201711-25989',
- 'arch': 'amd64',
- 'kernel': 'linux-generic',
- 'series': 'xenial',
- 'sru_type': 'oem',
- 'queue': 'argos-201711-25989',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial - linux-image-4_4_0-167-generic - (4.4.0-167.196)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- target_sut = "201711-25989-dgx-station"
- target_sut_kdc_id = kdeb_card.expected_tests.index(target_sut)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial")
- self.assertEqual(kdeb_card.sut, target_sut)
- self.assertEqual(kdeb_card.expected_tests[target_sut_kdc_id],
- target_sut)
-
- def test_argos_dgx_1_xenial_4_4(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-server-201802-26098',
- 'arch': 'amd64',
- 'kernel': 'linux-generic',
- 'series': 'xenial',
- 'sru_type': 'oem',
- 'queue': 'argos-201802-26098',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial - linux-image-4_4_0-167-generic - (4.4.0-167.196)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- target_sut = "201802-26098-dgx-1"
- target_sut_kdc_id = kdeb_card.expected_tests.index(target_sut)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial")
- self.assertEqual(kdeb_card.sut, target_sut)
- self.assertEqual(kdeb_card.expected_tests[target_sut_kdc_id],
- target_sut)
-
- def test_argos_dgx_1_xenial_hwe(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'xenial-hwe-server-201802-26098',
- 'arch': 'amd64',
- 'kernel': 'linux-generic-hwe-16_04',
- 'series': 'xenial',
- 'sru_type': 'oem',
- 'queue': 'argos-201802-26098',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "xenial-hwe - linux-image-4_15_0-66-generic " \
- "- (4.15.0-66.75~16.04.1)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_xenial-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- target_sut = "201802-26098-dgx-1"
- target_sut_kdc_id = kdeb_card.expected_tests.index(target_sut)
-
- self.assertEqual(kdeb_card.kernel_stack, "xenial-hwe")
- self.assertEqual(kdeb_card.sut, target_sut)
- self.assertEqual(kdeb_card.expected_tests[target_sut_kdc_id],
- target_sut)
-
- def test_oem_focal_5_6_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'focal-desktop-xps13-9310-c1',
- 'arch': 'amd64',
- 'kernel': 'linux-oem',
- 'series': 'focal',
- 'sru_type': 'oem',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "oem_focal - linux-image-oem-20_04 - (5.6.0.1034.30)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_focal-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "oem")
-
- def test_stock_focal_5_4_kernel_stack(self):
- jenkins_job_template = {
- 'cardonly': False,
- 'name': 'focal-desktop-inspiron20-3064',
- 'arch': 'amd64',
- 'kernel': 'linux-generic',
- 'series': 'focal',
- 'sru_type': 'stock',
- 'queue': '',
- 'config': self.debs_yaml_stream,
- 'summary': self.summary_stream
- }
- card_name = "focal - linux-image-5_4_0-54-generic - (5.4.0-54.60)"
-
- self._mock_factory(jenkins_job_template, card_name,
- "./data/deb-package_focal-main-amd64.json")
-
- kdeb_card = run(self.args, self.board, self.c3_link, self.jenkins_link)
-
- self.assertEqual(kdeb_card.kernel_stack, "focal")
-
-
-if __name__ == "__main__":
- main()
diff --git a/trello-board-updater-image-testing.py b/trello-board-updater-image-testing.py
deleted file mode 100755
index ca97b92..0000000
--- a/trello-board-updater-image-testing.py
+++ /dev/null
@@ -1,89 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2019-2020 Canonical Ltd.
-# All rights reserved.
-#
-# Written by:
-# Paul Larson <paul.larson@xxxxxxxxxxxxx>
-# Sylvain Pineau <sylvain.pineau@xxxxxxxxxxxxx>
-
-import argparse
-import importlib
-import os
-import re
-
-from datetime import datetime
-from trello import TrelloClient
-
-tbu = importlib.import_module("trello-board-updater")
-tbm = importlib.import_module("trello-board-manager")
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--key', help="Trello API key",
- **tbu.environ_or_required('TRELLO_API_KEY'))
- parser.add_argument('--token', help="Trello OAuth token",
- **tbu.environ_or_required('TRELLO_TOKEN'))
- parser.add_argument('--board', help="Trello board identifier",
- **tbu.environ_or_required('TRELLO_BOARD'))
- parser.add_argument('-n', '--name', help="SUT device name", required=True)
- parser.add_argument('-i', '--image', help="image name", required=True)
- parser.add_argument('-c', '--channel', help="image name", default="stable")
- parser.add_argument('-v', '--version', help="snap version", required=True)
- parser.add_argument('summary', help="test results summary",
- type=argparse.FileType())
- args = parser.parse_args()
- client = TrelloClient(api_key=args.key, token=args.token)
- board = client.get_board(args.board)
- c3_link = os.environ.get('C3LINK', '')
- jenkins_link = os.environ.get('BUILD_URL', '')
- pattern = "{} - {} - {}".format(
- re.escape(args.image),
- re.escape(args.channel),
- re.escape(args.version))
- # First, see if this exact card already exists
- card = tbu.search_card(board, pattern)
-
- # If not, see if there's an older one for this image
- if not card:
- pattern = "{} - {} - .*".format(re.escape(args.image), args.channel)
- card = tbu.search_card(board, pattern)
- if card:
- tbm.archive_card(card)
- # If we get here, then either we just archived the old card, or
- # it didn't exist. We need to create it either way
- lane = None
- for l in board.open_lists():
- if l.name == args.channel:
- lane = l
- break
- if not lane:
- lane = board.add_list(args.channel)
- card = lane.add_card('{} - {} - {}'.format(
- args.image, args.channel, args.version))
- summary = '**[TESTFLINGER] {} {} {}**\n---\n\n'.format(
- args.name, args.image, args.version)
- summary += '- Jenkins build details: {}\n'.format(jenkins_link)
- summary += '- Full results at: {}\n\n```\n'.format(c3_link)
- summary_data = args.summary.read()
- summary += summary_data
- summary += '\n```\n'
- comment = card.comment(summary)
- comment_link = "{}#comment-{}".format(card.url, comment['id'])
- checklist = tbu.find_or_create_checklist(card, 'Testflinger')
- item_content = "[{}]({}) ({})".format(
- args.name, comment_link, datetime.utcnow().isoformat())
- if jenkins_link:
- item_content += " [[JENKINS]({})]".format(jenkins_link)
- if c3_link:
- item_content += " [[C3]({})]".format(c3_link)
-
- if not tbu.change_checklist_item(
- checklist, args.name, item_content,
- checked=tbu.no_new_fails_or_skips(summary_data)):
- checklist.add_checklist_item(
- item_content, checked=tbu.no_new_fails_or_skips(summary_data))
-
-
-if __name__ == "__main__":
- main()
diff --git a/trello-board-updater.py b/trello-board-updater.py
deleted file mode 100755
index 9d9af50..0000000
--- a/trello-board-updater.py
+++ /dev/null
@@ -1,318 +0,0 @@
-#!/usr/bin/env python3
-# Copyright 2017 Canonical Ltd.
-# All rights reserved.
-#
-# Written by:
-# Sylvain Pineau <sylvain.pineau@xxxxxxxxxxxxx>
-
-import argparse
-import os
-import re
-import requests
-import sys
-import time
-import yaml
-
-from datetime import datetime
-from trello import TrelloClient
-from trello.exceptions import ResourceUnavailable
-from mailtool import send_mail
-
-
-def environ_or_required(key):
- if os.environ.get(key):
- return {'default': os.environ.get(key)}
- else:
- return {'required': True}
-
-
-def search_card(board, query, card_filter="open"):
- for card in board.get_cards(card_filter=card_filter):
- if re.match(query, card.name):
- return card
-
-
-def find_or_create_checklist(card, checklist_name, items=[]):
- existing_checklists = card.fetch_checklists()
- checklist = None
- for c in existing_checklists:
- if c.name == checklist_name:
- checklist = c
- break
- if not checklist:
- print("Creating checklist: {}".format(checklist_name))
- checklist = card.add_checklist(checklist_name, [])
- for item in items:
- checklist.add_checklist_item(item + ' (NO RESULTS)')
- return checklist
-
-
-def change_checklist_item(checklist, name, content, checked=False):
- """Attempt to rename and change details on a checklist item
-
- Return True if the item is located and changes are made
- Return False if no matching item was found in the specified checklist
-
- Since some cards have multiple results checklists, a False return
- may be expected in many cases
- """
- for item in checklist.items:
- if (
- name + ' ' in item.get('name') or
- name + ']' in item.get('name')
- ):
- checklist.rename_checklist_item(item.get('name'), content)
- checklist.set_checklist_item(content, checked)
- return True
- else:
- return False
-
-
-def no_new_fails_or_skips(summary_data):
- """Check summary data for new fails or skips
-
- Return True if there are no new fails or skips detected and if
- more than 1 test actually ran as a sanity check
- """
- return ("No new failed or skipped tests" in summary_data and
- "WARNING: Very small number of total tests" not in summary_data)
-
-
-def load_config(configfile, snapname):
- if not configfile:
- return []
- try:
- data = yaml.safe_load(configfile)
- except (yaml.parser.ParserError, yaml.scanner.ScannerError):
- print('ERROR: Error parsing', configfile.name)
- sys.exit(1)
- return data
-
-
-def attach_labels(board, card, label_list):
- for labelstr in label_list:
- for label in board.get_labels():
- if label.name == labelstr:
- labels = card.labels or []
- if label not in labels:
- # Avoid crash if checking labels fails to find it
- try:
- card.add_label(label)
- except ResourceUnavailable:
- pass
- break
-
-
-def main():
- parser = argparse.ArgumentParser()
- parser.add_argument('--key', help="Trello API key",
- **environ_or_required('TRELLO_API_KEY'))
- parser.add_argument('--token', help="Trello OAuth token",
- **environ_or_required('TRELLO_TOKEN'))
- parser.add_argument('--board', help="Trello board identifier",
- **environ_or_required('TRELLO_BOARD'))
- parser.add_argument('--config', help="Snaps configuration",
- type=argparse.FileType())
- parser.add_argument('-a', '--arch', help="snap architecture",
- required=True)
- parser.add_argument('-b', '--brandstore', help="brand store identifier",
- default='ubuntu')
- parser.add_argument('-e', '--email', help="Email recipients")
- parser.add_argument('-n', '--name', help="SUT name", required=True)
- parser.add_argument('-s', '--snap', help="snap name", required=True)
- parser.add_argument('-v', '--version', help="snap version", required=True)
- parser.add_argument('-r', '--revision', help="snap revision",
- required=True)
- parser.add_argument('-c', '--channel', help="snap channel", required=True)
- parser.add_argument('-t', '--track', help="snap track", required=True)
- parser.add_argument('summary', help="test results summary",
- type=argparse.FileType())
- parser.add_argument("--cardonly", help="Only create an empty card",
- action="store_true")
- args = parser.parse_args()
- client = TrelloClient(api_key=args.key, token=args.token)
- board = client.get_board(args.board)
- track = args.track.replace('__track__', '')
- c3_link = os.environ.get('C3LINK', '')
- jenkins_link = os.environ.get('BUILD_URL', '')
- pattern = "{} - {} - \({}\).*{}".format(
- re.escape(args.snap),
- re.escape(args.version),
- args.revision,
- re.escape(track))
- card = search_card(board, pattern)
- config = load_config(args.config, args.snap)
- snap_labels = config.get(args.snap, {}).get('labels', [])
- # Try to find the right card using other arch revision numbers
- # i.e. We could be recording a result for core rev 111 on armhf, but we
- # want to record it against the core rev 110 results with amd64, so find
- # that card by finding the arch/rev from the store to use in the search
- if not card:
- rev_list = dict()
- headers = {
- 'Snap-Device-Series': '16',
- 'Snap-Device-Store': args.brandstore,
- }
- try:
- json = requests.get(
- 'https://api.snapcraft.io/v2/'
- 'snaps/info/{}'.format(args.snap),
- headers=headers).json()
- except Exception:
- # Sometimes either the request is bad or it returns something that
- # .json() doesn't like. If it's a short-term issue, maybe we can
- # give it one more chance to pass
- print("WARNING: Bad store request, retrying...")
- time.sleep(30)
- json = requests.get(
- 'https://api.snapcraft.io/v2/'
- 'snaps/info/{}'.format(args.snap),
- headers=headers).json()
- # store_track is used for searching for the right track name in the
- # store, which would be latest if nothing is defined
- # track is used for search in the cards, which will be either the
- # default_track, the defined track for the run, or empty for 'latest'
- track = config.get(args.snap, {}).get('default_track', track)
- store_track = track or "latest"
- for channel_info in json['channel-map']:
- try:
- if channel_info['version'] != args.version:
- continue
- if (channel_info["channel"]["track"] == store_track and
- channel_info["channel"]["risk"] == args.channel):
- arch = channel_info["channel"]["architecture"]
- rev_list[arch] = channel_info['revision']
- except KeyError:
- continue
- # Try to find the right revision for the right architecture when a
- # channel follows the next one meaning nothing was pushed to it, e.g:
- #
- # channels:
- # stable: 0.7.1 2018-12-11 (329) 387MB -
- # candidate: ↑
- # beta: ↑
- if not rev_list:
- for risk in ['edge', 'beta', 'candidate', 'stable']:
- for channel_info in json['channel-map']:
- try:
- if channel_info['version'] != args.version:
- continue
- if channel_info["channel"]["track"] == store_track:
- if channel_info["channel"]["risk"] == risk:
- arch = channel_info["channel"]["architecture"]
- rev_list[arch] = channel_info['revision']
- except KeyError:
- continue
- if rev_list:
- break
- for rev in rev_list.values():
- pattern = "{} - {} - \({}\).*{}".format(
- re.escape(args.snap),
- re.escape(args.version),
- rev,
- re.escape(track))
- card = search_card(board, pattern)
- if card:
- # Prefer amd64 rev in card title
- if args.arch == 'amd64':
- if track:
- card.set_name('{} - {} - ({}) - [{}]'.format(
- args.snap, args.version, args.revision,
- track))
- else:
- card.set_name('{} - {} - ({})'.format(
- args.snap, args.version, args.revision))
- break
- print('Using card: {} ({})'.format(card.name, card.short_url))
- # Create the card in the right lane, since we still didn't find it
- # We only want one card for all architectures, so use the revision
- # declared for the default arch in snaps.yaml
- if not card:
- default_arch = config.get(args.snap, {}).get('arch', args.arch)
- default_rev = rev_list.get(default_arch, args.revision)
- channel = args.channel.capitalize()
- lane = None
- # Use the default_track if there is one, else use track name specified
- track = config.get(args.snap, {}).get('default_track', track)
- for l in board.open_lists():
- if channel == l.name:
- lane = l
- break
- if lane:
- if track:
- card = lane.add_card('{} - {} - ({}) - [{}]'.format(
- args.snap, args.version, default_rev, track))
- else:
- card = lane.add_card('{} - {} - ({})'.format(
- args.snap, args.version, default_rev))
- print('No card found!')
- print('Creating card: {} ({})'.format(card.name, card.short_url))
- if args.email:
- msg_subject = '[SUV-RESULT] {}'.format(card.name)
- msg_body = ('New results posted for {} version {}\n\n'
- '{}\n{}'.format(args.snap, args.version, card.name, card.short_url))
- try:
- send_mail(to=args.email, subject=msg_subject, body=msg_body)
- except Exception as e:
- print('WARNING: Unable to send email: {}'.format(e))
- if not args.cardonly:
- summary = '**[TESTFLINGER] {} {} {} ({}) {}**\n---\n\n'.format(
- args.name, args.snap, args.version, args.revision, args.channel)
- summary += '- Jenkins build details: {}\n'.format(jenkins_link)
- summary += '- Full results at: {}\n\n```\n'.format(c3_link)
- summary_data = args.summary.read()
- summary += summary_data
- summary += '\n```\n'
- comment = card.comment(summary)
- comment_link = "{}#comment-{}".format(card.url, comment['id'])
- else:
- summary_data = ""
- attach_labels(board, card, snap_labels)
- card_checklists = config.get(args.snap, {}).get('checklists', {})
- for checklist_name in card_checklists.keys():
- # expected_tests section in the yaml could be either a list of all
- # devices we want to see tests results on, or a dict of tracks with
- # those devices specified in a list under each track (ex. pi-kernel)
- expected_tests = card_checklists.get(
- checklist_name, {}).get('expected_tests', [])
- if isinstance(expected_tests, dict):
- # If multiple tracks are defined, only look at expected tests
- # for the track we care about
- expected_tests = expected_tests.get(track, [])
- checklist = find_or_create_checklist(
- card, checklist_name, expected_tests)
- if args.cardonly:
- item_content = "{} ({})".format(args.name, 'In progress')
- else:
- item_content = "[{}]({}) ({})".format(
- args.name, comment_link, datetime.utcnow().isoformat())
- if jenkins_link:
- item_content += " [[JENKINS]({})]".format(jenkins_link)
- if c3_link:
- item_content += " [[C3]({})]".format(c3_link)
- elif not args.cardonly:
- # If there was no c3_link, it's because the submission failed
- attach_labels(board, card, ['TESTFLINGER CRASH'])
-
- change_checklist_item(
- checklist, args.name, item_content,
- checked=no_new_fails_or_skips(summary_data))
-
- if not [c for c in card.fetch_checklists() if c.name == 'Sign-Off']:
- checklist = find_or_create_checklist(card, 'Sign-Off')
- checklist.add_checklist_item('Clear for Landing', True)
- checklist.add_checklist_item('Ready for Edge', True)
- checklist.add_checklist_item('Ready for Beta')
- if args.channel == 'beta':
- checklist.set_checklist_item('Ready for Beta', True)
- checklist.add_checklist_item('Ready for Candidate')
- checklist.add_checklist_item('Ready for Stable')
- checklist.add_checklist_item('Can be Archived')
- checklist = find_or_create_checklist(card, 'Revisions')
- rev = '{} ({})'.format(args.revision, args.arch)
- if rev not in [item['name'] for item in checklist.items]:
- checklist.add_checklist_item(rev)
-
-
-if __name__ == "__main__":
- main()
diff --git a/trello-sru-bug-manager.py b/trello-sru-bug-manager.py
deleted file mode 100755
index 6f0159b..0000000
--- a/trello-sru-bug-manager.py
+++ /dev/null
@@ -1,383 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2018 Canonical Ltd
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License version 3 as
-# published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-# Written by:
-# Paul Larson <paul.larson@xxxxxxxxxxxxx>
-
-import argparse
-import datetime
-import os
-import re
-import requests
-import sys
-import traceback
-import json
-
-from launchpadlib.launchpad import Launchpad, uris
-from trello import TrelloClient
-from trello.exceptions import ResourceUnavailable
-
-
-class LPHelper:
- def __init__(self, credentials, project, staging):
- lp_server = uris.STAGING_SERVICE_ROOT if staging else uris.LPNET_SERVICE_ROOT
- self.lp = Launchpad.login_with(
- sys.argv[0], lp_server, credentials_file=credentials)
- self.project = self.lp.projects(project)
- self.srubugs = []
-
- def _build_sru_bugs(self):
- """Find a bug in this project with the specified search_text"""
- if not self.srubugs:
- try:
- # Take the kernel sru bug status data and make a list out of it
- # so we can use it more easily
- url = ('https://kernel.ubuntu.com/~kernel-ppa/status/'
- 'swm/status.json')
- bugdata = json.loads(requests.get(url).content)['trackers']
- for x in bugdata:
- # Add a 'bug' field that we can use later
- bugdata[x]['bug'] = x
- self.srubugs.append(bugdata[x])
- except Exception:
- print('ERROR: Importing bug data from {}'.format(url))
-
- def find_sru_bug(self, target, version, target_type="snap"):
- self._build_sru_bugs()
-
- if target_type == "snap":
- return self.find_sru_bug_snap(target, version)
- else:
- return self.find_sru_bug_deb(target, version)
-
- def find_sru_bug_snap(self, snap, version):
- try:
- # Only match if there's a valid task for this snap name, and
- # version is the same
- for bug in self.srubugs:
- if (bug.get('version') == version and
- bug.get('task', {}).get(
- 'snap-certification-testing', {}).get(
- 'target') == snap):
- return SruBug(
- self.lp.bugs(bug.get('bug')),
- bug.get('cycle'))
- # If we get this far, no bug was found
- raise LookupError
- except Exception:
- traceback.print_exc()
- print('WARNING: something went wrong, but continuing with other cards...')
- raise LookupError
-
- def find_sru_bug_deb(self, stack, version):
- series = stack.split('-')[0]
- package = 'linux'
- if 'hwe' in stack:
- if not 'xenial' in series:
- package += '-hwe-' + version.split('.')[0] + '.' + version.split('.')[1]
- else:
- package += '-hwe'
- # TODO: this will evolve when time goes by
- elif 'oem' in stack:
- series = series.split('_')[1]
- package += '-oem-' + version.split('.')[0] + '.' + version.split('.')[1]
-
- try:
- # Only match if there's a valid task for this stack name, and
- # version is the same
- for bug in self.srubugs:
- if (
- bug.get('version') == version and
- bug.get('package') == package and
- bug.get('series') == series and
- bug.get('variant') == 'debs' and
- bug.get('task').get("kernel-sru-workflow").get('status') == 'In Progress'
- ):
- return SruBug(
- self.lp.bugs(bug.get('bug')),
- bug.get('cycle'))
- # If we get this far, no bug was found
- raise LookupError
- except Exception:
- traceback.print_exc()
- print('WARNING: something went wrong, but continuing with other cards...')
- raise LookupError
-
-
-class TrelloHelper:
- def __init__(self, api_key, token, board):
- self.client = TrelloClient(api_key=api_key, token=token)
- self.board = self.client.get_board(board)
-
- def _get_lane(self, lane_id):
- """Search (case insensitive) for a lane called <lane_id>"""
- for l in self.board.list_lists():
- lane_name = l.name.lower()
- if lane_name == lane_id:
- return l
- raise LookupError('Trello lane "{}" not found!'.format(lane_id))
-
- def search_cards_in_lane(self, lane_id, search_text):
- """Yield cards with search_text in lane_id one at a time"""
- lane = self._get_lane(lane_id)
- for card in lane.list_cards():
- if search_text in card.name:
- yield card
-
-
-class SruBug:
- """Simplify operations we care about on an LP bug"""
- def __init__(self, bug, cycle):
- self.bug = bug
- self.id = bug.id
- self.web_link = bug.web_link
- self.cycle = cycle
- self.calculate_due_date()
-
- def __repr__(self):
- return self.bug.title
-
- def calculate_due_date(self):
- try:
- cycle_parts = tuple(
- int(x) for x in self.cycle.split('-')[0].split('.'))
- cycle_start = datetime.datetime(*cycle_parts, hour=12)
- # Since cycle starts on a Monday, +11 days is the due date
- # (Friday of the 2nd week in the cycle)
- self.due_date = cycle_start + datetime.timedelta(days=11)
- except Exception:
- self.due_date = None
- traceback.print_exc()
- print("WARNING: Unable to set due date for {}".format(self.id))
-
- def get_task_state(self, task_name):
- for task in self.bug.bug_tasks:
- if task_name in str(task):
- return task
- raise LookupError
-
- def set_task_state(self, task_name, state):
- for task in self.bug.bug_tasks:
- if task_name in str(task):
- task.status = state
- task.lp_save()
-
- def add_comment(self, comment_text):
- self.bug.newMessage(content=comment_text)
-
-
-def environ_or_required(key):
- """Mapping for argparse to supply required or default from $ENV."""
- if os.environ.get(key):
- return {'default': os.environ.get(key)}
- else:
- return {'required': True}
-
-
-def attach_labels(board, card, label_list):
- for labelstr in label_list:
- for label in board.get_labels():
- if label.name == labelstr:
- labels = card.labels or []
- if label not in labels:
- # Avoid crash if checking labels fails to find it
- try:
- card.add_label(label)
- except ResourceUnavailable:
- pass
- break
-
-
-def get_checklist_value(checklist, key):
- """Return the value of an item in a Trello checklist"""
- index = checklist._get_item_index(key)
- return checklist.items[index]['checked']
-
-
-def get_card_snap_version(card):
- """Return snap name and version for a card, if formatted as expected"""
- m = re.match(
- r"(?P<snap>.*?)(?:\s+\-\s+)(?P<version>.*?)(?:\s+\-\s+)"
- r"\((?P<revision>.*?)\)(?:\s+\-\s+\[(?P<track>.*?)\])?", card.name)
- if m:
- return (m.group('snap'), m.group('version'))
- raise ValueError
-
-
-def get_card_deb_version(card):
- """Return snap name and version for a card, if formatted as expected"""
- m = re.match(
- r"(?P<stack>.*?)(?:\s+\-\s+)(?P<package>.*?)(?:\s+\-\s+)"
- r"\((?P<version>.*?)\)", card.name)
- if m:
- return (m.group('stack'), m.group('version'))
- raise ValueError
-
-
-def get_args():
- parser = argparse.ArgumentParser()
- parser.add_argument('--project', help="Launchpad project to search",
- **environ_or_required('LAUNCHPAD_PROJECT'))
- parser.add_argument('--credentials', help="Specify launchpad credentials",
- **environ_or_required('LAUNCHPAD_CREDENTIALS'))
- parser.add_argument('--key', help="Trello API key",
- **environ_or_required('TRELLO_API_KEY'))
- parser.add_argument('--token', help="Trello OAuth token",
- **environ_or_required('TRELLO_TOKEN'))
- parser.add_argument('--board', help="Trello board identifier",
- **environ_or_required('TRELLO_BOARD'))
- parser.add_argument('--deb', action='store_true',
- help='Work on deb instead of snap')
- parser.add_argument('--staging', action='store_true',
- help='Use staging Launchpad for testing this script')
- return parser.parse_args()
-
-
-def card_ready_for_candidate(card):
- """Return True if the signoff checkbox 'Ready for Candidate' is checked"""
- # Find the Sign-Off checklist
- try:
- checklist = [x for x in card.fetch_checklists()
- if x.name == 'Sign-Off'][0]
- except IndexError:
- print("WARNING: No Sign-Off checklist found!")
- return False
- return get_checklist_value(checklist, 'Ready for Candidate')
-
-
-def card_ready_for_updates(card):
- """Return True if the signoff checkbox 'Ready for Updates' is checked"""
- # Find the Sign-Off checklist
- try:
- checklist = [x for x in card.fetch_checklists()
- if x.name == 'Sign-Off'][0]
- except IndexError:
- print("WARNING: No Sign-Off checklist found!")
- return False
- return get_checklist_value(checklist, 'Ready for Updates')
-
-
-def process_snaps(lp, trello):
- print("Processing SRU snaps ready for promotion...")
- for card in trello.search_cards_in_lane('beta', '-kernel'):
- print('{} ({})'.format(card.name, card.short_url))
- # If we can't get version from title, it's not formatted how we
- # expect, so ignore it
- try:
- snap, version = get_card_snap_version(card)
- except ValueError:
- continue
-
- try:
- bug = lp.find_sru_bug(snap, version)
- except LookupError:
- print(
- 'No bug found for {} or bug is already closed'.format(version))
- continue
-
- add_bug_description(bug, card)
-
- # Add due date to the card
- if bug.due_date:
- card.set_due(bug.due_date)
-
- # Automatically mark our task "In Progress" if it's "Confirmed"
- TARGET_TASK = 'snap-certification-testing'
- if bug.get_task_state(TARGET_TASK).status == 'Confirmed':
- bug.set_task_state(TARGET_TASK, 'In Progress')
-
- if not card_ready_for_candidate(card):
- continue
-
- update_lp(bug, 'snap-certification-testing', card)
-
-
-def process_debs(lp, trello):
- print("Processing SRU kernel debs ready for Updates repository...")
- for card in trello.search_cards_in_lane('proposed', 'linux-image'):
- print('{} ({})'.format(card.name, card.short_url))
- # If we can't get version from title, it's not formatted how we
- # expect, so ignore it
- try:
- stack, version = get_card_deb_version(card)
- except ValueError:
- continue
-
- try:
- bug = lp.find_sru_bug(stack, version, 'deb')
- except LookupError:
- print(
- 'No bug found for {} or bug is already closed'.format(version))
- continue
-
- add_bug_description(bug, card)
-
- # Add due date to the card
- if bug.due_date:
- card.set_due(bug.due_date)
-
- # Automatically mark our task "In Progress" if it's still "Confirmed"
- TARGET_TASK = 'certification-testing'
- if bug.get_task_state(TARGET_TASK).status == 'Confirmed':
- bug.set_task_state(TARGET_TASK, 'In Progress')
-
- if card_ready_for_updates(card):
- attach_labels(trello.board, card, ['READY FOR UPDATES'])
- else:
- continue
-
- update_lp(bug, 'certification-testing', card)
-
-
-def add_bug_description(bug, card):
- desc = "[[{}]({})] - {}".format(bug.id, bug.web_link, bug)
- card.set_description(desc)
-
-
-def update_lp(bug, target_task, card):
- print('- LP:{}'.format(bug.id))
- # If the bug is already fix-released, there's nothing more to do
- TARGET_TASK = target_task
- try:
- if bug.get_task_state(TARGET_TASK).status == 'Fix Released':
- print('- {} task already completed.'.format(TARGET_TASK))
- return
- except LookupError:
- print('ERROR: No task called "{}" found!'.format(TARGET_TASK))
- return
-
- # This is the bug for the card we found, mark the task complete and
- # add a comment
- print('- Marking {} task complete.'.format(TARGET_TASK))
- bug.set_task_state(TARGET_TASK, 'Fix Released')
- comment = ("Kernel deb testing completes, no regressions found. Ready "
- "for Updates. Results here: {}".format(card.url))
- bug.add_comment(comment)
-
-
-def main():
- args = get_args()
- lp = LPHelper(args.credentials, args.project, args.staging)
- trello = TrelloHelper(args.key, args.token, args.board)
-
- if args.deb:
- process_debs(lp, trello)
- else:
- process_snaps(lp, trello)
-
-
-if __name__ == "__main__":
- main()
Follow ups