diff --git a/compose/config/config.py b/compose/config/config.py index 44f84ac82..fd933d939 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -211,8 +211,11 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')): def get_secrets(self): return {} if self.version < const.COMPOSEFILE_V3_1 else self.config.get('secrets', {}) + def get_configs(self): + return {} if self.version < const.COMPOSEFILE_V3_3 else self.config.get('configs', {}) -class Config(namedtuple('_Config', 'version services volumes networks secrets')): + +class Config(namedtuple('_Config', 'version services volumes networks secrets configs')): """ :param version: configuration version :type version: int @@ -224,6 +227,8 @@ class Config(namedtuple('_Config', 'version services volumes networks secrets')) :type networks: :class:`dict` :param secrets: Dictionary mapping secret names to description dictionaries :type secrets: :class:`dict` + :param configs: Dictionary mapping config names to description dictionaries + :type configs: :class:`dict` """ @@ -340,6 +345,7 @@ def check_swarm_only_config(service_dicts): check_swarm_only_key(service_dicts, 'deploy') check_swarm_only_key(service_dicts, 'credential_spec') + check_swarm_only_key(service_dicts, 'configs') def load(config_details): @@ -364,7 +370,12 @@ def load(config_details): networks = load_mapping( config_details.config_files, 'get_networks', 'Network' ) - secrets = load_secrets(config_details.config_files, config_details.working_dir) + secrets = load_mapping( + config_details.config_files, 'get_secrets', 'Secret', config_details.working_dir + ) + configs = load_mapping( + config_details.config_files, 'get_configs', 'Config', config_details.working_dir + ) service_dicts = load_services(config_details, main_file) if main_file.version != V1: @@ -373,10 +384,10 @@ def load(config_details): check_swarm_only_config(service_dicts) - return Config(main_file.version, service_dicts, volumes, networks, secrets) + return Config(main_file.version, service_dicts, volumes, networks, secrets, configs) -def load_mapping(config_files, get_func, entity_type): +def load_mapping(config_files, get_func, entity_type, working_dir=None): mapping = {} for config_file in config_files: @@ -401,6 +412,9 @@ def load_mapping(config_files, get_func, entity_type): if 'labels' in config: config['labels'] = parse_labels(config['labels']) + if 'file' in config: + config['file'] = expand_path(working_dir, config['file']) + return mapping @@ -414,29 +428,6 @@ def validate_external(entity_type, name, config): entity_type, name, ', '.join(k for k in config if k != 'external'))) -def load_secrets(config_files, working_dir): - mapping = {} - - for config_file in config_files: - for name, config in config_file.get_secrets().items(): - mapping[name] = config or {} - if not config: - continue - - external = config.get('external') - if external: - validate_external('Secret', name, config) - if isinstance(external, dict): - config['external_name'] = external.get('name') - else: - config['external_name'] = name - - if 'file' in config: - config['file'] = expand_path(working_dir, config['file']) - - return mapping - - def load_services(config_details, config_file): def build_service(service_name, service_dict, service_names): service_config = ServiceConfig.with_abs_paths( @@ -815,6 +806,11 @@ def finalize_service(service_config, service_names, version, environment): types.ServiceSecret.parse(s) for s in service_dict['secrets'] ] + if 'configs' in service_dict: + service_dict['configs'] = [ + types.ServiceConfig.parse(c) for c in service_dict['configs'] + ] + normalize_build(service_dict, service_config.working_dir, environment) service_dict['name'] = service_config.name @@ -906,6 +902,7 @@ def merge_service_dicts(base, override, version): md.merge_mapping('depends_on', parse_depends_on) md.merge_sequence('links', ServiceLink.parse) md.merge_sequence('secrets', types.ServiceSecret.parse) + md.merge_sequence('configs', types.ServiceConfig.parse) md.merge_mapping('deploy', parse_deploy) for field in ['volumes', 'devices']: diff --git a/compose/config/serialize.py b/compose/config/serialize.py index ac78b77a2..beafe02b9 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -8,7 +8,6 @@ from compose.config import types from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V2_1 as V2_1 from compose.const import COMPOSEFILE_V2_2 as V2_2 -from compose.const import COMPOSEFILE_V3_1 as V3_1 from compose.const import COMPOSEFILE_V3_2 as V3_2 from compose.const import COMPOSEFILE_V3_3 as V3_3 @@ -25,6 +24,7 @@ def serialize_dict_type(dumper, data): yaml.SafeDumper.add_representer(types.VolumeFromSpec, serialize_config_type) yaml.SafeDumper.add_representer(types.VolumeSpec, serialize_config_type) yaml.SafeDumper.add_representer(types.ServiceSecret, serialize_dict_type) +yaml.SafeDumper.add_representer(types.ServiceConfig, serialize_dict_type) yaml.SafeDumper.add_representer(types.ServicePort, serialize_dict_type) @@ -41,21 +41,15 @@ def denormalize_config(config, image_digests=None): service_dict.pop('name'): service_dict for service_dict in denormalized_services } - result['networks'] = config.networks.copy() - for net_name, net_conf in result['networks'].items(): - if 'external_name' in net_conf: - del net_conf['external_name'] + for key in ('networks', 'volumes', 'secrets', 'configs'): + config_dict = getattr(config, key) + if not config_dict: + continue + result[key] = config_dict.copy() + for name, conf in result[key].items(): + if 'external_name' in conf: + del conf['external_name'] - result['volumes'] = config.volumes.copy() - for vol_name, vol_conf in result['volumes'].items(): - if 'external_name' in vol_conf: - del vol_conf['external_name'] - - if config.version in (V3_1, V3_2, V3_3): - result['secrets'] = config.secrets.copy() - for secret_name, secret_conf in result['secrets'].items(): - if 'external_name' in secret_conf: - del secret_conf['external_name'] return result diff --git a/compose/config/types.py b/compose/config/types.py index 85daa70b0..6d3ca3f3b 100644 --- a/compose/config/types.py +++ b/compose/config/types.py @@ -238,8 +238,7 @@ class ServiceLink(namedtuple('_ServiceLink', 'target alias')): return self.alias -class ServiceSecret(namedtuple('_ServiceSecret', 'source target uid gid mode')): - +class ServiceConfigBase(namedtuple('_ServiceConfigBase', 'source target uid gid mode')): @classmethod def parse(cls, spec): if isinstance(spec, six.string_types): @@ -262,6 +261,14 @@ class ServiceSecret(namedtuple('_ServiceSecret', 'source target uid gid mode')): ) +class ServiceSecret(ServiceConfigBase): + pass + + +class ServiceConfig(ServiceConfigBase): + pass + + class ServicePort(namedtuple('_ServicePort', 'target published protocol mode external_ip')): def __new__(cls, target, published, *args, **kwargs): try: