Merge pull request #7588 from aiordache/rm_version

Merge V2 - V3 compose file formats (optional version field)
This commit is contained in:
Anca Iordache 2020-07-27 11:11:34 +02:00 committed by GitHub
commit 52d2fcc274
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 836 additions and 8404 deletions

View File

@ -66,7 +66,6 @@ def project_from_options(project_dir, options, additional_options=None):
context=context, context=context,
environment=environment, environment=environment,
override_dir=override_dir, override_dir=override_dir,
compatibility=compatibility_from_options(project_dir, options, environment),
interpolate=(not additional_options.get('--no-interpolate')), interpolate=(not additional_options.get('--no-interpolate')),
environment_file=environment_file environment_file=environment_file
) )
@ -98,7 +97,6 @@ def get_config_from_options(base_dir, options, additional_options=None):
) )
return config.load( return config.load(
config.find(base_dir, config_path, environment, override_dir), config.find(base_dir, config_path, environment, override_dir),
compatibility_from_options(config_path, options, environment),
not additional_options.get('--no-interpolate') not additional_options.get('--no-interpolate')
) )
@ -120,14 +118,14 @@ def get_config_path_from_options(base_dir, options, environment):
def get_project(project_dir, config_path=None, project_name=None, verbose=False, def get_project(project_dir, config_path=None, project_name=None, verbose=False,
context=None, environment=None, override_dir=None, context=None, environment=None, override_dir=None,
compatibility=False, interpolate=True, environment_file=None): interpolate=True, environment_file=None):
if not environment: if not environment:
environment = Environment.from_env_file(project_dir) environment = Environment.from_env_file(project_dir)
config_details = config.find(project_dir, config_path, environment, override_dir) config_details = config.find(project_dir, config_path, environment, override_dir)
project_name = get_project_name( project_name = get_project_name(
config_details.working_dir, project_name, environment config_details.working_dir, project_name, environment
) )
config_data = config.load(config_details, compatibility, interpolate) config_data = config.load(config_details, interpolate)
api_version = environment.get( api_version = environment.get(
'COMPOSE_API_VERSION', 'COMPOSE_API_VERSION',
@ -188,13 +186,3 @@ def get_project_name(working_dir, project_name=None, environment=None):
return normalize_name(project) return normalize_name(project)
return 'default' return 'default'
def compatibility_from_options(working_dir, options=None, environment=None):
"""Get compose v3 compatibility from --compatibility option
or from COMPOSE_COMPATIBILITY environment variable."""
compatibility_option = options.get('--compatibility')
compatibility_environment = environment.get_boolean('COMPOSE_COMPATIBILITY')
return compatibility_option or compatibility_environment

View File

@ -24,7 +24,6 @@ from ..config import resolve_build_args
from ..config.environment import Environment from ..config.environment import Environment
from ..config.serialize import serialize_config from ..config.serialize import serialize_config
from ..config.types import VolumeSpec from ..config.types import VolumeSpec
from ..const import COMPOSEFILE_V2_2 as V2_2
from ..const import IS_WINDOWS_PLATFORM from ..const import IS_WINDOWS_PLATFORM
from ..errors import StreamParseError from ..errors import StreamParseError
from ..progress_stream import StreamOutputError from ..progress_stream import StreamOutputError
@ -205,7 +204,7 @@ class TopLevelCommand(object):
--project-directory PATH Specify an alternate working directory --project-directory PATH Specify an alternate working directory
(default: the path of the Compose file) (default: the path of the Compose file)
--compatibility If set, Compose will attempt to convert keys --compatibility If set, Compose will attempt to convert keys
in v3 files to their non-Swarm equivalent in v3 files to their non-Swarm equivalent (DEPRECATED)
--env-file PATH Specify an alternate environment file --env-file PATH Specify an alternate environment file
Commands: Commands:
@ -882,12 +881,6 @@ class TopLevelCommand(object):
""" """
timeout = timeout_from_opts(options) timeout = timeout_from_opts(options)
if self.project.config_version == V2_2:
raise UserError(
'The scale command is incompatible with the v2.2 format. '
'Use the up command with the --scale flag instead.'
)
else:
log.warning( log.warning(
'The scale command is deprecated. ' 'The scale command is deprecated. '
'Use the up command with the --scale flag instead.' 'Use the up command with the --scale flag instead.'

View File

@ -12,18 +12,13 @@ import yaml
from cached_property import cached_property from cached_property import cached_property
from . import types from . import types
from .. import const from ..const import COMPOSE_SPEC as VERSION
from ..const import COMPOSEFILE_V1 as V1 from ..const import COMPOSEFILE_V1 as V1
from ..const import COMPOSEFILE_V2_1 as V2_1
from ..const import COMPOSEFILE_V2_3 as V2_3
from ..const import COMPOSEFILE_V3_0 as V3_0
from ..const import COMPOSEFILE_V3_4 as V3_4
from ..utils import build_string_dict from ..utils import build_string_dict
from ..utils import json_hash from ..utils import json_hash
from ..utils import parse_bytes from ..utils import parse_bytes
from ..utils import parse_nanoseconds_int from ..utils import parse_nanoseconds_int
from ..utils import splitdrive from ..utils import splitdrive
from ..version import ComposeVersion
from .environment import env_vars_from_file from .environment import env_vars_from_file
from .environment import Environment from .environment import Environment
from .environment import split_env from .environment import split_env
@ -189,15 +184,28 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
@cached_property @cached_property
def version(self): def version(self):
if 'version' not in self.config: version = self.config.get('version', None)
return V1 if not version:
# no version is specified in the config file
services = self.config.get('services', None)
networks = self.config.get('networks', None)
volumes = self.config.get('volumes', None)
if services or networks or volumes:
# validate V2/V3 structure
for section in ['services', 'networks', 'volumes']:
validate_config_section(
self.filename, self.config.get(section, {}), section)
return VERSION
version = self.config['version'] # validate V1 structure
validate_config_section(
self.filename, self.config, 'services')
return V1
if isinstance(version, dict): if isinstance(version, dict):
log.warning('Unexpected type for "version" key in "{}". Assuming ' log.warning('Unexpected type for "version" key in "{}". Assuming '
'"version" is the name of a service, and defaulting to ' '"version" is the name of a service, and defaulting to '
'Compose file version 1.'.format(self.filename)) 'Compose file version {}.'.format(self.filename, V1))
return V1 return V1
if not isinstance(version, str): if not isinstance(version, str):
@ -205,31 +213,32 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
'Version in "{}" is invalid - it should be a string.' 'Version in "{}" is invalid - it should be a string.'
.format(self.filename)) .format(self.filename))
if version == '1': if isinstance(version, str):
raise ConfigurationError( version_pattern = re.compile(r"^[1-3]+(\.\d+)?$")
'Version in "{}" is invalid. {}'
.format(self.filename, VERSION_EXPLANATION)
)
version_pattern = re.compile(r"^[2-9]+(\.\d+)?$")
if not version_pattern.match(version): if not version_pattern.match(version):
raise ConfigurationError( raise ConfigurationError(
'Version "{}" in "{}" is invalid.' 'Version "{}" in "{}" is invalid.'
.format(version, self.filename)) .format(version, self.filename))
if version == '2': if version.startswith("1"):
return const.COMPOSEFILE_V2_0 version = V1
else:
version = VERSION
if version == '3': if version == V1:
return const.COMPOSEFILE_V3_0 raise ConfigurationError(
'Version in "{}" is invalid. {}'
return ComposeVersion(version) .format(self.filename, VERSION_EXPLANATION)
)
return version
def get_service(self, name): def get_service(self, name):
return self.get_service_dicts()[name] return self.get_service_dicts()[name]
def get_service_dicts(self): def get_service_dicts(self):
return self.config if self.version == V1 else self.config.get('services', {}) if self.version == V1:
return self.config
return self.config.get('services', {})
def get_volumes(self): def get_volumes(self):
return {} if self.version == V1 else self.config.get('volumes', {}) return {} if self.version == V1 else self.config.get('volumes', {})
@ -238,10 +247,10 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
return {} if self.version == V1 else self.config.get('networks', {}) return {} if self.version == V1 else self.config.get('networks', {})
def get_secrets(self): def get_secrets(self):
return {} if self.version < const.COMPOSEFILE_V3_1 else self.config.get('secrets', {}) return {} if self.version == V1 else self.config.get('secrets', {})
def get_configs(self): def get_configs(self):
return {} if self.version < const.COMPOSEFILE_V3_3 else self.config.get('configs', {}) return {} if self.version == V1 else self.config.get('configs', {})
class Config(namedtuple('_Config', 'version services volumes networks secrets configs')): class Config(namedtuple('_Config', 'version services volumes networks secrets configs')):
@ -299,6 +308,7 @@ def find(base_dir, filenames, environment, override_dir=None):
def validate_config_version(config_files): def validate_config_version(config_files):
main_file = config_files[0] main_file = config_files[0]
validate_top_level_object(main_file) validate_top_level_object(main_file)
for next_file in config_files[1:]: for next_file in config_files[1:]:
validate_top_level_object(next_file) validate_top_level_object(next_file)
@ -355,7 +365,7 @@ def find_candidates_in_parent_dirs(filenames, path):
return (candidates, path) return (candidates, path)
def check_swarm_only_config(service_dicts, compatibility=False): def check_swarm_only_config(service_dicts):
warning_template = ( warning_template = (
"Some services ({services}) use the '{key}' key, which will be ignored. " "Some services ({services}) use the '{key}' key, which will be ignored. "
"Compose does not support '{key}' configuration - use " "Compose does not support '{key}' configuration - use "
@ -371,18 +381,20 @@ def check_swarm_only_config(service_dicts, compatibility=False):
key=key key=key
) )
) )
if not compatibility:
check_swarm_only_key(service_dicts, 'deploy') check_swarm_only_key(service_dicts, 'deploy')
check_swarm_only_key(service_dicts, 'configs') check_swarm_only_key(service_dicts, 'configs')
def load(config_details, compatibility=False, interpolate=True): def load(config_details, interpolate=True):
"""Load the configuration from a working directory and a list of """Load the configuration from a working directory and a list of
configuration files. Files are loaded in order, and merged on top configuration files. Files are loaded in order, and merged on top
of each other to create the final configuration. of each other to create the final configuration.
Return a fully interpolated, extended and validated configuration. Return a fully interpolated, extended and validated configuration.
""" """
# validate against latest version and if fails do it against v1 schema
validate_config_version(config_details.config_files) validate_config_version(config_details.config_files)
processed_files = [ processed_files = [
@ -404,15 +416,15 @@ def load(config_details, compatibility=False, interpolate=True):
configs = load_mapping( configs = load_mapping(
config_details.config_files, 'get_configs', 'Config', config_details.working_dir config_details.config_files, 'get_configs', 'Config', config_details.working_dir
) )
service_dicts = load_services(config_details, main_file, compatibility, interpolate=interpolate) service_dicts = load_services(config_details, main_file, interpolate=interpolate)
if main_file.version != V1: if main_file.version != V1:
for service_dict in service_dicts: for service_dict in service_dicts:
match_named_volumes(service_dict, volumes) match_named_volumes(service_dict, volumes)
check_swarm_only_config(service_dicts, compatibility) check_swarm_only_config(service_dicts)
version = V2_3 if compatibility and main_file.version >= V3_0 else main_file.version version = main_file.version
return Config(version, service_dicts, volumes, networks, secrets, configs) return Config(version, service_dicts, volumes, networks, secrets, configs)
@ -449,14 +461,15 @@ def load_mapping(config_files, get_func, entity_type, working_dir=None):
def validate_external(entity_type, name, config, version): def validate_external(entity_type, name, config, version):
if (version < V2_1 or (version >= V3_0 and version < V3_4)) and len(config.keys()) > 1: for k in config.keys():
if k not in ['external', 'name']:
raise ConfigurationError( raise ConfigurationError(
"{} {} declared as external but specifies additional attributes " "{} {} declared as external but specifies additional attributes "
"({}).".format( "({}).".format(
entity_type, name, ', '.join(k for k in config if k != 'external'))) entity_type, name, ', '.join(k for k in config if k != 'external')))
def load_services(config_details, config_file, compatibility=False, interpolate=True): def load_services(config_details, config_file, interpolate=True):
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(
config_details.working_dir, config_details.working_dir,
@ -475,7 +488,6 @@ def load_services(config_details, config_file, compatibility=False, interpolate=
service_names, service_names,
config_file.version, config_file.version,
config_details.environment, config_details.environment,
compatibility,
interpolate interpolate
) )
return service_dict return service_dict
@ -554,7 +566,6 @@ def process_config_file(config_file, environment, service_name=None, interpolate
environment, environment,
interpolate, interpolate,
) )
if config_file.version >= const.COMPOSEFILE_V3_1:
processed_config['secrets'] = process_config_section( processed_config['secrets'] = process_config_section(
config_file, config_file,
config_file.get_secrets(), config_file.get_secrets(),
@ -562,7 +573,6 @@ def process_config_file(config_file, environment, service_name=None, interpolate
environment, environment,
interpolate, interpolate,
) )
if config_file.version >= const.COMPOSEFILE_V3_3:
processed_config['configs'] = process_config_section( processed_config['configs'] = process_config_section(
config_file, config_file,
config_file.get_configs(), config_file.get_configs(),
@ -574,7 +584,7 @@ def process_config_file(config_file, environment, service_name=None, interpolate
processed_config = services processed_config = services
config_file = config_file._replace(config=processed_config) config_file = config_file._replace(config=processed_config)
validate_against_config_schema(config_file) validate_against_config_schema(config_file, config_file.version)
if service_name and service_name not in services: if service_name and service_name not in services:
raise ConfigurationError( raise ConfigurationError(
@ -876,7 +886,7 @@ def finalize_service_volumes(service_dict, environment):
return service_dict return service_dict
def finalize_service(service_config, service_names, version, environment, compatibility, def finalize_service(service_config, service_names, version, environment,
interpolate=True): interpolate=True):
service_dict = dict(service_config.config) service_dict = dict(service_config.config)
@ -918,106 +928,10 @@ def finalize_service(service_config, service_names, version, environment, compat
normalize_build(service_dict, service_config.working_dir, environment) normalize_build(service_dict, service_config.working_dir, environment)
if compatibility:
service_dict = translate_credential_spec_to_security_opt(service_dict)
service_dict, ignored_keys = translate_deploy_keys_to_container_config(
service_dict
)
if ignored_keys:
log.warning(
'The following deploy sub-keys are not supported in compatibility mode and have'
' been ignored: {}'.format(', '.join(ignored_keys))
)
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)
def translate_resource_keys_to_container_config(resources_dict, service_dict):
if 'limits' in resources_dict:
service_dict['mem_limit'] = resources_dict['limits'].get('memory')
if 'cpus' in resources_dict['limits']:
service_dict['cpus'] = float(resources_dict['limits']['cpus'])
if 'reservations' in resources_dict:
service_dict['mem_reservation'] = resources_dict['reservations'].get('memory')
if 'cpus' in resources_dict['reservations']:
return ['resources.reservations.cpus']
return []
def convert_restart_policy(name):
try:
return {
'any': 'always',
'none': 'no',
'on-failure': 'on-failure'
}[name]
except KeyError:
raise ConfigurationError('Invalid restart policy "{}"'.format(name))
def convert_credential_spec_to_security_opt(credential_spec):
if 'file' in credential_spec:
return 'file://{file}'.format(file=credential_spec['file'])
return 'registry://{registry}'.format(registry=credential_spec['registry'])
def translate_credential_spec_to_security_opt(service_dict):
result = []
if 'credential_spec' in service_dict:
spec = convert_credential_spec_to_security_opt(service_dict['credential_spec'])
result.append('credentialspec={spec}'.format(spec=spec))
if result:
service_dict['security_opt'] = result
return service_dict
def translate_deploy_keys_to_container_config(service_dict):
if 'credential_spec' in service_dict:
del service_dict['credential_spec']
if 'configs' in service_dict:
del service_dict['configs']
if 'deploy' not in service_dict:
return service_dict, []
deploy_dict = service_dict['deploy']
ignored_keys = [
k for k in ['endpoint_mode', 'labels', 'update_config', 'rollback_config']
if k in deploy_dict
]
if 'replicas' in deploy_dict and deploy_dict.get('mode', 'replicated') == 'replicated':
scale = deploy_dict.get('replicas', 1)
max_replicas = deploy_dict.get('placement', {}).get('max_replicas_per_node', scale)
service_dict['scale'] = min(scale, max_replicas)
if max_replicas < scale:
log.warning("Scale is limited to {} ('max_replicas_per_node' field).".format(
max_replicas))
if 'restart_policy' in deploy_dict:
service_dict['restart'] = {
'Name': convert_restart_policy(deploy_dict['restart_policy'].get('condition', 'any')),
'MaximumRetryCount': deploy_dict['restart_policy'].get('max_attempts', 0)
}
for k in deploy_dict['restart_policy'].keys():
if k != 'condition' and k != 'max_attempts':
ignored_keys.append('restart_policy.{}'.format(k))
ignored_keys.extend(
translate_resource_keys_to_container_config(
deploy_dict.get('resources', {}), service_dict
)
)
del service_dict['deploy']
return service_dict, ignored_keys
def normalize_v1_service_format(service_dict): def normalize_v1_service_format(service_dict):
if 'log_driver' in service_dict or 'log_opt' in service_dict: if 'log_driver' in service_dict or 'log_opt' in service_dict:
if 'logging' not in service_dict: if 'logging' not in service_dict:

View File

@ -1,14 +1,14 @@
{ {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft/2019-09/schema#",
"id": "config_schema_v3.6.json", "id": "config_schema_compose_spec.json",
"type": "object", "type": "object",
"required": ["version"], "title": "Compose Specification",
"description": "The Compose file is a YAML file defining a multi-containers based application.",
"properties": { "properties": {
"version": { "version": {
"type": "string" "type": "string",
"description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file."
}, },
"services": { "services": {
"id": "#/properties/services", "id": "#/properties/services",
"type": "object", "type": "object",
@ -19,7 +19,6 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"networks": { "networks": {
"id": "#/properties/networks", "id": "#/properties/networks",
"type": "object", "type": "object",
@ -29,7 +28,6 @@
} }
} }
}, },
"volumes": { "volumes": {
"id": "#/properties/volumes", "id": "#/properties/volumes",
"type": "object", "type": "object",
@ -40,7 +38,6 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"secrets": { "secrets": {
"id": "#/properties/secrets", "id": "#/properties/secrets",
"type": "object", "type": "object",
@ -51,7 +48,6 @@
}, },
"additionalProperties": false "additionalProperties": false
}, },
"configs": { "configs": {
"id": "#/properties/configs", "id": "#/properties/configs",
"type": "object", "type": "object",
@ -63,16 +59,12 @@
"additionalProperties": false "additionalProperties": false
} }
}, },
"patternProperties": {"^x-": {}}, "patternProperties": {"^x-": {}},
"additionalProperties": false, "additionalProperties": false,
"definitions": { "definitions": {
"service": { "service": {
"id": "#/definitions/service", "id": "#/definitions/service",
"type": "object", "type": "object",
"properties": { "properties": {
"deploy": {"$ref": "#/definitions/deployment"}, "deploy": {"$ref": "#/definitions/deployment"},
"build": { "build": {
@ -88,12 +80,42 @@
"cache_from": {"$ref": "#/definitions/list_of_strings"}, "cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"}, "network": {"type": "string"},
"target": {"type": "string"}, "target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]} "shm_size": {"type": ["integer", "string"]},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"isolation": {"type": "string"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
] ]
}, },
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"}, "cgroup_parent": {"type": "string"},
@ -116,19 +138,60 @@
"uid": {"type": "string"}, "uid": {"type": "string"},
"gid": {"type": "string"}, "gid": {"type": "string"},
"mode": {"type": "number"} "mode": {"type": "number"}
} },
"additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
] ]
} }
}, },
"container_name": {"type": "string"}, "container_name": {"type": "string"},
"credential_spec": {"type": "object", "properties": { "cpu_count": {"type": "integer", "minimum": 0},
"cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpu_period": {"type": ["number", "string"]},
"cpu_rt_period": {"type": ["number", "string"]},
"cpu_rt_runtime": {"type": ["number", "string"]},
"cpus": {"type": "number", "minimum": 0},
"cpuset": {"type": "string"},
"credential_spec": {
"type": "object",
"properties": {
"config": {"type": "string"},
"file": {"type": "string"}, "file": {"type": "string"},
"registry": {"type": "string"} "registry": {"type": "string"}
}}, },
"depends_on": {"$ref": "#/definitions/list_of_strings"}, "additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"depends_on": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": ["service_started", "service_healthy"]
}
},
"required": ["condition"]
}
}
}
]
},
"device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"}, "dns": {"$ref": "#/definitions/string_or_list"},
"dns_opt": {"type": "array","items": {"type": "string"}, "uniqueItems": true},
"dns_search": {"$ref": "#/definitions/string_or_list"}, "dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"}, "domainname": {"type": "string"},
"entrypoint": { "entrypoint": {
@ -149,19 +212,39 @@
"uniqueItems": true "uniqueItems": true
}, },
"extends": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"}, "extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"healthcheck": {"$ref": "#/definitions/healthcheck"}, "healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"}, "hostname": {"type": "string"},
"image": {"type": "string"}, "image": {"type": "string"},
"init": {"type": "boolean"},
"ipc": {"type": "string"}, "ipc": {"type": "string"},
"isolation": {"type": "string"}, "isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"}, "labels": {"$ref": "#/definitions/list_or_dict"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": { "logging": {
"type": "object", "type": "object",
"properties": { "properties": {
"driver": {"type": "string"}, "driver": {"type": "string"},
"options": { "options": {
@ -171,12 +254,15 @@
} }
} }
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"mac_address": {"type": "string"}, "mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"}, "network_mode": {"type": "string"},
"networks": { "networks": {
"oneOf": [ "oneOf": [
{"$ref": "#/definitions/list_of_strings"}, {"$ref": "#/definitions/list_of_strings"},
@ -190,9 +276,12 @@
"properties": { "properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"}, "aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"}, "ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"} "ipv6_address": {"type": "string"},
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
"priority": {"type": "number"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
{"type": "null"} {"type": "null"}
] ]
@ -202,8 +291,11 @@
} }
] ]
}, },
"oom_kill_disable": {"type": "boolean"},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"pid": {"type": ["string", "null"]}, "pid": {"type": ["string", "null"]},
"pids_limit": {"type": ["number", "string"]},
"platform": {"type": "string"},
"ports": { "ports": {
"type": "array", "type": "array",
"items": { "items": {
@ -218,16 +310,26 @@
"published": {"type": "integer"}, "published": {"type": "integer"},
"protocol": {"type": "string"} "protocol": {"type": "string"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
] ]
}, },
"uniqueItems": true "uniqueItems": true
}, },
"privileged": {"type": "boolean"}, "privileged": {"type": "boolean"},
"pull_policy": {"type": "string", "enum": [
"always", "never", "if_not_present"
]},
"read_only": {"type": "boolean"}, "read_only": {"type": "boolean"},
"restart": {"type": "string"}, "restart": {"type": "string"},
"runtime": {
"deprecated": true,
"type": "string"
},
"scale": {
"type": "integer"
},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]}, "shm_size": {"type": ["number", "string"]},
"secrets": { "secrets": {
@ -243,7 +345,9 @@
"uid": {"type": "string"}, "uid": {"type": "string"},
"gid": {"type": "string"}, "gid": {"type": "string"},
"mode": {"type": "number"} "mode": {"type": "number"}
} },
"additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
] ]
} }
@ -261,13 +365,14 @@
"oneOf": [ "oneOf": [
{"type": "integer"}, {"type": "integer"},
{ {
"type":"object", "type": "object",
"properties": { "properties": {
"hard": {"type": "integer"}, "hard": {"type": "integer"},
"soft": {"type": "integer"} "soft": {"type": "integer"}
}, },
"required": ["soft", "hard"], "required": ["soft", "hard"],
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
] ]
} }
@ -293,13 +398,17 @@
"type": "object", "type": "object",
"properties": { "properties": {
"propagation": {"type": "string"} "propagation": {"type": "string"}
} },
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"volume": { "volume": {
"type": "object", "type": "object",
"properties": { "properties": {
"nocopy": {"type": "boolean"} "nocopy": {"type": "boolean"}
} },
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"tmpfs": { "tmpfs": {
"type": "object", "type": "object",
@ -308,24 +417,32 @@
"type": "integer", "type": "integer",
"minimum": 0 "minimum": 0
} }
} },
"additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
], ],
"uniqueItems": true "uniqueItems": true
} }
}, },
"volumes_from": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"working_dir": {"type": "string"} "working_dir": {"type": "string"}
}, },
"patternProperties": {"^x-": {}},
"additionalProperties": false "additionalProperties": false
}, },
"healthcheck": { "healthcheck": {
"id": "#/definitions/healthcheck", "id": "#/definitions/healthcheck",
"type": "object", "type": "object",
"additionalProperties": false,
"properties": { "properties": {
"disable": {"type": "boolean"}, "disable": {"type": "boolean"},
"interval": {"type": "string", "format": "duration"}, "interval": {"type": "string", "format": "duration"},
@ -338,7 +455,9 @@
}, },
"timeout": {"type": "string", "format": "duration"}, "timeout": {"type": "string", "format": "duration"},
"start_period": {"type": "string", "format": "duration"} "start_period": {"type": "string", "format": "duration"}
} },
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"deployment": { "deployment": {
"id": "#/definitions/deployment", "id": "#/definitions/deployment",
@ -348,6 +467,21 @@
"endpoint_mode": {"type": "string"}, "endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"}, "replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/list_or_dict"}, "labels": {"$ref": "#/definitions/list_or_dict"},
"rollback_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"},
"order": {"type": "string", "enum": [
"start-first", "stop-first"
]}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"update_config": { "update_config": {
"type": "object", "type": "object",
"properties": { "properties": {
@ -360,7 +494,8 @@
"start-first", "stop-first" "start-first", "stop-first"
]} ]}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"resources": { "resources": {
"type": "object", "type": "object",
@ -371,7 +506,8 @@
"cpus": {"type": "string"}, "cpus": {"type": "string"},
"memory": {"type": "string"} "memory": {"type": "string"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"reservations": { "reservations": {
"type": "object", "type": "object",
@ -380,10 +516,12 @@
"memory": {"type": "string"}, "memory": {"type": "string"},
"generic_resources": {"$ref": "#/definitions/generic_resources"} "generic_resources": {"$ref": "#/definitions/generic_resources"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"restart_policy": { "restart_policy": {
"type": "object", "type": "object",
@ -393,7 +531,8 @@
"max_attempts": {"type": "integer"}, "max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"} "window": {"type": "string", "format": "duration"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"placement": { "placement": {
"type": "object", "type": "object",
@ -406,16 +545,19 @@
"properties": { "properties": {
"spread": {"type": "string"} "spread": {"type": "string"}
}, },
"additionalProperties": false "additionalProperties": false,
} "patternProperties": {"^x-": {}}
} }
}, },
"additionalProperties": false "max_replicas_per_node": {"type": "integer"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"generic_resources": { "generic_resources": {
"id": "#/definitions/generic_resources", "id": "#/definitions/generic_resources",
"type": "array", "type": "array",
@ -428,13 +570,14 @@
"kind": {"type": "string"}, "kind": {"type": "string"},
"value": {"type": "number"} "value": {"type": "number"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
} }
}, },
"network": { "network": {
"id": "#/definitions/network", "id": "#/definitions/network",
"type": ["object", "null"], "type": ["object", "null"],
@ -456,28 +599,46 @@
"items": { "items": {
"type": "object", "type": "object",
"properties": { "properties": {
"subnet": {"type": "string"} "subnet": {"type": "string", "format": "subnet_ip_address"},
}, "ip_range": {"type": "string"},
"additionalProperties": false "gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"additionalProperties": false,
"patternProperties": {"^.+$": {"type": "string"}}
} }
} }
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
},
"options": {
"type": "object",
"additionalProperties": false,
"patternProperties": {"^.+$": {"type": "string"}}
}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"external": { "external": {
"type": ["boolean", "object"], "type": ["boolean", "object"],
"properties": { "properties": {
"name": {"type": "string"} "name": {
"deprecated": true,
"type": "string"
}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"internal": {"type": "boolean"}, "internal": {"type": "boolean"},
"attachable": {"type": "boolean"}, "attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/list_or_dict"} "labels": {"$ref": "#/definitions/list_or_dict"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"volume": { "volume": {
"id": "#/definitions/volume", "id": "#/definitions/volume",
"type": ["object", "null"], "type": ["object", "null"],
@ -493,15 +654,19 @@
"external": { "external": {
"type": ["boolean", "object"], "type": ["boolean", "object"],
"properties": { "properties": {
"name": {"type": "string"} "name": {
"deprecated": true,
"type": "string"
}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"labels": {"$ref": "#/definitions/list_or_dict"} "labels": {"$ref": "#/definitions/list_or_dict"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"secret": { "secret": {
"id": "#/definitions/secret", "id": "#/definitions/secret",
"type": "object", "type": "object",
@ -514,11 +679,19 @@
"name": {"type": "string"} "name": {"type": "string"}
} }
}, },
"labels": {"$ref": "#/definitions/list_or_dict"} "labels": {"$ref": "#/definitions/list_or_dict"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
}, },
"additionalProperties": false "template_driver": {"type": "string"}
},
"additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"config": { "config": {
"id": "#/definitions/config", "id": "#/definitions/config",
"type": "object", "type": "object",
@ -528,27 +701,29 @@
"external": { "external": {
"type": ["boolean", "object"], "type": ["boolean", "object"],
"properties": { "properties": {
"name": {"type": "string"} "name": {
"deprecated": true,
"type": "string"
}
} }
}, },
"labels": {"$ref": "#/definitions/list_or_dict"} "labels": {"$ref": "#/definitions/list_or_dict"},
"template_driver": {"type": "string"}
}, },
"additionalProperties": false "additionalProperties": false,
"patternProperties": {"^x-": {}}
}, },
"string_or_list": { "string_or_list": {
"oneOf": [ "oneOf": [
{"type": "string"}, {"type": "string"},
{"$ref": "#/definitions/list_of_strings"} {"$ref": "#/definitions/list_of_strings"}
] ]
}, },
"list_of_strings": { "list_of_strings": {
"type": "array", "type": "array",
"items": {"type": "string"}, "items": {"type": "string"},
"uniqueItems": true "uniqueItems": true
}, },
"list_or_dict": { "list_or_dict": {
"oneOf": [ "oneOf": [
{ {
@ -563,7 +738,22 @@
{"type": "array", "items": {"type": "string"}, "uniqueItems": true} {"type": "array", "items": {"type": "string"}, "uniqueItems": true}
] ]
}, },
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": { "constraints": {
"service": { "service": {
"id": "#/definitions/constraints/service", "id": "#/definitions/constraints/service",

View File

@ -1,424 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v2.0.json",
"type": "object",
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpuset": {"type": "string"},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_opt": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {"type": "object"}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"},
"priority": {"type": "number"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volume_driver": {"type": "string"},
"volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": "object",
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {"$ref": "#/definitions/ipam_config"}
},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"}
},
"additionalProperties": false
},
"ipam_config": {
"id": "#/definitions/ipam_config",
"type": "object",
"properties": {
"subnet": {"type": "string"},
"ip_range": {"type": "string"},
"gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,480 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v2.1.json",
"type": "object",
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"isolation": {"type": "string"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpu_period": {"type": ["number", "string"]},
"cpuset": {"type": "string"},
"depends_on": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": ["service_started", "service_healthy"]
}
},
"required": ["condition"]
}
}
}
]
},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns_opt": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {"type": "object"}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"},
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
"priority": {"type": "number"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"oom_kill_disable": {"type": "boolean"},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"pids_limit": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"storage_opt": {"type": "object"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volume_driver": {"type": "string"},
"volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"network": {
"id": "#/definitions/network",
"type": "object",
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {"$ref": "#/definitions/ipam_config"}
},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"enable_ipv6": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"additionalProperties": false
},
"ipam_config": {
"id": "#/definitions/ipam_config",
"type": "object",
"properties": {
"subnet": {"type": "string"},
"ip_range": {"type": "string"},
"gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,489 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v2.2.json",
"type": "object",
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"},
"isolation": {"type": "string"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"cpu_count": {"type": "integer", "minimum": 0},
"cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpu_period": {"type": ["number", "string"]},
"cpu_rt_period": {"type": ["number", "string"]},
"cpu_rt_runtime": {"type": ["number", "string"]},
"cpus": {"type": "number", "minimum": 0},
"cpuset": {"type": "string"},
"depends_on": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": ["service_started", "service_healthy"]
}
},
"required": ["condition"]
}
}
}
]
},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns_opt": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"init": {"type": ["boolean", "string"]},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {"type": "object"}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"},
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
"priority": {"type": "number"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"oom_kill_disable": {"type": "boolean"},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"scale": {"type": "integer"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"pids_limit": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"storage_opt": {"type": "object"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"volume_driver": {"type": "string"},
"volumes_from": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"network": {
"id": "#/definitions/network",
"type": "object",
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {"$ref": "#/definitions/ipam_config"}
},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"enable_ipv6": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"additionalProperties": false
},
"ipam_config": {
"id": "#/definitions/ipam_config",
"type": "object",
"properties": {
"subnet": {"type": "string"},
"ip_range": {"type": "string"},
"gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,533 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v2.3.json",
"type": "object",
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"},
"target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"isolation": {"type": "string"}
},
"additionalProperties": false
}
]
},
"cap_add": {"$ref": "#/definitions/list_of_strings"},
"cap_drop": {"$ref": "#/definitions/list_of_strings"},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"cpu_count": {"type": "integer", "minimum": 0},
"cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpu_period": {"type": ["number", "string"]},
"cpu_rt_period": {"type": ["number", "string"]},
"cpu_rt_runtime": {"type": ["number", "string"]},
"cpus": {"type": "number", "minimum": 0},
"cpuset": {"type": "string"},
"depends_on": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": ["service_started", "service_healthy"]
}
},
"required": ["condition"]
}
}
}
]
},
"device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
"devices": {"$ref": "#/definitions/list_of_strings"},
"dns_opt": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"$ref": "#/definitions/list_of_strings"},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"init": {"type": ["boolean", "string"]},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"$ref": "#/definitions/list_of_strings"},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {"type": "object"}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"},
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
"priority": {"type": "number"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"oom_kill_disable": {"type": "boolean"},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"runtime": {"type": "string"},
"scale": {"type": "integer"},
"security_opt": {"$ref": "#/definitions/list_of_strings"},
"shm_size": {"type": ["number", "string"]},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"pids_limit": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"storage_opt": {"type": "object"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
},
"tmpfs": {
"type": "object",
"properties": {
"size": {"type": ["integer", "string"]}
}
}
}
}
],
"uniqueItems": true
}
},
"volume_driver": {"type": "string"},
"volumes_from": {"$ref": "#/definitions/list_of_strings"},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"start_period": {"type": "string"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"network": {
"id": "#/definitions/network",
"type": "object",
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {"$ref": "#/definitions/ipam_config"}
},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"enable_ipv6": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"additionalProperties": false
},
"ipam_config": {
"id": "#/definitions/ipam_config",
"type": "object",
"properties": {
"subnet": {"type": "string"},
"ip_range": {"type": "string"},
"gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,535 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v2.4.json",
"type": "object",
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"blkio_config": {
"type": "object",
"properties": {
"device_read_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_read_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_bps": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"device_write_iops": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_limit"}
},
"weight": {"type": "integer"},
"weight_device": {
"type": "array",
"items": {"$ref": "#/definitions/blkio_weight"}
}
},
"additionalProperties": false
},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"},
"target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"isolation": {"type": "string"}
},
"additionalProperties": false
}
]
},
"cap_add": {"$ref": "#/definitions/list_of_strings"},
"cap_drop": {"$ref": "#/definitions/list_of_strings"},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"cpu_count": {"type": "integer", "minimum": 0},
"cpu_percent": {"type": "integer", "minimum": 0, "maximum": 100},
"cpu_shares": {"type": ["number", "string"]},
"cpu_quota": {"type": ["number", "string"]},
"cpu_period": {"type": ["number", "string"]},
"cpu_rt_period": {"type": ["number", "string"]},
"cpu_rt_runtime": {"type": ["number", "string"]},
"cpus": {"type": "number", "minimum": 0},
"cpuset": {"type": "string"},
"depends_on": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"additionalProperties": false,
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"type": "object",
"additionalProperties": false,
"properties": {
"condition": {
"type": "string",
"enum": ["service_started", "service_healthy"]
}
},
"required": ["condition"]
}
}
}
]
},
"device_cgroup_rules": {"$ref": "#/definitions/list_of_strings"},
"devices": {"$ref": "#/definitions/list_of_strings"},
"dns_opt": {
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"extends": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"properties": {
"service": {"type": "string"},
"file": {"type": "string"}
},
"required": ["service"],
"additionalProperties": false
}
]
},
"external_links": {"$ref": "#/definitions/list_of_strings"},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"group_add": {
"type": "array",
"items": {
"type": ["string", "number"]
},
"uniqueItems": true
},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"init": {"type": ["boolean", "string"]},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"$ref": "#/definitions/list_of_strings"},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {"type": "object"}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"mem_limit": {"type": ["number", "string"]},
"mem_reservation": {"type": ["string", "integer"]},
"mem_swappiness": {"type": "integer"},
"memswap_limit": {"type": ["number", "string"]},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"},
"link_local_ips": {"$ref": "#/definitions/list_of_strings"},
"priority": {"type": "number"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"oom_kill_disable": {"type": "boolean"},
"oom_score_adj": {"type": "integer", "minimum": -1000, "maximum": 1000},
"pid": {"type": ["string", "null"]},
"platform": {"type": "string"},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"runtime": {"type": "string"},
"scale": {"type": "integer"},
"security_opt": {"$ref": "#/definitions/list_of_strings"},
"shm_size": {"type": ["number", "string"]},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"pids_limit": {"type": ["number", "string"]},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"storage_opt": {"type": "object"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
},
"tmpfs": {
"type": "object",
"properties": {
"size": {"type": ["integer", "string"]}
}
}
}
}
],
"uniqueItems": true
}
},
"volume_driver": {"type": "string"},
"volumes_from": {"$ref": "#/definitions/list_of_strings"},
"working_dir": {"type": "string"}
},
"dependencies": {
"memswap_limit": ["mem_limit"]
},
"patternProperties": {"^x-": {}},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"start_period": {"type": "string"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"network": {
"id": "#/definitions/network",
"type": "object",
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {"$ref": "#/definitions/ipam_config"}
},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"enable_ipv6": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false
},
"ipam_config": {
"id": "#/definitions/ipam_config",
"type": "object",
"properties": {
"subnet": {"type": "string"},
"ip_range": {"type": "string"},
"gateway": {"type": "string"},
"aux_addresses": {
"type": "object",
"patternProperties": {
"^.+$": {"type": "string"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"},
"name": {"type": "string"}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"blkio_limit": {
"type": "object",
"properties": {
"path": {"type": "string"},
"rate": {"type": ["integer", "string"]}
},
"additionalProperties": false
},
"blkio_weight": {
"type": "object",
"properties": {
"path": {"type": "string"},
"weight": {"type": "integer"}
},
"additionalProperties": false
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,399 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.0.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/labels"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {"$ref": "#/definitions/resource"},
"reservations": {"$ref": "#/definitions/resource"}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"resource": {
"id": "#/definitions/resource",
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,444 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.1.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "ports"
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/labels"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {"$ref": "#/definitions/resource"},
"reservations": {"$ref": "#/definitions/resource"}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"resource": {
"id": "#/definitions/resource",
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,492 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.2.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"cache_from": {"$ref": "#/definitions/list_of_strings"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"container_name": {"type": "string"},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "number", "format": "ports"},
{"type": "string", "format": "ports"},
{
"type": "object",
"properties": {
"mode": {"type": "string"},
"target": {"type": "integer"},
"published": {"type": "integer"},
"protocol": {"type": "string"}
},
"additionalProperties": false
}
]
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
}
}
}
],
"uniqueItems": true
}
},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/labels"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {"$ref": "#/definitions/resource"},
"reservations": {"$ref": "#/definitions/resource"}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"resource": {
"id": "#/definitions/resource",
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,551 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.3.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
},
"configs": {
"id": "#/properties/configs",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/config"
}
},
"additionalProperties": false
}
},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"cache_from": {"$ref": "#/definitions/list_of_strings"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"configs": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"container_name": {"type": "string"},
"credential_spec": {"type": "object", "properties": {
"file": {"type": "string"},
"registry": {"type": "string"}
}},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "number", "format": "ports"},
{"type": "string", "format": "ports"},
{
"type": "object",
"properties": {
"mode": {"type": "string"},
"target": {"type": "integer"},
"published": {"type": "integer"},
"protocol": {"type": "string"}
},
"additionalProperties": false
}
]
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
}
}
}
],
"uniqueItems": true
}
},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/labels"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {"$ref": "#/definitions/resource"},
"reservations": {"$ref": "#/definitions/resource"}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}},
"preferences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"spread": {"type": "string"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"resource": {
"id": "#/definitions/resource",
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"config": {
"id": "#/definitions/config",
"type": "object",
"properties": {
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,560 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.4.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
},
"configs": {
"id": "#/properties/configs",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/config"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"},
"target": {"type": "string"}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"configs": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"container_name": {"type": "string"},
"credential_spec": {"type": "object", "properties": {
"file": {"type": "string"},
"registry": {"type": "string"}
}},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "number", "format": "ports"},
{"type": "string", "format": "ports"},
{
"type": "object",
"properties": {
"mode": {"type": "string"},
"target": {"type": "integer"},
"published": {"type": "integer"},
"protocol": {"type": "string"}
},
"additionalProperties": false
}
]
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
}
}
}
],
"uniqueItems": true
}
},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string", "format": "duration"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string", "format": "duration"},
"start_period": {"type": "string", "format": "duration"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/labels"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"},
"order": {"type": "string", "enum": [
"start-first", "stop-first"
]}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {"$ref": "#/definitions/resource"},
"reservations": {"$ref": "#/definitions/resource"}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}},
"preferences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"spread": {"type": "string"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"resource": {
"id": "#/definitions/resource",
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"config": {
"id": "#/definitions/config",
"type": "object",
"properties": {
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -1,588 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.5.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
},
"configs": {
"id": "#/properties/configs",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/config"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/labels"},
"cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"},
"target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"configs": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"container_name": {"type": "string"},
"credential_spec": {"type": "object", "properties": {
"file": {"type": "string"},
"registry": {"type": "string"}
}},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/labels"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "number", "format": "ports"},
{"type": "string", "format": "ports"},
{
"type": "object",
"properties": {
"mode": {"type": "string"},
"target": {"type": "integer"},
"published": {"type": "integer"},
"protocol": {"type": "string"}
},
"additionalProperties": false
}
]
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
}
},
"additionalProperties": false
}
],
"uniqueItems": true
}
},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string", "format": "duration"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string", "format": "duration"},
"start_period": {"type": "string", "format": "duration"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/labels"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"},
"order": {"type": "string", "enum": [
"start-first", "stop-first"
]}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"reservations": {
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"},
"generic_resources": {"$ref": "#/definitions/generic_resources"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}},
"preferences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"spread": {"type": "string"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"generic_resources": {
"id": "#/definitions/generic_resources",
"type": "array",
"items": {
"type": "object",
"properties": {
"discrete_resource_spec": {
"type": "object",
"properties": {
"kind": {"type": "string"},
"value": {"type": "number"}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string", "format": "subnet_ip_address"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"name": {"type": "string"},
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"config": {
"id": "#/definitions/config",
"type": "object",
"properties": {
"name": {"type": "string"},
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/labels"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"labels": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": "string"
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@ import re
from string import Template from string import Template
from .errors import ConfigurationError from .errors import ConfigurationError
from compose.const import COMPOSEFILE_V2_0 as V2_0 from compose.const import COMPOSEFILE_V1 as V1
from compose.utils import parse_bytes from compose.utils import parse_bytes
from compose.utils import parse_nanoseconds_int from compose.utils import parse_nanoseconds_int
@ -25,7 +25,7 @@ class Interpolator(object):
def interpolate_environment_variables(version, config, section, environment): def interpolate_environment_variables(version, config, section, environment):
if version <= V2_0: if version == V1:
interpolator = Interpolator(Template, environment) interpolator = Interpolator(Template, environment)
else: else:
interpolator = Interpolator(TemplateWithDefaults, environment) interpolator = Interpolator(TemplateWithDefaults, environment)

View File

@ -1,13 +1,8 @@
import yaml import yaml
from compose.config import types from compose.config import types
from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_1 as V2_1
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_0 as V3_0
from compose.const import COMPOSEFILE_V3_2 as V3_2
from compose.const import COMPOSEFILE_V3_4 as V3_4
from compose.const import COMPOSEFILE_V3_5 as V3_5
def serialize_config_type(dumper, data): def serialize_config_type(dumper, data):
@ -49,7 +44,7 @@ yaml.SafeDumper.add_representer(types.ServicePort, serialize_dict_type)
def denormalize_config(config, image_digests=None): def denormalize_config(config, image_digests=None):
result = {'version': str(V2_1) if config.version == V1 else str(config.version)} result = {'version': str(config.version)}
denormalized_services = [ denormalized_services = [
denormalize_service_dict( denormalize_service_dict(
service_dict, service_dict,
@ -72,25 +67,11 @@ def denormalize_config(config, image_digests=None):
del conf['external_name'] del conf['external_name']
if 'name' in conf: if 'name' in conf:
if config.version < V2_1 or ( if 'external' in conf:
config.version >= V3_0 and config.version < v3_introduced_name_key(key)):
del conf['name']
elif 'external' in conf:
conf['external'] = bool(conf['external']) conf['external'] = bool(conf['external'])
if 'attachable' in conf and config.version < V3_2:
# For compatibility mode, this option is invalid in v2
del conf['attachable']
return result return result
def v3_introduced_name_key(key):
if key == 'volumes':
return V3_4
return V3_5
def serialize_config(config, image_digests=None, escape_dollar=True): def serialize_config(config, image_digests=None, escape_dollar=True):
if escape_dollar: if escape_dollar:
yaml.SafeDumper.add_representer(str, serialize_string_escape_dollar) yaml.SafeDumper.add_representer(str, serialize_string_escape_dollar)
@ -140,7 +121,7 @@ def denormalize_service_dict(service_dict, version, image_digest=None):
if version == V1 and 'network_mode' not in service_dict: if version == V1 and 'network_mode' not in service_dict:
service_dict['network_mode'] = 'bridge' service_dict['network_mode'] = 'bridge'
if 'depends_on' in service_dict and (version < V2_1 or version >= V3_0): if 'depends_on' in service_dict:
service_dict['depends_on'] = sorted([ service_dict['depends_on'] = sorted([
svc for svc in service_dict['depends_on'].keys() svc for svc in service_dict['depends_on'].keys()
]) ])
@ -162,10 +143,10 @@ def denormalize_service_dict(service_dict, version, image_digest=None):
if 'ports' in service_dict: if 'ports' in service_dict:
service_dict['ports'] = [ service_dict['ports'] = [
p.legacy_repr() if p.external_ip or version < V3_2 else p p.legacy_repr() if p.external_ip or version < VERSION else p
for p in service_dict['ports'] for p in service_dict['ports']
] ]
if 'volumes' in service_dict and (version < V2_3 or (version > V3_0 and version < V3_2)): if 'volumes' in service_dict and (version == V1):
service_dict['volumes'] = [ service_dict['volumes'] = [
v.legacy_repr() if isinstance(v, types.MountSpec) else v for v in service_dict['volumes'] v.legacy_repr() if isinstance(v, types.MountSpec) else v for v in service_dict['volumes']
] ]

View File

@ -282,7 +282,7 @@ def handle_error_for_schema_with_id(error, path):
invalid_config_key = parse_key_from_error_msg(error) invalid_config_key = parse_key_from_error_msg(error)
return get_unsupported_config_msg(path, invalid_config_key) return get_unsupported_config_msg(path, invalid_config_key)
if schema_id.startswith('config_schema_v'): if schema_id.startswith('config_schema_'):
invalid_config_key = parse_key_from_error_msg(error) invalid_config_key = parse_key_from_error_msg(error)
return ('Invalid top-level property "{key}". Valid top-level ' return ('Invalid top-level property "{key}". Valid top-level '
'sections for this Compose file are: {properties}, and ' 'sections for this Compose file are: {properties}, and '
@ -435,15 +435,29 @@ def process_config_schema_errors(error):
return handle_generic_error(error, path) return handle_generic_error(error, path)
def validate_against_config_schema(config_file): def keys_to_str(config_file):
schema = load_jsonschema(config_file) """
Non-string keys may break validator with patterned fields.
"""
d = {}
for k, v in config_file.items():
d[str(k)] = v
if isinstance(v, dict):
d[str(k)] = keys_to_str(v)
return d
def validate_against_config_schema(config_file, version):
schema = load_jsonschema(version)
config = keys_to_str(config_file.config)
format_checker = FormatChecker(["ports", "expose", "subnet_ip_address"]) format_checker = FormatChecker(["ports", "expose", "subnet_ip_address"])
validator = Draft4Validator( validator = Draft4Validator(
schema, schema,
resolver=RefResolver(get_resolver_path(), schema), resolver=RefResolver(get_resolver_path(), schema),
format_checker=format_checker) format_checker=format_checker)
handle_errors( handle_errors(
validator.iter_errors(config_file.config), validator.iter_errors(config),
process_config_schema_errors, process_config_schema_errors,
config_file.filename) config_file.filename)
@ -453,7 +467,7 @@ def validate_service_constraints(config, service_name, config_file):
return process_service_constraint_errors( return process_service_constraint_errors(
errors, service_name, config_file.version) errors, service_name, config_file.version)
schema = load_jsonschema(config_file) schema = load_jsonschema(config_file.version)
validator = Draft4Validator(schema['definitions']['constraints']['service']) validator = Draft4Validator(schema['definitions']['constraints']['service'])
handle_errors(validator.iter_errors(config), handler, None) handle_errors(validator.iter_errors(config), handler, None)
@ -472,16 +486,19 @@ def get_schema_path():
return os.path.dirname(os.path.abspath(__file__)) return os.path.dirname(os.path.abspath(__file__))
def load_jsonschema(config_file): def load_jsonschema(version):
suffix = "compose_spec"
if version == V1:
suffix = "v1"
filename = os.path.join( filename = os.path.join(
get_schema_path(), get_schema_path(),
"config_schema_v{0}.json".format(config_file.version)) "config_schema_{0}.json".format(suffix))
if not os.path.exists(filename): if not os.path.exists(filename):
raise ConfigurationError( raise ConfigurationError(
'Version in "{}" is unsupported. {}' 'Version in "{}" is unsupported. {}'
.format(config_file.filename, VERSION_EXPLANATION)) .format(filename, VERSION_EXPLANATION))
with open(filename, "r") as fh: with open(filename, "r") as fh:
return json.load(fh) return json.load(fh)

View File

@ -24,56 +24,16 @@ SECRETS_PATH = '/run/secrets'
WINDOWS_LONGPATH_PREFIX = '\\\\?\\' WINDOWS_LONGPATH_PREFIX = '\\\\?\\'
COMPOSEFILE_V1 = ComposeVersion('1') COMPOSEFILE_V1 = ComposeVersion('1')
COMPOSEFILE_V2_0 = ComposeVersion('2.0') COMPOSE_SPEC = ComposeVersion('3')
COMPOSEFILE_V2_1 = ComposeVersion('2.1')
COMPOSEFILE_V2_2 = ComposeVersion('2.2')
COMPOSEFILE_V2_3 = ComposeVersion('2.3')
COMPOSEFILE_V2_4 = ComposeVersion('2.4')
COMPOSEFILE_V3_0 = ComposeVersion('3.0')
COMPOSEFILE_V3_1 = ComposeVersion('3.1')
COMPOSEFILE_V3_2 = ComposeVersion('3.2')
COMPOSEFILE_V3_3 = ComposeVersion('3.3')
COMPOSEFILE_V3_4 = ComposeVersion('3.4')
COMPOSEFILE_V3_5 = ComposeVersion('3.5')
COMPOSEFILE_V3_6 = ComposeVersion('3.6')
COMPOSEFILE_V3_7 = ComposeVersion('3.7')
COMPOSEFILE_V3_8 = ComposeVersion('3.8')
# minimum DOCKER ENGINE API version needed to support # minimum DOCKER ENGINE API version needed to support
# features for each compose schema version # features for each compose schema version
API_VERSIONS = { API_VERSIONS = {
COMPOSEFILE_V1: '1.21', COMPOSEFILE_V1: '1.21',
COMPOSEFILE_V2_0: '1.22', COMPOSE_SPEC: '1.38',
COMPOSEFILE_V2_1: '1.24',
COMPOSEFILE_V2_2: '1.25',
COMPOSEFILE_V2_3: '1.30',
COMPOSEFILE_V2_4: '1.35',
COMPOSEFILE_V3_0: '1.25',
COMPOSEFILE_V3_1: '1.25',
COMPOSEFILE_V3_2: '1.25',
COMPOSEFILE_V3_3: '1.30',
COMPOSEFILE_V3_4: '1.30',
COMPOSEFILE_V3_5: '1.30',
COMPOSEFILE_V3_6: '1.36',
COMPOSEFILE_V3_7: '1.38',
COMPOSEFILE_V3_8: '1.38',
} }
API_VERSION_TO_ENGINE_VERSION = { API_VERSION_TO_ENGINE_VERSION = {
API_VERSIONS[COMPOSEFILE_V1]: '1.9.0', API_VERSIONS[COMPOSEFILE_V1]: '1.9.0',
API_VERSIONS[COMPOSEFILE_V2_0]: '1.10.0', API_VERSIONS[COMPOSE_SPEC]: '18.06.0',
API_VERSIONS[COMPOSEFILE_V2_1]: '1.12.0',
API_VERSIONS[COMPOSEFILE_V2_2]: '1.13.0',
API_VERSIONS[COMPOSEFILE_V2_3]: '17.06.0',
API_VERSIONS[COMPOSEFILE_V2_4]: '17.12.0',
API_VERSIONS[COMPOSEFILE_V3_0]: '1.13.0',
API_VERSIONS[COMPOSEFILE_V3_1]: '1.13.0',
API_VERSIONS[COMPOSEFILE_V3_2]: '1.13.0',
API_VERSIONS[COMPOSEFILE_V3_3]: '17.06.0',
API_VERSIONS[COMPOSEFILE_V3_4]: '17.06.0',
API_VERSIONS[COMPOSEFILE_V3_5]: '17.06.0',
API_VERSIONS[COMPOSEFILE_V3_6]: '18.02.0',
API_VERSIONS[COMPOSEFILE_V3_7]: '18.06.0',
API_VERSIONS[COMPOSEFILE_V3_8]: '18.06.0',
} }

View File

@ -123,6 +123,18 @@ class Project(object):
service_dict.pop('secrets', None) or [], service_dict.pop('secrets', None) or [],
config_data.secrets) config_data.secrets)
service_dict['scale'] = project.get_service_scale(service_dict)
service_dict = translate_credential_spec_to_security_opt(service_dict)
service_dict, ignored_keys = translate_deploy_keys_to_container_config(
service_dict
)
if ignored_keys:
log.warning(
'The following deploy sub-keys are not supported and have'
' been ignored: {}'.format(', '.join(ignored_keys))
)
project.services.append( project.services.append(
Service( Service(
service_dict.pop('name'), service_dict.pop('name'),
@ -262,6 +274,35 @@ class Project(object):
return PidMode(pid_mode) return PidMode(pid_mode)
def get_service_scale(self, service_dict):
# service.scale for v2 and deploy.replicas for v3
scale = service_dict.get('scale', None)
deploy_dict = service_dict.get('deploy', None)
if not deploy_dict:
return 1 if scale is None else scale
if deploy_dict.get('mode', 'replicated') != 'replicated':
return 1 if scale is None else scale
replicas = deploy_dict.get('replicas', None)
if scale and replicas:
raise ConfigurationError(
"Both service.scale and service.deploy.replicas are set."
" Only one of them must be set."
)
if replicas:
scale = replicas
# deploy may contain placement constraints introduced in v3.8
max_replicas = deploy_dict.get('placement', {}).get(
'max_replicas_per_node',
scale)
scale = min(scale, max_replicas)
if max_replicas < scale:
log.warning("Scale is limited to {} ('max_replicas_per_node' field).".format(
max_replicas))
return scale
def start(self, service_names=None, **options): def start(self, service_names=None, **options):
containers = [] containers = []
@ -777,6 +818,81 @@ class Project(object):
return container_operation_with_timeout return container_operation_with_timeout
def translate_credential_spec_to_security_opt(service_dict):
result = []
if 'credential_spec' in service_dict:
spec = convert_credential_spec_to_security_opt(service_dict['credential_spec'])
result.append('credentialspec={spec}'.format(spec=spec))
if result:
service_dict['security_opt'] = result
return service_dict
def translate_resource_keys_to_container_config(resources_dict, service_dict):
if 'limits' in resources_dict:
service_dict['mem_limit'] = resources_dict['limits'].get('memory')
if 'cpus' in resources_dict['limits']:
service_dict['cpus'] = float(resources_dict['limits']['cpus'])
if 'reservations' in resources_dict:
service_dict['mem_reservation'] = resources_dict['reservations'].get('memory')
if 'cpus' in resources_dict['reservations']:
return ['resources.reservations.cpus']
return []
def convert_restart_policy(name):
try:
return {
'any': 'always',
'none': 'no',
'on-failure': 'on-failure'
}[name]
except KeyError:
raise ConfigurationError('Invalid restart policy "{}"'.format(name))
def convert_credential_spec_to_security_opt(credential_spec):
if 'file' in credential_spec:
return 'file://{file}'.format(file=credential_spec['file'])
return 'registry://{registry}'.format(registry=credential_spec['registry'])
def translate_deploy_keys_to_container_config(service_dict):
if 'credential_spec' in service_dict:
del service_dict['credential_spec']
if 'configs' in service_dict:
del service_dict['configs']
if 'deploy' not in service_dict:
return service_dict, []
deploy_dict = service_dict['deploy']
ignored_keys = [
k for k in ['endpoint_mode', 'labels', 'update_config', 'rollback_config']
if k in deploy_dict
]
if 'restart_policy' in deploy_dict:
service_dict['restart'] = {
'Name': convert_restart_policy(deploy_dict['restart_policy'].get('condition', 'any')),
'MaximumRetryCount': deploy_dict['restart_policy'].get('max_attempts', 0)
}
for k in deploy_dict['restart_policy'].keys():
if k != 'condition' and k != 'max_attempts':
ignored_keys.append('restart_policy.{}'.format(k))
ignored_keys.extend(
translate_resource_keys_to_container_config(
deploy_dict.get('resources', {}), service_dict
)
)
del service_dict['deploy']
return service_dict, ignored_keys
def get_volumes_from(project, service_dict): def get_volumes_from(project, service_dict):
volumes_from = service_dict.pop('volumes_from', None) volumes_from = service_dict.pop('volumes_from', None)
if not volumes_from: if not volumes_from:

View File

@ -23,73 +23,8 @@ exe = EXE(pyz,
'DATA' 'DATA'
), ),
( (
'compose/config/config_schema_v2.0.json', 'compose/config/config_schema_compose_spec.json',
'compose/config/config_schema_v2.0.json', 'compose/config/config_schema_compose_spec.json',
'DATA'
),
(
'compose/config/config_schema_v2.1.json',
'compose/config/config_schema_v2.1.json',
'DATA'
),
(
'compose/config/config_schema_v2.2.json',
'compose/config/config_schema_v2.2.json',
'DATA'
),
(
'compose/config/config_schema_v2.3.json',
'compose/config/config_schema_v2.3.json',
'DATA'
),
(
'compose/config/config_schema_v2.4.json',
'compose/config/config_schema_v2.4.json',
'DATA'
),
(
'compose/config/config_schema_v3.0.json',
'compose/config/config_schema_v3.0.json',
'DATA'
),
(
'compose/config/config_schema_v3.1.json',
'compose/config/config_schema_v3.1.json',
'DATA'
),
(
'compose/config/config_schema_v3.2.json',
'compose/config/config_schema_v3.2.json',
'DATA'
),
(
'compose/config/config_schema_v3.3.json',
'compose/config/config_schema_v3.3.json',
'DATA'
),
(
'compose/config/config_schema_v3.4.json',
'compose/config/config_schema_v3.4.json',
'DATA'
),
(
'compose/config/config_schema_v3.5.json',
'compose/config/config_schema_v3.5.json',
'DATA'
),
(
'compose/config/config_schema_v3.6.json',
'compose/config/config_schema_v3.6.json',
'DATA'
),
(
'compose/config/config_schema_v3.7.json',
'compose/config/config_schema_v3.7.json',
'DATA'
),
(
'compose/config/config_schema_v3.8.json',
'compose/config/config_schema_v3.8.json',
'DATA' 'DATA'
), ),
( (

View File

@ -32,73 +32,8 @@ coll = COLLECT(exe,
'DATA' 'DATA'
), ),
( (
'compose/config/config_schema_v2.0.json', 'compose/config/config_schema_compose_spec.json',
'compose/config/config_schema_v2.0.json', 'compose/config/config_schema_compose_spec.json',
'DATA'
),
(
'compose/config/config_schema_v2.1.json',
'compose/config/config_schema_v2.1.json',
'DATA'
),
(
'compose/config/config_schema_v2.2.json',
'compose/config/config_schema_v2.2.json',
'DATA'
),
(
'compose/config/config_schema_v2.3.json',
'compose/config/config_schema_v2.3.json',
'DATA'
),
(
'compose/config/config_schema_v2.4.json',
'compose/config/config_schema_v2.4.json',
'DATA'
),
(
'compose/config/config_schema_v3.0.json',
'compose/config/config_schema_v3.0.json',
'DATA'
),
(
'compose/config/config_schema_v3.1.json',
'compose/config/config_schema_v3.1.json',
'DATA'
),
(
'compose/config/config_schema_v3.2.json',
'compose/config/config_schema_v3.2.json',
'DATA'
),
(
'compose/config/config_schema_v3.3.json',
'compose/config/config_schema_v3.3.json',
'DATA'
),
(
'compose/config/config_schema_v3.4.json',
'compose/config/config_schema_v3.4.json',
'DATA'
),
(
'compose/config/config_schema_v3.5.json',
'compose/config/config_schema_v3.5.json',
'DATA'
),
(
'compose/config/config_schema_v3.6.json',
'compose/config/config_schema_v3.6.json',
'DATA'
),
(
'compose/config/config_schema_v3.7.json',
'compose/config/config_schema_v3.7.json',
'DATA'
),
(
'compose/config/config_schema_v3.8.json',
'compose/config/config_schema_v3.8.json',
'DATA' 'DATA'
), ),
( (

View File

@ -20,6 +20,8 @@ from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from ..helpers import create_host_file from ..helpers import create_host_file
from compose.cli.command import get_project from compose.cli.command import get_project
from compose.config.errors import DuplicateOverrideFileFound from compose.config.errors import DuplicateOverrideFileFound
from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V1 as V1
from compose.container import Container from compose.container import Container
from compose.project import OneOffFilter from compose.project import OneOffFilter
from compose.utils import nanoseconds_from_time_seconds from compose.utils import nanoseconds_from_time_seconds
@ -29,10 +31,6 @@ from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster from tests.integration.testcases import no_cluster
from tests.integration.testcases import pull_busybox from tests.integration.testcases import pull_busybox
from tests.integration.testcases import SWARM_SKIP_RM_VOLUMES from tests.integration.testcases import SWARM_SKIP_RM_VOLUMES
from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_only
from tests.integration.testcases import v3_only
DOCKER_COMPOSE_EXECUTABLE = 'docker-compose' DOCKER_COMPOSE_EXECUTABLE = 'docker-compose'
@ -42,7 +40,7 @@ ProcessResult = namedtuple('ProcessResult', 'stdout stderr')
BUILD_CACHE_TEXT = 'Using cache' BUILD_CACHE_TEXT = 'Using cache'
BUILD_PULL_TEXT = 'Status: Image is up to date for busybox:1.27.2' BUILD_PULL_TEXT = 'Status: Image is up to date for busybox:1.27.2'
COMPOSE_COMPATIBILITY_DICT = { COMPOSE_COMPATIBILITY_DICT = {
'version': '2.3', 'version': str(VERSION),
'volumes': {'foo': {'driver': 'default'}}, 'volumes': {'foo': {'driver': 'default'}},
'networks': {'bar': {}}, 'networks': {'bar': {}},
'services': { 'services': {
@ -287,7 +285,7 @@ services:
output = yaml.safe_load(result.stdout) output = yaml.safe_load(result.stdout)
expected = { expected = {
'version': '2.0', 'version': str(VERSION),
'volumes': {'data': {'driver': 'local'}}, 'volumes': {'data': {'driver': 'local'}},
'networks': {'front': {}}, 'networks': {'front': {}},
'services': { 'services': {
@ -311,7 +309,7 @@ services:
self.base_dir = 'tests/fixtures/restart' self.base_dir = 'tests/fixtures/restart'
result = self.dispatch(['config']) result = self.dispatch(['config'])
assert yaml.safe_load(result.stdout) == { assert yaml.safe_load(result.stdout) == {
'version': '2.0', 'version': str(VERSION),
'services': { 'services': {
'never': { 'never': {
'image': 'busybox', 'image': 'busybox',
@ -343,10 +341,12 @@ services:
assert 'networks' in json_result assert 'networks' in json_result
assert json_result['networks'] == { assert json_result['networks'] == {
'networks_foo': { 'networks_foo': {
'external': True # {'name': 'networks_foo'} 'external': True,
'name': 'networks_foo'
}, },
'bar': { 'bar': {
'external': {'name': 'networks_bar'} 'external': True,
'name': 'networks_bar'
} }
} }
@ -355,14 +355,14 @@ services:
result = self.dispatch(['config']) result = self.dispatch(['config'])
json_result = yaml.safe_load(result.stdout) json_result = yaml.safe_load(result.stdout)
assert json_result == { assert json_result == {
'version': str(VERSION),
'services': { 'services': {
'web': { 'web': {
'command': 'true', 'command': 'true',
'image': 'alpine:latest', 'image': 'alpine:latest',
'ports': ['5643/tcp', '9999/tcp'] 'ports': [{'target': 5643}, {'target': 9999}]
}
} }
},
'version': '2.4'
} }
def test_config_with_env_file(self): def test_config_with_env_file(self):
@ -370,14 +370,14 @@ services:
result = self.dispatch(['--env-file', '.env2', 'config']) result = self.dispatch(['--env-file', '.env2', 'config'])
json_result = yaml.safe_load(result.stdout) json_result = yaml.safe_load(result.stdout)
assert json_result == { assert json_result == {
'version': str(VERSION),
'services': { 'services': {
'web': { 'web': {
'command': 'false', 'command': 'false',
'image': 'alpine:latest', 'image': 'alpine:latest',
'ports': ['5644/tcp', '9998/tcp'] 'ports': [{'target': 5644}, {'target': 9998}]
}
} }
},
'version': '2.4'
} }
def test_config_with_dot_env_and_override_dir(self): def test_config_with_dot_env_and_override_dir(self):
@ -385,14 +385,14 @@ services:
result = self.dispatch(['--project-directory', 'alt/', 'config']) result = self.dispatch(['--project-directory', 'alt/', 'config'])
json_result = yaml.safe_load(result.stdout) json_result = yaml.safe_load(result.stdout)
assert json_result == { assert json_result == {
'version': str(VERSION),
'services': { 'services': {
'web': { 'web': {
'command': 'echo uwu', 'command': 'echo uwu',
'image': 'alpine:3.10.1', 'image': 'alpine:3.10.1',
'ports': ['3341/tcp', '4449/tcp'] 'ports': [{'target': 3341}, {'target': 4449}]
}
} }
},
'version': '2.4'
} }
def test_config_external_volume_v2(self): def test_config_external_volume_v2(self):
@ -403,11 +403,11 @@ services:
assert json_result['volumes'] == { assert json_result['volumes'] == {
'foo': { 'foo': {
'external': True, 'external': True,
'name': 'foo',
}, },
'bar': { 'bar': {
'external': { 'external': True,
'name': 'some_bar', 'name': 'some_bar',
},
} }
} }
@ -435,11 +435,11 @@ services:
assert json_result['volumes'] == { assert json_result['volumes'] == {
'foo': { 'foo': {
'external': True, 'external': True,
'name': 'foo',
}, },
'bar': { 'bar': {
'external': { 'external': True,
'name': 'some_bar', 'name': 'some_bar',
},
} }
} }
@ -479,7 +479,7 @@ services:
self.base_dir = 'tests/fixtures/v1-config' self.base_dir = 'tests/fixtures/v1-config'
result = self.dispatch(['config']) result = self.dispatch(['config'])
assert yaml.safe_load(result.stdout) == { assert yaml.safe_load(result.stdout) == {
'version': '2.1', 'version': str(V1),
'services': { 'services': {
'net': { 'net': {
'image': 'busybox', 'image': 'busybox',
@ -498,13 +498,11 @@ services:
}, },
} }
@v3_only()
def test_config_v3(self): def test_config_v3(self):
self.base_dir = 'tests/fixtures/v3-full' self.base_dir = 'tests/fixtures/v3-full'
result = self.dispatch(['config']) result = self.dispatch(['config'])
assert yaml.safe_load(result.stdout) == { assert yaml.safe_load(result.stdout) == {
'version': '3.5', 'version': str(VERSION),
'volumes': { 'volumes': {
'foobar': { 'foobar': {
'labels': { 'labels': {
@ -576,12 +574,14 @@ services:
}, },
} }
@pytest.mark.skip(reason='deprecated option')
def test_config_compatibility_mode(self): def test_config_compatibility_mode(self):
self.base_dir = 'tests/fixtures/compatibility-mode' self.base_dir = 'tests/fixtures/compatibility-mode'
result = self.dispatch(['--compatibility', 'config']) result = self.dispatch(['--compatibility', 'config'])
assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT
@pytest.mark.skip(reason='deprecated option')
@mock.patch.dict(os.environ) @mock.patch.dict(os.environ)
def test_config_compatibility_mode_from_env(self): def test_config_compatibility_mode_from_env(self):
self.base_dir = 'tests/fixtures/compatibility-mode' self.base_dir = 'tests/fixtures/compatibility-mode'
@ -590,6 +590,7 @@ services:
assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT
@pytest.mark.skip(reason='deprecated option')
@mock.patch.dict(os.environ) @mock.patch.dict(os.environ)
def test_config_compatibility_mode_from_env_and_option_precedence(self): def test_config_compatibility_mode_from_env_and_option_precedence(self):
self.base_dir = 'tests/fixtures/compatibility-mode' self.base_dir = 'tests/fixtures/compatibility-mode'
@ -1018,7 +1019,6 @@ services:
result = self.dispatch(['down', '--rmi', 'bogus'], returncode=1) result = self.dispatch(['down', '--rmi', 'bogus'], returncode=1)
assert '--rmi flag must be' in result.stderr assert '--rmi flag must be' in result.stderr
@v2_only()
def test_down(self): def test_down(self):
self.base_dir = 'tests/fixtures/v2-full' self.base_dir = 'tests/fixtures/v2-full'
@ -1103,7 +1103,6 @@ services:
assert '{} exited with code 0'.format(simple_name) in result.stdout assert '{} exited with code 0'.format(simple_name) in result.stdout
assert '{} exited with code 0'.format(another_name) in result.stdout assert '{} exited with code 0'.format(another_name) in result.stdout
@v2_only()
def test_up(self): def test_up(self):
self.base_dir = 'tests/fixtures/v2-simple' self.base_dir = 'tests/fixtures/v2-simple'
self.dispatch(['up', '-d'], None) self.dispatch(['up', '-d'], None)
@ -1135,7 +1134,6 @@ services:
for service in services: for service in services:
assert self.lookup(container, service.name) assert self.lookup(container, service.name)
@v2_only()
def test_up_no_start(self): def test_up_no_start(self):
self.base_dir = 'tests/fixtures/v2-full' self.base_dir = 'tests/fixtures/v2-full'
self.dispatch(['up', '--no-start'], None) self.dispatch(['up', '--no-start'], None)
@ -1166,7 +1164,6 @@ services:
] ]
assert len(remote_volumes) > 0 assert len(remote_volumes) > 0
@v2_only()
def test_up_no_start_remove_orphans(self): def test_up_no_start_remove_orphans(self):
self.base_dir = 'tests/fixtures/v2-simple' self.base_dir = 'tests/fixtures/v2-simple'
self.dispatch(['up', '--no-start'], None) self.dispatch(['up', '--no-start'], None)
@ -1182,7 +1179,6 @@ services:
stopped=True) + next.containers(stopped=True)), services) stopped=True) + next.containers(stopped=True)), services)
assert len(stopped2) == 1 assert len(stopped2) == 1
@v2_only()
def test_up_no_ansi(self): def test_up_no_ansi(self):
self.base_dir = 'tests/fixtures/v2-simple' self.base_dir = 'tests/fixtures/v2-simple'
result = self.dispatch(['--no-ansi', 'up', '-d'], None) result = self.dispatch(['--no-ansi', 'up', '-d'], None)
@ -1190,7 +1186,6 @@ services:
assert "%c[1A" % 27 not in result.stderr assert "%c[1A" % 27 not in result.stderr
assert "%c[1B" % 27 not in result.stderr assert "%c[1B" % 27 not in result.stderr
@v2_only()
def test_up_with_default_network_config(self): def test_up_with_default_network_config(self):
filename = 'default-network-config.yml' filename = 'default-network-config.yml'
@ -1204,7 +1199,6 @@ services:
assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false' assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false'
@v2_only()
def test_up_with_network_aliases(self): def test_up_with_network_aliases(self):
filename = 'network-aliases.yml' filename = 'network-aliases.yml'
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
@ -1232,7 +1226,6 @@ services:
assert 'forward_facing' in front_aliases assert 'forward_facing' in front_aliases
assert 'ahead' in front_aliases assert 'ahead' in front_aliases
@v2_only()
def test_up_with_network_internal(self): def test_up_with_network_internal(self):
self.require_api_version('1.23') self.require_api_version('1.23')
filename = 'network-internal.yml' filename = 'network-internal.yml'
@ -1250,7 +1243,6 @@ services:
assert networks[0]['Internal'] is True assert networks[0]['Internal'] is True
@v2_only()
def test_up_with_network_static_addresses(self): def test_up_with_network_static_addresses(self):
filename = 'network-static-addresses.yml' filename = 'network-static-addresses.yml'
ipv4_address = '172.16.100.100' ipv4_address = '172.16.100.100'
@ -1274,7 +1266,6 @@ services:
assert ipv4_address in ipam_config.values() assert ipv4_address in ipam_config.values()
assert ipv6_address in ipam_config.values() assert ipv6_address in ipam_config.values()
@v2_only()
def test_up_with_networks(self): def test_up_with_networks(self):
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
self.dispatch(['up', '-d'], None) self.dispatch(['up', '-d'], None)
@ -1322,7 +1313,6 @@ services:
# app has aliased db to "database" # app has aliased db to "database"
assert self.lookup(app_container, "database") assert self.lookup(app_container, "database")
@v2_only()
def test_up_missing_network(self): def test_up_missing_network(self):
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
@ -1332,7 +1322,6 @@ services:
assert 'Service "web" uses an undefined network "foo"' in result.stderr assert 'Service "web" uses an undefined network "foo"' in result.stderr
@v2_only()
@no_cluster('container networks not supported in Swarm') @no_cluster('container networks not supported in Swarm')
def test_up_with_network_mode(self): def test_up_with_network_mode(self):
c = self.client.create_container( c = self.client.create_container(
@ -1371,7 +1360,6 @@ services:
assert not container_mode_container.get('NetworkSettings.Networks') assert not container_mode_container.get('NetworkSettings.Networks')
assert container_mode_container.get('HostConfig.NetworkMode') == container_mode_source assert container_mode_container.get('HostConfig.NetworkMode') == container_mode_source
@v2_only()
def test_up_external_networks(self): def test_up_external_networks(self):
filename = 'external-networks.yml' filename = 'external-networks.yml'
@ -1395,7 +1383,6 @@ services:
container = self.project.containers()[0] container = self.project.containers()[0]
assert sorted(list(container.get('NetworkSettings.Networks'))) == sorted(network_names) assert sorted(list(container.get('NetworkSettings.Networks'))) == sorted(network_names)
@v2_only()
def test_up_with_external_default_network(self): def test_up_with_external_default_network(self):
filename = 'external-default.yml' filename = 'external-default.yml'
@ -1418,7 +1405,6 @@ services:
container = self.project.containers()[0] container = self.project.containers()[0]
assert list(container.get('NetworkSettings.Networks')) == [network_name] assert list(container.get('NetworkSettings.Networks')) == [network_name]
@v2_1_only()
def test_up_with_network_labels(self): def test_up_with_network_labels(self):
filename = 'network-label.yml' filename = 'network-label.yml'
@ -1438,7 +1424,6 @@ services:
assert 'label_key' in networks[0]['Labels'] assert 'label_key' in networks[0]['Labels']
assert networks[0]['Labels']['label_key'] == 'label_val' assert networks[0]['Labels']['label_key'] == 'label_val'
@v2_1_only()
def test_up_with_volume_labels(self): def test_up_with_volume_labels(self):
filename = 'volume-label.yml' filename = 'volume-label.yml'
@ -1458,7 +1443,6 @@ services:
assert 'label_key' in volumes[0]['Labels'] assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val' assert volumes[0]['Labels']['label_key'] == 'label_val'
@v2_only()
def test_up_no_services(self): def test_up_no_services(self):
self.base_dir = 'tests/fixtures/no-services' self.base_dir = 'tests/fixtures/no-services'
self.dispatch(['up', '-d'], None) self.dispatch(['up', '-d'], None)
@ -1515,7 +1499,6 @@ services:
bar_container.id bar_container.id
) )
@v3_only()
def test_up_with_healthcheck(self): def test_up_with_healthcheck(self):
def wait_on_health_status(container, status): def wait_on_health_status(container, status):
def condition(): def condition():
@ -1649,7 +1632,6 @@ services:
os.kill(proc.pid, signal.SIGTERM) os.kill(proc.pid, signal.SIGTERM)
wait_on_condition(ContainerCountCondition(self.project, 0)) wait_on_condition(ContainerCountCondition(self.project, 0))
@v2_only()
def test_up_handles_force_shutdown(self): def test_up_handles_force_shutdown(self):
self.base_dir = 'tests/fixtures/sleeps-composefile' self.base_dir = 'tests/fixtures/sleeps-composefile'
proc = start_process(self.base_dir, ['up', '-t', '200']) proc = start_process(self.base_dir, ['up', '-t', '200'])
@ -1674,7 +1656,6 @@ services:
proc.wait() proc.wait()
assert proc.returncode == 1 assert proc.returncode == 1
@v2_only()
@no_cluster('Container PID mode does not work across clusters') @no_cluster('Container PID mode does not work across clusters')
def test_up_with_pid_mode(self): def test_up_with_pid_mode(self):
c = self.client.create_container( c = self.client.create_container(
@ -1738,7 +1719,6 @@ services:
assert stdout == "operator\n" assert stdout == "operator\n"
assert stderr == "" assert stderr == ""
@v3_only()
def test_exec_workdir(self): def test_exec_workdir(self):
self.base_dir = 'tests/fixtures/links-composefile' self.base_dir = 'tests/fixtures/links-composefile'
os.environ['COMPOSE_API_VERSION'] = '1.35' os.environ['COMPOSE_API_VERSION'] = '1.35'
@ -1748,7 +1728,6 @@ services:
stdout, stderr = self.dispatch(['exec', '-T', '--workdir', '/etc', 'console', 'ls']) stdout, stderr = self.dispatch(['exec', '-T', '--workdir', '/etc', 'console', 'ls'])
assert 'passwd' in stdout assert 'passwd' in stdout
@v2_2_only()
def test_exec_service_with_environment_overridden(self): def test_exec_service_with_environment_overridden(self):
name = 'service' name = 'service'
self.base_dir = 'tests/fixtures/environment-exec' self.base_dir = 'tests/fixtures/environment-exec'
@ -1793,7 +1772,6 @@ services:
assert len(db.containers()) == 1 assert len(db.containers()) == 1
assert len(console.containers()) == 0 assert len(console.containers()) == 0
@v2_only()
def test_run_service_with_dependencies(self): def test_run_service_with_dependencies(self):
self.base_dir = 'tests/fixtures/v2-dependencies' self.base_dir = 'tests/fixtures/v2-dependencies'
self.dispatch(['run', 'web', '/bin/true'], None) self.dispatch(['run', 'web', '/bin/true'], None)
@ -2105,7 +2083,6 @@ services:
container = service.containers(stopped=True, one_off=True)[0] container = service.containers(stopped=True, one_off=True)[0]
assert workdir == container.get('Config.WorkingDir') assert workdir == container.get('Config.WorkingDir')
@v2_only()
def test_run_service_with_use_aliases(self): def test_run_service_with_use_aliases(self):
filename = 'network-aliases.yml' filename = 'network-aliases.yml'
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
@ -2127,7 +2104,6 @@ services:
assert 'forward_facing' in front_aliases assert 'forward_facing' in front_aliases
assert 'ahead' in front_aliases assert 'ahead' in front_aliases
@v2_only()
def test_run_interactive_connects_to_network(self): def test_run_interactive_connects_to_network(self):
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
@ -2153,7 +2129,6 @@ services:
aliases = set(config['Aliases'] or []) - {container.short_id} aliases = set(config['Aliases'] or []) - {container.short_id}
assert not aliases assert not aliases
@v2_only()
def test_run_detached_connects_to_network(self): def test_run_detached_connects_to_network(self):
self.base_dir = 'tests/fixtures/networks' self.base_dir = 'tests/fixtures/networks'
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
@ -2332,7 +2307,6 @@ services:
assert 'failed' in result.stderr assert 'failed' in result.stderr
assert 'No containers to start' in result.stderr assert 'No containers to start' in result.stderr
@v2_only()
def test_up_logging(self): def test_up_logging(self):
self.base_dir = 'tests/fixtures/logging-composefile' self.base_dir = 'tests/fixtures/logging-composefile'
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
@ -2563,11 +2537,6 @@ services:
assert len(project.get_service('simple').containers()) == 0 assert len(project.get_service('simple').containers()) == 0
assert len(project.get_service('another').containers()) == 0 assert len(project.get_service('another').containers()) == 0
def test_scale_v2_2(self):
self.base_dir = 'tests/fixtures/scale'
result = self.dispatch(['scale', 'web=1'], returncode=1)
assert 'incompatible with the v2.2 format' in result.stderr
def test_up_scale_scale_up(self): def test_up_scale_scale_up(self):
self.base_dir = 'tests/fixtures/scale' self.base_dir = 'tests/fixtures/scale'
project = self.project project = self.project

View File

@ -21,11 +21,7 @@ from compose.config import ConfigurationError
from compose.config import types from compose.config import types
from compose.config.types import VolumeFromSpec from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec from compose.config.types import VolumeSpec
from compose.const import COMPOSEFILE_V2_0 as V2_0 from compose.const import COMPOSE_SPEC as VERSION
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_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_1 as V3_1
from compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE from compose.const import LABEL_SERVICE
from compose.container import Container from compose.container import Container
@ -37,16 +33,11 @@ from compose.service import ConvergenceStrategy
from tests.integration.testcases import if_runtime_available from tests.integration.testcases import if_runtime_available
from tests.integration.testcases import is_cluster from tests.integration.testcases import is_cluster
from tests.integration.testcases import no_cluster from tests.integration.testcases import no_cluster
from tests.integration.testcases import v2_1_only
from tests.integration.testcases import v2_2_only
from tests.integration.testcases import v2_3_only
from tests.integration.testcases import v2_only
from tests.integration.testcases import v3_only
def build_config(**kwargs): def build_config(**kwargs):
return config.Config( return config.Config(
version=kwargs.get('version'), version=kwargs.get('version', VERSION),
services=kwargs.get('services'), services=kwargs.get('services'),
volumes=kwargs.get('volumes'), volumes=kwargs.get('volumes'),
networks=kwargs.get('networks'), networks=kwargs.get('networks'),
@ -106,7 +97,6 @@ class ProjectTest(DockerClientTestCase):
def test_parallel_pull_with_no_image(self): def test_parallel_pull_with_no_image(self):
config_data = build_config( config_data = build_config(
version=V2_3,
services=[{ services=[{
'name': 'web', 'name': 'web',
'build': {'context': '.'}, 'build': {'context': '.'},
@ -162,14 +152,12 @@ class ProjectTest(DockerClientTestCase):
db = project.get_service('db') db = project.get_service('db')
assert db._get_volumes_from() == [data_container.id + ':rw'] assert db._get_volumes_from() == [data_container.id + ':rw']
@v2_only()
@no_cluster('container networks not supported in Swarm') @no_cluster('container networks not supported in Swarm')
def test_network_mode_from_service(self): def test_network_mode_from_service(self):
project = Project.from_config( project = Project.from_config(
name='composetest', name='composetest',
client=self.client, client=self.client,
config_data=load_config({ config_data=load_config({
'version': str(V2_0),
'services': { 'services': {
'net': { 'net': {
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -190,14 +178,12 @@ class ProjectTest(DockerClientTestCase):
net = project.get_service('net') net = project.get_service('net')
assert web.network_mode.mode == 'container:' + net.containers()[0].id assert web.network_mode.mode == 'container:' + net.containers()[0].id
@v2_only()
@no_cluster('container networks not supported in Swarm') @no_cluster('container networks not supported in Swarm')
def test_network_mode_from_container(self): def test_network_mode_from_container(self):
def get_project(): def get_project():
return Project.from_config( return Project.from_config(
name='composetest', name='composetest',
config_data=load_config({ config_data=load_config({
'version': str(V2_0),
'services': { 'services': {
'web': { 'web': {
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -448,7 +434,6 @@ class ProjectTest(DockerClientTestCase):
assert db_container.id != old_db_id assert db_container.id != old_db_id
assert db_container.get('Volumes./etc') == db_volume_path assert db_container.get('Volumes./etc') == db_volume_path
@v2_3_only()
def test_recreate_preserves_mounts(self): def test_recreate_preserves_mounts(self):
web = self.create_service('web') web = self.create_service('web')
db = self.create_service('db', volumes=[types.MountSpec(type='volume', target='/etc')]) db = self.create_service('db', volumes=[types.MountSpec(type='volume', target='/etc')])
@ -656,10 +641,8 @@ class ProjectTest(DockerClientTestCase):
service = project.get_service('web') service = project.get_service('web')
assert len(service.containers()) == 1 assert len(service.containers()) == 1
@v2_only()
def test_project_up_networks(self): def test_project_up_networks(self):
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -701,10 +684,8 @@ class ProjectTest(DockerClientTestCase):
foo_data = self.client.inspect_network('composetest_foo') foo_data = self.client.inspect_network('composetest_foo')
assert foo_data['Driver'] == 'bridge' assert foo_data['Driver'] == 'bridge'
@v2_only()
def test_up_with_ipam_config(self): def test_up_with_ipam_config(self):
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -761,10 +742,8 @@ class ProjectTest(DockerClientTestCase):
}], }],
} }
@v2_only()
def test_up_with_ipam_options(self): def test_up_with_ipam_options(self):
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -796,10 +775,8 @@ class ProjectTest(DockerClientTestCase):
"com.docker.compose.network.test": "9-29-045" "com.docker.compose.network.test": "9-29-045"
} }
@v2_1_only()
def test_up_with_network_static_addresses(self): def test_up_with_network_static_addresses(self):
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -845,13 +822,11 @@ class ProjectTest(DockerClientTestCase):
assert ipam_config.get('IPv4Address') == '172.16.100.100' assert ipam_config.get('IPv4Address') == '172.16.100.100'
assert ipam_config.get('IPv6Address') == 'fe80::1001:102' assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
@v2_3_only()
def test_up_with_network_priorities(self): def test_up_with_network_priorities(self):
mac_address = '74:6f:75:68:6f:75' mac_address = '74:6f:75:68:6f:75'
def get_config_data(p1, p2, p3): def get_config_data(p1, p2, p3):
return build_config( return build_config(
version=V2_3,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -910,11 +885,9 @@ class ProjectTest(DockerClientTestCase):
net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n3'] net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n3']
assert net_config['MacAddress'] == mac_address assert net_config['MacAddress'] == mac_address
@v2_1_only()
def test_up_with_enable_ipv6(self): def test_up_with_enable_ipv6(self):
self.require_api_version('1.23') self.require_api_version('1.23')
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -954,10 +927,8 @@ class ProjectTest(DockerClientTestCase):
get('IPAMConfig', {})) get('IPAMConfig', {}))
assert ipam_config.get('IPv6Address') == 'fe80::1001:102' assert ipam_config.get('IPv6Address') == 'fe80::1001:102'
@v2_only()
def test_up_with_network_static_addresses_missing_subnet(self): def test_up_with_network_static_addresses_missing_subnet(self):
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -990,10 +961,8 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(ProjectError): with pytest.raises(ProjectError):
project.up() project.up()
@v2_1_only()
def test_up_with_network_link_local_ips(self): def test_up_with_network_link_local_ips(self):
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1025,10 +994,8 @@ class ProjectTest(DockerClientTestCase):
assert 'LinkLocalIPs' in ipam_config assert 'LinkLocalIPs' in ipam_config
assert ipam_config['LinkLocalIPs'] == ['169.254.8.8'] assert ipam_config['LinkLocalIPs'] == ['169.254.8.8']
@v2_1_only()
def test_up_with_custom_name_resources(self): def test_up_with_custom_name_resources(self):
config_data = build_config( config_data = build_config(
version=V2_2,
services=[{ services=[{
'name': 'web', 'name': 'web',
'volumes': [VolumeSpec.parse('foo:/container-path')], 'volumes': [VolumeSpec.parse('foo:/container-path')],
@ -1062,11 +1029,9 @@ class ProjectTest(DockerClientTestCase):
assert network['Labels']['com.docker.compose.test_value'] == 'sharpdressedman' assert network['Labels']['com.docker.compose.test_value'] == 'sharpdressedman'
assert volume['Labels']['com.docker.compose.test_value'] == 'thefuror' assert volume['Labels']['com.docker.compose.test_value'] == 'thefuror'
@v2_1_only()
def test_up_with_isolation(self): def test_up_with_isolation(self):
self.require_api_version('1.24') self.require_api_version('1.24')
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1082,11 +1047,9 @@ class ProjectTest(DockerClientTestCase):
service_container = project.get_service('web').containers(stopped=True)[0] service_container = project.get_service('web').containers(stopped=True)[0]
assert service_container.inspect()['HostConfig']['Isolation'] == 'default' assert service_container.inspect()['HostConfig']['Isolation'] == 'default'
@v2_1_only()
def test_up_with_invalid_isolation(self): def test_up_with_invalid_isolation(self):
self.require_api_version('1.24') self.require_api_version('1.24')
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1101,12 +1064,10 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(ProjectError): with pytest.raises(ProjectError):
project.up() project.up()
@v2_3_only()
@if_runtime_available('runc') @if_runtime_available('runc')
def test_up_with_runtime(self): def test_up_with_runtime(self):
self.require_api_version('1.30') self.require_api_version('1.30')
config_data = build_config( config_data = build_config(
version=V2_3,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1122,11 +1083,9 @@ class ProjectTest(DockerClientTestCase):
service_container = project.get_service('web').containers(stopped=True)[0] service_container = project.get_service('web').containers(stopped=True)[0]
assert service_container.inspect()['HostConfig']['Runtime'] == 'runc' assert service_container.inspect()['HostConfig']['Runtime'] == 'runc'
@v2_3_only()
def test_up_with_invalid_runtime(self): def test_up_with_invalid_runtime(self):
self.require_api_version('1.30') self.require_api_version('1.30')
config_data = build_config( config_data = build_config(
version=V2_3,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1141,12 +1100,10 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(ProjectError): with pytest.raises(ProjectError):
project.up() project.up()
@v2_3_only()
@if_runtime_available('nvidia') @if_runtime_available('nvidia')
def test_up_with_nvidia_runtime(self): def test_up_with_nvidia_runtime(self):
self.require_api_version('1.30') self.require_api_version('1.30')
config_data = build_config( config_data = build_config(
version=V2_3,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1162,11 +1119,9 @@ class ProjectTest(DockerClientTestCase):
service_container = project.get_service('web').containers(stopped=True)[0] service_container = project.get_service('web').containers(stopped=True)[0]
assert service_container.inspect()['HostConfig']['Runtime'] == 'nvidia' assert service_container.inspect()['HostConfig']['Runtime'] == 'nvidia'
@v2_only()
def test_project_up_with_network_internal(self): def test_project_up_with_network_internal(self):
self.require_api_version('1.23') self.require_api_version('1.23')
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1188,14 +1143,12 @@ class ProjectTest(DockerClientTestCase):
assert network['Internal'] is True assert network['Internal'] is True
@v2_1_only()
def test_project_up_with_network_label(self): def test_project_up_with_network_label(self):
self.require_api_version('1.23') self.require_api_version('1.23')
network_name = 'network_with_label' network_name = 'network_with_label'
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1223,12 +1176,10 @@ class ProjectTest(DockerClientTestCase):
assert 'label_key' in networks[0]['Labels'] assert 'label_key' in networks[0]['Labels']
assert networks[0]['Labels']['label_key'] == 'label_val' assert networks[0]['Labels']['label_key'] == 'label_val'
@v2_only()
def test_project_up_volumes(self): def test_project_up_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1248,14 +1199,12 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local' assert volume_data['Driver'] == 'local'
@v2_1_only()
def test_project_up_with_volume_labels(self): def test_project_up_with_volume_labels(self):
self.require_api_version('1.23') self.require_api_version('1.23')
volume_name = 'volume_with_label' volume_name = 'volume_with_label'
config_data = build_config( config_data = build_config(
version=V2_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1290,12 +1239,10 @@ class ProjectTest(DockerClientTestCase):
assert 'label_key' in volumes[0]['Labels'] assert 'label_key' in volumes[0]['Labels']
assert volumes[0]['Labels']['label_key'] == 'label_val' assert volumes[0]['Labels']['label_key'] == 'label_val'
@v2_only()
def test_project_up_logging_with_multiple_files(self): def test_project_up_logging_with_multiple_files(self):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yml', 'base.yml',
{ {
'version': str(V2_0),
'services': { 'services': {
'simple': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'}, 'simple': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'},
'another': { 'another': {
@ -1314,7 +1261,6 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile( override_file = config.ConfigFile(
'override.yml', 'override.yml',
{ {
'version': str(V2_0),
'services': { 'services': {
'another': { 'another': {
'logging': { 'logging': {
@ -1342,12 +1288,10 @@ class ProjectTest(DockerClientTestCase):
assert log_config assert log_config
assert log_config.get('Type') == 'none' assert log_config.get('Type') == 'none'
@v2_only()
def test_project_up_port_mappings_with_multiple_files(self): def test_project_up_port_mappings_with_multiple_files(self):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yml', 'base.yml',
{ {
'version': str(V2_0),
'services': { 'services': {
'simple': { 'simple': {
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1360,7 +1304,6 @@ class ProjectTest(DockerClientTestCase):
override_file = config.ConfigFile( override_file = config.ConfigFile(
'override.yml', 'override.yml',
{ {
'version': str(V2_0),
'services': { 'services': {
'simple': { 'simple': {
'ports': ['1234:1234'] 'ports': ['1234:1234']
@ -1378,10 +1321,8 @@ class ProjectTest(DockerClientTestCase):
containers = project.containers() containers = project.containers()
assert len(containers) == 1 assert len(containers) == 1
@v2_2_only()
def test_project_up_config_scale(self): def test_project_up_config_scale(self):
config_data = build_config( config_data = build_config(
version=V2_2,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1406,12 +1347,10 @@ class ProjectTest(DockerClientTestCase):
project.up() project.up()
assert len(project.containers()) == 3 assert len(project.containers()) == 3
@v2_only()
def test_initialize_volumes(self): def test_initialize_volumes(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1430,12 +1369,10 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local' assert volume_data['Driver'] == 'local'
@v2_only()
def test_project_up_implicit_volume_driver(self): def test_project_up_implicit_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1454,12 +1391,10 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local' assert volume_data['Driver'] == 'local'
@v3_only()
def test_project_up_with_secrets(self): def test_project_up_with_secrets(self):
node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default')) node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
config_data = build_config( config_data = build_config(
version=V3_1,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1491,12 +1426,10 @@ class ProjectTest(DockerClientTestCase):
output = container.logs() output = container.logs()
assert output == b"This is the secret\n" assert output == b"This is the secret\n"
@v3_only()
def test_project_up_with_added_secrets(self): def test_project_up_with_added_secrets(self):
node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default')) node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default'))
config_input1 = { config_input1 = {
'version': V3_1,
'services': [ 'services': [
{ {
'name': 'web', 'name': 'web',
@ -1545,12 +1478,11 @@ class ProjectTest(DockerClientTestCase):
output = container.logs() output = container.logs()
assert output == b"This is the secret\n" assert output == b"This is the secret\n"
@v2_only()
def test_initialize_volumes_invalid_volume_driver(self): def test_initialize_volumes_invalid_volume_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
config_data = build_config( config_data = build_config(
version=V2_0, version=VERSION,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1566,14 +1498,12 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(APIError if is_cluster(self.client) else config.ConfigurationError): with pytest.raises(APIError if is_cluster(self.client) else config.ConfigurationError):
project.volumes.initialize() project.volumes.initialize()
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic') @no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_updated_driver(self): def test_initialize_volumes_updated_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1605,7 +1535,6 @@ class ProjectTest(DockerClientTestCase):
vol_name vol_name
) in str(e.value) ) in str(e.value)
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic') @no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_updated_driver_opts(self): def test_initialize_volumes_updated_driver_opts(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
@ -1615,7 +1544,6 @@ class ProjectTest(DockerClientTestCase):
driver_opts = {'o': 'bind', 'device': tmpdir, 'type': 'none'} driver_opts = {'o': 'bind', 'device': tmpdir, 'type': 'none'}
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1651,13 +1579,11 @@ class ProjectTest(DockerClientTestCase):
vol_name, driver_opts['device'] vol_name, driver_opts['device']
) in str(e.value) ) in str(e.value)
@v2_only()
def test_initialize_volumes_updated_blank_driver(self): def test_initialize_volumes_updated_blank_driver(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1688,7 +1614,6 @@ class ProjectTest(DockerClientTestCase):
assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Name'].split('/')[-1] == full_vol_name
assert volume_data['Driver'] == 'local' assert volume_data['Driver'] == 'local'
@v2_only()
@no_cluster('inspect volume by name defect on Swarm Classic') @no_cluster('inspect volume by name defect on Swarm Classic')
def test_initialize_volumes_external_volumes(self): def test_initialize_volumes_external_volumes(self):
# Use composetest_ prefix so it gets garbage-collected in tearDown() # Use composetest_ prefix so it gets garbage-collected in tearDown()
@ -1696,7 +1621,6 @@ class ProjectTest(DockerClientTestCase):
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
self.client.create_volume(vol_name) self.client.create_volume(vol_name)
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1715,12 +1639,10 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(NotFound): with pytest.raises(NotFound):
self.client.inspect_volume(full_vol_name) self.client.inspect_volume(full_vol_name)
@v2_only()
def test_initialize_volumes_inexistent_external_volume(self): def test_initialize_volumes_inexistent_external_volume(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
config_data = build_config( config_data = build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1740,7 +1662,6 @@ class ProjectTest(DockerClientTestCase):
vol_name vol_name
) in str(e.value) ) in str(e.value)
@v2_only()
def test_project_up_named_volumes_in_binds(self): def test_project_up_named_volumes_in_binds(self):
vol_name = '{0:x}'.format(random.getrandbits(32)) vol_name = '{0:x}'.format(random.getrandbits(32))
full_vol_name = 'composetest_{0}'.format(vol_name) full_vol_name = 'composetest_{0}'.format(vol_name)
@ -1748,7 +1669,6 @@ class ProjectTest(DockerClientTestCase):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yml', 'base.yml',
{ {
'version': str(V2_0),
'services': { 'services': {
'simple': { 'simple': {
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -1839,7 +1759,6 @@ class ProjectTest(DockerClientTestCase):
mock_log.warning.assert_not_called() mock_log.warning.assert_not_called()
@v2_1_only()
def test_project_up_healthy_dependency(self): def test_project_up_healthy_dependency(self):
config_dict = { config_dict = {
'version': '2.1', 'version': '2.1',
@ -1876,7 +1795,6 @@ class ProjectTest(DockerClientTestCase):
assert 'svc1' in svc2.get_dependency_names() assert 'svc1' in svc2.get_dependency_names()
assert svc1.is_healthy() assert svc1.is_healthy()
@v2_1_only()
def test_project_up_unhealthy_dependency(self): def test_project_up_unhealthy_dependency(self):
config_dict = { config_dict = {
'version': '2.1', 'version': '2.1',
@ -1915,7 +1833,6 @@ class ProjectTest(DockerClientTestCase):
with pytest.raises(HealthCheckFailed): with pytest.raises(HealthCheckFailed):
svc1.is_healthy() svc1.is_healthy()
@v2_1_only()
def test_project_up_no_healthcheck_dependency(self): def test_project_up_no_healthcheck_dependency(self):
config_dict = { config_dict = {
'version': '2.1', 'version': '2.1',

View File

@ -11,14 +11,8 @@ from compose.cli.docker_client import docker_client
from compose.config.config import resolve_environment from compose.config.config import resolve_environment
from compose.config.environment import Environment from compose.config.environment import Environment
from compose.const import API_VERSIONS from compose.const import API_VERSIONS
from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_0 as V2_1
from compose.const import COMPOSEFILE_V2_2 as V2_2
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_0 as V3_0
from compose.const import COMPOSEFILE_V3_2 as V3_2
from compose.const import COMPOSEFILE_V3_5 as V3_5
from compose.const import LABEL_PROJECT from compose.const import LABEL_PROJECT
from compose.progress_stream import stream_output from compose.progress_stream import stream_output
from compose.service import Service from compose.service import Service
@ -45,17 +39,11 @@ def get_links(container):
def engine_max_version(): def engine_max_version():
if 'DOCKER_VERSION' not in os.environ: if 'DOCKER_VERSION' not in os.environ:
return V3_5 return VERSION
version = os.environ['DOCKER_VERSION'].partition('-')[0] version = os.environ['DOCKER_VERSION'].partition('-')[0]
if version_lt(version, '1.10'): if version_lt(version, '1.10'):
return V1 return V1
if version_lt(version, '1.12'): return VERSION
return V2_0
if version_lt(version, '1.13'):
return V2_1
if version_lt(version, '17.06'):
return V3_2
return V3_5
def min_version_skip(version): def min_version_skip(version):
@ -66,23 +54,23 @@ def min_version_skip(version):
def v2_only(): def v2_only():
return min_version_skip(V2_0) return min_version_skip(VERSION)
def v2_1_only(): def v2_1_only():
return min_version_skip(V2_1) return min_version_skip(VERSION)
def v2_2_only(): def v2_2_only():
return min_version_skip(V2_2) return min_version_skip(VERSION)
def v2_3_only(): def v2_3_only():
return min_version_skip(V2_3) return min_version_skip(VERSION)
def v3_only(): def v3_only():
return min_version_skip(V3_0) return min_version_skip(VERSION)
class DockerClientTestCase(unittest.TestCase): class DockerClientTestCase(unittest.TestCase):

View File

@ -34,19 +34,19 @@ class TestHandleConnectionErrors(object):
def test_api_error_version_mismatch(self, mock_logging): def test_api_error_version_mismatch(self, mock_logging):
with pytest.raises(errors.ConnectionError): with pytest.raises(errors.ConnectionError):
with handle_connection_errors(mock.Mock(api_version='1.22')): with handle_connection_errors(mock.Mock(api_version='1.38')):
raise APIError(None, None, b"client is newer than server") raise APIError(None, None, b"client is newer than server")
_, args, _ = mock_logging.error.mock_calls[0] _, args, _ = mock_logging.error.mock_calls[0]
assert "Docker Engine of version 1.10.0 or greater" in args[0] assert "Docker Engine of version 18.06.0 or greater" in args[0]
def test_api_error_version_mismatch_unicode_explanation(self, mock_logging): def test_api_error_version_mismatch_unicode_explanation(self, mock_logging):
with pytest.raises(errors.ConnectionError): with pytest.raises(errors.ConnectionError):
with handle_connection_errors(mock.Mock(api_version='1.22')): with handle_connection_errors(mock.Mock(api_version='1.38')):
raise APIError(None, None, u"client is newer than server") raise APIError(None, None, u"client is newer than server")
_, args, _ = mock_logging.error.mock_calls[0] _, args, _ = mock_logging.error.mock_calls[0]
assert "Docker Engine of version 1.10.0 or greater" in args[0] assert "Docker Engine of version 18.06.0 or greater" in args[0]
def test_api_error_version_other(self, mock_logging): def test_api_error_version_other(self, mock_logging):
msg = b"Something broke!" msg = b"Something broke!"

View File

@ -26,21 +26,13 @@ from compose.config.serialize import denormalize_service_dict
from compose.config.serialize import serialize_config from compose.config.serialize import serialize_config
from compose.config.serialize import serialize_ns_time_value from compose.config.serialize import serialize_ns_time_value
from compose.config.types import VolumeSpec from compose.config.types import VolumeSpec
from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
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_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_0 as V3_0
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
from compose.const import COMPOSEFILE_V3_5 as V3_5
from compose.const import IS_WINDOWS_PLATFORM from compose.const import IS_WINDOWS_PLATFORM
from tests import mock from tests import mock
from tests import unittest from tests import unittest
DEFAULT_VERSION = V2_0 DEFAULT_VERSION = VERSION
def make_service_dict(name, service_dict, working_dir='.', filename=None): def make_service_dict(name, service_dict, working_dir='.', filename=None):
@ -73,8 +65,10 @@ class ConfigTest(unittest.TestCase):
service_dicts = config.load( service_dicts = config.load(
build_config_details( build_config_details(
{ {
'services': {
'foo': {'image': 'busybox'}, 'foo': {'image': 'busybox'},
'bar': {'image': 'busybox', 'environment': ['FOO=1']}, 'bar': {'image': 'busybox', 'environment': ['FOO=1']},
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'common.yml' 'common.yml'
@ -169,23 +163,23 @@ class ConfigTest(unittest.TestCase):
def test_valid_versions(self): def test_valid_versions(self):
for version in ['2', '2.0']: for version in ['2', '2.0']:
cfg = config.load(build_config_details({'version': version})) cfg = config.load(build_config_details({'version': version}))
assert cfg.version == V2_0 assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '2.1'})) cfg = config.load(build_config_details({'version': '2.1'}))
assert cfg.version == V2_1 assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '2.2'})) cfg = config.load(build_config_details({'version': '2.2'}))
assert cfg.version == V2_2 assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '2.3'})) cfg = config.load(build_config_details({'version': '2.3'}))
assert cfg.version == V2_3 assert cfg.version == VERSION
for version in ['3', '3.0']: for version in ['3', '3.0']:
cfg = config.load(build_config_details({'version': version})) cfg = config.load(build_config_details({'version': version}))
assert cfg.version == V3_0 assert cfg.version == VERSION
cfg = config.load(build_config_details({'version': '3.1'})) cfg = config.load(build_config_details({'version': '3.1'}))
assert cfg.version == V3_1 assert cfg.version == VERSION
def test_v1_file_version(self): def test_v1_file_version(self):
cfg = config.load(build_config_details({'web': {'image': 'busybox'}})) cfg = config.load(build_config_details({'web': {'image': 'busybox'}}))
@ -197,7 +191,7 @@ class ConfigTest(unittest.TestCase):
assert list(s['name'] for s in cfg.services) == ['version'] assert list(s['name'] for s in cfg.services) == ['version']
def test_wrong_version_type(self): def test_wrong_version_type(self):
for version in [None, 1, 2, 2.0]: for version in [1, 2, 2.0]:
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
@ -213,12 +207,12 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
{'version': '2.18'}, {'version': '1'},
filename='filename.yml', filename='filename.yml',
) )
) )
assert 'Version in "filename.yml" is unsupported' in excinfo.exconly() assert 'Version in "filename.yml" is invalid' in excinfo.exconly()
assert VERSION_EXPLANATION in excinfo.exconly() assert VERSION_EXPLANATION in excinfo.exconly()
def test_version_1_is_invalid(self): def test_version_1_is_invalid(self):
@ -328,7 +322,6 @@ class ConfigTest(unittest.TestCase):
} }
}, 'working_dir', 'filename.yml') }, 'working_dir', 'filename.yml')
) )
assert 'Unexpected type for "version" key in "filename.yml"' \ assert 'Unexpected type for "version" key in "filename.yml"' \
in mock_logging.warning.call_args[0][0] in mock_logging.warning.call_args[0][0]
@ -376,7 +369,7 @@ class ConfigTest(unittest.TestCase):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yaml', 'base.yaml',
{ {
'version': str(V2_1), 'version': '2',
'services': { 'services': {
'web': { 'web': {
'image': 'example/web', 'image': 'example/web',
@ -511,7 +504,15 @@ class ConfigTest(unittest.TestCase):
for invalid_name in ['?not?allowed', ' ', '', '!', '/', '\xe2']: for invalid_name in ['?not?allowed', ' ', '', '!', '/', '\xe2']:
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details( config.load(build_config_details(
{invalid_name: {'image': 'busybox'}})) {
'version': '2',
'services': {
invalid_name:
{
'image': 'busybox'
}
}
}))
assert 'Invalid service name \'%s\'' % invalid_name in exc.exconly() assert 'Invalid service name \'%s\'' % invalid_name in exc.exconly()
def test_load_config_invalid_service_names_v2(self): def test_load_config_invalid_service_names_v2(self):
@ -543,17 +544,24 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details( config.load(build_config_details(
{ {
'web': {'image': 'busybox', 'name': 'bogus'}, 'version': '2',
'services': {
'web': {'image': 'busybox', 'name': 'bogus'}
}
}, },
'working_dir', 'working_dir',
'filename.yml', 'filename.yml',
)) ))
assert "Unsupported config option for services.web: 'name'" in exc.exconly()
assert "Unsupported config option for web: 'name'" in exc.exconly()
def test_load_invalid_service_definition(self): def test_load_invalid_service_definition(self):
config_details = build_config_details( config_details = build_config_details(
{'web': 'wrong'}, {
'version': '2',
'services': {
'web': 'wrong'
}
},
'working_dir', 'working_dir',
'filename.yml') 'filename.yml')
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
@ -585,7 +593,10 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
{1: {'image': 'busybox'}}, {
'version': '2',
'services': {1: {'image': 'busybox'}}
},
'working_dir', 'working_dir',
'filename.yml' 'filename.yml'
) )
@ -836,10 +847,10 @@ class ConfigTest(unittest.TestCase):
def test_load_with_multiple_files_and_invalid_override(self): def test_load_with_multiple_files_and_invalid_override(self):
base_file = config.ConfigFile( base_file = config.ConfigFile(
'base.yaml', 'base.yaml',
{'web': {'image': 'example/web'}}) {'version': '2', 'services': {'web': {'image': 'example/web'}}})
override_file = config.ConfigFile( override_file = config.ConfigFile(
'override.yaml', 'override.yaml',
{'bogus': 'thing'}) {'version': '2', 'services': {'bogus': 'thing'}})
details = config.ConfigDetails('.', [base_file, override_file]) details = config.ConfigDetails('.', [base_file, override_file])
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
@ -977,7 +988,6 @@ class ConfigTest(unittest.TestCase):
service = config.load( service = config.load(
build_config_details( build_config_details(
{ {
'version': str(V3_3),
'services': { 'services': {
'web': { 'web': {
'build': { 'build': {
@ -1424,7 +1434,7 @@ class ConfigTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': str(V2_1), 'version': str(VERSION),
'networks': { 'networks': {
'foo': { 'foo': {
'driver': 'default', 'driver': 'default',
@ -1455,7 +1465,6 @@ class ConfigTest(unittest.TestCase):
networks = config.load( networks = config.load(
build_config_details( build_config_details(
{ {
'version': str(V2_1),
'networks': { 'networks': {
'foo': { 'foo': {
'driver': 'default', 'driver': 'default',
@ -1487,7 +1496,10 @@ class ConfigTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': str(VERSION),
'services': {
'foo': {'image': 'busybox', 'privilige': 'something'}, 'foo': {'image': 'busybox', 'privilige': 'something'},
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -1508,7 +1520,10 @@ class ConfigTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': str(VERSION),
'services': {
'foo': {'image': 1}, 'foo': {'image': 1},
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -1555,7 +1570,10 @@ class ConfigTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': str(VERSION),
'services': {
'foo': {'image': 'busybox', 'links': 'an_link'}, 'foo': {'image': 'busybox', 'links': 'an_link'},
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -1583,7 +1601,10 @@ class ConfigTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': str(VERSION),
'services': {
'web': {'build': '.', 'devices': ['/dev/foo:/dev/foo', '/dev/foo:/dev/foo']} 'web': {'build': '.', 'devices': ['/dev/foo:/dev/foo', '/dev/foo:/dev/foo']}
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -1597,7 +1618,10 @@ class ConfigTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': str(VERSION),
'services': {
'web': {'build': '.', 'command': [1]} 'web': {'build': '.', 'command': [1]}
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -1622,10 +1646,13 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
{'web': { {
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox', 'image': 'busybox',
'extra_hosts': 'somehost:162.242.195.82' 'extra_hosts': 'somehost:162.242.195.82'}}
}}, },
'working_dir', 'working_dir',
'filename.yml' 'filename.yml'
) )
@ -1638,13 +1665,16 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
{'web': { {
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox', 'image': 'busybox',
'extra_hosts': [ 'extra_hosts': [
{'somehost': '162.242.195.82'}, {'somehost': '162.242.195.82'},
{'otherhost': '50.31.209.229'} {'otherhost': '50.31.209.229'}
] ]}}
}}, },
'working_dir', 'working_dir',
'filename.yml' 'filename.yml'
) )
@ -1658,6 +1688,8 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details( config.load(build_config_details(
{ {
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'busybox', 'image': 'busybox',
'ulimits': { 'ulimits': {
@ -1668,6 +1700,7 @@ class ConfigTest(unittest.TestCase):
} }
} }
} }
}
}, },
'working_dir', 'working_dir',
'filename.yml')) 'filename.yml'))
@ -1679,10 +1712,13 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details( config.load(build_config_details(
{ {
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'busybox', 'image': 'busybox',
'ulimits': {'nofile': {"soft": 10000}} 'ulimits': {'nofile': {"soft": 10000}}
} }
}
}, },
'working_dir', 'working_dir',
'filename.yml')) 'filename.yml'))
@ -1695,12 +1731,15 @@ class ConfigTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details( config.load(build_config_details(
{ {
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'busybox', 'image': 'busybox',
'ulimits': { 'ulimits': {
'nofile': {"soft": 10000, "hard": 1000} 'nofile': {"soft": 10000, "hard": 1000}
} }
} }
}
}, },
'working_dir', 'working_dir',
'filename.yml')) 'filename.yml'))
@ -1711,10 +1750,12 @@ class ConfigTest(unittest.TestCase):
for expose in expose_values: for expose in expose_values:
service = config.load( service = config.load(
build_config_details( build_config_details(
{'web': { {
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox', 'image': 'busybox',
'expose': expose 'expose': expose}}},
}},
'working_dir', 'working_dir',
'filename.yml' 'filename.yml'
) )
@ -1726,10 +1767,12 @@ class ConfigTest(unittest.TestCase):
for entrypoint in entrypoint_values: for entrypoint in entrypoint_values:
service = config.load( service = config.load(
build_config_details( build_config_details(
{'web': { {
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox', 'image': 'busybox',
'entrypoint': entrypoint 'entrypoint': entrypoint}}},
}},
'working_dir', 'working_dir',
'filename.yml' 'filename.yml'
) )
@ -1738,10 +1781,13 @@ class ConfigTest(unittest.TestCase):
def test_logs_warning_for_boolean_in_environment(self): def test_logs_warning_for_boolean_in_environment(self):
config_details = build_config_details({ config_details = build_config_details({
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'busybox', 'image': 'busybox',
'environment': {'SHOW_STUFF': True} 'environment': {'SHOW_STUFF': True}
} }
}
}) })
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
@ -1752,10 +1798,12 @@ class ConfigTest(unittest.TestCase):
def test_config_valid_environment_dict_key_contains_dashes(self): def test_config_valid_environment_dict_key_contains_dashes(self):
services = config.load( services = config.load(
build_config_details( build_config_details(
{'web': { {
'version': str(VERSION),
'services': {
'web': {
'image': 'busybox', 'image': 'busybox',
'environment': {'SPRING_JPA_HIBERNATE_DDL-AUTO': 'none'} 'environment': {'SPRING_JPA_HIBERNATE_DDL-AUTO': 'none'}}}},
}},
'working_dir', 'working_dir',
'filename.yml' 'filename.yml'
) )
@ -1794,16 +1842,21 @@ web:
def test_validate_extra_hosts_invalid(self): def test_validate_extra_hosts_invalid(self):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details({ config.load(build_config_details({
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'alpine', 'image': 'alpine',
'extra_hosts': "www.example.com: 192.168.0.17", 'extra_hosts': "www.example.com: 192.168.0.17",
} }
}
})) }))
assert "web.extra_hosts contains an invalid type" in exc.exconly() assert "web.extra_hosts contains an invalid type" in exc.exconly()
def test_validate_extra_hosts_invalid_list(self): def test_validate_extra_hosts_invalid_list(self):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
config.load(build_config_details({ config.load(build_config_details({
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'alpine', 'image': 'alpine',
'extra_hosts': [ 'extra_hosts': [
@ -1811,16 +1864,20 @@ web:
{'api.example.com': '192.168.0.18'} {'api.example.com': '192.168.0.18'}
], ],
} }
}
})) }))
assert "which is an invalid type" in exc.exconly() assert "which is an invalid type" in exc.exconly()
def test_normalize_dns_options(self): def test_normalize_dns_options(self):
actual = config.load(build_config_details({ actual = config.load(build_config_details({
'version': str(VERSION),
'services': {
'web': { 'web': {
'image': 'alpine', 'image': 'alpine',
'dns': '8.8.8.8', 'dns': '8.8.8.8',
'dns_search': 'domain.local', 'dns_search': 'domain.local',
} }
}
})) }))
assert actual.services == [ assert actual.services == [
{ {
@ -1947,7 +2004,6 @@ web:
def test_isolation_option(self): def test_isolation_option(self):
actual = config.load(build_config_details({ actual = config.load(build_config_details({
'version': str(V2_1),
'services': { 'services': {
'web': { 'web': {
'image': 'win10', 'image': 'win10',
@ -1966,7 +2022,6 @@ web:
def test_runtime_option(self): def test_runtime_option(self):
actual = config.load(build_config_details({ actual = config.load(build_config_details({
'version': str(V2_3),
'services': { 'services': {
'web': { 'web': {
'image': 'nvidia/cuda', 'image': 'nvidia/cuda',
@ -2088,7 +2143,7 @@ web:
} }
actual = config.merge_service_dicts_from_files( actual = config.merge_service_dicts_from_files(
base, override, V3_2 base, override, VERSION
) )
assert actual['volumes'] == [ assert actual['volumes'] == [
@ -2135,7 +2190,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2169,7 +2224,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2201,7 +2256,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2233,7 +2288,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2262,7 +2317,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2282,7 +2337,7 @@ web:
} }
} }
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2304,7 +2359,7 @@ web:
} }
} }
override = {} override = {}
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'alpine:edge', 'image': 'alpine:edge',
'logging': { 'logging': {
@ -2332,7 +2387,7 @@ web:
'ports': ['1245:1245/udp'] 'ports': ['1245:1245/udp']
} }
actual = config.merge_service_dicts(base, override, V3_1) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
'command': 'top', 'command': 'top',
@ -2348,7 +2403,7 @@ web:
} }
} }
override = {} override = {}
actual = config.merge_service_dicts(base, override, V2_1) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == base assert actual == base
def test_merge_depends_on_mixed_syntax(self): def test_merge_depends_on_mixed_syntax(self):
@ -2363,7 +2418,7 @@ web:
'depends_on': ['app3'] 'depends_on': ['app3']
} }
actual = config.merge_service_dicts(base, override, V2_1) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'busybox', 'image': 'busybox',
'depends_on': { 'depends_on': {
@ -2401,7 +2456,7 @@ web:
'labels': {'com.docker.compose.test': 'yes'} 'labels': {'com.docker.compose.test': 'yes'}
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'busybox', 'image': 'busybox',
'pid': 'host', 'pid': 'host',
@ -2417,7 +2472,7 @@ web:
} }
override = {'secrets': ['other-src.txt']} override = {'secrets': ['other-src.txt']}
actual = config.merge_service_dicts(base, override, V3_1) actual = config.merge_service_dicts(base, override, VERSION)
assert secret_sort(actual['secrets']) == secret_sort([ assert secret_sort(actual['secrets']) == secret_sort([
{'source': 'src.txt'}, {'source': 'src.txt'},
{'source': 'other-src.txt'} {'source': 'other-src.txt'}
@ -2437,7 +2492,7 @@ web:
} }
] ]
} }
actual = config.merge_service_dicts(base, override, V3_1) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['secrets'] == override['secrets'] assert actual['secrets'] == override['secrets']
def test_merge_different_configs(self): def test_merge_different_configs(self):
@ -2449,7 +2504,7 @@ web:
} }
override = {'configs': ['other-src.txt']} override = {'configs': ['other-src.txt']}
actual = config.merge_service_dicts(base, override, V3_3) actual = config.merge_service_dicts(base, override, VERSION)
assert secret_sort(actual['configs']) == secret_sort([ assert secret_sort(actual['configs']) == secret_sort([
{'source': 'src.txt'}, {'source': 'src.txt'},
{'source': 'other-src.txt'} {'source': 'other-src.txt'}
@ -2469,7 +2524,7 @@ web:
} }
] ]
} }
actual = config.merge_service_dicts(base, override, V3_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['configs'] == override['configs'] assert actual['configs'] == override['configs']
def test_merge_deploy(self): def test_merge_deploy(self):
@ -2484,7 +2539,7 @@ web:
} }
} }
} }
actual = config.merge_service_dicts(base, override, V3_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['deploy'] == override['deploy'] assert actual['deploy'] == override['deploy']
def test_merge_deploy_override(self): def test_merge_deploy_override(self):
@ -2540,7 +2595,7 @@ web:
'update_config': {'max_failure_ratio': 0.712, 'parallelism': 4} 'update_config': {'max_failure_ratio': 0.712, 'parallelism': 4}
} }
} }
actual = config.merge_service_dicts(base, override, V3_5) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['deploy'] == { assert actual['deploy'] == {
'mode': 'replicated', 'mode': 'replicated',
'endpoint_mode': 'vip', 'endpoint_mode': 'vip',
@ -2596,7 +2651,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V3_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['credential_spec'] == override['credential_spec'] assert actual['credential_spec'] == override['credential_spec']
def test_merge_scale(self): def test_merge_scale(self):
@ -2609,7 +2664,7 @@ web:
'scale': 4, 'scale': 4,
} }
actual = config.merge_service_dicts(base, override, V2_2) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == {'image': 'bar', 'scale': 4} assert actual == {'image': 'bar', 'scale': 4}
def test_merge_blkio_config(self): def test_merge_blkio_config(self):
@ -2644,7 +2699,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_2) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'bar', 'image': 'bar',
'blkio_config': { 'blkio_config': {
@ -2671,7 +2726,7 @@ web:
'extra_hosts': ['bar:5.6.7.8', 'foo:127.0.0.1'] 'extra_hosts': ['bar:5.6.7.8', 'foo:127.0.0.1']
} }
actual = config.merge_service_dicts(base, override, V2_0) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['extra_hosts'] == { assert actual['extra_hosts'] == {
'foo': '127.0.0.1', 'foo': '127.0.0.1',
'bar': '5.6.7.8', 'bar': '5.6.7.8',
@ -2695,7 +2750,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['healthcheck'] == { assert actual['healthcheck'] == {
'start_period': base['healthcheck']['start_period'], 'start_period': base['healthcheck']['start_period'],
'test': override['healthcheck']['test'], 'test': override['healthcheck']['test'],
@ -2721,7 +2776,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['healthcheck'] == {'disabled': True} assert actual['healthcheck'] == {'disabled': True}
def test_merge_healthcheck_override_enables(self): def test_merge_healthcheck_override_enables(self):
@ -2743,7 +2798,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['healthcheck'] == override['healthcheck'] assert actual['healthcheck'] == override['healthcheck']
def test_merge_device_cgroup_rules(self): def test_merge_device_cgroup_rules(self):
@ -2756,7 +2811,7 @@ web:
'device_cgroup_rules': ['c 7:128 rwm', 'f 0:128 n'] 'device_cgroup_rules': ['c 7:128 rwm', 'f 0:128 n']
} }
actual = config.merge_service_dicts(base, override, V2_3) actual = config.merge_service_dicts(base, override, VERSION)
assert sorted(actual['device_cgroup_rules']) == sorted( assert sorted(actual['device_cgroup_rules']) == sorted(
['c 7:128 rwm', 'x 3:244 rw', 'f 0:128 n'] ['c 7:128 rwm', 'x 3:244 rw', 'f 0:128 n']
) )
@ -2771,7 +2826,7 @@ web:
'isolation': 'hyperv', 'isolation': 'hyperv',
} }
actual = config.merge_service_dicts(base, override, V2_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual == { assert actual == {
'image': 'bar', 'image': 'bar',
'isolation': 'hyperv', 'isolation': 'hyperv',
@ -2793,7 +2848,7 @@ web:
} }
} }
actual = config.merge_service_dicts(base, override, V2_3) actual = config.merge_service_dicts(base, override, VERSION)
assert actual['storage_opt'] == { assert actual['storage_opt'] == {
'size': '2G', 'size': '2G',
'readonly': 'false', 'readonly': 'false',
@ -3350,6 +3405,7 @@ class PortsTest(unittest.TestCase):
assert "non-unique" in exc.value.msg assert "non-unique" in exc.value.msg
@pytest.mark.skip(reason="Validator is one_off (generic error)")
def test_config_invalid_ports_format_validation(self): def test_config_invalid_ports_format_validation(self):
for invalid_ports in self.INVALID_PORT_MAPPINGS: for invalid_ports in self.INVALID_PORT_MAPPINGS:
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
@ -3599,6 +3655,7 @@ class InterpolationTest(unittest.TestCase):
assert 'BAR' in warnings[0] assert 'BAR' in warnings[0]
assert 'FOO' in warnings[1] assert 'FOO' in warnings[1]
@pytest.mark.skip(reason='compatibility mode was removed internally')
def test_compatibility_mode_warnings(self): def test_compatibility_mode_warnings(self):
config_details = build_config_details({ config_details = build_config_details({
'version': '3.5', 'version': '3.5',
@ -3637,6 +3694,7 @@ class InterpolationTest(unittest.TestCase):
assert 'restart_policy.delay' in warn_message assert 'restart_policy.delay' in warn_message
assert 'restart_policy.window' in warn_message assert 'restart_policy.window' in warn_message
@pytest.mark.skip(reason='compatibility mode was removed internally')
def test_compatibility_mode_load(self): def test_compatibility_mode_load(self):
config_details = build_config_details({ config_details = build_config_details({
'version': '3.5', 'version': '3.5',
@ -4369,7 +4427,8 @@ class EnvTest(unittest.TestCase):
service_dict = config.load( service_dict = config.load(
build_config_details( build_config_details(
{'foo': {'build': '.', 'volumes': ['$HOSTENV:$CONTAINERENV']}}, {'services': {
'foo': {'build': '.', 'volumes': ['$HOSTENV:$CONTAINERENV']}}},
"tests/fixtures/env", "tests/fixtures/env",
) )
).services[0] ).services[0]
@ -4377,7 +4436,8 @@ class EnvTest(unittest.TestCase):
service_dict = config.load( service_dict = config.load(
build_config_details( build_config_details(
{'foo': {'build': '.', 'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}}, {'services': {
'foo': {'build': '.', 'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}}},
"tests/fixtures/env", "tests/fixtures/env",
) )
).services[0] ).services[0]
@ -4497,8 +4557,12 @@ class ExtendsTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
{
'version': '3',
'services':
{ {
'web': {'image': 'busybox', 'extends': {}}, 'web': {'image': 'busybox', 'extends': {}},
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -4512,7 +4576,14 @@ class ExtendsTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'web': {'image': 'busybox', 'extends': {'file': 'common.yml'}}, 'version': '3',
'services':
{
'web': {
'image': 'busybox',
'extends': {'file': 'common.yml'}
}
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -4525,6 +4596,9 @@ class ExtendsTest(unittest.TestCase):
with pytest.raises(ConfigurationError) as excinfo: with pytest.raises(ConfigurationError) as excinfo:
config.load( config.load(
build_config_details( build_config_details(
{
'version': '3',
'services':
{ {
'web': { 'web': {
'image': 'busybox', 'image': 'busybox',
@ -4534,6 +4608,7 @@ class ExtendsTest(unittest.TestCase):
'rogue_key': 'is not allowed' 'rogue_key': 'is not allowed'
} }
}, },
}
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
'filename.yml' 'filename.yml'
@ -4548,12 +4623,15 @@ class ExtendsTest(unittest.TestCase):
config.load( config.load(
build_config_details( build_config_details(
{ {
'version': '3',
'services': {
'web': { 'web': {
'image': 'busybox', 'image': 'busybox',
'extends': { 'extends': {
'file': 1, 'file': 1,
'service': 'web', 'service': 'web',
} }
}
}, },
}, },
'tests/fixtures/extends', 'tests/fixtures/extends',
@ -5205,7 +5283,7 @@ class SerializeTest(unittest.TestCase):
} }
} }
assert denormalize_service_dict(service_dict, V3_0) == { assert denormalize_service_dict(service_dict, VERSION) == {
'image': 'busybox', 'image': 'busybox',
'command': 'true', 'command': 'true',
'depends_on': ['service2', 'service3'] 'depends_on': ['service2', 'service3']
@ -5221,7 +5299,11 @@ class SerializeTest(unittest.TestCase):
} }
} }
assert denormalize_service_dict(service_dict, V2_1) == service_dict assert denormalize_service_dict(service_dict, VERSION) == {
'image': 'busybox',
'command': 'true',
'depends_on': ['service2', 'service3']
}
def test_serialize_time(self): def test_serialize_time(self):
data = { data = {
@ -5255,7 +5337,7 @@ class SerializeTest(unittest.TestCase):
processed_service = config.process_service(config.ServiceConfig( processed_service = config.process_service(config.ServiceConfig(
'.', 'test', 'test', service_dict '.', 'test', 'test', service_dict
)) ))
denormalized_service = denormalize_service_dict(processed_service, V2_3) denormalized_service = denormalize_service_dict(processed_service, VERSION)
assert denormalized_service['healthcheck']['interval'] == '100s' assert denormalized_service['healthcheck']['interval'] == '100s'
assert denormalized_service['healthcheck']['timeout'] == '30s' assert denormalized_service['healthcheck']['timeout'] == '30s'
assert denormalized_service['healthcheck']['start_period'] == '2090ms' assert denormalized_service['healthcheck']['start_period'] == '2090ms'
@ -5266,7 +5348,7 @@ class SerializeTest(unittest.TestCase):
} }
image_digest = 'busybox@sha256:abcde' image_digest = 'busybox@sha256:abcde'
assert denormalize_service_dict(service_dict, V3_0, image_digest) == { assert denormalize_service_dict(service_dict, VERSION, image_digest) == {
'image': 'busybox@sha256:abcde' 'image': 'busybox@sha256:abcde'
} }
@ -5275,7 +5357,7 @@ class SerializeTest(unittest.TestCase):
'image': 'busybox' 'image': 'busybox'
} }
assert denormalize_service_dict(service_dict, V3_0) == { assert denormalize_service_dict(service_dict, VERSION) == {
'image': 'busybox' 'image': 'busybox'
} }
@ -5308,10 +5390,10 @@ class SerializeTest(unittest.TestCase):
serialized_service = serialized_config['services']['web'] serialized_service = serialized_config['services']['web']
assert secret_sort(serialized_service['secrets']) == secret_sort(service_dict['secrets']) assert secret_sort(serialized_service['secrets']) == secret_sort(service_dict['secrets'])
assert 'secrets' in serialized_config assert 'secrets' in serialized_config
assert serialized_config['secrets']['two'] == secrets_dict['two'] assert serialized_config['secrets']['two'] == {'external': True, 'name': 'two'}
def test_serialize_ports(self): def test_serialize_ports(self):
config_dict = config.Config(version=V2_0, services=[ config_dict = config.Config(version=VERSION, services=[
{ {
'ports': [types.ServicePort('80', '8080', None, None, None)], 'ports': [types.ServicePort('80', '8080', None, None, None)],
'image': 'alpine', 'image': 'alpine',
@ -5320,10 +5402,10 @@ class SerializeTest(unittest.TestCase):
], volumes={}, networks={}, secrets={}, configs={}) ], volumes={}, networks={}, secrets={}, configs={})
serialized_config = yaml.safe_load(serialize_config(config_dict)) serialized_config = yaml.safe_load(serialize_config(config_dict))
assert '8080:80/tcp' in serialized_config['services']['web']['ports'] assert [{'published': 8080, 'target': 80}] == serialized_config['services']['web']['ports']
def test_serialize_ports_with_ext_ip(self): def test_serialize_ports_with_ext_ip(self):
config_dict = config.Config(version=V3_5, services=[ config_dict = config.Config(version=VERSION, services=[
{ {
'ports': [types.ServicePort('80', '8080', None, None, '127.0.0.1')], 'ports': [types.ServicePort('80', '8080', None, None, '127.0.0.1')],
'image': 'alpine', 'image': 'alpine',
@ -5363,7 +5445,7 @@ class SerializeTest(unittest.TestCase):
serialized_service = serialized_config['services']['web'] serialized_service = serialized_config['services']['web']
assert secret_sort(serialized_service['configs']) == secret_sort(service_dict['configs']) assert secret_sort(serialized_service['configs']) == secret_sort(service_dict['configs'])
assert 'configs' in serialized_config assert 'configs' in serialized_config
assert serialized_config['configs']['two'] == configs_dict['two'] assert serialized_config['configs']['two'] == {'external': True, 'name': 'two'}
def test_serialize_bool_string(self): def test_serialize_bool_string(self):
cfg = { cfg = {

View File

@ -8,9 +8,7 @@ from compose.config.interpolation import Interpolator
from compose.config.interpolation import InvalidInterpolation from compose.config.interpolation import InvalidInterpolation
from compose.config.interpolation import TemplateWithDefaults from compose.config.interpolation import TemplateWithDefaults
from compose.config.interpolation import UnsetRequiredSubstitution from compose.config.interpolation import UnsetRequiredSubstitution
from compose.const import COMPOSEFILE_V2_0 as V2_0 from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V2_3 as V2_3
from compose.const import COMPOSEFILE_V3_4 as V3_4
@pytest.fixture @pytest.fixture
@ -63,7 +61,7 @@ def test_interpolate_environment_variables_in_services(mock_env):
} }
} }
} }
value = interpolate_environment_variables(V2_0, services, 'service', mock_env) value = interpolate_environment_variables(VERSION, services, 'service', mock_env)
assert value == expected assert value == expected
@ -88,7 +86,7 @@ def test_interpolate_environment_variables_in_volumes(mock_env):
}, },
'other': {}, 'other': {},
} }
value = interpolate_environment_variables(V2_0, volumes, 'volume', mock_env) value = interpolate_environment_variables(VERSION, volumes, 'volume', mock_env)
assert value == expected assert value == expected
@ -113,7 +111,7 @@ def test_interpolate_environment_variables_in_secrets(mock_env):
}, },
'other': {}, 'other': {},
} }
value = interpolate_environment_variables(V3_4, secrets, 'secret', mock_env) value = interpolate_environment_variables(VERSION, secrets, 'secret', mock_env)
assert value == expected assert value == expected
@ -184,7 +182,7 @@ def test_interpolate_environment_services_convert_types_v2(mock_env):
} }
} }
value = interpolate_environment_variables(V2_3, entry, 'service', mock_env) value = interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert value == expected assert value == expected
@ -257,7 +255,7 @@ def test_interpolate_environment_services_convert_types_v3(mock_env):
} }
} }
value = interpolate_environment_variables(V3_4, entry, 'service', mock_env) value = interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert value == expected assert value == expected
@ -265,21 +263,21 @@ def test_interpolate_environment_services_convert_types_invalid(mock_env):
entry = {'service1': {'privileged': '${POSINT}'}} entry = {'service1': {'privileged': '${POSINT}'}}
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
interpolate_environment_variables(V2_3, entry, 'service', mock_env) interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert 'Error while attempting to convert service.service1.privileged to '\ assert 'Error while attempting to convert service.service1.privileged to '\
'appropriate type: "50" is not a valid boolean value' in exc.exconly() 'appropriate type: "50" is not a valid boolean value' in exc.exconly()
entry = {'service1': {'cpus': '${TRUE}'}} entry = {'service1': {'cpus': '${TRUE}'}}
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
interpolate_environment_variables(V2_3, entry, 'service', mock_env) interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert 'Error while attempting to convert service.service1.cpus to '\ assert 'Error while attempting to convert service.service1.cpus to '\
'appropriate type: "True" is not a valid float' in exc.exconly() 'appropriate type: "True" is not a valid float' in exc.exconly()
entry = {'service1': {'ulimits': {'nproc': '${FLOAT}'}}} entry = {'service1': {'ulimits': {'nproc': '${FLOAT}'}}}
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
interpolate_environment_variables(V2_3, entry, 'service', mock_env) interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert 'Error while attempting to convert service.service1.ulimits.nproc to '\ assert 'Error while attempting to convert service.service1.ulimits.nproc to '\
'appropriate type: "0.145" is not a valid integer' in exc.exconly() 'appropriate type: "0.145" is not a valid integer' in exc.exconly()
@ -302,7 +300,7 @@ def test_interpolate_environment_network_convert_types(mock_env):
} }
} }
value = interpolate_environment_variables(V3_4, entry, 'network', mock_env) value = interpolate_environment_variables(VERSION, entry, 'network', mock_env)
assert value == expected assert value == expected
@ -319,13 +317,13 @@ def test_interpolate_environment_external_resource_convert_types(mock_env):
} }
} }
value = interpolate_environment_variables(V3_4, entry, 'network', mock_env) value = interpolate_environment_variables(VERSION, entry, 'network', mock_env)
assert value == expected assert value == expected
value = interpolate_environment_variables(V3_4, entry, 'volume', mock_env) value = interpolate_environment_variables(VERSION, entry, 'volume', mock_env)
assert value == expected assert value == expected
value = interpolate_environment_variables(V3_4, entry, 'secret', mock_env) value = interpolate_environment_variables(VERSION, entry, 'secret', mock_env)
assert value == expected assert value == expected
value = interpolate_environment_variables(V3_4, entry, 'config', mock_env) value = interpolate_environment_variables(VERSION, entry, 'config', mock_env)
assert value == expected assert value == expected
@ -356,7 +354,7 @@ def test_interpolate_service_name_uses_dot(mock_env):
} }
} }
value = interpolate_environment_variables(V3_4, entry, 'service', mock_env) value = interpolate_environment_variables(VERSION, entry, 'service', mock_env)
assert value == expected assert value == expected

View File

@ -5,8 +5,8 @@ from compose.config.types import parse_extra_hosts
from compose.config.types import ServicePort from compose.config.types import ServicePort
from compose.config.types import VolumeFromSpec from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec from compose.config.types import VolumeSpec
from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
def test_parse_extra_hosts_list(): def test_parse_extra_hosts_list():
@ -233,26 +233,26 @@ class TestVolumesFromSpec(object):
VolumeFromSpec.parse('unknown:format:ro', self.services, V1) VolumeFromSpec.parse('unknown:format:ro', self.services, V1)
def test_parse_v2_from_service(self): def test_parse_v2_from_service(self):
volume_from = VolumeFromSpec.parse('servicea', self.services, V2_0) volume_from = VolumeFromSpec.parse('servicea', self.services, VERSION)
assert volume_from == VolumeFromSpec('servicea', 'rw', 'service') assert volume_from == VolumeFromSpec('servicea', 'rw', 'service')
def test_parse_v2_from_service_with_mode(self): def test_parse_v2_from_service_with_mode(self):
volume_from = VolumeFromSpec.parse('servicea:ro', self.services, V2_0) volume_from = VolumeFromSpec.parse('servicea:ro', self.services, VERSION)
assert volume_from == VolumeFromSpec('servicea', 'ro', 'service') assert volume_from == VolumeFromSpec('servicea', 'ro', 'service')
def test_parse_v2_from_container(self): def test_parse_v2_from_container(self):
volume_from = VolumeFromSpec.parse('container:foo', self.services, V2_0) volume_from = VolumeFromSpec.parse('container:foo', self.services, VERSION)
assert volume_from == VolumeFromSpec('foo', 'rw', 'container') assert volume_from == VolumeFromSpec('foo', 'rw', 'container')
def test_parse_v2_from_container_with_mode(self): def test_parse_v2_from_container_with_mode(self):
volume_from = VolumeFromSpec.parse('container:foo:ro', self.services, V2_0) volume_from = VolumeFromSpec.parse('container:foo:ro', self.services, VERSION)
assert volume_from == VolumeFromSpec('foo', 'ro', 'container') assert volume_from == VolumeFromSpec('foo', 'ro', 'container')
def test_parse_v2_invalid_type(self): def test_parse_v2_invalid_type(self):
with pytest.raises(ConfigurationError) as exc: with pytest.raises(ConfigurationError) as exc:
VolumeFromSpec.parse('bogus:foo:ro', self.services, V2_0) VolumeFromSpec.parse('bogus:foo:ro', self.services, VERSION)
assert "Unknown volumes_from type 'bogus'" in exc.exconly() assert "Unknown volumes_from type 'bogus'" in exc.exconly()
def test_parse_v2_invalid(self): def test_parse_v2_invalid(self):
with pytest.raises(ConfigurationError): with pytest.raises(ConfigurationError):
VolumeFromSpec.parse('unknown:format:ro', self.services, V2_0) VolumeFromSpec.parse('unknown:format:ro', self.services, VERSION)

View File

@ -13,10 +13,8 @@ from ..helpers import BUSYBOX_IMAGE_WITH_TAG
from compose.config import ConfigurationError from compose.config import ConfigurationError
from compose.config.config import Config from compose.config.config import Config
from compose.config.types import VolumeFromSpec from compose.config.types import VolumeFromSpec
from compose.const import COMPOSE_SPEC as VERSION
from compose.const import COMPOSEFILE_V1 as V1 from compose.const import COMPOSEFILE_V1 as V1
from compose.const import COMPOSEFILE_V2_0 as V2_0
from compose.const import COMPOSEFILE_V2_4 as V2_4
from compose.const import COMPOSEFILE_V3_7 as V3_7
from compose.const import DEFAULT_TIMEOUT from compose.const import DEFAULT_TIMEOUT
from compose.const import LABEL_SERVICE from compose.const import LABEL_SERVICE
from compose.container import Container from compose.container import Container
@ -29,6 +27,17 @@ from compose.service import ImageType
from compose.service import Service from compose.service import Service
def build_config(**kwargs):
return Config(
version=kwargs.get('version', VERSION),
services=kwargs.get('services'),
volumes=kwargs.get('volumes'),
networks=kwargs.get('networks'),
secrets=kwargs.get('secrets'),
configs=kwargs.get('configs'),
)
class ProjectTest(unittest.TestCase): class ProjectTest(unittest.TestCase):
def setUp(self): def setUp(self):
self.mock_client = mock.create_autospec(docker.APIClient) self.mock_client = mock.create_autospec(docker.APIClient)
@ -36,7 +45,7 @@ class ProjectTest(unittest.TestCase):
self.mock_client.api_version = docker.constants.DEFAULT_DOCKER_API_VERSION self.mock_client.api_version = docker.constants.DEFAULT_DOCKER_API_VERSION
def test_from_config_v1(self): def test_from_config_v1(self):
config = Config( config = build_config(
version=V1, version=V1,
services=[ services=[
{ {
@ -67,8 +76,7 @@ class ProjectTest(unittest.TestCase):
@mock.patch('compose.network.Network.true_name', lambda n: n.full_name) @mock.patch('compose.network.Network.true_name', lambda n: n.full_name)
def test_from_config_v2(self): def test_from_config_v2(self):
config = Config( config = build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'web', 'name': 'web',
@ -174,8 +182,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[{ services=[{
'name': 'test', 'name': 'test',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -202,8 +209,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'vol', 'name': 'vol',
@ -230,8 +236,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=None, client=None,
config_data=Config( config_data=build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'vol', 'name': 'vol',
@ -540,7 +545,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V1, version=V1,
services=[ services=[
{ {
@ -565,8 +570,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'test', 'name': 'test',
@ -596,8 +600,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'aaa', 'name': 'aaa',
@ -623,8 +626,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'foo', 'name': 'foo',
@ -644,8 +646,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[ services=[
{ {
'name': 'foo', 'name': 'foo',
@ -679,8 +680,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -697,8 +697,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
@ -748,8 +747,8 @@ class ProjectTest(unittest.TestCase):
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,
} }
config_data = Config( config_data = build_config(
version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None services=[service_config], networks={}, volumes={}, secrets=None, configs=None
) )
project = Project.from_config(name='test', client=self.mock_client, config_data=config_data) project = Project.from_config(name='test', client=self.mock_client, config_data=config_data)
@ -770,8 +769,7 @@ class ProjectTest(unittest.TestCase):
assert project.get_service('web').platform == 'linux/s390x' assert project.get_service('web').platform == 'linux/s390x'
def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self): def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self):
config_data = Config( config_data = build_config(
version=V3_7,
services=[ services=[
{'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG}, {'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG},
{'name': 'db', 'image': BUSYBOX_IMAGE_WITH_TAG, 'stop_grace_period': '1s'}, {'name': 'db', 'image': BUSYBOX_IMAGE_WITH_TAG, 'stop_grace_period': '1s'},
@ -802,8 +800,7 @@ class ProjectTest(unittest.TestCase):
project = Project.from_config( project = Project.from_config(
name='test', name='test',
client=self.mock_client, client=self.mock_client,
config_data=Config( config_data=build_config(
version=V2_0,
services=[{ services=[{
'name': 'web', 'name': 'web',
'image': BUSYBOX_IMAGE_WITH_TAG, 'image': BUSYBOX_IMAGE_WITH_TAG,