From 0de341a746cbba1cf5f14328ca8f7d301af86a3d Mon Sep 17 00:00:00 2001 From: geargyri Date: Apr 05 2022 09:35:57 +0000 Subject: New feature: command download-scratch-build added - anon_handle_download_scratch_build() method implements the command - test_download_scratch_build.py holds all testing for the new method - A few updates for supporting the new command --- diff --git a/cli/koji b/cli/koji index 87fd6f1..1eb9b6b 100755 --- a/cli/koji +++ b/cli/koji @@ -103,7 +103,7 @@ def load_plugins(plugin_paths): def get_options(): """process options from command line and config file""" - common_commands = ['build', 'help', 'download-build', + common_commands = ['build', 'help', 'download-build', 'download-scratch-build', 'latest-build', 'search', 'list-targets'] usage = "%%prog [global-options] command [command-options-and-arguments]\n\n" \ "Common commands: %s" % ', '.join(sorted(common_commands)) diff --git a/cli/koji_cli/commands.py b/cli/koji_cli/commands.py index 354cd1a..24a995b 100644 --- a/cli/koji_cli/commands.py +++ b/cli/koji_cli/commands.py @@ -7147,6 +7147,75 @@ def anon_handle_download_build(options, session, args): quiet=suboptions.quiet, noprogress=suboptions.noprogress) +def anon_handle_download_scratch_build(options, session, args): + # Read, filter and check arguments + "[download] Download a scratch built package" + usage = "usage: %prog download-scratch-build [options] " + parser = OptionParser(usage=get_usage_str(usage)) + parser.add_option("--logs", dest="logs", action="store_true", default=False, + help="Also download build logs") + parser.add_option("--topurl", metavar="URL", default=options.topurl, + help="URL under which Koji files are accessible") + parser.add_option("--noprogress", action="store_true", + help="Do not display progress meter") + parser.add_option("-q", "--quiet", action="store_true", + help="Suppress output", default=options.quiet) + (suboptions, args) = parser.parse_args(args) + + if len(args) < 1: + parser.error("Please specify a valid task id") + elif len(args) > 1: + parser.error("Only a single task id may be specified") + try: + taskid = int(args[0]) + except ValueError: + error("The argument %r cannot be a task-id number" % args[0]) + if not suboptions.topurl: + error("You must specify --topurl to download files") + + # Connect, check the id and download the builds + ensure_connection(session, options) + + def check_taskid(taskid): + taskinfo = session.getTaskInfo(taskid) + taskchildren = session.getTaskChildren(taskid) + if not taskinfo["parent"] and taskinfo["method"] == "image" and len(taskchildren) == 1: + # taskid belongs to a scratch image build parent task + taskid_upd = taskchildren[0]['id'] + return taskid_upd + else: + error("No such scratch image build") + + taskinfo = session.getTaskInfo(taskid) + taskchildren = session.getTaskChildren(taskid) + if taskinfo["method"] == "createImage" and len(taskchildren) == 0: + # taskid belongs to a child image task + taskid_upd = check_taskid(taskinfo["parent"]) + else: + taskid_upd = check_taskid(taskid) + + files = list_task_output_all_volumes(session, taskid_upd) + if not files: + error("No files were found for taskid %i" %taskid_upd) + + pi = koji.PathInfo(topdir=suboptions.topurl) + urls = [] + for filename in files: + if suboptions.logs: + for volume in files[filename]: + url = os.path.join(pi.task(taskid_upd), filename) + urls.append(url) + else: + if not filename.endswith('.log'): + for volume in files[filename]: + url = os.path.join(pi.task(taskid_upd), filename) + urls.append(url) + + for url in urls: + download_file(url, os.path.basename(url), quiet=suboptions.quiet, + noprogress=suboptions.noprogress, filesize=None) + + def anon_handle_download_logs(options, session, args): "[download] Download logs for task" diff --git a/tests/test_cli/data/list-commands.txt b/tests/test_cli/data/list-commands.txt index 329db13..73db58d 100644 --- a/tests/test_cli/data/list-commands.txt +++ b/tests/test_cli/data/list-commands.txt @@ -93,6 +93,7 @@ build commands: download commands: download-build Download a built package download-logs Download logs for task + download-scratch-build Download a scratch built package download-task Download the output of a build task info commands: diff --git a/tests/test_cli/test_download_scratch_build.py b/tests/test_cli/test_download_scratch_build.py new file mode 100644 index 0000000..9c20853 --- /dev/null +++ b/tests/test_cli/test_download_scratch_build.py @@ -0,0 +1,204 @@ +from __future__ import absolute_import + +import mock +from mock import call +import six +from six.moves import StringIO +import koji +from koji_cli.commands import anon_handle_download_scratch_build +from . import utils + + +class TestDownloadScratchBuild(utils.CliTestCase): + # Show long diffs in error output... + maxDiff = None + + def setUp(self): + self.options = mock.MagicMock() + self.options.quiet = False + self.options.topurl = 'https://topurl' + self.session = mock.MagicMock() + self.session.getAPIVersion.return_value = koji.API_VERSION + self.stdout = mock.patch('sys.stdout', new_callable=six.StringIO).start() + self.ensure_connection = mock.patch('koji_cli.commands.ensure_connection').start() + self.download_file = mock.patch('koji_cli.commands.download_file').start() + self.list_task_output_all_volumes = mock.patch( + 'koji_cli.commands.list_task_output_all_volumes').start() + + + def test_download_scratch_build(self): + task_id = 123333 + args = [str(task_id)] + self.session.getTaskInfo.return_value = {'id': task_id, + 'parent': None, + 'method': 'image'} + self.session.getTaskChildren.return_value = [{'id': 22222, + 'parent': 123333, + 'method': 'createImage', + 'arch': 'noarch', + 'state': 2}] + self.list_task_output_all_volumes.return_value = { + 'ks-name_koji-tag.ks': ['DEFAULT'], + 'oz-taskarch.log': ['DEFAULT'], + 'ks-name.ks': ['DEFAULT'], + 'tdl-taskarch.xml': ['DEFAULT'], + 'build-name_date_release.tar.xz': ['DEFAULT']} + + rv = anon_handle_download_scratch_build(self.options, self.session, args) + actual = self.stdout.getvalue() + expected = '' + self.assertMultiLineEqual(actual, expected) + self.ensure_connection.assert_called_once_with(self.session, self.options) + self.assertEqual(self.list_task_output_all_volumes.mock_calls, [call(self.session, 22222)]) + self.assertListEqual(self.download_file.mock_calls, [ + call('https://topurl/work/tasks/2222/22222/ks-name_koji-tag.ks', + 'ks-name_koji-tag.ks', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/ks-name.ks', + 'ks-name.ks', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/tdl-taskarch.xml', + 'tdl-taskarch.xml', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/build-name_date_release.tar.xz', + 'build-name_date_release.tar.xz', filesize=None, quiet=False, noprogress=None)]) + self.assertIsNone(rv) + + + def test_download_scratch_build_wlogs(self): + task_id = 123333 + args = [str(task_id), "--logs"] + self.session.getTaskInfo.return_value = {'id': task_id, + 'parent': None, + 'method': 'image'} + self.session.getTaskChildren.return_value = [{'id': 22222, + 'parent': 123333, + 'method': 'createImage', + 'arch': 'noarch', + 'state': 2}] + self.list_task_output_all_volumes.return_value = { + 'ks-name_koji-tag.ks': ['DEFAULT'], + 'oz-taskarch.log': ['DEFAULT'], + 'ks-name.ks': ['DEFAULT'], + 'tdl-taskarch.xml': ['DEFAULT'], + 'build-name_date_release.tar.xz': ['DEFAULT']} + + rv = anon_handle_download_scratch_build(self.options, self.session, args) + actual = self.stdout.getvalue() + expected = '' + self.assertMultiLineEqual(actual, expected) + self.ensure_connection.assert_called_once_with(self.session, self.options) + self.assertEqual(self.list_task_output_all_volumes.mock_calls, [call(self.session, 22222)]) + self.assertListEqual(self.download_file.mock_calls, [ + call('https://topurl/work/tasks/2222/22222/ks-name_koji-tag.ks', + 'ks-name_koji-tag.ks', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/oz-taskarch.log', + 'oz-taskarch.log', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/ks-name.ks', + 'ks-name.ks', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/tdl-taskarch.xml', + 'tdl-taskarch.xml', filesize=None, quiet=False, noprogress=None), + call('https://topurl/work/tasks/2222/22222/build-name_date_release.tar.xz', + 'build-name_date_release.tar.xz', filesize=None, quiet=False, noprogress=None)]) + self.assertIsNone(rv) + + + @mock.patch('sys.stderr', new_callable=StringIO) + def test_download_scratch_build_nonscratch_img_id(self, stderr): + task_id = 123333 + args = [str(task_id)] + self.session.getTaskInfo.return_value = {'id': task_id, + 'parent': None, + 'method': 'image'} + self.session.getTaskChildren.return_value = [{'id': 22222, + 'parent': 123333, + 'method': 'createImage', + 'arch': 'noarch', + 'state': 2}, + {'id': 33333, + 'parent': 123333, + 'method': 'createImage', + 'arch': 'noarch', + 'state': 2}] + expected = "No such scratch image build\n" + with self.assertRaises(SystemExit) as ex: + anon_handle_download_scratch_build(self.options, self.session, args) + self.assertExitCode(ex, 1) + self.assert_console_message(stderr, expected) + + + @mock.patch('sys.stderr', new_callable=StringIO) + def test_download_scratch_build_rpm_id(self, stderr): + task_id = 123333 + args = [str(task_id)] + self.session.getTaskInfo.return_value = {'id': task_id, + 'parent': None, + 'method': 'build'} + self.session.getTaskChildren.return_value = [{'id': 22222, + 'parent': 123333, + 'method': 'rebuildSRPM', + 'arch': 'noarch', + 'state': 2}, + {'id': 33333, + 'parent': 123333, + 'method': 'rebuildSRPM', + 'arch': 'noarch', + 'state': 2}] + expected = "No such scratch image build\n" + with self.assertRaises(SystemExit) as ex: + anon_handle_download_scratch_build(self.options, self.session, args) + self.assertExitCode(ex, 1) + self.assert_console_message(stderr, expected) + + + @mock.patch('sys.stderr', new_callable=StringIO) + def test_download_scratch_build_without_option(self, stderr): + expected = "Usage: %s download-scratch-build [options] \n" \ + "(Specify the --help global option for a list of other help options)\n\n" \ + "%s: error: Please specify a valid task id\n" \ + % (self.progname, self.progname) + with self.assertRaises(SystemExit) as ex: + anon_handle_download_scratch_build(self.options, self.session, []) + self.assertExitCode(ex, 2) + self.assert_console_message(stderr, expected) + + + @mock.patch('sys.stderr', new_callable=StringIO) + def test_download_scratch_build_invalid_option(self, stderr): + invalid_option = "random_string_arg" + expected = "The argument '%s' cannot be a task-id number\n" % invalid_option + with self.assertRaises(SystemExit) as ex: + anon_handle_download_scratch_build(self.options, self.session, [invalid_option]) + self.assertExitCode(ex, 1) + self.assert_console_message(stderr, expected) + + + @mock.patch('sys.stderr', new_callable=StringIO) + def test_download_scratch_build_no_files(self, stderr): + task_id = '1' + expected = "No files were found for taskid %s\n" % task_id + self.session.getTaskInfo.return_value = {'id': task_id, + 'parent': None, + 'method': 'image'} + self.session.getTaskChildren.return_value = [{'id': 1, + 'parent': 123333, + 'method': 'createImage', + 'arch': 'noarch', + 'state': 2}] + self.list_task_output_all_volumes.return_value = {} + with self.assertRaises(SystemExit) as ex: + anon_handle_download_scratch_build(self.options, self.session, [task_id]) + self.assertExitCode(ex, 1) + self.assert_console_message(stderr, expected) + + + def test_handle_add_volume_help(self): + self.assert_help( + anon_handle_download_scratch_build, + """Usage: %s download-scratch-build [options] +(Specify the --help global option for a list of other help options) + +Options: + -h, --help show this help message and exit + --logs Also download build logs + --topurl=URL URL under which Koji files are accessible + --noprogress Do not display progress meter + -q, --quiet Suppress output +""" % self.progname)