|
@@ -1,7 +1,6 @@
|
|
from __future__ import absolute_import
|
|
from __future__ import absolute_import
|
|
from __future__ import unicode_literals
|
|
from __future__ import unicode_literals
|
|
|
|
|
|
-import codecs
|
|
|
|
import functools
|
|
import functools
|
|
import logging
|
|
import logging
|
|
import operator
|
|
import operator
|
|
@@ -17,6 +16,9 @@ from cached_property import cached_property
|
|
from ..const import COMPOSEFILE_V1 as V1
|
|
from ..const import COMPOSEFILE_V1 as V1
|
|
from ..const import COMPOSEFILE_V2_0 as V2_0
|
|
from ..const import COMPOSEFILE_V2_0 as V2_0
|
|
from ..utils import build_string_dict
|
|
from ..utils import build_string_dict
|
|
|
|
+from .environment import env_vars_from_file
|
|
|
|
+from .environment import Environment
|
|
|
|
+from .environment import split_env
|
|
from .errors import CircularReference
|
|
from .errors import CircularReference
|
|
from .errors import ComposeFileNotFound
|
|
from .errors import ComposeFileNotFound
|
|
from .errors import ConfigurationError
|
|
from .errors import ConfigurationError
|
|
@@ -113,13 +115,21 @@ 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, environment=None):
|
|
|
|
+ if environment is None:
|
|
|
|
+ environment = Environment.from_env_file(working_dir)
|
|
|
|
+ return super(ConfigDetails, cls).__new__(
|
|
|
|
+ cls, working_dir, config_files, environment
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
|
|
class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
|
|
@@ -207,11 +217,13 @@ class ServiceConfig(namedtuple('_ServiceConfig', 'working_dir filename name conf
|
|
config)
|
|
config)
|
|
|
|
|
|
|
|
|
|
-def find(base_dir, filenames):
|
|
|
|
|
|
+def find(base_dir, filenames, environment):
|
|
if filenames == ['-']:
|
|
if filenames == ['-']:
|
|
return ConfigDetails(
|
|
return ConfigDetails(
|
|
os.getcwd(),
|
|
os.getcwd(),
|
|
- [ConfigFile(None, yaml.safe_load(sys.stdin))])
|
|
|
|
|
|
+ [ConfigFile(None, yaml.safe_load(sys.stdin))],
|
|
|
|
+ environment
|
|
|
|
+ )
|
|
|
|
|
|
if filenames:
|
|
if filenames:
|
|
filenames = [os.path.join(base_dir, f) for f in filenames]
|
|
filenames = [os.path.join(base_dir, f) for f in filenames]
|
|
@@ -221,7 +233,9 @@ def find(base_dir, filenames):
|
|
log.debug("Using configuration files: {}".format(",".join(filenames)))
|
|
log.debug("Using configuration files: {}".format(",".join(filenames)))
|
|
return ConfigDetails(
|
|
return ConfigDetails(
|
|
os.path.dirname(filenames[0]),
|
|
os.path.dirname(filenames[0]),
|
|
- [ConfigFile.from_filename(f) for f in filenames])
|
|
|
|
|
|
+ [ConfigFile.from_filename(f) for f in filenames],
|
|
|
|
+ environment
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
def validate_config_version(config_files):
|
|
def validate_config_version(config_files):
|
|
@@ -289,7 +303,7 @@ def load(config_details):
|
|
validate_config_version(config_details.config_files)
|
|
validate_config_version(config_details.config_files)
|
|
|
|
|
|
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)
|
|
@@ -301,10 +315,7 @@ def load(config_details):
|
|
networks = load_mapping(
|
|
networks = load_mapping(
|
|
config_details.config_files, 'get_networks', 'Network'
|
|
config_details.config_files, 'get_networks', 'Network'
|
|
)
|
|
)
|
|
- service_dicts = load_services(
|
|
|
|
- config_details.working_dir,
|
|
|
|
- main_file,
|
|
|
|
- [file.get_service_dicts() for file in config_details.config_files])
|
|
|
|
|
|
+ service_dicts = load_services(config_details, main_file)
|
|
|
|
|
|
if main_file.version != V1:
|
|
if main_file.version != V1:
|
|
for service_dict in service_dicts:
|
|
for service_dict in service_dicts:
|
|
@@ -348,14 +359,16 @@ def load_mapping(config_files, get_func, entity_type):
|
|
return mapping
|
|
return mapping
|
|
|
|
|
|
|
|
|
|
-def load_services(working_dir, config_file, service_configs):
|
|
|
|
|
|
+def load_services(config_details, config_file):
|
|
def build_service(service_name, service_dict, service_names):
|
|
def build_service(service_name, service_dict, service_names):
|
|
service_config = ServiceConfig.with_abs_paths(
|
|
service_config = ServiceConfig.with_abs_paths(
|
|
- working_dir,
|
|
|
|
|
|
+ config_details.working_dir,
|
|
config_file.filename,
|
|
config_file.filename,
|
|
service_name,
|
|
service_name,
|
|
service_dict)
|
|
service_dict)
|
|
- resolver = ServiceExtendsResolver(service_config, config_file)
|
|
|
|
|
|
+ resolver = ServiceExtendsResolver(
|
|
|
|
+ service_config, config_file, environment=config_details.environment
|
|
|
|
+ )
|
|
service_dict = process_service(resolver.run())
|
|
service_dict = process_service(resolver.run())
|
|
|
|
|
|
service_config = service_config._replace(config=service_dict)
|
|
service_config = service_config._replace(config=service_dict)
|
|
@@ -363,7 +376,8 @@ def load_services(working_dir, config_file, service_configs):
|
|
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):
|
|
@@ -383,6 +397,10 @@ def load_services(working_dir, config_file, service_configs):
|
|
for name in all_service_names
|
|
for name in all_service_names
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ service_configs = [
|
|
|
|
+ file.get_service_dicts() for file in config_details.config_files
|
|
|
|
+ ]
|
|
|
|
+
|
|
service_config = service_configs[0]
|
|
service_config = service_configs[0]
|
|
for next_config in service_configs[1:]:
|
|
for next_config in service_configs[1:]:
|
|
service_config = merge_services(service_config, next_config)
|
|
service_config = merge_services(service_config, next_config)
|
|
@@ -390,16 +408,17 @@ def load_services(working_dir, config_file, service_configs):
|
|
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)
|
|
@@ -407,11 +426,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
|
|
@@ -428,11 +449,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, 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
|
|
|
|
|
|
@property
|
|
@property
|
|
def signature(self):
|
|
def signature(self):
|
|
@@ -462,8 +484,8 @@ 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)
|
|
|
|
|
|
return config_path, service_config, service_name
|
|
return config_path, service_config, service_name
|
|
@@ -476,7 +498,9 @@ class ServiceExtendsResolver(object):
|
|
service_name,
|
|
service_name,
|
|
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()
|
|
other_service_dict = process_service(service_config)
|
|
other_service_dict = process_service(service_config)
|
|
@@ -505,7 +529,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.
|
|
"""
|
|
"""
|
|
@@ -514,12 +538,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):
|
|
@@ -598,11 +622,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:
|
|
@@ -629,7 +653,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)
|
|
@@ -777,15 +801,6 @@ def merge_environment(base, override):
|
|
return env
|
|
return env
|
|
|
|
|
|
|
|
|
|
-def split_env(env):
|
|
|
|
- if isinstance(env, six.binary_type):
|
|
|
|
- env = env.decode('utf-8', 'replace')
|
|
|
|
- if '=' in env:
|
|
|
|
- return env.split('=', 1)
|
|
|
|
- else:
|
|
|
|
- return env, None
|
|
|
|
-
|
|
|
|
-
|
|
|
|
def split_label(label):
|
|
def split_label(label):
|
|
if '=' in label:
|
|
if '=' in label:
|
|
return label.split('=', 1)
|
|
return label.split('=', 1)
|
|
@@ -823,30 +838,15 @@ def parse_ulimits(ulimits):
|
|
return dict(ulimits)
|
|
return dict(ulimits)
|
|
|
|
|
|
|
|
|
|
-def resolve_env_var(key, val):
|
|
|
|
|
|
+def resolve_env_var(key, val, environment):
|
|
if val is not None:
|
|
if val is not None:
|
|
return key, val
|
|
return key, val
|
|
- elif key in os.environ:
|
|
|
|
- return key, os.environ[key]
|
|
|
|
|
|
+ elif environment and key in environment:
|
|
|
|
+ return key, environment[key]
|
|
else:
|
|
else:
|
|
return key, None
|
|
return key, None
|
|
|
|
|
|
|
|
|
|
-def env_vars_from_file(filename):
|
|
|
|
- """
|
|
|
|
- Read in a line delimited file of environment variables.
|
|
|
|
- """
|
|
|
|
- if not os.path.exists(filename):
|
|
|
|
- raise ConfigurationError("Couldn't find env file: %s" % filename)
|
|
|
|
- env = {}
|
|
|
|
- for line in codecs.open(filename, 'r', 'utf-8'):
|
|
|
|
- line = line.strip()
|
|
|
|
- if line and not line.startswith('#'):
|
|
|
|
- k, v = split_env(line)
|
|
|
|
- env[k] = v
|
|
|
|
- return env
|
|
|
|
-
|
|
|
|
-
|
|
|
|
def resolve_volume_paths(working_dir, service_dict):
|
|
def resolve_volume_paths(working_dir, service_dict):
|
|
return [
|
|
return [
|
|
resolve_volume_path(working_dir, volume)
|
|
resolve_volume_path(working_dir, volume)
|
|
@@ -866,7 +866,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 = {}
|
|
@@ -876,7 +876,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
|
|
|
|
|