diff --git a/compose/config/config.py b/compose/config/config.py index 201266208..fa214767b 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -257,16 +257,11 @@ class ServiceExtendsResolver(object): def run(self): self.detect_cycle() - service_dict = dict(self.service_config.config) - env = resolve_environment(self.working_dir, self.service_config.config) - if env: - service_dict['environment'] = env - service_dict.pop('env_file', None) - - if 'extends' in service_dict: + if 'extends' in self.service_config.config: service_dict = self.resolve_extends(*self.validate_and_construct_extends()) + return self.service_config._replace(config=service_dict) - return self.service_config._replace(config=service_dict) + return self.service_config def validate_and_construct_extends(self): extends = self.service_config.config['extends'] @@ -316,16 +311,15 @@ class ServiceExtendsResolver(object): return filename -def resolve_environment(working_dir, service_dict): +def resolve_environment(service_config): """Unpack any environment variables from an env_file, if set. Interpolate environment values if set. """ - if 'environment' not in service_dict and 'env_file' not in service_dict: - return {} + service_dict = service_config.config env = {} if 'env_file' in service_dict: - for env_file in get_env_files(working_dir, service_dict): + for env_file in get_env_files(service_config.working_dir, service_dict): env.update(env_vars_from_file(env_file)) env.update(parse_environment(service_dict.get('environment'))) @@ -362,6 +356,10 @@ def process_service(service_config): working_dir = service_config.working_dir service_dict = dict(service_config.config) + if 'environment' in service_dict or 'env_file' in service_dict: + service_dict['environment'] = resolve_environment(service_config) + service_dict.pop('env_file', None) + if 'volumes' in service_dict and service_dict.get('volume_driver') is None: service_dict['volumes'] = resolve_volume_paths(working_dir, service_dict) diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index d63f05916..de2d1a701 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -46,7 +46,8 @@ class DockerClientTestCase(unittest.TestCase): service_config = ServiceConfig('.', None, name, kwargs) options = process_service(service_config) - options['environment'] = resolve_environment('.', kwargs) + options['environment'] = resolve_environment( + service_config._replace(config=options)) labels = options.setdefault('labels', {}) labels['com.docker.compose.test-name'] = self.id() diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 3038af80d..c69e34306 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -1317,6 +1317,54 @@ class ExtendsTest(unittest.TestCase): }, ])) + def test_extends_with_environment_and_env_files(self): + tmpdir = py.test.ensuretemp('test_extends_with_environment') + self.addCleanup(tmpdir.remove) + commondir = tmpdir.mkdir('common') + commondir.join('base.yml').write(""" + app: + image: 'example/app' + env_file: + - 'envs' + environment: + - SECRET + """) + tmpdir.join('docker-compose.yml').write(""" + ext: + extends: + file: common/base.yml + service: app + env_file: + - 'envs' + environment: + - THING + """) + commondir.join('envs').write(""" + COMMON_ENV_FILE=1 + """) + tmpdir.join('envs').write(""" + FROM_ENV_FILE=1 + """) + + expected = [ + { + 'name': 'ext', + 'image': 'example/app', + 'environment': { + 'SECRET': 'secret', + 'FROM_ENV_FILE': '1', + 'COMMON_ENV_FILE': '1', + 'THING': 'thing', + }, + }, + ] + with mock.patch.dict(os.environ): + os.environ['SECRET'] = 'secret' + os.environ['THING'] = 'thing' + config = load_from_filename(str(tmpdir.join('docker-compose.yml'))) + + assert config == expected + @pytest.mark.xfail(IS_WINDOWS_PLATFORM, reason='paths use slash') class ExpandPathTest(unittest.TestCase):