1
0
Эх сурвалжийг харах

Interpolate extended config

This refactoring is now really coming together. Construction is
happening in the __init__, which is a constructor and helps
clean up the design and clarity of intent of the code. We can now
see (nearly) everything that is being constructed when a ServiceLoader
is created. It needs all of these data constructs to perform the
domain logic and actions. Which are now clearer to see and moving
more towards the principle of functions doing (mostly)one thing and
function names being more descriptive.

resolve_extends is now concerned with the resolving of extends, rather
than the construction, validation, pre processing and *then* resolving
of extends.

Happy days :)

Signed-off-by: Mazz Mosley <[email protected]>
Mazz Mosley 10 жил өмнө
parent
commit
4a8b2947ca

+ 23 - 21
compose/config/config.py

@@ -11,6 +11,7 @@ from .errors import ComposeFileNotFound
 from .errors import ConfigurationError
 from .interpolation import interpolate_environment_variables
 from .validation import validate_against_schema
+from .validation import validate_extended_service_exists
 from .validation import validate_extends_file_path
 from .validation import validate_service_names
 from .validation import validate_top_level_object
@@ -178,12 +179,25 @@ class ServiceLoader(object):
                 self.service_dict['extends'],
                 self.filename
             )
-
             self.extended_config_path = self.get_extended_config_path(
                 self.service_dict['extends']
             )
-            extended_config = load_yaml(self.extended_config_path)
-            validate_against_schema(extended_config)
+            self.extended_service_name = self.service_dict['extends']['service']
+
+            full_extended_config = pre_process_config(
+                load_yaml(self.extended_config_path)
+            )
+
+            validate_extended_service_exists(
+                self.extended_service_name,
+                full_extended_config,
+                self.extended_config_path
+            )
+            validate_against_schema(full_extended_config)
+
+            self.extended_config = full_extended_config[self.extended_service_name]
+        else:
+            self.extended_config = None
 
     def detect_cycle(self, name):
         if self.signature(name) in self.already_seen:
@@ -214,40 +228,28 @@ class ServiceLoader(object):
         self.service_dict['environment'] = env
 
     def resolve_extends(self):
-        if 'extends' not in self.service_dict:
+        if self.extended_config is None:
             return self.service_dict
 
-        extends_options = self.service_dict['extends']
         service_name = self.service_dict['name']
-        other_config_path = self.extended_config_path
 
         other_working_dir = os.path.dirname(self.extended_config_path)
         other_already_seen = self.already_seen + [self.signature(service_name)]
 
-        base_service = extends_options['service']
-        other_config = load_yaml(other_config_path)
-
-        if base_service not in other_config:
-            msg = (
-                "Cannot extend service '%s' in %s: Service not found"
-            ) % (base_service, other_config_path)
-            raise ConfigurationError(msg)
-
-        other_service_dict = other_config[base_service]
         other_loader = ServiceLoader(
             working_dir=other_working_dir,
-            filename=other_config_path,
+            filename=self.extended_config_path,
             service_name=service_name,
-            service_dict=other_service_dict,
+            service_dict=self.extended_config,
             already_seen=other_already_seen,
         )
 
-        other_loader.detect_cycle(extends_options['service'])
+        other_loader.detect_cycle(self.extended_service_name)
         other_service_dict = other_loader.make_service_dict()
         validate_extended_service_dict(
             other_service_dict,
-            filename=other_config_path,
-            service=extends_options['service'],
+            filename=self.extended_config_path,
+            service=self.extended_service_name,
         )
 
         return merge_service_dicts(other_service_dict, self.service_dict)

+ 8 - 0
compose/config/validation.py

@@ -79,6 +79,14 @@ def validate_extends_file_path(service_name, extends_options, filename):
         )
 
 
+def validate_extended_service_exists(extended_service_name, full_extended_config, extended_config_path):
+    if extended_service_name not in full_extended_config:
+        msg = (
+            "Cannot extend service '%s' in %s: Service not found"
+        ) % (extended_service_name, extended_config_path)
+        raise ConfigurationError(msg)
+
+
 def get_unsupported_config_msg(service_name, error_key):
     msg = "Unsupported config option for '{}' service: '{}'".format(service_name, error_key)
     if error_key in DOCKER_CONFIG_HINTS:

+ 3 - 0
tests/fixtures/extends/valid-interpolation-2.yml

@@ -0,0 +1,3 @@
+web:
+  build: '.'
+  hostname: "host-${HOSTNAME_VALUE}"

+ 5 - 0
tests/fixtures/extends/valid-interpolation.yml

@@ -0,0 +1,5 @@
+myweb:
+  extends:
+    service: web
+    file: valid-interpolation-2.yml
+  command: top

+ 11 - 0
tests/unit/config_test.py

@@ -914,6 +914,17 @@ class ExtendsTest(unittest.TestCase):
         with self.assertRaisesRegexp(ConfigurationError, expected_error_msg):
             load_from_filename('tests/fixtures/extends/invalid-net.yml')
 
+    @mock.patch.dict(os.environ)
+    def test_valid_interpolation_in_extended_service(self):
+        os.environ.update(
+            HOSTNAME_VALUE="penguin",
+        )
+        expected_interpolated_value = "host-penguin"
+
+        service_dicts = load_from_filename('tests/fixtures/extends/valid-interpolation.yml')
+        for service in service_dicts:
+            self.assertTrue(service['hostname'], expected_interpolated_value)
+
     def test_volume_path(self):
         dicts = load_from_filename('tests/fixtures/volume-path/docker-compose.yml')