Example #1
0
    def stage(self, startimage, newimage):
        """ Copies the file from source to target

        Args:
            startimage (str): name of the image to stage these files into
            newimage (str): name of the created image
        """
        client = utils.get_client()
        cprint(
            '  Copying file from "%s:/%s" \n                 to "%s://%s/"' %
            (self.sourceimage, self.sourcepath, startimage, self.destpath),
            'blue')

        # copy build artifacts from the container if necessary
        cachedir = self._setcache(client)
        cacherelpath = os.path.relpath(cachedir, TMPDIR)

        # if cached file doesn't exist (presumably purged by OS), trigger it to be recreated
        if os.path.exists(cachedir) and not os.path.exists(
                os.path.join(cachedir, 'content.tar')):
            shutil.rmtree(cachedir)

        if not os.path.exists(cachedir):
            print(' * Creating cache at %s' % cacherelpath)
            container = client.containers.create(self.sourceimage)
            try:
                tarfile_stream, tarfile_stats = container.get_archive(
                    self.sourcepath)
            except docker.errors.NotFound:
                raise errors.MissingFileError(
                    'Cannot copy file "%s" from image "%s" - it does not exist!'
                    % (self.sourcepath, self.sourceimage))

            # write files to disk (would be nice to stream them, haven't gotten it to work)
            tempdir = tempfile.mkdtemp(dir=BUILD_TEMPDIR)
            with open(os.path.join(tempdir, 'content.tar'), 'wb') as localfile:
                for chunk in tarfile_stream:
                    localfile.write(chunk)
            os.mkdir(cachedir)
            os.rename(tempdir, cachedir)
        else:
            print('  Using cached files from %s' % cacherelpath)

        # write Dockerfile for the new image and then build it
        dockerfile = 'FROM %s\nADD content.tar %s' % (startimage,
                                                      self.destpath)
        with open(os.path.join(cachedir, 'Dockerfile'), 'w') as df:
            df.write(dockerfile)

        buildargs = dict(path=cachedir, tag=newimage, decode=True)
        utils.set_build_cachefrom(self.cache_from, buildargs, client)

        # Build and show logs
        stream = client.api.build(**buildargs)
        try:
            utils.stream_docker_logs(stream, newimage)
        except ValueError as e:
            raise errors.BuildError(dockerfile,
                                    e.args[0],
                                    build_args=buildargs)
Example #2
0
 def dockerfile_lines(self):
     lines = ["FROM %s\n" % self.baseimage]
     if self.squash:
         lines.append("# This build step should be built with --squash")
     if self.secret_files:
         assert self.squash
         lines.append(
             ('RUN for file in %s; do if [ -e $file ]; then '
              'echo "ERROR: Secret file $file already exists."; exit 1; '
              'fi; done;') % (' '.join(self.secret_files)))
     try:
         # Get run command and preprocess any Jinja directives
         template = jinja2.Template(self.img_def.get('build', ''))
         content = template.render(self.buildargs if self.buildargs else {})
         lines.append(content)
     except Exception as e:
         raise errors.BuildError('Could not process build statements: %s',
                                 e)
     if self.secret_files:
         lines.append("RUN rm -rf %s" % (" ".join(self.secret_files)))
     return lines
Example #3
0
    def build(self, client, pull=False, usecache=True):
        """
        Drives an individual build step. Build steps are separated by build_directory.
        If a build has zero one or less build_directories, it will be built in a single
        step.

        Args:
            client (docker.Client): docker client object that will build the image
            pull (bool): whether to pull dependent layers from remote repositories
            usecache (bool): whether to use cached layers or rebuild from scratch
        """
        print(colored('  Building step', 'blue'),
              colored(self.imagename, 'blue', attrs=['bold']),
              colored('defined in', 'blue'),
              colored(self.sourcefile, 'blue', attrs=['bold']))

        if self.build_first and not self.build_first.built:
            self.build_external_dockerfile(client, self.build_first)

        if self.bust_cache:
            usecache = False

        if not usecache:
            cprint('  Build cache disabled - this image will be rebuilt from scratch',
                   'yellow')

        dockerfile = u'\n'.join(self.dockerfile_lines)

        kwargs = dict(tag=self.buildname,
                      pull=pull,
                      nocache=not usecache,
                      decode=True, rm=True,
                      buildargs=self.buildargs)

        if usecache:
            utils.set_build_cachefrom(self.cache_from, kwargs, client)

        if self.build_dir is not None:
            tempdir = self.write_dockerfile(dockerfile)
            context_path = os.path.abspath(os.path.expanduser(self.build_dir))
            kwargs.update(fileobj=None,
                              dockerfile=os.path.join(DOCKER_TMPDIR, 'Dockerfile'))
            print(colored('  Build context:', 'blue'),
                  colored(os.path.relpath(context_path), 'blue', attrs=['bold']))

            if not self.custom_exclude:
                kwargs.update(path=context_path)
            else:
                print(colored('  Custom .dockerignore from:','blue'),
                      colored(os.path.relpath(self.ignoredefs_file),  'blue', attrs=['bold']))
                context = docker.utils.tar(self.build_dir,
                                           exclude=self.custom_exclude,
                                           dockerfile=os.path.join(DOCKER_TMPDIR, 'Dockerfile'),
                                           gzip=False)
                kwargs.update(fileobj=context,
                                  custom_context=True)

        else:
            if sys.version_info.major == 2:
                kwargs.update(fileobj=StringIO(dockerfile),
                                  path=None,
                                  dockerfile=None)
            else:
                kwargs.update(fileobj=BytesIO(dockerfile.encode('utf-8')),
                                  path=None,
                                  dockerfile=None)

            tempdir = None

        # start the build
        stream = client.api.build(**kwargs)
        try:
            utils.stream_docker_logs(stream, self.buildname)
        except (ValueError, docker.errors.APIError) as e:
            raise errors.BuildError(dockerfile, str(e), kwargs)

        # remove the temporary dockerfile
        if tempdir is not None:
            os.unlink(os.path.join(tempdir, 'Dockerfile'))
            os.rmdir(tempdir)
Example #4
0
    def build(self, client, pull=False, usecache=True):
        """
        Drives an individual build step. Build steps are separated by build_directory.
        If a build has zero one or less build_directories, it will be built in a single
        step.

        Args:
            client (docker.Client): docker client object that will build the image
            pull (bool): whether to pull dependent layers from remote repositories
            usecache (bool): whether to use cached layers or rebuild from scratch
        """
        print(
            colored("  Building step", "blue"),
            colored(self.imagename, "blue", attrs=["bold"]),
            colored("defined in", "blue"),
            colored(self.sourcefile, "blue", attrs=["bold"]),
        )

        if self.build_first and not self.build_first.built:
            self.build_external_dockerfile(client, self.build_first)

        if self.bust_cache:
            usecache = False

        if not usecache:
            cprint(
                "  Build cache disabled - this image will be rebuilt from scratch",
                "yellow",
            )

        dockerfile = "\n".join(self.dockerfile_lines)

        kwargs = dict(
            tag=self.buildname,
            pull=pull,
            nocache=not usecache,
            decode=True,
            rm=True,
            buildargs=self.buildargs,
            squash=self.squash,
        )

        if usecache:
            utils.set_build_cachefrom(self.cache_from, kwargs, client)

        if self.build_dir is not None:
            tempdir = self.write_dockerfile(dockerfile)
            context_path = os.path.abspath(os.path.expanduser(self.build_dir))
            kwargs.update(fileobj=None,
                          dockerfile=os.path.join(DOCKER_TMPDIR, "Dockerfile"))
            print(
                colored("  Build context:", "blue"),
                colored(os.path.relpath(context_path), "blue", attrs=["bold"]),
            )

            if not self.custom_exclude:
                kwargs.update(path=context_path)
            else:
                print(
                    colored("  Custom .dockerignore from:", "blue"),
                    colored(os.path.relpath(self.ignoredefs_file),
                            "blue",
                            attrs=["bold"]),
                )

                # AMV - this is a brittle call to an apparently "private' docker sdk method
                context = docker.utils.tar(
                    self.build_dir,
                    exclude=self.custom_exclude,
                    dockerfile=(os.path.join(DOCKER_TMPDIR,
                                             "Dockerfile"), dockerfile),
                    gzip=False,
                )
                kwargs.update(fileobj=context, custom_context=True)

        else:
            if sys.version_info.major == 2:
                fileobj = StringIO(dockerfile)
            else:
                fileobj = BytesIO(dockerfile.encode("utf-8"))

            kwargs.update(fileobj=fileobj, path=None, dockerfile=None)

            tempdir = None

        # start the build
        stream = client.api.build(**kwargs)
        try:
            utils.stream_docker_logs(stream, self.buildname)
        except docker.errors.APIError as e:
            if self.squash and not client.version().get("Experimental", False):
                raise errors.ExperimentalDaemonRequiredError(
                    "Docker error message:\n   " + str(e) +
                    "\n\nUsing `squash` and/or `secret_files` requires a docker"
                    " daemon with experimental features enabled. See\n"
                    "    https://github.com/docker/docker-ce/blob/master/components/cli/"
                    "experimental/README.md")
            else:
                raise errors.BuildError(dockerfile, str(e), kwargs)
        except ValueError as e:
            raise errors.BuildError(dockerfile, str(e), kwargs)

        if self.squash and not self.bust_cache:
            self._resolve_squash_cache(client)

        # remove the temporary dockerfile
        if tempdir is not None:
            os.unlink(os.path.join(tempdir, "Dockerfile"))
            os.rmdir(tempdir)