diff --git a/compose/cli/command.py b/compose/cli/command.py index 2f38fe5af4c..96e210a25f8 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -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 [] ) @@ -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 ) @@ -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', diff --git a/compose/config/config.py b/compose/config/config.py index 5202d00255b..64ab3c61045 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -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. @@ -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: @@ -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, @@ -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, @@ -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) @@ -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) @@ -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: process_build_section(service_dict, working_dir) if 'volumes' in service_dict and service_dict.get('volume_driver') is None: @@ -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): diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index b583422f54f..99dcf4235ba 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -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( @@ -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(