diff --git a/compose/cli/command.py b/compose/cli/command.py index f18f76639..e907a05cc 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -66,7 +66,6 @@ def project_from_options(project_dir, options, additional_options=None): context=context, environment=environment, override_dir=override_dir, - compatibility=compatibility_from_options(project_dir, options, environment), interpolate=(not additional_options.get('--no-interpolate')), environment_file=environment_file ) @@ -98,7 +97,6 @@ def get_config_from_options(base_dir, options, additional_options=None): ) return config.load( config.find(base_dir, config_path, environment, override_dir), - compatibility_from_options(config_path, options, environment), not additional_options.get('--no-interpolate') ) @@ -120,14 +118,14 @@ def get_config_path_from_options(base_dir, options, environment): def get_project(project_dir, config_path=None, project_name=None, verbose=False, context=None, environment=None, override_dir=None, - compatibility=False, interpolate=True, environment_file=None): + interpolate=True, environment_file=None): 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, interpolate) api_version = environment.get( 'COMPOSE_API_VERSION', @@ -188,13 +186,3 @@ def get_project_name(working_dir, project_name=None, environment=None): return normalize_name(project) return 'default' - - -def compatibility_from_options(working_dir, options=None, environment=None): - """Get compose v3 compatibility from --compatibility option - or from COMPOSE_COMPATIBILITY environment variable.""" - - compatibility_option = options.get('--compatibility') - compatibility_environment = environment.get_boolean('COMPOSE_COMPATIBILITY') - - return compatibility_option or compatibility_environment diff --git a/compose/config/config.py b/compose/config/config.py index 1557e52d0..5eed6b59a 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -365,7 +365,7 @@ def find_candidates_in_parent_dirs(filenames, path): return (candidates, path) -def check_swarm_only_config(service_dicts, compatibility=False): +def check_swarm_only_config(service_dicts): warning_template = ( "Some services ({services}) use the '{key}' key, which will be ignored. " "Compose does not support '{key}' configuration - use " @@ -386,7 +386,7 @@ def check_swarm_only_config(service_dicts, compatibility=False): check_swarm_only_key(service_dicts, 'configs') -def load(config_details, compatibility=False, interpolate=True): +def load(config_details, interpolate=True): """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. @@ -416,13 +416,13 @@ 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, interpolate=interpolate) + service_dicts = load_services(config_details, main_file, interpolate=interpolate) if main_file.version != V1: for service_dict in service_dicts: match_named_volumes(service_dict, volumes) - check_swarm_only_config(service_dicts, compatibility) + check_swarm_only_config(service_dicts) version = main_file.version @@ -469,7 +469,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, interpolate=True): +def load_services(config_details, config_file, interpolate=True): def build_service(service_name, service_dict, service_names): service_config = ServiceConfig.with_abs_paths( config_details.working_dir, @@ -488,7 +488,6 @@ def load_services(config_details, config_file, compatibility=False, interpolate= service_names, config_file.version, config_details.environment, - compatibility, interpolate ) return service_dict @@ -887,7 +886,7 @@ def finalize_service_volumes(service_dict, environment): return service_dict -def finalize_service(service_config, service_names, version, environment, compatibility, +def finalize_service(service_config, service_names, version, environment, interpolate=True): service_dict = dict(service_config.config) @@ -929,17 +928,6 @@ def finalize_service(service_config, service_names, version, environment, compat normalize_build(service_dict, service_config.working_dir, environment) - if compatibility: - service_dict = translate_credential_spec_to_security_opt(service_dict) - service_dict, ignored_keys = translate_deploy_keys_to_container_config( - service_dict - ) - if ignored_keys: - log.warning( - 'The following deploy sub-keys are not supported in compatibility mode and have' - ' been ignored: {}'.format(', '.join(ignored_keys)) - ) - service_dict['name'] = service_config.name return normalize_v1_service_format(service_dict) @@ -973,62 +961,6 @@ def convert_credential_spec_to_security_opt(credential_spec): return 'registry://{registry}'.format(registry=credential_spec['registry']) -def translate_credential_spec_to_security_opt(service_dict): - result = [] - - if 'credential_spec' in service_dict: - spec = convert_credential_spec_to_security_opt(service_dict['credential_spec']) - result.append('credentialspec={spec}'.format(spec=spec)) - - if result: - service_dict['security_opt'] = result - - return service_dict - - -def translate_deploy_keys_to_container_config(service_dict): - if 'credential_spec' in service_dict: - del service_dict['credential_spec'] - if 'configs' in service_dict: - del service_dict['configs'] - - if 'deploy' not in service_dict: - return service_dict, [] - - deploy_dict = service_dict['deploy'] - ignored_keys = [ - k for k in ['endpoint_mode', 'labels', 'update_config', 'rollback_config'] - if k in deploy_dict - ] - - if 'replicas' in deploy_dict and deploy_dict.get('mode', 'replicated') == 'replicated': - scale = deploy_dict.get('replicas', 1) - max_replicas = deploy_dict.get('placement', {}).get('max_replicas_per_node', scale) - service_dict['scale'] = min(scale, max_replicas) - if max_replicas < scale: - log.warning("Scale is limited to {} ('max_replicas_per_node' field).".format( - max_replicas)) - - if 'restart_policy' in deploy_dict: - service_dict['restart'] = { - 'Name': convert_restart_policy(deploy_dict['restart_policy'].get('condition', 'any')), - 'MaximumRetryCount': deploy_dict['restart_policy'].get('max_attempts', 0) - } - for k in deploy_dict['restart_policy'].keys(): - if k != 'condition' and k != 'max_attempts': - ignored_keys.append('restart_policy.{}'.format(k)) - - ignored_keys.extend( - translate_resource_keys_to_container_config( - deploy_dict.get('resources', {}), service_dict - ) - ) - - del service_dict['deploy'] - - return service_dict, ignored_keys - - def normalize_v1_service_format(service_dict): if 'log_driver' in service_dict or 'log_opt' in service_dict: if 'logging' not in service_dict: diff --git a/compose/project.py b/compose/project.py index 4c8b197f2..216615aee 100644 --- a/compose/project.py +++ b/compose/project.py @@ -123,6 +123,8 @@ class Project(object): service_dict.pop('secrets', None) or [], config_data.secrets) + service_dict['scale'] = project.get_service_scale(service_dict) + project.services.append( Service( service_dict.pop('name'), @@ -262,6 +264,23 @@ class Project(object): return PidMode(pid_mode) + def get_service_scale(self, service_dict): + # service.scale for v2 and deploy.replicas for v3 + scale = service_dict.get('scale', 1) + deploy_dict = service_dict.get('deploy', None) + if deploy_dict: + if deploy_dict.get('mode', 'replicated') == 'replicated': + scale = deploy_dict.get('replicas', scale) + # deploy may contain placement constraints introduced in v3.8 + max_replicas = deploy_dict.get('placement', {}).get( + 'max_replicas_per_node', + scale) + scale = min(scale, max_replicas) + if max_replicas < scale: + log.warning("Scale is limited to {} ('max_replicas_per_node' field).".format( + max_replicas)) + return scale + def start(self, service_names=None, **options): containers = [] diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 612d0f38f..4196d8d83 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -3655,6 +3655,7 @@ class InterpolationTest(unittest.TestCase): assert 'BAR' in warnings[0] assert 'FOO' in warnings[1] + @pytest.mark.skip(reason='compatibility mode was removed internally') def test_compatibility_mode_warnings(self): config_details = build_config_details({ 'version': '3.5', @@ -3693,6 +3694,7 @@ class InterpolationTest(unittest.TestCase): assert 'restart_policy.delay' in warn_message assert 'restart_policy.window' in warn_message + @pytest.mark.skip(reason='compatibility mode was removed internally') def test_compatibility_mode_load(self): config_details = build_config_details({ 'version': '3.5',