From b9ca5188a21f99a2798c5451049e27be3e75ac27 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Mon, 7 Mar 2016 17:16:25 -0800 Subject: [PATCH] Remove Environment singleton, instead carry instance during config processing Project name and compose file detection also updated Signed-off-by: Joffrey F --- compose/cli/command.py | 6 +-- compose/config/config.py | 69 ++++++++++++++++++++------------- compose/config/environment.py | 22 +---------- compose/config/interpolation.py | 6 +-- 4 files changed, 48 insertions(+), 55 deletions(-) diff --git a/compose/cli/command.py b/compose/cli/command.py index 3fcfbb4b3e..8c0bf07ffd 100644 --- a/compose/cli/command.py +++ b/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) diff --git a/compose/config/config.py b/compose/config/config.py index c9c4e3084f..7db66004fd 100644 --- a/compose/config/config.py +++ b/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 diff --git a/compose/config/environment.py b/compose/config/environment.py index 45f1c43fb7..87b41223b6 100644 --- a/compose/config/environment.py +++ b/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) diff --git a/compose/config/interpolation.py b/compose/config/interpolation.py index b76638d930..63020d91ad 100644 --- a/compose/config/interpolation.py +++ b/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() )