diff --git a/compose/config/config.py b/compose/config/config.py index ea122bc42..cfa8086f0 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -358,7 +358,7 @@ def parse_environment(environment): return dict(split_env(e) for e in environment) if isinstance(environment, dict): - return environment + return dict(environment) raise ConfigurationError( "environment \"%s\" must be a list or mapping," % diff --git a/compose/service.py b/compose/service.py index a0423ff44..b48f2e14b 100644 --- a/compose/service.py +++ b/compose/service.py @@ -576,7 +576,6 @@ class Service(object): number, one_off=False, previous_container=None): - add_config_hash = (not one_off and not override_options) container_options = dict( @@ -589,13 +588,6 @@ class Service(object): elif not container_options.get('name'): container_options['name'] = self.get_container_name(number, one_off) - if add_config_hash: - config_hash = self.config_hash - if 'labels' not in container_options: - container_options['labels'] = {} - container_options['labels'][LABEL_CONFIG_HASH] = config_hash - log.debug("Added config hash: %s" % config_hash) - if 'detach' not in container_options: container_options['detach'] = True @@ -643,7 +635,8 @@ class Service(object): container_options['labels'] = build_container_labels( container_options.get('labels', {}), self.labels(one_off=one_off), - number) + number, + self.config_hash if add_config_hash else None) # Delete options which are only used when starting for key in DOCKER_START_KEYS: @@ -899,11 +892,16 @@ def parse_volume_spec(volume_config): # Labels -def build_container_labels(label_options, service_labels, number, one_off=False): - labels = label_options or {} +def build_container_labels(label_options, service_labels, number, config_hash): + labels = dict(label_options or {}) labels.update(label.split('=', 1) for label in service_labels) labels[LABEL_CONTAINER_NUMBER] = str(number) labels[LABEL_VERSION] = __version__ + + if config_hash: + log.debug("Added config hash: %s" % config_hash) + labels[LABEL_CONFIG_HASH] = config_hash + return labels diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index a24e524dd..aa6d4d74f 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -6,6 +6,7 @@ from docker.utils import LogConfig from .. import mock from .. import unittest +from compose.const import LABEL_CONFIG_HASH from compose.const import LABEL_ONE_OFF from compose.const import LABEL_PROJECT from compose.const import LABEL_SERVICE @@ -163,6 +164,40 @@ class ServiceTest(unittest.TestCase): one_off=True) self.assertEqual(opts['name'], name) + def test_get_container_create_options_does_not_mutate_options(self): + labels = {'thing': 'real'} + environment = {'also': 'real'} + service = Service( + 'foo', + image='foo', + labels=dict(labels), + client=self.mock_client, + environment=dict(environment), + ) + self.mock_client.inspect_image.return_value = {'Id': 'abcd'} + prev_container = mock.Mock( + id='ababab', + image_config={'ContainerConfig': {}}) + + opts = service._get_container_create_options( + {}, + 1, + previous_container=prev_container) + + self.assertEqual(service.options['labels'], labels) + self.assertEqual(service.options['environment'], environment) + + self.assertEqual( + opts['labels'][LABEL_CONFIG_HASH], + 'b30306d0a73b67f67a45b99b88d36c359e470e6fa0c04dda1cf62d2087205b81') + self.assertEqual( + opts['environment'], + { + 'affinity:container': '=ababab', + 'also': 'real', + } + ) + def test_get_container_not_found(self): self.mock_client.containers.return_value = [] service = Service('foo', client=self.mock_client, image='foo')