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)
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
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)
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)