Skip to content

Commit

Permalink
Merge pull request #43 from avirshup/build-args
Browse files Browse the repository at this point in the history
Support docker build args
  • Loading branch information
avirshup authored Jan 11, 2018
2 parents 04df953 + 0ee7dff commit b7b40f0
Show file tree
Hide file tree
Showing 7 changed files with 64 additions and 19 deletions.
3 changes: 3 additions & 0 deletions dockermake/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ def make_arg_parser():
help="Print or build all images (or those specified by _ALL_)")
bo.add_argument('-l', '--list', action='store_true',
help='List all available targets in the file, then exit.')
bo.add_argument('--build-arg', action='append',
help="Set build-time variables (used the same way as docker build --build-arg)"
', e.g., `... --build-arg VAR1=val1 --build-arg VAR2=val2`')
bo.add_argument('--requires', nargs="*",
help='Build a special image from these requirements. Requires --name')
bo.add_argument('--name', type=str,
Expand Down
4 changes: 4 additions & 0 deletions dockermake/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ class MultipleIgnoreError(UserException):
CODE = 51


class CLIError(UserException):
CODE = 52


class BuildError(Exception):
CODE = 200

Expand Down
6 changes: 4 additions & 2 deletions dockermake/imagedefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def _check_yaml_and_paths(ymlfilepath, yamldefs):
(key, imagename, relpath))

def generate_build(self, image, targetname, rebuilds=None, cache_repo='', cache_tag='',
**kwargs):
buildargs=None, **kwargs):
"""
Separate the build into a series of one or more intermediate steps.
Each specified build directory gets its own step
Expand All @@ -133,6 +133,7 @@ def generate_build(self, image, targetname, rebuilds=None, cache_repo='', cache_
rebuilds (List[str]): list of image layers to rebuild (i.e., without docker's cache)
cache_repo (str): repository to get images for caches in builds
cache_tag (str): tags to use from repository for caches in builds
buildargs (dict): build-time dockerfile arugments
**kwargs (dict): extra keyword arguments for the BuildTarget object
"""
from_image = self.get_external_base_image(image)
Expand Down Expand Up @@ -163,7 +164,8 @@ def generate_build(self, image, targetname, rebuilds=None, cache_repo='', cache_
dockermake.step.BuildStep(
base_name, base_image, self.ymldefs[base_name],
buildname, bust_cache=base_name in rebuilds,
build_first=build_first, cache_from=cache_from))
build_first=build_first, cache_from=cache_from,
buildargs=buildargs))

base_image = buildname
build_first = None
Expand Down
30 changes: 17 additions & 13 deletions dockermake/step.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,12 @@ class BuildStep(object):
buildname (str): what to call this image, once built
bust_cache(bool): never use docker cache for this build step
cache_from (str or list): use this(these) image(s) to resolve build cache
buildargs (dict): build-time "buildargs" for dockerfiles
"""

def __init__(self, imagename, baseimage, img_def, buildname,
build_first=None, bust_cache=False, cache_from=None):
build_first=None, bust_cache=False, cache_from=None,
buildargs=None):
self.imagename = imagename
self.baseimage = baseimage
self.img_def = img_def
Expand All @@ -50,6 +52,7 @@ def __init__(self, imagename, baseimage, img_def, buildname,
self.build_first = build_first
self.custom_exclude = self._get_ignorefile(img_def)
self.ignoredefs_file = img_def.get('ignorefile', img_def['_sourcefile'])
self.buildargs = buildargs
if cache_from and isinstance(cache_from, str):
self.cache_from = [cache_from]
else:
Expand Down Expand Up @@ -99,52 +102,53 @@ def build(self, client, pull=False, usecache=True):

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

build_args = dict(tag=self.buildname,
pull=pull,
nocache=not usecache,
decode=True, rm=True)
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, build_args, client)
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))
build_args.update(fileobj=None,
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:
build_args.update(path=context_path)
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)
build_args.update(fileobj=context,
kwargs.update(fileobj=context,
custom_context=True)

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

tempdir = None

# start the build
stream = client.api.build(**build_args)
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), build_args)
raise errors.BuildError(dockerfile, str(e), kwargs)

# remove the temporary dockerfile
if tempdir is not None:
Expand Down
29 changes: 25 additions & 4 deletions dockermake/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

import yaml
import docker.errors
from termcolor import cprint
from termcolor import cprint, colored

from . import errors

Expand Down Expand Up @@ -81,7 +81,7 @@ def get_build_targets(args, defs):
elif args.all:
# build all targets in the file
assert len(args.TARGETS) == 0, "Pass either a list of targets or `--all`, not both"
if defs.all_targets is not None:
if defs.all_targets:
targets = defs.all_targets
else:
targets = list(defs.ymldefs.keys())
Expand All @@ -106,15 +106,20 @@ def build_targets(args, defs, targets):
password=args.registry_token,
registry=registry,
reauth=True)
print("\nREGISTRY LOGIN SUCCESS:",registry)
print("\nREGISTRY LOGIN SUCCESS:", registry)

if args.build_arg:
buildargs = _make_buildargs(args.build_arg)
else:
buildargs = None
built, warnings = [], []
builders = [defs.generate_build(t,
generate_name(t, args.repository, args.tag),
rebuilds=args.bust_cache,
cache_repo=args.cache_repo,
cache_tag=args.cache_tag,
keepbuildtags=args.keep_build_tags)
keepbuildtags=args.keep_build_tags,
buildargs=buildargs)
for t in targets]
for b in builders:
b.build(client,
Expand All @@ -139,6 +144,22 @@ def build_targets(args, defs, targets):
return built, warnings


def _make_buildargs(build_args):
if build_args:
cprint('Build arguments:', attrs=['bold'])
argdict = {}
for buildarg in build_args:
try:
split = buildarg.index('=')
except ValueError:
raise errors.CLIError('Buildarg options must be passed as --build-arg NAME=VALUE')
argname = buildarg[:split]
argval = buildarg[split+1:]
argdict[argname] = argval
print(' -', colored(argname, 'yellow'), '=', colored(argval, 'blue'))
return argdict


def push(client, name):
success = False
warnings = []
Expand Down
5 changes: 5 additions & 0 deletions test/data/build-args.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
target-buildargs:
FROM: continuumio/miniconda3
build: |
ARG FILENAME
RUN echo 'hello world' > ${FILENAME}
6 changes: 6 additions & 0 deletions test/test_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,9 @@ def test_keep_build_tags(twostep, docker_client):
run_docker_make('-f data/twostep.yml target-twostep --keep-build-tags')
docker_client.images.get('dmkbuild_target-twostep_1')
docker_client.images.get('dmkbuild_target-twostep_2')


buildargs = creates_images('target-buildargs')
def test_build_args(buildargs):
run_docker_make('-f data/build-args.yml --build-arg FILENAME=hello-world.txt target-buildargs')
assert_file_content('target-buildargs', 'hello-world.txt', 'hello world')

0 comments on commit b7b40f0

Please sign in to comment.