def parse_arguments(options, args, flatpak):
    "Build a container"
    if flatpak:
        usage = _("usage: %prog flatpak-build [options] target <scm url>")
    else:
        usage = _("usage: %prog container-build [options] target <scm url or "
                  "archive path>")
    usage += _("\n(Specify the --help global option for a list of other help "
               "options)")
    parser = OptionParser(usage=usage)
    parser.add_option("--scratch",
                      action="store_true",
                      help=_("Perform a scratch build"))
    if not flatpak:
        parser.add_option("--isolated",
                          action="store_true",
                          help=_("Perform an isolated build"))
    parser.add_option("--arch-override",
                      help=_(
                          "Requires --scratch. Limit a scratch build to "
                          "the specified arches. Comma or space separated."))
    parser.add_option("--wait",
                      action="store_true",
                      help=_("Wait on the build, even if running in the "
                             "background"))
    parser.add_option("--nowait",
                      action="store_false",
                      dest="wait",
                      help=_("Don't wait on build"))
    parser.add_option("--quiet",
                      action="store_true",
                      help=_("Do not print the task information"),
                      default=options.quiet)
    parser.add_option("--background",
                      action="store_true",
                      help=_("Run the build at a lower priority"))
    parser.add_option("--epoch",
                      help=_("Specify container epoch. Requires koji admin "
                             "permission."))
    parser.add_option("--repo-url",
                      dest='yum_repourls',
                      metavar="REPO_URL",
                      action='append',
                      help=_("URL of yum repo file. May be used multiple "
                             "times. Cannot be used with --compose-id"))
    parser.add_option("--git-branch",
                      metavar="GIT_BRANCH",
                      help=_("Git branch"))
    parser.add_option("--channel-override",
                      help=_("Use a non-standard channel [default: %default]"),
                      default=DEFAULT_CHANNEL)
    parser.add_option(
        "--signing-intent",
        help=_("Signing intent of the ODCS composes [default: %default]."
               " Cannot be used with --compose-id"),
        default=None,
        dest='signing_intent')
    parser.add_option(
        "--compose-id",
        help=_("ODCS composes used. May be used multiple times. Cannot be"
               " used with --signing-intent or --repo-url"),
        dest='compose_ids',
        action='append',
        metavar="COMPOSE_ID",
        type="int")
    if not flatpak:
        parser.add_option("--release", help=_("Set release value"))
        parser.add_option(
            "--koji-parent-build",
            help=_("Overwrite parent image with image from koji build"))
    build_opts, args = parser.parse_args(args)
    if len(args) != 2:
        parser.error(
            _("Exactly two arguments (a build target and a SCM URL) "
              "are required"))
        assert False

    if build_opts.arch_override and not (build_opts.scratch
                                         or build_opts.isolated):
        parser.error(
            _("--arch-override is only allowed for --scratch or --isolated builds"
              ))

    if build_opts.signing_intent and build_opts.compose_ids:
        parser.error(_("--signing-intent cannot be used with --compose-id"))

    if build_opts.compose_ids and build_opts.yum_repourls:
        parser.error(_("--compose-id cannot be used with --repo-url"))

    opts = {}
    if not build_opts.git_branch:
        parser.error(_("git-branch must be specified"))

    keys = ('scratch', 'epoch', 'yum_repourls', 'git_branch', 'signing_intent',
            'compose_ids')

    if flatpak:
        opts['flatpak'] = True
    else:
        if build_opts.isolated and build_opts.scratch:
            parser.error(_("Build cannot be both isolated and scratch"))

        keys += ('release', 'isolated', 'koji_parent_build')

    if build_opts.arch_override:
        opts['arch_override'] = parse_arches(build_opts.arch_override)

    for key in keys:
        val = getattr(build_opts, key)
        if val is not None:
            opts[key] = val
    # create the parser in this function and return it to
    # simplify the unit test cases
    return build_opts, args, opts, parser
Пример #2
0
def handle_runroot(options, session, args):
    "[admin] Run a command in a buildroot"
    usage = _("usage: %prog runroot [options] <tag> <arch> <command>")
    usage += _(
        "\n(Specify the --help global option for a list of other help options)"
    )
    parser = OptionParser(usage=usage)
    parser.disable_interspersed_args()
    parser.add_option("-p",
                      "--package",
                      action="append",
                      default=[],
                      help=_("make sure this package is in the chroot"))
    parser.add_option("-m",
                      "--mount",
                      action="append",
                      default=[],
                      help=_("mount this directory read-write in the chroot"))
    parser.add_option("--skip-setarch",
                      action="store_true",
                      default=False,
                      help=_("bypass normal setarch in the chroot"))
    parser.add_option("-w", "--weight", type='int', help=_("set task weight"))
    parser.add_option("--channel-override",
                      help=_("use a non-standard channel"))
    parser.add_option("--task-id",
                      action="store_true",
                      default=False,
                      help=_("Print the ID of the runroot task"))
    parser.add_option(
        "--use-shell",
        action="store_true",
        default=False,
        help=_("Run command through a shell, otherwise uses exec"))
    parser.add_option(
        "--new-chroot",
        action="store_true",
        default=False,
        help=_(
            "Run command with the --new-chroot (systemd-nspawn) option to mock"
        ))
    parser.add_option("--repo-id", type="int", help=_("ID of the repo to use"))
    parser.add_option("--nowait",
                      action="store_false",
                      dest="wait",
                      default=True,
                      help=_("Do not wait on task"))
    parser.add_option("--watch",
                      action="store_true",
                      help=_("Watch task instead of printing runroot.log"))
    parser.add_option("--quiet",
                      action="store_true",
                      default=options.quiet,
                      help=_("Do not print the task information"))

    (opts, args) = parser.parse_args(args)

    if len(args) < 3:
        parser.error(_("Incorrect number of arguments"))
        assert False  # pragma: no cover

    activate_session(session, options)
    tag = args[0]
    arch = args[1]
    if opts.use_shell:
        # everything must be correctly quoted
        command = ' '.join(args[2:])
    else:
        command = args[2:]
    try:
        kwargs = {
            'channel': opts.channel_override,
            'packages': opts.package,
            'mounts': opts.mount,
            'repo_id': opts.repo_id,
            'skip_setarch': opts.skip_setarch,
            'weight': opts.weight
        }
        # Only pass this kwarg if it is true - this prevents confusing older
        # builders with a different function signature
        if opts.new_chroot:
            kwargs['new_chroot'] = True

        task_id = session.runroot(tag, arch, command, **kwargs)
    except koji.GenericError as e:
        if 'Invalid method' in str(e):
            print("* The runroot plugin appears to not be installed on the"
                  " koji hub.  Please contact the administrator.")
        raise
    if opts.task_id:
        print(task_id)

    if not opts.wait:
        return

    if opts.watch:
        session.logout()
        return watch_tasks(session, [task_id],
                           quiet=opts.quiet,
                           poll_interval=options.poll_interval)

    try:
        while True:
            # wait for the task to finish
            if session.taskFinished(task_id):
                break
            time.sleep(options.poll_interval)
    except KeyboardInterrupt:
        # this is probably the right thing to do here
        print("User interrupt: canceling runroot task")
        session.cancelTask(task_id)
        raise
    output = list_task_output_all_volumes(session, task_id)
    if 'runroot.log' in output:
        for volume in output['runroot.log']:
            log = session.downloadTaskOutput(task_id,
                                             'runroot.log',
                                             volume=volume)
            sys.stdout.write(log)
    info = session.getTaskInfo(task_id)
    if info is None:
        sys.exit(1)
    state = koji.TASK_STATES[info['state']]
    if state in ('FAILED', 'CANCELED'):
        sys.exit(1)
Пример #3
0
def parse_source_arguments(options, args):
    "Build a source container"
    usage = _("usage: %prog source-container-build [options] target")
    usage += _("\n(Specify the --help global option for a list of other help "
               "options)")
    parser = OptionParser(usage=usage)
    parser.add_option("--scratch",
                      action="store_true",
                      help=_("Perform a scratch build"))
    parser.add_option("--wait",
                      action="store_true",
                      help=_("Wait on the build, even if running in the "
                             "background"))
    parser.add_option("--nowait",
                      action="store_false",
                      dest="wait",
                      help=_("Don't wait on build"))
    parser.add_option("--quiet",
                      action="store_true",
                      help=_("Do not print the task information"),
                      default=options.quiet)
    parser.add_option("--background",
                      action="store_true",
                      help=_("Run the build at a lower priority"))
    parser.add_option("--channel-override",
                      help=_("Use a non-standard channel [default: %default]"),
                      default=DEFAULT_CHANNEL)
    parser.add_option(
        "--signing-intent",
        help=_("Signing intent of the ODCS composes [default: %default]."),
        default=None,
        dest='signing_intent')
    parser.add_option("--koji-build-id",
                      type="int",
                      help=_("Koji build id for sources, "
                             "is required or koji-build-nvr is provided"))
    parser.add_option("--koji-build-nvr",
                      help=_("Koji build nvr for sources, "
                             "is required or koji-build-id is provided"))

    build_opts, args = parser.parse_args(args)

    if len(args) != 1:
        parser.error(_("Exactly one argument (a build target) is required"))
        assert False

    if not (build_opts.koji_build_id or build_opts.koji_build_nvr):
        parser.error(
            _("at least one of --koji-build-id and --koji-build-nvr has to be specified"
              ))

    opts = {}
    keys = ('scratch', 'signing_intent', 'koji_build_id', 'koji_build_nvr')

    for key in keys:
        val = getattr(build_opts, key)
        if val is not None:
            opts[key] = val
    # create the parser in this function and return it to
    # simplify the unit test cases
    return build_opts, args, opts, parser
Пример #4
0
def parse_arguments(options, args, flatpak):
    "Build a container"
    if flatpak:
        usage = _("usage: %prog flatpak-build [options] target <scm url>")
    else:
        usage = _("usage: %prog container-build [options] target <scm url or "
                  "archive path>")
    usage += _("\n(Specify the --help global option for a list of other help "
               "options)")
    parser = OptionParser(usage=usage)
    parser.add_option("--scratch",
                      action="store_true",
                      help=_("Perform a scratch build"))
    if not flatpak:
        parser.add_option("--isolated",
                          action="store_true",
                          help=_("Perform an isolated build"))
    parser.add_option("--arch-override",
                      help=_(
                          "Requires --scratch or --isolated. Limit a build to "
                          "the specified arches. Comma or space separated."))
    parser.add_option("--wait",
                      action="store_true",
                      help=_("Wait on the build, even if running in the "
                             "background"))
    parser.add_option("--nowait",
                      action="store_false",
                      dest="wait",
                      help=_("Don't wait on build"))
    parser.add_option("--quiet",
                      action="store_true",
                      help=_("Do not print the task information"),
                      default=options.quiet)
    parser.add_option("--background",
                      action="store_true",
                      help=_("Run the build at a lower priority"))
    parser.add_option(
        "--replace-dependency",
        dest='dependency_replacements',
        metavar="pkg_manager:name:version[:new_name]",
        action='append',
        help=_("Cachito dependency replacement. May be used multiple times."))
    parser.add_option(
        "--repo-url",
        dest='yum_repourls',
        metavar="REPO_URL",
        action='append',
        help=_("URL of yum repo file. May be used multiple times."))
    parser.add_option("--git-branch",
                      metavar="GIT_BRANCH",
                      help=_("Git branch"))
    parser.add_option("--channel-override",
                      help=_("Use a non-standard channel [default: %default]"),
                      default=DEFAULT_CHANNEL)
    parser.add_option(
        "--signing-intent",
        help=_("Signing intent of the ODCS composes [default: %default]."
               " Cannot be used with --compose-id"),
        default=None,
        dest='signing_intent')
    parser.add_option(
        "--compose-id",
        help=_("ODCS composes used. May be used multiple times. Cannot be"
               " used with --signing-intent"),
        dest='compose_ids',
        action='append',
        metavar="COMPOSE_ID",
        type="int")
    parser.add_option("--skip-build",
                      action="store_true",
                      help=_("Skip build and update buildconfig. "
                             "Use this option to update autorebuild settings"))
    parser.add_option(
        "--userdata",
        help=_("JSON dictionary of user defined custom metadata"))
    parser.add_option(
        "--operator-csv-modifications-url",
        help=_("URL to JSON file with operator CSV modification"),
        action='store',
        default=None,
        dest='operator_csv_modifications_url',
        metavar='URL',
    )

    if not flatpak:
        parser.add_option("--release", help=_("Set release value"))
        parser.add_option(
            "--koji-parent-build",
            help=_("Overwrite parent image with image from koji build"))
    build_opts, args = parser.parse_args(args)
    if len(args) != 2:
        parser.error(
            _("Exactly two arguments (a build target and a SCM URL) "
              "are required"))
        assert False

    source = args[1]
    if '://' not in source:
        parser.error(
            _("scm URL does not look like an URL to a source repository"))
    if '#' not in source:
        parser.error(
            _("scm URL must be of the form <url_to_repository>#<revision>)"))

    if build_opts.arch_override and not (build_opts.scratch
                                         or build_opts.isolated):
        parser.error(
            _("--arch-override is only allowed for --scratch or --isolated builds"
              ))

    if build_opts.signing_intent and build_opts.compose_ids:
        parser.error(_("--signing-intent cannot be used with --compose-id"))

    if build_opts.operator_csv_modifications_url and not build_opts.isolated:
        parser.error(
            _("Only --isolated builds support option --operator-csv-modifications-url"
              ))

    if (build_opts.operator_csv_modifications_url
            and '://' not in build_opts.operator_csv_modifications_url):
        parser.error(
            _("Value provided to --operator-csv-modifications-url "
              "does not look like an URL"))

    opts = {}
    if not build_opts.git_branch:
        parser.error(_("git-branch must be specified"))

    keys = ('scratch', 'yum_repourls', 'git_branch', 'signing_intent',
            'compose_ids', 'skip_build', 'userdata', 'dependency_replacements',
            'operator_csv_modifications_url')

    if flatpak:
        opts['flatpak'] = True
    else:
        if build_opts.isolated and build_opts.scratch:
            parser.error(_("Build cannot be both isolated and scratch"))

        keys += ('release', 'isolated', 'koji_parent_build')

    if build_opts.arch_override:
        opts['arch_override'] = parse_arches(build_opts.arch_override)

    for key in keys:
        val = getattr(build_opts, key)
        if val is not None:
            opts[key] = val
            if key == 'userdata':
                opts[key] = json.loads(val)

    # create the parser in this function and return it to
    # simplify the unit test cases
    return build_opts, args, opts, parser
Пример #5
0
def handle_save_failed_tree(options, session, args):
    "Create tarball with whole buildtree"
    usage = _("usage: %prog save-failed-tree [options] ID")
    usage += _("\n(Specify the --help global option for a list of other help options)")
    parser = OptionParser(usage=usage)
    parser.add_option("-f", "--full", action="store_true", default=False,
            help=_("Download whole tree, if not specified, only builddir will be downloaded"))
    parser.add_option("-t", "--task", action="store_const", dest="mode",
            const="task", default="task",
            help=_("Treat ID as a task ID (the default)"))
    parser.add_option("-r", "--buildroot", action="store_const", dest="mode",
            const="buildroot",
            help=_("Treat ID as a buildroot ID"))
    parser.add_option("--quiet", action="store_true", default=options.quiet,
                      help=_("Do not print the task information"))
    parser.add_option("--nowait", action="store_true",
                      help=_("Don't wait on build"))

    (opts, args) = parser.parse_args(args)

    if len(args) != 1:
        parser.error(_("List exactly one task or buildroot ID"))

    try:
        id_val = int(args[0])
    except ValueError:
        parser.error(_("ID must be an integer"))

    activate_session(session, options)

    if opts.mode == "buildroot":
        br_id = id_val
    else:
        brs = [b['id'] for b in session.listBuildroots(taskID=id_val)]
        if not brs:
            print(_("No buildroots for task %s") % id_val)
            return 1
        br_id = max(brs)
        if len(brs) > 1:
            print(_("Multiple buildroots for task. Choosing last one (%s)") % br_id)

    try:
        task_id = session.saveFailedTree(br_id, opts.full)
    except koji.GenericError as e:
        m = str(e)
        if 'Invalid method' in m:
            print(_("* The save_failed_tree plugin appears to not be "
                    "installed on the koji hub.  Please contact the "
                    "administrator."))
            return 1
        raise

    if not opts.quiet:
        print(_("Created task %s for buildroot %s") % (task_id, br_id))
        print("Task info: %s/taskinfo?taskID=%s"
                % (options.weburl, task_id))

    if opts.nowait:
        return
    else:
        session.logout()
        return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval)
Пример #6
0
def handle_save_failed_tree(options, session, args):
    "Create tarball with whole buildtree"
    usage = _("usage: %prog save-failed-tree [options] ID")
    usage += _("\n(Specify the --help global option for a list of other help options)")
    parser = OptionParser(usage=usage)
    parser.add_option("-f", "--full", action="store_true", default=False,
            help=_("Download whole tree, if not specified, only builddir will be downloaded"))
    parser.add_option("-t", "--task", action="store_const", dest="mode",
            const="task", default="task",
            help=_("Treat ID as a task ID (the default)"))
    parser.add_option("-r", "--buildroot", action="store_const", dest="mode",
            const="buildroot",
            help=_("Treat ID as a buildroot ID"))
    parser.add_option("--quiet", action="store_true", default=options.quiet,
                      help=_("Do not print the task information"))
    parser.add_option("--nowait", action="store_true",
                      help=_("Don't wait on build"))

    (opts, args) = parser.parse_args(args)

    if len(args) != 1:
        parser.error(_("List exactly one task or buildroot ID"))

    try:
        id_val = int(args[0])
    except ValueError:
        parser.error(_("ID must be an integer"))

    activate_session(session, options)

    if opts.mode == "buildroot":
        br_id = id_val
    else:
        brs = [b['id'] for b in session.listBuildroots(taskID=id_val)]
        if not brs:
            print(_("No buildroots for task %s") % id_val)
            return 1
        br_id = max(brs)
        if len(brs) > 1:
            print(_("Multiple buildroots for task. Choosing last one (%s)") % br_id)

    try:
        task_id = session.saveFailedTree(br_id, opts.full)
    except koji.GenericError as e:
        m = str(e)
        if 'Invalid method' in m:
            print(_("* The save_failed_tree plugin appears to not be "
                    "installed on the koji hub.  Please contact the "
                    "administrator."))
            return 1
        raise

    if not opts.quiet:
        print(_("Created task %s for buildroot %s") % (task_id, br_id))
        print("Task info: %s/taskinfo?taskID=%s"
                % (options.weburl, task_id))

    if opts.nowait:
        return
    else:
        session.logout()
        return watch_tasks(session, [task_id], quiet=opts.quiet, poll_interval=options.poll_interval)
def parse_arguments(options, args, flatpak):
    "Build a container"
    if flatpak:
        usage = _("usage: %prog flatpak-build [options] target <scm url>")
    else:
        usage = _("usage: %prog container-build [options] target <scm url or "
                  "archive path>")
    usage += _("\n(Specify the --help global option for a list of other help "
               "options)")
    parser = OptionParser(usage=usage)
    parser.add_option("--scratch", action="store_true",
                      help=_("Perform a scratch build"))
    if not flatpak:
        parser.add_option("--isolated", action="store_true",
                          help=_("Perform an isolated build"))
    parser.add_option("--arch-override",
                      help=_("Requires --scratch. Limit a scratch build to "
                             "the specified arches. Comma or space separated."))
    parser.add_option("--wait", action="store_true",
                      help=_("Wait on the build, even if running in the "
                             "background"))
    parser.add_option("--nowait", action="store_false", dest="wait",
                      help=_("Don't wait on build"))
    parser.add_option("--quiet", action="store_true",
                      help=_("Do not print the task information"),
                      default=options.quiet)
    parser.add_option("--background", action="store_true",
                      help=_("Run the build at a lower priority"))
    parser.add_option("--epoch",
                      help=_("Specify container epoch. Requires koji admin "
                             "permission."))
    parser.add_option("--repo-url", dest='yum_repourls', metavar="REPO_URL",
                      action='append',
                      help=_("URL of yum repo file. May be used multiple times."))
    parser.add_option("--git-branch", metavar="GIT_BRANCH",
                      help=_("Git branch"))
    parser.add_option("--channel-override",
                      help=_("Use a non-standard channel [default: %default]"),
                      default=DEFAULT_CHANNEL)
    parser.add_option("--signing-intent",
                      help=_("Signing intent of the ODCS composes [default: %default]."
                             " Cannot be used with --compose-id"),
                      default=None, dest='signing_intent')
    parser.add_option("--compose-id",
                      help=_("ODCS composes used. May be used multiple times. Cannot be"
                             " used with --signing-intent"),
                      dest='compose_ids', action='append', metavar="COMPOSE_ID", type="int")
    if not flatpak:
        parser.add_option("--release",
                          help=_("Set release value"))
        parser.add_option("--koji-parent-build",
                          help=_("Overwrite parent image with image from koji build"))
    build_opts, args = parser.parse_args(args)
    if len(args) != 2:
        parser.error(_("Exactly two arguments (a build target and a SCM URL) "
                       "are required"))
        assert False

    if build_opts.arch_override and not (build_opts.scratch or build_opts.isolated):
        parser.error(_("--arch-override is only allowed for --scratch or --isolated builds"))

    if build_opts.signing_intent and build_opts.compose_ids:
        parser.error(_("--signing-intent cannot be used with --compose-id"))

    opts = {}
    if not build_opts.git_branch:
        parser.error(_("git-branch must be specified"))

    keys = ('scratch', 'epoch', 'yum_repourls', 'git_branch', 'signing_intent', 'compose_ids')

    if flatpak:
        opts['flatpak'] = True
    else:
        if build_opts.isolated and build_opts.scratch:
            parser.error(_("Build cannot be both isolated and scratch"))

        keys += ('release', 'isolated', 'koji_parent_build')

    if build_opts.arch_override:
        opts['arch_override'] = parse_arches(build_opts.arch_override)

    for key in keys:
        val = getattr(build_opts, key)
        if val is not None:
            opts[key] = val
    # create the parser in this function and return it to
    # simplify the unit test cases
    return build_opts, args, opts, parser
Пример #8
0
def parse_options(options, args):
    usage = _("usage: %prog replicate-tasks [options] [<task_id>...]")
    usage += _(
        "\nto replicate scratch tasks from existing tasks with specified IDs"
        " or by query"
        "\n(Specify the --help global option for a list of"
        " other help options)")
    parser = OptionParser(usage=usage)
    parser.disable_interspersed_args()
    parser.add_option(
        "-s",
        "--strategy",
        default=Strategy.reuse.name,
        help=_("specify the strategy to construct the buildroot for"
               " replicating the task, Options: %s,"
               " [Default: %%default]." %
               ", ".join(Strategy.__members__.keys())))
    parser.add_option(
        "-T",
        "--override-tag",
        help=_(
            "specify the tag in the inheritance to override the content / config"
            " of the origin build tag when strategy is clone"))
    parser.add_option("-C",
                      "--channel",
                      dest="channels",
                      action="append",
                      default=[],
                      help=_("specify channels where tasks are from"))
    parser.add_option("-H",
                      "--host",
                      dest="hosts",
                      action="append",
                      default=[],
                      help=_("specify hosts where tasks are replicated from"))
    parser.add_option(
        "-m",
        "--method",
        dest="methods",
        action="append",
        default=[],
        help=_(
            "specify methods that original tasks are. Only supports 'build' now"
        ))
    parser.add_option(
        "-S",
        "--state",
        dest="states",
        action="append",
        default=['CLOSED'],
        help=_(
            "specify states of tasks which are replicated, [Default: %default]"
        ))
    parser.add_option("-w", "--weight", type='int', help=_("set task weight"))
    parser.add_option("--channel-override",
                      help=_("use a non-standard channel to replicate tasks"))
    parser.add_option("--arch-override",
                      dest="arches",
                      action="append",
                      default=[],
                      help=_("to override arches to replicate tasks"))
    parser.add_option("--include-scratch",
                      action="store_true",
                      help=_("also replicate scratch tasks"))
    # parser.add_option("--limit-by", default='channel',
    #                   help=_("specify field used by --limit"))
    parser.add_option(
        "--limit",
        type='int',
        default=3,
        help=_(
            "limit per method and/or per channel/host, [Default: %default]"))
    parser.add_option("--offset",
                      type="int",
                      default=0,
                      help=_("offset of limit, [Default: %default]"))
    parser.add_option("--quiet",
                      action="store_true",
                      default=options.quiet,
                      help=_("Do not print the task information"))

    return (parser, ) + parser.parse_args(args)
Пример #9
0
def handle_runroot(options, session, args):
    "[admin] Run a command in a buildroot"
    usage = _("usage: %prog runroot [options] <tag> <arch> <command>")
    usage += _("\n(Specify the --help global option for a list of other help options)")
    parser = OptionParser(usage=usage)
    parser.disable_interspersed_args()
    parser.add_option("-p", "--package", action="append", default=[], help=_("make sure this package is in the chroot"))
    parser.add_option("-m", "--mount", action="append", default=[], help=_("mount this directory read-write in the chroot"))
    parser.add_option("--skip-setarch", action="store_true", default=False,
            help=_("bypass normal setarch in the chroot"))
    parser.add_option("-w", "--weight", type='int', help=_("set task weight"))
    parser.add_option("--channel-override", help=_("use a non-standard channel"))
    parser.add_option("--task-id", action="store_true", default=False,
            help=_("Print the ID of the runroot task"))
    parser.add_option("--use-shell", action="store_true", default=False,
            help=_("Run command through a shell, otherwise uses exec"))
    parser.add_option("--new-chroot", action="store_true", default=None,
            help=_("Run command with the --new-chroot (systemd-nspawn) option to mock"))
    parser.add_option("--old-chroot", action="store_false", default=None, dest='new_chroot',
            help=_("Run command with the --old-chroot (systemd-nspawn) option to mock"))
    parser.add_option("--repo-id", type="int", help=_("ID of the repo to use"))
    parser.add_option("--nowait", action="store_false", dest="wait",
            default=True, help=_("Do not wait on task"))
    parser.add_option("--watch", action="store_true", help=_("Watch task instead of printing runroot.log"))
    parser.add_option("--quiet", action="store_true", default=options.quiet,
                      help=_("Do not print the task information"))

    (opts, args) = parser.parse_args(args)

    if len(args) < 3:
        parser.error(_("Incorrect number of arguments"))
        assert False  # pragma: no cover

    activate_session(session, options)
    tag = args[0]
    arch = args[1]
    if opts.use_shell:
        # everything must be correctly quoted
        command = ' '.join(args[2:])
    else:
        command = args[2:]
    try:
        kwargs = { 'channel':       opts.channel_override,
                   'packages':      opts.package,
                   'mounts':        opts.mount,
                   'repo_id':       opts.repo_id,
                   'skip_setarch':  opts.skip_setarch,
                   'weight':        opts.weight }
        # Only pass this kwarg if it is true - this prevents confusing older
        # builders with a different function signature
        if opts.new_chroot is not None:
            kwargs['new_chroot'] = opts.new_chroot

        task_id = session.runroot(tag, arch, command, **kwargs)
    except koji.GenericError as e:
        if 'Invalid method' in str(e):
            print("* The runroot plugin appears to not be installed on the"
                  " koji hub.  Please contact the administrator.")
        raise
    if opts.task_id:
        print(task_id)

    if not opts.wait:
        return

    if opts.watch:
        session.logout()
        return watch_tasks(session, [task_id], quiet=opts.quiet,
                           poll_interval=options.poll_interval)

    try:
        while True:
            # wait for the task to finish
            if session.taskFinished(task_id):
                break
            time.sleep(options.poll_interval)
    except KeyboardInterrupt:
        # this is probably the right thing to do here
        print("User interrupt: canceling runroot task")
        session.cancelTask(task_id)
        raise
    sys.stdout.flush()
    if not opts.quiet:
        output = list_task_output_all_volumes(session, task_id)
        if 'runroot.log' in output:
            for volume in output['runroot.log']:
                log = session.downloadTaskOutput(task_id, 'runroot.log', volume=volume)
                # runroot output, while normally text, can be *anything*, so
                # treat it as binary
                bytes_to_stdout(log)
    info = session.getTaskInfo(task_id)
    if info is None:
        sys.exit(1)
    state = koji.TASK_STATES[info['state']]
    if state in ('FAILED', 'CANCELED'):
        sys.exit(1)