diff --git a/compose/config/config.py b/compose/config/config.py index ea122bc422..cfa8086f09 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 a0423ff447..b48f2e14bd 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/integration/service_test.py b/tests/integration/service_test.py index fc634c8ca3..0cf8cdb0ef 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -695,7 +695,7 @@ class ServiceTest(DockerClientTestCase): Test that calling scale on a service that has a custom container name results in warning output. """ - service = self.create_service('web', container_name='custom-container') + service = self.create_service('app', container_name='custom-container') self.assertEqual(service.custom_container_name(), 'custom-container') service.scale(3) diff --git a/tests/integration/state_test.py b/tests/integration/state_test.py index 3d4a5b5aa6..b3dd42d996 100644 --- a/tests/integration/state_test.py +++ b/tests/integration/state_test.py @@ -199,7 +199,6 @@ class ServiceStateTest(DockerClientTestCase): self.assertEqual([c.is_running for c in containers], [False, True]) - web = self.create_service('web', **options) self.assertEqual( ('start', containers[0:1]), web.convergence_plan(), diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index e239010ea2..08ef9f272f 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -33,6 +33,9 @@ class DockerClientTestCase(unittest.TestCase): options = ServiceLoader(working_dir='.').make_service_dict(name, kwargs) + labels = options.setdefault('labels', {}) + labels['com.docker.compose.test-name'] = self.id() + return Service( project='composetest', client=self.client, diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index a24e524dd5..aa6d4d74f4 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')