one more step
authorTomasz Buchert <tomasz@debian.org>
Sat, 29 Aug 2015 22:16:23 +0000 (00:16 +0200)
committerTomasz Buchert <tomasz@debian.org>
Sat, 29 Aug 2015 22:16:23 +0000 (00:16 +0200)
debocker
src/debocker/debian.py
src/debocker/log.py
src/debocker/utils.py

index 548a6a4..da3cda6 100755 (executable)
--- a/debocker
+++ b/debocker
 # coding: utf-8
 
 import click
-import re
-import os
-from os.path import isdir, join, isfile, abspath, dirname, \
-    realpath, splitext, relpath
-from shutil import which
-from shlex import quote as shell_quote
-from collections import OrderedDict
-from string import Template
+from os.path import join, dirname, realpath
 import sys
 
 HERE = realpath(dirname(__file__))
 # this is to allow to use it directory from source repository
 sys.path.append(join(HERE, 'src'))
 
-from debocker.utils import cached_constant, cached_property, tmppath, \
+from debocker.utils import tmppath, \
     calculate_md5_and_size, log_check_call, log_check_output, \
-    get_filelist
-from debocker.log import log, LOW, fail
-from debocker.debian import extract_pristine_tar
+    assert_command
+from debocker.log import log, fail, LOW, set_verbosity, is_verbose
+from debocker.debian import Package, docker_build_bundle, STAGES
 
 __version__ = "0.2"
 
-@cached_constant
-def find_bundle_files():
-    '''finds the location of bundle files'''
-    debocker_dir = dirname(abspath(realpath(__file__)))
-    locations = [ debocker_dir, '/usr/share/debocker' ]
-    for loc in locations:
-        bundle_files = join(loc, 'bundle-files')
-        if isdir(bundle_files):
-            log("Bundle files found in '{}'.".format(bundle_files), LOW)
-            return bundle_files
-    fail('could not find bundle files')
-
-def assert_command(cmd):
-    if which(cmd) is None:
-        fail("command '{}' is not available", cmd)
-
-@cached_constant
-def assert_docker():
-    if which('docker') is None:
-        fail('docker is not available')
-
-class Package:
-
-    def __init__(self, path):
-        self.path = path
-        self.debian = join(self.path, 'debian')
-        self.control = join(self.debian, 'control')
-        self.changelog = join(self.debian, 'changelog')
-        self.source_format = join(self.debian, 'source', 'format')
-
-    def is_valid(self):
-        '''verifies that the current directory is a debian package'''
-        return isdir(self.debian) and isfile(self.control) and \
-            isfile(self.source_format) and self.format in ['native', 'quilt']
-
-    def assert_is_valid(self):
-        if not self.is_valid():
-            fail('not in debian package directory')
-
-    @cached_property
-    def format(self):
-        with open(self.source_format) as f:
-            line = f.readline().strip()
-        m = re.match(r'^3\.0 \((native|quilt)\)', line)
-        if not m:
-            fail('unsupported format ({})', line)
-        fmt = m.group(1)
-        log("Detected format '{}'.".format(fmt), LOW)
-        return fmt
-
-    @cached_property
-    def native(self):
-        return self.format == 'native'
-
-    @cached_property
-    def name(self):
-        with open(self.control) as f:
-            for line in f:
-                m = re.match(r'^Source: (\S+)$', line)
-                if m:
-                    return m.group(1)
-        fail('could not find the name of the package')
-
-    @cached_property
-    def long_version(self):
-        '''long version'''
-        with open(self.changelog) as f:
-            line = f.readline()
-        m = re.match(r'^(\S+) \((\S+)\)', line)
-        if not m:
-            fail("could not parse package version (from '{}')", line.strip())
-        return m.group(2)
-
-    @cached_property
-    def version(self):
-        '''upstream version'''
-        if self.native:
-            return self.long_version
-        else:
-            m = re.match(r'^(.+)-(\d+)$', self.long_version)
-            if not m:
-                fail('could not parse version ({})', self.long_version)
-            return m.group(1)
-
-    @cached_property
-    def orig_tarball_candidates(self):
-        '''possible original upstream tarballs'''
-        formats = [ 'xz', 'gz', 'bz2' ]
-        names = [ '{}_{}.orig.tar.{}'.format(self.name, self.version, fmt)
-                  for fmt in formats ]
-        return names
-
-    @cached_property
-    def orig_tarball(self):
-        '''finds the original tarball'''
-        for name in self.orig_tarball_candidates:
-            tarball = join(self.path, '..', name)
-            log("Trying tarball candidate '{}'.".format(tarball), LOW)
-            if isfile(tarball):
-                log("Original tarball found at '{}'.".format(tarball), LOW)
-                return tarball
-        result = extract_pristine_tar(self.path, self.orig_tarball_candidates)
-        if result is not None:
-            return result
-        fail('could not find original tarball')
-
-    def assert_orig_tarball(self):
-        if self.native:
-            # for now, we just tar the current directory
-            path = tmppath('{}_{}.tar.xz'.format(
-                self.name, self.version))
-            with open(path, 'wb') as output:
-                tar = [ 'tar', 'c', '--xz', '--exclude=.git',
-                        '-C', self.path, '.' ]
-                log_check_call(tar, stdout = output)
-            return path
-        else:
-            return self.orig_tarball  # simple alias
-
-    def tar_package_debian(self, output, comp = None):
-        # TODO: make it reproducible
-        compressions = {
-            'xz': '--xz'
-        }
-        tar = [ 'tar', 'c' ]
-        if comp:
-            tar += [ compressions[comp] ]
-        debian = join(self.path, 'debian')
-        filelist = get_filelist(debian, self.path)
-        tar += [ '--no-recursion', '-C', self.path ]
-        tar += filelist
-        log_check_call(tar, stdout = output)
-
-    def tar_original_tarball(self, output, stdout):
-        orig = self.assert_orig_tarball()
-        return orig  # for now
-
-    def build_docker_tarball_to_fd(self, output, buildinfo):
-        '''builds the docker tarball that builds the package'''
-        controlfile = join(self.path, 'debian', 'control')
-        controlfile = abspath(controlfile)
-        debianfile = tmppath('debian.tar.xz')
-        with open(debianfile, 'wb') as debian:
-            self.tar_package_debian(debian, comp = 'xz')
-        originalfile = self.assert_orig_tarball()
-        originalfile = abspath(originalfile)
-        if self.native:
-            make_native_bundle(self.name, self.version,
-                               controlfile, originalfile,
-                               buildinfo, output)
-        else:
-            make_quilt_bundle(self.name, self.long_version,
-                              controlfile, originalfile, debianfile,
-                              buildinfo, output)
-
-    def build_docker_tarball(self, filename, buildinfo):
-        with open(filename, 'wb') as output:
-            self.build_docker_tarball_to_fd(output, buildinfo)
-
-def make_native_bundle(name, version, control,
-                       source, buildinfo, output):
-    dsc_name = '{}_{}.dsc'.format(name, version)
-    bundler = Bundler(name, version, control, dsc_name,
-                      buildinfo, native = True)
-    _, ext = splitext(source)
-    source_name = '{}_{}.tar{}'.format(name, version, ext)
-    bundler.add_source_file(source_name, source, 'source_tarball')
-    bundler.write_bundle(output = output)
-
-def make_quilt_bundle(name, version, control,
-                      original, debian, buildinfo, output):
-    dsc_name = '{}_{}.dsc'.format(name, version)
-    bundler = Bundler(name, version, control, dsc_name,
-                      buildinfo, native = False)
-    _, oext = splitext(original)
-    _, dext = splitext(debian)
-    # TODO: improve
-    uversion = version.split('-')[0]
-    original_name = '{}_{}.orig.tar{}'.format(name, uversion, oext)
-    debian_name = '{}_{}.debian.tar{}'.format(name, version, dext)
-    bundler.add_source_file(original_name, original, 'original_tarball')
-    bundler.add_source_file(debian_name, debian, 'debian_tarball')
-    bundler.write_bundle(output = output)
-
-# STEPS
-
-STEPS = OrderedDict([
-    ('end', None),
-    ('build', 'args_05'),
-    ('extract-source', 'args_04'),
-    ('install-deps', 'args_03'),
-    ('install-utils', 'args_02'),
-    ('upgrade', 'args_01')
-])
-
-class Bundler:
-
-    def __init__(self, name, version, control, dsc_name, buildinfo, native):
-        self.name = name
-        self.version = version
-        self.native = native
-        self.control = control
-        self.sources = []
-        self.dsc_name = dsc_name
-        self.step_name = buildinfo['step']
-        self.step = STEPS[self.step_name]
-        self.image = buildinfo['image']
-        self.buildinfo = buildinfo
-        self.wdir = tmppath('bundle', directory = True)
-
-    @property
-    def format_string(self):
-        return ('3.0 (native)' if self.native else '3.0 (quilt)')
-
-    def add_source_file(self, name, path, tag):
-        md5, size = calculate_md5_and_size(path)
-        self.sources.append({
-            'name': name,
-            'path': path,
-            'md5': md5,
-            'size': size,
-            'tag': tag
-        })
-
-    def write_dsc_file(self):
-        '''makes minimal, yet functional .dsc file'''
-        path = join(self.wdir, 'source', self.dsc_name)
-        with open(path, 'w') as f:
-            f.write('Format: {}\n'.format(self.format_string))
-            f.write('Source: {}\n'.format(self.name))
-            f.write('Version: {}\n'.format(self.version))
-            f.write('Files:\n')
-            for s in self.sources:
-                f.write(' {} {} {}\n'.format(s['md5'], s['size'], s['name']))
-
-    def write_info_file(self, info):
-        path = join(self.wdir, 'info')
-        with open(path, 'w') as f:
-            for k, v in info.items():
-                f.write("{}={}\n".format(k, v))
-
-    def write_buildinfo_file(self):
-        path = join(self.wdir, 'buildinfo')
-        with open(path, 'w') as f:
-            f.write("flags='{}'\n".format(self.buildinfo['flags']))
-
-    def write_bundle(self, output):
-        '''writes bundle to a given file'''
-
-        os.makedirs(join(self.wdir, 'source'))
-
-        info = OrderedDict()
-        info['bundle_version'] = __version__
-        info['name'] = self.name
-        info['version'] = self.version
-        info['format'] = ('native' if self.native else 'quilt')
-
-        def make_link(target, parts):
-            return os.symlink(target, join(self.wdir, *parts))
-
-        # control file
-        make_link(self.control, [ 'control' ])
-
-        # dsc file
-        self.write_dsc_file()
-        info['dsc_name'] = self.dsc_name
-
-        # sources
-        for s in self.sources:
-            name = s['name']
-            tag = s['tag']
-            make_link(s['path'], [ 'source', name ])
-            info[tag] = name
-
-        # info & buildinfo
-        self.write_info_file(info)
-        self.write_buildinfo_file()
-
-        # bundle files
-        bundle_files = find_bundle_files()
-
-        copytree(join(bundle_files, 'steps'),
-                 join(self.wdir, 'steps'))
-        dockertmpl = join(bundle_files, 'Dockerfile')
-        with open(dockertmpl, 'r') as df:
-            t = Template(df.read())
-            ctx = {
-                'image': self.image,
-                'args_01': 'none',
-                'args_02': 'none',
-                'args_03': 'none',
-                'args_04': 'none',
-                'args_05': 'none'
-            }
-            if self.step:
-                log("Replacing '{}' with '{}'.".format(
-                    self.step, CURRENT_TIME))
-                if self.step not in ctx:
-                    fail('internal error in dockerfile template')
-                ctx[self.step] = CURRENT_TIME
-            rendered = t.substitute(ctx)
-            dockerfile = join(self.wdir, 'Dockerfile')
-            with open(dockerfile, 'w') as f:
-                f.write(rendered)
-
-        file_list = get_filelist(self.wdir)
-        # tar everything
-        tar = [ 'tar', 'c', '-h', '--numeric-owner' ]
-        tar += [ '--no-recursion' ]
-        tar += [ '--owner=0', '--group=0' ]
-        tar += [ '--mtime=1970-01-01' ]
-        tar += [ '-C', self.wdir ]
-        tar += file_list
-        log_check_call(tar, stdout = output)
-
-def docker_build_bundle(bundle, no_cache, pull):
-    '''builds the given image and returns the final image'''
-    # TODO: quite ugly, cannot be done cleaner?
-    build_log = tmppath()
-    bundle_esc = shell_quote(bundle)
-    build_log_esc = shell_quote(build_log)
-    docker_opts = []
-    if no_cache:
-        docker_opts.append('--no-cache')
-    if pull:
-        docker_opts.append('--pull')
-    docker_opts = ' '.join(docker_opts)
-    log_check_call('docker build {} - < {} | tee {}'.format(
-        docker_opts, bundle_esc, build_log_esc), shell = True)
-    with open(build_log) as f:
-        s = f.read().strip()
-        ms = re.findall(r'Successfully built (\S+)', s)
-        if len(ms) != 1:
-            fail('cannot parse logs (build failed?)')
-        image = ms[0]
-    return image
-
 # CLI INTERFACE
 
 @click.group()
 @click.option('-v', '--verbose', count=True,
               help = 'be verbose, repeat for more effect')
 def cli(verbose):
-    global VERBOSITY
-    VERBOSITY = verbose
+    set_verbosity(verbose)
 
 @cli.command(help = 'Write tar bundle')
 @click.argument('path', default = '.')
@@ -378,7 +33,7 @@ def cli(verbose):
               help = 'build flags')
 @click.option('step', '--from', default = 'end',
               help = 'start from the given step',
-              type = click.Choice(STEPS.keys()))
+              type = click.Choice(STAGES))
 @click.option('--image', default = 'debian:unstable', metavar = 'IMAGE',
               help = 'base docker image')
 def bundle(path, output, flags, step, image):
@@ -393,7 +48,7 @@ def bundle(path, output, flags, step, image):
     pkg.build_docker_tarball(output, {
         'flags': flags, 'step': step, 'image': image })
     log("Bundle created in '{}'.".format(output))
-    if VERBOSITY > NONE:
+    if is_verbose():
         md5, size = calculate_md5_and_size(output)
         log('Bundle hash and size: {}, {}.'.format(md5, size), LOW)
 
@@ -408,7 +63,7 @@ def bundle(path, output, flags, step, image):
 @click.option('--pull', default = False,
               is_flag = True, help = 'pull the newest base image')
 def build_bundle(bundle, output, sign, no_cache, pull):
-    assert_docker()
+    assert_command('docker')
     if sign:
         assert_command('debsign')
     image = docker_build_bundle(bundle,
@@ -448,14 +103,14 @@ def build_bundle(bundle, output, sign, no_cache, pull):
               is_flag = True, help = 'do not use docker image cache')
 @click.option('step', '--from', default = 'end',
               help = 'start from the given step',
-              type = click.Choice(STEPS.keys()))
+              type = click.Choice(STAGES))
 @click.option('--image', default = 'debian:unstable', metavar = 'IMAGE',
               help = 'base docker image')
 @click.option('--pull', default = False,
               is_flag = True, help = 'pull the newest base image')
 @click.pass_context
 def build(ctx, path, output, sign, flags, no_cache, step, image, pull):
-    assert_docker()
+    assert_command('docker')
     if sign:
         assert_command('debsign')
     tarball_path = tmppath('bundle.tar')
index 2be25f1..72f4714 100644 (file)
 # debian-related functionality
 
 import os
-from shutil import which, copyfile
-from .log import log, LOW
+import re
+from shutil import which, copyfile, copytree
+from shlex import quote as shell_quote
+from os.path import isdir, join, isfile, abspath, splitext, \
+    dirname, realpath
+from .log import log, fail, LOW
 from .utils import log_check_call, log_check_output, \
-    tmppath, join
+    tmppath, cached_property, get_filelist, calculate_md5_and_size, \
+    cached_constant, CURRENT_TIME
 from subprocess import CalledProcessError, DEVNULL
+from collections import OrderedDict
+from string import Template
+
+class Package:
+
+    def __init__(self, path):
+        self.path = path
+        self.debian = join(self.path, 'debian')
+        self.control = join(self.debian, 'control')
+        self.changelog = join(self.debian, 'changelog')
+        self.source_format = join(self.debian, 'source', 'format')
+
+    def is_valid(self):
+        '''verifies that the current directory is a debian package'''
+        return isdir(self.debian) and isfile(self.control) and \
+            isfile(self.source_format) and self.format in ['native', 'quilt']
+
+    def assert_is_valid(self):
+        if not self.is_valid():
+            fail('not in debian package directory')
+
+    @cached_property
+    def format(self):
+        with open(self.source_format) as f:
+            line = f.readline().strip()
+        m = re.match(r'^3\.0 \((native|quilt)\)', line)
+        if not m:
+            fail('unsupported format ({})', line)
+        fmt = m.group(1)
+        log("Detected format '{}'.".format(fmt), LOW)
+        return fmt
+
+    @cached_property
+    def native(self):
+        return self.format == 'native'
+
+    @cached_property
+    def name(self):
+        with open(self.control) as f:
+            for line in f:
+                m = re.match(r'^Source: (\S+)$', line)
+                if m:
+                    return m.group(1)
+        fail('could not find the name of the package')
+
+    @cached_property
+    def long_version(self):
+        '''long version'''
+        with open(self.changelog) as f:
+            line = f.readline()
+        m = re.match(r'^(\S+) \((\S+)\)', line)
+        if not m:
+            fail("could not parse package version (from '{}')", line.strip())
+        return m.group(2)
+
+    @cached_property
+    def version(self):
+        '''upstream version'''
+        if self.native:
+            return self.long_version
+        else:
+            m = re.match(r'^(.+)-(\d+)$', self.long_version)
+            if not m:
+                fail('could not parse version ({})', self.long_version)
+            return m.group(1)
+
+    @cached_property
+    def orig_tarball_candidates(self):
+        '''possible original upstream tarballs'''
+        formats = [ 'xz', 'gz', 'bz2' ]
+        names = [ '{}_{}.orig.tar.{}'.format(self.name, self.version, fmt)
+                  for fmt in formats ]
+        return names
+
+    @cached_property
+    def orig_tarball(self):
+        '''finds the original tarball'''
+        for name in self.orig_tarball_candidates:
+            tarball = join(self.path, '..', name)
+            log("Trying tarball candidate '{}'.".format(tarball), LOW)
+            if isfile(tarball):
+                log("Original tarball found at '{}'.".format(tarball), LOW)
+                return tarball
+        result = extract_pristine_tar(self.path, self.orig_tarball_candidates)
+        if result is not None:
+            return result
+        fail('could not find original tarball')
+
+    def assert_orig_tarball(self):
+        if self.native:
+            # for now, we just tar the current directory
+            path = tmppath('{}_{}.tar.xz'.format(
+                self.name, self.version))
+            with open(path, 'wb') as output:
+                tar = [ 'tar', 'c', '--xz', '--exclude=.git',
+                        '-C', self.path, '.' ]
+                log_check_call(tar, stdout = output)
+            return path
+        else:
+            return self.orig_tarball  # simple alias
+
+    def tar_package_debian(self, output, comp = None):
+        # TODO: make it reproducible
+        compressions = {
+            'xz': '--xz'
+        }
+        tar = [ 'tar', 'c' ]
+        if comp:
+            tar += [ compressions[comp] ]
+        debian = join(self.path, 'debian')
+        filelist = get_filelist(debian, self.path)
+        tar += [ '--no-recursion', '-C', self.path ]
+        tar += filelist
+        log_check_call(tar, stdout = output)
+
+    def tar_original_tarball(self, output, stdout):
+        orig = self.assert_orig_tarball()
+        return orig  # for now
+
+    def build_docker_tarball_to_fd(self, output, buildinfo):
+        '''builds the docker tarball that builds the package'''
+        controlfile = join(self.path, 'debian', 'control')
+        controlfile = abspath(controlfile)
+        debianfile = tmppath('debian.tar.xz')
+        with open(debianfile, 'wb') as debian:
+            self.tar_package_debian(debian, comp = 'xz')
+        originalfile = self.assert_orig_tarball()
+        originalfile = abspath(originalfile)
+        if self.native:
+            make_native_bundle(self.name, self.version,
+                               controlfile, originalfile,
+                               buildinfo, output)
+        else:
+            make_quilt_bundle(self.name, self.long_version,
+                              controlfile, originalfile, debianfile,
+                              buildinfo, output)
+
+    def build_docker_tarball(self, filename, buildinfo):
+        with open(filename, 'wb') as output:
+            self.build_docker_tarball_to_fd(output, buildinfo)
+
+
+STEPS = OrderedDict([
+    ('end', None),
+    ('build', 'args_05'),
+    ('extract-source', 'args_04'),
+    ('install-deps', 'args_03'),
+    ('install-utils', 'args_02'),
+    ('upgrade', 'args_01')
+])
+
+STAGES = STEPS.keys()
+
+class Bundler:
+
+    def __init__(self, name, version, control, dsc_name, buildinfo, native):
+        self.name = name
+        self.version = version
+        self.native = native
+        self.control = control
+        self.sources = []
+        self.dsc_name = dsc_name
+        self.step_name = buildinfo['step']
+        self.step = STEPS[self.step_name]
+        self.image = buildinfo['image']
+        self.buildinfo = buildinfo
+        self.wdir = tmppath('bundle', directory = True)
+
+    @property
+    def format_string(self):
+        return ('3.0 (native)' if self.native else '3.0 (quilt)')
+
+    def add_source_file(self, name, path, tag):
+        md5, size = calculate_md5_and_size(path)
+        self.sources.append({
+            'name': name,
+            'path': path,
+            'md5': md5,
+            'size': size,
+            'tag': tag
+        })
+
+    def write_dsc_file(self):
+        '''makes minimal, yet functional .dsc file'''
+        path = join(self.wdir, 'source', self.dsc_name)
+        with open(path, 'w') as f:
+            f.write('Format: {}\n'.format(self.format_string))
+            f.write('Source: {}\n'.format(self.name))
+            f.write('Version: {}\n'.format(self.version))
+            f.write('Files:\n')
+            for s in self.sources:
+                f.write(' {} {} {}\n'.format(s['md5'], s['size'], s['name']))
+
+    def write_info_file(self, info):
+        path = join(self.wdir, 'info')
+        with open(path, 'w') as f:
+            for k, v in info.items():
+                f.write("{}={}\n".format(k, v))
+
+    def write_buildinfo_file(self):
+        path = join(self.wdir, 'buildinfo')
+        with open(path, 'w') as f:
+            f.write("flags='{}'\n".format(self.buildinfo['flags']))
+
+    def write_bundle(self, output):
+        '''writes bundle to a given file'''
+
+        os.makedirs(join(self.wdir, 'source'))
+
+        info = OrderedDict()
+        info['bundle_version'] = __version__
+        info['name'] = self.name
+        info['version'] = self.version
+        info['format'] = ('native' if self.native else 'quilt')
+
+        def make_link(target, parts):
+            return os.symlink(target, join(self.wdir, *parts))
+
+        # control file
+        make_link(self.control, [ 'control' ])
+
+        # dsc file
+        self.write_dsc_file()
+        info['dsc_name'] = self.dsc_name
+
+        # sources
+        for s in self.sources:
+            name = s['name']
+            tag = s['tag']
+            make_link(s['path'], [ 'source', name ])
+            info[tag] = name
+
+        # info & buildinfo
+        self.write_info_file(info)
+        self.write_buildinfo_file()
+
+        # bundle files
+        bundle_files = find_bundle_files()
+
+        copytree(join(bundle_files, 'steps'),
+                 join(self.wdir, 'steps'))
+        dockertmpl = join(bundle_files, 'Dockerfile')
+        with open(dockertmpl, 'r') as df:
+            t = Template(df.read())
+            ctx = {
+                'image': self.image,
+                'args_01': 'none',
+                'args_02': 'none',
+                'args_03': 'none',
+                'args_04': 'none',
+                'args_05': 'none'
+            }
+            if self.step:
+                log("Replacing '{}' with '{}'.".format(
+                    self.step, CURRENT_TIME))
+                if self.step not in ctx:
+                    fail('internal error in dockerfile template')
+                ctx[self.step] = CURRENT_TIME
+            rendered = t.substitute(ctx)
+            dockerfile = join(self.wdir, 'Dockerfile')
+            with open(dockerfile, 'w') as f:
+                f.write(rendered)
+
+        file_list = get_filelist(self.wdir)
+        # tar everything
+        tar = [ 'tar', 'c', '-h', '--numeric-owner' ]
+        tar += [ '--no-recursion' ]
+        tar += [ '--owner=0', '--group=0' ]
+        tar += [ '--mtime=1970-01-01' ]
+        tar += [ '-C', self.wdir ]
+        tar += file_list
+        log_check_call(tar, stdout = output)
+
+def make_native_bundle(name, version, control,
+                       source, buildinfo, output):
+    dsc_name = '{}_{}.dsc'.format(name, version)
+    bundler = Bundler(name, version, control, dsc_name,
+                      buildinfo, native = True)
+    _, ext = splitext(source)
+    source_name = '{}_{}.tar{}'.format(name, version, ext)
+    bundler.add_source_file(source_name, source, 'source_tarball')
+    bundler.write_bundle(output = output)
+
+def make_quilt_bundle(name, version, control,
+                      original, debian, buildinfo, output):
+    dsc_name = '{}_{}.dsc'.format(name, version)
+    bundler = Bundler(name, version, control, dsc_name,
+                      buildinfo, native = False)
+    _, oext = splitext(original)
+    _, dext = splitext(debian)
+    # TODO: improve
+    uversion = version.split('-')[0]
+    original_name = '{}_{}.orig.tar{}'.format(name, uversion, oext)
+    debian_name = '{}_{}.debian.tar{}'.format(name, version, dext)
+    bundler.add_source_file(original_name, original, 'original_tarball')
+    bundler.add_source_file(debian_name, debian, 'debian_tarball')
+    bundler.write_bundle(output = output)
 
 def extract_pristine_tar(path, candidates):
+    # extracts a tarball using pristine-tar
     if which('pristine-tar') is None:
         log('No pristine-tar available.', LOW)
         return None
@@ -36,3 +339,37 @@ def extract_pristine_tar(path, candidates):
     else:
         log('No matching pristine tar found.', LOW)
         return None
+
+def docker_build_bundle(bundle, no_cache, pull):
+    '''builds the given image and returns the final image'''
+    # TODO: quite ugly, cannot be done cleaner?
+    build_log = tmppath()
+    bundle_esc = shell_quote(bundle)
+    build_log_esc = shell_quote(build_log)
+    docker_opts = []
+    if no_cache:
+        docker_opts.append('--no-cache')
+    if pull:
+        docker_opts.append('--pull')
+    docker_opts = ' '.join(docker_opts)
+    log_check_call('docker build {} - < {} | tee {}'.format(
+        docker_opts, bundle_esc, build_log_esc), shell = True)
+    with open(build_log) as f:
+        s = f.read().strip()
+        ms = re.findall(r'Successfully built (\S+)', s)
+        if len(ms) != 1:
+            fail('cannot parse logs (build failed?)')
+        image = ms[0]
+    return image
+
+@cached_constant
+def find_bundle_files():
+    '''finds the location of bundle files'''
+    debocker_dir = dirname(abspath(realpath(__file__)))
+    locations = [ debocker_dir, '/usr/share/debocker' ]
+    for loc in locations:
+        bundle_files = join(loc, 'bundle-files')
+        if isdir(bundle_files):
+            log("Bundle files found in '{}'.".format(bundle_files), LOW)
+            return bundle_files
+    fail('could not find bundle files')
index aa18ba9..0e8b0fc 100644 (file)
@@ -8,6 +8,14 @@ VERBOSITY = NONE
 def fail(msg, *params):
     raise click.ClickException(msg.format(*params))
 
+def set_verbosity(value):
+    global VERBOSITY
+    VERBOSITY = value
+
+def is_verbose():
+    # return True is there is positive verbosity set
+    return (VERBOSITY > NONE)
+
 def log(msg, v = 0):
     if VERBOSITY == NONE:
         if v == 0:
index 5006e3e..384d571 100644 (file)
@@ -4,10 +4,11 @@ import os
 import hashlib
 from tempfile import TemporaryDirectory
 from datetime import datetime
-from os.path import join, relpath
+from os.path import join, relpath, dirname, abspath, realpath, isdir
 from subprocess import check_call, check_output
+from shutil import which
 
-from .log import log, LOW
+from .log import log, fail, LOW
 
 def cached_constant(f):
     cache = []
@@ -79,3 +80,7 @@ def get_filelist(path, base = None):
         elements += [ join(p, x) for x in ds ]
         elements += [ join(p, x) for x in fs ]
     return sorted(elements)
+
+def assert_command(cmd):
+    if which(cmd) is None:
+        fail("command '{}' is not available", cmd)