def create_placeholder_cli(runtime, kind, advisory_id, default_advisory_type, noop): """Create a placeholder bug for attaching to an advisory. KIND - The kind of placeholder to create ({}). ADVISORY - Optional. The advisory to attach the bug to. $ elliott --group openshift-4.1 create-placeholder --kind rpm --attach 12345 """.format('/'.join(elliottlib.constants.standard_advisory_types)) if advisory_id and default_advisory_type: raise click.BadParameter( "Use only one of --use-default-advisory or --advisory") runtime.initialize() if default_advisory_type is not None: advisory_id = find_default_advisory(runtime, default_advisory_type) kind = default_advisory_type if not kind: raise click.BadParameter( "--kind must be specified when not using --use-default-advisory") bug_trackers = runtime.bug_trackers # we want to create one placeholder bug regardless of multiple bug trackers being used # we give priority to jira in case both are in use if runtime.use_jira or runtime.only_jira: create_placeholder(kind, advisory_id, bug_trackers['jira'], noop) else: create_placeholder(kind, advisory_id, bug_trackers['bugzilla'], noop)
def advisory_impetus_cli(runtime, advisory, default_advisory_type): """Get advisory impetus. $ elliott advisory-impetus --advisory 48465 or $ elliott -g openshift-4.2 advisory-impetus --use-default-advisory extras """ if advisory and default_advisory_type: raise click.BadParameter( 'Use only one of --use-default-advisory or --advisory') runtime.initialize(no_group=(advisory is not None)) if advisory is None: advisory = find_default_advisory(runtime, default_advisory_type) comments = errata.get_metadata_comments_json(advisory) if not comments or 'impetus' not in comments[0]: print('Error! impetus not found for advisory {}'.format(advisory), file=sys.stderr) print(comments, file=sys.stderr) exit(1) print(comments[0]['impetus'])
def repair_bugs(runtime, advisory, auto, id, original_state, new_state, comment, close_placeholder, use_jira, noop, default_advisory_type, bug_tracker): changed_bug_count = 0 if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if auto: click.echo("Fetching Advisory(errata_id={})".format(advisory)) if use_jira: raw_bug_list = [ issue["key"] for issue in errata.get_jira_issue_from_advisory(advisory) ] else: e = elliottlib.errata.Advisory(errata_id=advisory) raw_bug_list = e.errata_bugs else: click.echo("Bypassed fetching erratum, using provided BZs") raw_bug_list = cli_opts.id_convert(id) green_print("Getting bugs for advisory") # Fetch bugs in parallel because it can be really slow doing it # one-by-one when you have hundreds of bugs pbar_header("Fetching data for {} bugs: ".format(len(raw_bug_list)), "Hold on a moment, we have to grab each one", raw_bug_list) pool = ThreadPool(cpu_count()) click.secho("[", nl=False) attached_bugs = pool.map( lambda bug: progress_func(lambda: bug_tracker.get_bug(bug), '*'), raw_bug_list) # Wait for results pool.close() pool.join() click.echo(']') green_print("Got bugs for advisory") for bug in attached_bugs: if close_placeholder and "Placeholder" in bug.summary: # if set close placeholder, ignore bug state bug_tracker.update_bug_status(bug, "CLOSED") changed_bug_count += 1 else: if bug.status in original_state: bug_tracker.update_bug_status(bug, new_state) # only add comments for non-placeholder bug if comment and not noop: bug_tracker.add_comment(bug, comment, private=False) changed_bug_count += 1 green_print("{} bugs successfully modified (or would have been)".format( changed_bug_count))
def remove_bugs(runtime, advisory, default_advisory_type, id): """Remove given BUGS from ADVISORY. Remove bugs that have been attached an advisory: \b $ elliott --group openshift-3.7 remove-bugs --id 123456 --advisory 1234123 Remove two bugs from default rpm advisory. Note that --group is required because default advisory is from ocp-build-data: \b $ elliott --group openshift-3.7 remove-bugs --id 123456 --id 3412311 --use-default-advisory rpm """ if bool(advisory) == bool(default_advisory_type): raise click.BadParameter( "Specify exactly one of --use-default-advisory or advisory arg") runtime.initialize() bz_data = runtime.gitdata.load_data(key='bugzilla').data bzapi = elliottlib.bzutil.get_bzapi(bz_data) bug_ids = [bzapi.getbug(i) for i in cli_opts.id_convert(id)] green_prefix("Found {} bugs:".format(len(bug_ids))) click.echo(" {}".format(", ".join([str(b.bug_id) for b in bug_ids]))) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if advisory is not False: try: advs = Erratum(errata_id=advisory) except GSSError: exit_unauthenticated() if advs is False: raise ElliottFatalError( "Error: Could not locate advisory {advs}".format( advs=advisory)) try: green_prefix("Removing {count} bugs from advisory:".format( count=len(bug_ids))) click.echo(" {advs}".format(advs=advisory)) advs.removeBugs([bug.id for bug in bug_ids]) advs.commit() except ErrataException as ex: raise ElliottFatalError(getattr(ex, 'message', repr(ex)))
async def attach_cve_flaws_cli(runtime: Runtime, advisory_id: int, noop: bool, default_advisory_type: str): """Attach corresponding flaw bugs for trackers in advisory (first-fix only). Also converts advisory to RHSA, if not already. Example: $ elliott --group openshift-4.6 attach-cve-flaws --use-default-advisory image INFO Cloning config data from https://github.com/openshift/ocp-build-data.git INFO Using branch from group.yml: rhaos-4.6-rhel-8 INFO found 114 tracker bugs attached to the advisory INFO found 58 corresponding flaw bugs INFO 23 out of 58 flaw bugs considered "first-fix" INFO Adding the following BZs to the advisory: [1880456, 1858827, 1880460, 1847310, 1857682, 1857550, 1857551, 1857559, 1848089, 1848092, 1849503, 1851422, 1866148, 1858981, 1852331, 1861044, 1857081, 1857977, 1848647, 1849044, 1856529, 1843575, 1840253] """ if sum(map(bool, [advisory_id, default_advisory_type])) != 1: raise click.BadParameter("Use one of --use-default-advisory or --advisory") runtime.initialize() if not advisory_id and default_advisory_type is not None: advisory_id = find_default_advisory(runtime, default_advisory_type) runtime.logger.info("Getting advisory %s", advisory_id) advisory = Erratum(errata_id=advisory_id) exit_code = 0 # Flaw bugs associated with jira tracker bugs # exist in bugzilla. so to work with jira trackers # we need both bugzilla and jira instances initialized if runtime.only_jira: runtime.use_jira = True tasks = [] for bug_tracker in runtime.bug_trackers.values(): flaw_bug_tracker = runtime.bug_trackers['bugzilla'] tasks.append(asyncio.get_event_loop().create_task(get_flaws(runtime, advisory, bug_tracker, flaw_bug_tracker, noop))) try: lists_of_flaw_bugs = await asyncio.gather(*tasks) flaw_bugs = list(set(sum(lists_of_flaw_bugs, []))) if flaw_bugs: bug_tracker = runtime.bug_trackers['bugzilla'] _update_advisory(runtime, advisory, flaw_bugs, bug_tracker, noop) except Exception as e: runtime.logger.error(traceback.format_exc()) runtime.logger.error(f'Exception: {e}') exit_code = 1 sys.exit(exit_code)
def create_placeholder_cli(runtime, kind, advisory, default_advisory_type): """Create a placeholder bug for attaching to an advisory. KIND - The kind of placeholder to create ({}). ADVISORY - Optional. The advisory to attach the bug to. $ elliott --group openshift-4.1 create-placeholder --kind rpm --attach 12345 """.format('/'.join(elliottlib.constants.standard_advisory_types)) runtime.initialize() if advisory and default_advisory_type: raise click.BadParameter( "Use only one of --use-default-advisory or --advisory") if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) kind = default_advisory_type if kind is None: raise click.BadParameter( "--kind must be specified when not using --use-default-advisory") bz_data = runtime.gitdata.load_data(key='bugzilla').data target_release = bz_data['target_release'][0] newbug = elliottlib.bzutil.create_placeholder(bz_data, kind, target_release) click.echo("Created BZ: {} {}".format(newbug.id, newbug.weburl)) if advisory is not False: click.echo("Attaching to advisory...") try: advs = Erratum(errata_id=advisory) except GSSError: exit_unauthenticated() if advs is False: raise ElliottFatalError( "Error: Could not locate advisory {advs}".format( advs=advisory)) try: green_prefix("Adding placeholder bug to advisory:") click.echo(" {advs}".format(advs=advisory)) advs.addBugs([newbug.id]) advs.commit() except ErrataException as ex: raise ElliottFatalError(getattr(ex, 'message', repr(ex)))
def attach_bugs_cli(runtime: Runtime, advisory, default_advisory_type, bug_ids, report, output, noop): """Attach OCP Bugs to ADVISORY Print bug details with --report For attaching use --advisory, --use-default-advisory <TYPE> Print bug report (no attach) \b $ elliott -g openshift-4.10 attach-bugs 8675309 7001337 --report Print bug report for jira bugs (no attach) \b $ USEJIRA=true elliott -g openshift-4.10 attach-bugs OCPBUGS-10 OCPBUGS-9 --report Attach bugs to the advisory 123456 \b $ elliott -g openshift-4.10 attach-bugs 8675309 7001337 --advisory 123456 Attach bugs to the 4.10.2 assembly defined image advisory \b $ elliott -g openshift-4.10 --assembly 4.10.2 attach-bugs 8675309 7001337 --use-default-advisory image """ if advisory and default_advisory_type: raise click.BadParameter( "Use only one of --use-default-advisory <TYPE> or --advisory <ADVISORY_ID>" ) runtime.initialize() if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) bug_trackers = runtime.bug_trackers if runtime.use_jira or runtime.only_jira: bug_ids = cli_opts.id_convert_str(bug_ids) attach_bugs(runtime, advisory, bug_ids, report, output, noop, bug_trackers['jira']) else: bug_ids = cli_opts.id_convert(bug_ids) attach_bugs(runtime, advisory, bug_ids, report, output, noop, bug_trackers['bugzilla'])
def advisory_images_cli(runtime, advisory): """List images of a given advisory in the format we usually send to CCS (docs team) $ elliott advisory-images --advisory 48465 If no `--advisory` is provided, elliott will use the default image advisory of the given group. $ elliott --group openshift-4.2 advisory-images """ runtime.initialize(no_group=(advisory is not None)) if advisory is None: advisory = find_default_advisory(runtime, 'image') print(errata.get_advisory_images(advisory))
def get_golang_versions_cli(runtime, advisory_id, default_advisory_type, nvrs, components): """ Prints the Go version used to build a component to stdout. Usage: \b $ elliott go -a 76557 List go version for brew builds in the given advisory \b $ elliott go -a 79683 -c ironic-container,ose-ovirt-csi-driver-container List go version for brew builds in the given advisory \b $ elliott -g openshift-4.8 go --use-default-advisory image -c grafana-container,ose-installer-container List go version for brew builds for given component names attached to the default advisory for a group \b $ elliott go -n podman-3.0.1-6.el8,podman-1.9.3-3.rhaos4.6.el8 List go version for given brew builds """ count_options = sum(map(bool, [advisory_id, nvrs, default_advisory_type])) if count_options > 1: raise click.BadParameter( "Use only one of --advisory, --nvrs, --use-default-advisory") if default_advisory_type: runtime.initialize() advisory_id = find_default_advisory(runtime, default_advisory_type) else: runtime.initialize(no_group=True) if advisory_id: if components: components = [c.strip() for c in components.split(',')] return get_advisory_golang(advisory_id, components, logger) if nvrs: nvrs = [n.strip() for n in nvrs.split(',')] return get_nvrs_golang(nvrs, logger)
def remove_bugs_cli(runtime, advisory_id, default_advisory_type, bug_ids, remove_all, noop): """Remove given BUGS from ADVISORY. Remove bugs that have been attached an advisory: \b $ elliott --group openshift-4.10 remove-bugs 123456 --advisory 1234123 Remove two bugs from default image advisory \b $ elliott --group openshift-4.10 --assembly 4.10.19 remove-bugs 123456 3412311 --use-default-advisory image Remove all bugs from default image advisory \b $ elliott --group openshift-4.10 --assembly 4.10.19 remove-bugs --all --use-default-advisory image """ if bool(remove_all) == bool(bug_ids): raise click.BadParameter("Specify either <BUGID> or --all param") if bool(advisory_id) == bool(default_advisory_type): raise click.BadParameter("Specify exactly one of --use-default-advisory or advisory arg") runtime.initialize() if default_advisory_type is not None: advisory_id = find_default_advisory(runtime, default_advisory_type) bug_trackers = runtime.bug_trackers if runtime.use_jira or runtime.only_jira: bug_ids = cli_opts.id_convert_str(bug_ids) remove_bugs(advisory_id, bug_ids, remove_all, bug_trackers['jira'], noop) else: bug_ids = cli_opts.id_convert(bug_ids) remove_bugs(advisory_id, bug_ids, remove_all, bug_trackers['bugzilla'], noop)
def find_builds_cli(runtime, advisory, default_advisory_type, builds, kind, from_diff, as_json, allow_attached, remove, clean, no_cdn_repos, payload, non_payload, brew_event): '''Automatically or manually find or attach/remove viable rpm or image builds to ADVISORY. Default behavior searches Brew for viable builds in the given group. Provide builds manually by giving one or more --build (-b) options. Manually provided builds are verified against the Errata Tool API. \b * Attach the builds to ADVISORY by giving --attach * Remove the builds to ADVISORY by giving --remove * Specify the build type using --kind KIND Example: Assuming --group=openshift-3.7, then a build is a VIABLE BUILD IFF it meets ALL of the following criteria: \b * HAS the tag in brew: rhaos-3.7-rhel7-candidate * DOES NOT have the tag in brew: rhaos-3.7-rhel7 * IS NOT attached to ANY existing RHBA, RHSA, or RHEA That is to say, a viable build is tagged as a "candidate", has NOT received the "shipped" tag yet, and is NOT attached to any PAST or PRESENT advisory. Here are some examples: SHOW the latest OSE 3.6 image builds that would be attached to a 3.6 advisory: $ elliott --group openshift-3.6 find-builds -k image ATTACH the latest OSE 3.6 rpm builds to advisory 123456: \b $ elliott --group openshift-3.6 find-builds -k rpm --attach 123456 VERIFY (no --attach) that the manually provided RPM NVR and build ID are viable builds: $ elliott --group openshift-3.6 find-builds -k rpm -b megafrobber-1.0.1-2.el7 -a 93170 \b Remove specific RPM NVR and build ID from advisory: $ elliott --group openshift-4.3 find-builds -k image -b oauth-server-container-v4.3.22-202005212137 -a 55017 --remove ''' if from_diff and builds: raise click.BadParameter( 'Use only one of --build or --from-diff/--between.') if clean and (remove or from_diff or builds): raise click.BadParameter( 'Option --clean cannot be used with --build or --from-diff/--between.' ) if not builds and remove: raise click.BadParameter( 'Option --remove only support removing specific build with -b.') if from_diff and kind != "image": raise click.BadParameter( 'Option --from-diff/--between should be used with --kind/-k image.' ) if advisory and default_advisory_type: raise click.BadParameter( 'Use only one of --use-default-advisory or --attach') if payload and non_payload: raise click.BadParameter('Use only one of --payload or --non-payload.') runtime.initialize(mode='images' if kind == 'image' else 'none') replace_vars = runtime.group_config.vars.primitive( ) if runtime.group_config.vars else {} et_data = runtime.gitdata.load_data(key='erratatool', replace_vars=replace_vars).data tag_pv_map = et_data.get('brew_tag_product_version_mapping') if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) ensure_erratatool_auth( ) # before we waste time looking up builds we can't process # get the builds we want to add unshipped_nvrps = [] brew_session = koji.ClientSession(runtime.group_config.urls.brewhub or constants.BREW_HUB) if builds: green_prefix('Fetching builds...') unshipped_nvrps = _fetch_nvrps_by_nvr_or_id( builds, tag_pv_map, ignore_product_version=remove) elif clean: unshipped_builds = errata.get_brew_builds(advisory) elif from_diff: unshipped_nvrps = _fetch_builds_from_diff(from_diff[0], from_diff[1], tag_pv_map) else: if kind == 'image': unshipped_nvrps = _fetch_builds_by_kind_image( runtime, tag_pv_map, brew_event, brew_session, payload, non_payload) elif kind == 'rpm': unshipped_nvrps = _fetch_builds_by_kind_rpm( runtime, tag_pv_map, brew_event, brew_session) pbar_header('Fetching builds from Errata: ', 'Hold on a moment, fetching buildinfos from Errata Tool...', unshipped_builds if clean else unshipped_nvrps) if not clean and not remove: # if is --clean then batch fetch from Erratum no need to fetch them individually # if is not for --clean fetch individually using nvrp tuples then get specific # elliottlib.brew.Build Objects by get_brew_build() # e.g. : # ('atomic-openshift-descheduler-container', 'v4.3.23', '202005250821', 'RHEL-7-OSE-4.3'). # Build(atomic-openshift-descheduler-container-v4.3.23-202005250821). unshipped_builds = parallel_results_with_progress( unshipped_nvrps, lambda nvrp: elliottlib.errata.get_brew_build( '{}-{}-{}'.format(nvrp[0], nvrp[1], nvrp[2]), nvrp[3], session=requests.Session())) if not (allow_attached or builds): unshipped_builds = _filter_out_inviable_builds( kind, unshipped_builds, elliottlib.errata) _json_dump(as_json, unshipped_builds, kind, tag_pv_map) if not unshipped_builds: green_print('No builds needed to be attached.') return if advisory: if remove: _detach_builds( advisory, [f"{nvrp[0]}-{nvrp[1]}-{nvrp[2]}" for nvrp in unshipped_nvrps]) elif clean: _detach_builds(advisory, [b.nvr for b in unshipped_builds]) else: # attach erratum = _update_to_advisory(unshipped_builds, kind, advisory, remove, clean) if not no_cdn_repos and kind == "image" and not (remove or clean): cdn_repos = et_data.get('cdn_repos') if cdn_repos: # set up CDN repos click.echo( f"Configuring CDN repos {', '.join(cdn_repos)}...") erratum.metadataCdnRepos(enable=cdn_repos) click.echo("Done") else: click.echo('The following {n} builds '.format(n=len(unshipped_builds)), nl=False) if not (remove or clean): click.secho('may be attached', bold=True, nl=False) click.echo(' to an advisory:') else: click.secho('may be removed from', bold=True, nl=False) click.echo(' from an advisory:') for b in sorted(unshipped_builds): click.echo(' ' + b.nvr)
def change_state_cli(runtime, state, advisory, default_advisory_type, noop): """Change the state of an ADVISORY. Additional permissions may be required to change an advisory to certain states. An advisory may not move between some states until all criteria have been met. For example, an advisory can not move from NEW_FILES to QE unless Bugzilla Bugs or JIRA Issues have been attached. NOTE: The two advisory options are mutually exclusive and can not be used together. See the find-bugs help for additional information on adding Bugzilla Bugs. Move the advisory 123456 from NEW_FILES to QE state: $ elliott change-state --state QE --advisory 123456 Move the advisory 123456 back to NEW_FILES (short option flag): $ elliott change-state -s NEW_FILES -a 123456 Do not actually change state, just check that the command could have ran (for example, when testing out pipelines) $ elliott change-state -s NEW_FILES -a 123456 --noop """ if not (bool(advisory) ^ bool(default_advisory_type)): raise click.BadParameter( "Use only one of --use-default-advisory or --advisory") runtime.initialize(no_group=default_advisory_type is None) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if noop: prefix = "[NOOP] " else: prefix = "" try: e = Erratum(errata_id=advisory) if e.errata_state == state: green_prefix("{}No change to make: ".format(prefix)) click.echo("Target state is same as current state") return # we have 5 different states we can only change the state if it's in NEW_FILES or QE # "NEW_FILES", # "QE", # "REL_PREP", # "PUSH_READY", # "IN_PUSH" if e.errata_state != 'NEW_FILES' and e.errata_state != 'QE': if default_advisory_type is not None: raise ElliottFatalError( "Error: Could not change '{state}' advisory {advs}, group.yml is probably pointing at old one" .format(state=e.errata_state, advs=advisory)) else: raise ElliottFatalError( "Error: we can only change the state if it's in NEW_FILES or QE, current state is {s}" .format(s=e.errata_state)) else: if noop: green_prefix("{}Would have changed state: ".format(prefix)) click.echo("{} ➔ {}".format(e.errata_state, state)) return else: # Capture current state because `e.commit()` will # refresh the `e.errata_state` attribute old_state = e.errata_state e.setState(state) e.commit() green_prefix("Changed state: ") click.echo("{old_state} ➔ {new_state}".format( old_state=old_state, new_state=state)) except ErrataException as ex: raise ElliottFatalError(getattr(ex, 'message', repr(ex))) green_print("Successfully changed advisory state")
def poll_signed(runtime, minutes, advisory, default_advisory_type, noop): """Poll for the signed-status of RPM builds attached to ADVISORY. Returns rc=0 when all builds have been signed. Returns non-0 after MINUTES have passed and all builds have not been signed. This non-0 return code is the number of unsigned builds remaining. All builds must show 'signed' for this command to succeed. NOTE: The two advisory options are mutually exclusive. For testing in pipeline scripts this sub-command accepts a --noop option. When --noop is used the value of --minutes is irrelevant. This command will print out the signed state of all attached builds and then exit with rc=0 if all builds are signed and non-0 if builds are still unsigned. In the non-0 case the return code is the number of unsigned builds. Wait 15 minutes for the default 4.2 advisory to show all RPMS have been signed: $ elliott -g openshift-4.2 poll-signed --use-default-advisory rpm Wait 5 mintes for the provided 4.2 advisory to show all RPMs have been signed: $ elliott -g openshift-4.2 poll-signed -m 5 --advisory 123456 Print the signed status of all attached builds, exit immediately. Return code is the number of unsigned builds. \b $ elliott -g openshift-4.2 poll-signed --noop --use-default-advisory rpm """ if not (bool(advisory) ^ bool(default_advisory_type)): raise click.BadParameter( "Use only one of --use-default-advisory or --advisory") runtime.initialize(no_group=default_advisory_type is None) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if not noop: click.echo("Polling up to {} minutes for all RPMs to be signed".format( minutes)) try: e = elliottlib.errata.Advisory(errata_id=advisory) all_builds = set([]) all_signed = False # `errata_builds` is a dict with brew tags as keys, values are # lists of builds on the advisory with that tag for k, v in e.errata_builds.items(): all_builds = all_builds.union(set(v)) green_prefix("Fetching initial states: ") click.echo("{} builds to check".format(len(all_builds))) start_time = datetime.datetime.now() while datetime.datetime.now() - start_time < datetime.timedelta( minutes=minutes): pbar_header("Getting build signatures: ", "Should be pretty quick", all_builds) pool = ThreadPool(cpu_count()) # Look up builds concurrently click.secho("[", nl=False) build_sigs = pool.map( lambda build: progress_func( lambda: elliottlib.errata.build_signed(build), '*'), all_builds) # Wait for results pool.close() pool.join() click.echo(']') if all(build_sigs): all_signed = True break elif noop: # Escape the time-loop break else: yellow_prefix("Not all builds signed: ") click.echo("re-checking") continue if not all_signed: red_prefix("Signing incomplete: ") if noop: click.echo("All builds not signed. ") else: click.echo( "All builds not signed in given window ({} minutes). ". format(minutes)) exit(1) else: green_prefix("All builds signed: ") click.echo("Enjoy!") except ErrataException as ex: raise ElliottFatalError(getattr(ex, 'message', repr(ex)))
def get(ctx, runtime, default_advisory_type, details, id_only, as_json, advisory): """Get details about a specific advisory from the Errata Tool. By default a brief one-line informational string is printed. Use the --details option to fetch and print the full details of the advisory. Use of --id-only will override all other printing options. Requires using --use-default-advisory. Only the ID of the advisory is printed to standard out. Fields for the short format: Release date, State, Synopsys, URL Basic one-line output for advisory 123456: \b $ elliott get 123456 2018-02-23T18:34:40 NEW_FILES OpenShift Container Platform 3.9 bug fix and enhancement update - https://errata.devel.redhat.com/advisory/123456 Get the full JSON advisory object, use `jq` to print just the errata portion of the advisory: \b $ elliott get --json - 123456 | jq '.errata' { "rhba": { "actual_ship_date": null, "assigned_to_id": 3002255, "batch_id": null, ... """ runtime.initialize(no_group=default_advisory_type is None) if bool(advisory) == bool(default_advisory_type): raise click.BadParameter( "Specify exactly one of --use-default-advisory or advisory arg") if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type, quiet=True) if id_only: click.echo(advisory) return try: advisory = elliottlib.errata.Advisory(errata_id=advisory) except GSSError: exit_unauthenticated() if details: click.echo(advisory) return if not as_json: advisory_string = "{date} {state} {synopsis} {url}".format( date=advisory.publish_date_override, state=advisory.errata_state, synopsis=advisory.synopsis, url=advisory.url()) click.echo(advisory_string) return json_data = advisory.get_erratum_data() json_data['bugs'] = advisory.errata_bugs json_data['current_flags'] = advisory.current_flags json_data['errata_builds'] = advisory.errata_builds json_data['rpmdiffs'] = advisory.externalTests(test_type='rpmdiff') if as_json == "-": click.echo(json.dumps(json_data, indent=4, sort_keys=True)) return with open(as_json, "w") as json_file: json.dump(json_data, json_file, indent=4, sort_keys=True)
def change_state_cli(runtime, state, advisory, default_advisories, default_advisory_type, noop): """Change the state of an ADVISORY. Additional permissions may be required to change an advisory to certain states. An advisory may not move between some states until all criteria have been met. For example, an advisory can not move from NEW_FILES to QE unless Bugzilla Bugs or JIRA Issues have been attached. NOTE: The two advisory options are mutually exclusive and can not be used together. See the find-bugs help for additional information on adding Bugs. Move assembly release advisories to QE $ elliott -g openshift-4.10 --assembly 4.10.4 change-state -s QE Move group release advisories to QE: $ elliott -g openshift-4.5 change-state -s QE --default-advisories Move the advisory 123456 to QE: $ elliott change-state --state QE --advisory 123456 Move the advisory 123456 back to NEW_FILES (short option flag): $ elliott change-state -s NEW_FILES -a 123456 Do not actually change state, just check that the command could have ran (for example, when testing out pipelines) $ elliott change-state -s NEW_FILES -a 123456 --noop """ count_flags = sum( map(bool, [advisory, default_advisory_type, default_advisories])) if count_flags > 1: raise click.BadParameter( "Use only one of --use-default-advisory or --advisory or --default-advisories" ) runtime.initialize(no_group=bool(advisory)) advisories = [] if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if advisory: advisories.append(advisory) if not advisories: advisories = list(runtime.group_config.advisories.values()) click.echo(f"Attempting to move advisories {advisories} to {state}") errors = [] for advisory in advisories: try: e = Erratum(errata_id=advisory) if e.errata_state == state: green_prefix(f"No Change ({advisory}): ") click.echo(f"Target state is same as current state: {state}") # we have 5 different states we can only change the state if it's in NEW_FILES or QE # "NEW_FILES", # "QE", # "REL_PREP", # "PUSH_READY", # "IN_PUSH" elif e.errata_state != 'NEW_FILES' and e.errata_state != 'QE': red_prefix(f"Error ({advisory}): ") if default_advisory_type is not None: click.echo( f"Could not change '{e.errata_state}', group.yml is probably pointing at old one" ) else: click.echo( f"Can only change the state if it's in NEW_FILES or QE, current state is {e.errata_state}" ) else: if noop: green_prefix(f"NOOP ({advisory}): ") click.echo( f"Would have changed state {e.errata_state} ➔ {state}") else: # Capture current state because `e.commit()` will # refresh the `e.errata_state` attribute old_state = e.errata_state e.setState(state) e.commit() green_prefix(f"Changed state ({advisory}): ") click.echo(f"{old_state} ➔ {state}") except ErrataException as ex: click.echo(f'Error fetching/changing state of {advisory}: {ex}') errors.append(ex) if errors: raise Exception(errors)
def find_bugs_cli(runtime, advisory, default_advisory_type, mode, status, id, cve_trackers, from_diff, flag, report, into_default_advisories, noop): """Find Red Hat Bugzilla bugs or add them to ADVISORY. Bugs can be "swept" into the advisory either automatically (--mode sweep), or by manually specifying one or more bugs using --mode list and the --id option. Use cases are described below: Note: Using --id without --add is basically pointless SWEEP: For this use-case the --group option MUST be provided. The --group automatically determines the correct target-releases to search for bugs claimed to be fixed, but not yet attached to advisories. LIST: The --group option is not required if you are specifying bugs manually. Provide one or more --id's for manual bug addition. In LIST mode you must provide a list of IDs to attach with the --id option. DIFF: For this use case, you must provide the --between option using two URLs to payloads. QE: Find MODIFIED bugs for the target-releases, and set them to ON_QA. The --group option MUST be provided. Cannot be used in combination with --into-default-advisories, --add, --into-default-advisories Using --use-default-advisory without a value set for the matching key in the build-data will cause an error and elliott will exit in a non-zero state. Use of this option silently overrides providing an advisory with the --add option. Automatically add bugs with target-release matching 3.7.Z or 3.7.0 to advisory 123456: \b $ elliott --group openshift-3.7 find-bugs --mode sweep --add 123456 List bugs that WOULD be added to an advisory and have set the bro_ok flag on them (NOOP): \b $ elliott --group openshift-3.7 find-bugs --mode sweep --flag bro_ok Attach bugs to their correct default advisories, e.g. operator-related bugs go to "extras" instead of the default "image": \b $ elliott --group=openshift-4.4 find-bugs --mode=sweep --into-default-advisories Add two bugs to advisory 123456. Note that --group is not required because we're not auto searching: \b $ elliott find-bugs --mode list --id 8675309 --id 7001337 --add 123456 Automatically find bugs for openshift-4.1 and attach them to the rpm advisory defined in ocp-build-data: \b $ elliott --group=openshift-4.1 --mode sweep --use-default-advisory rpm Find bugs for 4.6 that are in MODIFIED state, and set them to ON_QA: \b $ elliott --group=openshift-4.6 --mode qe """ if mode != 'list' and len(id) > 0: raise click.BadParameter( "Combining the automatic and manual bug attachment options is not supported" ) if mode == 'list' and len(id) == 0: raise click.BadParameter( "When using mode=list, you must provide a list of bug IDs") if mode == 'payload' and not len(from_diff) == 2: raise click.BadParameter( "If using mode=payload, you must provide two payloads to compare") if sum( map(bool, [advisory, default_advisory_type, into_default_advisories ])) > 1: raise click.BadParameter( "Use only one of --use-default-advisory, --add, or --into-default-advisories" ) if mode == 'qe' and sum( map(bool, [advisory, default_advisory_type, into_default_advisories ])) > 0: raise click.BadParameter( "--mode=qe does not operate on an advisory. Do not specify any of `--use-default-advisory`, `--add`, or `--into-default-advisories`" ) runtime.initialize() bz_data = runtime.gitdata.load_data(key='bugzilla').data bzapi = bzutil.get_bzapi(bz_data) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if mode == 'sweep' or mode == 'qe': if mode == 'qe': status = ['MODIFIED'] green_prefix( f"Searching for bugs with status {' '.join(status)} and target release(s):" ) click.echo(" {tr}".format(tr=", ".join(bz_data['target_release']))) bugs = bzutil.search_for_bugs( bz_data, status, filter_out_security_bugs=not (cve_trackers), verbose=runtime.debug) elif mode == 'list': bugs = [bzapi.getbug(i) for i in cli_opts.id_convert(id)] elif mode == 'diff': click.echo(runtime.working_dir) bug_id_strings = openshiftclient.get_bug_list(runtime.working_dir, from_diff[0], from_diff[1]) bugs = [bzapi.getbug(i) for i in bug_id_strings] # Some bugs should goes to CPaaS so we should ignore them m = re.match( r"rhaos-(\d+).(\d+)", runtime.branch ) # extract OpenShift version from the branch name. there should be a better way... if not m: raise ElliottFatalError( f"Unable to determine OpenShift version from branch name {runtime.branch}." ) major_version = int(m[1]) minor_version = int(m[2]) def _filter_bugs(bugs): # returns a list of bugs that should be processed r = [] ignored_repos = set() # GitHub repos that should be ignored if major_version == 4 and minor_version == 5: # per https://issues.redhat.com/browse/ART-997: these repos should have their release-4.5 branches ignored by ART: ignored_repos = { "https://github.com/openshift/aws-ebs-csi-driver", "https://github.com/openshift/aws-ebs-csi-driver-operator", "https://github.com/openshift/cloud-provider-openstack", "https://github.com/openshift/csi-driver-nfs", "https://github.com/openshift/csi-driver-manila-operator" } for bug in bugs: external_links = [ ext["type"]["full_url"].replace("%id%", ext["ext_bz_bug_id"]) for ext in bug.external_bugs ] # https://github.com/python-bugzilla/python-bugzilla/blob/7aa70edcfea9b524cd8ac51a891b6395ca40dc87/bugzilla/_cli.py#L750 public_links = [ runtime.get_public_upstream(url)[0] for url in external_links ] # translate openshift-priv org to openshift org when comparing to filter (i.e. prow may link to a PR on the private org). # if a bug has 1 or more public_links, we should ignore the bug if ALL of the public_links are ANY of `ignored_repos` if public_links and all( map( lambda url: any( map( lambda repo: url != repo and url.startswith( repo), ignored_repos)), public_links)): continue r.append(bug) return r if len( id ) == 0: # unless --id is given, we should ignore bugs that don't belong to ART. e.g. some bugs should go to CPaaS filtered_bugs = _filter_bugs(bugs) green_prefix( f"Found {len(filtered_bugs)} bugs ({len(bugs) - len(filtered_bugs)} ignored):" ) bugs = filtered_bugs else: green_prefix("Found {} bugs:".format(len(bugs))) click.echo(" {}".format(", ".join([str(b.bug_id) for b in bugs]))) if mode == 'qe': for bug in bugs: bzutil.set_state(bug, 'ON_QA', noop=noop) if len(flag) > 0: for bug in bugs: for f in flag: if noop: click.echo( f'Would have updated bug {bug.id} by setting flag {f}') continue bug.updateflags({f: "+"}) if report: green_print("{:<8s} {:<25s} {:<12s} {:<7s} {:<10s} {:60s}".format( "ID", "COMPONENT", "STATUS", "SCORE", "AGE", "SUMMARY")) for bug in bugs: created_date = datetime.datetime.strptime(str(bug.creation_time), '%Y%m%dT%H:%M:%S') days_ago = (datetime.datetime.today() - created_date).days click.echo( "{:<8d} {:<25s} {:<12s} {:<7s} {:<3d} days {:60s} ".format( bug.id, bug.component, bug.status, bug.cf_pm_score if hasattr(bug, "cf_pm_score") else '?', days_ago, bug.summary[:60])) if advisory and not default_advisory_type: # `--add ADVISORY_NUMBER` should respect the user's wish and attach all available bugs to whatever advisory is specified. errata.add_bugs_with_retry(advisory, bugs, noop=noop) return # If --use-default-advisory or --into-default-advisories is given, we need to determine which bugs should be swept into which advisory. # Otherwise we don't need to sweep bugs at all. if not (into_default_advisories or default_advisory_type): return impetus_bugs = { } # key is impetus ("rpm", "image", "extras"), value is a set of bug IDs. # @lmeyer: simple and stupid would still be keeping the logic in python, possibly with config flags for branched logic. until that logic becomes too ugly to keep in python, i suppose.. if major_version < 4: # for 3.x, all bugs should go to the rpm advisory impetus_bugs["rpm"] = set(bugs) else: # for 4.x # optional operators bugs should be swept to the "extras" advisory, while other bugs should be swept to "image" advisory. # a way to identify operator-related bugs is by its "Component" value. temporarily hardcode here until we need to move it to ocp-build-data. extra_components = { "Logging", "Service Brokers", "Metering Operator", "Node Feature Discovery Operator" } # we will probably find more impetus_bugs["extras"] = { b for b in bugs if b.component in extra_components } impetus_bugs["image"] = { b for b in bugs if b.component not in extra_components } if default_advisory_type and impetus_bugs.get(default_advisory_type): errata.add_bugs_with_retry(advisory, impetus_bugs[default_advisory_type], noop=noop) elif into_default_advisories: for impetus, bugs in impetus_bugs.items(): if bugs: errata.add_bugs_with_retry( runtime.group_config.advisories[impetus], bugs, noop=noop)
def attach_cve_flaws_cli(runtime, advisory_id, noop, default_advisory_type): """Attach corresponding flaw bugs for trackers in advisory (first-fix only). Also converts advisory to RHSA, if not already. Example: $ elliott --group openshift-4.6 attach-cve-flaws --use-default-advisory image INFO Cloning config data from https://github.com/openshift/ocp-build-data.git INFO Using branch from group.yml: rhaos-4.6-rhel-8 INFO found 114 tracker bugs attached to the advisory INFO found 58 corresponding flaw bugs INFO 23 out of 58 flaw bugs considered "first-fix" INFO Adding the following BZs to the advisory: [1880456, 1858827, 1880460, 1847310, 1857682, 1857550, 1857551, 1857559, 1848089, 1848092, 1849503, 1851422, 1866148, 1858981, 1852331, 1861044, 1857081, 1857977, 1848647, 1849044, 1856529, 1843575, 1840253] """ runtime.initialize() bzurl = runtime.gitdata.bz_server_url() bzapi = bugzilla.Bugzilla(bzurl) if not advisory_id and default_advisory_type is not None: advisory_id = find_default_advisory(runtime, default_advisory_type) # get attached bugs from advisory attached_tracker_bugs = get_attached_tracker_bugs(bzapi, advisory_id) runtime.logger.info( 'found {} tracker bugs attached to the advisory: {}'.format( len(attached_tracker_bugs), sorted(bug.id for bug in attached_tracker_bugs))) if len(attached_tracker_bugs) == 0: exit(0) # validate and get target_release current_target_release, err = util.get_target_release( attached_tracker_bugs) if err: runtime.logger.error(err) exit(1) runtime.logger.info( 'current_target_release: {}'.format(current_target_release)) corresponding_flaw_bugs = get_corresponding_flaw_bugs( bzapi, attached_tracker_bugs) runtime.logger.info('found {} corresponding flaw bugs: {}'.format( len(corresponding_flaw_bugs), sorted(bug.id for bug in corresponding_flaw_bugs))) # current_target_release is digit.digit.[z|0] # if current_target_release is GA then run first-fix bug filtering # for GA not every flaw bug is considered first-fix # for z-stream every flaw bug is considered first-fix if current_target_release[-1] == 'z': runtime.logger.info( "detected z-stream target release, every flaw bug is considered first-fix" ) first_fix_flaw_bugs = corresponding_flaw_bugs else: runtime.logger.info( "detected GA release, applying first-fix filtering..") attached_tracker_ids = [ tracker.id for tracker in attached_tracker_bugs ] first_fix_flaw_bugs = [ flaw_bug for flaw_bug in corresponding_flaw_bugs if is_first_fix( bzapi, flaw_bug, current_target_release, attached_tracker_ids) ] runtime.logger.info('{} out of {} flaw bugs considered "first-fix"'.format( len(first_fix_flaw_bugs), len(corresponding_flaw_bugs), )) if not first_fix_flaw_bugs: runtime.logger.info('No "first-fix" bugs found, exiting') exit(0) advisory = Erratum(errata_id=advisory_id) if not is_security_advisory(advisory): runtime.logger.info( 'Advisory type is {}, converting it to RHSA'.format( advisory.errata_type)) cve_boilerplate = runtime.gitdata.load_data( key='erratatool').data['boilerplates']['cve'] advisory.update( errata_type='RHSA', security_reviewer=cve_boilerplate['security_reviewer'], synopsis=cve_boilerplate['synopsis'], description=cve_boilerplate['description'], topic=cve_boilerplate['topic'], solution=cve_boilerplate['solution'], security_impact='Low', ) cves = ' '.join([flaw_bug.alias[0] for flaw_bug in first_fix_flaw_bugs]) cve_str = cves if advisory.cve_names and cves not in advisory.cve_names: cve_str = "{} {}".format(advisory.cve_names, cves).strip() advisory.update(cve_names=cve_str) runtime.logger.info('List of *new* CVEs: {}'.format(cves)) highest_impact = get_highest_security_impact(first_fix_flaw_bugs) if is_advisory_impact_smaller_than(advisory, highest_impact): runtime.logger.info( 'Adjusting advisory security impact from {} to {}'.format( advisory.security_impact, highest_impact)) advisory.update(security_impact=highest_impact) flaw_ids = [flaw_bug.id for flaw_bug in first_fix_flaw_bugs] runtime.logger.info( f'Request to attach {len(flaw_ids)} bugs to the advisory') existing_bug_ids = advisory.errata_bugs new_bugs = set(flaw_ids) - set(existing_bug_ids) print(f'Bugs already attached: {len(existing_bug_ids)}') print(f'New bugs ({len(new_bugs)}) : {sorted(new_bugs)}') if new_bugs: advisory.addBugs(flaw_ids) if noop: print( 'DRY-RUN: The following changes would have been applied to the advisory:' ) print(advisory) return True return advisory.commit()
def attach_cve_flaws_cli(runtime, advisory_id, noop, default_advisory_type): """Attach corresponding flaw bugs for trackers in advisory (first-fix only). Also converts advisory to RHSA, if not already. Example: $ elliott --group openshift-4.6 attach-cve-flaws --use-default-advisory image INFO Cloning config data from https://github.com/openshift/ocp-build-data.git INFO Using branch from group.yml: rhaos-4.6-rhel-8 INFO found 114 tracker bugs attached to the advisory INFO found 58 corresponding flaw bugs INFO 23 out of 58 flaw bugs considered "first-fix" INFO Adding the following BZs to the advisory: [1880456, 1858827, 1880460, 1847310, 1857682, 1857550, 1857551, 1857559, 1848089, 1848092, 1849503, 1851422, 1866148, 1858981, 1852331, 1861044, 1857081, 1857977, 1848647, 1849044, 1856529, 1843575, 1840253] """ runtime.initialize() bzurl = runtime.gitdata.load_data(key='bugzilla').data['server'] bzapi = bugzilla.Bugzilla(bzurl) if not advisory_id and default_advisory_type is not None: advisory_id = find_default_advisory(runtime, default_advisory_type) attached_tracker_bugs = get_attached_tracker_bugs(bzapi, advisory_id) runtime.logger.info( 'found {} tracker bugs attached to the advisory'.format( len(attached_tracker_bugs))) corresponding_flaw_bugs = get_corresponding_flaw_bugs( bzapi, attached_tracker_bugs) runtime.logger.info('found {} corresponding flaw bugs'.format( len(corresponding_flaw_bugs))) attached_tracker_ids = [tracker.id for tracker in attached_tracker_bugs] current_target_release = runtime.gitdata.load_data( key='bugzilla').data['target_release'] first_fix_flaw_bugs = [ flaw_bug for flaw_bug in corresponding_flaw_bugs if is_first_fix( bzapi, flaw_bug, current_target_release, attached_tracker_ids) ] runtime.logger.info('{} out of {} flaw bugs considered "first-fix"'.format( len(first_fix_flaw_bugs), len(corresponding_flaw_bugs), )) if not first_fix_flaw_bugs: runtime.logger.info('No "first-fix" bugs found, exiting') exit(0) advisory = Erratum(errata_id=advisory_id) if not is_security_advisory(advisory): runtime.logger.info( 'Advisory type is {}, converting it to RHSA'.format( advisory.errata_type)) cve_boilerplate = runtime.gitdata.load_data( key='erratatool').data['boilerplates']['cve'] advisory.update( errata_type='RHSA', security_reviewer=cve_boilerplate['security_reviewer'], synopsis=cve_boilerplate['synopsis'], description=cve_boilerplate['description'], topic=cve_boilerplate['topic'], solution=cve_boilerplate['solution'], security_impact='Low', ) cves = ' '.join([flaw_bug.alias[0] for flaw_bug in first_fix_flaw_bugs]) advisory.update(cve_names="{} {}".format(advisory.cve_names, cves).strip()) print('List of *new* CVEs: {}'.format(cves)) highest_impact = get_highest_security_impact(first_fix_flaw_bugs) if is_advisory_impact_smaller_than(advisory, highest_impact): runtime.logger.info( 'Adjusting advisory security impact from {} to {}'.format( advisory.security_impact, highest_impact)) advisory.update(security_impact=highest_impact) flaw_ids = [flaw_bug.id for flaw_bug in first_fix_flaw_bugs] runtime.logger.info( 'Adding the following BZs to the advisory: {}'.format(flaw_ids)) advisory.addBugs(flaw_ids) if noop: print( 'DRY-RUN: The following changes would have been applied to the advisory:' ) print(advisory) return True return advisory.commit()
def find_bugs(runtime, advisory, default_advisory_type, mode, status, id, from_diff, flag, report): """Find Red Hat Bugzilla bugs or add them to ADVISORY. Bugs can be "swept" into the advisory either automatically (--mode sweep), or by manually specifying one or more bugs using --mode list and the --id option. Use cases are described below: Note: Using --id without --add is basically pointless SWEEP: For this use-case the --group option MUST be provided. The --group automatically determines the correct target-releases to search for MODIFIED bugs in. LIST: The --group option is not required if you are specifying bugs manually. Provide one or more --id's for manual bug addition. In LIST mode you must provide a list of IDs to attach with the --id option. DIFF: For this use case, you must provide the --between option using two URLs to payloads. Using --use-default-advisory without a value set for the matching key in the build-data will cause an error and elliott will exit in a non-zero state. Use of this option silently overrides providing an advisory with the --add option. Automatically add bugs with target-release matching 3.7.Z or 3.7.0 to advisory 123456: \b $ elliott --group openshift-3.7 find-bugs --mode sweep --add 123456 List bugs that WOULD be added to an advisory and have set the bro_ok flag on them (NOOP): \b $ elliott --group openshift-3.7 find-bugs --mode sweep --flag bro_ok Add two bugs to advisory 123456. Note that --group is not required because we're not auto searching: \b $ elliott find-bugs --mode list --id 8675309 --id 7001337 --add 123456 Automatically find bugs for openshift-4.1 and attach them to the rpm advisory defined in ocp-build-data: \b $ elliott --group=openshift-4.1 --mode sweep --use-default-advisory rpm """ if mode != 'list' and len(id) > 0: raise click.BadParameter( "Combining the automatic and manual bug attachment options is not supported" ) if mode == 'list' and len(id) == 0: raise click.BadParameter( "When using mode=list, you must provide a list of bug IDs") if mode == 'payload' and not len(from_diff) == 2: raise click.BadParameter( "If using mode=payload, you must provide two payloads to compare") if advisory and default_advisory_type: raise click.BadParameter( "Use only one of --use-default-advisory or --add") runtime.initialize() bz_data = runtime.gitdata.load_data(key='bugzilla').data bzapi = elliottlib.bzutil.get_bzapi(bz_data) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if mode == 'sweep': green_prefix("Searching for bugs with target release(s):") click.echo(" {tr}".format(tr=", ".join(bz_data['target_release']))) bug_ids = elliottlib.bzutil.search_for_bugs(bz_data, status, verbose=runtime.debug) elif mode == 'list': bug_ids = [bzapi.getbug(i) for i in cli_opts.id_convert(id)] elif mode == "diff": click.echo(runtime.working_dir) bug_id_strings = elliottlib.openshiftclient.get_bug_list( runtime.working_dir, from_diff[0], from_diff[1]) bug_ids = [bzapi.getbug(i) for i in bug_id_strings] green_prefix("Found {} bugs:".format(len(bug_ids))) click.echo(" {}".format(", ".join([str(b.bug_id) for b in bug_ids]))) if report: green_print("{:<8s} {:<25s} {:<12s} {:<7s} {:<10s} {:60s}".format( "ID", "COMPONENT", "STATUS", "SCORE", "AGE", "SUMMARY")) for bug in bug_ids: created_date = datetime.datetime.strptime(str(bug.creation_time), '%Y%m%dT%H:%M:%S') days_ago = (datetime.datetime.today() - created_date).days click.echo( "{:<8d} {:<25s} {:<12s} {:<7s} {:<3d} days {:60s} ".format( bug.id, bug.component, bug.status, bug.cf_pm_score if hasattr(bug, "cf_pm_score") else '?', days_ago, bug.summary[:60])) if len(flag) > 0: for bug in bug_ids: for f in flag: bug.updateflags({f: "+"}) if advisory is not False: elliottlib.errata.add_bugs_with_retry(advisory, [bug.id for bug in bug_ids], False)
def repair_bugs(runtime, advisory, auto, id, original_state, new_state, noop, default_advisory_type): """Move bugs attached to the advisory from one state to another state. This is useful if the bugs have changed states *after* they were attached. Similar to `find-bugs` but in reverse. `repair-bugs` begins by reading bugs from an advisory, whereas `find-bugs` reads from bugzilla. This looks at attached bugs in the provided --from state and moves them to the provided --to state. \b Background: This is intended for bugs which went to MODIFIED, were attached to advisories, set to ON_QA, and then failed testing. When this happens their state is reset back to ASSIGNED. Using --use-default-advisory without a value set for the matching key in the build-data will cause an error and elliott will exit in a non-zero state. Most likely you will only want to use the `rpm` state, but that could change in the future. Use of this option conflicts with providing an advisory with the -a/--advisory option. Move bugs on 123456 FROM the MODIFIED state back TO ON_QA state: \b $ elliott --group=openshift-4.1 repair-bugs --auto --advisory 123456 --from MODIFIED --to ON_QA As above, but using the default RPM advisory defined in ocp-build-data: \b $ elliott --group=openshift-4.1 repair-bugs --auto --use-default-advisory rpm --from MODIFIED --to ON_QA The previous examples could also be ran like this (MODIFIED and ON_QA are both defaults): \b $ elliott --group=openshift-4.1 repair-bugs --auto --use-default-advisory rpm Bug ids may be given manually instead of using --auto: \b $ elliott --group=openshift-4.1 repair-bugs --id 170899 --id 8675309 --use-default-advisory rpm """ if auto and len(id) > 0: raise click.BadParameter( "Combining the automatic and manual bug modification options is not supported" ) if not auto and len(id) == 0: # No bugs were provided raise click.BadParameter( "If not using --auto then one or more --id's must be provided") if advisory and default_advisory_type: raise click.BadParameter( "Use only one of --use-default-advisory or --advisory") if len(id) == 0 and advisory is None and default_advisory_type is None: # error, no bugs, advisory, or default selected raise click.BadParameter( "No input provided: Must use one of --id, --advisory, or --use-default-advisory" ) # Load bugzilla infomation and get a reference to the api runtime.initialize() bz_data = runtime.gitdata.load_data(key='bugzilla').data bzapi = elliottlib.bzutil.get_bzapi(bz_data) changed_bug_count = 0 attached_bugs = [] if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) raw_bug_list = [] if auto: click.echo("Fetching Erratum(errata_id={})".format(advisory)) e = Erratum(errata_id=advisory) raw_bug_list = e.errata_bugs else: click.echo("Bypassed fetching erratum, using provided BZs") raw_bug_list = cli_opts.id_convert(id) green_print("Getting bugs for advisory") # Fetch bugs in parallel because it can be really slow doing it # one-by-one when you have hundreds of bugs pbar_header("Fetching data for {} bugs: ".format(len(raw_bug_list)), "Hold on a moment, we have to grab each one", raw_bug_list) pool = ThreadPool(cpu_count()) click.secho("[", nl=False) attached_bugs = pool.map( lambda bug: progress_func(lambda: bzapi.getbug(bug), '*'), raw_bug_list) # Wait for results pool.close() pool.join() click.echo(']') green_print("Got bugs for advisory") for bug in attached_bugs: if bug.status in original_state: changed_bug_count += 1 elliottlib.bzutil.set_state(bug, new_state, noop=noop) green_print("{} bugs successfullly modified (or would have been)".format( changed_bug_count))
def find_bugs_cli(runtime, advisory, default_advisory_type, mode, check_builds, status, exclude_status, id, cve_trackers, from_diff, flag, report, into_default_advisories, noop): """Find Red Hat Bugzilla bugs or add them to ADVISORY. Bugs can be "swept" into the advisory either automatically (--mode sweep), or by manually specifying one or more bugs using --mode list with the --id option. Use cases are described below: Note: Using --id without --add is basically pointless SWEEP: For this use-case the --group option MUST be provided. The --group automatically determines the correct target-releases to search for bugs claimed to be fixed, but not yet attached to advisories. --check-builds flag forces bug validation with attached builds to rpm advisory. It assumes builds have been attached and only attaches bugs with matching builds. default --status: ['MODIFIED', 'ON_QA', 'VERIFIED'] LIST: The --group option is not required if you are specifying bugs manually. Provide one or more --id's for manual bug addition. In LIST mode you must provide a list of IDs to attach with the --id option. DIFF: For this use case, you must provide the --between option using two URLs to payloads. QE: Find MODIFIED bugs for the target-releases, and set them to ON_QA. The --group option MUST be provided. Cannot be used in combination with --add, --use-default-advisory, --into-default-advisories, --exclude-status. BLOCKER: List active blocker+ bugs for the target-releases. The --group option MUST be provided. Cannot be used in combination with --add, --use-default-advisory, --into-default-advisories. default --status: ['NEW', 'ASSIGNED', 'POST', 'MODIFIED', 'ON_DEV', 'ON_QA'] Use --exclude_status to filter out from default status list. By default --cve-trackers is True. Using --use-default-advisory without a value set for the matching key in the build-data will cause an error and elliott will exit in a non-zero state. Use of this option silently overrides providing an advisory with the --add option. Automatically add bugs with target-release matching 3.7.Z or 3.7.0 to advisory 123456: \b $ elliott --group openshift-3.7 find-bugs --mode sweep --add 123456 List bugs that WOULD be added to an advisory and have set the bro_ok flag on them (NOOP): \b $ elliott --group openshift-3.7 find-bugs --mode sweep --flag bro_ok Attach bugs to their correct default advisories, e.g. operator-related bugs go to "extras" instead of the default "image": \b $ elliott --group=openshift-4.4 find-bugs --mode=sweep --into-default-advisories Add two bugs to advisory 123456. Note that --group is not required because we're not auto searching: \b $ elliott find-bugs --mode list --id 8675309 --id 7001337 --add 123456 Automatically find bugs for openshift-4.1 and attach them to the rpm advisory defined in ocp-build-data: \b $ elliott --group=openshift-4.1 --mode sweep --use-default-advisory rpm Find bugs for 4.6 that are in MODIFIED state, and set them to ON_QA: \b $ elliott --group=openshift-4.6 --mode qe \b $ elliott --group=openshift-4.6 --mode blocker --report """ count_advisory_attach_flags = sum( map(bool, [advisory, default_advisory_type, into_default_advisories])) if mode != 'list' and len(id) > 0: raise click.BadParameter( "Combining the automatic and manual bug attachment options is not supported" ) if mode == 'list' and len(id) == 0: raise click.BadParameter( "When using mode=list, you must provide a list of bug IDs") if mode == 'list' and into_default_advisories: raise click.BadParameter( "Cannot use --into-default-advisories with mode=list") if mode == 'diff' and not len(from_diff) == 2: raise click.BadParameter( "If using mode=diff, you must provide two payloads to compare") if count_advisory_attach_flags > 1: raise click.BadParameter( "Use only one of --use-default-advisory, --add, or --into-default-advisories" ) if mode in ['qe', 'blocker'] and count_advisory_attach_flags > 0: raise click.BadParameter( "Mode does not operate on an advisory. Do not specify any of " "`--use-default-advisory`, `--add`, or `--into-default-advisories`" ) runtime.initialize() bz_data = runtime.gitdata.load_data(key='bugzilla').data bzapi = bzutil.get_bzapi(bz_data) # filter out bugs ART does not manage m = re.match( r"rhaos-(\d+).(\d+)", runtime.branch ) # extract OpenShift version from the branch name. there should be a better way... if not m: raise ElliottFatalError( f"Unable to determine OpenShift version from branch name {runtime.branch}." ) major_version = int(m[1]) minor_version = int(m[2]) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) if mode in ['sweep', 'qe', 'blocker']: if not cve_trackers: if mode == 'blocker': cve_trackers = True else: cve_trackers = False if not status: # use default status filter according to mode if mode == 'sweep': status = ['MODIFIED', 'ON_QA', 'VERIFIED'] if mode == 'qe': status = ['MODIFIED'] if mode == 'blocker': status = [ 'NEW', 'ASSIGNED', 'POST', 'MODIFIED', 'ON_DEV', 'ON_QA' ] if mode != 'qe' and exclude_status: status = set(status) - set(exclude_status) green_prefix( f"Searching for bugs with status {' '.join(status)} and target release(s):" ) click.echo(" {tr}".format(tr=", ".join(bz_data['target_release']))) search_flag = 'blocker+' if mode == 'blocker' else None bugs = bzutil.search_for_bugs( bz_data, status, flag=search_flag, filter_out_security_bugs=not (cve_trackers), verbose=runtime.debug) elif mode == 'list': bugs = [bzapi.getbug(i) for i in cli_opts.id_convert(id)] mode_list(advisory=advisory, bugs=bugs, flags=flag, report=report, noop=noop) return elif mode == 'diff': click.echo(runtime.working_dir) bug_id_strings = openshiftclient.get_bug_list(runtime.working_dir, from_diff[0], from_diff[1]) bugs = [bzapi.getbug(i) for i in bug_id_strings] filtered_bugs = filter_bugs(bugs, major_version, minor_version, runtime) green_prefix( f"Found {len(filtered_bugs)} bugs ({len(bugs) - len(filtered_bugs)} ignored): " ) bugs = filtered_bugs click.echo(", ".join(sorted(str(b.bug_id) for b in bugs))) if mode == 'qe': for bug in bugs: bzutil.set_state(bug, 'ON_QA', noop=noop) if len(flag) > 0: add_flags(bugs=bugs, flags=flag, noop=noop) if report: print_report(bugs) if advisory and not default_advisory_type: # `--add ADVISORY_NUMBER` should respect the user's wish and attach all available bugs to whatever advisory is specified. errata.add_bugs_with_retry(advisory, bugs, noop=noop) return # If --use-default-advisory or --into-default-advisories is given, we need to determine which bugs should be swept into which advisory. # Otherwise we don't need to sweep bugs at all. if not (into_default_advisories or default_advisory_type): return # key is impetus ("rpm", "image", "extras"), value is a set of bug IDs. impetus_bugs = {"rpm": set(), "image": set(), "extras": set()} # @lmeyer: simple and stupid would still be keeping the logic in python, # possibly with config flags for branched logic. # until that logic becomes too ugly to keep in python, i suppose.. if major_version < 4: # for 3.x, all bugs should go to the rpm advisory impetus_bugs["rpm"] = set(bugs) else: # for 4.x # sweep rpm cve trackers into "rpm" advisory rpm_bugs = dict() if mode == 'sweep' and cve_trackers: rpm_bugs = bzutil.get_valid_rpm_cves(bugs) green_prefix("RPM CVEs found: ") click.echo(sorted(b.id for b in rpm_bugs)) if rpm_bugs: # if --check-builds flag is set # only attach bugs that have corresponding brew builds attached to rpm advisory if check_builds: click.echo( "Validating bugs with builds attached to the rpm advisory" ) attached_builds = errata.get_advisory_nvrs( runtime.group_config.advisories["rpm"]) packages = attached_builds.keys() not_found = [] for bug, package_name in rpm_bugs.items(): if package_name not in packages: not_found.append((bug.id, package_name)) else: click.echo( f"Build found for #{bug.id}, {package_name}") impetus_bugs["rpm"].add(bug) if not_found: red_prefix("RPM CVE Warning: ") click.echo( "The following CVE (bug, package) were found but not attached, because no corresponding brew builds were found attached to the rpm advisory. First attach builds and then rerun to attach the bugs" ) click.echo(not_found) else: click.echo( "Skipping attaching RPM CVEs. Use --check-builds flag to validate with builds." ) # optional operators bugs should be swept to the "extras" advisory # a way to identify operator-related bugs is by its "Component" value. # temporarily hardcode here until we need to move it to ocp-build-data. extra_components = { "Logging", "Service Brokers", "Metering Operator", "Node Feature Discovery Operator" } # we will probably find more impetus_bugs["extras"] = { b for b in bugs if b.component in extra_components } # all other bugs should go into "image" advisory impetus_bugs["image"] = set( bugs) - impetus_bugs["extras"] - rpm_bugs.keys() if default_advisory_type and impetus_bugs.get(default_advisory_type): errata.add_bugs_with_retry(advisory, impetus_bugs[default_advisory_type], noop=noop) elif into_default_advisories: for impetus, bugs in impetus_bugs.items(): if bugs: green_prefix(f'{impetus} advisory: ') errata.add_bugs_with_retry( runtime.group_config.advisories[impetus], bugs, noop=noop)
def find_builds_cli(runtime, advisory, default_advisory_type, builds, kind, from_diff, as_json): '''Automatically or manually find or attach viable rpm or image builds to ADVISORY. Default behavior searches Brew for viable builds in the given group. Provide builds manually by giving one or more --build (-b) options. Manually provided builds are verified against the Errata Tool API. \b * Attach the builds to ADVISORY by giving --attach * Specify the build type using --kind KIND Example: Assuming --group=openshift-3.7, then a build is a VIABLE BUILD IFF it meets ALL of the following criteria: \b * HAS the tag in brew: rhaos-3.7-rhel7-candidate * DOES NOT have the tag in brew: rhaos-3.7-rhel7 * IS NOT attached to ANY existing RHBA, RHSA, or RHEA That is to say, a viable build is tagged as a "candidate", has NOT received the "shipped" tag yet, and is NOT attached to any PAST or PRESENT advisory. Here are some examples: SHOW the latest OSE 3.6 image builds that would be attached to a 3.6 advisory: $ elliott --group openshift-3.6 find-builds -k image ATTACH the latest OSE 3.6 rpm builds to advisory 123456: \b $ elliott --group openshift-3.6 find-builds -k rpm --attach 123456 VERIFY (no --attach) that the manually provided RPM NVR and build ID are viable builds: \b $ elliott --group openshift-3.6 find-builds -k rpm -b megafrobber-1.0.1-2.el7 -b 93170 ''' if from_diff and builds: raise ElliottFatalError('Use only one of --build or --from-diff.') if advisory and default_advisory_type: raise click.BadParameter( 'Use only one of --use-default-advisory or --attach') runtime.initialize() base_tag, product_version = _determine_errata_info(runtime) if default_advisory_type is not None: advisory = find_default_advisory(runtime, default_advisory_type) ensure_erratatool_auth( ) # before we waste time looking up builds we can't process # get the builds we want to add unshipped_builds = [] session = requests.Session() if builds: unshipped_builds = _fetch_builds_by_id(builds, product_version, session) elif from_diff: unshipped_builds = _fetch_builds_from_diff(from_diff[0], from_diff[1], product_version, session) else: if kind == 'image': unshipped_builds = _fetch_builds_by_kind_image( runtime, product_version, session) elif kind == 'rpm': unshipped_builds = _fetch_builds_by_kind_rpm( builds, base_tag, product_version, session) _json_dump(as_json, unshipped_builds, base_tag, kind) if not unshipped_builds: green_print('No builds needed to be attached.') return if advisory is not False: _attach_to_advisory(unshipped_builds, kind, advisory) else: click.echo('The following {n} builds '.format(n=len(unshipped_builds)), nl=False) click.secho('may be attached ', bold=True, nl=False) click.echo('to an advisory:') for b in sorted(unshipped_builds): click.echo(' ' + b.nvr)
def find_bugs_sweep(runtime: Runtime, advisory_id, default_advisory_type, check_builds, major_version, find_bugs_obj, report, output, brew_event, noop, count_advisory_attach_flags, bug_tracker): if output == 'text': statuses = sorted(find_bugs_obj.status) tr = bug_tracker.target_release() green_prefix(f"Searching {bug_tracker.type} for bugs with status {statuses} and target releases: {tr}\n") bugs = find_bugs_obj.search(bug_tracker_obj=bug_tracker, verbose=runtime.debug) sweep_cutoff_timestamp = get_sweep_cutoff_timestamp(runtime, cli_brew_event=brew_event) if sweep_cutoff_timestamp: utc_ts = datetime.utcfromtimestamp(sweep_cutoff_timestamp) green_print(f"Filtering bugs that have changed ({len(bugs)}) to one of the desired statuses before the " f"cutoff time {utc_ts}...") qualified_bugs = [] for chunk_of_bugs in chunk(bugs, constants.BUG_LOOKUP_CHUNK_SIZE): b = bug_tracker.filter_bugs_by_cutoff_event(chunk_of_bugs, find_bugs_obj.status, sweep_cutoff_timestamp) qualified_bugs.extend(b) click.echo(f"{len(qualified_bugs)} of {len(bugs)} bugs are qualified for the cutoff time " f"{utc_ts}...") bugs = qualified_bugs included_bug_ids, excluded_bug_ids = get_assembly_bug_ids(runtime) if included_bug_ids & excluded_bug_ids: raise ValueError("The following bugs are defined in both 'include' and 'exclude': " f"{included_bug_ids & excluded_bug_ids}") if included_bug_ids: yellow_print("The following bugs will be additionally included because they are " f"explicitly defined in the assembly config: {included_bug_ids}") included_bugs = bug_tracker.get_bugs(included_bug_ids) bugs.extend(included_bugs) if excluded_bug_ids: yellow_print("The following bugs will be excluded because they are explicitly " f"defined in the assembly config: {excluded_bug_ids}") bugs = [bug for bug in bugs if bug.id not in excluded_bug_ids] if output == 'text': green_prefix(f"Found {len(bugs)} bugs: ") click.echo(", ".join(sorted(str(b.id) for b in bugs))) if report: print_report(bugs, output) if count_advisory_attach_flags < 1: return # `--add ADVISORY_NUMBER` should respect the user's wish # and attach all available bugs to whatever advisory is specified. if advisory_id and not default_advisory_type: bug_tracker.attach_bugs(advisory_id, [b.id for b in bugs], noop=noop) return rpm_advisory_id = common.find_default_advisory(runtime, 'rpm') if check_builds else None bugs_by_type = categorize_bugs_by_type(bugs, rpm_advisory_id=rpm_advisory_id, major_version=major_version, check_builds=check_builds) advisory_types_to_attach = [default_advisory_type] if default_advisory_type else bugs_by_type.keys() for advisory_type in sorted(advisory_types_to_attach): bugs = bugs_by_type.get(advisory_type) green_prefix(f'{advisory_type} advisory: ') if bugs: adv_id = common.find_default_advisory(runtime, advisory_type) bug_tracker.attach_bugs(adv_id, [b.id for b in bugs], noop=noop) else: click.echo("0 bugs found")
def tag_builds_cli(runtime: Runtime, advisories: Tuple[int], default_advisory_type: str, product_version: str, builds: Tuple[str], tag: str, dont_untag: bool, dry_run: bool): """ Tag builds into Brew tag and optionally untag unspecified builds. Example 1: Tag RHEL7 RPMs that on ocp-build-data recorded advisory into rhaos-4.3-rhel-7-image-build $ elliott --group=openshift-4.3 tag-builds --use-default-advisory rpm --product-version RHEL-7-OSE-4.3 --tag rhaos-4.3-rhel-7-image-build Example 2: Tag RHEL8 RPMs that are on advisory 55016 into rhaos-4.3-rhel-8-image-build $ elliott --group=openshift-4.3 tag-builds --advisory 55016 --product-version OSE-4.4-RHEL-8 --tag rhaos-4.3-rhel-8-image-build Example 3: Tag specified builds into rhaos-4.3-rhel-8-image-build $ elliott --group=openshift-4.3 tag-builds --build buildah-1.11.6-6.rhaos4.3.el8 --build openshift-4.3.23-202005230952.g1.b596217.el8 --tag rhaos-4.3-rhel-8-image-build """ if advisories and builds: raise click.BadParameter('Use only one of --build or --advisory/-a.') if advisories and default_advisory_type: raise click.BadParameter( 'Use only one of --use-default-advisory or --advisory/-a.') if default_advisory_type and builds: raise click.BadParameter( 'Use only one of --build or --use-default-advisory.') if product_version and not advisories and not default_advisory_type: raise click.BadParameter( '--product-version should only be used with --use-default-advisory or --advisory/-a.' ) runtime.initialize() logger = runtime.logger if default_advisory_type: advisories = (find_default_advisory(runtime, default_advisory_type), ) all_builds = set() # All Brew builds that should be in the tag if advisories: errata_session = requests.session() for advisory in advisories: logger.info( f"Fetching attached Brew builds from advisory {advisory}...") errata_builds = errata.get_builds(advisory, errata_session) product_versions = list(errata_builds.keys()) logger.debug( f"Advisory {advisory} has builds for {len(product_versions)} product versions: {product_versions}" ) if product_version: # Only this product version should be concerned product_versions = [product_version] for pv in product_versions: logger.debug(f"Extract Errata builds for product version {pv}") nvrs = _extract_nvrs_from_errata_build_list(errata_builds, pv) logger.info( f"Found {len(nvrs)} builds from advisory {advisory} with product version {pv}" ) logger.debug( f"The following builds are found for product version {pv}:\n\t{list(nvrs)}" ) all_builds |= set(nvrs) brew_session = koji.ClientSession(runtime.group_config.urls.brewhub or constants.BREW_HUB) if builds: # NVRs are directly specified with --build build_objs = brew.get_build_objects(list(builds), brew_session) all_builds = {build["nvr"] for build in build_objs} click.echo( f"The following {len(all_builds)} build(s) should be in tag {tag}:") for nvr in all_builds: green_print(f"\t{nvr}") # get NVRs that have been tagged tagged_build_objs = brew_session.listTagged(tag, latest=False, inherit=False) tagged_builds = {build["nvr"] for build in tagged_build_objs} # get NVRs that should be tagged missing_builds = all_builds - tagged_builds click.echo(f"{len(missing_builds)} build(s) need to be tagged into {tag}:") for nvr in missing_builds: green_print(f"\t{nvr}") # get NVRs that should be untagged extra_builds = tagged_builds - all_builds click.echo(f"{len(extra_builds)} build(s) need to be untagged from {tag}:") for nvr in extra_builds: green_print(f"\t{nvr}") if dry_run: yellow_print("Dry run: Do nothing.") return brew_session.gssapi_login() if not dont_untag: # untag extra builds extra_builds = list(extra_builds) logger.info(f"Untagging {len(extra_builds)} build(s) from {tag}...") multicall_tasks = brew.untag_builds(tag, extra_builds, brew_session) failed_to_untag = [] for index, task in enumerate(multicall_tasks): try: task.result click.echo(f"{nvr} has been successfully untagged from {tag}") except Exception as ex: nvr = extra_builds[index] failed_to_untag.append(nvr) logger.error(f"Failed to untag {nvr}: {ex}") # tag missing builds missing_builds = list(missing_builds) task_id_nvr_map = {} logger.info(f"Tagging {len(missing_builds)} build(s) into {tag}...") multicall_tasks = brew.tag_builds(tag, missing_builds, brew_session) failed_to_tag = [] for index, task in enumerate(multicall_tasks): nvr = missing_builds[index] try: task_id = task.result task_id_nvr_map[task_id] = nvr except Exception as ex: failed_to_tag.append(nvr) logger.error(f"Failed to tag {nvr}: {ex}") if task_id_nvr_map: # wait for tag task to finish logger.info("Waiting for tag tasks to finish") brew.wait_tasks(task_id_nvr_map.keys(), brew_session, logger=logger) # get tagging results stopped_tasks = list(task_id_nvr_map.keys()) with brew_session.multicall(strict=False) as m: multicall_tasks = [] for task_id in stopped_tasks: multicall_tasks.append( m.getTaskResult(task_id, raise_fault=False)) for index, t in enumerate(multicall_tasks): task_id = stopped_tasks[index] nvr = task_id_nvr_map[task_id] tag_res = t.result logger.debug( f"Tagging task {task_id} {nvr} returned result {tag_res}") click.echo(f"{nvr} has been successfully tagged into {tag}") if tag_res and 'faultCode' in tag_res: if "already tagged" not in tag_res["faultString"]: failed_to_tag.append(nvr) logger.error( f'Failed to tag {nvr} into {tag}: {tag_res["faultString"]}' ) if failed_to_untag: red_print("The following builds were failed to untag:") for nvr in failed_to_untag: red_print(f"\t{nvr}") elif not dont_untag: green_print( f"All unspecified builds have been successfully untagged from {tag}." ) if failed_to_tag: red_print("The following builds were failed to tag:") for nvr in failed_to_tag: red_print(f"\t{nvr}") else: green_print(f"All builds have been successfully tagged into {tag}.") if failed_to_untag or failed_to_tag: raise exceptions.ElliottFatalError( "Not all builds were successfully tagged/untagged.")