diff --git a/vm/kojikamid b/vm/kojikamid index af2ae768..8ad6ee8e 100755 --- a/vm/kojikamid +++ b/vm/kojikamid @@ -177,6 +177,7 @@ class SCM(object): sourcedir = '%s/%s' % (scmdir, self.module) update_checkout_cmd = None + update_checkout_dir = None if self.scmtype == 'CVS': pserver = ':pserver:%s@%s:%s' % ((self.user or 'anonymous'), self.host, self.repository) @@ -203,6 +204,7 @@ class SCM(object): sourcedir = '%s/%s' % (scmdir, checkout_path) module_checkout_cmd = ['git', 'clone', '-n', gitrepo, sourcedir] update_checkout_cmd = ['git', 'reset', '--hard', self.revision] + update_checkout_dir = sourcedir # self.module may be empty, in which case the specfile should be in the top-level directory if self.module: @@ -222,6 +224,7 @@ class SCM(object): sourcedir = '%s/%s' % (scmdir, checkout_path) module_checkout_cmd = ['git', 'clone', '-n', gitrepo, sourcedir] update_checkout_cmd = ['git', 'reset', '--hard', self.revision] + update_checkout_dir = sourcedir # self.module may be empty, in which case the specfile should be in the top-level directory if self.module: @@ -256,7 +259,7 @@ class SCM(object): if update_checkout_cmd: # Currently only required for GIT checkouts # Run the command in the directory the source was checked out into - ret, output = run(update_checkout_cmd, chdir=sourcedir) + ret, output = run(update_checkout_cmd, chdir=update_checkout_dir) log(output) if ret: raise BuildError, 'Error running %s update command "%s": %s' % \ @@ -332,18 +335,21 @@ class WindowsBuild(object): def log(msg): print >> sys.stderr, '%s: %s' % (datetime.datetime.now().ctime(), msg) -def run(cmd, chdir='.'): +def run(cmd, chdir=None): shell = False if isinstance(cmd, (str, unicode)) and len(cmd.split()) > 1: shell = True - olddir = os.getcwd() - os.chdir(chdir) + olddir = None + if chdir: + olddir = os.getcwd() + os.chdir(chdir) log('running command: %s' % cmd) proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True, shell=shell) ret = proc.wait() output = proc.stdout.read() - os.chdir(olddir) + if olddir: + os.chdir(olddir) return ret, output def find_net_info(): @@ -422,38 +428,50 @@ def get_options(): parser = OptionParser(usage=usage) parser.add_option('-i', '--install', action='store_true', help='Install this daemon as a service', default=False) parser.add_option('-u', '--uninstall', action='store_true', help='Uninstall this daemon if it was installed previously as a service', default=False) - parser.add_option('-s', '--scmurl', action='append', help='Forcibly specify an scmurl to checkout from', default=[]) + parser.add_option('-s', '--scmurl', help='Forcibly specify an scmurl to checkout from') (options, args) = parser.parse_args() return options -def run_build(workdir, scmurls): +def apply_patches(src_dir, patch_dir): + patches = [patch for patch in os.listdir(patch_dir) if \ + os.path.isfile(os.path.join(patch_dir, patch)) and \ + not patch.startswith('.')] + if not patches: + raise BuildError, 'no patches found at %s' % patch_dir + patches.sort() + for patch in patches: + cmd = ['/usr/bin/patch', '--verbose', '-d', src_dir, '-p1', '-i', os.path.join(patch_dir, patch)] + ret, output = run(cmd) + if ret: + raise BuildError, 'error applying patches, output was: %s' % output + +def run_build(workdir, source_url, task_opts): """run the build""" - if len(scmurls) == 2: - # should SCM.assert_allowed() be called on SCM objs? - specurl, srcurl = scmurls - spec_scm = SCM(specurl) - spec_dir = spec_scm.checkout(workdir) - src_scm = SCM(srcurl) - src_dir = src_scm.checkout(workdir) - elif len(scmurls) == 1: - specurl = scmurls[0] - spec_scm = SCM(specurl) - spec_dir = spec_scm.checkout(workdir) - src_scm, src_dir = spec_scm, spec_dir + src_scm = SCM(source_url) + src_dir = src_scm.checkout(os.path.join(workdir, 'source')) + if 'specfile' in task_opts: + spec_scm = SCM(task_opts['specfile']) + spec_dir = spec_scm.checkout(os.path.join(workdir, 'spec')) else: - raise BuildError, 'Unexpected number of SCM URLs given: %s' % scmurls + spec_dir = src_dir + if 'patches' in task_opts: + patch_scm = SCM(task_opts['patches']) + patch_dir = patch_scm.checkout(os.path.join(workdir, 'patches')) + apply_patches(src_dir, patch_dir) specfile = [spec for spec in os.listdir(spec_dir) if spec.endswith('.ini')] - if len(specfile) != 1: - raise BuildError, 'Exactly one .ini file must be found in a check out' + if len(specfile) == 0: + raise BuildError, 'No .ini file found' + elif len(specfile) > 1: + raise BuildError, 'Multiple .ini files found' winbld = WindowsBuild(os.path.join(spec_dir, specfile[0]), src_dir) return winbld.doAll(), src_dir -def flunk(server_report, server=None): +def flunk(server): """do the right thing when a build fails""" exc_info = sys.exc_info() tb = ''.join(traceback.format_exception(*exc_info)) - if server_report: + if server is not None: server.failTask(tb) log(tb) sys.exit(1) @@ -481,20 +499,27 @@ if __name__ == '__main__': else: print 'Successfully removed the %s service' % prog sys.exit(0) - use_server, server = False, None + server = None try: - if len(opts.scmurl) == 0: - use_server = True + source_url = None + task_opts = None + if opts.scmurl: + source_url = opts.scmurl + else: server = get_mgmt_server() - scmurl = server.getTaskInfo() - opts.scmurl.append(scmurl) + info = server.getTaskInfo() + source_url = info[0] + if len(info) > 1: + task_opts = info[1] + if not task_opts: + task_opts = {} workdir = '/tmp/workdir' os.mkdir(workdir) - results, results_dir = run_build(workdir, opts.scmurl) - if use_server: + results, results_dir = run_build(workdir, source_url, task_opts) + if server is not None: upload_results(server, results_dir, results) server.closeTask(results) log('Build results: %s' % results) except: - flunk(use_server, server=server) + flunk(server) sys.exit(0) diff --git a/vm/kojivmd b/vm/kojivmd index a3ecad1f..d79cc4e2 100755 --- a/vm/kojivmd +++ b/vm/kojivmd @@ -22,7 +22,7 @@ import koji import koji.util -from koji.daemon import TaskManager +from koji.daemon import SCM, TaskManager from koji.tasks import ServerExit, BaseTaskHandler import sys import logging @@ -125,6 +125,7 @@ def get_options(): 'max_retries': 120, 'offline_retry': True, 'offline_retry_interval': 120, + 'allowed_scms': '', 'cert': '/etc/kojivmd/client.crt', 'ca': '/etc/kojivmd/clientca.crt', 'serverca': '/etc/kojivmd/serverca.crt'} @@ -426,7 +427,7 @@ class VMTask(BaseTaskHandler): thr.setDaemon(True) thr.start() - def handler(self, name, task_info, opts=None): + def handler(self, name, source_url, opts=None): """ Clone the VM named "name", and provide the data in "task_info" to it. Available options: @@ -437,7 +438,14 @@ class VMTask(BaseTaskHandler): opts = {} timeout = opts.get('timeout', 720) - self.task_info = task_info + self.task_info = [source_url, koji.util.dslice(opts, ['specfile', 'patches'], + strict=False)] + # opts may contain specfile and patches entries, both of which are + # urls. + # Verify the urls before passing them to the VM. + for url in [source_url] + self.task_info[1].values(): + scm = SCM(url) + scm.assert_allowed(self.options.allowed_scms) conn = libvirt.open(None) clone_name = self.clone(conn, name) diff --git a/vm/kojivmd.conf b/vm/kojivmd.conf index 09101ab9..d4b1a4a8 100644 --- a/vm/kojivmd.conf +++ b/vm/kojivmd.conf @@ -17,6 +17,13 @@ ; The URL for the xmlrpc server server=http://hub.example.com/kojihub +; A space-separated list of hostname:repository[:use_common] tuples that kojivmd is authorized to checkout from (no quotes). +; Wildcards (as supported by fnmatch) are allowed. +; If use_common is specified and is one of "false", "no", "off", or "0" (without quotes), then kojid will not attempt to checkout +; a common/ dir when checking out sources from the source control system. Otherwise, it will attempt to checkout a common/ +; dir, and will raise an exception if it cannot. +allowed_scms=scm.example.com:/cvs/example git.example.org:/example svn.example.org:/users/*:no + ; The mail host to use for sending email notifications smtphost=example.com diff --git a/vm/run-vm-task b/vm/run-vm-task index 44694462..136f5728 100755 --- a/vm/run-vm-task +++ b/vm/run-vm-task @@ -15,14 +15,16 @@ parser.add_option('--cpus', help='Number of virtual CPUs to allocate to the VM ( parser.add_option('--mem', help='Amount of memory (in megabytes) to allocate to the VM (optional)', type='int') parser.add_option('--channel', help='Channel to create the task in', default='vm') +parser.add_option('--specfile', help='Alternate SCM URL of the specfile') +parser.add_option('--patches', help='SCM URL of patches to apply before build') opts, args = parser.parse_args() if len(args) < 2: - parser.error('You must specify a VM name and a command to run') + parser.error('You must specify a VM name and SCM URL') vm_name = args[0] -cmd = ' '.join(args[1:]) +scm_url = args[1] session = koji.ClientSession(opts.server) session.ssl_login(opts.cert, opts.ca, opts.server_ca) @@ -32,8 +34,12 @@ if opts.cpus: task_opts['cpus'] = opts.cpus if opts.mem: task_opts['mem'] = opts.mem +if opts.specfile: + task_opts['specfile'] = opts.specfile +if opts.patches: + task_opts['patches'] = opts.patches -params = [vm_name, [cmd], task_opts] +params = [vm_name, scm_url, task_opts] task_id = session.makeTask('vmExec', params, channel=opts.channel)