def test_watch_tasks_with_keyboardinterrupt_handler(self, stdout, sleep): """Raise KeyboardInterrupt inner watch_tasks with a ki_handler""" cfile = os.path.dirname(__file__) + '/data/calls/watchtasks2.json' cdata = koji.load_json(cfile) self.session.load_calls(cdata) sleep.side_effect = [None] * 10 + [KeyboardInterrupt] def customized_handler(progname, tasks, quiet): print('some output') with self.assertRaises(KeyboardInterrupt): # watch_tasks catches and re-raises it to display a message watch_tasks(self.session, [1208], quiet=False, poll_interval=5, ki_handler=customized_handler, topurl=self.options.topurl) expected = ('''Watching tasks (this may be safely interrupted)... 1208 build (f24, /users/mikem/fake.git:master): free 1208 build (f24, /users/mikem/fake.git:master): free -> open (builder-01) 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free -> open (builder-01) some output ''') self.assertMultiLineEqual(stdout.getvalue(), expected)
def test_watch_tasks_with_keyboardinterrupt(self, dtrMock, dtsMock, twClzMock, stdout): """Raise KeyboardInterrupt inner watch_tasks. Raising it by SIGNAL might be better""" self.options.poll_interval = 0 manager = mock.MagicMock() manager.attach_mock(twClzMock, 'TaskWatcherMock') manager.attach_mock(dtrMock, 'display_task_results_mock') manager.attach_mock(dtsMock, 'display_tasklist_status_mock') tw1 = manager.tw1 tw1.level = 0 tw1.is_done.side_effect = [False, KeyboardInterrupt, False] tw1.update.side_effect = [False, False] tw1.is_success.return_value = False tw1.str.return_value = 'tw1' tw1.display_state.return_value = 'tw1.display_state' tw2 = manager.tw2 tw2.level = 0 tw2.is_done.side_effect = [False, False, False, False, True] tw2.update.side_effect = [True, False, False, True, True] tw2.is_success.return_value = False tw2.str.return_value = 'tw2' tw2.display_state.return_value = 'tw2.display_state' self.session.getTaskChildren.side_effect = lambda p: [{ 'id': 11 }, { 'id': 12 }] if (0 == p) else [] manager.attach_mock(self.session, 'sessionMock') def side_effect(*args, **kwargs): rt = None if args[0] not in list(range(2)): rt = mock.MagicMock() rt.level = args[2] rt.is_done.return_value = True rt.update.return_value = True rt.is_success.return_value = True manager.attach_mock(rt, 'tw' + str(args[0])) else: rt = {0: tw1, 1: tw2}.get(args[0]) return rt twClzMock.side_effect = side_effect with self.assertRaises(KeyboardInterrupt): watch_tasks(self.session, list(range(2)), quiet=False, poll_interval=0) actual = stdout.getvalue() self.assertMultiLineEqual( actual, """Watching tasks (this may be safely interrupted)... Tasks still running. You can continue to watch with the '%s watch-task' command. Running Tasks: tw1: tw1.display_state tw2: tw2.display_state """ % (os.path.basename(sys.argv[0]) or 'koji'))
def replicate_build_task(session, task, options): if isinstance(task, six.integer_types): task = session.getTaskInfo(task, request=True) task_id = task['id'] logger.info("%i: Looking at task", task_id) if task['parent'] is not None: raise koji.GenericError("%(id)i: not a parent task" % task) if task['method'] == 'build': params = replicate_build_request(session, task, options) # TODO: ONLY build task is supported right now # elif task['method'] in ['image', 'livemedia', 'livecd', 'appliance']: # params = replicate_image_request(session, task, options) else: raise koji.GenericError("%(id)i: can not replicate %(method)s task" % task) channel = task['channel_id'] if options.channel_override: channel = options.channel_override new_task_id = session.makeTask(task['method'], koji.encode_args(**params), channel=channel) logger.info("Original task %i replicated as task %i", task_id, new_task_id) rv = watch_tasks(session, [new_task_id], quiet=options.quiet) # always True return True
def ensure_tagged(buildinfo, tags, session, dryrun): """ Ensure this build is tagged into Koji. :param dict buildinfo: dict from getBuild with this name/version/release :param list tags: list of tags for this build. :param session: Koji session :param bool dryrun: show what would happen, but don't do it. """ task_ids = [] nvr = '%(name)s-%(version)s-%(release)s' % buildinfo for tag in sorted(tags): tagged_builds = session.listTagged(tag, package=buildinfo['name'], type='debian') tagged_nvrs = [tagged_build['nvr'] for tagged_build in tagged_builds] if nvr in tagged_nvrs: log.info('%s is already tagged into %s' % (nvr, tag)) continue log.info('tagging %s into %s' % (nvr, tag)) if dryrun: continue task_id = session.tagBuild(tag, nvr) task_ids.append(task_id) if not task_ids: # This build is already tagged into all the necessary tags. return task_result = watch_tasks(session, task_ids, poll_interval=15) if task_result != 0 and not dryrun: raise RuntimeError('failed to tag build %s' % nvr)
def handle_save_failed_tree(options, session, args): "Create tarball with whole buildtree" usage = "usage: %prog save-failed-tree [options] ID" usage += "\n(Specify the --help global option for a list of other help options)" parser = OptionParser(usage=usage) parser.add_option("-f", "--full", action="store_true", default=False, help="Download whole tree, if not specified, " "only builddir will be downloaded") parser.add_option("-t", "--task", action="store_const", dest="mode", const="task", default="task", help="Treat ID as a task ID (the default)") parser.add_option("-r", "--buildroot", action="store_const", dest="mode", const="buildroot", help="Treat ID as a buildroot ID") parser.add_option("--quiet", action="store_true", default=options.quiet, help="Do not print the task information") parser.add_option("--nowait", action="store_true", help="Don't wait on build") (opts, args) = parser.parse_args(args) if len(args) != 1: parser.error("List exactly one task or buildroot ID") try: id_val = int(args[0]) except ValueError: parser.error("ID must be an integer") activate_session(session, options) if opts.mode == "buildroot": br_id = id_val else: brs = [b['id'] for b in session.listBuildroots(taskID=id_val)] if not brs: print("No buildroots for task %s" % id_val) return 1 br_id = max(brs) if len(brs) > 1: print("Multiple buildroots for task. Choosing last one (%s)" % br_id) try: task_id = session.saveFailedTree(br_id, opts.full) except koji.GenericError as e: m = str(e) if 'Invalid method' in m: print("* The save_failed_tree plugin appears to not be installed on the koji hub. " "Please contact the administrator.") return 1 raise if not opts.quiet: print("Created task %s for buildroot %s" % (task_id, br_id)) print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if opts.nowait: return else: session.logout() return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval, topurl=options.topurl)
def test_watch_tasks_no_tasklist(self, stdout): returned = watch_tasks(self.session, [], poll_interval=0, topurl=self.options.topurl) actual = stdout.getvalue() expected = "" self.assertIsNone(returned) self.assertEqual(actual, expected)
def tag_build(buildinfo, tag, session): """ Tag this build in Koji. """ nvr = '%(name)s-%(version)s-%(release)s' % buildinfo log.info('tagging %s into %s' % (nvr, tag)) task_id = session.tagBuild(tag, nvr) task_result = watch_tasks(session, [task_id], poll_interval=15) if task_result != 0: raise RuntimeError('failed to tag builds')
def watch_task(self, id_, interval=5): """ Watch a Koji task ID, printing its state transitions to STDOUT """ weburl = self.session.opts['weburl'] url = posixpath.join(weburl, 'taskinfo?taskID=%s' % id_) print('Watching Koji task %s' % url) task_result = watch_tasks(self.session, [id_], poll_interval=interval) if task_result != 0: raise RuntimeError('failed buildContainer task')
def handle_osbuild_image(options, session, argv): "[build] Build images via osbuild" args = parse_args(argv) name, version, arch, target = args.name, args.version, args.arch, args.target distro, image_types = args.distro, args.image_type if not image_types: image_types = ["qcow2"] opts = {} if args.release: opts["release"] = args.release if args.repo: opts["repo"] = ",".join(args.repo) # Do some early checks to be able to give quick feedback check_target(session, target) if not options.quiet: print("name:", name) print("version:", version) print("distro:", distro) print("arches:", ", ".join(arch)) print("target:", target) print("image types ", str(image_types)) pprint(opts) kl.activate_session(session, options) task_id = session.osbuildImage(name, version, distro, image_types, target, arch, opts=opts) if not options.quiet: print("Created task: %s" % task_id) print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) # pylint: disable=protected-access if (args.wait is None and kl._running_in_bg()) or args.wait is False: # either running in the background or must not wait by user's # request. All done. return None session.logout() res = kl.watch_tasks(session, [task_id], quiet=options.quiet) if res == 0: result = session.getTaskResult(task_id) print(result) return res
def handle_build(options, session, args, flatpak=False, sourcebuild=False): if sourcebuild: build_opts, args, opts, parser = parse_source_arguments(options, args) else: build_opts, args, opts, parser = parse_arguments( options, args, flatpak) activate_session(session, options) target = args[0] build_target = session.getBuildTarget(target) if not build_target: parser.error(_("Unknown build target: %s" % target)) dest_tag = session.getTag(build_target['dest_tag']) if not dest_tag: parser.error( _("Unknown destination tag: %s" % build_target['dest_tag_name'])) if dest_tag['locked'] and not build_opts.scratch: parser.error(_("Destination tag %s is locked" % dest_tag['name'])) priority = None if build_opts.background: # relative to koji.PRIO_DEFAULT priority = 5 if sourcebuild: task_id = session.buildSourceContainer( target, opts, priority=priority, channel=build_opts.channel_override) else: source = args[1] task_id = session.buildContainer(source, target, opts, priority=priority, channel=build_opts.channel_override) if not build_opts.quiet: print("Created task: %s" % task_id) print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if build_opts.wait or (build_opts.wait is None and not _running_in_bg()): session.logout() rv = watch_tasks(session, [task_id], quiet=build_opts.quiet) # Task completed and a result should be available. if rv == 0: result = session.getTaskResult(task_id) print_task_result(task_id, result, options.weburl) return rv else: return
def handle_build(options, session, args, flatpak): build_opts, args, opts, parser = parse_arguments(options, args, flatpak) activate_session(session, options) target = args[0] if target.lower() == "none" and build_opts.repo_id: target = None build_opts.skip_tag = True else: build_target = session.getBuildTarget(target) if not build_target: parser.error(_("Unknown build target: %s" % target)) dest_tag = session.getTag(build_target['dest_tag']) if not dest_tag: parser.error( _("Unknown destination tag: %s" % build_target['dest_tag_name'])) if dest_tag['locked'] and not build_opts.scratch: parser.error(_("Destination tag %s is locked" % dest_tag['name'])) source = args[1] priority = None if build_opts.background: # relative to koji.PRIO_DEFAULT priority = 5 if '://' not in source: parser.error( _("scm URL does not look like an URL to a source repository")) if '#' not in source: parser.error( _("scm URL must be of the form <url_to_repository>#<revision>)")) task_id = session.buildContainer(source, target, opts, priority=priority, channel=build_opts.channel_override) if not build_opts.quiet: print("Created task: %s" % task_id) print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if build_opts.wait or (build_opts.wait is None and not _running_in_bg()): session.logout() rv = watch_tasks(session, [task_id], quiet=build_opts.quiet) # Task completed and a result should be available. if rv == 0: result = session.getTaskResult(task_id) print_task_result(task_id, result, options.weburl) return rv else: return
def test_watch_tasks_with_keyboardinterrupt(self, stdout, sleep): """Raise KeyboardInterrupt inner watch_tasks. Raising it by SIGNAL might be better""" cfile = os.path.dirname(__file__) + '/data/calls/watchtasks2.json' with open(cfile) as fp: cdata = json.load(fp) self.session.load_calls(cdata) sleep.side_effect = [None] * 10 + [KeyboardInterrupt] with self.assertRaises(KeyboardInterrupt): # watch_tasks catches and re-raises it to display a message watch_tasks(self.session, [1208], quiet=False, poll_interval=5) expected = ('''Watching tasks (this may be safely interrupted)... 1208 build (f24, /users/mikem/fake.git:master): free 1208 build (f24, /users/mikem/fake.git:master): free -> open (builder-01) 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free -> open (builder-01) Tasks still running. You can continue to watch with the '%s watch-task' command. Running Tasks: 1208 build (f24, /users/mikem/fake.git:master): open (builder-01) 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): open (builder-01) ''' % (os.path.basename(sys.argv[0]) or 'koji')) self.assertMultiLineEqual(stdout.getvalue(), expected)
def wait_task_complete(self, task_id): """ Wait until koji finsihes building task """ while True: with redirect_stdout(this.logger): watch_tasks(self.hub, [task_id], poll_interval=10) taskinfo = self.hub.getTaskInfo(task_id) state = taskinfo['state'] # /usr/lib/python3.8/site-packages/koji_cli/lib.py if state == koji.TASK_STATES['CLOSED']: this.logger.info("task completed successfully") return True if state == koji.TASK_STATES['FAILED']: this.logger.info("task failed") return False if state == koji.TASK_STATES['CANCELED']: this.logger.info("was canceled") return False # shouldn't happen this.logger.info("task has not completed yet")
def new_repo(session, tag, src_repo, orig_repo_id): # cloning # tag_id is the ID of cloned tag if tag: act_repo = session.getRepo(tag, event=None) if act_repo: REPOCACHE.setdefault(orig_repo_id, act_repo['id']) return session.repoInfo(act_repo['id']) event = None # reusing/refreshing else: if isinstance(src_repo, six.integer_types): src_repo = session.repoInfo(src_repo) if src_repo and isinstance(src_repo, dict): event = src_repo['create_event'] tag = src_repo['tag_id'] rtaskid = session.newRepo(tag, event=event) watch_tasks(session, [rtaskid]) new_repo_id, event_id = session.getTaskResult(rtaskid) repo_info = session.repoInfo(new_repo_id) REPOCACHE.setdefault(orig_repo_id, new_repo_id) return repo_info
def new_build(profile='cbs', scratch=True): # Very ugly: some utilities are only available in koji CLI # and not in the koji module if not KOJI_AVAILABLE: raise ModuleNotAvailable(module='koji') if not RPM_AVAILABLE: raise RpmModuleNotAvailable() import imp kojibin = find_executable('koji') kojicli = imp.load_source('kojicli', kojibin) # Koji 1.13 moves client internal API into another path try: from koji_cli.lib import _unique_path, _progress_callback, watch_tasks except ImportError: from kojicli import _unique_path, _progress_callback, watch_tasks kojiclient = setup_kojiclient(profile) # Note: required to make watch_tasks work kojicli.options = opts = munchify(kojiclient.opts) build_target = guess_build() if not build_target: log.warn("failed to identify build tag from branch") return retrieve_sources() srpm = create_srpm() if not srpm: log.warn('No srpm available') return serverdir = _unique_path('cli-build') kojiclient.uploadWrapper(srpm, serverdir, callback=_progress_callback) source = "%s/%s" % (serverdir, os.path.basename(srpm)) task_id = kojiclient.build(source, build_target, {'scratch': scratch}) print("Created task:", task_id) print("Task info: {}/taskinfo?taskID={}".format(opts['weburl'], task_id)) kojiclient.logout() watch_tasks(kojiclient, [task_id])
def new_build(profile='cbs', scratch=True): # Very ugly: some utilities are only available in koji CLI # and not in the koji module if not KOJI_AVAILABLE: raise ModuleNotAvailable(module='koji') if not RPM_AVAILABLE: raise RpmModuleNotAvailable() import imp kojibin = find_executable('koji') kojicli = imp.load_source('kojicli', kojibin) # Koji 1.13 moves client internal API into another path try: from koji_cli.lib import _unique_path, _progress_callback, watch_tasks except ImportError: from kojicli import _unique_path, _progress_callback, watch_tasks kojiclient = setup_kojiclient(profile) # Note: required to make watch_tasks work kojicli.options = opts = bunchify(kojiclient.opts) build_target = guess_build() if not build_target: log.warn("failed to identify build tag from branch") return retrieve_sources() srpm = create_srpm() if not srpm: log.warn('No srpm available') return serverdir = _unique_path('cli-build') kojiclient.uploadWrapper(srpm, serverdir, callback=_progress_callback) source = "%s/%s" % (serverdir, os.path.basename(srpm)) task_id = kojiclient.build(source, build_target, {'scratch': scratch}) print("Created task:", task_id) print("Task info: {}/taskinfo?taskID={}".format(opts['weburl'], task_id)) kojiclient.logout() watch_tasks(kojiclient, [task_id])
def watch_tasks_with_retry(self, tasks, max_retries=20, retry_interval=20): tries = 0 while True: try: return kojicli.watch_tasks(self.kojisession, tasks) except kojilib.ServerOffline as err: # these have a large chance of being bogus log.info("Got error from server: %s", err) tries += 1 if tries < max_retries: log.info("Retrying in %d seconds", retry_interval) time.sleep(retry_interval) else: raise
def test_watch_tasks_with_keyboardinterrupt_handler(self, stdout, sleep): """Raise KeyboardInterrupt inner watch_tasks with a ki_handler""" cfile = os.path.dirname(__file__) + '/data/calls/watchtasks2.json' with open(cfile) as fp: cdata = json.load(fp) self.session.load_calls(cdata) sleep.side_effect = [None] * 10 + [KeyboardInterrupt] def customized_handler(progname, tasks, quiet): print('some output') with self.assertRaises(KeyboardInterrupt): # watch_tasks catches and re-raises it to display a message watch_tasks(self.session, [1208], quiet=False, poll_interval=5, ki_handler=customized_handler) expected = ('''Watching tasks (this may be safely interrupted)... 1208 build (f24, /users/mikem/fake.git:master): free 1208 build (f24, /users/mikem/fake.git:master): free -> open (builder-01) 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free -> open (builder-01) some output ''') self.assertMultiLineEqual(stdout.getvalue(), expected)
def test_watch_tasks(self, stdout): # self.setup_record('foo.json') cfile = os.path.dirname(__file__) + '/data/calls/watchtasks1.json' with open(cfile) as fp: cdata = json.load(fp) self.session.load_calls(cdata) rv = watch_tasks(self.session, [1188], quiet=False, poll_interval=0) self.assertEqual(rv, 0) expected = ('''Watching tasks (this may be safely interrupted)... 1188 build (f24, /users/mikem/fake.git:adaf62586b4b4a23b24394da5586abd7cd9f679e): closed 1189 buildSRPMFromSCM (/users/mikem/fake.git:adaf62586b4b4a23b24394da5586abd7cd9f679e): closed 1190 buildArch (fake-1.1-21.src.rpm, noarch): closed 1188 build (f24, /users/mikem/fake.git:adaf62586b4b4a23b24394da5586abd7cd9f679e) completed successfully ''') self.assertMultiLineEqual(stdout.getvalue(), expected)
def test_watch_tasks(self, stdout): # self.setup_record('foo.json') cfile = os.path.dirname(__file__) + '/data/calls/watchtasks1.json' with open(cfile) as fp: cdata = json.load(fp) self.session.load_calls(cdata) rv = watch_tasks(self.session, [1188], quiet=False, poll_interval=0) self.assertEqual(rv, 0) expected = ( '''Watching tasks (this may be safely interrupted)... 1188 build (f24, /users/mikem/fake.git:adaf62586b4b4a23b24394da5586abd7cd9f679e): closed 1189 buildSRPMFromSCM (/users/mikem/fake.git:adaf62586b4b4a23b24394da5586abd7cd9f679e): closed 1190 buildArch (fake-1.1-21.src.rpm, noarch): closed 1188 build (f24, /users/mikem/fake.git:adaf62586b4b4a23b24394da5586abd7cd9f679e) completed successfully ''') self.assertMultiLineEqual(stdout.getvalue(), expected)
def handle_build(options, session, args, flatpak): build_opts, args, opts, parser = parse_arguments(options, args, flatpak) activate_session(session, options) target = args[0] build_target = session.getBuildTarget(target) if not build_target: parser.error(_("Unknown build target: %s" % target)) dest_tag = session.getTag(build_target['dest_tag']) if not dest_tag: parser.error(_("Unknown destination tag: %s" % build_target['dest_tag_name'])) if dest_tag['locked'] and not build_opts.scratch: parser.error(_("Destination tag %s is locked" % dest_tag['name'])) source = args[1] priority = None if build_opts.background: # relative to koji.PRIO_DEFAULT priority = 5 if '://' not in source: parser.error(_("scm URL does not look like an URL to a source repository")) if '#' not in source: parser.error(_("scm URL must be of the form <url_to_repository>#<revision>)")) task_id = session.buildContainer(source, target, opts, priority=priority, channel=build_opts.channel_override) if not build_opts.quiet: print("Created task: %s" % task_id) print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if build_opts.wait or (build_opts.wait is None and not _running_in_bg()): session.logout() rv = watch_tasks(session, [task_id], quiet=build_opts.quiet) # Task completed and a result should be available. if rv == 0: result = session.getTaskResult(task_id) print_task_result(task_id, result, options.weburl) return rv else: return
def test_watch_tasks_fail(self, stdout, sleep): # self.setup_record('foo.json') cfile = os.path.dirname(__file__) + '/data/calls/watchtasks2.json' with open(cfile) as fp: cdata = json.load(fp) self.session.load_calls(cdata) rv = watch_tasks(self.session, [1208], quiet=False, poll_interval=5) self.assertEqual(rv, 1) expected = ('''Watching tasks (this may be safely interrupted)... 1208 build (f24, /users/mikem/fake.git:master): free 1208 build (f24, /users/mikem/fake.git:master): free -> open (builder-01) 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free -> open (builder-01) 1208 build (f24, /users/mikem/fake.git:master): open (builder-01) -> FAILED: GenericError: Build already exists (id=425, state=COMPLETE): {'name': 'fake', 'task_id': 1208, 'extra': None, 'pkg_id': 298, 'epoch': 7, 'completion_time': None, 'state': 0, 'version': '1.1', 'source': None, 'volume_id': 0, 'owner': 1, 'release': '22', 'start_time': 'NOW'} 0 free 1 open 0 done 1 failed 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): open (builder-01) -> closed 0 free 0 open 1 done 1 failed 1208 build (f24, /users/mikem/fake.git:master) failed ''') self.assertMultiLineEqual(stdout.getvalue(), expected)
def test_watch_tasks_fail(self, stdout, sleep): # self.setup_record('foo.json') cfile = os.path.dirname(__file__) + '/data/calls/watchtasks2.json' with open(cfile) as fp: cdata = json.load(fp) self.session.load_calls(cdata) rv = watch_tasks(self.session, [1208], quiet=False, poll_interval=5, topurl=None) self.assertEqual(rv, 1) expected = ('''Watching tasks (this may be safely interrupted)... 1208 build (f24, /users/mikem/fake.git:master): free 1208 build (f24, /users/mikem/fake.git:master): free -> open (builder-01) 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): free -> open (builder-01) 1208 build (f24, /users/mikem/fake.git:master): open (builder-01) -> FAILED: GenericError: Build already exists (id=425, state=COMPLETE): {'name': 'fake', 'task_id': 1208, 'extra': None, 'pkg_id': 298, 'epoch': 7, 'completion_time': None, 'state': 0, 'version': '1.1', 'source': None, 'volume_id': 0, 'owner': 1, 'release': '22', 'start_time': 'NOW'} 0 free 1 open 0 done 1 failed 1209 buildSRPMFromSCM (/users/mikem/fake.git:master): open (builder-01) -> closed 0 free 0 open 1 done 1 failed 1208 build (f24, /users/mikem/fake.git:master) failed ''') self.assertMultiLineEqual(stdout.getvalue(), expected)
def test_watch_tasks_no_tasklist(self, stdout): returned = watch_tasks(self.session, [], poll_interval=0) actual = stdout.getvalue() expected = "" self.assertIsNone(returned) self.assertEqual(actual, expected)
def process(self, message: fedora_messaging.api.Message): logger.debug(message.topic) logger.debug(message.body) # Grab the raw message body and the status from that msg = message.body branch = msg['ref'] repo = msg['repository']['full_name'] if (repo != self.github_repo_fullname): logger.info(f'Skipping message from unrelated repo: {repo}') return if (branch != self.github_repo_branch): logger.info(f'Skipping message from unrelated branch: {branch}') return # Some messages don't have commit information # For example: https://apps.fedoraproject.org/datagrepper/id?id=2019-f32c811b-658b-4ac7-a455-a7edf616a033&is_raw=true&size=extra-large commit = None if msg['head_commit']: commit = msg['head_commit']['id'] if commit is None: logger.error('No commit id in message!') return # Now grab lockfile data from the commit we should operate on: desiredrpms = set() for arch in ['x86_64', 'aarch64', 'ppc64le', 's390x']: for lockfile in ['manifest-lock', 'manifest-lock.overrides']: url = f'https://raw.githubusercontent.com/{repo}/{commit}/{lockfile}.{arch}.json' logger.info(f'Attempting to retrieve data from {url}') r = requests.get(url) if r.ok: # parse the lockfile and add the set of rpm NEVRAs (strings) desiredrpms.update(parse_lockfile_data(r.text)) else: # Log any errors we encounter. 404s are ok, but won't hurt to log logger.warn('URL request error: %s' % r.text.strip()) # NOMENCLATURE: # # In koji there is the concept of a pkg and a build. A pkg # is a piece of software (i.e. kernel) whereas a build is a # specific build of that software that is unique by NVR (i.e. # kernel-5.0.17-300.fc30). RPMs are output of a build. There # can be many rpms (including subpackages) output from a build # (for example kernel-5.0.17-300.fc30.x86_64.rpm and # kernel-devel-5.0.17-300.fc30.x86_64.rpm). So we have: # # kernel --> koji pkg # kernel-5.0.17-300.fc30 --> koji build (matches srpm name) # kernel-5.0.17-300.fc30.x86_64 --> main rpm package # kernel-devel-5.0.17-300.fc30.x86_64 --> rpm subpackage # # STRATEGY: # # The lockfile input gives a list of rpm names in NEVRA format. We # must derive the srpm name (koji build name) from that and compare # that with existing koji builds in the tag. Once we have a list of # koji builds that aren't in the tag we can add the koji pkg to the # tag (if needed) and then tag the koji build into the tag. # convert the NEVRAs into a dict of build IDs -> BuildInfo objects buildsinfo = self.get_buildsinfo_from_rpmnevras(desiredrpms) desiredbuildids = buildsinfo.keys() # Grab the build IDs of currently tagged builds currentbuildids = self.get_tagged_buildids(self.target_tag) # Get the set of names of pkgs needed desiredpkgs = set([x.name for x in buildsinfo.values()]) # Grab the set of names of pkgs that can be tagged into the tag pkgsintag = self.get_pkglist_in_tag(self.target_tag) # compute the pkgstoadd and the buildstotag pkgstoadd = desiredpkgs - pkgsintag buildstotag = desiredbuildids - currentbuildids # Log if there is nothing to do if not pkgstoadd and not buildstotag: logger.info(f'No new builds to tag.. going back to sleep') return # Make sure all packages desired are in the pkglist if pkgstoadd: logger.info('Adding packages to the ' f'{self.target_tag} tag: {pkgstoadd}') if self.keytab_file: with self.koji_client.multicall(strict=True) as m: for pkg in pkgstoadd: m.packageListAdd(self.target_tag, pkg, owner=self.koji_user) logger.info('Package adding done') # Perform the tagging for each release into the intermediate # tag for that release if we have credentials if buildstotag: # Get a set of tuples of build name to tag to tag into tuples = [(self.intermediate_tag.format( releasever=buildsinfo[x].releasever), buildsinfo[x].nvr) for x in buildstotag] logger.info('Tagging the following (tag, nvr): \n\t%s' % '\n\t'.join(map(str, tuples))) if self.keytab_file: with self.koji_client.multicall(strict=True) as m: tasks = [ m.tagBuild(tag=tag, build=nvr) for (tag, nvr) in tuples ] watch_tasks(self.koji_client, [task.result for task in tasks], poll_interval=10) logger.info('Tagging done')
def watch_scratch_build(session, task_id): error = watch_tasks(session, [task_id], poll_interval=4) if error: raise RuntimeError('scratch build failed')
def process_lockfiles(self, rev): # Now grab lockfile data from the commit we should operate on: desiredrpms = set() for arch in ['x86_64', 'aarch64', 'ppc64le', 's390x']: for lockfile in ['manifest-lock', 'manifest-lock.overrides']: for filetype in ['yaml', 'json']: url = f'https://raw.githubusercontent.com/{self.github_repo_fullname}/{rev}/{lockfile}.{arch}.{filetype}' logger.info(f'Attempting to retrieve data from {url}') r = requests.get(url) if r.ok: # parse the lockfile and add the set of rpm NEVRAs (strings) desiredrpms.update( parse_lockfile_data(r.text, filetype)) break # If both yaml and json files exist, only parse one # of them. Prefer yaml. else: # Log any errors we encounter. 404s are ok, but won't hurt to log logger.warning('URL request error: %s' % r.text.strip()) if not desiredrpms: logger.warning('No locked RPMs found!') logger.warning("Does the repo:ref (%s:%s) have any lockfiles?" % (self.github_repo_fullname, rev)) logger.warning('Continuing...') return # NOMENCLATURE: # # In koji there is the concept of a pkg and a build. A pkg # is a piece of software (i.e. kernel) whereas a build is a # specific build of that software that is unique by NVR (i.e. # kernel-5.0.17-300.fc30). RPMs are output of a build. There # can be many rpms (including subpackages) output from a build # (for example kernel-5.0.17-300.fc30.x86_64.rpm and # kernel-devel-5.0.17-300.fc30.x86_64.rpm). So we have: # # kernel --> koji pkg # kernel-5.0.17-300.fc30 --> koji build (matches srpm name) # kernel-5.0.17-300.fc30.x86_64 --> main rpm package # kernel-devel-5.0.17-300.fc30.x86_64 --> rpm subpackage # # STRATEGY: # # The lockfile input gives a list of rpm names in NEVRA format. We # must derive the srpm name (koji build name) from that and compare # that with existing koji builds in the tag. Once we have a list of # koji builds that aren't in the tag we can add the koji pkg to the # tag (if needed) and then tag the koji build into the tag. # convert the NEVRAs into a dict of build IDs -> BuildInfo objects buildsinfo = self.get_buildsinfo_from_rpmnevras(desiredrpms) desiredbuildids = buildsinfo.keys() # Grab the build IDs of currently tagged builds currentbuildids = self.get_tagged_buildids(self.target_tag) # Get the set of names of pkgs needed desiredpkgs = set([x.name for x in buildsinfo.values()]) # Grab the set of names of pkgs that can be tagged into the tag pkgsintag = self.get_pkglist_in_tag(self.target_tag) # compute the pkgstoadd and the buildstotag pkgstoadd = desiredpkgs - pkgsintag buildstotag = desiredbuildids - currentbuildids # Log if there is nothing to do if not pkgstoadd and not buildstotag: logger.info(f'No new builds to tag.. going back to sleep') return # Make sure all packages desired are in the pkglist if pkgstoadd: logger.info('Adding packages to the ' f'{self.target_tag} tag: {pkgstoadd}') if self.keytab_file: # pylint: disable=E1102 with self.koji_client.multicall(strict=True) as m: for pkg in pkgstoadd: m.packageListAdd(self.target_tag, pkg, owner=self.koji_user) logger.info('Package adding done') # Perform the tagging for each release into the intermediate # tag for that release if we have credentials if buildstotag: # Get a set of tuples of build name to tag to tag into tuples = [(self.intermediate_tag.format( releasever=buildsinfo[x].releasever), buildsinfo[x].nvr) for x in buildstotag] logger.info('Tagging the following (tag, nvr): \n\t%s' % '\n\t'.join(map(str, tuples))) if self.keytab_file: # pylint: disable=E1102 with self.koji_client.multicall(strict=True) as m: tasks = [ m.tagBuild(tag=tag, build=nvr) for (tag, nvr) in tuples ] watch_tasks(self.koji_client, [task.result for task in tasks], poll_interval=10) logger.info('Tagging done') # Subsequently run a distrepo task because there are # races in tag2distrepo. https://pagure.io/koji/issue/1630 # Before running distrepo let's wait for all rpms to # pass through signing and make it into the target tag # # If not done in ten minutes then just timeout (60*10s = 10 minutes) for x in range(0, 60): currentbuildids = self.get_tagged_buildids(self.target_tag) difference = desiredbuildids - currentbuildids if difference: logger.info('Waiting on builds to be signed') logger.info('Remaining builds: %s' % [buildsinfo[x].nvr for x in difference]) time.sleep(10) continue break # If all the builds didn't make it into the target # then just return here. if difference: logger.error('Some builds never got signed.. Giving up') return # This code is mostly stolen from: # https://pagure.io/releng/tag2distrepo/blob/master/f/tag2distrepo.py taginfo = self.koji_client.getTag(self.target_tag) keys = taginfo['extra'].get("tag2distrepo.keys", '').split() task_opts = { 'arch': taginfo['arches'].split(), 'comp': None, 'delta': [], 'event': None, 'inherit': False, 'latest': False, 'multilib': False, 'split_debuginfo': False, 'skip_missing_signatures': False, 'allow_missing_signatures': False, } task = self.koji_client.distRepo(self.target_tag, keys, **task_opts) watch_tasks(self.koji_client, [task], poll_interval=10) logger.info('Dist-repo task has finished')
"-o", "--output", help="Add output to the specified file", default=None, ) args = parser.parse_args() logger = configure_logging(verbose=args.verbose, output=args.output) build = session.getBuild(args.build_id) package = build["name"] source = build["source"] if not is_eln(package): logger.info("{0} is not in ELN".format(package)) exit(0) task_id = rebuild_source(source, scratch=args.scratch) info_fmt = ("{0}\n" "https://koji.fedoraproject.org/koji/taskinfo?taskID={1}\n\n" "{2}\n") logger.info(info_fmt.format(package, task_id, source)) if task_id and args.wait: with redirect_stdout(logger): rv = watch_tasks(session, [task_id], poll_interval=10) exit(rv)
def watch_tasks(self, tasks): return kojicli.watch_tasks(self.kojisession, tasks)
def test_watch_tasks(self, dtrMock, dtsMock, twClzMock, stdout): self.options.poll_interval = 0 manager = mock.MagicMock() manager.attach_mock(twClzMock, 'TaskWatcherMock') manager.attach_mock(dtrMock, 'display_task_results_mock') manager.attach_mock(dtsMock, 'display_tasklist_status_mock') tw1 = manager.tw1 tw1.level = 0 tw1.is_done.side_effect = [False, True, False, True, True] tw1.update.side_effect = [False, False, True, True, True] tw1.is_success.return_value = False tw2 = manager.tw2 tw2.level = 0 tw2.is_done.side_effect = [False, False, False, False, True] tw2.update.side_effect = [True, False, False, True, True] tw2.is_success.return_value = False self.session.getTaskChildren.side_effect = lambda p: [{ 'id': 11 }, { 'id': 12 }] if (0 == p) else [] manager.attach_mock(self.session, 'sessionMock') def side_effect(*args, **kwargs): rt = None if args[0] not in list(range(2)): rt = mock.MagicMock() rt.level = args[2] rt.is_done.return_value = True rt.update.return_value = True rt.is_success.return_value = True manager.attach_mock(rt, 'tw' + str(args[0])) else: rt = {0: tw1, 1: tw2}.get(args[0]) return rt twClzMock.side_effect = side_effect rv = watch_tasks(self.session, list(range(2)), quiet=False, poll_interval=0) actual = stdout.getvalue() self.assertMultiLineEqual( actual, "Watching tasks (this may be safely interrupted)...\n\n") self.assertEqual(rv, 1) self.assertEqual(manager.mock_calls, [ call.TaskWatcherMock(0, self.session, quiet=False), call.TaskWatcherMock(1, self.session, quiet=False), call.tw1.update(), call.tw1.is_done(), call.sessionMock.getTaskChildren(0), call.TaskWatcherMock(11, self.session, 1, quiet=False), call.tw11.update(), call.TaskWatcherMock(12, self.session, 1, quiet=False), call.tw12.update(), call.tw2.update(), call.tw2.is_done(), call.sessionMock.getTaskChildren(1), call.tw1.update(), call.tw1.is_done(), call.tw1.is_success(), call.sessionMock.getTaskChildren(0), call.tw2.update(), call.tw2.is_done(), call.sessionMock.getTaskChildren(1), call.tw11.update(), call.tw11.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw11.is_success(), call.sessionMock.getTaskChildren(11), call.tw12.update(), call.tw12.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw12.is_success(), call.sessionMock.getTaskChildren(12), call.tw1.update(), call.tw1.is_done(), call.sessionMock.getTaskChildren(0), call.tw2.update(), call.tw2.is_done(), call.sessionMock.getTaskChildren(1), call.tw11.update(), call.tw11.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw11.is_success(), call.sessionMock.getTaskChildren(11), call.tw12.update(), call.tw12.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw12.is_success(), call.sessionMock.getTaskChildren(12), call.tw1.update(), call.tw1.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw1.is_success(), call.sessionMock.getTaskChildren(0), call.tw2.update(), call.tw2.is_done(), call.sessionMock.getTaskChildren(1), call.tw11.update(), call.tw11.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw11.is_success(), call.sessionMock.getTaskChildren(11), call.tw12.update(), call.tw12.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw12.is_success(), call.sessionMock.getTaskChildren(12), call.tw1.update(), call.tw1.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw1.is_success(), call.sessionMock.getTaskChildren(0), call.tw2.update(), call.tw2.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw2.is_success(), call.sessionMock.getTaskChildren(1), call.tw11.update(), call.tw11.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw11.is_success(), call.sessionMock.getTaskChildren(11), call.tw12.update(), call.tw12.is_done(), call.display_tasklist_status_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }), call.tw12.is_success(), call.sessionMock.getTaskChildren(12), call.display_task_results_mock({ 0: tw1, 1: tw2, 11: manager.tw11, 12: manager.tw12 }) ])
def handle_save_failed_tree(options, session, args): "Create tarball with whole buildtree" usage = _("usage: %prog save-failed-tree [options] ID") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.add_option("-f", "--full", action="store_true", default=False, help=_("Download whole tree, if not specified, only builddir will be downloaded")) parser.add_option("-t", "--task", action="store_const", dest="mode", const="task", default="task", help=_("Treat ID as a task ID (the default)")) parser.add_option("-r", "--buildroot", action="store_const", dest="mode", const="buildroot", help=_("Treat ID as a buildroot ID")) parser.add_option("--quiet", action="store_true", default=options.quiet, help=_("Do not print the task information")) parser.add_option("--nowait", action="store_true", help=_("Don't wait on build")) (opts, args) = parser.parse_args(args) if len(args) != 1: parser.error(_("List exactly one task or buildroot ID")) try: id_val = int(args[0]) except ValueError: parser.error(_("ID must be an integer")) activate_session(session, options) if opts.mode == "buildroot": br_id = id_val else: brs = [b['id'] for b in session.listBuildroots(taskID=id_val)] if not brs: print(_("No buildroots for task %s") % id_val) return 1 br_id = max(brs) if len(brs) > 1: print(_("Multiple buildroots for task. Choosing last one (%s)") % br_id) try: task_id = session.saveFailedTree(br_id, opts.full) except koji.GenericError as e: m = str(e) if 'Invalid method' in m: print(_("* The save_failed_tree plugin appears to not be " "installed on the koji hub. Please contact the " "administrator.")) return 1 raise if not opts.quiet: print(_("Created task %s for buildroot %s") % (task_id, br_id)) print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id)) if opts.nowait: return else: session.logout() return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval)
def handle_runroot(options, session, args): "[admin] Run a command in a buildroot" usage = _("usage: %prog runroot [options] <tag> <arch> <command>") usage += _("\n(Specify the --help global option for a list of other help options)") parser = OptionParser(usage=usage) parser.disable_interspersed_args() parser.add_option("-p", "--package", action="append", default=[], help=_("make sure this package is in the chroot")) parser.add_option("-m", "--mount", action="append", default=[], help=_("mount this directory read-write in the chroot")) parser.add_option("--skip-setarch", action="store_true", default=False, help=_("bypass normal setarch in the chroot")) parser.add_option("-w", "--weight", type='int', help=_("set task weight")) parser.add_option("--channel-override", help=_("use a non-standard channel")) parser.add_option("--task-id", action="store_true", default=False, help=_("Print the ID of the runroot task")) parser.add_option("--use-shell", action="store_true", default=False, help=_("Run command through a shell, otherwise uses exec")) parser.add_option("--new-chroot", action="store_true", default=None, help=_("Run command with the --new-chroot (systemd-nspawn) option to mock")) parser.add_option("--old-chroot", action="store_false", default=None, dest='new_chroot', help=_("Run command with the --old-chroot (systemd-nspawn) option to mock")) parser.add_option("--repo-id", type="int", help=_("ID of the repo to use")) parser.add_option("--nowait", action="store_false", dest="wait", default=True, help=_("Do not wait on task")) parser.add_option("--watch", action="store_true", help=_("Watch task instead of printing runroot.log")) parser.add_option("--quiet", action="store_true", default=options.quiet, help=_("Do not print the task information")) (opts, args) = parser.parse_args(args) if len(args) < 3: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover activate_session(session, options) tag = args[0] arch = args[1] if opts.use_shell: # everything must be correctly quoted command = ' '.join(args[2:]) else: command = args[2:] try: kwargs = { 'channel': opts.channel_override, 'packages': opts.package, 'mounts': opts.mount, 'repo_id': opts.repo_id, 'skip_setarch': opts.skip_setarch, 'weight': opts.weight } # Only pass this kwarg if it is true - this prevents confusing older # builders with a different function signature if opts.new_chroot is not None: kwargs['new_chroot'] = opts.new_chroot task_id = session.runroot(tag, arch, command, **kwargs) except koji.GenericError as e: if 'Invalid method' in str(e): print("* The runroot plugin appears to not be installed on the" " koji hub. Please contact the administrator.") raise if opts.task_id: print(task_id) if not opts.wait: return if opts.watch: session.logout() return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval) try: while True: # wait for the task to finish if session.taskFinished(task_id): break time.sleep(options.poll_interval) except KeyboardInterrupt: # this is probably the right thing to do here print("User interrupt: canceling runroot task") session.cancelTask(task_id) raise sys.stdout.flush() if not opts.quiet: output = list_task_output_all_volumes(session, task_id) if 'runroot.log' in output: for volume in output['runroot.log']: log = session.downloadTaskOutput(task_id, 'runroot.log', volume=volume) # runroot output, while normally text, can be *anything*, so # treat it as binary bytes_to_stdout(log) info = session.getTaskInfo(task_id) if info is None: sys.exit(1) state = koji.TASK_STATES[info['state']] if state in ('FAILED', 'CANCELED'): sys.exit(1)
def handle_runroot(options, session, args): "[admin] Run a command in a buildroot" usage = _("usage: %prog runroot [options] <tag> <arch> <command>") usage += _( "\n(Specify the --help global option for a list of other help options)" ) parser = OptionParser(usage=usage) parser.disable_interspersed_args() parser.add_option("-p", "--package", action="append", default=[], help=_("make sure this package is in the chroot")) parser.add_option("-m", "--mount", action="append", default=[], help=_("mount this directory read-write in the chroot")) parser.add_option("--skip-setarch", action="store_true", default=False, help=_("bypass normal setarch in the chroot")) parser.add_option("-w", "--weight", type='int', help=_("set task weight")) parser.add_option("--channel-override", help=_("use a non-standard channel")) parser.add_option("--task-id", action="store_true", default=False, help=_("Print the ID of the runroot task")) parser.add_option( "--use-shell", action="store_true", default=False, help=_("Run command through a shell, otherwise uses exec")) parser.add_option( "--new-chroot", action="store_true", default=False, help=_( "Run command with the --new-chroot (systemd-nspawn) option to mock" )) parser.add_option("--repo-id", type="int", help=_("ID of the repo to use")) parser.add_option("--nowait", action="store_false", dest="wait", default=True, help=_("Do not wait on task")) parser.add_option("--watch", action="store_true", help=_("Watch task instead of printing runroot.log")) parser.add_option("--quiet", action="store_true", default=options.quiet, help=_("Do not print the task information")) (opts, args) = parser.parse_args(args) if len(args) < 3: parser.error(_("Incorrect number of arguments")) assert False # pragma: no cover activate_session(session, options) tag = args[0] arch = args[1] if opts.use_shell: # everything must be correctly quoted command = ' '.join(args[2:]) else: command = args[2:] try: kwargs = { 'channel': opts.channel_override, 'packages': opts.package, 'mounts': opts.mount, 'repo_id': opts.repo_id, 'skip_setarch': opts.skip_setarch, 'weight': opts.weight } # Only pass this kwarg if it is true - this prevents confusing older # builders with a different function signature if opts.new_chroot: kwargs['new_chroot'] = True task_id = session.runroot(tag, arch, command, **kwargs) except koji.GenericError as e: if 'Invalid method' in str(e): print("* The runroot plugin appears to not be installed on the" " koji hub. Please contact the administrator.") raise if opts.task_id: print(task_id) if not opts.wait: return if opts.watch: session.logout() return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval) try: while True: # wait for the task to finish if session.taskFinished(task_id): break time.sleep(options.poll_interval) except KeyboardInterrupt: # this is probably the right thing to do here print("User interrupt: canceling runroot task") session.cancelTask(task_id) raise output = list_task_output_all_volumes(session, task_id) if 'runroot.log' in output: for volume in output['runroot.log']: log = session.downloadTaskOutput(task_id, 'runroot.log', volume=volume) sys.stdout.write(log) info = session.getTaskInfo(task_id) if info is None: sys.exit(1) state = koji.TASK_STATES[info['state']] if state in ('FAILED', 'CANCELED'): sys.exit(1)
def handle_kiwi_build(goptions, session, args): "[build] Run a command in a buildroot" usage = "usage: %prog kiwi-build [options] <target> <description_scm> <description_path>" usage += "\n(Specify the --help global option for a list of other help options)" parser = OptionParser(usage=usage) parser.add_option("--scratch", action="store_true", default=False, help="Perform a scratch build") parser.add_option( "--repo", action="append", help="Specify a repo that will override the repo used to install " "RPMs in the image. May be used multiple times. The " "build tag repo associated with the target is the default.") parser.add_option("--noprogress", action="store_true", help="Do not display progress of the upload") parser.add_option("--kiwi-profile", action="store", default=None, help="Select profile from description file") parser.add_option("--can-fail", action="store", dest="optional_arches", metavar="ARCH1,ARCH2,...", default="", help="List of archs which are not blocking for build " "(separated by commas.") parser.add_option("--arch", action="append", dest="arches", default=[], help="Limit arches to this subset") parser.add_option("--nowait", action="store_false", dest="wait", default=True) parser.add_option( "--wait", action="store_true", help="Wait on the image creation, even if running in the background") (options, args) = parser.parse_args(args) if len(args) != 3: parser.error("Incorrect number of arguments") assert False # pragma: no cover target, scm, path = args activate_session(session, goptions) kwargs = { 'scratch': options.scratch, 'optional_arches': [ canonArch(arch) for arch in options.optional_arches.split(',') if arch ], 'profile': options.kiwi_profile, } arches = [] if options.arches: arches = [canonArch(arch) for arch in options.arches] task_id = session.kiwiBuild(target=target, arches=arches, desc_url=scm, desc_path=path, **kwargs) if not goptions.quiet: print("Created task: %d" % task_id) print("Task info: %s/taskinfo?taskID=%s" % (goptions.weburl, task_id)) if options.wait or (options.wait is None and not _running_in_bg()): session.logout() return watch_tasks(session, [task_id], quiet=goptions.quiet, poll_interval=goptions.poll_interval, topurl=goptions.topurl)