From 31dedd8bddc789ed03864c6280ba119f9c24d90c Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Thu, 2 Jul 2015 14:28:44 +0100 Subject: [PATCH 01/12] Rename function process to validate The process function contained purely validation checks, so re-named the function to aid intent clarity. Signed-off-by: Mazz Mosley --- compose/config.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose/config.py b/compose/config.py index f1883e693..a5fe5b3ef 100644 --- a/compose/config.py +++ b/compose/config.py @@ -158,7 +158,7 @@ class ServiceLoader(object): if 'extends' not in service_dict: return service_dict - extends_options = process_extends_options(service_dict['name'], service_dict['extends']) + extends_options = validate_extends_options(service_dict['name'], service_dict['extends']) if self.working_dir is None: raise Exception("No working_dir passed to ServiceLoader()") @@ -190,7 +190,7 @@ class ServiceLoader(object): return (self.filename, name) -def process_extends_options(service_name, extends_options): +def validate_extends_options(service_name, extends_options): error_prefix = "Invalid 'extends' configuration for %s:" % service_name if not isinstance(extends_options, dict): From 6e4a954dbd4f899b15d8cc621175463c56d6ac70 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Thu, 2 Jul 2015 15:31:38 +0100 Subject: [PATCH 02/12] 'file' key can be omitted from extends If the 'file' key is not set in the extends_options dict then we look for the 'service' from within the same file. Fixes this issue: https://github.com/docker/compose/issues/1237 Signed-off-by: Mazz Mosley --- compose/config.py | 7 +++++- tests/fixtures/extends/no-file-specified.yml | 9 ++++++++ tests/unit/config_test.py | 24 ++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/extends/no-file-specified.yml diff --git a/compose/config.py b/compose/config.py index a5fe5b3ef..cf831f312 100644 --- a/compose/config.py +++ b/compose/config.py @@ -163,7 +163,12 @@ class ServiceLoader(object): if self.working_dir is None: raise Exception("No working_dir passed to ServiceLoader()") - other_config_path = expand_path(self.working_dir, extends_options['file']) + try: + extends_from_filename = extends_options['file'] + except KeyError: + extends_from_filename = os.path.split(self.filename)[1] + + other_config_path = expand_path(self.working_dir, extends_from_filename) other_working_dir = os.path.dirname(other_config_path) other_already_seen = self.already_seen + [self.signature(service_dict['name'])] other_loader = ServiceLoader( diff --git a/tests/fixtures/extends/no-file-specified.yml b/tests/fixtures/extends/no-file-specified.yml new file mode 100644 index 000000000..40e43c4bf --- /dev/null +++ b/tests/fixtures/extends/no-file-specified.yml @@ -0,0 +1,9 @@ +myweb: + extends: + service: web + environment: + - "BAR=1" +web: + image: busybox + environment: + - "BAZ=3" diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 5a61ad528..4f0f1893d 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -447,6 +447,30 @@ class ExtendsTest(unittest.TestCase): dictionary['extends']['what'] = 'is this' self.assertRaisesRegexp(config.ConfigurationError, 'what', load_config) + def test_extends_file_defaults_to_self(self): + """ + Test not specifying a file in our extends options that the + config is valid and correctly extends from itself. + """ + service_dicts = config.load('tests/fixtures/extends/no-file-specified.yml') + self.assertEqual(service_dicts, [ + { + 'name': 'myweb', + 'image': 'busybox', + 'environment': { + "BAR": "1", + "BAZ": "3", + } + }, + { + 'name': 'web', + 'image': 'busybox', + 'environment': { + "BAZ": "3", + } + } + ]) + def test_blacklisted_options(self): def load_config(): return config.make_service_dict('myweb', { From 5e2d43843c3155b864dce97bd23494ff45f3a601 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Thu, 2 Jul 2015 16:50:33 +0100 Subject: [PATCH 03/12] Reduce path manipulation If we're using self.filename, then it's already a full path and we don't need to splice off the filename.yml just so we can .join it back together again. Signed-off-by: Mazz Mosley --- compose/config.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compose/config.py b/compose/config.py index cf831f312..2427476f7 100644 --- a/compose/config.py +++ b/compose/config.py @@ -163,12 +163,12 @@ class ServiceLoader(object): if self.working_dir is None: raise Exception("No working_dir passed to ServiceLoader()") - try: + if 'file' in extends_options: extends_from_filename = extends_options['file'] - except KeyError: - extends_from_filename = os.path.split(self.filename)[1] + other_config_path = expand_path(self.working_dir, extends_from_filename) + else: + other_config_path = self.filename - other_config_path = expand_path(self.working_dir, extends_from_filename) other_working_dir = os.path.dirname(other_config_path) other_already_seen = self.already_seen + [self.signature(service_dict['name'])] other_loader = ServiceLoader( From 24c1d95869a6fbebacbb11ec57c27e5ffb9134d9 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 10:36:44 +0100 Subject: [PATCH 04/12] Refactor extends validation tests Split them out into individual validation tests so it is clearer to see what is going on and to enable adding further validation tests. Signed-off-by: Mazz Mosley --- tests/unit/config_test.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 4f0f1893d..48dd9afa8 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -427,7 +427,7 @@ class ExtendsTest(unittest.TestCase): ], ) - def test_extends_validation(self): + def test_extends_validation_empty_dictionary(self): dictionary = {'extends': None} def load_config(): @@ -438,15 +438,35 @@ class ExtendsTest(unittest.TestCase): dictionary['extends'] = {} self.assertRaises(config.ConfigurationError, load_config) - dictionary['extends']['file'] = 'common.yml' + def test_extends_validation_missing_service_key(self): + dictionary = {'extends': {'file': 'common.yml'}} + + def load_config(): + return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + self.assertRaisesRegexp(config.ConfigurationError, 'service', load_config) - dictionary['extends']['service'] = 'web' - self.assertIsInstance(load_config(), dict) + def test_extends_validation_invalid_key(self): + dictionary = { + 'extends': + { + 'service': 'web', 'file': 'common.yml', 'what': 'is this' + } + } + + def load_config(): + return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') - dictionary['extends']['what'] = 'is this' self.assertRaisesRegexp(config.ConfigurationError, 'what', load_config) + def test_extends_validation_valid_config(self): + dictionary = {'extends': {'service': 'web', 'file': 'common.yml'}} + + def load_config(): + return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + + self.assertIsInstance(load_config(), dict) + def test_extends_file_defaults_to_self(self): """ Test not specifying a file in our extends options that the From 254bc4908ca2270413b95aae11cc3156911ac463 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 10:39:03 +0100 Subject: [PATCH 05/12] Move extends validation into ServiceLoader class This refactoring allows us to raise an error when there is no 'file' key specified in the .yml and no self.filename set. This error was specific to the tests, as the tests are the only place that constructs service dicts without sometimes setting a filename. Moving the function within the class as well as it is code that is exclusively for the use of validating properties for the ServiceLoader class. Signed-off-by: Mazz Mosley --- compose/config.py | 34 +++++++++++++++++++--------------- tests/unit/config_test.py | 8 ++++++++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/compose/config.py b/compose/config.py index 2427476f7..c054213eb 100644 --- a/compose/config.py +++ b/compose/config.py @@ -158,7 +158,7 @@ class ServiceLoader(object): if 'extends' not in service_dict: return service_dict - extends_options = validate_extends_options(service_dict['name'], service_dict['extends']) + extends_options = self.validate_extends_options(service_dict['name'], service_dict['extends']) if self.working_dir is None: raise Exception("No working_dir passed to ServiceLoader()") @@ -194,25 +194,29 @@ class ServiceLoader(object): def signature(self, name): return (self.filename, name) + def validate_extends_options(self, service_name, extends_options): + error_prefix = "Invalid 'extends' configuration for %s:" % service_name -def validate_extends_options(service_name, extends_options): - error_prefix = "Invalid 'extends' configuration for %s:" % service_name + if not isinstance(extends_options, dict): + raise ConfigurationError("%s must be a dictionary" % error_prefix) - if not isinstance(extends_options, dict): - raise ConfigurationError("%s must be a dictionary" % error_prefix) - - if 'service' not in extends_options: - raise ConfigurationError( - "%s you need to specify a service, e.g. 'service: web'" % error_prefix - ) - - for k, _ in extends_options.items(): - if k not in ['file', 'service']: + if 'service' not in extends_options: raise ConfigurationError( - "%s unsupported configuration option '%s'" % (error_prefix, k) + "%s you need to specify a service, e.g. 'service: web'" % error_prefix ) - return extends_options + if 'file' not in extends_options and self.filename is None: + raise ConfigurationError( + "%s you need to specify a 'file', e.g. 'file: something.yml'" % error_prefix + ) + + for k, _ in extends_options.items(): + if k not in ['file', 'service']: + raise ConfigurationError( + "%s unsupported configuration option '%s'" % (error_prefix, k) + ) + + return extends_options def validate_extended_service_dict(service_dict, filename, service): diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 48dd9afa8..4047a7253 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -459,6 +459,14 @@ class ExtendsTest(unittest.TestCase): self.assertRaisesRegexp(config.ConfigurationError, 'what', load_config) + def test_extends_validation_no_file_key_no_filename_set(self): + dictionary = {'extends': {'service': 'web'}} + + def load_config(): + return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + + self.assertRaisesRegexp(config.ConfigurationError, 'file', load_config) + def test_extends_validation_valid_config(self): dictionary = {'extends': {'service': 'web', 'file': 'common.yml'}} From bd7fcd112339c40c2add6d394f584305275b749a Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 12:21:12 +0100 Subject: [PATCH 06/12] Use absolute paths A circular reference bug occurs when there is a difference in the paths of the file specified in the extends. So one time it is relative, second time is absolute thus allowing a further circular reference to occur. By using absolute paths we can be sure that the service filename check is correct. Signed-off-by: Mazz Mosley --- compose/config.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/compose/config.py b/compose/config.py index c054213eb..d32fe37e5 100644 --- a/compose/config.py +++ b/compose/config.py @@ -140,8 +140,11 @@ def make_service_dict(name, service_dict, working_dir=None): class ServiceLoader(object): def __init__(self, working_dir, filename=None, already_seen=None): - self.working_dir = working_dir - self.filename = filename + self.working_dir = os.path.abspath(working_dir) + if filename: + self.filename = os.path.abspath(filename) + else: + self.filename = filename self.already_seen = already_seen or [] def make_service_dict(self, name, service_dict): From c51d53afbab679ec6e42956e39ed84ba18396190 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 12:24:33 +0100 Subject: [PATCH 07/12] Fix off by one error In our circular reference check the stack was previously off by one, by not including the current service name that was calling another. Signed-off-by: Mazz Mosley --- compose/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose/config.py b/compose/config.py index d32fe37e5..9adbcfbde 100644 --- a/compose/config.py +++ b/compose/config.py @@ -149,7 +149,7 @@ class ServiceLoader(object): def make_service_dict(self, name, service_dict): if self.signature(name) in self.already_seen: - raise CircularReference(self.already_seen) + raise CircularReference(self.already_seen + [self.signature(name)]) service_dict = service_dict.copy() service_dict['name'] = name From ba71e2a54925aabd8dde248f4759c30648688e51 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 14:05:14 +0100 Subject: [PATCH 08/12] Use new load_from_filename Signed-off-by: Mazz Mosley --- tests/unit/config_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 4047a7253..3e9b56c3d 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -480,7 +480,7 @@ class ExtendsTest(unittest.TestCase): Test not specifying a file in our extends options that the config is valid and correctly extends from itself. """ - service_dicts = config.load('tests/fixtures/extends/no-file-specified.yml') + service_dicts = load_from_filename('tests/fixtures/extends/no-file-specified.yml') self.assertEqual(service_dicts, [ { 'name': 'myweb', From 6a6e7934bde138ddaa2cfa0bc20786a33fa3027a Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 14:05:35 +0100 Subject: [PATCH 09/12] Refactor circular-reference check Signed-off-by: Mazz Mosley --- compose/config.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compose/config.py b/compose/config.py index 9adbcfbde..ba8bd8e03 100644 --- a/compose/config.py +++ b/compose/config.py @@ -147,10 +147,11 @@ class ServiceLoader(object): self.filename = filename self.already_seen = already_seen or [] - def make_service_dict(self, name, service_dict): + def detect_cycle(self, name): if self.signature(name) in self.already_seen: raise CircularReference(self.already_seen + [self.signature(name)]) + def make_service_dict(self, name, service_dict): service_dict = service_dict.copy() service_dict['name'] = name service_dict = resolve_environment(service_dict, working_dir=self.working_dir) @@ -182,6 +183,7 @@ class ServiceLoader(object): other_config = load_yaml(other_config_path) other_service_dict = other_config[extends_options['service']] + other_loader.detect_cycle(extends_options['service']) other_service_dict = other_loader.make_service_dict( service_dict['name'], other_service_dict, From c6e03d739d3318e67aa8b3eb0df091cbd775ba56 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 14:14:47 +0100 Subject: [PATCH 10/12] Test self referencing 'file' When specifying the 'file' key to a value of it's own name, test that this works and does not cause a false positive circular reference error. Signed-off-by: Mazz Mosley --- .../fixtures/extends/specify-file-as-self.yml | 16 +++++++++++ tests/unit/config_test.py | 27 +++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/fixtures/extends/specify-file-as-self.yml diff --git a/tests/fixtures/extends/specify-file-as-self.yml b/tests/fixtures/extends/specify-file-as-self.yml new file mode 100644 index 000000000..7e2499762 --- /dev/null +++ b/tests/fixtures/extends/specify-file-as-self.yml @@ -0,0 +1,16 @@ +myweb: + extends: + file: specify-file-as-self.yml + service: web + environment: + - "BAR=1" +web: + extends: + file: specify-file-as-self.yml + service: otherweb + image: busybox + environment: + - "BAZ=3" +otherweb: + environment: + - "YEP=1" diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 3e9b56c3d..d66b9fbb8 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -413,6 +413,33 @@ class ExtendsTest(unittest.TestCase): }, ]) + def test_self_referencing_file(self): + """ + We specify a 'file' key that is the filename we're already in. + """ + service_dicts = load_from_filename('tests/fixtures/extends/specify-file-as-self.yml') + self.assertEqual(service_dicts, [ + { + 'environment': + { + 'YEP': '1', 'BAR': '1', 'BAZ': '3' + }, + 'image': 'busybox', + 'name': 'myweb' + }, + { + 'environment': + {'YEP': '1'}, + 'name': 'otherweb' + }, + { + 'environment': + {'YEP': '1', 'BAZ': '3'}, + 'image': 'busybox', + 'name': 'web' + } + ]) + def test_circular(self): try: load_from_filename('tests/fixtures/extends/circle-1.yml') From 6e6dbdad955d7e85dceb2011b21feca2d124fcad Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 14:56:18 +0100 Subject: [PATCH 11/12] working_dir is no longer optional When building test data using make_service_dict, we need to include working_dir as it is core to some of the functionality of ServiceLoader. Signed-off-by: Mazz Mosley --- compose/config.py | 2 +- tests/unit/config_test.py | 25 +++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/compose/config.py b/compose/config.py index ba8bd8e03..a465586c2 100644 --- a/compose/config.py +++ b/compose/config.py @@ -134,7 +134,7 @@ def load(config_details): return service_dicts -def make_service_dict(name, service_dict, working_dir=None): +def make_service_dict(name, service_dict, working_dir): return ServiceLoader(working_dir=working_dir).make_service_dict(name, service_dict) diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index d66b9fbb8..57dec4670 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -47,9 +47,9 @@ class ConfigTest(unittest.TestCase): def test_config_validation(self): self.assertRaises( config.ConfigurationError, - lambda: config.make_service_dict('foo', {'port': ['8000']}) + lambda: config.make_service_dict('foo', {'port': ['8000']}, 'tests/') ) - config.make_service_dict('foo', {'ports': ['8000']}) + config.make_service_dict('foo', {'ports': ['8000']}, 'tests/') class VolumePathTest(unittest.TestCase): @@ -219,36 +219,36 @@ class MergeLabelsTest(unittest.TestCase): def test_no_override(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}), - config.make_service_dict('foo', {}), + config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), + config.make_service_dict('foo', {}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': ''}) def test_no_base(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {}), - config.make_service_dict('foo', {'labels': ['foo=2']}), + config.make_service_dict('foo', {}, 'tests/'), + config.make_service_dict('foo', {'labels': ['foo=2']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '2'}) def test_override_explicit_value(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}), - config.make_service_dict('foo', {'labels': ['foo=2']}), + config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), + config.make_service_dict('foo', {'labels': ['foo=2']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '2', 'bar': ''}) def test_add_explicit_value(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}), - config.make_service_dict('foo', {'labels': ['bar=2']}), + config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), + config.make_service_dict('foo', {'labels': ['bar=2']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': '2'}) def test_remove_explicit_value(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar=2']}), - config.make_service_dict('foo', {'labels': ['bar']}), + config.make_service_dict('foo', {'labels': ['foo=1', 'bar=2']}, 'tests/'), + config.make_service_dict('foo', {'labels': ['bar']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': ''}) @@ -295,6 +295,7 @@ class EnvTest(unittest.TestCase): 'NO_DEF': None }, }, + 'tests/' ) self.assertEqual( From 317bbec98c0aa263a8760177f77b76b8911e165d Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Fri, 3 Jul 2015 15:06:53 +0100 Subject: [PATCH 12/12] Move make_service_dict out of config This top level function is a test helper, so I've moved it into the config_test file and updated accordingly. Signed-off-by: Mazz Mosley --- compose/config.py | 4 --- tests/integration/testcases.py | 6 ++-- tests/unit/config_test.py | 65 +++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/compose/config.py b/compose/config.py index a465586c2..7fceb50cc 100644 --- a/compose/config.py +++ b/compose/config.py @@ -134,10 +134,6 @@ def load(config_details): return service_dicts -def make_service_dict(name, service_dict, working_dir): - return ServiceLoader(working_dir=working_dir).make_service_dict(name, service_dict) - - class ServiceLoader(object): def __init__(self, working_dir, filename=None, already_seen=None): self.working_dir = os.path.abspath(working_dir) diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index 98c5876eb..2a7c0a440 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from __future__ import absolute_import from compose.service import Service -from compose.config import make_service_dict +from compose.config import ServiceLoader from compose.const import LABEL_PROJECT from compose.cli.docker_client import docker_client from compose.progress_stream import stream_output @@ -30,10 +30,12 @@ class DockerClientTestCase(unittest.TestCase): if 'command' not in kwargs: kwargs['command'] = ["top"] + options = ServiceLoader(working_dir='.').make_service_dict(name, kwargs) + return Service( project='composetest', client=self.client, - **make_service_dict(name, kwargs, working_dir='.') + **options ) def check_build(self, *args, **kwargs): diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 57dec4670..d56b2a181 100644 --- a/tests/unit/config_test.py +++ b/tests/unit/config_test.py @@ -7,6 +7,13 @@ from .. import unittest from compose import config +def make_service_dict(name, service_dict, working_dir): + """ + Test helper function to contruct a ServiceLoader + """ + return config.ServiceLoader(working_dir=working_dir).make_service_dict(name, service_dict) + + class ConfigTest(unittest.TestCase): def test_load(self): service_dicts = config.load( @@ -47,22 +54,22 @@ class ConfigTest(unittest.TestCase): def test_config_validation(self): self.assertRaises( config.ConfigurationError, - lambda: config.make_service_dict('foo', {'port': ['8000']}, 'tests/') + lambda: make_service_dict('foo', {'port': ['8000']}, 'tests/') ) - config.make_service_dict('foo', {'ports': ['8000']}, 'tests/') + make_service_dict('foo', {'ports': ['8000']}, 'tests/') class VolumePathTest(unittest.TestCase): @mock.patch.dict(os.environ) def test_volume_binding_with_environ(self): os.environ['VOLUME_PATH'] = '/host/path' - d = config.make_service_dict('foo', {'volumes': ['${VOLUME_PATH}:/container/path']}, working_dir='.') + d = make_service_dict('foo', {'volumes': ['${VOLUME_PATH}:/container/path']}, working_dir='.') self.assertEqual(d['volumes'], ['/host/path:/container/path']) @mock.patch.dict(os.environ) def test_volume_binding_with_home(self): os.environ['HOME'] = '/home/user' - d = config.make_service_dict('foo', {'volumes': ['~:/container/path']}, working_dir='.') + d = make_service_dict('foo', {'volumes': ['~:/container/path']}, working_dir='.') self.assertEqual(d['volumes'], ['/home/user:/container/path']) @@ -219,36 +226,36 @@ class MergeLabelsTest(unittest.TestCase): def test_no_override(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), - config.make_service_dict('foo', {}, 'tests/'), + make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), + make_service_dict('foo', {}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': ''}) def test_no_base(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {}, 'tests/'), - config.make_service_dict('foo', {'labels': ['foo=2']}, 'tests/'), + make_service_dict('foo', {}, 'tests/'), + make_service_dict('foo', {'labels': ['foo=2']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '2'}) def test_override_explicit_value(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), - config.make_service_dict('foo', {'labels': ['foo=2']}, 'tests/'), + make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), + make_service_dict('foo', {'labels': ['foo=2']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '2', 'bar': ''}) def test_add_explicit_value(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), - config.make_service_dict('foo', {'labels': ['bar=2']}, 'tests/'), + make_service_dict('foo', {'labels': ['foo=1', 'bar']}, 'tests/'), + make_service_dict('foo', {'labels': ['bar=2']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': '2'}) def test_remove_explicit_value(self): service_dict = config.merge_service_dicts( - config.make_service_dict('foo', {'labels': ['foo=1', 'bar=2']}, 'tests/'), - config.make_service_dict('foo', {'labels': ['bar']}, 'tests/'), + make_service_dict('foo', {'labels': ['foo=1', 'bar=2']}, 'tests/'), + make_service_dict('foo', {'labels': ['bar']}, 'tests/'), ) self.assertEqual(service_dict['labels'], {'foo': '1', 'bar': ''}) @@ -286,7 +293,7 @@ class EnvTest(unittest.TestCase): os.environ['FILE_DEF_EMPTY'] = 'E2' os.environ['ENV_DEF'] = 'E3' - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'foo', { 'environment': { 'FILE_DEF': 'F1', @@ -304,7 +311,7 @@ class EnvTest(unittest.TestCase): ) def test_env_from_file(self): - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'foo', {'env_file': 'one.env'}, 'tests/fixtures/env', @@ -315,7 +322,7 @@ class EnvTest(unittest.TestCase): ) def test_env_from_multiple_files(self): - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'foo', {'env_file': ['one.env', 'two.env']}, 'tests/fixtures/env', @@ -329,7 +336,7 @@ class EnvTest(unittest.TestCase): options = {'env_file': 'nonexistent.env'} self.assertRaises( config.ConfigurationError, - lambda: config.make_service_dict('foo', options, 'tests/fixtures/env'), + lambda: make_service_dict('foo', options, 'tests/fixtures/env'), ) @mock.patch.dict(os.environ) @@ -337,7 +344,7 @@ class EnvTest(unittest.TestCase): os.environ['FILE_DEF'] = 'E1' os.environ['FILE_DEF_EMPTY'] = 'E2' os.environ['ENV_DEF'] = 'E3' - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'foo', {'env_file': 'resolve.env'}, 'tests/fixtures/env', @@ -352,14 +359,14 @@ class EnvTest(unittest.TestCase): os.environ['HOSTENV'] = '/tmp' os.environ['CONTAINERENV'] = '/host/tmp' - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'foo', {'volumes': ['$HOSTENV:$CONTAINERENV']}, working_dir="tests/fixtures/env" ) self.assertEqual(set(service_dict['volumes']), set(['/tmp:/host/tmp'])) - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'foo', {'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}, working_dir="tests/fixtures/env" @@ -459,7 +466,7 @@ class ExtendsTest(unittest.TestCase): dictionary = {'extends': None} def load_config(): - return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + return make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') self.assertRaisesRegexp(config.ConfigurationError, 'dictionary', load_config) @@ -470,7 +477,7 @@ class ExtendsTest(unittest.TestCase): dictionary = {'extends': {'file': 'common.yml'}} def load_config(): - return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + return make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') self.assertRaisesRegexp(config.ConfigurationError, 'service', load_config) @@ -483,7 +490,7 @@ class ExtendsTest(unittest.TestCase): } def load_config(): - return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + return make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') self.assertRaisesRegexp(config.ConfigurationError, 'what', load_config) @@ -491,7 +498,7 @@ class ExtendsTest(unittest.TestCase): dictionary = {'extends': {'service': 'web'}} def load_config(): - return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + return make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') self.assertRaisesRegexp(config.ConfigurationError, 'file', load_config) @@ -499,7 +506,7 @@ class ExtendsTest(unittest.TestCase): dictionary = {'extends': {'service': 'web', 'file': 'common.yml'}} def load_config(): - return config.make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') + return make_service_dict('myweb', dictionary, working_dir='tests/fixtures/extends') self.assertIsInstance(load_config(), dict) @@ -529,7 +536,7 @@ class ExtendsTest(unittest.TestCase): def test_blacklisted_options(self): def load_config(): - return config.make_service_dict('myweb', { + return make_service_dict('myweb', { 'extends': { 'file': 'whatever', 'service': 'web', @@ -603,7 +610,7 @@ class BuildPathTest(unittest.TestCase): def test_relative_path(self): relative_build_path = '../build-ctx/' - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'relpath', {'build': relative_build_path}, working_dir='tests/fixtures/build-path' @@ -611,7 +618,7 @@ class BuildPathTest(unittest.TestCase): self.assertEquals(service_dict['build'], self.abs_context_path) def test_absolute_path(self): - service_dict = config.make_service_dict( + service_dict = make_service_dict( 'abspath', {'build': self.abs_context_path}, working_dir='tests/fixtures/build-path'