Example #1
0
class Map(BuildStep):
    '''
    Maps a value to another one.
    Supports regex matching and priority matching by order.
    '''
    parameters = {
        'input': Parameter(type=str, description='Input to map'),
        'mapping': Parameter(type=OrderedDict,
                             description='Mapping ordered dict'),
    }

    outparameters = {
        'result': Parameter(type=str, description='Result of the calculation')
    }

    def run(self):
        for pattern, subst in self.mapping.items():
            match = re.match(pattern, self.input)

            if match is not None:
                self.result = subst.format(*match.groups())
                self.log.debug('Map %r via %r to %r' %
                               (self.input, pattern, self.result))
                return

        raise RuntimeError('No suitable mapping entry found')
Example #2
0
class SystemCall(BuildStep):
    '''
    Build step to execute given shell command.
    '''
    parameters = {
        'command':
        Parameter(type=str, description='command to execute'),
        'workingdir':
        Parameter(type=str,
                  description='Working directory for command execution',
                  default='.'),
    }

    outparameters = {
        'commandoutput':
        Parameter(type=none_or(str),
                  description='Command output (if captured)',
                  default=None)
    }

    def run(self):
        cwd = os.getcwd()

        try:
            os.chdir(self.workingdir)
            self.commandoutput = systemCall(self.command, log=self.log)
        finally:
            os.chdir(cwd)
Example #3
0
class MakeDirs(BuildStep):
    '''
    This build step create the desired directories.
    '''

    parameters = {
        'dirs':
        Parameter(type=listof(str), description='List of directories'),
        'removeoncleanup':
        Parameter(type=bool,
                  description='Remove the directories on clenup',
                  default=True),
    }

    def run(self):
        for entry in self.dirs:
            # TODO: Referencer support for nested types
            entry = Referencer(entry).evaluate(self.chain)
            self.log.debug('Create directory: %s ...' % entry)
            ensureDirectory(entry)

    def cleanup(self):
        if self.removeoncleanup:
            for entry in self.dirs:
                entry = Referencer(entry).evaluate(self.chain)
                shutil.rmtree(entry)
Example #4
0
class TmpDir(BuildStep):
    parameters = {
        'parentdir':
        Parameter(type=str,
                  description='Path to parent directory',
                  default='/tmp'),
    }

    outparameters = {
        'tmpdir': Parameter(
            type=str,
            description='Created temporary directory',
        )
    }

    def run(self):
        timehash = hashlib.sha1(str(time.time())).hexdigest()
        dirhash = hashlib.sha1(self.parentdir).hexdigest()

        dest = hashlib.sha1(timehash + dirhash).hexdigest()
        dest = path.join(self.parentdir, dest)

        self.log.info('Create temporary dir: %s' % dest)

        ensureDirectory(dest)
        self.tmpdir = dest

    def cleanup(self):
        shutil.rmtree(self.tmpdir)
Example #5
0
class MovePath(BuildStep):
    '''
    This build step moves/renames a path to a given destination path.
    Shell wildcards are supported!
    '''

    parameters = {
        'source': Parameter(type=str, description='Source path'),
        'destination': Parameter(
            type=str,
            description='Destination path',
        ),
    }

    def run(self):
        self.log.info('Move %s to %s' % (self.source, self.destination))

        for entry in glob.glob(self.source):
            self.log.debug('Copy %s to %s' % (entry, self.destination))
            if os.path.isfile(entry):
                shutil.copy(entry, self.destination)
            else:
                shutil.copytree(entry, self.destination)

            self.log.debug('Remove %s ...' % entry)
            if os.path.isfile(entry):
                os.remove(entry)
            else:
                shutil.rmtree(entry)
Example #6
0
class GitClone(BuildStep):
    '''
    Clones a git project.
    '''
    parameters = {
        'url' : Parameter(type=str,
                                 description='Url to clone from'),
        'destdir' : Parameter(type=str,
                                 description='Destination directory to clone '
                                 'to (will be completely removed during '
                                 'cleanup!'),
        'uselastversion' : Parameter(type=bool,
                                 description='Try to determine the last release '
                                 'version (by analyzing the tags) and use it '
                                 'as target',
                                 default=False),
        'target' : Parameter(type=str,
                                 description='Checkout target (tag or branch)',
                                 default=''),
        'asbranch' : Parameter(type=str,
                                 description='Checkout the desired target as '
                                 'given branch',
                                 default=''),
    }

    def run(self):
        systemCall('git clone %s %s' % (self.url, self.destdir), log=self.log)

        if self.uselastversion:
            self.target = self._determineLastVersion()

        if self.target:
            checkoutCmd = 'git --git-dir=%s/.git --work-tree=%s checkout %s' % (
                self.destdir, self.destdir, self.target
                )

            if self.asbranch:
                checkoutCmd += ' -b %s' % self.asbranch

            systemCall(checkoutCmd, log=self.log)

    def cleanup(self):
        shutil.rmtree(self.destdir)

    def _determineLastVersion(self):
        self.log.debug('Determine last version ...')
        # find last tag
        tags = systemCall('git --git-dir=%s/.git --work-tree=%s tag -l' % (
            self.destdir,
            self.destdir),
            log=self.log).splitlines()

        tags = sorted([LooseVersion(entry.strip()) for entry in tags])
        result = tags[-1].vstring
        self.log.debug('Last version: %s' % result)
        return result
Example #7
0
class PBuilderExecCmds(BuildStep):
    '''
    Execute commands inside a pbuilder jail.
    '''
    parameters = {
        'cmds':
        Parameter(type=listof(str), description='List of commands to execute'),
        'save':
        Parameter(type=bool,
                  description='Save jail after execution',
                  default=False),
        'config':
        Parameter(type=str,
                  description='Pbuilder config file '
                  '(pbuilderrc)',
                  default='/etc/pbuilderrc'),
    }

    def __init__(self, name, paramValues, chain=None):
        BuildStep.__init__(self, name, paramValues, chain)
        self._scriptfile = None

    def run(self):
        # create tmp script file name
        self._scriptfile = '/tmp/conduct.PBuilderExecCmds.%s.sh' \
                           % hashlib.md5(str(time.time)).hexdigest()

        # create script with commands
        script = '#!/bin/sh\n'

        self.cmds.append('exit')  # exit the jail at the end
        for cmd in self.cmds:
            script += '''echo '%s' \n''' % cmd

        self.log.debug('Generated script:\n%s' % script)

        with open(self._scriptfile, 'w') as f:
            f.write(script)

        # pipe commands to pbuilder
        cmd = 'sh %s | pbuilder --login ' % self._scriptfile

        if self.save:
            cmd += '--save-after-login '

        cmd += '--configfile %s' % self.config

        systemCall(cmd, log=self.log)

    def cleanup(self):
        if self._scriptfile and path.exists(self._scriptfile):
            os.remove(self._scriptfile)
Example #8
0
class Debootstrap(BuildStep):
    '''
    This build step bootstraps a basic debian system to the given directory.
    '''

    parameters = {
        'distribution':
        Parameter(type=str, description='Desired distribution'),
        # TODO: map archs? => system analyzer?
        'arch':
        Parameter(type=str, description='Desired architecture'),
        'destdir':
        Parameter(type=str, description='Destination directory'),
        'includes':
        Parameter(type=listof(str),
                  description='Packages to include',
                  default=[]),
    }

    def run(self):
        cmd = 'debootstrap --verbose --arch=%s ' % self.arch

        if self.includes:
            cmd += '--include %s ' % ','.join(self.includes)

        if self._isForeignArch():
            # separate first and second stage
            cmd += '--foreign '

        cmd += '%s %s' % (self.distribution, self.destdir)

        self.log.info('Bootstrapping ...')
        systemCall(cmd, log=self.log)

        if self._isForeignArch():
            self._strapSecondStage()

    def _isForeignArch(self):
        return self.arch != conduct.app.sysinfo['arch']

    def _strapSecondStage(self):
        self.log.info('Boostrap second stage ...')
        qemuStatic = '/usr/bin/qemu-%s-static' % self.arch
        chrootQemuStaticPlace = path.join(self.destdir, 'usr', 'bin')

        self.log.debug('Copy qemu static to chroot ...')
        shutil.copy(qemuStatic, chrootQemuStaticPlace)
        chrootedSystemCall(self.destdir,
                           'debootstrap/debootstrap --second-stage',
                           mountPseudoFs=False,
                           log=self.log)
Example #9
0
class Calculation(BuildStep):
    '''
    Build step to do some calculation.
    '''
    parameters = {
        'formula': Parameter(type=str, description='Formula to calculate'),
    }

    outparameters = {
        'result': Parameter(type=float,
                            description='Result of the calculation')
    }

    def run(self):
        self.result = float(eval(self.formula))
Example #10
0
class Mount(BuildStep):
    '''
    This build step mounts given device to given mount point.
    '''
    parameters = {
        'dev': Parameter(type=str, description='Path to the device file'),
        'mountpoint': Parameter(type=str,
                                description='Path to the mount point'),
    }

    def run(self):
        mount(self.dev, self.mountpoint, log=self.log)

    def cleanup(self):
        umount(self.mountpoint, log=self.log)
Example #11
0
class CreateFileSystem(BuildStep):
    '''
    This build step creates the desired file system on the given device.
    Used tool: mkfs
    '''

    parameters = {
        'dev':
        Parameter(type=str, description='Path to the device file'),
        'fstype':
        Parameter(type=oneof('bfs', 'cramfs', 'ext2', 'ext3', 'ext4', 'fat',
                             'ntfs', 'vfat'),
                  description='Desired file system'),
    }

    def run(self):
        systemCall('mkfs -t %s %s' % (self.fstype, self.dev), log=self.log)
Example #12
0
class InstallDebPkg(BuildStep):
    parameters = {
        'pkg':
        Parameter(type=str, description='Package to install'),
        'chrootdir':
        Parameter(type=str,
                  description='Chroot directory (if desired)',
                  default=''),
        'depsonly':
        Parameter(type=bool,
                  description='Install dependencies only',
                  default=False),
    }

    def run(self):
        self._syscall = lambda cmd: (chrootedSystemCall(self.chrootdir, cmd, log=self.log) \
            if self.chrootdir else systemCall(cmd, log=self.log))
        self._installCmd = 'env DEBIAN_FRONTEND=noninteractive ' \
                'apt-get install --yes --force-yes ' \
                '--no-install-recommends ' \
                '-o Dpkg::Options::="--force-overwrite" ' \
                '-o Dpkg::Options::="--force-confnew" ' \
                '%s'

        if self.depsonly:
            deps = self._determineDependencies()

            for pkg in deps:
                self._syscall(self._installCmd % pkg)
        else:
            # install actual pkg
            self._syscall(self._installCmd % self.pkg)

    def _determineDependencies(self):
        try:
            out = self._syscall('apt-cache show %s | grep Depends:' % self.pkg)
        except RuntimeError as e:
            self.log.exception(e)
            self.log.warn('Therefore: Assume that there are no dependencies!')
            return []  # no deps

        out = out[9:]  # strip 'Depends: '
        depStrs = [entry.strip() for entry in out.split(',')]

        return [entry.split(' ')[0] for entry in depStrs]
Example #13
0
class CopyPath(BuildStep):
    '''
    This build step copies a path to a given destination path.
    Shell wildcards are supported!
    '''

    parameters = {
        'source': Parameter(type=str, description='Source path'),
        'destination': Parameter(
            type=str,
            description='Destination path',
        ),
    }

    def run(self):
        self.log.info('Copy %s to %s' % (self.source, self.destination))

        for entry in glob.glob(self.source):
            shutil.copytree(entry, self.destination)
Example #14
0
class Partitioning(BuildStep):
    parameters = {
        'dev' : Parameter(type=str,
                                 description='Path to the device file'),
        'partitions' : Parameter(type=listof(referencer_or(int)),
                                 description='List of partition sizes (in MB)')
    }

    def run(self):
        cmds = []

        for i in range(len(self.partitions)):
            cmds += self._createPartitionCmds(i+1, self.partitions[i])

        cmds.append('p') # print partition table
        cmds.append('w') # write partition table
        cmds.append('') # confirm

        shCmd = '(%s)' % ''.join([ 'echo %s;' % entry for entry in cmds])
        shCmd += '| fdisk %s 2>&1' % self.dev

        systemCall(shCmd, log=self.log)

    def _createPartitionCmds(self, index, size):
        # TODO: better referencer handling: give step instead of chain
        if isinstance(size, Referencer):
            size = size.evaluate(self.chain, float)

        cmds = [
            'n' # new partition
        ]

        if index < 4:
            cmds.append('p') # primary
        else:
            cmds.append('e') # extended

        cmds.append(str(index)) # partition number
        cmds.append('') # confirm

        cmds.append('+%dM' % size) # partition size

        return cmds
Example #15
0
class RmPath(BuildStep):
    parameters = {
        'path':
        Parameter(type=str, description='Path to remove'),
        'recursive':
        Parameter(type=bool, description='Remove recursive', default=True),
    }

    def run(self):
        self.log.info('Remove path: %s' % self.path)

        if path.isfile(self.path):
            os.remove(self.path)
        elif path.isdir(self.path):
            if self.recursive:
                shutil.rmtree(self.path)
            else:
                os.rmdir(self.path)

        if path.exists(self.path):
            raise RuntimeError('Could not remove path')
Example #16
0
class WriteFile(BuildStep):
    parameters = {
        'path':
        Parameter(type=str, description='Path to target file'),
        'content':
        Parameter(type=str, description='Content wo write'),
        'append':
        Parameter(type=bool,
                  description='Append to the file '
                  '(if existing)',
                  default=False),
    }

    def run(self):
        ensureDirectory(path.dirname(self.path))

        openMode = 'a' if self.append else 'w'

        self.log.info('Write to file %s ...' % self.path)
        with open(self.path, openMode) as f:
            f.write(self.content)
Example #17
0
class Pdebuild(BuildStep):
    '''
    Build debian package via pdebuild.
    '''
    parameters = {
        'sourcedir':
        Parameter(type=str, description='Source directory'),
        'config':
        Parameter(type=str,
                  description='Pbuilder config file '
                  '(pbuilderrc)',
                  default='/etc/pbuilderrc'),
    }

    def run(self):
        cwd = os.getcwd()
        try:
            os.chdir(self.sourcedir)
            cmd = 'pdebuild --configfile %s' % self.config
            systemCall(cmd, log=self.log)
        finally:
            os.chdir(cwd)
Example #18
0
class ChrootedSystemCall(BuildStep):
    '''
    Build step to execute given shell command in a chroot environment.
    '''
    parameters = {
        'command':
        Parameter(type=str, description='command to execute'),
        'chrootdir':
        Parameter(type=str, description='Chroot directory', default='.'),
    }

    outparameters = {
        'commandoutput':
        Parameter(type=none_or(str),
                  description='Command output (if captured)',
                  default=None)
    }

    def run(self):
        self.commandoutput = chrootedSystemCall(self.chrootdir,
                                                self.command,
                                                log=self.log)
Example #19
0
class DevMapper(BuildStep):
    '''
    This build step uses kpartx (devmapper) to map the partitions of the given
    device to own device files.
    '''
    parameters = {
        'dev' : Parameter(type=str,
                                 description='Path to the device file'),
    }

    outparameters = {
        'mapped' : Parameter(type=list,
                                description='Created device files',),
        'loopdev' : Parameter(type=str,
                                description='Used loop device',)
    }

    def run(self):
        # request a proper formated list of created devs
        out = systemCall('kpartx -v -l -s %s' % self.dev, log=self.log)

        # create device files
        systemCall('kpartx -v -a -s %s' % self.dev, log=self.log)

        # store loop dev
        self.loopdev = re.findall('(/dev/.*?) ', out)[0]

        # store created device file paths
        self.mapped = []
        for line in out.splitlines():
            devFile = line.rpartition(':')[0].strip()
            self.mapped.append(path.join('/dev/mapper', devFile))

        self.log.info('Loop device: %s' % self.loopdev)
        self.log.info('Mapped devices: %s' % ', '.join(self.mapped))

    def cleanup(self):
        systemCall('kpartx -v -d -s %s' % self.dev,
                   log=self.log)
Example #20
0
class TriggerCleanup(BuildStep):
    '''
    Build step that triggers the cleanup of another step.
    '''
    parameters = {
        'step': Parameter(type=str, description='Step to clean up'),
    }

    def run(self):
        self.log.info('Trigger cleanup of %s ...' % self.step)
        if (self.step not in self.chain.steps):
            self.error('Given step (%s) not found!' % self.step)
            return

        step = self.chain.steps[self.step]
        step.cleanupBuild()
Example #21
0
class Config(BuildStep):
    '''
    Build step to read given configuration file.
    '''
    parameters = {
        'path':
        Parameter(type=str,
                  description='Path to the configuration file',
                  default='.'),
        'format':
        Parameter(type=oneof('ini', 'py', 'auto'),
                  description='Format of config file',
                  default='auto'),
    }

    outparameters = {
        'config':
        Parameter(type=dict,
                  description='Command output (if captured)',
                  default={})
    }

    def run(self):
        parseFuncs = {'ini': self._parseIni, 'py': self._parsePy}

        self.log.info('Parse config: %s' % self.path)

        configFormat = self.format

        if configFormat == 'auto':
            configFormat = path.splitext(self.path)[1][1:]

            if configFormat not in parseFuncs.keys():
                raise RuntimeError('Unsupported configuration format: %s' %
                                   configFormat)

        self.log.debug('Used format: %s' % configFormat)

        self.config = parseFuncs[configFormat](self.path)

        self.log.debug('Parsed config: %r' % self.config)

    def _parseIni(self, path):
        cfg = {}

        parser = ConfigParser.SafeConfigParser()
        parser.readfp(open(path))

        for section in parser.sections():
            cfg[section] = {}

            for name, value in parser.items(section):
                cfg[section][name] = value

        return cfg

    def _parsePy(self, path):
        cfg = {}

        content = open(path).read()

        exec content in cfg
        del cfg['__builtins__']

        return cfg
Example #22
0
class BuildStep(object):
    __metaclass__ = BuildStepMeta

    parameters = {
        'description':
        Parameter(type=str,
                  description='Build step description',
                  default='Undescribed'),
        'loglevel':
        Parameter(type=oneof(LOGLEVELS.keys()),
                  description='Log level',
                  default='info'),
        'retries':
        Parameter(type=int,
                  description='Number of retries to execute the '
                  'build step',
                  default=0),
    }

    outparameters = {}

    def __init__(self, name, paramValues, chain=None):
        self.name = name
        self.chain = chain  # Maintain a reference to the chain (for refs)
        self.wasRun = False

        self._params = {}

        self._initLogger()
        self._applyParams(paramValues)

    def build(self):
        '''
        Build the build step.
        This method is a wrapper around run() that does some logging and
        exception handling.
        '''
        # log some bs stuff
        self.log.info('=' * 80)
        self.log.info('Build: %s' % self.name)
        self.log.info(self.description)
        self.log.info('-' * 80)

        success = False

        for i in range(0, self.retries + 1):
            try:
                # execute actual build actions
                self.run()
                self.wasRun = True
                success = True
                break
            except Exception as exc:
                self.log.exception(exc)

                # handle retries
                if self.retries > i:
                    self.log.warn('Failed; Retry %s/%s' %
                                  (i + 1, self.retries))

        # log some bs stuff
        self.log.info('')
        self.log.info('%s' % 'SUCCESS' if success else 'FAILED')
        self.log.info('')

        if not success:
            raise RuntimeError('Build step failed')

    def cleanupBuild(self):
        '''
        Cleanup all the build step's leftovers.
        his method is a wrapper around cleanup() that does some logging and
        exception handling.
        '''

        if not self.wasRun:
            self.log.info('Cleanup: Step was not run; Skip')
            return

        if self.cleanup.im_func == BuildStep.cleanup.im_func:
            self.log.info('Cleanup: No custom cleanup; Skip')
            return

        self.log.info('=' * 80)
        self.log.info('Cleanup: %s' % self.name)
        self.log.info(self.description)
        self.log.info('-' * 80)

        resultStr = 'SUCCESS'
        try:
            self.cleanup()
            self.wasRun = False
        except Exception as exc:
            resultStr = 'FAILED'
            self.log.exception(exc)

            raise
        finally:
            self.log.info('')
            self.log.info('%s' % resultStr)
            self.log.info('')

    def cleanup(self):
        '''
        This function shall be overwritten by the specific build steps
        and should do everything that's necessary for cleaning up after
        the build.
        '''
        pass

    def run(self):
        '''
        This function shall be overwritten by the specific build steps
        and should do everything that's necessary build this step.
        '''
        raise NotImplemented('Buildstep not implemented (may be abstract)')

    def doWriteLoglevel(self, value):
        self.log.setLevel(LOGLEVELS[value])

    def doReadLoglevel(self):
        level = self.log.getEffectiveLevel()
        return INVLOGLEVELS[level]

    def _initLogger(self):
        if self.chain is not None:
            self.log = self.chain.log.getChild(self.name)
        else:
            self.log = conduct.app.log.getChild(self.name)
        self.log.setLevel(LOGLEVELS[self.loglevel])

    def _applyParams(self, paramValues):

        for name, paramDef in self.parameters.items():
            if name in paramValues:
                setattr(self, name, paramValues[name])
            elif paramDef.default is None:
                raise RuntimeError('%s: Mandatory parameter %s is missing' %
                                   (self.name, name))