debian-koji/cli/koji
Tomas Kopecek 15421a5637 put fix_pyver before printing command help
If pyver forces koji to reload under different interpreter, it could
happen, that different set of plugins is available. In such case help
printed under original interpreter can be misleading.

Fixes: https://pagure.io/koji/issue/1276
2019-06-14 16:15:08 -04:00

330 lines
12 KiB
Python
Executable file

#!/usr/bin/python2
# coding=utf-8
# command line interface for the Koji build system
# Copyright (c) 2005-2014 Red Hat, Inc.
#
# Koji is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this software; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#
# Authors:
# Dennis Gregorovic <dgregor@redhat.com>
# Mike McLean <mikem@redhat.com>
# Mike Bonnet <mikeb@redhat.com>
# Cristian Balint <cbalint@redhat.com>
from __future__ import absolute_import
from __future__ import division
import logging
import os
import re
import six
import sys
from optparse import OptionParser, SUPPRESS_HELP
import six.moves.configparser
import six.moves.xmlrpc_client
import koji
import koji.util
import koji.plugin
from koji_cli.lib import _, get_epilog_str, greetings, \
warn, categories
from koji_cli.commands import *
def register_plugin(plugin):
"""Scan a given plugin for handlers
Handlers are functions marked with one of the decorators defined in koji.plugin
"""
for v in six.itervalues(vars(plugin)):
if isinstance(v, six.class_types):
#skip classes
continue
if callable(v):
if getattr(v, 'exported_cli', False):
if hasattr(v, 'export_alias'):
name = getattr(v, 'export_alias')
else:
name = v.__name__
# copy object to local namespace
globals()[name] = v
def load_plugins(options, path):
"""Load plugins specified by our configuration plus system plugins. Order
is that system plugins are first, so they can be overridden by
user-specified ones with same name."""
logger = logging.getLogger('koji.plugins')
if os.path.exists(path):
tracker = koji.plugin.PluginTracker(path=path)
for name in sorted(os.listdir(path)):
if not name.endswith('.py'):
continue
name = name[:-3]
logger.info('Loading plugin: %s', name)
tracker.load(name)
register_plugin(tracker.get(name))
def get_options():
"""process options from command line and config file"""
common_commands = ['build', 'help', 'download-build',
'latest-build', 'search', 'list-targets']
usage = _("%%prog [global-options] command [command-options-and-arguments]"
"\n\nCommon commands: %s" % ', '.join(sorted(common_commands)))
parser = OptionParser(usage=usage)
parser.disable_interspersed_args()
progname = os.path.basename(sys.argv[0]) or 'koji'
parser.__dict__['origin_format_help'] = parser.format_help
parser.__dict__['format_help'] = lambda formatter=None: (
"%(origin_format_help)s%(epilog)s" % ({
'origin_format_help': parser.origin_format_help(formatter),
'epilog': get_epilog_str()}))
parser.add_option("-c", "--config", dest="configFile",
help=_("use alternate configuration file"), metavar="FILE")
parser.add_option("-p", "--profile", default=progname,
help=_("specify a configuration profile"))
parser.add_option("--keytab", help=_("specify a Kerberos keytab to use"), metavar="FILE")
parser.add_option("--principal", help=_("specify a Kerberos principal to use"))
parser.add_option("--krbservice", help=_("specify the Kerberos service name for the hub"))
parser.add_option("--runas", help=_("run as the specified user (requires special privileges)"))
parser.add_option("--user", help=_("specify user"))
parser.add_option("--password", help=_("specify password"))
parser.add_option("--noauth", action="store_true", default=False,
help=_("do not authenticate"))
parser.add_option("--force-auth", action="store_true", default=False,
help=_("authenticate even for read-only operations"))
parser.add_option("--authtype", help=_("force use of a type of authentication, options: noauth, ssl, password, or kerberos"))
parser.add_option("-d", "--debug", action="store_true",
help=_("show debug output"))
parser.add_option("--debug-xmlrpc", action="store_true",
help=_("show xmlrpc debug output"))
parser.add_option("-q", "--quiet", action="store_true", default=False,
help=_("run quietly"))
parser.add_option("--skip-main", action="store_true", default=False,
help=_("don't actually run main"))
parser.add_option("-s", "--server", help=_("url of XMLRPC server"))
parser.add_option("--topdir", help=_("specify topdir"))
parser.add_option("--weburl", help=_("url of the Koji web interface"))
parser.add_option("--topurl", help=_("url for Koji file access"))
parser.add_option("--pkgurl", help=SUPPRESS_HELP)
parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands"))
(options, args) = parser.parse_args()
# load local config
try:
result = koji.read_config(options.profile, user_config=options.configFile)
except koji.ConfigurationError as e:
parser.error(e.args[0])
assert False # pragma: no cover
# update options according to local config
for name, value in six.iteritems(result):
if getattr(options, name, None) is None:
setattr(options, name, value)
dir_opts = ('topdir', 'cert', 'serverca')
for name in dir_opts:
# expand paths here, so we don't have to worry about it later
value = os.path.expanduser(getattr(options, name))
setattr(options, name, value)
#honor topdir
if options.topdir:
koji.BASEDIR = options.topdir
koji.pathinfo.topdir = options.topdir
#pkgurl is obsolete
if options.pkgurl:
if options.topurl:
warn("Warning: the pkgurl option is obsolete")
else:
suggest = re.sub(r'/packages/?$', '', options.pkgurl)
if suggest != options.pkgurl:
warn("Warning: the pkgurl option is obsolete, using topurl=%r"
% suggest)
options.topurl = suggest
else:
warn("Warning: The pkgurl option is obsolete, please use topurl instead")
plugins_path = '%s/lib/python%s.%s/site-packages/koji_cli_plugins' % \
(sys.prefix, sys.version_info[0], sys.version_info[1])
load_plugins(options, plugins_path)
if not args:
options.help_commands = True
if options.help_commands:
# hijack args to [return_code, message]
return options, '_list_commands', [0, '']
aliases = {
'cancel-task' : 'cancel',
'cxl' : 'cancel',
'list-commands' : 'help',
'move-pkg': 'move-build',
'move': 'move-build',
'latest-pkg': 'latest-build',
'tag-pkg': 'tag-build',
'tag': 'tag-build',
'untag-pkg': 'untag-build',
'untag': 'untag-build',
'watch-tasks': 'watch-task',
}
cmd = args[0]
cmd = aliases.get(cmd, cmd)
if cmd.lower() in greetings:
cmd = "moshimoshi"
cmd = cmd.replace('-', '_')
if ('anon_handle_' + cmd) in globals():
if not options.force_auth and '--mine' not in args:
options.noauth = True
cmd = 'anon_handle_' + cmd
elif ('handle_' + cmd) in globals():
cmd = 'handle_' + cmd
else:
# hijack args to [return_code, message]
return options, '_list_commands', [1, 'Unknown command: %s' % args[0]]
return options, cmd, args[1:]
def handle_help(options, session, args):
"[info] List available commands"
usage = _("usage: %prog help <category> ...")
usage += _("\n(Specify the --help global option for a list of other help options)")
parser = OptionParser(usage=usage)
# the --admin opt is for backwards compatibility. It is equivalent to: koji help admin
parser.add_option("--admin", action="store_true", help=SUPPRESS_HELP)
(options, args) = parser.parse_args(args)
chosen = set(args)
if options.admin:
chosen.add('admin')
avail = set(list(categories.keys()) + ['all'])
unavail = chosen - avail
for arg in unavail:
print("No such help category: %s" % arg)
if not chosen:
list_commands()
else:
list_commands(chosen)
def fix_pyver(options, logger):
'''Attempt to run under the correct python version, if requested'''
pyver = getattr(options, 'pyver', None)
if not pyver:
return
if pyver not in [2,3]:
logger.warning('Invalid python version requested: %s', pyver)
if sys.version_info[0] == pyver:
return
py_exec = '/usr/bin/python%i' % pyver
if not os.path.exists(py_exec):
logger.error('No such file: %s', py_exec)
return
args = list(sys.argv)
args.insert(0, py_exec)
logger.debug('Executing via %s', py_exec)
logger.debug('args = %r', args)
try:
os.execvp(py_exec, args)
except Exception:
logger.exception('Unable to execute with requested python version')
def list_commands(categories_chosen=None):
if categories_chosen is None or "all" in categories_chosen:
categories_chosen = list(categories.keys())
else:
# copy list since we're about to modify it
categories_chosen = list(categories_chosen)
categories_chosen.sort()
handlers = []
for name,value in globals().items():
if name.startswith('handle_'):
alias = name.replace('handle_','')
alias = alias.replace('_','-')
handlers.append((alias,value))
elif name.startswith('anon_handle_'):
alias = name.replace('anon_handle_','')
alias = alias.replace('_','-')
handlers.append((alias,value))
handlers.sort()
print(_("Available commands:"))
for category in categories_chosen:
print(_("\n%s:" % categories[category]))
for alias,handler in handlers:
desc = handler.__doc__ or ''
if desc.startswith('[%s] ' % category):
desc = desc[len('[%s] ' % category):]
elif category != 'misc' or desc.startswith('['):
continue
print(" %-25s %s" % (alias, desc))
print("%s" % get_epilog_str().rstrip("\n"))
if __name__ == "__main__":
global options
options, command, args = get_options()
logger = logging.getLogger("koji")
handler = logging.StreamHandler(sys.stderr)
handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(name)s: %(message)s'))
handler.setLevel(logging.DEBUG)
logger.addHandler(handler)
if options.debug:
logger.setLevel(logging.DEBUG)
elif options.quiet:
logger.setLevel(logging.ERROR)
else:
logger.setLevel(logging.WARN)
fix_pyver(options, logger)
session_opts = koji.grab_session_options(options)
session = koji.ClientSession(options.server, session_opts)
if command == '_list_commands':
list_commands()
if args[0] != 0:
logger.error(args[1])
sys.exit(args[0])
# run handler
rv = 0
try:
rv = locals()[command].__call__(options, session, args)
if not rv:
rv = 0
except (KeyboardInterrupt, SystemExit):
rv = 1
except:
if options.debug:
raise
else:
exctype, value = sys.exc_info()[:2]
rv = 1
logger.error("%s: %s" % (exctype.__name__, value))
try:
session.logout()
except:
pass
sys.exit(rv)