Exemplo n.º 1
0
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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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)
Exemplo n.º 4
0
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)
Exemplo n.º 5
0
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)
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
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)
Exemplo n.º 8
0
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))
Exemplo n.º 9
0
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)
Exemplo n.º 10
0
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)
Exemplo n.º 11
0
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)
Exemplo n.º 12
0
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']))
Exemplo n.º 13
0
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)
Exemplo n.º 14
0
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'])
Exemplo n.º 15
0
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)
Exemplo n.º 16
0
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))
Exemplo n.º 17
0
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)
Exemplo n.º 18
0
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()
Exemplo n.º 19
0
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)
Exemplo n.º 20
0
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)
Exemplo n.º 21
0
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)
Exemplo n.º 22
0
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))
Exemplo n.º 23
0
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()
Exemplo n.º 24
0
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!")