Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce a new env var "COMPOSE_DOCKER_CLI_BUILD_EXTRA_ARGS" #7296

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions compose/cli/command.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
import os
import re
import shlex

from . import errors
from .. import config
Expand All @@ -11,6 +12,7 @@
from ..const import LABEL_ENVIRONMENT_FILE
from ..const import LABEL_WORKING_DIR
from ..project import Project
from ..service import _CLIBuilder
from .docker_client import get_client
from .docker_client import load_context
from .docker_client import make_context
Expand Down Expand Up @@ -137,13 +139,25 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False,
verbose=verbose, version=api_version, context=context, environment=environment
)

native_builder = environment.get_boolean('COMPOSE_DOCKER_CLI_BUILD')
if native_builder:
native_builder_extra_args = environment.get('COMPOSE_DOCKER_CLI_BUILD_EXTRA_ARGS')
arg_modifiers = []
if native_builder_extra_args:
splitted_args = shlex.split(native_builder_extra_args)
arg_modifiers.append(lambda command_builder: command_builder.add_bare_args(*splitted_args))
builder = _CLIBuilder(arg_modifiers)
else:
builder = None

with errors.handle_connection_errors(client):
return Project.from_config(
project_name,
config_data,
client,
environment.get('DOCKER_DEFAULT_PLATFORM'),
execution_context_labels(config_details, environment_file),
builder=builder,
)


Expand Down
6 changes: 0 additions & 6 deletions compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,6 @@ def build(self, options):
)
build_args = resolve_build_args(build_args, self.toplevel_environment)

native_builder = self.toplevel_environment.get_boolean('COMPOSE_DOCKER_CLI_BUILD')

self.project.build(
service_names=options['SERVICE'],
no_cache=bool(options.get('--no-cache', False)),
Expand All @@ -296,7 +294,6 @@ def build(self, options):
gzip=options.get('--compress', False),
parallel_build=options.get('--parallel', False),
silent=options.get('--quiet', False),
cli=native_builder,
progress=options.get('--progress'),
)

Expand Down Expand Up @@ -1046,8 +1043,6 @@ def up(self, options):
for excluded in [x for x in opts if options.get(x) and no_start]:
raise UserError('--no-start and {} cannot be combined.'.format(excluded))

native_builder = self.toplevel_environment.get_boolean('COMPOSE_DOCKER_CLI_BUILD')

with up_shutdown_context(self.project, service_names, timeout, detached):
warn_for_swarm_mode(self.project.client)

Expand All @@ -1067,7 +1062,6 @@ def up(rebuild):
reset_container_image=rebuild,
renew_anonymous_volumes=options.get('--renew-anon-volumes'),
silent=options.get('--quiet-pull'),
cli=native_builder,
)

try:
Expand Down
26 changes: 17 additions & 9 deletions compose/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,15 @@ class Project(object):
"""
A collection of services.
"""
def __init__(self, name, services, client, networks=None, volumes=None, config_version=None):
def __init__(self, name, services, client, networks=None, volumes=None, config_version=None,
builder=None):
self.name = name
self.services = services
self.client = client
self.volumes = volumes or ProjectVolumes({})
self.networks = networks or ProjectNetworks({}, False)
self.config_version = config_version
self.builder = builder

def labels(self, one_off=OneOffFilter.exclude, legacy=False):
name = self.name
Expand All @@ -83,7 +85,8 @@ def labels(self, one_off=OneOffFilter.exclude, legacy=False):
return labels

@classmethod
def from_config(cls, name, config_data, client, default_platform=None, extra_labels=None):
def from_config(cls, name, config_data, client, default_platform=None, extra_labels=None,
builder=None):
"""
Construct a Project from a config.Config object.
"""
Expand All @@ -95,7 +98,7 @@ def from_config(cls, name, config_data, client, default_platform=None, extra_lab
networks,
use_networking)
volumes = ProjectVolumes.from_config(name, config_data, client)
project = cls(name, [], client, project_networks, volumes, config_data.version)
project = cls(name, [], client, project_networks, volumes, config_data.version, builder=builder)

for service_dict in config_data.services:
service_dict = dict(service_dict)
Expand Down Expand Up @@ -138,6 +141,7 @@ def from_config(cls, name, config_data, client, default_platform=None, extra_lab
platform=service_dict.pop('platform', None),
default_platform=default_platform,
extra_labels=extra_labels,
builder=builder,
**service_dict)
)

Expand Down Expand Up @@ -262,6 +266,10 @@ def get_pid_mode(self, service_dict):

return PidMode(pid_mode)

@property
def native_build_enabled(self):
return self.builder is not None and self.builder is not self.client

def start(self, service_names=None, **options):
containers = []

Expand Down Expand Up @@ -358,7 +366,7 @@ def restart(self, service_names=None, **options):
return containers

def build(self, service_names=None, no_cache=False, pull=False, force_rm=False, memory=None,
build_args=None, gzip=False, parallel_build=False, rm=True, silent=False, cli=False,
build_args=None, gzip=False, parallel_build=False, rm=True, silent=False,
progress=None):

services = []
Expand All @@ -368,7 +376,7 @@ def build(self, service_names=None, no_cache=False, pull=False, force_rm=False,
elif not silent:
log.info('%s uses an image, skipping' % service.name)

if cli:
if self.native_build_enabled:
log.warning("Native build is an experimental feature and could change at any time")
if parallel_build:
log.warning("Flag '--parallel' is ignored when building with "
Expand All @@ -378,7 +386,8 @@ def build(self, service_names=None, no_cache=False, pull=False, force_rm=False,
"COMPOSE_DOCKER_CLI_BUILD=1")

def build_service(service):
service.build(no_cache, pull, force_rm, memory, build_args, gzip, rm, silent, cli, progress)
service.build(no_cache, pull, force_rm, memory, build_args, gzip, rm, silent,
progress=progress)

if parallel_build:
_, errors = parallel.parallel_execute(
Expand Down Expand Up @@ -523,10 +532,9 @@ def up(self,
reset_container_image=False,
renew_anonymous_volumes=False,
silent=False,
cli=False,
):

if cli:
if self.native_build_enabled:
log.warning("Native build is an experimental feature and could change at any time")

self.initialize()
Expand All @@ -541,7 +549,7 @@ def up(self,
include_deps=start_deps)

for svc in services:
svc.ensure_image_exists(do_build=do_build, silent=silent, cli=cli)
svc.ensure_image_exists(do_build=do_build, silent=silent)
plans = self._get_convergence_plans(
services, strategy, always_recreate_deps=always_recreate_deps)

Expand Down
32 changes: 22 additions & 10 deletions compose/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,12 @@ def __init__(
pid_mode=None,
default_platform=None,
extra_labels=None,
builder=None,
**options
):
self.name = name
self.client = client
self.builder = client if builder is None else builder
self.project = project
self.use_networking = use_networking
self.links = links or []
Expand Down Expand Up @@ -339,9 +341,9 @@ def create_container(self,
raise OperationFailedError("Cannot create container for service %s: %s" %
(self.name, binarystr_to_unicode(ex.explanation)))

def ensure_image_exists(self, do_build=BuildAction.none, silent=False, cli=False):
def ensure_image_exists(self, do_build=BuildAction.none, silent=False):
if self.can_be_built() and do_build == BuildAction.force:
self.build(cli=cli)
self.build()
return

try:
Expand All @@ -357,7 +359,7 @@ def ensure_image_exists(self, do_build=BuildAction.none, silent=False, cli=False
if do_build == BuildAction.skip:
raise NeedsBuildError(self)

self.build(cli=cli)
self.build()
log.warning(
"Image for service {} was built because it did not already exist. To "
"rebuild this image you must use `docker-compose build` or "
Expand Down Expand Up @@ -1053,8 +1055,12 @@ def build_spec(secret):

return [build_spec(secret) for secret in self.secrets]

@property
def native_build_enabled(self):
return self.builder is not self.client

def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_args_override=None,
gzip=False, rm=True, silent=False, cli=False, progress=None):
gzip=False, rm=True, silent=False, progress=None):
output_stream = open(os.devnull, 'w')
if not silent:
output_stream = sys.stdout
Expand All @@ -1075,8 +1081,8 @@ def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_a
'Impossible to perform platform-targeted builds for API version < 1.35'
)

builder = self.client if not cli else _CLIBuilder(progress)
build_output = builder.build(
extra = {"progress": progress} if self.native_build_enabled else {}
build_output = self.builder.build(
path=path,
tag=self.image_name,
rm=rm,
Expand All @@ -1097,6 +1103,7 @@ def build(self, no_cache=False, pull=False, force_rm=False, memory=None, build_a
gzip=gzip,
isolation=build_opts.get('isolation', self.options.get('isolation', None)),
platform=self.platform,
**extra
)

try:
Expand Down Expand Up @@ -1707,8 +1714,8 @@ def rewrite_build_path(path):


class _CLIBuilder(object):
def __init__(self, progress):
self._progress = progress
def __init__(self, arg_modifiers=[]):
self._arg_modifiers = arg_modifiers

def build(self, path, tag=None, quiet=False, fileobj=None,
nocache=False, rm=False, timeout=None,
Expand All @@ -1717,7 +1724,7 @@ def build(self, path, tag=None, quiet=False, fileobj=None,
decode=False, buildargs=None, gzip=False, shmsize=None,
labels=None, cache_from=None, target=None, network_mode=None,
squash=None, extra_hosts=None, platform=None, isolation=None,
use_config_proxy=True):
use_config_proxy=True, progress=None):
"""
Args:
path (str): Path to the directory containing the Dockerfile
Expand Down Expand Up @@ -1783,11 +1790,13 @@ def build(self, path, tag=None, quiet=False, fileobj=None,
command_builder.add_params("--label", labels)
command_builder.add_arg("--memory", container_limits.get("memory"))
command_builder.add_flag("--no-cache", nocache)
command_builder.add_arg("--progress", self._progress)
command_builder.add_arg("--progress", progress)
command_builder.add_flag("--pull", pull)
command_builder.add_arg("--tag", tag)
command_builder.add_arg("--target", target)
command_builder.add_arg("--iidfile", iidfile)
for arg_modifier in self._arg_modifiers:
arg_modifier(command_builder)
args = command_builder.build([path])

magic_word = "Successfully built "
Expand Down Expand Up @@ -1836,5 +1845,8 @@ def add_list(self, name, values):
for val in values:
self._args.extend([name, val])

def add_bare_args(self, *args):
self._args.extend(args)

def build(self, args):
return self._args + args
33 changes: 31 additions & 2 deletions tests/integration/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from compose.parallel import ParallelStreamWriter
from compose.project import OneOffFilter
from compose.project import Project
from compose.service import _CLIBuilder
from compose.service import BuildAction
from compose.service import ConvergencePlan
from compose.service import ConvergenceStrategy
Expand Down Expand Up @@ -974,13 +975,15 @@ def test_build_cli(self):
with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("FROM busybox\n")

builder = _CLIBuilder()
service = self.create_service('web',
build={'context': base_dir},
environment={
'COMPOSE_DOCKER_CLI_BUILD': '1',
'DOCKER_BUILDKIT': '1',
})
service.build(cli=True)
},
builder=builder)
service.build()
self.addCleanup(self.client.remove_image, service.image_name)
assert self.client.inspect_image('composetest_web')

Expand Down Expand Up @@ -1021,6 +1024,32 @@ def test_up_build_cli(self):
assert len(containers) == 1
assert containers[0].name.startswith('composetest_web_')

def test_up_build_cli_extra_args(self):
base_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir)

with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f:
f.write("FROM busybox\n")
f.write("ARG HELLO=\n")
f.write("ENV WORLD ${HELLO}\n")

builder = _CLIBuilder([
lambda command_builder: command_builder.add_arg('--build-arg', 'HELLO=WORLD'),
])
web = self.create_service('web',
build={'context': base_dir},
environment={
'COMPOSE_DOCKER_CLI_BUILD': '1',
'DOCKER_BUILDKIT': '1',
},
builder=builder)
project = Project('composetest', [web], self.client, builder=builder)
project.up(do_build=BuildAction.force)

containers = project.containers(['web'])
assert len(containers) == 1
assert containers[0].environment['WORLD'] == 'WORLD'

def test_build_non_ascii_filename(self):
base_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, base_dir)
Expand Down
Loading