浏览代码

Remove Environment singleton, instead carry instance during config processing

Project name and compose file detection also updated

Signed-off-by: Joffrey F <[email protected]>
Joffrey F 9 年之前
父节点
当前提交
b9ca5188a2
共有 4 个文件被更改,包括 48 次插入55 次删除
  1. 3 3
      compose/cli/command.py
  2. 41 28
      compose/config/config.py
  3. 2 20
      compose/config/environment.py
  4. 2 4
      compose/config/interpolation.py

+ 3 - 3
compose/cli/command.py

@@ -34,7 +34,7 @@ def get_config_path_from_options(base_dir, options):
     if file_option:
         return file_option
 
-    environment = config.environment.get_instance(base_dir)
+    environment = config.environment.Environment(base_dir)
     config_files = environment.get('COMPOSE_FILE')
     if config_files:
         return config_files.split(os.pathsep)
@@ -58,7 +58,7 @@ def get_project(project_dir, config_path=None, project_name=None, verbose=False,
     config_details = config.find(project_dir, config_path)
     project_name = get_project_name(config_details.working_dir, project_name)
     config_data = config.load(config_details)
-    environment = config.environment.get_instance(project_dir)
+    environment = config.environment.Environment(project_dir)
 
     api_version = environment.get(
         'COMPOSE_API_VERSION',
@@ -75,7 +75,7 @@ def get_project_name(working_dir, project_name=None):
     def normalize_name(name):
         return re.sub(r'[^a-z0-9]', '', name.lower())
 
-    environment = config.environment.get_instance(working_dir)
+    environment = config.environment.Environment(working_dir)
     project_name = project_name or environment.get('COMPOSE_PROJECT_NAME')
     if project_name:
         return normalize_name(project_name)

+ 41 - 28
compose/config/config.py

@@ -114,14 +114,24 @@ DEFAULT_OVERRIDE_FILENAME = 'docker-compose.override.yml'
 log = logging.getLogger(__name__)
 
 
-class ConfigDetails(namedtuple('_ConfigDetails', 'working_dir config_files')):
+class ConfigDetails(namedtuple('_ConfigDetails', 'working_dir config_files environment')):
     """
     :param working_dir: the directory to use for relative paths in the config
     :type  working_dir: string
     :param config_files: list of configuration files to load
     :type  config_files: list of :class:`ConfigFile`
+    :param environment: computed environment values for this project
+    :type  environment: :class:`environment.Environment`
      """
 
+    def __new__(cls, working_dir, config_files):
+        return super(ConfigDetails, cls).__new__(
+            cls,
+            working_dir,
+            config_files,
+            Environment(working_dir),
+        )
+
 
 class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
     """
@@ -291,12 +301,8 @@ def load(config_details):
     """
     validate_config_version(config_details.config_files)
 
-    # load environment in working dir for later use in interpolation
-    # it is done here to avoid having to pass down working_dir
-    Environment.get_instance(config_details.working_dir)
-
     processed_files = [
-        process_config_file(config_file)
+        process_config_file(config_file, config_details.environment)
         for config_file in config_details.config_files
     ]
     config_details = config_details._replace(config_files=processed_files)
@@ -362,7 +368,7 @@ def load_services(config_details, config_file):
             service_name,
             service_dict)
         resolver = ServiceExtendsResolver(
-            service_config, config_file
+            service_config, config_file, environment=config_details.environment
         )
         service_dict = process_service(resolver.run())
 
@@ -371,7 +377,8 @@ def load_services(config_details, config_file):
         service_dict = finalize_service(
             service_config,
             service_names,
-            config_file.version)
+            config_file.version,
+            config_details.environment)
         return service_dict
 
     def build_services(service_config):
@@ -402,16 +409,17 @@ def load_services(config_details, config_file):
     return build_services(service_config)
 
 
-def interpolate_config_section(filename, config, section):
+def interpolate_config_section(filename, config, section, environment):
     validate_config_section(filename, config, section)
-    return interpolate_environment_variables(config, section)
+    return interpolate_environment_variables(config, section, environment)
 
 
-def process_config_file(config_file, service_name=None):
+def process_config_file(config_file, environment, service_name=None):
     services = interpolate_config_section(
         config_file.filename,
         config_file.get_service_dicts(),
-        'service')
+        'service',
+        environment,)
 
     if config_file.version == V2_0:
         processed_config = dict(config_file.config)
@@ -419,11 +427,13 @@ def process_config_file(config_file, service_name=None):
         processed_config['volumes'] = interpolate_config_section(
             config_file.filename,
             config_file.get_volumes(),
-            'volume')
+            'volume',
+            environment,)
         processed_config['networks'] = interpolate_config_section(
             config_file.filename,
             config_file.get_networks(),
-            'network')
+            'network',
+            environment,)
 
     if config_file.version == V1:
         processed_config = services
@@ -440,11 +450,12 @@ def process_config_file(config_file, service_name=None):
 
 
 class ServiceExtendsResolver(object):
-    def __init__(self, service_config, config_file, already_seen=None):
+    def __init__(self, service_config, config_file, environment=None, already_seen=None):
         self.service_config = service_config
         self.working_dir = service_config.working_dir
         self.already_seen = already_seen or []
         self.config_file = config_file
+        self.environment = environment or Environment(None)
 
     @property
     def signature(self):
@@ -474,7 +485,7 @@ class ServiceExtendsResolver(object):
         extends_file = ConfigFile.from_filename(config_path)
         validate_config_version([self.config_file, extends_file])
         extended_file = process_config_file(
-            extends_file, service_name=service_name
+            extends_file, self.environment, service_name=service_name
         )
         service_config = extended_file.get_service(service_name)
 
@@ -489,6 +500,7 @@ class ServiceExtendsResolver(object):
                 service_dict),
             self.config_file,
             already_seen=self.already_seen + [self.signature],
+            environment=self.environment
         )
 
         service_config = resolver.run()
@@ -518,7 +530,7 @@ class ServiceExtendsResolver(object):
         return filename
 
 
-def resolve_environment(service_dict):
+def resolve_environment(service_dict, environment=None):
     """Unpack any environment variables from an env_file, if set.
     Interpolate environment values if set.
     """
@@ -527,12 +539,12 @@ def resolve_environment(service_dict):
         env.update(env_vars_from_file(env_file))
 
     env.update(parse_environment(service_dict.get('environment')))
-    return dict(resolve_env_var(k, v) for k, v in six.iteritems(env))
+    return dict(resolve_env_var(k, v, environment) for k, v in six.iteritems(env))
 
 
-def resolve_build_args(build):
+def resolve_build_args(build, environment):
     args = parse_build_arguments(build.get('args'))
-    return dict(resolve_env_var(k, v) for k, v in six.iteritems(args))
+    return dict(resolve_env_var(k, v, environment) for k, v in six.iteritems(args))
 
 
 def validate_extended_service_dict(service_dict, filename, service):
@@ -611,11 +623,11 @@ def process_service(service_config):
     return service_dict
 
 
-def finalize_service(service_config, service_names, version):
+def finalize_service(service_config, service_names, version, environment):
     service_dict = dict(service_config.config)
 
     if 'environment' in service_dict or 'env_file' in service_dict:
-        service_dict['environment'] = resolve_environment(service_dict)
+        service_dict['environment'] = resolve_environment(service_dict, environment)
         service_dict.pop('env_file', None)
 
     if 'volumes_from' in service_dict:
@@ -642,7 +654,7 @@ def finalize_service(service_config, service_names, version):
     if 'restart' in service_dict:
         service_dict['restart'] = parse_restart_spec(service_dict['restart'])
 
-    normalize_build(service_dict, service_config.working_dir)
+    normalize_build(service_dict, service_config.working_dir, environment)
 
     service_dict['name'] = service_config.name
     return normalize_v1_service_format(service_dict)
@@ -836,11 +848,10 @@ def parse_ulimits(ulimits):
         return dict(ulimits)
 
 
-def resolve_env_var(key, val):
-    environment = Environment.get_instance()
+def resolve_env_var(key, val, environment):
     if val is not None:
         return key, val
-    elif key in environment:
+    elif environment and key in environment:
         return key, environment[key]
     else:
         return key, None
@@ -880,7 +891,7 @@ def resolve_volume_path(working_dir, volume):
         return container_path
 
 
-def normalize_build(service_dict, working_dir):
+def normalize_build(service_dict, working_dir, environment):
 
     if 'build' in service_dict:
         build = {}
@@ -890,7 +901,9 @@ def normalize_build(service_dict, working_dir):
         else:
             build.update(service_dict['build'])
             if 'args' in build:
-                build['args'] = build_string_dict(resolve_build_args(build))
+                build['args'] = build_string_dict(
+                    resolve_build_args(build, environment)
+                )
 
         service_dict['build'] = build
 

+ 2 - 20
compose/config/environment.py

@@ -29,24 +29,10 @@ class BlankDefaultDict(dict):
 
 
 class Environment(BlankDefaultDict):
-    __instance = None
-
-    @classmethod
-    def get_instance(cls, base_dir='.'):
-        if cls.__instance:
-            return cls.__instance
-
-        instance = cls(base_dir)
-        cls.__instance = instance
-        return instance
-
-    @classmethod
-    def reset(cls):
-        cls.__instance = None
-
     def __init__(self, base_dir):
         super(Environment, self).__init__()
-        self.load_environment_file(os.path.join(base_dir, '.env'))
+        if base_dir:
+            self.load_environment_file(os.path.join(base_dir, '.env'))
         self.update(os.environ)
 
     def load_environment_file(self, path):
@@ -63,7 +49,3 @@ class Environment(BlankDefaultDict):
                     )
                 mapping.__setitem__(*line.split('=', 1))
         self.update(mapping)
-
-
-def get_instance(base_dir=None):
-    return Environment.get_instance(base_dir)

+ 2 - 4
compose/config/interpolation.py

@@ -6,17 +6,15 @@ from string import Template
 
 import six
 
-from .environment import Environment
 from .errors import ConfigurationError
 log = logging.getLogger(__name__)
 
 
-def interpolate_environment_variables(config, section):
-    mapping = Environment.get_instance()
+def interpolate_environment_variables(config, section, environment):
 
     def process_item(name, config_dict):
         return dict(
-            (key, interpolate_value(name, key, val, section, mapping))
+            (key, interpolate_value(name, key, val, section, environment))
             for key, val in (config_dict or {}).items()
         )