From 446c941cd4325cf8bb099e186b7bcbf751f9a308 Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Wed, 24 Jul 2019 16:21:29 +0200 Subject: [PATCH 1/7] Add project_data top-level config key Signed-off-by: Felipe Martinez --- compose/cli/command.py | 10 +++++++--- compose/config/config.py | 23 +++++++++++++++++++++-- compose/config/config_schema_v3.7.json | 4 ++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/compose/cli/command.py b/compose/cli/command.py index 2f38fe5af4..00c088084d 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -129,10 +129,10 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False, if not environment: environment = Environment.from_env_file(project_dir) config_details = config.find(project_dir, config_path, environment, override_dir) + config_data = config.load(config_details, compatibility, interpolate) project_name = get_project_name( - config_details.working_dir, project_name, environment + config_details.working_dir, config_data, project_name, environment ) - config_data = config.load(config_details, compatibility, interpolate) api_version = environment.get( 'COMPOSE_API_VERSION', @@ -149,7 +149,7 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False, ) -def get_project_name(working_dir, project_name=None, environment=None): +def get_project_name(working_dir, config_data, project_name=None, environment=None): def normalize_name(name): return re.sub(r'[^-_a-z0-9]', '', name.lower()) @@ -158,6 +158,10 @@ def normalize_name(name): project_name = project_name or environment.get('COMPOSE_PROJECT_NAME') if project_name: return normalize_name(project_name) + + project_name = config_data.project_name + if project_name: + return normalize_name(project_name) project = os.path.basename(os.path.abspath(working_dir)) if project: diff --git a/compose/config/config.py b/compose/config/config.py index 5202d00255..f27a5d9b58 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -240,8 +240,11 @@ def get_secrets(self): def get_configs(self): return {} if self.version < const.COMPOSEFILE_V3_3 else self.config.get('configs', {}) + def get_project_name(self): + return {} if self.version < const.COMPOSEFILE_V3_7 else self.config.get('project_name', '') -class Config(namedtuple('_Config', 'version services volumes networks secrets configs')): + +class Config(namedtuple('_Config', 'version services volumes networks secrets configs project_name')): """ :param version: configuration version :type version: int @@ -255,6 +258,8 @@ class Config(namedtuple('_Config', 'version services volumes networks secrets co :type secrets: :class:`dict` :param configs: Dictionary mapping config names to description dictionaries :type configs: :class:`dict` + :param project_name The name of the project + :type project_name: str """ @@ -401,6 +406,12 @@ def load(config_details, compatibility=False, interpolate=True): configs = load_mapping( config_details.config_files, 'get_configs', 'Config', config_details.working_dir ) + + for config_file in config_details.config_files: + name = config_file.get_project_name() + if name: + project_name = name + service_dicts = load_services(config_details, main_file, compatibility) if main_file.version != V1: @@ -411,7 +422,7 @@ def load(config_details, compatibility=False, interpolate=True): version = V2_3 if compatibility and main_file.version >= V3_0 else main_file.version - return Config(version, service_dicts, volumes, networks, secrets, configs) + return Config(version, service_dicts, volumes, networks, secrets, configs, project_name) def load_mapping(config_files, get_func, entity_type, working_dir=None): @@ -568,6 +579,14 @@ def process_config_file(config_file, environment, service_name=None, interpolate environment, interpolate, ) + if config_file.version > const.COMPOSEFILE_V3_7: + processed_config['project_name'] = process_config_section( + config_file, + config_file.get_project_name(), + 'project_name', + environment, + interpolate, + ) else: processed_config = services diff --git a/compose/config/config_schema_v3.7.json b/compose/config/config_schema_v3.7.json index cd7882f5b2..60dcbb7693 100644 --- a/compose/config/config_schema_v3.7.json +++ b/compose/config/config_schema_v3.7.json @@ -9,6 +9,10 @@ "type": "string" }, + "project_name": { + "type": "string" + }, + "services": { "id": "#/properties/services", "type": "object", From 49e13a71ed78d10fb065a05fe68648d497147094 Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Wed, 24 Jul 2019 17:11:11 +0200 Subject: [PATCH 2/7] Remove trailing whitespace Signed-off-by: Felipe Martinez --- compose/cli/command.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/cli/command.py b/compose/cli/command.py index 00c088084d..a121e9ba1a 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -158,7 +158,7 @@ def normalize_name(name): project_name = project_name or environment.get('COMPOSE_PROJECT_NAME') if project_name: return normalize_name(project_name) - + project_name = config_data.project_name if project_name: return normalize_name(project_name) From ea77a3e77403885c3af9c9e1215f547ac223762f Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Wed, 24 Jul 2019 17:33:30 +0200 Subject: [PATCH 3/7] Set project name to empty beforehand Signed-off-by: Felipe Martinez --- compose/config/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compose/config/config.py b/compose/config/config.py index f27a5d9b58..ab132e5ee2 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -407,6 +407,7 @@ def load(config_details, compatibility=False, interpolate=True): config_details.config_files, 'get_configs', 'Config', config_details.working_dir ) + project_name = '' for config_file in config_details.config_files: name = config_file.get_project_name() if name: From 6f7d56a61e555f7015e3088e74530babd72e371f Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Wed, 24 Jul 2019 18:06:59 +0200 Subject: [PATCH 4/7] Add project name to build_config on project_test.py Signed-off-by: Felipe Martinez --- tests/integration/project_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 4c88f3d6b8..bc18103573 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -55,6 +55,7 @@ def build_config(**kwargs): networks=kwargs.get('networks'), secrets=kwargs.get('secrets'), configs=kwargs.get('configs'), + project_name=kwargs.get('project_name'), ) From b54719bc98c59f940bb309f72452d3c23e9aee6e Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Wed, 24 Jul 2019 19:11:33 +0200 Subject: [PATCH 5/7] Fix tests with missing project_name in config constructors Signed-off-by: Felipe Martinez --- compose/cli/command.py | 9 +++++---- tests/unit/config/config_test.py | 4 ++-- tests/unit/project_test.py | 16 +++++++++++++++- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/compose/cli/command.py b/compose/cli/command.py index a121e9ba1a..67191f3878 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -149,7 +149,7 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False, ) -def get_project_name(working_dir, config_data, project_name=None, environment=None): +def get_project_name(working_dir, config_data=None, project_name=None, environment=None): def normalize_name(name): return re.sub(r'[^-_a-z0-9]', '', name.lower()) @@ -159,9 +159,10 @@ def normalize_name(name): if project_name: return normalize_name(project_name) - project_name = config_data.project_name - if project_name: - return normalize_name(project_name) + if config_data: + project_name = config_data.project_name + if project_name: + return normalize_name(project_name) project = os.path.basename(os.path.abspath(working_dir)) if project: diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index b583422f54..8b1d15858b 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -5278,7 +5278,7 @@ def test_serialize_ports(self): 'image': 'alpine', 'name': 'web' } - ], volumes={}, networks={}, secrets={}, configs={}) + ], volumes={}, networks={}, secrets={}, configs={}, project_name='') serialized_config = yaml.load(serialize_config(config_dict)) assert '8080:80/tcp' in serialized_config['services']['web']['ports'] @@ -5290,7 +5290,7 @@ def test_serialize_ports_with_ext_ip(self): 'image': 'alpine', 'name': 'web' } - ], volumes={}, networks={}, secrets={}, configs={}) + ], volumes={}, networks={}, secrets={}, configs={}, project_name='') serialized_config = yaml.load(serialize_config(config_dict)) assert '127.0.0.1:8080:80/tcp' in serialized_config['services']['web']['ports'] diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 93a9aa292d..97a8575c5d 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -36,6 +36,7 @@ def setUp(self): def test_from_config_v1(self): config = Config( + project_name='', version=V1, services=[ { @@ -67,6 +68,7 @@ def test_from_config_v1(self): @mock.patch('compose.network.Network.true_name', lambda n: n.full_name) def test_from_config_v2(self): config = Config( + project_name='', version=V2_0, services=[ { @@ -174,6 +176,7 @@ def test_use_volumes_from_container(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[{ 'name': 'test', @@ -202,6 +205,7 @@ def test_use_volumes_from_service_no_container(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[ { @@ -230,6 +234,7 @@ def test_use_volumes_from_service_container(self): name='test', client=None, config_data=Config( + project_name='', version=V2_0, services=[ { @@ -540,6 +545,7 @@ def test_net_unset(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V1, services=[ { @@ -565,6 +571,7 @@ def test_use_net_from_container(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[ { @@ -596,6 +603,7 @@ def test_use_net_from_service(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[ { @@ -623,6 +631,7 @@ def test_uses_default_network_true(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[ { @@ -644,6 +653,7 @@ def test_uses_default_network_false(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[ { @@ -679,6 +689,7 @@ def test_container_without_name(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[{ 'name': 'web', @@ -697,6 +708,7 @@ def test_down_with_no_resources(self): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[{ 'name': 'web', @@ -748,7 +760,7 @@ def test_project_platform_value(self): 'image': BUSYBOX_IMAGE_WITH_TAG, } config_data = Config( - version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None + project_name='', version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None ) project = Project.from_config(name='test', client=self.mock_client, config_data=config_data) @@ -770,6 +782,7 @@ def test_project_platform_value(self): def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self): config_data = Config( + project_name='', version=V3_7, services=[ {'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG}, @@ -802,6 +815,7 @@ def test_error_parallel_pull(self, mock_write): name='test', client=self.mock_client, config_data=Config( + project_name='', version=V2_0, services=[{ 'name': 'web', From 95bf12d5bc92afe26f6048f66650aed0e4387be8 Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Wed, 24 Jul 2019 19:23:34 +0200 Subject: [PATCH 6/7] Line too long Signed-off-by: Felipe Martinez --- tests/unit/project_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 97a8575c5d..59db512094 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -760,7 +760,8 @@ def test_project_platform_value(self): 'image': BUSYBOX_IMAGE_WITH_TAG, } config_data = Config( - project_name='', version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None + project_name='', version=V2_4, services=[service_config], networks={}, + volumes={}, secrets=None, configs=None ) project = Project.from_config(name='test', client=self.mock_client, config_data=config_data) From 064dcac8f5414cd7ae9674121868e41ee0aaa9b2 Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Thu, 25 Jul 2019 00:16:44 +0200 Subject: [PATCH 7/7] Fix test Signed-off-by: Felipe Martinez --- tests/unit/bundle_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/bundle_test.py b/tests/unit/bundle_test.py index 8faebb7f1f..e4f07d6e48 100644 --- a/tests/unit/bundle_test.py +++ b/tests/unit/bundle_test.py @@ -91,7 +91,8 @@ def test_to_bundle(): volumes={'special': {}}, networks={'extra': {}}, secrets={}, - configs={} + configs={}, + project_name='' ) with mock.patch('compose.bundle.log.warning', autospec=True) as mock_log: