| |
@@ -26,143 +26,62 @@
|
| |
|
| |
from __future__ import absolute_import
|
| |
from __future__ import division
|
| |
- import sys
|
| |
- from six.moves import range
|
| |
- from six.moves import zip
|
| |
- import six
|
| |
- from six.moves import filter
|
| |
- from six.moves import map
|
| |
- try:
|
| |
- import krbV
|
| |
- except ImportError: # pragma: no cover
|
| |
- krbV = None
|
| |
- try:
|
| |
- import ast
|
| |
- except ImportError: # pragma: no cover
|
| |
- ast = None
|
| |
- try:
|
| |
- import json
|
| |
- except ImportError: # pragma: no cover
|
| |
- try:
|
| |
- import simplejson as json
|
| |
- except ImportError:
|
| |
- json = None
|
| |
- import six.moves.configparser
|
| |
- import base64
|
| |
- import dateutil.parser
|
| |
- import errno
|
| |
- import koji
|
| |
- import koji.util
|
| |
- import fnmatch
|
| |
- from koji.util import md5_constructor
|
| |
import logging
|
| |
+ import optparse
|
| |
import os
|
| |
import re
|
| |
- import pprint
|
| |
- import pycurl
|
| |
- import random
|
| |
- import socket
|
| |
- import stat
|
| |
- import string
|
| |
- import time
|
| |
- import traceback
|
| |
- import six.moves.xmlrpc_client
|
| |
- try:
|
| |
- import libcomps
|
| |
- except ImportError: # pragma: no cover
|
| |
- libcomps = None
|
| |
- try:
|
| |
- import yum.comps as yumcomps
|
| |
- except ImportError:
|
| |
- yumcomps = None
|
| |
- import optparse
|
| |
+ import six
|
| |
+ import sys
|
| |
+ import types
|
| |
|
| |
+ import six.moves.configparser
|
| |
+ import six.moves.xmlrpc_client
|
| |
|
| |
- # fix OptionParser for python 2.3 (optparse verion 1.4.1+)
|
| |
- # code taken from optparse version 1.5a2
|
| |
- OptionParser = optparse.OptionParser
|
| |
- if optparse.__version__ == "1.4.1+": # pragma: no cover
|
| |
- def _op_error(self, msg):
|
| |
- self.print_usage(sys.stderr)
|
| |
- msg = "%s: error: %s\n" % (self._get_prog_name(), msg)
|
| |
- if msg:
|
| |
- sys.stderr.write(msg)
|
| |
- sys.exit(2)
|
| |
- OptionParser.error = _op_error
|
| |
+ import koji
|
| |
+ import koji.util
|
| |
+ import koji.plugin
|
| |
|
| |
- greetings = ('hello', 'hi', 'yo', "what's up", "g'day", 'back to work',
|
| |
- 'bonjour',
|
| |
- 'hallo',
|
| |
- 'ciao',
|
| |
- 'hola',
|
| |
- u'olá',
|
| |
- u'dobrý den',
|
| |
- u'zdravstvuite',
|
| |
- u'góðan daginn',
|
| |
- 'hej',
|
| |
- 'tervehdys',
|
| |
- u'grüezi',
|
| |
- u'céad míle fáilte',
|
| |
- u'hylô',
|
| |
- u'bună ziua',
|
| |
- u'jó napot',
|
| |
- 'dobre dan',
|
| |
- u'你好',
|
| |
- u'こんにちは',
|
| |
- u'नमस्कार',
|
| |
- u'안녕하세요')
|
| |
+ from koji_cli.lib import _, OptionParser, get_epilog_str, greetings, \
|
| |
+ warn, categories
|
| |
+ from koji_cli.commands import *
|
| |
|
| |
- def _(args):
|
| |
- """Stub function for translation"""
|
| |
- return args
|
| |
|
| |
- def _printable_unicode(s):
|
| |
- if six.PY2:
|
| |
- return s.encode('utf-8')
|
| |
- else:
|
| |
- return s
|
| |
+ def register_plugin(plugin):
|
| |
+ """Scan a given plugin for handlers
|
| |
|
| |
- ARGMAP = {'None': None,
|
| |
- 'True': True,
|
| |
- 'False': False}
|
| |
+ Handlers are functions marked with one of the decorators defined in koji.plugin
|
| |
+ """
|
| |
+ for v in vars(plugin).itervalues():
|
| |
+ if isinstance(v, (types.ClassType, types.TypeType)):
|
| |
+ #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 arg_filter(arg):
|
| |
- try:
|
| |
- return int(arg)
|
| |
- except ValueError:
|
| |
- pass
|
| |
- try:
|
| |
- return float(arg)
|
| |
- except ValueError:
|
| |
- pass
|
| |
- if arg in ARGMAP:
|
| |
- return ARGMAP[arg]
|
| |
- #handle lists/dicts?
|
| |
- return arg
|
| |
|
| |
- categories = {
|
| |
- 'admin' : 'admin commands',
|
| |
- 'build' : 'build commands',
|
| |
- 'search' : 'search commands',
|
| |
- 'download' : 'download commands',
|
| |
- 'monitor' : 'monitor commands',
|
| |
- 'info' : 'info commands',
|
| |
- 'bind' : 'bind commands',
|
| |
- 'misc' : 'miscellaneous commands',
|
| |
- }
|
| |
+ def load_plugins(options):
|
| |
+ """Load plugins specified by our configuration plus system plugins. Order
|
| |
+ is that system plugins are first, so they can be overriden by
|
| |
+ user-specified ones with same name."""
|
| |
+ logger = logging.getLogger('koji.plugins')
|
| |
+ syspath = '%s/lib/python%s.%s/site-packages/koji_cli_plugins' % \
|
| |
+ (sys.prefix, sys.version_info.major, sys.version_info.minor)
|
| |
+ if os.path.exists(syspath):
|
| |
+ tracker = koji.plugin.PluginTracker(path=syspath)
|
| |
+ for name in sorted(os.listdir(syspath)):
|
| |
+ 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_epilog_str(progname=None):
|
| |
- if progname is None:
|
| |
- progname = os.path.basename(sys.argv[0]) or 'koji'
|
| |
- categories_ordered=', '.join(sorted(['all'] + list(categories.keys())))
|
| |
- epilog_str = '''
|
| |
- Try "%(progname)s --help" for help about global options
|
| |
- Try "%(progname)s help" to get all available commands
|
| |
- Try "%(progname)s <command> --help" for help about the options of a particular command
|
| |
- Try "%(progname)s help <category>" to get commands under a particular category
|
| |
- Available categories are: %(categories)s
|
| |
- ''' % ({'progname': progname, 'categories': categories_ordered})
|
| |
- return _(epilog_str)
|
| |
|
| |
def get_options():
|
| |
"""process options from command line and config file"""
|
| |
@@ -210,42 +129,6 @@
|
| |
parser.add_option("--help-commands", action="store_true", default=False, help=_("list commands"))
|
| |
(options, args) = parser.parse_args()
|
| |
|
| |
- if options.help_commands:
|
| |
- list_commands()
|
| |
- sys.exit(0)
|
| |
- if not args:
|
| |
- list_commands()
|
| |
- sys.exit(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:
|
| |
- list_commands()
|
| |
- parser.error('Unknown command: %s' % args[0])
|
| |
- assert False # pragma: no cover
|
| |
-
|
| |
# load local config
|
| |
try:
|
| |
result = koji.read_config(options.profile, user_config=options.configFile)
|
| |
@@ -282,7364 +165,102 @@
|
| |
else:
|
| |
warn("Warning: The pkgurl option is obsolete, please use topurl instead")
|
| |
|
| |
- return options, cmd, args[1:]
|
| |
-
|
| |
- def ensure_connection(session):
|
| |
- try:
|
| |
- ret = session.getAPIVersion()
|
| |
- except six.moves.xmlrpc_client.ProtocolError:
|
| |
- error(_("Error: Unable to connect to server"))
|
| |
- if ret != koji.API_VERSION:
|
| |
- warn(_("WARNING: The server is at API version %d and the client is at %d" % (ret, koji.API_VERSION)))
|
| |
-
|
| |
- def print_task_headers():
|
| |
- """Print the column headers"""
|
| |
- print("ID Pri Owner State Arch Name")
|
| |
-
|
| |
- def print_task(task,depth=0):
|
| |
- """Print a task"""
|
| |
- task = task.copy()
|
| |
- task['state'] = koji.TASK_STATES.get(task['state'],'BADSTATE')
|
| |
- fmt = "%(id)-8s %(priority)-4s %(owner_name)-20s %(state)-8s %(arch)-10s "
|
| |
- if depth:
|
| |
- indent = " "*(depth-1) + " +"
|
| |
- else:
|
| |
- indent = ''
|
| |
- label = koji.taskLabel(task)
|
| |
- print(''.join([fmt % task, indent, label]))
|
| |
-
|
| |
- def print_task_recurse(task,depth=0):
|
| |
- """Print a task and its children"""
|
| |
- print_task(task,depth)
|
| |
- for child in task.get('children',()):
|
| |
- print_task_recurse(child,depth+1)
|
| |
-
|
| |
- def parse_arches(arches, to_list=False):
|
| |
- """Parse comma or space-separated list of arches and return
|
| |
- only space-separated one."""
|
| |
- arches = arches.replace(',', ' ').split()
|
| |
- if to_list:
|
| |
- return arches
|
| |
- else:
|
| |
- return ' '.join(arches)
|
| |
-
|
| |
- class TaskWatcher(object):
|
| |
-
|
| |
- def __init__(self,task_id,session,level=0,quiet=False):
|
| |
- self.id = task_id
|
| |
- self.session = session
|
| |
- self.info = None
|
| |
- self.level = level
|
| |
- self.quiet = quiet
|
| |
-
|
| |
- #XXX - a bunch of this stuff needs to adapt to different tasks
|
| |
-
|
| |
- def str(self):
|
| |
- if self.info:
|
| |
- label = koji.taskLabel(self.info)
|
| |
- return "%s%d %s" % (' ' * self.level, self.id, label)
|
| |
- else:
|
| |
- return "%s%d" % (' ' * self.level, self.id)
|
| |
-
|
| |
- def __str__(self):
|
| |
- return self.str()
|
| |
-
|
| |
- def get_failure(self):
|
| |
- """Print infomation about task completion"""
|
| |
- if self.info['state'] != koji.TASK_STATES['FAILED']:
|
| |
- return ''
|
| |
- error = None
|
| |
- try:
|
| |
- result = self.session.getTaskResult(self.id)
|
| |
- except (six.moves.xmlrpc_client.Fault,koji.GenericError) as e:
|
| |
- error = e
|
| |
- if error is None:
|
| |
- # print("%s: complete" % self.str())
|
| |
- # We already reported this task as complete in update()
|
| |
- return ''
|
| |
- else:
|
| |
- return '%s: %s' % (error.__class__.__name__, str(error).strip())
|
| |
-
|
| |
- def update(self):
|
| |
- """Update info and log if needed. Returns True on state change."""
|
| |
- if self.is_done():
|
| |
- # Already done, nothing else to report
|
| |
- return False
|
| |
- last = self.info
|
| |
- self.info = self.session.getTaskInfo(self.id, request=True)
|
| |
- if self.info is None:
|
| |
- if not self.quiet:
|
| |
- print("No such task id: %i" % self.id)
|
| |
- sys.exit(1)
|
| |
- state = self.info['state']
|
| |
- if last:
|
| |
- #compare and note status changes
|
| |
- laststate = last['state']
|
| |
- if laststate != state:
|
| |
- if not self.quiet:
|
| |
- print("%s: %s -> %s" % (self.str(), self.display_state(last), self.display_state(self.info)))
|
| |
- return True
|
| |
- return False
|
| |
- else:
|
| |
- # First time we're seeing this task, so just show the current state
|
| |
- if not self.quiet:
|
| |
- print("%s: %s" % (self.str(), self.display_state(self.info)))
|
| |
- return False
|
| |
-
|
| |
- def is_done(self):
|
| |
- if self.info is None:
|
| |
- return False
|
| |
- state = koji.TASK_STATES[self.info['state']]
|
| |
- return (state in ['CLOSED','CANCELED','FAILED'])
|
| |
-
|
| |
- def is_success(self):
|
| |
- if self.info is None:
|
| |
- return False
|
| |
- state = koji.TASK_STATES[self.info['state']]
|
| |
- return (state == 'CLOSED')
|
| |
-
|
| |
- def display_state(self, info):
|
| |
- # We can sometimes be passed a task that is not yet open, but
|
| |
- # not finished either. info would be none.
|
| |
- if not info:
|
| |
- return 'unknown'
|
| |
- if info['state'] == koji.TASK_STATES['OPEN']:
|
| |
- if info['host_id']:
|
| |
- host = self.session.getHost(info['host_id'])
|
| |
- return 'open (%s)' % host['name']
|
| |
- else:
|
| |
- return 'open'
|
| |
- elif info['state'] == koji.TASK_STATES['FAILED']:
|
| |
- return 'FAILED: %s' % self.get_failure()
|
| |
- else:
|
| |
- return koji.TASK_STATES[info['state']].lower()
|
| |
-
|
| |
- def display_tasklist_status(tasks):
|
| |
- free = 0
|
| |
- open = 0
|
| |
- failed = 0
|
| |
- done = 0
|
| |
- for task_id in tasks.keys():
|
| |
- status = tasks[task_id].info['state']
|
| |
- if status == koji.TASK_STATES['FAILED']:
|
| |
- failed += 1
|
| |
- elif status == koji.TASK_STATES['CLOSED'] or status == koji.TASK_STATES['CANCELED']:
|
| |
- done += 1
|
| |
- elif status == koji.TASK_STATES['OPEN'] or status == koji.TASK_STATES['ASSIGNED']:
|
| |
- open += 1
|
| |
- elif status == koji.TASK_STATES['FREE']:
|
| |
- free += 1
|
| |
- print(" %d free %d open %d done %d failed" % (free, open, done, failed))
|
| |
-
|
| |
- def display_task_results(tasks):
|
| |
- for task in [task for task in tasks.values() if task.level == 0]:
|
| |
- state = task.info['state']
|
| |
- task_label = task.str()
|
| |
-
|
| |
- if state == koji.TASK_STATES['CLOSED']:
|
| |
- print('%s completed successfully' % task_label)
|
| |
- elif state == koji.TASK_STATES['FAILED']:
|
| |
- print('%s failed' % task_label)
|
| |
- elif state == koji.TASK_STATES['CANCELED']:
|
| |
- print('%s was canceled' % task_label)
|
| |
- else:
|
| |
- # shouldn't happen
|
| |
- print('%s has not completed' % task_label)
|
| |
-
|
| |
- def watch_tasks(session,tasklist,quiet=False):
|
| |
- global options
|
| |
- if not tasklist:
|
| |
- return
|
| |
- if not quiet:
|
| |
- print("Watching tasks (this may be safely interrupted)...")
|
| |
- sys.stdout.flush()
|
| |
- rv = 0
|
| |
- try:
|
| |
- tasks = {}
|
| |
- for task_id in tasklist:
|
| |
- tasks[task_id] = TaskWatcher(task_id,session,quiet=quiet)
|
| |
- while True:
|
| |
- all_done = True
|
| |
- for task_id, task in list(tasks.items()):
|
| |
- changed = task.update()
|
| |
- if not task.is_done():
|
| |
- all_done = False
|
| |
- else:
|
| |
- if changed:
|
| |
- # task is done and state just changed
|
| |
- if not quiet:
|
| |
- display_tasklist_status(tasks)
|
| |
- if not task.is_success():
|
| |
- rv = 1
|
| |
- for child in session.getTaskChildren(task_id):
|
| |
- child_id = child['id']
|
| |
- if not child_id in list(tasks.keys()):
|
| |
- tasks[child_id] = TaskWatcher(child_id, session, task.level + 1, quiet=quiet)
|
| |
- tasks[child_id].update()
|
| |
- # If we found new children, go through the list again,
|
| |
- # in case they have children also
|
| |
- all_done = False
|
| |
- if all_done:
|
| |
- if not quiet:
|
| |
- print('')
|
| |
- display_task_results(tasks)
|
| |
- break
|
| |
-
|
| |
- sys.stdout.flush()
|
| |
- time.sleep(options.poll_interval)
|
| |
- except KeyboardInterrupt:
|
| |
- if tasks and not quiet:
|
| |
- progname = os.path.basename(sys.argv[0]) or 'koji'
|
| |
- tlist = ['%s: %s' % (t.str(), t.display_state(t.info))
|
| |
- for t in tasks.values() if not t.is_done()]
|
| |
- print( \
|
| |
- """Tasks still running. You can continue to watch with the '%s watch-task' command.
|
| |
- Running Tasks:
|
| |
- %s""" % (progname, '\n'.join(tlist)))
|
| |
- raise
|
| |
- return rv
|
| |
-
|
| |
- def watch_logs(session, tasklist, opts):
|
| |
- global options
|
| |
- print("Watching logs (this may be safely interrupted)...")
|
| |
- def _isDone(session, taskId):
|
| |
- info = session.getTaskInfo(taskId)
|
| |
- if info is None:
|
| |
- print("No such task id: %i" % taskId)
|
| |
- sys.exit(1)
|
| |
- state = koji.TASK_STATES[info['state']]
|
| |
- return (state in ['CLOSED','CANCELED','FAILED'])
|
| |
-
|
| |
- offsets = {}
|
| |
- for task_id in tasklist:
|
| |
- offsets[task_id] = {}
|
| |
-
|
| |
- lastlog = None
|
| |
- while True:
|
| |
- for task_id in tasklist[:]:
|
| |
- if _isDone(session, task_id):
|
| |
- tasklist.remove(task_id)
|
| |
-
|
| |
- output = list_task_output_all_volumes(session, task_id)
|
| |
- # convert to list of (file, volume)
|
| |
- files = []
|
| |
- for filename, volumes in six.iteritems(output):
|
| |
- files += [(filename, volume) for volume in volumes]
|
| |
-
|
| |
- if opts.log:
|
| |
- logs = [file_volume for file_volume in files if file_volume[0] == opts.log]
|
| |
- else:
|
| |
- logs = [file_volume for file_volume in files if file_volume[0].endswith('log')]
|
| |
-
|
| |
- taskoffsets = offsets[task_id]
|
| |
- for log, volume in logs:
|
| |
- contents = 'placeholder'
|
| |
- while contents:
|
| |
- if (log, volume) not in taskoffsets:
|
| |
- taskoffsets[(log, volume)] = 0
|
| |
-
|
| |
- contents = session.downloadTaskOutput(task_id, log, taskoffsets[(log, volume)], 16384, volume=volume)
|
| |
- taskoffsets[(log, volume)] += len(contents)
|
| |
- if contents:
|
| |
- currlog = "%d:%s:%s:" % (task_id, volume, log)
|
| |
- if currlog != lastlog:
|
| |
- if lastlog:
|
| |
- sys.stdout.write("\n")
|
| |
- sys.stdout.write("==> %s <==\n" % currlog)
|
| |
- lastlog = currlog
|
| |
- sys.stdout.write(contents)
|
| |
-
|
| |
- if not tasklist:
|
| |
- break
|
| |
-
|
| |
- time.sleep(options.poll_interval)
|
| |
-
|
| |
-
|
| |
- def list_task_output_all_volumes(session, task_id):
|
| |
- """List task output with all volumes, or fake it"""
|
| |
- try:
|
| |
- return session.listTaskOutput(task_id, all_volumes=True)
|
| |
- except koji.GenericError as e:
|
| |
- if 'got an unexpected keyword argument' not in str(e):
|
| |
- raise
|
| |
- # otherwise leave off the option and fake it
|
| |
- output = session.listTaskOutput(task_id)
|
| |
- return dict([fn, ['DEFAULT']] for fn in output)
|
| |
-
|
| |
-
|
| |
- def handle_add_group(options, session, args):
|
| |
- "[admin] Add a group to a tag"
|
| |
- usage = _("usage: %prog add-group <tag> <group>")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) != 2:
|
| |
- parser.error(_("Please specify a tag name and a group name"))
|
| |
- assert False # pragma: no cover
|
| |
- tag = args[0]
|
| |
- group = args[1]
|
| |
-
|
| |
- activate_session(session)
|
| |
- if not session.hasPerm('admin'):
|
| |
- print("This action requires admin privileges")
|
| |
- return 1
|
| |
-
|
| |
- dsttag = session.getTag(tag)
|
| |
- if not dsttag:
|
| |
- print("Unknown tag: %s" % tag)
|
| |
- return 1
|
| |
-
|
| |
- groups = dict([(p['name'], p['group_id']) for p in session.getTagGroups(tag, inherit=False)])
|
| |
- group_id = groups.get(group, None)
|
| |
- if group_id is not None:
|
| |
- print("Group %s already exists for tag %s" % (group, tag))
|
| |
- return 1
|
| |
-
|
| |
- session.groupListAdd(tag, group)
|
| |
-
|
| |
- def handle_assign_task(options, session, args):
|
| |
- "[admin] Assign a task to a host"
|
| |
- usage = _('usage: %prog assign-task task_id hostname')
|
| |
- usage += _('\n(Specify the --help global option for a list of other help options)')
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option('-f', '--force', action='store_true', default=False,
|
| |
- help=_('force to assign a non-free task'))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
-
|
| |
- if len(args) != 2:
|
| |
- parser.error(_('please specify a task id and a hostname'))
|
| |
- else:
|
| |
- task_id = int(args[0])
|
| |
- hostname = args[1]
|
| |
-
|
| |
- taskinfo = session.getTaskInfo(task_id, request=False)
|
| |
- if taskinfo is None:
|
| |
- raise koji.GenericError("No such task: %s" % task_id)
|
| |
-
|
| |
- hostinfo = session.getHost(hostname)
|
| |
- if hostinfo is None:
|
| |
- raise koji.GenericError("No such host: %s" % hostname)
|
| |
-
|
| |
- force = False
|
| |
- if options.force:
|
| |
- force = True
|
| |
+ load_plugins(options)
|
| |
|
| |
- activate_session(session)
|
| |
- if not session.hasPerm('admin'):
|
| |
- print("This action requires admin privileges")
|
| |
- return 1
|
| |
-
|
| |
- ret = session.assignTask(task_id, hostname, force)
|
| |
- if ret:
|
| |
- print('assigned task %d to host %s' % (task_id, hostname))
|
| |
- else:
|
| |
- print('failed to assign task %d to host %s' % (task_id, hostname))
|
| |
-
|
| |
-
|
| |
- def handle_add_host(options, session, args):
|
| |
- "[admin] Add a host"
|
| |
- usage = _("usage: %prog add-host [options] hostname arch [arch2 ...]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--krb-principal", help=_("set a non-default kerberos principal for the host"))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) < 2:
|
| |
- parser.error(_("Please specify a hostname and at least one arch"))
|
| |
- assert False # pragma: no cover
|
| |
- host = args[0]
|
| |
- activate_session(session)
|
| |
- id = session.getHost(host)
|
| |
- if id:
|
| |
- print("%s is already in the database" % host)
|
| |
- return 1
|
| |
- else:
|
| |
- kwargs = {}
|
| |
- if options.krb_principal is not None:
|
| |
- kwargs['krb_principal'] = options.krb_principal
|
| |
- id = session.addHost(host, args[1:], **kwargs)
|
| |
- if id:
|
| |
- print("%s added: id %d" % (host, id))
|
| |
-
|
| |
- def handle_edit_host(options, session, args):
|
| |
- "[admin] Edit a host"
|
| |
- usage = _("usage: %prog edit-host hostname ... [options]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--arches", help=_("Space or comma-separated list of supported architectures"))
|
| |
- parser.add_option("--capacity", type="float", help=_("Capacity of this host"))
|
| |
- parser.add_option("--description", metavar="DESC", help=_("Description of this host"))
|
| |
- parser.add_option("--comment", help=_("A brief comment about this host"))
|
| |
- (subopts, args) = parser.parse_args(args)
|
| |
+ if options.help_commands:
|
| |
+ list_commands()
|
| |
+ sys.exit(0)
|
| |
if not args:
|
| |
- parser.error(_("Please specify a hostname"))
|
| |
-
|
| |
- activate_session(session)
|
| |
-
|
| |
- vals = {}
|
| |
- for key, val in subopts.__dict__.items():
|
| |
- if val is not None:
|
| |
- vals[key] = val
|
| |
- if 'arches' in vals:
|
| |
- vals['arches'] = parse_arches(vals['arches'])
|
| |
-
|
| |
- session.multicall = True
|
| |
- for host in args:
|
| |
- session.getHost(host)
|
| |
- error = False
|
| |
- for host, [info] in zip(args, session.multiCall(strict=True)):
|
| |
- if not info:
|
| |
- print(_("Host %s does not exist") % host)
|
| |
- error = True
|
| |
-
|
| |
- if error:
|
| |
- print(_("No changes made, please correct the command line"))
|
| |
- return 1
|
| |
-
|
| |
- session.multicall = True
|
| |
- for host in args:
|
| |
- session.editHost(host, **vals)
|
| |
- for host, [result] in zip(args, session.multiCall(strict=True)):
|
| |
- if result:
|
| |
- print(_("Edited %s") % host)
|
| |
- else:
|
| |
- print(_("No changes made to %s") % host)
|
| |
-
|
| |
- def handle_add_host_to_channel(options, session, args):
|
| |
- "[admin] Add a host to a channel"
|
| |
- usage = _("usage: %prog add-host-to-channel [options] hostname channel")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--list", action="store_true", help=optparse.SUPPRESS_HELP)
|
| |
- parser.add_option("--new", action="store_true", help=_("Create channel if needed"))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if not options.list and len(args) != 2:
|
| |
- parser.error(_("Please specify a hostname and a channel"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- if options.list:
|
| |
- for channel in session.listChannels():
|
| |
- print(channel['name'])
|
| |
- return
|
| |
- channel = args[1]
|
| |
- if not options.new:
|
| |
- channelinfo = session.getChannel(channel)
|
| |
- if not channelinfo:
|
| |
- print("No such channel: %s" % channel)
|
| |
- return 1
|
| |
- host = args[0]
|
| |
- hostinfo = session.getHost(host)
|
| |
- if not hostinfo:
|
| |
- print("No such host: %s" % host)
|
| |
- return 1
|
| |
- kwargs = {}
|
| |
- if options.new:
|
| |
- kwargs['create'] = True
|
| |
- session.addHostToChannel(host, channel, **kwargs)
|
| |
-
|
| |
- def handle_remove_host_from_channel(options, session, args):
|
| |
- "[admin] Remove a host from a channel"
|
| |
- usage = _("usage: %prog remove-host-from-channel [options] hostname channel")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) != 2:
|
| |
- parser.error(_("Please specify a hostname and a channel"))
|
| |
- assert False # pragma: no cover
|
| |
- host = args[0]
|
| |
- activate_session(session)
|
| |
- hostinfo = session.getHost(host)
|
| |
- if not hostinfo:
|
| |
- print("No such host: %s" % host)
|
| |
- return 1
|
| |
- hostchannels = [c['name'] for c in session.listChannels(hostinfo['id'])]
|
| |
-
|
| |
- channel = args[1]
|
| |
- if channel not in hostchannels:
|
| |
- print("Host %s is not a member of channel %s" % (host, channel))
|
| |
- return 1
|
| |
-
|
| |
- session.removeHostFromChannel(host, channel)
|
| |
-
|
| |
- def handle_remove_channel(options, session, args):
|
| |
- "[admin] Remove a channel entirely"
|
| |
- usage = _("usage: %prog remove-channel [options] channel")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--force", action="store_true", help=_("force removal, if possible"))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) != 1:
|
| |
- parser.error(_("Incorrect number of arguments"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- cinfo = session.getChannel(args[0])
|
| |
- if not cinfo:
|
| |
- print("No such channel: %s" % args[0])
|
| |
- return 1
|
| |
- session.removeChannel(args[0], force=options.force)
|
| |
-
|
| |
- def handle_rename_channel(options, session, args):
|
| |
- "[admin] Rename a channel"
|
| |
- usage = _("usage: %prog rename-channel [options] old-name new-name")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) != 2:
|
| |
- parser.error(_("Incorrect number of arguments"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- cinfo = session.getChannel(args[0])
|
| |
- if not cinfo:
|
| |
- print("No such channel: %s" % args[0])
|
| |
- return 1
|
| |
- session.renameChannel(args[0], args[1])
|
| |
+ list_commands()
|
| |
+ sys.exit(0)
|
| |
|
| |
- def handle_add_pkg(options, session, args):
|
| |
- "[admin] Add a package to the listing for tag"
|
| |
- usage = _("usage: %prog add-pkg [options] tag package [package2 ...]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--force", action='store_true', help=_("Override blocks if necessary"))
|
| |
- parser.add_option("--owner", help=_("Specify owner"))
|
| |
- parser.add_option("--extra-arches", help=_("Specify extra arches"))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) < 2:
|
| |
- parser.error(_("Please specify a tag and at least one package"))
|
| |
- assert False # pragma: no cover
|
| |
- if not options.owner:
|
| |
- parser.error(_("Please specify an owner for the package(s)"))
|
| |
+ 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:
|
| |
+ list_commands()
|
| |
+ parser.error('Unknown command: %s' % args[0])
|
| |
assert False # pragma: no cover
|
| |
- if not session.getUser(options.owner):
|
| |
- print("User %s does not exist" % options.owner)
|
| |
- return 1
|
| |
- activate_session(session)
|
| |
- tag = args[0]
|
| |
- opts = {}
|
| |
- opts['force'] = options.force
|
| |
- opts['block'] = False
|
| |
- # check if list of packages exists for that tag already
|
| |
- dsttag=session.getTag(tag)
|
| |
- if dsttag is None:
|
| |
- print("No such tag: %s" % tag)
|
| |
- sys.exit(1)
|
| |
- pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'])])
|
| |
- to_add = []
|
| |
- for package in args[1:]:
|
| |
- package_id = pkglist.get(package, None)
|
| |
- if not package_id is None:
|
| |
- print("Package %s already exists in tag %s" % (package, tag))
|
| |
- continue
|
| |
- to_add.append(package)
|
| |
- if options.extra_arches:
|
| |
- opts['extra_arches'] = parse_arches(options.extra_arches)
|
| |
|
| |
- # add the packages
|
| |
- print("Adding %i packages to tag %s" % (len(to_add), dsttag['name']))
|
| |
- session.multicall = True
|
| |
- for package in to_add:
|
| |
- session.packageListAdd(tag, package, options.owner, **opts)
|
| |
- session.multiCall(strict=True)
|
| |
+ return options, cmd, args[1:]
|
| |
|
| |
|
| |
- def handle_block_pkg(options, session, args):
|
| |
- "[admin] Block a package in the listing for tag"
|
| |
- usage = _("usage: %prog block-pkg [options] tag package [package2 ...]")
|
| |
+ 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)
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) < 2:
|
| |
- parser.error(_("Please specify a tag and at least one package"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- tag = args[0]
|
| |
- # check if list of packages exists for that tag already
|
| |
- dsttag=session.getTag(tag)
|
| |
- if dsttag is None:
|
| |
- print("No such tag: %s" % tag)
|
| |
- return 1
|
| |
- pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'], inherited=True)])
|
| |
- ret = 0
|
| |
- for package in args[1:]:
|
| |
- package_id = pkglist.get(package, None)
|
| |
- if package_id is None:
|
| |
- print("Package %s doesn't exist in tag %s" % (package, tag))
|
| |
- ret = 1
|
| |
- if ret:
|
| |
- return ret
|
| |
- session.multicall = True
|
| |
- for package in args[1:]:
|
| |
- session.packageListBlock(tag, package)
|
| |
- session.multiCall(strict=True)
|
| |
+ # the --admin opt is for backwards compatibility. It is equivalent to: koji help admin
|
| |
+ parser.add_option("--admin", action="store_true", help=optparse.SUPPRESS_HELP)
|
| |
|
| |
- def handle_remove_pkg(options, session, args):
|
| |
- "[admin] Remove a package from the listing for tag"
|
| |
- usage = _("usage: %prog remove-pkg [options] tag package [package2 ...]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--force", action='store_true', help=_("Override blocks if necessary"))
|
| |
(options, args) = parser.parse_args(args)
|
| |
- if len(args) < 2:
|
| |
- parser.error(_("Please specify a tag and at least one package"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- tag = args[0]
|
| |
- opts = {}
|
| |
- opts['force'] = options.force
|
| |
- # check if list of packages exists for that tag already
|
| |
- dsttag=session.getTag(tag)
|
| |
- if dsttag is None:
|
| |
- print("No such tag: %s" % tag)
|
| |
- return 1
|
| |
- pkglist = dict([(p['package_name'], p['package_id']) for p in session.listPackages(tagID=dsttag['id'])])
|
| |
- ret = 0
|
| |
- for package in args[1:]:
|
| |
- package_id = pkglist.get(package, None)
|
| |
- if package_id is None:
|
| |
- print("Package %s is not in tag %s" % (package, tag))
|
| |
- ret = 1
|
| |
- if ret:
|
| |
- return ret
|
| |
- session.multicall = True
|
| |
- for package in args[1:]:
|
| |
- session.packageListRemove(tag, package, **opts)
|
| |
- session.multiCall(strict=True)
|
| |
|
| |
- def _unique_path(prefix):
|
| |
- """Create a unique path fragment by appending a path component
|
| |
- to prefix. The path component will consist of a string of letter and numbers
|
| |
- that is unlikely to be a duplicate, but is not guaranteed to be unique."""
|
| |
- # Use time() in the dirname to provide a little more information when
|
| |
- # browsing the filesystem.
|
| |
- # For some reason repr(time.time()) includes 4 or 5
|
| |
- # more digits of precision than str(time.time())
|
| |
- return '%s/%r.%s' % (prefix, time.time(),
|
| |
- ''.join([random.choice(string.ascii_letters) for i in range(8)]))
|
| |
+ 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)
|
| |
|
| |
- def _format_size(size):
|
| |
- if (size / 1073741824 >= 1):
|
| |
- return "%0.2f GiB" % (size / 1073741824.0)
|
| |
- if (size / 1048576 >= 1):
|
| |
- return "%0.2f MiB" % (size / 1048576.0)
|
| |
- if (size / 1024 >=1):
|
| |
- return "%0.2f KiB" % (size / 1024.0)
|
| |
- return "%0.2f B" % (size)
|
| |
+ if not chosen:
|
| |
+ list_commands()
|
| |
+ else:
|
| |
+ list_commands(chosen)
|
| |
|
| |
- def _format_secs(t):
|
| |
- h = t / 3600
|
| |
- t %= 3600
|
| |
- m = t / 60
|
| |
- s = t % 60
|
| |
- return "%02d:%02d:%02d" % (h, m, s)
|
| |
|
| |
- def _progress_callback(uploaded, total, piece, time, total_time):
|
| |
- if total == 0:
|
| |
- percent_done = 0.0
|
| |
+ def list_commands(categories_chosen=None):
|
| |
+ if categories_chosen is None or "all" in categories_chosen:
|
| |
+ categories_chosen = list(categories.keys())
|
| |
else:
|
| |
- percent_done = float(uploaded)/float(total)
|
| |
- percent_done_str = "%02d%%" % (percent_done * 100)
|
| |
- data_done = _format_size(uploaded)
|
| |
- elapsed = _format_secs(total_time)
|
| |
+ # 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__
|
| |
+ if desc.startswith('[%s] ' % category):
|
| |
+ desc = desc[len('[%s] ' % category):]
|
| |
+ elif category != 'misc' or desc.startswith('['):
|
| |
+ continue
|
| |
+ print(" %-25s %s" % (alias, desc))
|
| |
|
| |
- speed = "- B/sec"
|
| |
- if (time):
|
| |
- if (uploaded != total):
|
| |
- speed = _format_size(float(piece)/float(time)) + "/sec"
|
| |
- else:
|
| |
- speed = _format_size(float(total)/float(total_time)) + "/sec"
|
| |
+ print("%s" % get_epilog_str().rstrip("\n"))
|
| |
|
| |
- # write formated string and flush
|
| |
- sys.stdout.write("[% -36s] % 4s % 8s % 10s % 14s\r" % ('='*(int(percent_done*36)), percent_done_str, elapsed, data_done, speed))
|
| |
- sys.stdout.flush()
|
| |
-
|
| |
- def _running_in_bg():
|
| |
- try:
|
| |
- return (not os.isatty(0)) or (os.getpgrp() != os.tcgetpgrp(0))
|
| |
- except OSError as e:
|
| |
- return True
|
| |
-
|
| |
- def handle_build(options, session, args):
|
| |
- "[build] Build a package from source"
|
| |
- usage = _("usage: %prog build [options] target <srpm path or scm url>")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--skip-tag", action="store_true",
|
| |
- help=_("Do not attempt to tag package"))
|
| |
- 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("--arch-override", help=_("Override build arches"))
|
| |
- parser.add_option("--repo-id", type="int", help=_("Use a specific repo"))
|
| |
- parser.add_option("--noprogress", action="store_true",
|
| |
- help=_("Do not display progress of the upload"))
|
| |
- parser.add_option("--background", action="store_true",
|
| |
- help=_("Run the build at a lower priority"))
|
| |
- (build_opts, args) = parser.parse_args(args)
|
| |
- if len(args) != 2:
|
| |
- parser.error(_("Exactly two arguments (a build target and a SCM URL or srpm file) are required"))
|
| |
- assert False # pragma: no cover
|
| |
- if build_opts.arch_override and not build_opts.scratch:
|
| |
- parser.error(_("--arch_override is only allowed for --scratch builds"))
|
| |
- activate_session(session)
|
| |
- target = args[0]
|
| |
- if target.lower() == "none" and build_opts.repo_id:
|
| |
- target = None
|
| |
- build_opts.skip_tag = True
|
| |
- else:
|
| |
- build_target = session.getBuildTarget(target)
|
| |
- if not build_target:
|
| |
- parser.error(_("Unknown build target: %s" % target))
|
| |
- dest_tag = session.getTag(build_target['dest_tag'])
|
| |
- if not dest_tag:
|
| |
- parser.error(_("Unknown destination tag: %s" % build_target['dest_tag_name']))
|
| |
- if dest_tag['locked'] and not build_opts.scratch:
|
| |
- parser.error(_("Destination tag %s is locked" % dest_tag['name']))
|
| |
- source = args[1]
|
| |
- opts = {}
|
| |
- if build_opts.arch_override:
|
| |
- opts['arch_override'] = parse_arches(build_opts.arch_override)
|
| |
- for key in ('skip_tag', 'scratch', 'repo_id'):
|
| |
- val = getattr(build_opts, key)
|
| |
- if val is not None:
|
| |
- opts[key] = val
|
| |
- priority = None
|
| |
- if build_opts.background:
|
| |
- #relative to koji.PRIO_DEFAULT
|
| |
- priority = 5
|
| |
- # try to check that source is an SRPM
|
| |
- if '://' not in source:
|
| |
- #treat source as an srpm and upload it
|
| |
- if not build_opts.quiet:
|
| |
- print("Uploading srpm: %s" % source)
|
| |
- serverdir = _unique_path('cli-build')
|
| |
- if _running_in_bg() or build_opts.noprogress or build_opts.quiet:
|
| |
- callback = None
|
| |
- else:
|
| |
- callback = _progress_callback
|
| |
- session.uploadWrapper(source, serverdir, callback=callback)
|
| |
- print('')
|
| |
- source = "%s/%s" % (serverdir, os.path.basename(source))
|
| |
- task_id = session.build(source, target, opts, priority=priority)
|
| |
- if not build_opts.quiet:
|
| |
- print("Created task: %d" % task_id)
|
| |
- print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))
|
| |
- if build_opts.wait or (build_opts.wait is None and not _running_in_bg()):
|
| |
- session.logout()
|
| |
- return watch_tasks(session, [task_id], quiet=build_opts.quiet)
|
| |
- else:
|
| |
- return
|
| |
-
|
| |
- def handle_chain_build(options, session, args):
|
| |
- # XXX - replace handle_build with this, once chain-building has gotten testing
|
| |
- "[build] Build one or more packages from source"
|
| |
- usage = _("usage: %prog chain-build [options] target URL [URL2 [:] URL3 [:] URL4 ...]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--nowait", action="store_true",
|
| |
- 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"))
|
| |
- (build_opts, args) = parser.parse_args(args)
|
| |
- if len(args) < 2:
|
| |
- parser.error(_("At least two arguments (a build target and a SCM URL) are required"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- target = args[0]
|
| |
- build_target = session.getBuildTarget(target)
|
| |
- if not build_target:
|
| |
- parser.error(_("Unknown build target: %s" % target))
|
| |
- dest_tag = session.getTag(build_target['dest_tag'], strict=True)
|
| |
- if dest_tag['locked']:
|
| |
- parser.error(_("Destination tag %s is locked" % dest_tag['name']))
|
| |
-
|
| |
- # check that the destination tag is in the inheritance tree of the build tag
|
| |
- # otherwise there is no way that a chain-build can work
|
| |
- ancestors = session.getFullInheritance(build_target['build_tag'])
|
| |
- if dest_tag['id'] not in [build_target['build_tag']] + [ancestor['parent_id'] for ancestor in ancestors]:
|
| |
- print(_("Packages in destination tag %(dest_tag_name)s are not inherited by build tag %(build_tag_name)s" % build_target))
|
| |
- print(_("Target %s is not usable for a chain-build" % build_target['name']))
|
| |
- return 1
|
| |
-
|
| |
- sources = args[1:]
|
| |
-
|
| |
- src_list = []
|
| |
- build_level = []
|
| |
- #src_lists is a list of lists of sources to build.
|
| |
- # each list is block of builds ("build level") which must all be completed
|
| |
- # before the next block begins. Blocks are separated on the command line with ':'
|
| |
- for src in sources:
|
| |
- if src == ':':
|
| |
- if build_level:
|
| |
- src_list.append(build_level)
|
| |
- build_level = []
|
| |
- elif '://' in src:
|
| |
- # quick check that src might be a url
|
| |
- build_level.append(src)
|
| |
- elif '/' not in src and not src.endswith('.rpm') and len(src.split('-')) >= 3:
|
| |
- # quick check that it looks like a N-V-R
|
| |
- build_level.append(src)
|
| |
- else:
|
| |
- print(_('"%s" is not a SCM URL or package N-V-R' % src))
|
| |
- return 1
|
| |
- if build_level:
|
| |
- src_list.append(build_level)
|
| |
-
|
| |
- if len(src_list) < 2:
|
| |
- parser.error(_('You must specify at least one dependency between builds with : (colon)\nIf there are no dependencies, use the build command instead'))
|
| |
-
|
| |
- priority = None
|
| |
- if build_opts.background:
|
| |
- #relative to koji.PRIO_DEFAULT
|
| |
- priority = 5
|
| |
-
|
| |
- task_id = session.chainBuild(src_list, target, priority=priority)
|
| |
- if not build_opts.quiet:
|
| |
- print("Created task: %d" % task_id)
|
| |
- print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))
|
| |
- if _running_in_bg() or build_opts.nowait:
|
| |
- return
|
| |
- else:
|
| |
- session.logout()
|
| |
- return watch_tasks(session, [task_id], quiet=build_opts.quiet)
|
| |
-
|
| |
- def handle_maven_build(options, session, args):
|
| |
- "[build] Build a Maven package from source"
|
| |
- usage = _("usage: %prog maven-build [options] target URL")
|
| |
- usage += _("\n %prog maven-build --ini=CONFIG... [options] target")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--patches", action="store", metavar="URL",
|
| |
- help=_("SCM URL of a directory containing patches to apply to the sources before building"))
|
| |
- parser.add_option("-G", "--goal", action="append",
|
| |
- dest="goals", metavar="GOAL", default=[],
|
| |
- help=_("Additional goal to run before \"deploy\""))
|
| |
- parser.add_option("-P", "--profile", action="append",
|
| |
- dest="profiles", metavar="PROFILE", default=[],
|
| |
- help=_("Enable a profile for the Maven build"))
|
| |
- parser.add_option("-D", "--property", action="append",
|
| |
- dest="properties", metavar="NAME=VALUE", default=[],
|
| |
- help=_("Pass a system property to the Maven build"))
|
| |
- parser.add_option("-E", "--env", action="append",
|
| |
- dest="envs", metavar="NAME=VALUE", default=[],
|
| |
- help=_("Set an environment variable"))
|
| |
- parser.add_option("-p", "--package", action="append",
|
| |
- dest="packages", metavar="PACKAGE", default=[],
|
| |
- help=_("Install an additional package into the buildroot"))
|
| |
- parser.add_option("-J", "--jvm-option", action="append",
|
| |
- dest="jvm_options", metavar="OPTION", default=[],
|
| |
- help=_("Pass a command-line option to the JVM"))
|
| |
- parser.add_option("-M", "--maven-option", action="append",
|
| |
- dest="maven_options", metavar="OPTION", default=[],
|
| |
- help=_("Pass a command-line option to Maven"))
|
| |
- parser.add_option("--ini", action="append",
|
| |
- dest="inis", metavar="CONFIG", default=[],
|
| |
- help=_("Pass build parameters via a .ini file"))
|
| |
- parser.add_option("-s", "--section",
|
| |
- help=_("Get build parameters from this section of the .ini"))
|
| |
- parser.add_option("--debug", action="store_true",
|
| |
- help=_("Run Maven build in debug mode"))
|
| |
- parser.add_option("--specfile", action="store", metavar="URL",
|
| |
- help=_("SCM URL of a spec file fragment to use to generate wrapper RPMs"))
|
| |
- parser.add_option("--skip-tag", action="store_true",
|
| |
- help=_("Do not attempt to tag package"))
|
| |
- parser.add_option("--scratch", action="store_true",
|
| |
- help=_("Perform a scratch build"))
|
| |
- parser.add_option("--nowait", action="store_true",
|
| |
- 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"))
|
| |
- (build_opts, args) = parser.parse_args(args)
|
| |
- if build_opts.inis:
|
| |
- if len(args) != 1:
|
| |
- parser.error(_("Exactly one argument (a build target) is required"))
|
| |
- else:
|
| |
- if len(args) != 2:
|
| |
- parser.error(_("Exactly two arguments (a build target and a SCM URL) are required"))
|
| |
- activate_session(session)
|
| |
- target = args[0]
|
| |
- build_target = session.getBuildTarget(target)
|
| |
- if not build_target:
|
| |
- parser.error(_("Unknown build target: %s" % target))
|
| |
- dest_tag = session.getTag(build_target['dest_tag'])
|
| |
- if not dest_tag:
|
| |
- parser.error(_("Unknown destination tag: %s" % build_target['dest_tag_name']))
|
| |
- if dest_tag['locked'] and not build_opts.scratch:
|
| |
- parser.error(_("Destination tag %s is locked" % dest_tag['name']))
|
| |
- if build_opts.inis:
|
| |
- try:
|
| |
- params = koji.util.parse_maven_param(build_opts.inis, scratch=build_opts.scratch,
|
| |
- section=build_opts.section)
|
| |
- except ValueError as e:
|
| |
- parser.error(e.args[0])
|
| |
- opts = list(params.values())[0]
|
| |
- if opts.pop('type', 'maven') != 'maven':
|
| |
- parser.error(_("Section %s does not contain a maven-build config") % list(params.keys())[0])
|
| |
- source = opts.pop('scmurl')
|
| |
- else:
|
| |
- source = args[1]
|
| |
- opts = koji.util.maven_opts(build_opts, scratch=build_opts.scratch)
|
| |
- if '://' not in source:
|
| |
- parser.error(_("Invalid SCM URL: %s" % source))
|
| |
- if build_opts.debug:
|
| |
- opts.setdefault('maven_options', []).append('--debug')
|
| |
- if build_opts.skip_tag:
|
| |
- opts['skip_tag'] = True
|
| |
- priority = None
|
| |
- if build_opts.background:
|
| |
- #relative to koji.PRIO_DEFAULT
|
| |
- priority = 5
|
| |
- task_id = session.mavenBuild(source, target, opts, priority=priority)
|
| |
- if not build_opts.quiet:
|
| |
- print("Created task: %d" % task_id)
|
| |
- print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))
|
| |
- if _running_in_bg() or build_opts.nowait:
|
| |
- return
|
| |
- else:
|
| |
- session.logout()
|
| |
- return watch_tasks(session, [task_id], quiet=build_opts.quiet)
|
| |
-
|
| |
- def handle_wrapper_rpm(options, session, args):
|
| |
- """[build] Build wrapper rpms for any archives associated with a build."""
|
| |
- usage = _("usage: %prog wrapper-rpm [options] target build-id|n-v-r URL")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--create-build", action="store_true", help=_("Create a new build to contain wrapper rpms"))
|
| |
- parser.add_option("--ini", action="append",
|
| |
- dest="inis", metavar="CONFIG", default=[],
|
| |
- help=_("Pass build parameters via a .ini file"))
|
| |
- parser.add_option("-s", "--section",
|
| |
- help=_("Get build parameters from this section of the .ini"))
|
| |
- parser.add_option("--skip-tag", action="store_true", help=_("If creating a new build, don't tag it"))
|
| |
- parser.add_option("--scratch", action="store_true", help=_("Perform a scratch build"))
|
| |
- parser.add_option("--nowait", action="store_true", help=_("Don't wait on build"))
|
| |
- parser.add_option("--background", action="store_true", help=_("Run the build at a lower priority"))
|
| |
-
|
| |
- (build_opts, args) = parser.parse_args(args)
|
| |
- if build_opts.inis:
|
| |
- if len(args)!= 1:
|
| |
- parser.error(_("Exactly one argument (a build target) is required"))
|
| |
- else:
|
| |
- if len(args) < 3:
|
| |
- parser.error(_("You must provide a build target, a build ID or NVR, and a SCM URL to a specfile fragment"))
|
| |
- activate_session(session)
|
| |
-
|
| |
- target = args[0]
|
| |
- if build_opts.inis:
|
| |
- try:
|
| |
- params = koji.util.parse_maven_param(build_opts.inis, scratch=build_opts.scratch,
|
| |
- section=build_opts.section)
|
| |
- except ValueError as e:
|
| |
- parser.error(e.args[0])
|
| |
- opts = list(params.values())[0]
|
| |
- if opts.get('type') != 'wrapper':
|
| |
- parser.error(_("Section %s does not contain a wrapper-rpm config") % list(params.keys())[0])
|
| |
- url = opts['scmurl']
|
| |
- package = opts['buildrequires'][0]
|
| |
- target_info = session.getBuildTarget(target, strict=True)
|
| |
- latest_builds = session.getLatestBuilds(target_info['dest_tag'], package=package)
|
| |
- if not latest_builds:
|
| |
- parser.error(_("No build of %s in %s") % (package, target_info['dest_tag_name']))
|
| |
- build_id = latest_builds[0]['nvr']
|
| |
- else:
|
| |
- build_id = args[1]
|
| |
- if build_id.isdigit():
|
| |
- build_id = int(build_id)
|
| |
- url = args[2]
|
| |
- priority = None
|
| |
- if build_opts.background:
|
| |
- priority = 5
|
| |
- opts = {}
|
| |
- if build_opts.create_build:
|
| |
- opts['create_build'] = True
|
| |
- if build_opts.skip_tag:
|
| |
- opts['skip_tag'] = True
|
| |
- if build_opts.scratch:
|
| |
- opts['scratch'] = True
|
| |
- task_id = session.wrapperRPM(build_id, url, target, priority, opts=opts)
|
| |
- print("Created task: %d" % task_id)
|
| |
- print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))
|
| |
- if _running_in_bg() or build_opts.nowait:
|
| |
- return
|
| |
- else:
|
| |
- session.logout()
|
| |
- return watch_tasks(session,[task_id],quiet=options.quiet)
|
| |
-
|
| |
- def handle_maven_chain(options, session, args):
|
| |
- "[build] Run a set of Maven builds in dependency order"
|
| |
- usage = _("usage: %prog maven-chain [options] target config...")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--skip-tag", action="store_true",
|
| |
- help=_("Do not attempt to tag builds"))
|
| |
- parser.add_option("--scratch", action="store_true",
|
| |
- help=_("Perform scratch builds"))
|
| |
- parser.add_option("--debug", action="store_true",
|
| |
- help=_("Run Maven build in debug mode"))
|
| |
- parser.add_option("--force", action="store_true",
|
| |
- help=_("Force rebuilds of all packages"))
|
| |
- parser.add_option("--nowait", action="store_true",
|
| |
- help=_("Don't wait on build"))
|
| |
- parser.add_option("--background", action="store_true",
|
| |
- help=_("Run the build at a lower priority"))
|
| |
- (build_opts, args) = parser.parse_args(args)
|
| |
- if len(args) < 2:
|
| |
- parser.error(_("Two arguments (a build target and a config file) are required"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- target = args[0]
|
| |
- build_target = session.getBuildTarget(target)
|
| |
- if not build_target:
|
| |
- parser.error(_("Unknown build target: %s") % target)
|
| |
- dest_tag = session.getTag(build_target['dest_tag'])
|
| |
- if not dest_tag:
|
| |
- parser.error(_("Unknown destination tag: %s") % build_target['dest_tag_name'])
|
| |
- if dest_tag['locked'] and not build_opts.scratch:
|
| |
- parser.error(_("Destination tag %s is locked") % dest_tag['name'])
|
| |
- opts = {}
|
| |
- for key in ('skip_tag', 'scratch', 'debug', 'force'):
|
| |
- val = getattr(build_opts, key)
|
| |
- if val:
|
| |
- opts[key] = val
|
| |
- try:
|
| |
- builds = koji.util.parse_maven_chain(args[1:], scratch=opts.get('scratch'))
|
| |
- except ValueError as e:
|
| |
- parser.error(e.args[0])
|
| |
- priority = None
|
| |
- if build_opts.background:
|
| |
- priority = 5
|
| |
- task_id = session.chainMaven(builds, target, opts, priority=priority)
|
| |
- print("Created task: %d" % task_id)
|
| |
- print("Task info: %s/taskinfo?taskID=%s" % (options.weburl, task_id))
|
| |
- if _running_in_bg() or build_opts.nowait:
|
| |
- return
|
| |
- else:
|
| |
- session.logout()
|
| |
- return watch_tasks(session, [task_id], quiet=options.quiet)
|
| |
-
|
| |
- def handle_resubmit(options, session, args):
|
| |
- """[build] Retry a canceled or failed task, using the same parameter as the original task."""
|
| |
- usage = _("usage: %prog resubmit [options] taskID")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--nowait", action="store_true", help=_("Don't wait on task"))
|
| |
- parser.add_option("--nowatch", action="store_true", dest="nowait",
|
| |
- help=_("An alias for --nowait"))
|
| |
- parser.add_option("--quiet", action="store_true", help=_("Do not print the task information"), default=options.quiet)
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) != 1:
|
| |
- parser.error(_("Please specify a single task ID"))
|
| |
- assert False # pragma: no cover
|
| |
- activate_session(session)
|
| |
- taskID = int(args[0])
|
| |
- if not options.quiet:
|
| |
- print("Resubmitting the following task:")
|
| |
- _printTaskInfo(session, taskID, 0, False, True)
|
| |
- newID = session.resubmitTask(taskID)
|
| |
- if not options.quiet:
|
| |
- print("Resubmitted task %s as new task %s" % (taskID, newID))
|
| |
- if _running_in_bg() or options.nowait:
|
| |
- return
|
| |
- else:
|
| |
- session.logout()
|
| |
- return watch_tasks(session, [newID], quiet=options.quiet)
|
| |
-
|
| |
- def handle_call(options, session, args):
|
| |
- "Execute an arbitrary XML-RPC call"
|
| |
- usage = _("usage: %prog call [options] name [arg...]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("--python", action="store_true", help=_("Use python syntax for values"))
|
| |
- parser.add_option("--kwargs", help=_("Specify keyword arguments as a dictionary (implies --python)"))
|
| |
- parser.add_option("--json-output", action="store_true", help=_("Use JSON syntax for output"))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- if len(args) < 1:
|
| |
- parser.error(_("Please specify the name of the XML-RPC method"))
|
| |
- assert False # pragma: no cover
|
| |
- if options.kwargs:
|
| |
- options.python = True
|
| |
- if options.python and ast is None:
|
| |
- parser.error(_("The ast module is required to read python syntax"))
|
| |
- if options.json_output and json is None:
|
| |
- parser.error(_("The json module is required to output JSON syntax"))
|
| |
- activate_session(session)
|
| |
- name = args[0]
|
| |
- non_kw = []
|
| |
- kw = {}
|
| |
- if options.python:
|
| |
- non_kw = [ast.literal_eval(a) for a in args[1:]]
|
| |
- if options.kwargs:
|
| |
- kw = ast.literal_eval(options.kwargs)
|
| |
- else:
|
| |
- for arg in args[1:]:
|
| |
- if arg.find('=') != -1:
|
| |
- key, value = arg.split('=', 1)
|
| |
- kw[key] = arg_filter(value)
|
| |
- else:
|
| |
- non_kw.append(arg_filter(arg))
|
| |
- response = getattr(session, name).__call__(*non_kw, **kw)
|
| |
- if options.json_output:
|
| |
- print(json.dumps(response, indent=2, separators=(',', ': ')))
|
| |
- else:
|
| |
- pprint.pprint(response)
|
| |
-
|
| |
- def anon_handle_mock_config(options, session, args):
|
| |
- "[info] Create a mock config"
|
| |
- usage = _("usage: %prog mock-config [options]")
|
| |
- usage += _("\n(Specify the --help global option for a list of other help options)")
|
| |
- parser = OptionParser(usage=usage)
|
| |
- parser.add_option("-a", "--arch", help=_("Specify the arch"))
|
| |
- parser.add_option("-n", "--name", help=_("Specify the name for the buildroot"))
|
| |
- parser.add_option("--tag", help=_("Create a mock config for a tag"))
|
| |
- parser.add_option("--target", help=_("Create a mock config for a build target"))
|
| |
- parser.add_option("--task", help=_("Duplicate the mock config of a previous task"))
|
| |
- parser.add_option("--latest", action="store_true", help=_("use the latest redirect url"))
|
| |
- parser.add_option("--buildroot", help=_("Duplicate the mock config for the specified buildroot id"))
|
| |
- parser.add_option("--mockdir", default="/var/lib/mock", metavar="DIR",
|
| |
- help=_("Specify mockdir"))
|
| |
- parser.add_option("--topdir", metavar="DIR",
|
| |
- help=_("Specify topdir"))
|
| |
- parser.add_option("--topurl", metavar="URL", default=options.topurl,
|
| |
- help=_("URL under which Koji files are accessible"))
|
| |
- parser.add_option("--distribution", default="Koji Testing",
|
| |
- help=_("Change the distribution macro"))
|
| |
- parser.add_option("--yum-proxy", help=_("Specify a yum proxy"))
|
| |
- parser.add_option("-o", metavar="FILE", dest="ofile", help=_("Output to a file"))
|
| |
- (options, args) = parser.parse_args(args)
|
| |
- activate_session(session)
|
| |
- if args:
|
| |
- #for historical reasons, we also accept buildroot name as first arg
|
| |
- if not options.name:
|
| |
- options.name = args[0]
|
| |
- else:
|
| |
- parser.error(_("Name already specified via option"))
|
| |
- arch = None
|
| |
- opts = {}
|
| |
- for k in ('topdir', 'topurl', 'distribution', 'mockdir', 'yum_proxy'):
|
| |
- if hasattr(options, k):
|
| |
- opts[k] = getattr(options, k)
|
| |
- if options.buildroot:
|
| |
- try:
|
| |
- br_id = int(options.buildroot)
|
| |
- except ValueError:
|
| |
- parser.error(_("Buildroot id must be an integer"))
|
| |
- brootinfo = session.getBuildroot(br_id)
|
| |
- if options.latest:
|
| |
- opts['repoid'] = 'latest'
|
| |
- else:
|
| |
- opts['repoid'] = brootinfo['repo_id']
|
| |
- opts['tag_name'] = brootinfo['tag_name']
|
| |
- arch = brootinfo['arch']
|
| |
- elif options.task:
|
| |
- try:
|
| |
- task_id = int(options.task)
|
| |
- except ValueError:
|
| |
- parser.error(_("Task id must be an integer"))
|
| |
- broots = session.listBuildroots(taskID=task_id)
|
| |
- if not broots:
|
| |
- print(_("No buildroots for task %s (or no such task)") % options.task)
|
| |
- return 1
|
| |
- if len(broots) > 1:
|
| |
- print(_("Multiple buildroots found: %s" % [br['id'] for br in broots]))
|
| |
- brootinfo = broots[-1]
|
| |
- if options.latest:
|
| |
- opts['repoid'] = 'latest'
|
| |
- else:
|
| |
- opts['repoid'] = brootinfo['repo_id']
|
| |
- opts['tag_name'] = brootinfo['tag_name']
|
| |
- arch = brootinfo['arch']
|
| |
- def_name = "%s-task_%i" % (opts['tag_name'], task_id)
|
| |
- elif options.tag:
|
| |
- if not options.arch:
|
| |
- print(_("Please specify an arch"))
|
| |
- return 1
|
| |
- tag = session.getTag(options.tag)
|
| |
- if not tag:
|
| |
- parser.error(_("Invalid tag: %s" % options.tag))
|
| |
- arch = options.arch
|
| |
- config = session.getBuildConfig(tag['id'])
|
| |
- if not config:
|
| |
- print(_("Could not get config info for tag: %(name)s") % tag)
|
| |
- return 1
|
| |
- opts['tag_name'] = tag['name']
|
| |
- if options.latest:
|
| |
- opts['repoid'] = 'latest'
|
| |
- else:
|
| |
- repo = session.getRepo(config['id'])
|
| |
- if not repo:
|
| |
- print(_("Could not get a repo for tag: %(name)s") % tag)
|
| |
- return 1
|
| |
- opts['repoid'] = repo['id']
|
| |
- def_name = "%(tag_name)s-repo_%(repoid)s" % opts
|
| |
- elif options.tar
|