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

View File

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

View File

@ -29,23 +29,9 @@ class BlankDefaultDict(dict):
class Environment(BlankDefaultDict): 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): def __init__(self, base_dir):
super(Environment, self).__init__() super(Environment, self).__init__()
if base_dir:
self.load_environment_file(os.path.join(base_dir, '.env')) self.load_environment_file(os.path.join(base_dir, '.env'))
self.update(os.environ) self.update(os.environ)
@ -63,7 +49,3 @@ class Environment(BlankDefaultDict):
) )
mapping.__setitem__(*line.split('=', 1)) mapping.__setitem__(*line.split('=', 1))
self.update(mapping) 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 import six
from .environment import Environment
from .errors import ConfigurationError from .errors import ConfigurationError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def interpolate_environment_variables(config, section): def interpolate_environment_variables(config, section, environment):
mapping = Environment.get_instance()
def process_item(name, config_dict): def process_item(name, config_dict):
return 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() for key, val in (config_dict or {}).items()
) )