diff --git a/compose/config/config.py b/compose/config/config.py index 91c2f6a62b9..f52853eab03 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -97,6 +97,8 @@ 'log_opt', 'logging', 'network_mode', + 'override_strategy', + 'overwrite', ] DOCKER_VALID_URL_PREFIXES = ( @@ -323,7 +325,6 @@ def load(config_details): if main_file.version != V1: for service_dict in service_dicts: match_named_volumes(service_dict, volumes) - return Config(main_file.version, service_dicts, volumes, networks) @@ -749,8 +750,17 @@ def merge_service_dicts(base, override, version): for field in ['volumes', 'devices']: md.merge_field(field, merge_path_mappings) + for field in ['ports']: + if ('override_strategy' in base and + base['override_strategy'] == 'overwrite'): + md.merge_field(field, merge_overwrite_items_lists, default=[]) + elif ('overwrite' in base and field in base['overwrite']): + md.merge_field(field, merge_overwrite_items_lists, default=[]) + else: + md.merge_field(field, merge_unique_items_lists, default=[]) + for field in [ - 'ports', 'cap_add', 'cap_drop', 'expose', 'external_links', + 'cap_add', 'cap_drop', 'expose', 'external_links', 'security_opt', 'volumes_from', 'depends_on', ]: md.merge_field(field, merge_unique_items_lists, default=[]) @@ -773,6 +783,10 @@ def merge_unique_items_lists(base, override): return sorted(set().union(base, override)) +def merge_overwrite_items_lists(base, override): + return sorted(set(override)) + + def merge_build(output, base, override): def to_dict(service): build_config = service.get('build', {}) diff --git a/compose/config/config_schema_v2.0.json b/compose/config/config_schema_v2.0.json index 59caac9b7f9..ccfdb9fc78f 100644 --- a/compose/config/config_schema_v2.0.json +++ b/compose/config/config_schema_v2.0.json @@ -211,7 +211,16 @@ "volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "volume_driver": {"type": "string"}, "volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, - "working_dir": {"type": "string"} + "working_dir": {"type": "string"}, + "overwrite": { + "type": "array", + "items": { + "type": ["string"], + "format": "overwrites" + }, + "uniqueItems": true + }, + "override_strategy": {"type": "string"} }, "dependencies": { diff --git a/compose/config/validation.py b/compose/config/validation.py index 7452e9849bb..e8800ae0118 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -42,6 +42,8 @@ VALID_NAME_CHARS = '[a-zA-Z0-9\._\-]' VALID_EXPOSE_FORMAT = r'^\d+(\-\d+)?(\/[a-zA-Z]+)?$' +VALID_OVERWRITE_FORMAT = r'^(ports)$' +VALID_OVERRIDE_STRATEGY_FORMAT = r'^(default|overwrite)$' @FormatChecker.cls_checks(format="ports", raises=ValidationError) @@ -63,6 +65,26 @@ def format_expose(instance): return True +@FormatChecker.cls_checks(format="overwrite", raises=ValidationError) +def format_overwrite(instance): + if isinstance(instance, six.string_types): + if not re.match(VALID_OVERWRITE_FORMAT, instance): + raise ValidationError( + "should be of the format 'ports'") + + return True + + +@FormatChecker.cls_checks(format="override_strategy", raises=ValidationError) +def format_override_strategy(instance): + if isinstance(instance, six.string_types): + if not re.match(VALID_OVERRIDE_STRATEGY_FORMAT, instance): + raise ValidationError( + "should be either 'default' or 'overwrite'") + + return True + + def match_named_volumes(service_dict, project_volumes): service_volumes = service_dict.get('volumes', []) for volume_spec in service_volumes: @@ -361,7 +383,7 @@ def process_config_schema_errors(error): def validate_against_config_schema(config_file): schema = load_jsonschema(config_file.version) - format_checker = FormatChecker(["ports", "expose"]) + format_checker = FormatChecker(["overwrite", "override_strategy", "ports", "expose"]) validator = Draft4Validator( schema, resolver=RefResolver(get_resolver_path(), schema),