David Caro has uploaded a new change for review. Change subject: Added script to manage repositories ......................................................................
Added script to manage repositories Quite big script, here's what it can do right now: * Remove duplicated rpms and create hard links instead * Sign packages in batch mode (passing passphrase as parameter) * Create/update rpm repos using multiple sources, passed as arguments or in a configuration file: · From jenkins url (multijob or simple job) · From koji url · From a url with rpm links on it, recursively or not · From a directory, all packages, optionally filtering · From a directory only the latest packages, optionally filtering * Remove the old rpm versions and keep only the N newest * Run createrepo in a multidistro repository (runs on each distribution subrepo) * Generate the sources tree from the source rpms: · Including the patches or not · Generating detached signatures or not I tried to keep the output informative but human readable, but mostly to be run on jenkins jobs. Change-Id: Ib630eb1e3b701eaf224451f02692723447aa1f8b Signed-off-by: David Caro <dcaro...@redhat.com> --- A repoman.py 1 file changed, 983 insertions(+), 0 deletions(-) git pull ssh://gerrit.ovirt.org:29418/jenkins refs/changes/99/33299/1 diff --git a/repoman.py b/repoman.py new file mode 100755 index 0000000..5aa0fa2 --- /dev/null +++ b/repoman.py @@ -0,0 +1,983 @@ +#!/usr/bin/env python +""" +This program is a helper to rpm repo management, some common tasks +""" +import os +import sys +import shutil +import rpm +import hashlib +import argparse +import logging +import subprocess +import re +import requests +import gnupg +import tempfile +import atexit +import pexpect +from urllib3 import connectionpool +from getpass import getpass + +DISTROS = ( + re.compile(r'\.fc\d+'), + re.compile(r'\.el\d+'), +) + +BASE_REPOS_PATH = '/var/www/html/pub' +# To be set on main, if set at all +TEMP_DIR = None + + +class NotSamePackage(Exception): + """Thrown when trying to compare different packages""" + pass + + +def cleanup(temp_dir): + if os.path.isdir(temp_dir): + shutil.rmtree(temp_dir) + logging.info('Cleaning up temporary dir %s', temp_dir) + + +def tryint(mayint): + try: + return int(mayint) + except ValueError: + return mayint + + +def cmpver(ver1, ver2): + ver1 = '.' in ver1 and ver1.split('.') or (ver1,) + ver2 = '.' in ver2 and ver2.split('.') or (ver2,) + ver1 = [tryint(i) for i in ver1] + ver2 = [tryint(i) for i in ver2] + if ver1 > ver2: + return -1 + if ver1 == ver2: + return 0 + else: + return 1 + + +def cmpfullver(fullver1, fullver2): + ver1, rel1 = fullver1.split('-', 1) + ver2, rel2 = fullver2.split('-', 1) + ver_res = cmpver(ver1, ver2) + if ver_res != 0: + return ver_res + return cmpver(rel1, rel2) + + +def print_busy(prev_pos=0): + sys.stdout.write('\r') + if prev_pos == 0: + sys.stdout.write('-') + elif prev_pos == 1: + sys.stdout.write('/') + elif prev_pos == 2: + sys.stdout.write('|') + else: + sys.stdout.write('\\') + sys.stdout.flush() + return (prev_pos + 1) % 4 + + +def get_distro(release): + for distro in DISTROS: + match = distro.search(release) + if match: + return match.group()[1:] + raise Exception('Unknown distro for %s' % release) + + +def to_human_size(fsize): + mb = fsize/(1024*1024) + if mb >= 1: + return '%dM' % mb + kb = fsize / 1024 + if kb >= 1: + return '%dK' % kb + return '%dB' % fsize + + +def download(path, dest_path): + headers = requests.head(path) + chunk_size = 4096 + length = int(headers.headers.get('content-length', 0)) or 0 + logging.info('Downloading %s, length %s ...', + path, + length and to_human_size(length) or 'unknown') + num_dots = 100 + dot_frec = length/num_dots or 1 + stream = requests.get(path, stream=True) + prev_percent = 0 + progress = 0 + if length: + sys.stdout.write(' %[') + sys.stdout.flush() + with open(dest_path, 'w') as rpm_fd: + for chunk in stream.iter_content(chunk_size=chunk_size): + if chunk: + rpm_fd.write(chunk) + progress += len(chunk) + cur_percent = int(progress / dot_frec) + if length and cur_percent > prev_percent: + for _ in xrange(cur_percent - prev_percent): + sys.stdout.write('=') + sys.stdout.flush() + prev_percent = cur_percent + elif not length: + prev_percent = print_busy(prev_percent) + if length: + if cur_percent < num_dots: + sys.stdout.write('=') + sys.stdout.write(']') + sys.stdout.write('\n') + if not length: + logging.info(' Done') + + +class RPM(object): + def __init__(self, path): + trans = rpm.TransactionSet() + # Ignore unsigned rpms + trans.setVSFlags(rpm._RPMVSF_NOSIGNATURES) + if path.startswith('http:') or path.startswith('https:'): + name = path.rsplit('/', 1)[-1] + if not name: + raise Exception('Passed trailing slash in path %s, ' + 'unable to guess package name' + % path) + fpath = TEMP_DIR + '/' + name + download(path, fpath) + path = fpath + self.path = path + with open(path) as fdno: + try: + hdr = trans.hdrFromFdno(fdno) + except Exception as exc: + print "Failed to parse header for %s" % path + raise + self.inode = os.fstat(fdno.fileno()).st_ino + self.is_source = hdr[rpm.RPMTAG_SOURCEPACKAGE] and True or False + self.sourcerpm = hdr[rpm.RPMTAG_SOURCERPM] + self.name = hdr[rpm.RPMTAG_NAME] + self.version = hdr[rpm.RPMTAG_VERSION] + self.release = hdr[rpm.RPMTAG_RELEASE] + self.signature = hdr[rpm.RPMTAG_SIGPGP] + self._raw_hdr = hdr + # will be calculated if needed + self._md5 = None + # ovirt release rnd guest tools pms must go to all the distros + if self.name.startswith('ovirt-release') \ + or self.name.startswith('ovirt-guest-tools'): + self.distro = 'all' + else: + self.distro = get_distro(self.release) + self.arch = hdr[rpm.RPMTAG_ARCH] or 'none' + self.full_name = 'rpm(%s %s %s %s)' % ( + self.name, self.distro, self.arch, + self.is_source and 'src' or 'bin', + ) + # remove the distro from the release for the version string + if self.distro: + self.ver_release = re.sub( + r'\.%s[^.]*' % self.distro, + '', + self.release, + 1 + ) + else: + self.ver_release = self.release + self.full_version = '%s-%s' % (self.version, self.ver_release) + + @property + def md5(self): + if self._md5 is None: + with open(self.path) as fdno: + self._md5 = hashlib.md5(fdno.read()).hexdigest() + return self._md5 + + def generate_path(self, distro=None): + if distro is None: + distro = self.distro + if self.is_source: + arch_path = 'SRPMS' + arch_name = 'src' + else: + arch_path = self.arch + arch_name = self.arch + return 'rpm/%s/%s/%s-%s-%s.%s.rpm' % ( + distro, + arch_path, + self.name, + self.version, + self.release, + arch_name, + ) + + def sign(self, keyuid, passwd): + logging.info("SIGNING: %s", self.path) + child = pexpect.spawn( + 'rpmsign', + [ + '--resign', + '-D', '_signature gpg', + '-D', '_gpg_name %s' % keyuid, + self.path, + ], + ) + child.expect('Enter pass phrase: ') + child.sendline(passwd) + child.expect(pexpect.EOF) + child.close() + if child.exitstatus != 0: + raise Exception("Failed to sign package.") + self.__init__(self.path) + if not self.signature: + raise Exception("Failed to sign rpm %s with key '%s'" + % (self.path, keyuid)) + + def __str__(self): + return 'rpm(%s %s %s %s %s %s)' % ( + self.name, self.version, + self.release, self.arch, + self.is_source and 'src' or 'bin', + self.signature and 'signed' or 'unsigned', + ) + + def __repr__(self): + return self.__str__() + + +class RPMinode(list): + """ + Simple list, abstracts a set of rpm inodes referencing the same + name-version + """ + def __init__(self, inode): + self.inode = inode + super(RPMinode, self).__init__(self) + + def delete(self, noop=False): + for pkg in self: + if noop: + os.remove(pkg.path) + else: + logging.info('NOOP::%s would have been removed', pkg.path) + + def get_rpms(self, regmatch=None, fmatch=None): + pkgs = self + if regmatch: + pkgs = [pkg for pkg in self if regmatch.search(pkg.path)] + if fmatch: + pkgs = [pkg for pkg in pkgs if fmatch(pkg)] + return pkgs + + +class RPMEntry(dict): + """Abstracsts a set of rpm inodes for a version""" + def __init__(self, version): + self.version = version + super(RPMEntry, self).__init__(self) + + def add_pkg(self, pkg): + if pkg.inode not in self: + self[pkg.inode] = RPMinode(pkg.inode) + self[pkg.inode].append(pkg) + return True + + def del_inode(self, inode, noop=False): + if inode in self: + self[inode].delete(noop) + self.pop(inode) + + def get_rpms(self, regmatch=None, fmatch=None): + pkgs = [] + for inode in self.itervalues(): + pkgs.extend(inode.get_rpms(regmatch=regmatch, + fmatch=fmatch)) + return pkgs + + +class RPMVersionList(dict): + """List of available versions for a package name""" + def add_pkg(self, pkg, onlyifnewer): + if onlyifnewer and ( + pkg.full_version in self + or next((ver for ver in self.keys() + if cmpfullver(ver, pkg.full_version) >= 0), None)): + return False + elif pkg.full_version not in self: + self[pkg.full_version] = RPMEntry(pkg.full_version) + return self[pkg.full_version].add_pkg(pkg) + + def get_latest(self, num=1): + """ + Returns the list of available inodes for the latest version + if any + """ + if not self: + return None + if not num: + num = len(self) + sorted_list = self.keys() + sorted_list.sort(cmp=cmpfullver) + latest = {} + for pos in xrange(num): + latest[sorted_list[pos]] = self.get(sorted_list[pos]) + return latest + + def del_version(self, version, noop=False): + if version in self: + for inode in self[version].keys(): + self[version].del_inode(inode, noop=noop) + self.pop(version) + + def get_rpms(self, regmatch=None, fmatch=None, latest=0): + pkgs = [] + for entry in self.get_latest(latest).itervalues(): + pkgs.extend(entry.get_rpms(regmatch=regmatch, + fmatch=fmatch)) + return pkgs + + +class RPMList(dict): + """List of rpms, separated by name + The shape of the rpms list is: + { + name1:{ + version1:{ + inode1: [rpm_object1, ...], + inode2: [...], + }, + version2:{...} + }, + name2: {...} + } + + Where the top level is abstracted by RPMList(dict) + The second level by RPMVersionList(dict) + The third by RPMEntry(dict) + The third by RPMinode(list) + And the last level by RPM + + So: + + RPMList{ + name1: RPMVersionList{ + version1: RPMEntry{ + inode1: RPMinode[RPM, RPM, ...] + inode2: RPMinode[...] + }, + version2: RPMEntry{...} + }, + name2: RPMVersionList{...} + } + """ + def add_pkg(self, pkg, onlyifnewer=False): + if pkg.full_name not in self: + self[pkg.full_name] = RPMVersionList() + return self[pkg.full_name].add_pkg(pkg, onlyifnewer) + + def del_pkg(self, name): + if name in self: + for version in self[name].keys(): + self[name].del_version(version) + self.pop(name) + + def get_rpms(self, regmatch=None, fmatch=None, latest=0): + rpms = [] + for version in self.itervalues(): + rpms.extend(version.get_rpms( + regmatch=regmatch, + fmatch=fmatch, + latest=latest)) + return rpms + + +class RPMRepo(object): + """Represents the repository sctructure, does not include metadata""" + def __init__(self, path): + self.rpms = RPMList() + self.path = path + self.to_copy = [] + self.distros = set() + if path: + logging.info('Loading repo %s', path) + for pkg in list_rpms(path): + self.add_pkg(RPM(pkg), to_copy=False, hidelog=True) + logging.info('Repo %s loaded', path) + + def add_pkg(self, pkg, onlyifnewer=False, to_copy=True, hidelog=False): + if self.rpms.add_pkg(pkg, onlyifnewer): + if to_copy: + self.to_copy.append(pkg) + if not hidelog: + logging.info('Adding package %s to repo %s', pkg.path, + self.path) + else: + if not hidelog: + logging.info("Not adding %s, there's already an equal or " + "newer version", pkg) + if pkg.distro != 'all': + self.distros.add(pkg.distro) + + def save(self, onlylatest=False): + logging.info('Saving new added rpms into %s', self.path) + for pkg in self.to_copy: + if onlylatest and not self.is_latest_version(pkg): + logging.info('Skipping %s a newer version is already ' + 'in the repo.', pkg) + continue + if pkg.distro == 'all': + if not self.distros: + raise Exception('No distros found in the repo and no ' + 'packages with any distros added.') + dst_distros = self.distros + else: + dst_distros = [pkg.distro] + for distro in dst_distros: + dst_path = self.path + '/' + pkg.generate_path(distro) + pkg_copy(pkg.path, dst_path) + logging.info('Saved %s\n', self.path) + + def is_latest_version(self, pkg): + verlist = self.rpms.get(pkg.full_name, {}) + if not verlist or pkg.full_version in verlist.get_latest(): + return True + return False + + def generate_sources(self, with_patches=False, key=None, passphrase=None): + logging.info("Generating src directory from srpms") + for name, versions in self.rpms.iteritems(): + if not name.endswith('src)'): + continue + for version in versions.itervalues(): + pkg = version.popitem()[1][0] + logging.info("Parsing srpm %s", pkg) + dst_dir = '%s/src/%s' % (self.path, pkg.name) + extract_sources(pkg.path, dst_dir, with_patches) + if key: + sign_sources(dst_dir, key, passphrase) + logging.info('src dir generated') + + def createrepo(self): + for distro in self.distros: + logging.info('Creating metadata for %s', distro) + subprocess.call(['createrepo', self.path + '/rpm/' + distro]) + + def delete_old(self, keep=1, noop=False): + new_rpms = RPMList(self.rpms) + for name, versions in self.rpms.iteritems(): + if len(versions) <= keep: + continue + to_keep = RPMVersionList() + for _ in range(keep): + latest = versions.get_latest() + to_keep.update(latest) + versions.pop(latest.keys()[0]) + new_rpms[name] = to_keep + for version in versions.keys(): + logging.info('Deleting %s version %s', name, version) + versions.del_version(version, noop) + self.rpms = new_rpms + + def get_rpms(self, regmatch=None, fmatch=None, latest=0): + return self.rpms.get_rpms( + regmatch=regmatch, + fmatch=fmatch, + latest=latest) + + def sign_rpms(self, key, passwd): + gpg = gnupg.GPG(gnupghome=os.path.expanduser('~/.gnupg')) + with open(key) as key_fd: + skey = gpg.import_keys(key_fd.read()) + fprint = skey.results[0]['fingerprint'] + keyuid = None + for key in gpg.list_keys(True): + if key['fingerprint'] == fprint: + keyuid = key['uids'][0] + for pkg in self.get_rpms( + fmatch=lambda pkg: not pkg.signature): + pkg.sign(keyuid, passwd) + logging.info("Done signing") + + +def copy(what, where): + """Try to link, try to copy if cross-device""" + try: + os.link(what, where) + except OSError as oerror: + if oerror.errno == 18: + shutil.copy2(what, where) + else: + raise + + +def extract_sources(rpm_path, dst_dir, with_patches=False): + if not os.path.isdir(dst_dir): + os.makedirs(dst_dir) + oldpath = os.getcwd() + if not dst_dir.startswith('/'): + dst_dir = oldpath + '/' + dst_dir + if not rpm_path.startswith('/'): + rpm_path = oldpath + '/' + rpm_path + dst_path = dst_dir + '/' + rpm_path.rsplit('/', 1)[-1] + copy(rpm_path, dst_path) + os.chdir(dst_dir) + try: + rpm2cpio = subprocess.Popen(['rpm2cpio', dst_path], + stdout=subprocess.PIPE) + cpio_cmd = ['cpio', '-iv', '*gz', '*.zip', '*.7z'] + if with_patches: + cpio_cmd.append('*.patch') + with open(os.devnull, 'w') as devnull: + cpio = subprocess.Popen( + cpio_cmd, + stdin=rpm2cpio.stdout, + stdout=devnull, + stderr=devnull, + ) + rpm2cpio.stdout.close() + cpio.communicate() + finally: + os.chdir(oldpath) + os.remove(dst_path) + + +def sign_sources(src_dir, key, passphrase=None): + oldpath = os.getcwd() + if not src_dir.startswith('/'): + src_dir = oldpath + '/' + src_dir + gpg = gnupg.GPG(gnupghome=os.path.expanduser('~/.gnupg')) + with open(key) as key_fd: + skey = gpg.import_keys(key_fd.read()) + fprint = skey.results[0]['fingerprint'] + keyid = None + for key in gpg.list_keys(True): + if key['fingerprint'] == fprint: + keyid = key['keyid'] + for dname, _, files in os.walk(src_dir): + for fname in files: + if fname.endswith('.sig'): + continue + fname = os.path.join(dname, fname) + with open(fname) as fd: + signature = gpg.sign_file( + fd, + keyid=keyid, + passphrase=passphrase, + detach=True, + ) + if not signature.data: + raise Exception("Failed to sign file %s: \n%s", + file, signature.stderr) + with open(fname + '.sig', 'w') as sfd: + sfd.write(signature.data) + + +def pkg_copy(src_path, dst_path): + if os.path.exists(dst_path): + logging.debug('Not saving %s, already exists', dst_path) + return + logging.info('Saving package %s', dst_path) + if not os.path.exists(dst_path.rsplit('/', 1)[0]): + os.makedirs(dst_path.rsplit('/', 1)[0]) + copy(src_path, dst_path) + + +def list_rpms(path): + '''Find all the rpms under the given dir''' + rpms = [] + for root, _, files in os.walk(path): + for fname in files: + if fname.endswith('.rpm'): + rpms.append(root + '/' + fname) + return rpms + + +def rm_dups(path, noop=False): + '''Remove all the duplicated rpms from a directory tree, favoring + the signed ones''' + rpms = RPMList() + for rpm_path in list_rpms(path): + rpms.add_pkg(RPM(rpm_path)) + # create a backup just in case + if noop: + logging.info('Not creating backup, as nothing will be changed (NOOP)') + else: + logging.info('Creating backup at %s, delete if happy with the ' + 'results', path + '.bkp') + subprocess.call(['cp', '-la', path, path + '.bkp']) + for pkg_name, versions in rpms.iteritems(): + for version, inodes in versions.iteritems(): + # skip if no duplicates + if len(inodes) <= 1: + logging.debug('No dupes for %s.%s', pkg_name, version) + continue + logging.info('Dupes found for %s.%s!', pkg_name, version) + # get a base package, with signature if able + base_inode = None + for inode, pkg_files in inodes.iteritems(): + if pkg_files and pkg_files[0].signature is not None: + base_inode = inode + break + if base_inode is not None: + base = inodes.pop(base_inode)[0] + else: + # we do not have any signed copy for that package + base = inodes.popitem()[1][0] + # Now replace all the other with hard links to the base + logging.info(' Using %s as base', base) + for inode in inodes.itervalues(): + for pkg in inode: + logging.info(' Linking %s -> %s', pkg.path, base.path) + if pkg.md5 != base.md5: + logging.warn(' MD5 checksums do not match ' + '(probably mixed signed and unsigned ' + 'copies)') + else: + logging.debug(' MD5 checksums match, ok') + if noop: + logging.info(' NOOP, not doing anything') + continue + os.remove(pkg.path) + copy(base.path, pkg.path) + logging.info('DONE') + + +def read_conf(conf_file): + with open(conf_file) as conf_fd: + for line in conf_fd: + line = line.strip() + if not line or line.startswith('#'): + continue + yield line + + +def expand_koji(koji_lvl1_url): + pkg_list = [] + url_base = koji_lvl1_url.rsplit('/', 1)[0] + lvl1_page = requests.get(koji_lvl1_url).text + lvl2_reg = re.compile(r'(?<=href=")[^"]+(?=.*(buildArch|buildSRPM))') + logging.info('Parsing Koji URL: %s', koji_lvl1_url) + lvl2_urls = [ + get_link(koji_lvl1_url, match.group()) + for match in (lvl2_reg.search(i) for i in lvl1_page.splitlines()) + if match + ] + for url in lvl2_urls: + logging.info(' Got 2nd level URL: %s', url) + pkg_list.extend(expand_page(url)) + if not pkg_list: + logging.warn(' No packages found') + logging.info(' Done') + return pkg_list + + +def expand_jenkins(jenkins_lvl1_url): + pkg_list = [] + lvl1_page = requests.get(jenkins_lvl1_url + '/api/json?depth=3').json() + url = lvl1_page['url'] + logging.info('Parsing jenkins URL: %s', jenkins_lvl1_url) + if url.endswith('/'): + url = url[:-1] + # handle multicongif jobs + for run in lvl1_page.get('runs', (lvl1_page,)): + if run['number'] != lvl1_page['number']: + continue + for artifact in run['artifacts']: + if not artifact['relativePath'].endswith('.rpm'): + continue + new_url = '%s/artifact/%s' % (run['url'], artifact['relativePath']) + pkg_list.append(new_url) + logging.info(' Got URL: %s', new_url) + if not pkg_list: + logging.warn(' No packages found') + logging.info(' Done') + return pkg_list + + +def expand_page(page_url): + logging.info('Parsing URL: %s', page_url) + data = requests.get(page_url).text + pkg_reg = re.compile(r'(?<=href=")[^"]+\.rpm') + pkg_list = [ + get_link(page_url, match.group()) + for match in (pkg_reg.search(i) for i in data.splitlines()) + if match + ] + for pkg_url in pkg_list: + logging.info(' Got package URL: %s', pkg_url) + return pkg_list + + +def get_link(page_url, link, internal=False): + page_url = page_url.rsplit('?', 1)[0] + if page_url.endswith('/'): + page_url = page_url[:-1] + if link.startswith(page_url): + return link + elif link.startswith('/'): + base_url = '/'.join(page_url.split('/', 3)[:-1]) + return base_url + link + elif re.match('https?://', link): + if internal: + return False + else: + return link + else: + return page_url + '/' + link + + +def expand_recursive(page_url, level=0): + if level > 0: + logging.debug('Recursively fetching URL (level %d): %s', + level, page_url) + else: + logging.info('Recursively fetching URL (level %d): %s', + level, page_url) + pkg_list = [] + data = requests.get(page_url).text + url_reg = re.compile( + r'(?<=href=")(/|%s|(?![^:]+?://))[^"]+/(?=")' % page_url) + next_urls = ( + get_link(page_url, match.group(), internal=True) + for match in url_reg.finditer(data) + if match and (match.group().startswith(page_url) + or not match.group().startswith('http')) + and match.group() != page_url + ) + for next_url in next_urls: + pkg_list.extend(expand_recursive(next_url, level + 1)) + pkg_list.extend(expand_page(page_url)) + return pkg_list + + +def expand_pkg_url(pkg_url): + pkg_list = [] + if 'koji' in pkg_url: + pkg_list.extend(expand_koji(pkg_url)) + elif 'jenkins' in pkg_url: + pkg_list.extend(expand_jenkins(pkg_url)) + elif not pkg_url.endswith('.rpm'): + pkg_list.extend(expand_page(pkg_url)) + else: + pkg_list.append(pkg_url) + return pkg_list + + +def expand_dir(pkg_dir, latest=0, name_reg='.*'): + if not pkg_dir.startswith('/'): + pkg_dir += '/' + BASE_REPOS_PATH + repo = RPMRepo(pkg_dir) + match = re.compile(name_reg) + return [pkg.path + for pkg in repo.get_rpms( + regmatch=match, + latest=latest) + ] + + +def expand_source(pkg_src): + """Return a list of direct urls and absolute paths to the rpms + from the given rpm source""" + pkg_list = set() + if pkg_src.startswith('conf:'): + for conf_line in read_conf(pkg_src.split(':', 1)[1]): + # TODO: avoid conf loops + pkg_list = pkg_list.union(expand_source(conf_line)) + elif pkg_src.startswith('pub:'): + for job in get_publisher_jobs(pkg_src): + pkg_list = pkg_list.union(expand_source(job)) + elif pkg_src.startswith('rec:'): + pkg_list = pkg_list.union(expand_recursive(pkg_src.split(':', 1)[1])) + elif pkg_src.startswith('http'): + pkg_list = pkg_list.union(expand_pkg_url(pkg_src)) + elif pkg_src.startswith('dir:'): + _, pkg_src = pkg_src.split(':', 1) + if ':' in pkg_src: + dirname, filter = pkg_src.split(':', 1) + else: + dirname, filter = pkg_src, '.*' + pkg_list = pkg_list.union(expand_dir( + pkg_dir=dirname, + name_reg=filter)) + elif pkg_src.startswith('latest:'): + _, pkg_src = pkg_src.split(':', 1) + if ':' in pkg_src: + dirname, filter = pkg_src.split(':', 1) + else: + dirname, filter = pkg_src, '.*' + pkgs = set(expand_dir( + pkg_dir=dirname, + name_reg=filter, + latest=True)) + for pkg in pkgs: + logging.info(" Adding rpm %s" % pkg) + pkg_list = pkg_list.union(pkgs) + elif ':' in pkg_src: + repo_name, pkg_name = pkg_src.split(':', 1) + pkg_list = pkg_list.union(expand_dir( + repo_name, + name_reg=pkg_name)) + else: + logging.error('Unknown source %s', pkg_src) + return 1 + return pkg_list + + +def get_publisher_jobs(pub_url): + console_log = requests.get(pub_url + '/consoleText').text + entry_reg = re.compile(r'Copied.*(?<=")\w+[^"]*".*number [0-9]+') + pub_jobs = [ + match.group() for match + in (entry_reg.search(i) for i in console_log.splitlines()) + if match + ] + return pub_jobs + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument('-v', '--verbose', action='store_true') + parser.add_argument('-n', '--noop', action='store_true') + subparsers = parser.add_subparsers(dest='action') + + par_rm_dups = subparsers.add_parser('rm-dups', help='Remove duplicated ' + 'packages and create hard links.') + par_rm_dups.add_argument('-d', '--dir', required=True, + help='Directory to add the package to.') + + par_repo = subparsers.add_parser('repo', help='Repository management.') + par_repo.add_argument('-d', '--dir', required=True, + help='Directory of the repo.') + repo_subparser = par_repo.add_subparsers(dest='repoaction') + add_rpm = repo_subparser.add_parser('add', help='Add a package') + add_rpm.add_argument('-t', '--temp-dir', action='store', default=None, + help='Temporary dir to use when downloading packages') + add_rpm.add_argument( + 'rpm_source', nargs='+', + help='An rpm source to add, it can be one of: ' + 'a path to a rpm/tarball, ' + 'a path to another dir prepended with "dir:" or "latest:"' + ' with an optional regexp filter, like dir:path[:regexp], ' + 'an http(s) url to an rpm/tarball, ' + 'an http(s) url to a page containing rpms/tarballs, ' + 'a link to a jenkins job, ' + 'a link to a koji job, ' + 'a link to a http(s) page to recurse prepended with "rec:", ' + 'a repopath:regexp pair, where repopath is the path to a repo and ' + 'regexp a filter for the rpms in it.' + ' a path to a configuration file prepended with "conf:" ' + 'containing a plain text list of rpm sources, one by line (comments ' + 'with # and empty lines allowed). ' + 'For any repository path specified with dir: latest: or the ' + 'repopath:regexp options, if it\'s not an absolute path, ' + + BASE_REPOS_PATH + ' will be prepended.' + + ) + + generate_src = repo_subparser.add_parser( + 'generate-src', + help='Populate the src dir with the tarballs from the src.rpm ' + 'files in the repo') + generate_src.add_argument('-p', '--with-patches', action='store_true', + help='Include the patch files') + generate_src.add_argument('-k', '--key', action='store', + default=None, + help='Key to sign the sources with') + generate_src.add_argument('--passphrase', action='store', + default='ask', + help='Passphrase to use when signing') + + rpms_sign = repo_subparser.add_parser( + 'sign-rpms', + help='sign the unsigned rpms.') + rpms_sign.add_argument('-k', '--key', action='store', + default=None, required=True, + help='Key to sign the rpms with') + rpms_sign.add_argument('--passphrase', action='store', + default='ask', + help='Passphrase to use when signing') + + repo_subparser.add_parser( + 'createrepo', + help='Run createrepo on each distro repository.') + + remove_old = repo_subparser.add_parser( + 'remove-old', + help='Remove old versions of packages.') + remove_old.add_argument('-k', '--keep', action='store', + default=1, help='Number of versions to ' + 'keep') + return parser.parse_args() + + +def main(): + global TEMP_DIR + args = parse_args() + + if args.verbose: + logging.root.level = logging.DEBUG + # we want connectionpool debug logs + connectionpool.log.setLevel(logging.DEBUG) + else: + logging.root.level = logging.INFO + # we don't want connectionpool info logs + connectionpool.log.setLevel(logging.WARN) + + if args.action == 'rm-dups': + if args.dir.endswith('/'): + path = args.dir[:-1] + else: + path = args.dir + rm_dups(path=path, noop=args.noop) + elif args.action == 'repo': + if args.dir.endswith('/'): + path = args.dir[:-1] + else: + path = args.dir + repo = RPMRepo(path) + logging.info('') + if args.repoaction == 'add': + if args.temp_dir is None: + TEMP_DIR = tempfile.mkdtemp() + atexit.register(cleanup, TEMP_DIR) + else: + TEMP_DIR = args.temp_dir + if not os.path.exists(TEMP_DIR): + os.makedirs(TEMP_DIR) + pkg_list = [] + logging.info('Resolving all the package sources') + for pkg_src in args.rpm_source: + pkg_list.extend(expand_source(pkg_src)) + logging.info('All sources resolved') + logging.info('') + logging.info('Adding packages to the repo %s', repo.path) + for pkg in pkg_list: + pkg = RPM(pkg) + repo.add_pkg(pkg) + logging.info('') + repo.save() + elif args.repoaction == 'generate-src': + if args.key and args.passphrase == 'ask': + passphrase = getpass('Key passphrase: ') + else: + passphrase = args.passphrase + repo.generate_sources(args.with_patches, args.key, passphrase) + elif args.repoaction == 'createrepo': + repo.createrepo() + elif args.repoaction == 'remove-old': + repo.delete_old(keep=int(args.keep), noop=args.noop) + elif args.repoaction == 'sign-rpms': + if args.passphrase == 'ask': + passphrase = getpass('Key passphrase: ') + else: + passphrase = args.passphrase + repo.sign_rpms(key=args.key, passwd=passphrase) + +if __name__ == '__main__': + main() -- To view, visit http://gerrit.ovirt.org/33299 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ib630eb1e3b701eaf224451f02692723447aa1f8b Gerrit-PatchSet: 1 Gerrit-Project: jenkins Gerrit-Branch: master Gerrit-Owner: David Caro <dcaro...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches