Blob Blame History Raw
#pylint: disable=line-too-long,abstract-class-not-used
    Top level function library for centpkg
# Author(s):
#            Jesse Keating <>
#            Pat Riehecky <>
#            Brian Stinson <>
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2 of the License, or (at your
# option) any later version.  See for
# the full text of the license.

import os
import re
import sys
import warnings

from pyrpkg import Commands, rpkgError
from centos import centos_cert
from . import cli

__version__ = '0.4.5'

class DistGitDirectory(object):

    signame = None
    centosversion = None
    projectname = None
    releasename = None
    distrobranch = False

    def __init__(self, branchtext):
        sigtobranchre = r'sig-(?P<signame>\w+)(?P<centosversion>\d)-?(?P<projectname>\w+)?-?(?P<releasename>\w+)?'
        distrobranchre = r'c(?P<centosversion>\d+)-?(?P<projectname>\w+)?'
        oldbranchre = r'(?P<signame>\w+)(?P<centosversion>\d)'
        sigmatch = re.match(sigtobranchre, branchtext)
        distromatch = re.match(distrobranchre, branchtext)
        oldbranchmatch = re.match(oldbranchre, branchtext)
        if sigmatch:
            gd = sigmatch.groupdict()
            self.signame = gd['signame']
            self.centosversion = gd['centosversion']

            # Users have the option to specify (or not specify) common in their
            # git repos. Ww need to handle these cases because common is not a
            # project nor is it a release.
            if gd['projectname'] != 'common':
                self.projectname = gd['projectname']
            if gd['releasename'] != 'common':
                self.releasename = gd['releasename']
        elif distromatch:
            gd = distromatch.groupdict()
            self.distrobranch = True
            self.signame = 'centos'
            self.centosversion = gd['centosversion']

            if gd['projectname'] != 'common':
                self.projectname = gd['projectname']
        elif oldbranchmatch:
            warnings.warn("This branch is deprecated and will be removed soon",
            raise ValueError("Branchname: {0} is not valid".format(branchtext))

    def target(self):
        projectorcommon = self.projectname
        releaseorcommon = self.releasename

        if self.distrobranch:
            return '-'.join(filter(None, ['c'+self.centosversion,

        if not releaseorcommon:
            if not projectorcommon or projectorcommon == 'common':
                projectorcommon = 'common'
                releaseorcommon = 'common'

        return '-'.join(filter(None, [self.signame+self.centosversion,
                                      projectorcommon, releaseorcommon])) + '-el{0}'.format(self.centosversion)

class Commands(Commands):
        For the pyrpkg commands with centpkg behavior
    def __init__(self, path, lookaside, lookasidehash, lookaside_cgi,
                 gitbaseurl, anongiturl, branchre, kojiconfig,
                 build_client, user=None, dist=None, target=None,
                 quiet=False, distgit_namespaced=False):
            Init the object and some configuration details.
        super(Commands, self).__init__(path, lookaside, lookasidehash,
                                      lookaside_cgi, gitbaseurl, anongiturl,
                                      branchre, kojiconfig, build_client,
                                      user, dist, target, quiet)

        self.distgitdir = DistGitDirectory(self.branch_merge)

    # redefined loaders
    def load_rpmdefines(self):
            Populate rpmdefines based on branch data

        if not self.distgitdir.centosversion:
            raise rpkgError('Could not get the OS version from the branch:{0}'.format(self.branch_merge))

        self._distval = self.distgitdir.centosversion
        self._distval = self._distval.replace('.', '_')

        self._disttag = 'el%s' % self._distval

        self._rpmdefines = ["--define '_topdir {0}'".format(self.path),
                            "--define '_srcrpmdir {0}'".format(self.path),
                            "--define '_rpmdir {0}'".format(self.path),
                            "--define 'dist .{0}'".format(self._disttag),
                            # int and float this to remove the decimal
                            "--define '{0} 1'".format(self._disttag)]

    def load_spec(self):
        """This sets the spec attribute"""

        # We are not using the upstream load_spec because the file structure is
        # hard-coded

        # Get a list of files in the path we're looking at
        files = os.listdir(os.path.join(self.path,'SPECS'))
        # Search the files for the first one that ends with ".spec"
        for __f in files:
            if __f.endswith('.spec') and not __f.startswith('.'):
                self._spec = os.path.join('SPECS', __f)

        raise rpkgError('No spec file found.')

    def load_target(self):
        """ This sets the target attribute (used for mock and koji) """

        # Distribution branches start with c and may or may not end in -plus
        # otherwise, it's a sig branch 
        if not self.distgitdir.distrobranch:
            # send distribution packages to build on the the bananas tags for now
            self._target = 'bananas{0}-{1}'.format(self.distval, self.disttag)
            # sig packages are built in the tag that matches the git branch
            self._target =

    def load_user(self):
            self._user = centos_cert.CentOSUserCert().CN
            print >>sys.stderr, "Could not load user from cert file"
            super(Commands, self).load_user()

    # These are the commands defined in the base pyrpkg.Commands class
    # and have been implemented here

    def sources(self, outdir=None):
        """Download source files"""

        #  See also:
        # in 'super' the sources function expects a file named 'sources' to be in the base directory.
        # A patch has been sent to upstream to allow a more flexible location.
        # This code doesn't work due to:
        #              archive.strip().split('  ', 1) # patch provided to upstream to fix
        #              url = '%s/%s/%s/%s/%s' % (self.lookaside, self.module_name,
        #                                        file.replace(' ', '%20'),
        #                                        csum, file.replace(' ', '%20'))
        #os.symlink(os.path.join(self.path, '.{0}.metadata'.format(self.module_name)), os.path.join(self.path, 'sources'))
        #super(Commands, self).sources(outdir=None)
        #os.unlink(os.path.join(self.path, 'sources'))

        # The following is copied from rpkg/ with minor changes
            archives = open(os.path.join(self.path,
        except IOError, e:
            raise rpkgError('%s is not a valid repo: %s' % (self.path, e))
        # Default to putting the files where the module is
        if not outdir:
            outdir = self.path
        for archive in archives:
                # This strip / split is kind a ugly, but checksums shouldn't have
                # two spaces in them.  sources file might need more structure in the
                # future
                csum, file = archive.strip().split(None, 1)
            except ValueError:
                raise rpkgError('Malformed sources file.')

            # The default lookaside hash is stored in centpkg.conf, but there is
            # a mix of md5 and sha sums in the CentOS lookaside, here we divine
            # which one we are using

            sum_lengths = { 128: 'sha512',
                            64: 'sha256',
                            40: 'sha1',
                            32: 'md5',

            self.lookasidehash = sum_lengths[len(csum)]

            # If a directory is specified in the metadata file, append it to
            # outdir
            if os.path.dirname(file):
                outdir = os.path.join(self.path, os.path.dirname(file))
                file = os.path.basename(file)

            # Create the output directory if it's not checked into git
            if not os.path.exists(outdir):
      "Creating OUTDIR: {0}".format(outdir))

            outfile = os.path.join(outdir, file)
            # See if we already have a valid copy downloaded
            if os.path.exists(outfile):
                if self.lookasidecache.file_is_valid(outfile, csum, self.lookasidehash):
  "Downloading %s" % (file))
            url = '%s/%s/%s/%s' % (self.lookaside, self.module_name,
            command = ['curl', '-H', 'Pragma:', '-o', outfile, '-R', '-S', '--fail']
            if self.quiet:
            if not self._verify_file(outfile, csum, self.lookasidehash):
                raise rpkgError('%s failed checksum' % file)


    def get_latest_commit(self, *args, **kwargs):
        raise NotImplementedError("get_latest_commit is not yet implemented in centpkg")

    def gitbuildhash(self, *args, **kwargs):
        raise NotImplementedError("gitbuildhash is not yet implemented in centpkg")

    def import_srpm(self, *args, **kwargs):
        raise NotImplementedError("import_srpm is not yet implemented in centpkg")

    def new(self, *args, **kwargs):
        raise NotImplementedError("new is not yet implemented in centpkg")

    def patch(self, *args, **kwargs):
        raise NotImplementedError("patch is not yet implemented in centpkg")

    def push(self, *args, **kwargs):
        raise NotImplementedError("push is not yet implemented in centpkg")

    def file_exists(self, *args, **kwargs):
        raise NotImplementedError("file_exists is not yet implemented in centpkg")

    def upload_file(self, *args, **kwargs):
        raise NotImplementedError("upload_file is not yet implemented in centpkg")

    def install(self, *args, **kwargs):
        raise NotImplementedError("install is not yet implemented in centpkg")

    def lint(self, *args, **kwargs):
        raise NotImplementedError("lint is not yet implemented in centpkg")

    def upload(self, *args, **kwargs):
        raise NotImplementedError("upload is not yet implemented in centpkg")

    def prep(self, *args, **kwargs):
        raise NotImplementedError("prep is not yet implemented in centpkg")

    def unused_patches(self):
        """Discover patches checked into source control that are not used

        Returns a list of unused patches, which may be empty.

        # Create a list for unused patches
        unused = []
        # Get the content of spec into memory for fast searching
        spec = open(self.spec, 'r').read()
        # Replace %{name} with the package name
        spec = spec.replace("%{name}", self.module_name)
        # Replace %{version} with the package version
        spec = spec.replace("%{version}", self.ver)

        # Get a list of files tracked in source control
        files = self.repo.git.ls_files('--exclude-standard').split()
        for file in map(os.path.basename, files):
            # throw out non patches
            if not file.endswith(('.patch', '.diff')):
            if file not in spec:
        return unused