Skip to content
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
13 changes: 9 additions & 4 deletions compose/cli/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ def project_from_options(project_dir, options, additional_options={}):
environment=environment,
override_dir=override_dir,
compatibility=options.get('--compatibility'),
interpolate=(not additional_options.get('--no-interpolate'))
interpolate=(not additional_options.get('--no-interpolate')),
skipped_sections=['services.build'] if '--no-build' in options else []
)


Expand All @@ -84,10 +85,14 @@ def get_config_from_options(base_dir, options, additional_options={}):
config_path = get_config_path_from_options(
base_dir, options, environment
)

skipped_sections = ['services.build'] if '--no-build' in options else []

return config.load(
config.find(base_dir, config_path, environment, override_dir),
options.get('--compatibility'),
not additional_options.get('--no-interpolate')
not additional_options.get('--no-interpolate'),
skipped_sections
)


Expand Down Expand Up @@ -125,14 +130,14 @@ def get_client(environment, verbose=False, version=None, tls_config=None, host=N

def get_project(project_dir, config_path=None, project_name=None, verbose=False,
host=None, tls_config=None, environment=None, override_dir=None,
compatibility=False, interpolate=True):
compatibility=False, interpolate=True, skipped_sections=[]):
if not environment:
environment = Environment.from_env_file(project_dir)
config_details = config.find(project_dir, config_path, environment, override_dir)
project_name = get_project_name(
config_details.working_dir, project_name, environment
)
config_data = config.load(config_details, compatibility, interpolate)
config_data = config.load(config_details, compatibility, interpolate, skipped_sections)

api_version = environment.get(
'COMPOSE_API_VERSION',
Expand Down
36 changes: 25 additions & 11 deletions compose/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,16 @@ def check_swarm_only_key(service_dicts, key):
check_swarm_only_key(service_dicts, 'configs')


def load(config_details, compatibility=False, interpolate=True):
def filter_sections(parent_section, skipped_sections, separator='.'):
"""Filters a list of sections to skip based on their parent section

Return a list of sections under the given parent section
"""
return [parts[2] for parts in (section.partition(separator) for section in skipped_sections)
if separator == parts[1] and parent_section == parts[0]]


def load(config_details, compatibility=False, interpolate=True, skipped_sections=[]):
"""Load the configuration from a working directory and a list of
configuration files. Files are loaded in order, and merged on top
of each other to create the final configuration.
Expand Down Expand Up @@ -401,7 +410,12 @@ def load(config_details, compatibility=False, interpolate=True):
configs = load_mapping(
config_details.config_files, 'get_configs', 'Config', config_details.working_dir
)
service_dicts = load_services(config_details, main_file, compatibility)
service_dicts = load_services(
config_details,
main_file,
compatibility,
filter_sections('services', skipped_sections)
)

if main_file.version != V1:
for service_dict in service_dicts:
Expand Down Expand Up @@ -453,7 +467,7 @@ def validate_external(entity_type, name, config, version):
entity_type, name, ', '.join(k for k in config if k != 'external')))


def load_services(config_details, config_file, compatibility=False):
def load_services(config_details, config_file, compatibility=False, skipped_sections=[]):
def build_service(service_name, service_dict, service_names):
service_config = ServiceConfig.with_abs_paths(
config_details.working_dir,
Expand All @@ -463,10 +477,10 @@ def build_service(service_name, service_dict, service_names):
resolver = ServiceExtendsResolver(
service_config, config_file, environment=config_details.environment
)
service_dict = process_service(resolver.run())
service_dict = process_service(resolver.run(), skipped_sections)

service_config = service_config._replace(config=service_dict)
validate_service(service_config, service_names, config_file)
validate_service(service_config, service_names, config_file, skipped_sections)
service_dict = finalize_service(
service_config,
service_names,
Expand Down Expand Up @@ -715,10 +729,10 @@ def validate_extended_service_dict(service_dict, filename, service):
"%s services with 'depends_on' cannot be extended" % error_prefix)


def validate_service(service_config, service_names, config_file):
def validate_service(service_config, service_names, config_file, skipped_sections=[]):
service_dict, service_name = service_config.config, service_config.name
validate_service_constraints(service_dict, service_name, config_file)
validate_paths(service_dict)
validate_paths(service_dict, skipped_sections)

validate_cpu(service_config)
validate_ulimits(service_config)
Expand All @@ -737,7 +751,7 @@ def validate_service(service_config, service_names, config_file):
.format(name=service_name))


def process_service(service_config):
def process_service(service_config, skipped_sections=[]):
working_dir = service_config.working_dir
service_dict = dict(service_config.config)

Expand All @@ -747,7 +761,7 @@ def process_service(service_config):
for path in to_list(service_dict['env_file'])
]

if 'build' in service_dict:
if 'build' in service_dict and 'build' not in skipped_sections:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the design for the feature is made in a generic fashion, but we end up just making a couple string matches (here and in validate_paths) which seems to defeat the purpose. What's the rationale for not removing the declared skipped_sections from the config entirely? Wouldn't it make #1973 simpler to implement down the line?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review.

Not much of a rationale except I didn't think about it and minimum entropy production principle...

Now that you mention it, I'm mostly fine with it except I'm not sure if we should change original config that downward components might need.

Pretty busy this week. Will see if I can update the PR if I get another sleepless night.

process_build_section(service_dict, working_dir)

if 'volumes' in service_dict and service_dict.get('volume_driver') is None:
Expand Down Expand Up @@ -1399,8 +1413,8 @@ def is_url(build_path):
return build_path.startswith(DOCKER_VALID_URL_PREFIXES)


def validate_paths(service_dict):
if 'build' in service_dict:
def validate_paths(service_dict, skipped_sections=[]):
if 'build' in service_dict and 'build' not in skipped_sections:
build = service_dict.get('build', {})

if isinstance(build, six.string_types):
Expand Down
29 changes: 29 additions & 0 deletions tests/unit/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ def secret_sort(secrets):

class ConfigTest(unittest.TestCase):

def test_filter_sections(self):
filtered_sections = config.filter_sections(
"services",
[
'secrets.build',
'services',
'services.image',
'services.build',
'networks.driver',
]
)

assert filtered_sections == [
'image',
'build',
]

def test_load(self):
service_dicts = config.load(
build_config_details(
Expand Down Expand Up @@ -4954,6 +4971,18 @@ def test_nonexistent_path(self):
)
)

def test_skip_nonexistent_path(self):
service_dict = config.load(
build_config_details(
{
'foo': {'build': 'nonexistent.path'},
},
'working_dir',
'filename.yml'
), skipped_sections=['services.build']
).services
assert service_dict[0]['name'] == 'foo'

def test_relative_path(self):
relative_build_path = '../build-ctx/'
service_dict = make_service_dict(
Expand Down