Remove Environment singleton, instead carry instance during config processing

Project name and compose file detection also updated

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2016-03-07 17:16:25 -08:00
parent bf8e501b5e
commit b9ca5188a2
4 changed files with 48 additions and 55 deletions

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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()
)