Procházet zdrojové kódy

Resolves #1804

Fix mutation of service.options when a label or environment variable is specified in the config.

Signed-off-by: Daniel Nephin <[email protected]>
Daniel Nephin před 10 roky
rodič
revize
d264c2e33a
3 změnil soubory, kde provedl 45 přidání a 12 odebrání
  1. 1 1
      compose/config/config.py
  2. 9 11
      compose/service.py
  3. 35 0
      tests/unit/service_test.py

+ 1 - 1
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," %

+ 9 - 11
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
 
 

+ 35 - 0
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')