#!/usr/bin/env python # -*- coding: utf-8 -*- import ConfigParser import logging import paramiko import optparse import sys import os LOG = logging.getLogger('rpmbuild-remote') LOG.addHandler(logging.StreamHandler()) EXTENSIONS = ( 'patch', 'bz2', 'gz', 'tgz', 'lzma', 'xz', ) def get_parser(): parser = optparse.OptionParser(usage="%prog [options]... ...") home = os.path.expanduser("~") parser.set_conflict_handler("resolve") parser.add_option("-c", "--config", action="store", type="string", dest="config", default=os.path.join(home, ".rpmbuild-remote"), help="Configuration file to use for the build. " "[Default: ~/.rpmbuild-remote") parser.add_option("-s", "--system", action="store", type="string", dest="system", help="The remote machine to use (defined in config file)") parser.add_option("-k", "--ssh-key", action="store", type="string", dest="ssh_key", help="The private SSH key to use to login.") parser.add_option("-u", "--user", action="store", type="string", dest="user", default=os.getlogin(), help="The username on the remote system [Default: current user]") parser.add_option("-h", "--host", action="store", type="string", dest="host", help="The hostname/IP of the remote system") parser.add_option("-p", "--port", action="store", type="int", dest="port", default=21, help="The port to use on the remote system [Default: 21]") parser.add_option("-d", "--dest", action="store", type="string", dest="dest", default=os.path.join(home, "rpmbuild-remote"), help="Directory to place resulting files " "[Default: ~/rpmbuild-remote/]") parser.add_option("-m", "--mock", action="store", type="string", dest="mock", help="Mock configuration to use (only applies to SRPM files) " "[Default: Use rpmbuild --rebuild)]") return parser def read_section(config, section, values): ret = {} if config.has_section(section): for value, typ in values: if config.has_option(section, value): if typ == 'int': method = config.getint elif typ == 'float': method = config.getfloat elif typ == 'boolean': method = config.getboolean elif typ == 'str': method = config.get else: method = config.get LOG.warning("Unknown type %s, assuming string" % typ) ret[value] = method(section, value) return ret def read_preferences(opts): values = ( ('user', 'str'), ('ssh_key', 'str'), ('host', 'str'), ('port', 'int'), ) config = ConfigParser.SafeConfigParser() config.read(opts.config) system = read_section(config, opts.system, values) if system == {}: system = read_section(config, 'default', values) if 'user' not in system: system['user'] = opts.user if 'ssh_key' not in system: system['ssh_key'] = opts.ssh_key if 'host' not in system: if opts.host is None: LOG.error("Host not set") sys.exit(1) system['host'] = opts.host if 'port' not in system: system['port'] = opts.port return system def send_files(session, files, dest): sftp = session.open_sftp() for file in files: LOG.info("Writing %s to server (%s)" % (file, dest)) sftp.put(file, dest) sftp.close() def get_files(session, files, dest): sftp = session.open_sftp() for file in files: LOG.info("Fetching %s from server" % file) sftp.get(file, dest) sftp.close() def log_regex(build_output): out = open(build_output, 'r') regex = re.compile("Wrote: (.+)") files = regex.findall(out.readall()) out.close() def get_mock_results(session, dest, sout, serr): os.mkdirs(dest) out = open(os.path.join(dest, 'mock.stdout'), 'w+') out.write(sout.readall()) out.close() err = open(os.path.join(dest, 'mock.stderr'), 'w+') err.write(serr.readall()) err.close() output_dir = re.compile(r'INFO: Results and/or logs in: (.+)').group(1) logs = [os.path.join(output_dir, x) for x in ['build.log', 'root.log', 'state.log']] get_files(session, logs, dest) get_files(os.path.join(dest, 'build.log'), dest) def get_rpmbuild_results(session, dest, sout, serr): os.mkdirs(dest) out = open(os.path.join(dest, 'rpmbuild.stdout', 'w+')) out.write(sout.readall()) out.close() err = open(os.path.join(dest, 'rpmbuild.stderr', 'w+')) err.write(serr.readall()) err.close() get_files(os.path.join(dest, 'rpmbuild.stdout'), dest) def execute_jobs(opts, system, jobs): session = paramiko.SSHClient() session.load_system_host_keys() LOG.info("Connecting to %(user)s@%(host)s:%(port)d" % system) session.connect(system['host'], port=system['port'], username=system['user'], key_filename=system['ssh_key']) for job in jobs: if type(job) == type(''): dest = "rpmbuild/SRPMS/" send_files(session, [job], dest) file = os.path.basename(job) if opts.mock is None: LOG.info("Building %s" % file) cmd = "rpmbuild --rebuild rpmbuild/SRPMS/%s" % file fetch = get_mock_results else: LOG.info("Building %s with mock config %s" % (file, opts.mock)) cmd = "mock -r %s rpmbuild/SRPMS/%s" % (opts.mock, file) fetch = get_rpmbuild_results else: dest = "rpmbuild/SPECS/" send_files(session, [job[0]], dest) dest = "rpmbuild/SOURCES/" send_files(session, job[1:], dest) file = os.path.basename(job[0]) LOG.info("Building %s from %d souces" % (file, len(job) - 1)) cmd = "rpmbuild -ba rpmbuild/SPECS/%s" % file fetch = get_rpmbuild_results start = time.clock() files = session.exec_command(cmd) finish = time.clock() LOG.info("Completed build %.03f seconds" % (finish - start)) LOG.info("Downloading results...") files[0].close() fetch(session, os.path.join(opts.dest, file), files[1], files[2]) files[1].close() files[2].close() session.close() def main(): parser = get_parser() opts, args = parser.parse_args() jobs = [] for arg in args: ext = os.path.splitext(arg)[1] if ext == '.srpm': jobs.append(arg) elif ext == '.spec': jobs.append([arg]) else: if ext not in EXTENSIONS: LOG.warning("Uncommon source extension %s for file %s. " "Please double check files." % (ext, arg)) if not len(jobs) or not type(jobs[-1]) == type([]): LOG.error("Source file given without spec file.") sys.exit(1) jobs[-1].append(arg) system = read_preferences(opts) execute_jobs(opts, system, jobs) if __name__ == '__main__': main()