def comment(update, text, karma, user, password, url, openid_api, **kwargs): # User Docs that show in the --help """ Comment on an update. UPDATE: The alias of the update (e.g. FEDORA-2017-f8e0ef2850) TEXT: the comment to be added to the update """ # Developer Docs """ Comment on an update. Args: update (unicode): The update you wish to modify. text (unicode): The text of the comment you wish to leave on the update. karma (int): The karma you wish to leave on the update. Must be +1, 0, or -1. user (unicode): The username to authenticate as. password (unicode): The user's password. staging (bool): Whether to use the staging server or not. url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. openid_api (str): The URL for an OpenID API to use to authenticate to Bodhi. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging'], openid_api=openid_api) resp = client.comment(update, text, karma) print_resp(resp, client)
def edit(username, password, url, **kwargs): """Edit an existing release.""" client = bindings.BodhiClient(base_url=url, username=username, password=password) csrf = client.csrf() edited = kwargs.pop('name') if edited is None: print("ERROR: Please specify the name of the release to edit") return res = client.send_request('releases/%s' % edited, verb='GET', auth=True) data = munch.unmunchify(res) if 'errors' in data: print_errors(data) data['edited'] = edited data['csrf_token'] = csrf new_name = kwargs.pop('new_name') if new_name is not None: data['name'] = new_name for k, v in kwargs.items(): if v is not None: data[k] = v save(client, **data)
def edit_release(user, password, url, debug, composed_by_bodhi, openid_api, **kwargs): """Edit an existing release.""" client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging'], openid_api=openid_api) csrf = client.csrf() edited = kwargs.pop('name') if edited is None: click.echo("ERROR: Please specify the name of the release to edit") return res = client.send_request(f'releases/{edited}', verb='GET', auth=True) data = munch.unmunchify(res) if 'errors' in data: print_errors(data) data['edited'] = edited data['csrf_token'] = csrf data['composed_by_bodhi'] = composed_by_bodhi new_name = kwargs.pop('new_name') if new_name is not None: data['name'] = new_name for k, v in kwargs.items(): if v is not None: data[k] = v save(client, **data)
def request(update, state, user, password, url, openid_api, **kwargs): # User Docs that show in the --help """ Change an update's request status. UPDATE: The alias of the update (e.g. FEDORA-2017-f8e0ef2850) STATE: The state you wish to change the update's request to. Valid options are testing, stable, obsolete, unpush, and revoke. """ # Developer Docs """ Change an update's request to the given state. Args: update (str): The update you wish to modify. state (str): The state you wish to change the update's request to. Valid options are testing, stable, obsolete, unpush, and revoke. user (str): The username to authenticate as. password (str): The user's password. staging (bool): Whether to use the staging server or not. url (str): The URL of a Bodhi server to create the update on. Ignored if staging is True. openid_api (str): The URL for an OpenID API to use to authenticate to Bodhi. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging'], openid_api=openid_api) try: resp = client.request(update, state) except bindings.UpdateNotFound as exc: raise click.BadParameter(str(exc), param_hint='UPDATE') print_resp(resp, client)
def query_buildroot_overrides(url, user=None, mine=False, packages=None, expired=None, releases=None, builds=None, rows=None, page=None, **kwargs): # Docs that show in the --help """Query the buildroot overrides.""" # Developer Docs """ Query the buildroot overrides. Args: user (str): If supplied, overrides for this user will be queried. staging (bool): Whether to use the staging server or not. mine (bool): Whether to use the --mine flag was given. url (str): The URL of a Bodhi server to create the update on. Ignored if staging is True. packages (str): If supplied, the overrides for these package are queried expired (bool): If supplied, True returns only expired overrides, False only active. releases (str): If supplied, the overrides for these releases are queried. builds (str): If supplied, the overrides for these builds are queried. rows (str): The limit of rows displayed per page for query result. page (str): If supplied, returns the results for a specific page number. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) if mine: client.init_username() user = client.username resp = client.list_overrides(user=user, packages=packages, expired=expired, releases=releases, builds=builds, rows_per_page=rows, page=page) print_resp(resp, client)
def query(url, debug, mine=False, rows=None, **kwargs): # User Docs that show in the --help """Query updates on Bodhi. A leading '*' means that this is a 'security' update. The number between brackets next to the date indicates the number of days the update is in the current state. """ # Developer Docs """ Query updates based on flags. Args: url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. mine (Boolean): If the --mine flag was set debug (Boolean): If the --debug flag was set kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) if mine: client.init_username() kwargs['user'] = client.username resp = client.query(rows_per_page=rows, **kwargs) print_resp(resp, client)
def _save_override(url, user, password, staging, edit=False, openid_api=None, **kwargs): """ Create or edit a buildroot override. Args: url (str): The URL of a Bodhi server to create the update on. Ignored if staging is True. user (str): The username to authenticate as. password (str): The user's password. staging (bool): Whether to use the staging server or not. edit (bool): Set to True to edit an existing buildroot override. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=staging, openid_api=openid_api) resp = client.save_override(nvr=kwargs['nvr'], duration=kwargs['duration'], notes=kwargs['notes'], edit=edit, expired=kwargs.get('expire', False)) if kwargs['wait']: print_resp(resp, client, override_hint=False) command = _generate_wait_repo_command(resp, client) if command: click.echo(f"\n\nRunning {' '.join(command)}\n") ret = subprocess.call(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) if ret: click.echo(f"WARNING: ensuring active override failed for {resp.build.nvr}") sys.exit(ret) else: print_resp(resp, client, override_hint=True)
def edit(user, password, url, debug, openid_api, **kwargs): # User Docs that show in the --help """ Edit an existing update. UPDATE: The alias of the update (e.g. FEDORA-2017-f8e0ef2850) """ # Developer Docs """ The update argument must be an update id. Args: user (str): The username to authenticate as. password (str): The user's password. url (str): The URL of a Bodhi server to create the update on. Ignored if staging is True. debug (bool): If the --debug flag was set openid_api (str): A URL for an OpenID API to use to authenticate to Bodhi. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging'], openid_api=openid_api) kwargs['notes'] = _get_notes(**kwargs) try: query_param = {'updateid': kwargs['update']} resp = client.query(**query_param) del(kwargs['update']) # Convert list of 'Bug' instances in DB to comma separated bug_ids for parsing. former_update = resp['updates'][0].copy() if not kwargs['bugs']: kwargs['bugs'] = ",".join([str(bug['bug_id']) for bug in former_update['bugs']]) former_update.pop('bugs', None) kwargs['builds'] = [b['nvr'] for b in former_update['builds']] kwargs['edited'] = former_update['alias'] if kwargs['addbuilds']: for build in kwargs['addbuilds'].split(','): if build not in kwargs['builds']: kwargs['builds'].append(build) if kwargs['removebuilds']: for build in kwargs['removebuilds'].split(','): kwargs['builds'].remove(build) del kwargs['addbuilds'] del kwargs['removebuilds'] # Replace empty fields with former values from database. for field in kwargs: if kwargs[field] in (None, '') and field in former_update: kwargs[field] = former_update[field] require_severity_for_security_update(type=kwargs['type'], severity=kwargs['severity']) resp = client.save(**kwargs) print_resp(resp, client) except bindings.BodhiClientException as e: click.echo(str(e))
def waive(update, show, test, comment, url, openid_api, **kwargs): # User Docs that show in the --help """ Show or waive unsatified requirements (ie: missing or failing tests) on an existing update. UPDATE: The alias of the update (e.g. FEDORA-2017-f8e0ef2850) COMMENT: A comment explaining why the requirements were waived (mandatory with --test) """ # Developer Docs """ The update argument must be an update id.. Args: update (str): The update who unsatisfied requirements wish to waive. show (boolean): Whether to show all missing required tests of the specified update. test (tuple(str)): Waive those specified tests or all of them if 'all' is specified. comment (str): A comment explaining the waiver. url (str): The URL of a Bodhi server to create the update on. Ignored if staging is True. openid_api (str): The URL for an OpenID API to use to authenticate to Bodhi. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, staging=kwargs['staging'], openid_api=openid_api) if show and test: click.echo( 'ERROR: You can not list the unsatisfied requirements and waive them ' 'at the same time, please use either --show or --test=... but not both.') sys.exit(1) if show: test_status = client.get_test_status(update) if 'errors' in test_status: click.echo('One or more error occured while retrieving the unsatisfied requirements:') for el in test_status.errors: click.echo(f' - {el.description}') elif 'decision' not in test_status: click.echo('Could not retrieve the unsatisfied requirements from bodhi.') else: click.echo(f'CI status: {test_status.decision.summary}') if test_status.decision.unsatisfied_requirements: click.echo('Missing tests:') for req in test_status.decision.unsatisfied_requirements: click.echo(f' - {req.testcase}') else: click.echo('Missing tests: None') else: if not comment: click.echo('ERROR: Comment are mandatory when waiving unsatisfied requirements') sys.exit(1) if 'all' in test: click.echo('Waiving all unsatisfied requirements') resp = client.waive(update, comment) else: click.echo(f"Waiving unsatisfied requirements: {', '.join(test)}") resp = client.waive(update, comment, test) print_resp(resp, client)
def create_release(user, password, url, debug, composed_by_bodhi, openid_api, **kwargs): """Create a release.""" client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging'], openid_api=openid_api) kwargs['csrf_token'] = client.csrf() kwargs['composed_by_bodhi'] = composed_by_bodhi save(client, **kwargs)
def create(username, password, url, **kwargs): """Create a release.""" client = bindings.BodhiClient(base_url=url, username=username, password=password) kwargs['csrf_token'] = client.csrf() save(client, **kwargs)
def download(url, **kwargs): # User Docs that show in the --help """Download the builds in one or more updates.""" # Developer Docs """ Download the builds for an update. Args: staging (bool): Whether to use the staging server or not. arch (unicode): Requested architecture of packages to download. "all" will retrieve packages from all architectures. url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) requested_arch = kwargs['arch'] del(kwargs['staging']) del(kwargs['arch']) # At this point we need to have reduced the kwargs dict to only our # query options (cves, updateid, builds) if not any(kwargs.values()): click.echo("ERROR: must specify at least one of --cves, --updateid, --builds") sys.exit(1) # As the query method doesn't let us construct OR queries, we're # gonna run one query for each option that was passed. The syntax # for this is a bit ugly, sorry. for (attr, value) in kwargs.items(): if value: expecteds = len(value.split(',')) resp = client.query(**{attr: value}) if len(resp.updates) == 0: click.echo("WARNING: No {0} found!".format(attr)) elif len(resp.updates) < expecteds: click.echo("WARNING: Some {0} not found!".format(attr)) # Not sure if we need a check for > expecteds, I don't # *think* that should ever be possible for these opts. for update in resp.updates: click.echo("Downloading packages from {0}".format(update['title'])) for build in update['builds']: # subprocess is icky, but koji module doesn't # expose this in any usable way, and we don't want # to rewrite it here. if requested_arch is None: args = ('koji', 'download-build', '--arch=noarch', '--arch={0}'.format(platform.machine()), build['nvr']) else: if u'all' in requested_arch: args = ('koji', 'download-build', build['nvr']) if u'all' not in requested_arch: args = ('koji', 'download-build', '--arch=noarch', '--arch={0}'.format(requested_arch), build['nvr']) ret = subprocess.call(args) if ret: click.echo("WARNING: download of {0} failed!".format(build['nvr']))
def info_compose(release, request, url, **kwargs): """Retrieve and print info about a compose.""" client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) try: resp = client.get_compose(release, request) except bindings.ComposeNotFound as exc: raise click.BadParameter(str(exc), param_hint='RELEASE/REQUEST') print_resp(resp, client)
def list_releases(display_archived, url, rows=None, page=None, **kwargs): """Retrieve and print list of releases.""" exclude_archived = True if display_archived: exclude_archived = False client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) res = client.get_releases(rows_per_page=rows, page=page, exclude_archived=exclude_archived) print_releases_list(res['releases'])
def info_release(name, url, **kwargs): """Retrieve and print info about a named release.""" client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) res = client.send_request('releases/%s' % name, verb='GET', auth=False) if 'errors' in res: print_errors(res) else: click.echo('Release:') print_release(res)
def edit(user, password, url, **kwargs): # User Docs that show in the --help """ Edit an existing update. UPDATE: The title of the update (e.g. FEDORA-2017-f8e0ef2850) """ # Developer Docs """ The update argument can be an update id or the update title. Args: user (unicode): The username to authenticate as. password (unicode): The user's password. url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging']) kwargs['notes'] = _get_notes(**kwargs) try: if re.search(bindings.UPDATE_ID_RE, kwargs['update']): query_param = {'updateid': kwargs['update']} resp = client.query(**query_param) title = resp['updates'][0]['title'] elif re.search(bindings.UPDATE_TITLE_RE, kwargs['update']): query_param = {'like': kwargs['update']} resp = client.query(**query_param) title = kwargs['update'] del (kwargs['update']) kwargs['builds'] = title kwargs['edited'] = title # Convert list of 'Bug' instances in DB to comma separated bug_ids for parsing. former_update = resp['updates'][0] if not kwargs['bugs']: kwargs['bugs'] = ",".join( [str(bug['bug_id']) for bug in former_update['bugs']]) # Replace empty fields with former values from database. for field in kwargs: if kwargs[field] in (None, '') and field in former_update: kwargs[field] = former_update[field] resp = client.save(**kwargs) print_resp(resp, client) except bindings.BodhiClientException as e: click.echo(str(e))
def info(name, url): """Retrieve and print info about a named release.""" client = bindings.BodhiClient(base_url=url) res = client.send_request('releases/%s' % name, verb='GET', auth=False) if 'errors' in res: print_errors(res) else: print('Release:') print_release(res)
def new(user, password, url, debug, openid_api, **kwargs): # User Docs that show in the --help """ Create a new update. BUILDS: a comma separated list of Builds to be added to the update (e.g. 0ad-0.0.21-4.fc26,2ping-3.2.1-4.fc26) """ # Developer Docs """ Args: user (unicode): The username to authenticate as. password (unicode): The user's password. url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. debug (bool): If the --debug flag was set openid_api (str): A URL for an OpenID API to use to authenticate to Bodhi. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging'], openid_api=openid_api) if kwargs['file'] is None: updates = [kwargs] else: updates = client.parse_file(os.path.abspath(kwargs['file'])) kwargs['notes'] = _get_notes(**kwargs) if not kwargs['notes'] and not kwargs['file']: click.echo( "ERROR: must specify at least one of --file, --notes, or --notes-file" ) sys.exit(1) for update in updates: require_severity_for_security_update(type=update['type'], severity=update['severity']) try: resp = client.save(**update) print_resp(resp, client) except bindings.BodhiClientException as e: click.echo(str(e)) except Exception: traceback.print_exc()
def list_composes(url, staging, verbose): # User docs for the CLI """ List composes. Asterisks next to composes indicate that they contain security updates. """ # developer docs """ Args: url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. staging (bool): Whether to use the staging server or not. verbose (bool): Whether to show verbose output or not. """ client = bindings.BodhiClient(base_url=url, staging=staging) print_resp(client.list_composes(), client, verbose)
def query(url, mine=False, **kwargs): # User Docs that show in the --help """Query updates on Bodhi.""" # Developer Docs """ Query updates based on flags. Args: url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. mine (Boolean): If the --mine flag was set kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) if mine: client.init_username() kwargs['user'] = client.username resp = client.query(**kwargs) print_resp(resp, client)
def _save_override(url, user, password, staging, edit=False, **kwargs): """ Create or edit a buildroot override. Args: url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. user (unicode): The username to authenticate as. password (unicode): The user's password. staging (bool): Whether to use the staging server or not. edit (bool): Set to True to edit an existing buildroot override. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=staging) resp = client.save_override(nvr=kwargs['nvr'], duration=kwargs['duration'], notes=kwargs['notes'], edit=edit, expired=kwargs.get('expire', False)) print_resp(resp, client)
def edit(user, password, url, **kwargs): # User Docs that show in the --help """ Edit an existing update. UPDATE: The title of the update (e.g. FEDORA-2017-f8e0ef2850) """ # Developer Docs """ The update argument can be an update id or the update title. Args: user (unicode): The username to authenticate as. password (unicode): The user's password. url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging']) kwargs['notes'] = _get_notes(**kwargs) try: if re.search(bindings.UPDATE_ID_RE, kwargs['update']): query_param = {'updateid': kwargs['update']} resp = client.query(**query_param) title = resp['updates'][0]['title'] elif re.search(bindings.UPDATE_TITLE_RE, kwargs['update']): title = kwargs['update'] del (kwargs['update']) kwargs['builds'] = title kwargs['edited'] = title resp = client.save(**kwargs) print_resp(resp, client) except bindings.BodhiClientException as e: click.echo(str(e))
def new(user, password, url, **kwargs): # User Docs that show in the --help """ Create a new update. BUILDS: a comma separated list of Builds to be added to the update (e.g. 0ad-0.0.21-4.fc26, 2ping-3.2.1-4.fc26) """ # Developer Docs """ Args: user (unicode): The username to authenticate as. password (unicode): The user's password. url (unicode): The URL of a Bodhi server to create the update on. Ignored if staging is True. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, username=user, password=password, staging=kwargs['staging']) if kwargs['file'] is None: updates = [kwargs] else: updates = client.parse_file(os.path.abspath(kwargs['file'])) kwargs['notes'] = _get_notes(**kwargs) for update in updates: try: resp = client.save(**update) print_resp(resp, client) except bindings.BodhiClientException as e: click.echo(str(e)) except Exception as e: traceback.print_exc()
def download(url, **kwargs): # User Docs that show in the --help """Download the builds in one or more updates.""" # Developer Docs """ Download the builds for an update. Args: staging (bool): Whether to use the staging server or not. arch (str): Requested architecture of packages to download. "all" will retrieve packages from all architectures. debuginfo (bool): Whether to include debuginfo packages. url (str): The URL of a Bodhi server to create the update on. Ignored if staging is True. kwargs (dict): Other keyword arguments passed to us by click. """ client = bindings.BodhiClient(base_url=url, staging=kwargs['staging']) requested_arch = kwargs['arch'] debuginfo = kwargs['debuginfo'] del(kwargs['staging']) del(kwargs['arch']) del(kwargs['debuginfo']) # At this point we need to have reduced the kwargs dict to only our # query options (updateid or builds) if not any(kwargs.values()): click.echo("ERROR: must specify at least one of --updateid or --builds") sys.exit(1) # As the query method doesn't let us construct OR queries, we're # gonna run one query for each option that was passed. The syntax # for this is a bit ugly, sorry. for (attr, value) in kwargs.items(): if value: expecteds = len(value.split(',')) resp = client.query(**{attr: value}) if len(resp.updates) == 0: click.echo(f"WARNING: No {attr} found!") else: if attr == 'updateid': resp_no = len(resp.updates) else: # attr == 'builds', add up number of builds for each returned update resp_no = functools.reduce( lambda x, y: x + len(y.get('builds', [])), resp.updates, 0 ) if resp_no < expecteds: click.echo(f"WARNING: Some {attr} not found!") # Not sure if we need a check for > expecteds, I don't # *think* that should ever be possible for these opts. for update in resp.updates: click.echo(f"Downloading packages from {update['alias']}") for build in update['builds']: args = ['koji', 'download-build'] if debuginfo: args.append('--debuginfo') # subprocess is icky, but koji module doesn't # expose this in any usable way, and we don't want # to rewrite it here. if requested_arch is None: args.extend(['--arch=noarch', f'--arch={platform.machine()}', build['nvr']]) else: if 'all' in requested_arch: args.append(build['nvr']) if 'all' not in requested_arch: args.extend(['--arch=noarch', f'--arch={requested_arch}', build['nvr']]) ret = subprocess.call(args) if ret: click.echo(f"WARNING: download of {build['nvr']} failed!")