From b78c1ec193c5ee65fd59d00a42c4ca996251af57 Mon Sep 17 00:00:00 2001 From: aiordache Date: Fri, 26 Jun 2020 10:21:48 +0200 Subject: [PATCH 1/7] Merge 2.x and 3.x schemas Signed-off-by: aiordache --- compose/cli/main.py | 17 +- compose/config/config.py | 117 +- compose/config/config_schema_v2.0.json | 424 ------- compose/config/config_schema_v2.1.json | 480 -------- compose/config/config_schema_v2.2.json | 489 -------- compose/config/config_schema_v2.3.json | 533 --------- compose/config/config_schema_v2.4.json | 535 --------- compose/config/config_schema_v3.0.json | 399 ------- compose/config/config_schema_v3.1.json | 444 -------- compose/config/config_schema_v3.2.json | 492 -------- compose/config/config_schema_v3.3.json | 551 --------- compose/config/config_schema_v3.4.json | 560 --------- compose/config/config_schema_v3.5.json | 588 ---------- compose/config/config_schema_v3.6.json | 582 ---------- compose/config/config_schema_v3.7.json | 1013 ----------------- ...hema_v3.8.json => config_schema_v4.0.json} | 101 +- compose/config/interpolation.py | 4 +- compose/config/serialize.py | 31 +- compose/config/validation.py | 29 +- compose/const.py | 46 +- docker-compose.spec | 69 +- docker-compose_darwin.spec | 69 +- tests/acceptance/cli_test.py | 89 +- tests/integration/project_test.py | 89 +- tests/integration/testcases.py | 28 +- tests/unit/cli/errors_test.py | 8 +- tests/unit/config/config_test.py | 378 +++--- tests/unit/config/interpolation_test.py | 32 +- tests/unit/config/types_test.py | 14 +- tests/unit/project_test.py | 59 +- 30 files changed, 522 insertions(+), 7748 deletions(-) delete mode 100644 compose/config/config_schema_v2.0.json delete mode 100644 compose/config/config_schema_v2.1.json delete mode 100644 compose/config/config_schema_v2.2.json delete mode 100644 compose/config/config_schema_v2.3.json delete mode 100644 compose/config/config_schema_v2.4.json delete mode 100644 compose/config/config_schema_v3.0.json delete mode 100644 compose/config/config_schema_v3.1.json delete mode 100644 compose/config/config_schema_v3.2.json delete mode 100644 compose/config/config_schema_v3.3.json delete mode 100644 compose/config/config_schema_v3.4.json delete mode 100644 compose/config/config_schema_v3.5.json delete mode 100644 compose/config/config_schema_v3.6.json delete mode 100644 compose/config/config_schema_v3.7.json rename compose/config/{config_schema_v3.8.json => config_schema_v4.0.json} (89%) diff --git a/compose/cli/main.py b/compose/cli/main.py index 0027a7e96..079ea160a 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -24,7 +24,6 @@ from ..config import resolve_build_args from ..config.environment import Environment from ..config.serialize import serialize_config from ..config.types import VolumeSpec -from ..const import COMPOSEFILE_V2_2 as V2_2 from ..const import IS_WINDOWS_PLATFORM from ..errors import StreamParseError from ..progress_stream import StreamOutputError @@ -205,7 +204,7 @@ class TopLevelCommand(object): --project-directory PATH Specify an alternate working directory (default: the path of the Compose file) --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 Commands: @@ -882,16 +881,10 @@ class TopLevelCommand(object): """ 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( - 'The scale command is deprecated. ' - 'Use the up command with the --scale flag instead.' - ) + log.warning( + 'The scale command is deprecated. ' + 'Use the up command with the --scale flag instead.' + ) for service_name, num in parse_scale_args(options['SERVICE=NUM']).items(): self.project.get_service(service_name).scale(num, timeout=timeout) diff --git a/compose/config/config.py b/compose/config/config.py index 61fad199f..1557e52d0 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -12,18 +12,13 @@ import yaml from cached_property import cached_property from . import types -from .. import const 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 ..const import COMPOSEFILE_V4 as VERSION from ..utils import build_string_dict from ..utils import json_hash from ..utils import parse_bytes from ..utils import parse_nanoseconds_int from ..utils import splitdrive -from ..version import ComposeVersion from .environment import env_vars_from_file from .environment import Environment from .environment import split_env @@ -189,15 +184,28 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')): @cached_property def version(self): - if 'version' not in self.config: - return V1 + version = self.config.get('version', None) + 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): log.warning('Unexpected type for "version" key in "{}". Assuming ' '"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 if not isinstance(version, str): @@ -205,31 +213,32 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')): 'Version in "{}" is invalid - it should be a string.' .format(self.filename)) - if version == '1': + if isinstance(version, str): + version_pattern = re.compile(r"^[1-4]+(\.\d+)?$") + if not version_pattern.match(version): + raise ConfigurationError( + 'Version "{}" in "{}" is invalid.' + .format(version, self.filename)) + + if version.startswith("1"): + version = V1 + else: + version = VERSION + + if version == V1: raise ConfigurationError( 'Version in "{}" is invalid. {}' .format(self.filename, VERSION_EXPLANATION) ) - - version_pattern = re.compile(r"^[2-9]+(\.\d+)?$") - if not version_pattern.match(version): - raise ConfigurationError( - 'Version "{}" in "{}" is invalid.' - .format(version, self.filename)) - - if version == '2': - return const.COMPOSEFILE_V2_0 - - if version == '3': - return const.COMPOSEFILE_V3_0 - - return ComposeVersion(version) + return version def get_service(self, name): return self.get_service_dicts()[name] 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): 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', {}) 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): - 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')): @@ -299,6 +308,7 @@ def find(base_dir, filenames, environment, override_dir=None): def validate_config_version(config_files): main_file = config_files[0] validate_top_level_object(main_file) + for next_file in config_files[1:]: validate_top_level_object(next_file) @@ -371,8 +381,8 @@ def check_swarm_only_config(service_dicts, compatibility=False): 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') @@ -383,6 +393,8 @@ def load(config_details, compatibility=False, interpolate=True): 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) processed_files = [ @@ -412,7 +424,7 @@ def load(config_details, compatibility=False, interpolate=True): check_swarm_only_config(service_dicts, compatibility) - 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) @@ -449,11 +461,12 @@ def load_mapping(config_files, get_func, entity_type, working_dir=None): 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: - raise ConfigurationError( - "{} {} declared as external but specifies additional attributes " - "({}).".format( - entity_type, name, ', '.join(k for k in config if k != 'external'))) + for k in config.keys(): + if k not in ['external', 'name']: + raise ConfigurationError( + "{} {} declared as external but specifies additional attributes " + "({}).".format( + entity_type, name, ', '.join(k for k in config if k != 'external'))) def load_services(config_details, config_file, compatibility=False, interpolate=True): @@ -554,27 +567,25 @@ def process_config_file(config_file, environment, service_name=None, interpolate environment, interpolate, ) - if config_file.version >= const.COMPOSEFILE_V3_1: - processed_config['secrets'] = process_config_section( - config_file, - config_file.get_secrets(), - 'secret', - environment, - interpolate, - ) - if config_file.version >= const.COMPOSEFILE_V3_3: - processed_config['configs'] = process_config_section( - config_file, - config_file.get_configs(), - 'config', - environment, - interpolate, - ) + processed_config['secrets'] = process_config_section( + config_file, + config_file.get_secrets(), + 'secret', + environment, + interpolate, + ) + processed_config['configs'] = process_config_section( + config_file, + config_file.get_configs(), + 'config', + environment, + interpolate, + ) else: processed_config = services 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: raise ConfigurationError( diff --git a/compose/config/config_schema_v2.0.json b/compose/config/config_schema_v2.0.json deleted file mode 100644 index 419f2e28c..000000000 --- a/compose/config/config_schema_v2.0.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v2.1.json b/compose/config/config_schema_v2.1.json deleted file mode 100644 index 3cb1ee213..000000000 --- a/compose/config/config_schema_v2.1.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v2.2.json b/compose/config/config_schema_v2.2.json deleted file mode 100644 index 8e1f288ba..000000000 --- a/compose/config/config_schema_v2.2.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v2.3.json b/compose/config/config_schema_v2.3.json deleted file mode 100644 index 659dbcd1a..000000000 --- a/compose/config/config_schema_v2.3.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v2.4.json b/compose/config/config_schema_v2.4.json deleted file mode 100644 index 4e6417887..000000000 --- a/compose/config/config_schema_v2.4.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.0.json b/compose/config/config_schema_v3.0.json deleted file mode 100644 index 10c363521..000000000 --- a/compose/config/config_schema_v3.0.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.1.json b/compose/config/config_schema_v3.1.json deleted file mode 100644 index 8630ec317..000000000 --- a/compose/config/config_schema_v3.1.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.2.json b/compose/config/config_schema_v3.2.json deleted file mode 100644 index 5eccdea72..000000000 --- a/compose/config/config_schema_v3.2.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.3.json b/compose/config/config_schema_v3.3.json deleted file mode 100644 index f63842b9d..000000000 --- a/compose/config/config_schema_v3.3.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.4.json b/compose/config/config_schema_v3.4.json deleted file mode 100644 index 23e955446..000000000 --- a/compose/config/config_schema_v3.4.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.5.json b/compose/config/config_schema_v3.5.json deleted file mode 100644 index e3bdecbc1..000000000 --- a/compose/config/config_schema_v3.5.json +++ /dev/null @@ -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"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.6.json b/compose/config/config_schema_v3.6.json deleted file mode 100644 index 95a552b34..000000000 --- a/compose/config/config_schema_v3.6.json +++ /dev/null @@ -1,582 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "config_schema_v3.6.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/list_or_dict"}, - "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/list_or_dict"}, - "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"} - } - }, - "tmpfs": { - "type": "object", - "properties": { - "size": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "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/list_or_dict"}, - "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"} - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "external": { - "type": ["boolean", "object"], - "properties": { - "name": {"type": "string"} - }, - "additionalProperties": false - }, - "internal": {"type": "boolean"}, - "attachable": {"type": "boolean"}, - "labels": {"$ref": "#/definitions/list_or_dict"} - }, - "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/list_or_dict"} - }, - "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/list_or_dict"} - }, - "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/list_or_dict"} - }, - "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} - ] - }, - - "constraints": { - "service": { - "id": "#/definitions/constraints/service", - "anyOf": [ - {"required": ["build"]}, - {"required": ["image"]} - ], - "properties": { - "build": { - "required": ["context"] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.7.json b/compose/config/config_schema_v3.7.json deleted file mode 100644 index c5906af9f..000000000 --- a/compose/config/config_schema_v3.7.json +++ /dev/null @@ -1,1013 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "id": "config_schema_v3.7.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/list_or_dict" - }, - "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" - }, - "init": { - "type": "boolean" - }, - "ipc": { - "type": "string" - }, - "isolation": { - "type": "string" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "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" - } - } - }, - "tmpfs": { - "type": "object", - "properties": { - "size": { - "type": "integer", - "minimum": 0 - } - } - } - }, - "additionalProperties": false - } - ], - "uniqueItems": true - } - }, - "working_dir": { - "type": "string" - } - }, - "patternProperties": { - "^x-": {} - }, - "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/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 - }, - "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" - } - }, - "additionalProperties": false - } - } - }, - "additionalProperties": false - }, - "external": { - "type": [ - "boolean", - "object" - ], - "properties": { - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "internal": { - "type": "boolean" - }, - "attachable": { - "type": "boolean" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - } - }, - "patternProperties": { - "^x-": {} - }, - "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/list_or_dict" - } - }, - "patternProperties": { - "^x-": {} - }, - "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/list_or_dict" - } - }, - "patternProperties": { - "^x-": {} - }, - "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/list_or_dict" - } - }, - "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 - } - ] - }, - "constraints": { - "service": { - "id": "#/definitions/constraints/service", - "anyOf": [ - { - "required": [ - "build" - ] - }, - { - "required": [ - "image" - ] - } - ], - "properties": { - "build": { - "required": [ - "context" - ] - } - } - } - } - } -} diff --git a/compose/config/config_schema_v3.8.json b/compose/config/config_schema_v4.0.json similarity index 89% rename from compose/config/config_schema_v3.8.json rename to compose/config/config_schema_v4.0.json index e9bc20cee..316fbf9a0 100644 --- a/compose/config/config_schema_v3.8.json +++ b/compose/config/config_schema_v4.0.json @@ -1,10 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "id": "config_schema_v3.8.json", + "id": "config_schema_v4.0.json", "type": "object", - "required": [ - "version" - ], "properties": { "version": { "type": "string" @@ -190,7 +187,26 @@ "additionalProperties": false }, "depends_on": { - "$ref": "#/definitions/list_of_strings" + "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", @@ -199,6 +215,13 @@ }, "uniqueItems": true }, + "dns_opt": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, "dns": { "$ref": "#/definitions/string_or_list" }, @@ -238,6 +261,23 @@ }, "uniqueItems": true }, + "extends": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + + "properties": { + "service": {"type": "string"}, + "file": {"type": "string"} + }, + "required": ["service"], + "additionalProperties": false + } + ] + }, "external_links": { "type": "array", "items": { @@ -300,6 +340,10 @@ "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" }, @@ -324,7 +368,11 @@ }, "ipv6_address": { "type": "string" - } + }, + "link_local_ips": { + "$ref": "#/definitions/list_of_strings" + }, + "priority": {"type": "number"} }, "additionalProperties": false }, @@ -338,6 +386,15 @@ } ] }, + "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", @@ -387,6 +444,12 @@ "restart": { "type": "string" }, + "runtime": { + "type": "string" + }, + "scale": { + "type": "integer" + }, "security_opt": { "type": "array", "items": { @@ -543,6 +606,13 @@ "uniqueItems": true } }, + "volumes_from": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, "working_dir": { "type": "string" } @@ -810,11 +880,28 @@ "type": "object", "properties": { "subnet": { - "type": "string" + "type": "string", + "format": "subnet_ip_address" + }, + "ip_range": {"type": "string"}, + "gateway": {"type": "string"}, + "aux_addresses": { + "type": "object", + "patternProperties": { + "^.+$": {"type": "string"} + }, + "additionalProperties": false } }, "additionalProperties": false } + }, + "options": { + "type": "object", + "patternProperties": { + "^.+$": {"type": "string"} + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/compose/config/interpolation.py b/compose/config/interpolation.py index 9f0fc4847..bfa6a56c9 100644 --- a/compose/config/interpolation.py +++ b/compose/config/interpolation.py @@ -3,7 +3,7 @@ import re from string import Template 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_nanoseconds_int @@ -25,7 +25,7 @@ class Interpolator(object): def interpolate_environment_variables(version, config, section, environment): - if version <= V2_0: + if version == V1: interpolator = Interpolator(Template, environment) else: interpolator = Interpolator(TemplateWithDefaults, environment) diff --git a/compose/config/serialize.py b/compose/config/serialize.py index fe0d007a6..a162e3873 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -2,12 +2,7 @@ import yaml from compose.config import types from compose.const import COMPOSEFILE_V1 as V1 -from compose.const import COMPOSEFILE_V2_1 as V2_1 -from compose.const import COMPOSEFILE_V2_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 +from compose.const import COMPOSEFILE_V4 as VERSION 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): - result = {'version': str(V2_1) if config.version == V1 else str(config.version)} + result = {'version': str(config.version)} denormalized_services = [ denormalize_service_dict( service_dict, @@ -72,25 +67,11 @@ def denormalize_config(config, image_digests=None): del conf['external_name'] if 'name' in conf: - if config.version < V2_1 or ( - config.version >= V3_0 and config.version < v3_introduced_name_key(key)): - del conf['name'] - elif 'external' in conf: + if 'external' in conf: 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 -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): if 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: 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([ 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: 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'] ] - 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'] = [ v.legacy_repr() if isinstance(v, types.MountSpec) else v for v in service_dict['volumes'] ] diff --git a/compose/config/validation.py b/compose/config/validation.py index 3161b2395..f52de03de 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -435,15 +435,29 @@ def process_config_schema_errors(error): return handle_generic_error(error, path) -def validate_against_config_schema(config_file): - schema = load_jsonschema(config_file) +def keys_to_str(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"]) validator = Draft4Validator( schema, resolver=RefResolver(get_resolver_path(), schema), format_checker=format_checker) handle_errors( - validator.iter_errors(config_file.config), + validator.iter_errors(config), process_config_schema_errors, config_file.filename) @@ -453,7 +467,7 @@ def validate_service_constraints(config, service_name, config_file): return process_service_constraint_errors( errors, service_name, config_file.version) - schema = load_jsonschema(config_file) + schema = load_jsonschema(config_file.version) validator = Draft4Validator(schema['definitions']['constraints']['service']) handle_errors(validator.iter_errors(config), handler, None) @@ -472,16 +486,15 @@ def get_schema_path(): return os.path.dirname(os.path.abspath(__file__)) -def load_jsonschema(config_file): +def load_jsonschema(version): filename = os.path.join( get_schema_path(), - "config_schema_v{0}.json".format(config_file.version)) + "config_schema_v{0}.json".format(version)) if not os.path.exists(filename): raise ConfigurationError( 'Version in "{}" is unsupported. {}' - .format(config_file.filename, VERSION_EXPLANATION)) - + .format(filename, VERSION_EXPLANATION)) with open(filename, "r") as fh: return json.load(fh) diff --git a/compose/const.py b/compose/const.py index d80e23c9e..9623769d1 100644 --- a/compose/const.py +++ b/compose/const.py @@ -24,56 +24,16 @@ SECRETS_PATH = '/run/secrets' WINDOWS_LONGPATH_PREFIX = '\\\\?\\' COMPOSEFILE_V1 = ComposeVersion('1') -COMPOSEFILE_V2_0 = ComposeVersion('2.0') -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') +COMPOSEFILE_V4 = ComposeVersion('4.0') # minimum DOCKER ENGINE API version needed to support # features for each compose schema version API_VERSIONS = { COMPOSEFILE_V1: '1.21', - COMPOSEFILE_V2_0: '1.22', - 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', + COMPOSEFILE_V4: '1.38', } API_VERSION_TO_ENGINE_VERSION = { API_VERSIONS[COMPOSEFILE_V1]: '1.9.0', - API_VERSIONS[COMPOSEFILE_V2_0]: '1.10.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', + API_VERSIONS[COMPOSEFILE_V4]: '18.06.0', } diff --git a/docker-compose.spec b/docker-compose.spec index 4c8db3e51..35195f9b4 100644 --- a/docker-compose.spec +++ b/docker-compose.spec @@ -23,73 +23,8 @@ exe = EXE(pyz, 'DATA' ), ( - 'compose/config/config_schema_v2.0.json', - 'compose/config/config_schema_v2.0.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', + 'compose/config/config_schema_v4.0.json', + 'compose/config/config_schema_v4.0.json', 'DATA' ), ( diff --git a/docker-compose_darwin.spec b/docker-compose_darwin.spec index df7fcdd6f..f4642314e 100644 --- a/docker-compose_darwin.spec +++ b/docker-compose_darwin.spec @@ -32,73 +32,8 @@ coll = COLLECT(exe, 'DATA' ), ( - 'compose/config/config_schema_v2.0.json', - 'compose/config/config_schema_v2.0.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', + 'compose/config/config_schema_v4.0.json', + 'compose/config/config_schema_v4.0.json', 'DATA' ), ( diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index c84d3f8cb..51f6428be 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -20,6 +20,8 @@ from ..helpers import BUSYBOX_IMAGE_WITH_TAG from ..helpers import create_host_file from compose.cli.command import get_project from compose.config.errors import DuplicateOverrideFileFound +from compose.const import COMPOSEFILE_V1 as V1 +from compose.const import COMPOSEFILE_V4 as VERSION from compose.container import Container from compose.project import OneOffFilter 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 pull_busybox 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' @@ -42,7 +40,7 @@ ProcessResult = namedtuple('ProcessResult', 'stdout stderr') BUILD_CACHE_TEXT = 'Using cache' BUILD_PULL_TEXT = 'Status: Image is up to date for busybox:1.27.2' COMPOSE_COMPATIBILITY_DICT = { - 'version': '2.3', + 'version': str(VERSION), 'volumes': {'foo': {'driver': 'default'}}, 'networks': {'bar': {}}, 'services': { @@ -287,7 +285,7 @@ services: output = yaml.safe_load(result.stdout) expected = { - 'version': '2.0', + 'version': str(VERSION), 'volumes': {'data': {'driver': 'local'}}, 'networks': {'front': {}}, 'services': { @@ -311,7 +309,7 @@ services: self.base_dir = 'tests/fixtures/restart' result = self.dispatch(['config']) assert yaml.safe_load(result.stdout) == { - 'version': '2.0', + 'version': str(VERSION), 'services': { 'never': { 'image': 'busybox', @@ -343,10 +341,12 @@ services: assert 'networks' in json_result assert json_result['networks'] == { 'networks_foo': { - 'external': True # {'name': 'networks_foo'} + 'external': True, + 'name': 'networks_foo' }, 'bar': { - 'external': {'name': 'networks_bar'} + 'external': True, + 'name': 'networks_bar' } } @@ -355,14 +355,14 @@ services: result = self.dispatch(['config']) json_result = yaml.safe_load(result.stdout) assert json_result == { + 'version': str(VERSION), 'services': { 'web': { 'command': 'true', 'image': 'alpine:latest', - 'ports': ['5643/tcp', '9999/tcp'] + 'ports': [{'target': 5643}, {'target': 9999}] } - }, - 'version': '2.4' + } } def test_config_with_env_file(self): @@ -370,14 +370,14 @@ services: result = self.dispatch(['--env-file', '.env2', 'config']) json_result = yaml.safe_load(result.stdout) assert json_result == { + 'version': str(VERSION), 'services': { 'web': { 'command': 'false', '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): @@ -385,14 +385,14 @@ services: result = self.dispatch(['--project-directory', 'alt/', 'config']) json_result = yaml.safe_load(result.stdout) assert json_result == { + 'version': str(VERSION), 'services': { 'web': { 'command': 'echo uwu', '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): @@ -403,11 +403,11 @@ services: assert json_result['volumes'] == { 'foo': { 'external': True, + 'name': 'foo', }, 'bar': { - 'external': { - 'name': 'some_bar', - }, + 'external': True, + 'name': 'some_bar', } } @@ -435,11 +435,11 @@ services: assert json_result['volumes'] == { 'foo': { 'external': True, + 'name': 'foo', }, 'bar': { - 'external': { - 'name': 'some_bar', - }, + 'external': True, + 'name': 'some_bar', } } @@ -479,7 +479,7 @@ services: self.base_dir = 'tests/fixtures/v1-config' result = self.dispatch(['config']) assert yaml.safe_load(result.stdout) == { - 'version': '2.1', + 'version': str(V1), 'services': { 'net': { 'image': 'busybox', @@ -498,13 +498,11 @@ services: }, } - @v3_only() def test_config_v3(self): self.base_dir = 'tests/fixtures/v3-full' result = self.dispatch(['config']) - assert yaml.safe_load(result.stdout) == { - 'version': '3.5', + 'version': str(VERSION), 'volumes': { 'foobar': { 'labels': { @@ -576,12 +574,14 @@ services: }, } + @pytest.mark.skip(reason='deprecated option') def test_config_compatibility_mode(self): self.base_dir = 'tests/fixtures/compatibility-mode' result = self.dispatch(['--compatibility', 'config']) assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT + @pytest.mark.skip(reason='deprecated option') @mock.patch.dict(os.environ) def test_config_compatibility_mode_from_env(self): self.base_dir = 'tests/fixtures/compatibility-mode' @@ -590,6 +590,7 @@ services: assert yaml.load(result.stdout) == COMPOSE_COMPATIBILITY_DICT + @pytest.mark.skip(reason='deprecated option') @mock.patch.dict(os.environ) def test_config_compatibility_mode_from_env_and_option_precedence(self): self.base_dir = 'tests/fixtures/compatibility-mode' @@ -1018,7 +1019,6 @@ services: result = self.dispatch(['down', '--rmi', 'bogus'], returncode=1) assert '--rmi flag must be' in result.stderr - @v2_only() def test_down(self): 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(another_name) in result.stdout - @v2_only() def test_up(self): self.base_dir = 'tests/fixtures/v2-simple' self.dispatch(['up', '-d'], None) @@ -1135,7 +1134,6 @@ services: for service in services: assert self.lookup(container, service.name) - @v2_only() def test_up_no_start(self): self.base_dir = 'tests/fixtures/v2-full' self.dispatch(['up', '--no-start'], None) @@ -1166,7 +1164,6 @@ services: ] assert len(remote_volumes) > 0 - @v2_only() def test_up_no_start_remove_orphans(self): self.base_dir = 'tests/fixtures/v2-simple' self.dispatch(['up', '--no-start'], None) @@ -1182,7 +1179,6 @@ services: stopped=True) + next.containers(stopped=True)), services) assert len(stopped2) == 1 - @v2_only() def test_up_no_ansi(self): self.base_dir = 'tests/fixtures/v2-simple' result = self.dispatch(['--no-ansi', 'up', '-d'], None) @@ -1190,7 +1186,6 @@ services: assert "%c[1A" % 27 not in result.stderr assert "%c[1B" % 27 not in result.stderr - @v2_only() def test_up_with_default_network_config(self): filename = 'default-network-config.yml' @@ -1204,7 +1199,6 @@ services: assert networks[0]['Options']['com.docker.network.bridge.enable_icc'] == 'false' - @v2_only() def test_up_with_network_aliases(self): filename = 'network-aliases.yml' self.base_dir = 'tests/fixtures/networks' @@ -1232,7 +1226,6 @@ services: assert 'forward_facing' in front_aliases assert 'ahead' in front_aliases - @v2_only() def test_up_with_network_internal(self): self.require_api_version('1.23') filename = 'network-internal.yml' @@ -1250,7 +1243,6 @@ services: assert networks[0]['Internal'] is True - @v2_only() def test_up_with_network_static_addresses(self): filename = 'network-static-addresses.yml' ipv4_address = '172.16.100.100' @@ -1274,7 +1266,6 @@ services: assert ipv4_address in ipam_config.values() assert ipv6_address in ipam_config.values() - @v2_only() def test_up_with_networks(self): self.base_dir = 'tests/fixtures/networks' self.dispatch(['up', '-d'], None) @@ -1322,7 +1313,6 @@ services: # app has aliased db to "database" assert self.lookup(app_container, "database") - @v2_only() def test_up_missing_network(self): self.base_dir = 'tests/fixtures/networks' @@ -1332,7 +1322,6 @@ services: assert 'Service "web" uses an undefined network "foo"' in result.stderr - @v2_only() @no_cluster('container networks not supported in Swarm') def test_up_with_network_mode(self): c = self.client.create_container( @@ -1371,7 +1360,6 @@ services: assert not container_mode_container.get('NetworkSettings.Networks') assert container_mode_container.get('HostConfig.NetworkMode') == container_mode_source - @v2_only() def test_up_external_networks(self): filename = 'external-networks.yml' @@ -1395,7 +1383,6 @@ services: container = self.project.containers()[0] assert sorted(list(container.get('NetworkSettings.Networks'))) == sorted(network_names) - @v2_only() def test_up_with_external_default_network(self): filename = 'external-default.yml' @@ -1418,7 +1405,6 @@ services: container = self.project.containers()[0] assert list(container.get('NetworkSettings.Networks')) == [network_name] - @v2_1_only() def test_up_with_network_labels(self): filename = 'network-label.yml' @@ -1438,7 +1424,6 @@ services: assert 'label_key' in networks[0]['Labels'] assert networks[0]['Labels']['label_key'] == 'label_val' - @v2_1_only() def test_up_with_volume_labels(self): filename = 'volume-label.yml' @@ -1458,7 +1443,6 @@ services: assert 'label_key' in volumes[0]['Labels'] assert volumes[0]['Labels']['label_key'] == 'label_val' - @v2_only() def test_up_no_services(self): self.base_dir = 'tests/fixtures/no-services' self.dispatch(['up', '-d'], None) @@ -1515,7 +1499,6 @@ services: bar_container.id ) - @v3_only() def test_up_with_healthcheck(self): def wait_on_health_status(container, status): def condition(): @@ -1649,7 +1632,6 @@ services: os.kill(proc.pid, signal.SIGTERM) wait_on_condition(ContainerCountCondition(self.project, 0)) - @v2_only() def test_up_handles_force_shutdown(self): self.base_dir = 'tests/fixtures/sleeps-composefile' proc = start_process(self.base_dir, ['up', '-t', '200']) @@ -1674,7 +1656,6 @@ services: proc.wait() assert proc.returncode == 1 - @v2_only() @no_cluster('Container PID mode does not work across clusters') def test_up_with_pid_mode(self): c = self.client.create_container( @@ -1738,7 +1719,6 @@ services: assert stdout == "operator\n" assert stderr == "" - @v3_only() def test_exec_workdir(self): self.base_dir = 'tests/fixtures/links-composefile' os.environ['COMPOSE_API_VERSION'] = '1.35' @@ -1748,7 +1728,6 @@ services: stdout, stderr = self.dispatch(['exec', '-T', '--workdir', '/etc', 'console', 'ls']) assert 'passwd' in stdout - @v2_2_only() def test_exec_service_with_environment_overridden(self): name = 'service' self.base_dir = 'tests/fixtures/environment-exec' @@ -1793,7 +1772,6 @@ services: assert len(db.containers()) == 1 assert len(console.containers()) == 0 - @v2_only() def test_run_service_with_dependencies(self): self.base_dir = 'tests/fixtures/v2-dependencies' self.dispatch(['run', 'web', '/bin/true'], None) @@ -2105,7 +2083,6 @@ services: container = service.containers(stopped=True, one_off=True)[0] assert workdir == container.get('Config.WorkingDir') - @v2_only() def test_run_service_with_use_aliases(self): filename = 'network-aliases.yml' self.base_dir = 'tests/fixtures/networks' @@ -2127,7 +2104,6 @@ services: assert 'forward_facing' in front_aliases assert 'ahead' in front_aliases - @v2_only() def test_run_interactive_connects_to_network(self): self.base_dir = 'tests/fixtures/networks' @@ -2153,7 +2129,6 @@ services: aliases = set(config['Aliases'] or []) - {container.short_id} assert not aliases - @v2_only() def test_run_detached_connects_to_network(self): self.base_dir = 'tests/fixtures/networks' self.dispatch(['up', '-d']) @@ -2332,7 +2307,6 @@ services: assert 'failed' in result.stderr assert 'No containers to start' in result.stderr - @v2_only() def test_up_logging(self): self.base_dir = 'tests/fixtures/logging-composefile' self.dispatch(['up', '-d']) @@ -2563,11 +2537,6 @@ services: assert len(project.get_service('simple').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): self.base_dir = 'tests/fixtures/scale' project = self.project diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index 19d27185c..cb40dbd43 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -21,11 +21,7 @@ from compose.config import ConfigurationError from compose.config import types from compose.config.types import VolumeFromSpec from compose.config.types import VolumeSpec -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_1 as V3_1 +from compose.const import COMPOSEFILE_V4 as VERSION from compose.const import LABEL_PROJECT from compose.const import LABEL_SERVICE 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 is_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): return config.Config( - version=kwargs.get('version'), + version=kwargs.get('version', VERSION), services=kwargs.get('services'), volumes=kwargs.get('volumes'), networks=kwargs.get('networks'), @@ -106,7 +97,6 @@ class ProjectTest(DockerClientTestCase): def test_parallel_pull_with_no_image(self): config_data = build_config( - version=V2_3, services=[{ 'name': 'web', 'build': {'context': '.'}, @@ -162,14 +152,12 @@ class ProjectTest(DockerClientTestCase): db = project.get_service('db') assert db._get_volumes_from() == [data_container.id + ':rw'] - @v2_only() @no_cluster('container networks not supported in Swarm') def test_network_mode_from_service(self): project = Project.from_config( name='composetest', client=self.client, config_data=load_config({ - 'version': str(V2_0), 'services': { 'net': { 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -190,14 +178,12 @@ class ProjectTest(DockerClientTestCase): net = project.get_service('net') assert web.network_mode.mode == 'container:' + net.containers()[0].id - @v2_only() @no_cluster('container networks not supported in Swarm') def test_network_mode_from_container(self): def get_project(): return Project.from_config( name='composetest', config_data=load_config({ - 'version': str(V2_0), 'services': { 'web': { 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -448,7 +434,6 @@ class ProjectTest(DockerClientTestCase): assert db_container.id != old_db_id assert db_container.get('Volumes./etc') == db_volume_path - @v2_3_only() def test_recreate_preserves_mounts(self): web = self.create_service('web') db = self.create_service('db', volumes=[types.MountSpec(type='volume', target='/etc')]) @@ -656,10 +641,8 @@ class ProjectTest(DockerClientTestCase): service = project.get_service('web') assert len(service.containers()) == 1 - @v2_only() def test_project_up_networks(self): config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -701,10 +684,8 @@ class ProjectTest(DockerClientTestCase): foo_data = self.client.inspect_network('composetest_foo') assert foo_data['Driver'] == 'bridge' - @v2_only() def test_up_with_ipam_config(self): config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -761,10 +742,8 @@ class ProjectTest(DockerClientTestCase): }], } - @v2_only() def test_up_with_ipam_options(self): config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -796,10 +775,8 @@ class ProjectTest(DockerClientTestCase): "com.docker.compose.network.test": "9-29-045" } - @v2_1_only() def test_up_with_network_static_addresses(self): config_data = build_config( - version=V2_1, services=[{ 'name': 'web', '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('IPv6Address') == 'fe80::1001:102' - @v2_3_only() def test_up_with_network_priorities(self): mac_address = '74:6f:75:68:6f:75' def get_config_data(p1, p2, p3): return build_config( - version=V2_3, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -910,11 +885,9 @@ class ProjectTest(DockerClientTestCase): net_config = service_container.inspect()['NetworkSettings']['Networks']['composetest_n3'] assert net_config['MacAddress'] == mac_address - @v2_1_only() def test_up_with_enable_ipv6(self): self.require_api_version('1.23') config_data = build_config( - version=V2_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -954,10 +927,8 @@ class ProjectTest(DockerClientTestCase): get('IPAMConfig', {})) assert ipam_config.get('IPv6Address') == 'fe80::1001:102' - @v2_only() def test_up_with_network_static_addresses_missing_subnet(self): config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -990,10 +961,8 @@ class ProjectTest(DockerClientTestCase): with pytest.raises(ProjectError): project.up() - @v2_1_only() def test_up_with_network_link_local_ips(self): config_data = build_config( - version=V2_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1025,10 +994,8 @@ class ProjectTest(DockerClientTestCase): assert 'LinkLocalIPs' in ipam_config assert ipam_config['LinkLocalIPs'] == ['169.254.8.8'] - @v2_1_only() def test_up_with_custom_name_resources(self): config_data = build_config( - version=V2_2, services=[{ 'name': 'web', 'volumes': [VolumeSpec.parse('foo:/container-path')], @@ -1062,11 +1029,9 @@ class ProjectTest(DockerClientTestCase): assert network['Labels']['com.docker.compose.test_value'] == 'sharpdressedman' assert volume['Labels']['com.docker.compose.test_value'] == 'thefuror' - @v2_1_only() def test_up_with_isolation(self): self.require_api_version('1.24') config_data = build_config( - version=V2_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1082,11 +1047,9 @@ class ProjectTest(DockerClientTestCase): service_container = project.get_service('web').containers(stopped=True)[0] assert service_container.inspect()['HostConfig']['Isolation'] == 'default' - @v2_1_only() def test_up_with_invalid_isolation(self): self.require_api_version('1.24') config_data = build_config( - version=V2_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1101,12 +1064,10 @@ class ProjectTest(DockerClientTestCase): with pytest.raises(ProjectError): project.up() - @v2_3_only() @if_runtime_available('runc') def test_up_with_runtime(self): self.require_api_version('1.30') config_data = build_config( - version=V2_3, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1122,11 +1083,9 @@ class ProjectTest(DockerClientTestCase): service_container = project.get_service('web').containers(stopped=True)[0] assert service_container.inspect()['HostConfig']['Runtime'] == 'runc' - @v2_3_only() def test_up_with_invalid_runtime(self): self.require_api_version('1.30') config_data = build_config( - version=V2_3, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1141,12 +1100,10 @@ class ProjectTest(DockerClientTestCase): with pytest.raises(ProjectError): project.up() - @v2_3_only() @if_runtime_available('nvidia') def test_up_with_nvidia_runtime(self): self.require_api_version('1.30') config_data = build_config( - version=V2_3, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1162,11 +1119,9 @@ class ProjectTest(DockerClientTestCase): service_container = project.get_service('web').containers(stopped=True)[0] assert service_container.inspect()['HostConfig']['Runtime'] == 'nvidia' - @v2_only() def test_project_up_with_network_internal(self): self.require_api_version('1.23') config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1188,14 +1143,12 @@ class ProjectTest(DockerClientTestCase): assert network['Internal'] is True - @v2_1_only() def test_project_up_with_network_label(self): self.require_api_version('1.23') network_name = 'network_with_label' config_data = build_config( - version=V2_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1223,12 +1176,10 @@ class ProjectTest(DockerClientTestCase): assert 'label_key' in networks[0]['Labels'] assert networks[0]['Labels']['label_key'] == 'label_val' - @v2_only() def test_project_up_volumes(self): vol_name = '{0:x}'.format(random.getrandbits(32)) full_vol_name = 'composetest_{0}'.format(vol_name) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1248,14 +1199,12 @@ class ProjectTest(DockerClientTestCase): assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Driver'] == 'local' - @v2_1_only() def test_project_up_with_volume_labels(self): self.require_api_version('1.23') volume_name = 'volume_with_label' config_data = build_config( - version=V2_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1290,12 +1239,10 @@ class ProjectTest(DockerClientTestCase): assert 'label_key' in volumes[0]['Labels'] assert volumes[0]['Labels']['label_key'] == 'label_val' - @v2_only() def test_project_up_logging_with_multiple_files(self): base_file = config.ConfigFile( 'base.yml', { - 'version': str(V2_0), 'services': { 'simple': {'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top'}, 'another': { @@ -1314,7 +1261,6 @@ class ProjectTest(DockerClientTestCase): override_file = config.ConfigFile( 'override.yml', { - 'version': str(V2_0), 'services': { 'another': { 'logging': { @@ -1342,12 +1288,10 @@ class ProjectTest(DockerClientTestCase): assert log_config assert log_config.get('Type') == 'none' - @v2_only() def test_project_up_port_mappings_with_multiple_files(self): base_file = config.ConfigFile( 'base.yml', { - 'version': str(V2_0), 'services': { 'simple': { 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1360,7 +1304,6 @@ class ProjectTest(DockerClientTestCase): override_file = config.ConfigFile( 'override.yml', { - 'version': str(V2_0), 'services': { 'simple': { 'ports': ['1234:1234'] @@ -1378,10 +1321,8 @@ class ProjectTest(DockerClientTestCase): containers = project.containers() assert len(containers) == 1 - @v2_2_only() def test_project_up_config_scale(self): config_data = build_config( - version=V2_2, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1406,12 +1347,10 @@ class ProjectTest(DockerClientTestCase): project.up() assert len(project.containers()) == 3 - @v2_only() def test_initialize_volumes(self): vol_name = '{0:x}'.format(random.getrandbits(32)) full_vol_name = 'composetest_{0}'.format(vol_name) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1430,12 +1369,10 @@ class ProjectTest(DockerClientTestCase): assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Driver'] == 'local' - @v2_only() def test_project_up_implicit_volume_driver(self): vol_name = '{0:x}'.format(random.getrandbits(32)) full_vol_name = 'composetest_{0}'.format(vol_name) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1454,12 +1391,10 @@ class ProjectTest(DockerClientTestCase): assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Driver'] == 'local' - @v3_only() def test_project_up_with_secrets(self): node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default')) config_data = build_config( - version=V3_1, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1491,12 +1426,10 @@ class ProjectTest(DockerClientTestCase): output = container.logs() assert output == b"This is the secret\n" - @v3_only() def test_project_up_with_added_secrets(self): node = create_host_file(self.client, os.path.abspath('tests/fixtures/secrets/default')) config_input1 = { - 'version': V3_1, 'services': [ { 'name': 'web', @@ -1545,12 +1478,11 @@ class ProjectTest(DockerClientTestCase): output = container.logs() assert output == b"This is the secret\n" - @v2_only() def test_initialize_volumes_invalid_volume_driver(self): vol_name = '{0:x}'.format(random.getrandbits(32)) config_data = build_config( - version=V2_0, + version=VERSION, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1566,14 +1498,12 @@ class ProjectTest(DockerClientTestCase): with pytest.raises(APIError if is_cluster(self.client) else config.ConfigurationError): project.volumes.initialize() - @v2_only() @no_cluster('inspect volume by name defect on Swarm Classic') def test_initialize_volumes_updated_driver(self): vol_name = '{0:x}'.format(random.getrandbits(32)) full_vol_name = 'composetest_{0}'.format(vol_name) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1605,7 +1535,6 @@ class ProjectTest(DockerClientTestCase): vol_name ) in str(e.value) - @v2_only() @no_cluster('inspect volume by name defect on Swarm Classic') def test_initialize_volumes_updated_driver_opts(self): vol_name = '{0:x}'.format(random.getrandbits(32)) @@ -1615,7 +1544,6 @@ class ProjectTest(DockerClientTestCase): driver_opts = {'o': 'bind', 'device': tmpdir, 'type': 'none'} config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1651,13 +1579,11 @@ class ProjectTest(DockerClientTestCase): vol_name, driver_opts['device'] ) in str(e.value) - @v2_only() def test_initialize_volumes_updated_blank_driver(self): vol_name = '{0:x}'.format(random.getrandbits(32)) full_vol_name = 'composetest_{0}'.format(vol_name) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1688,7 +1614,6 @@ class ProjectTest(DockerClientTestCase): assert volume_data['Name'].split('/')[-1] == full_vol_name assert volume_data['Driver'] == 'local' - @v2_only() @no_cluster('inspect volume by name defect on Swarm Classic') def test_initialize_volumes_external_volumes(self): # 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) self.client.create_volume(vol_name) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1715,12 +1639,10 @@ class ProjectTest(DockerClientTestCase): with pytest.raises(NotFound): self.client.inspect_volume(full_vol_name) - @v2_only() def test_initialize_volumes_inexistent_external_volume(self): vol_name = '{0:x}'.format(random.getrandbits(32)) config_data = build_config( - version=V2_0, services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1740,7 +1662,6 @@ class ProjectTest(DockerClientTestCase): vol_name ) in str(e.value) - @v2_only() def test_project_up_named_volumes_in_binds(self): vol_name = '{0:x}'.format(random.getrandbits(32)) full_vol_name = 'composetest_{0}'.format(vol_name) @@ -1748,7 +1669,6 @@ class ProjectTest(DockerClientTestCase): base_file = config.ConfigFile( 'base.yml', { - 'version': str(V2_0), 'services': { 'simple': { 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -1839,7 +1759,6 @@ class ProjectTest(DockerClientTestCase): mock_log.warning.assert_not_called() - @v2_1_only() def test_project_up_healthy_dependency(self): config_dict = { 'version': '2.1', @@ -1876,7 +1795,6 @@ class ProjectTest(DockerClientTestCase): assert 'svc1' in svc2.get_dependency_names() assert svc1.is_healthy() - @v2_1_only() def test_project_up_unhealthy_dependency(self): config_dict = { 'version': '2.1', @@ -1915,7 +1833,6 @@ class ProjectTest(DockerClientTestCase): with pytest.raises(HealthCheckFailed): svc1.is_healthy() - @v2_1_only() def test_project_up_no_healthcheck_dependency(self): config_dict = { 'version': '2.1', diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index f787923ad..e6de0b921 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -12,13 +12,7 @@ from compose.config.config import resolve_environment from compose.config.environment import Environment from compose.const import API_VERSIONS 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 COMPOSEFILE_V4 as VERSION from compose.const import LABEL_PROJECT from compose.progress_stream import stream_output from compose.service import Service @@ -45,17 +39,11 @@ def get_links(container): def engine_max_version(): if 'DOCKER_VERSION' not in os.environ: - return V3_5 + return VERSION version = os.environ['DOCKER_VERSION'].partition('-')[0] if version_lt(version, '1.10'): return V1 - if version_lt(version, '1.12'): - return V2_0 - if version_lt(version, '1.13'): - return V2_1 - if version_lt(version, '17.06'): - return V3_2 - return V3_5 + return VERSION def min_version_skip(version): @@ -66,23 +54,23 @@ def min_version_skip(version): def v2_only(): - return min_version_skip(V2_0) + return min_version_skip(VERSION) def v2_1_only(): - return min_version_skip(V2_1) + return min_version_skip(VERSION) def v2_2_only(): - return min_version_skip(V2_2) + return min_version_skip(VERSION) def v2_3_only(): - return min_version_skip(V2_3) + return min_version_skip(VERSION) def v3_only(): - return min_version_skip(V3_0) + return min_version_skip(VERSION) class DockerClientTestCase(unittest.TestCase): diff --git a/tests/unit/cli/errors_test.py b/tests/unit/cli/errors_test.py index cb5f59df1..f7359a48b 100644 --- a/tests/unit/cli/errors_test.py +++ b/tests/unit/cli/errors_test.py @@ -34,19 +34,19 @@ class TestHandleConnectionErrors(object): def test_api_error_version_mismatch(self, mock_logging): 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") _, 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): 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") _, 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): msg = b"Something broke!" diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 1f9a168c0..612d0f38f 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -27,20 +27,12 @@ from compose.config.serialize import serialize_config from compose.config.serialize import serialize_ns_time_value from compose.config.types import VolumeSpec 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 COMPOSEFILE_V4 as VERSION from compose.const import IS_WINDOWS_PLATFORM from tests import mock from tests import unittest -DEFAULT_VERSION = V2_0 +DEFAULT_VERSION = VERSION def make_service_dict(name, service_dict, working_dir='.', filename=None): @@ -73,8 +65,10 @@ class ConfigTest(unittest.TestCase): service_dicts = config.load( build_config_details( { - 'foo': {'image': 'busybox'}, - 'bar': {'image': 'busybox', 'environment': ['FOO=1']}, + 'services': { + 'foo': {'image': 'busybox'}, + 'bar': {'image': 'busybox', 'environment': ['FOO=1']}, + } }, 'tests/fixtures/extends', 'common.yml' @@ -169,23 +163,23 @@ class ConfigTest(unittest.TestCase): def test_valid_versions(self): for version in ['2', '2.0']: 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'})) - assert cfg.version == V2_1 + assert cfg.version == VERSION 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'})) - assert cfg.version == V2_3 + assert cfg.version == VERSION for version in ['3', '3.0']: 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'})) - assert cfg.version == V3_1 + assert cfg.version == VERSION def test_v1_file_version(self): 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'] 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: config.load( build_config_details( @@ -213,12 +207,12 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as excinfo: config.load( build_config_details( - {'version': '2.18'}, + {'version': '1'}, 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() def test_version_1_is_invalid(self): @@ -328,7 +322,6 @@ class ConfigTest(unittest.TestCase): } }, 'working_dir', 'filename.yml') ) - assert 'Unexpected type for "version" key in "filename.yml"' \ in mock_logging.warning.call_args[0][0] @@ -376,7 +369,7 @@ class ConfigTest(unittest.TestCase): base_file = config.ConfigFile( 'base.yaml', { - 'version': str(V2_1), + 'version': '2', 'services': { 'web': { 'image': 'example/web', @@ -511,7 +504,15 @@ class ConfigTest(unittest.TestCase): for invalid_name in ['?not?allowed', ' ', '', '!', '/', '\xe2']: with pytest.raises(ConfigurationError) as exc: 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() def test_load_config_invalid_service_names_v2(self): @@ -543,17 +544,24 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as exc: config.load(build_config_details( { - 'web': {'image': 'busybox', 'name': 'bogus'}, + 'version': '2', + 'services': { + 'web': {'image': 'busybox', 'name': 'bogus'} + } }, 'working_dir', 'filename.yml', )) - - assert "Unsupported config option for web: 'name'" in exc.exconly() + assert "Unsupported config option for services.web: 'name'" in exc.exconly() def test_load_invalid_service_definition(self): config_details = build_config_details( - {'web': 'wrong'}, + { + 'version': '2', + 'services': { + 'web': 'wrong' + } + }, 'working_dir', 'filename.yml') with pytest.raises(ConfigurationError) as exc: @@ -585,7 +593,10 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as excinfo: config.load( build_config_details( - {1: {'image': 'busybox'}}, + { + 'version': '2', + 'services': {1: {'image': 'busybox'}} + }, 'working_dir', 'filename.yml' ) @@ -836,10 +847,10 @@ class ConfigTest(unittest.TestCase): def test_load_with_multiple_files_and_invalid_override(self): base_file = config.ConfigFile( 'base.yaml', - {'web': {'image': 'example/web'}}) + {'version': '2', 'services': {'web': {'image': 'example/web'}}}) override_file = config.ConfigFile( 'override.yaml', - {'bogus': 'thing'}) + {'version': '2', 'services': {'bogus': 'thing'}}) details = config.ConfigDetails('.', [base_file, override_file]) with pytest.raises(ConfigurationError) as exc: @@ -977,7 +988,6 @@ class ConfigTest(unittest.TestCase): service = config.load( build_config_details( { - 'version': str(V3_3), 'services': { 'web': { 'build': { @@ -1424,7 +1434,7 @@ class ConfigTest(unittest.TestCase): config.load( build_config_details( { - 'version': str(V2_1), + 'version': str(VERSION), 'networks': { 'foo': { 'driver': 'default', @@ -1455,7 +1465,6 @@ class ConfigTest(unittest.TestCase): networks = config.load( build_config_details( { - 'version': str(V2_1), 'networks': { 'foo': { 'driver': 'default', @@ -1487,7 +1496,10 @@ class ConfigTest(unittest.TestCase): config.load( build_config_details( { - 'foo': {'image': 'busybox', 'privilige': 'something'}, + 'version': str(VERSION), + 'services': { + 'foo': {'image': 'busybox', 'privilige': 'something'}, + } }, 'tests/fixtures/extends', 'filename.yml' @@ -1508,7 +1520,10 @@ class ConfigTest(unittest.TestCase): config.load( build_config_details( { - 'foo': {'image': 1}, + 'version': str(VERSION), + 'services': { + 'foo': {'image': 1}, + } }, 'tests/fixtures/extends', 'filename.yml' @@ -1555,7 +1570,10 @@ class ConfigTest(unittest.TestCase): config.load( build_config_details( { - 'foo': {'image': 'busybox', 'links': 'an_link'}, + 'version': str(VERSION), + 'services': { + 'foo': {'image': 'busybox', 'links': 'an_link'}, + } }, 'tests/fixtures/extends', 'filename.yml' @@ -1583,7 +1601,10 @@ class ConfigTest(unittest.TestCase): config.load( build_config_details( { - 'web': {'build': '.', 'devices': ['/dev/foo:/dev/foo', '/dev/foo:/dev/foo']} + 'version': str(VERSION), + 'services': { + 'web': {'build': '.', 'devices': ['/dev/foo:/dev/foo', '/dev/foo:/dev/foo']} + } }, 'tests/fixtures/extends', 'filename.yml' @@ -1597,7 +1618,10 @@ class ConfigTest(unittest.TestCase): config.load( build_config_details( { - 'web': {'build': '.', 'command': [1]} + 'version': str(VERSION), + 'services': { + 'web': {'build': '.', 'command': [1]} + } }, 'tests/fixtures/extends', 'filename.yml' @@ -1622,10 +1646,13 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as excinfo: config.load( build_config_details( - {'web': { - 'image': 'busybox', - 'extra_hosts': 'somehost:162.242.195.82' - }}, + { + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'extra_hosts': 'somehost:162.242.195.82'}} + }, 'working_dir', 'filename.yml' ) @@ -1638,13 +1665,16 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as excinfo: config.load( build_config_details( - {'web': { - 'image': 'busybox', - 'extra_hosts': [ - {'somehost': '162.242.195.82'}, - {'otherhost': '50.31.209.229'} - ] - }}, + { + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'extra_hosts': [ + {'somehost': '162.242.195.82'}, + {'otherhost': '50.31.209.229'} + ]}} + }, 'working_dir', 'filename.yml' ) @@ -1658,13 +1688,16 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as exc: config.load(build_config_details( { - 'web': { - 'image': 'busybox', - 'ulimits': { - 'nofile': { - "not_soft_or_hard": 100, - "soft": 10000, - "hard": 20000, + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'ulimits': { + 'nofile': { + "not_soft_or_hard": 100, + "soft": 10000, + "hard": 20000, + } } } } @@ -1679,9 +1712,12 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as exc: config.load(build_config_details( { - 'web': { - 'image': 'busybox', - 'ulimits': {'nofile': {"soft": 10000}} + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'ulimits': {'nofile': {"soft": 10000}} + } } }, 'working_dir', @@ -1695,10 +1731,13 @@ class ConfigTest(unittest.TestCase): with pytest.raises(ConfigurationError) as exc: config.load(build_config_details( { - 'web': { - 'image': 'busybox', - 'ulimits': { - 'nofile': {"soft": 10000, "hard": 1000} + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'ulimits': { + 'nofile': {"soft": 10000, "hard": 1000} + } } } }, @@ -1711,10 +1750,12 @@ class ConfigTest(unittest.TestCase): for expose in expose_values: service = config.load( build_config_details( - {'web': { - 'image': 'busybox', - 'expose': expose - }}, + { + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'expose': expose}}}, 'working_dir', 'filename.yml' ) @@ -1726,10 +1767,12 @@ class ConfigTest(unittest.TestCase): for entrypoint in entrypoint_values: service = config.load( build_config_details( - {'web': { - 'image': 'busybox', - 'entrypoint': entrypoint - }}, + { + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'entrypoint': entrypoint}}}, 'working_dir', 'filename.yml' ) @@ -1738,9 +1781,12 @@ class ConfigTest(unittest.TestCase): def test_logs_warning_for_boolean_in_environment(self): config_details = build_config_details({ - 'web': { - 'image': 'busybox', - 'environment': {'SHOW_STUFF': True} + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'environment': {'SHOW_STUFF': True} + } } }) @@ -1752,10 +1798,12 @@ class ConfigTest(unittest.TestCase): def test_config_valid_environment_dict_key_contains_dashes(self): services = config.load( build_config_details( - {'web': { - 'image': 'busybox', - 'environment': {'SPRING_JPA_HIBERNATE_DDL-AUTO': 'none'} - }}, + { + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'busybox', + 'environment': {'SPRING_JPA_HIBERNATE_DDL-AUTO': 'none'}}}}, 'working_dir', 'filename.yml' ) @@ -1794,9 +1842,12 @@ web: def test_validate_extra_hosts_invalid(self): with pytest.raises(ConfigurationError) as exc: config.load(build_config_details({ - 'web': { - 'image': 'alpine', - 'extra_hosts': "www.example.com: 192.168.0.17", + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'alpine', + 'extra_hosts': "www.example.com: 192.168.0.17", + } } })) assert "web.extra_hosts contains an invalid type" in exc.exconly() @@ -1804,22 +1855,28 @@ web: def test_validate_extra_hosts_invalid_list(self): with pytest.raises(ConfigurationError) as exc: config.load(build_config_details({ - 'web': { - 'image': 'alpine', - 'extra_hosts': [ - {'www.example.com': '192.168.0.17'}, - {'api.example.com': '192.168.0.18'} - ], + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'alpine', + 'extra_hosts': [ + {'www.example.com': '192.168.0.17'}, + {'api.example.com': '192.168.0.18'} + ], + } } })) assert "which is an invalid type" in exc.exconly() def test_normalize_dns_options(self): actual = config.load(build_config_details({ - 'web': { - 'image': 'alpine', - 'dns': '8.8.8.8', - 'dns_search': 'domain.local', + 'version': str(VERSION), + 'services': { + 'web': { + 'image': 'alpine', + 'dns': '8.8.8.8', + 'dns_search': 'domain.local', + } } })) assert actual.services == [ @@ -1947,7 +2004,6 @@ web: def test_isolation_option(self): actual = config.load(build_config_details({ - 'version': str(V2_1), 'services': { 'web': { 'image': 'win10', @@ -1966,7 +2022,6 @@ web: def test_runtime_option(self): actual = config.load(build_config_details({ - 'version': str(V2_3), 'services': { 'web': { 'image': 'nvidia/cuda', @@ -2088,7 +2143,7 @@ web: } actual = config.merge_service_dicts_from_files( - base, override, V3_2 + base, override, VERSION ) 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 == { 'image': 'alpine:edge', '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 == { 'image': 'alpine:edge', '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 == { 'image': 'alpine:edge', '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 == { 'image': 'alpine:edge', '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 == { 'image': 'alpine:edge', '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 == { 'image': 'alpine:edge', 'logging': { @@ -2304,7 +2359,7 @@ web: } } override = {} - actual = config.merge_service_dicts(base, override, V2_0) + actual = config.merge_service_dicts(base, override, VERSION) assert actual == { 'image': 'alpine:edge', 'logging': { @@ -2332,7 +2387,7 @@ web: 'ports': ['1245:1245/udp'] } - actual = config.merge_service_dicts(base, override, V3_1) + actual = config.merge_service_dicts(base, override, VERSION) assert actual == { 'image': BUSYBOX_IMAGE_WITH_TAG, 'command': 'top', @@ -2348,7 +2403,7 @@ web: } } override = {} - actual = config.merge_service_dicts(base, override, V2_1) + actual = config.merge_service_dicts(base, override, VERSION) assert actual == base def test_merge_depends_on_mixed_syntax(self): @@ -2363,7 +2418,7 @@ web: 'depends_on': ['app3'] } - actual = config.merge_service_dicts(base, override, V2_1) + actual = config.merge_service_dicts(base, override, VERSION) assert actual == { 'image': 'busybox', 'depends_on': { @@ -2401,7 +2456,7 @@ web: '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 == { 'image': 'busybox', 'pid': 'host', @@ -2417,7 +2472,7 @@ web: } 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([ {'source': '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'] def test_merge_different_configs(self): @@ -2449,7 +2504,7 @@ web: } 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([ {'source': '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'] 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'] def test_merge_deploy_override(self): @@ -2540,7 +2595,7 @@ web: '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'] == { 'mode': 'replicated', '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'] def test_merge_scale(self): @@ -2609,7 +2664,7 @@ web: '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} 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 == { 'image': 'bar', 'blkio_config': { @@ -2671,7 +2726,7 @@ web: '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'] == { 'foo': '127.0.0.1', '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'] == { 'start_period': base['healthcheck']['start_period'], '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} 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'] def test_merge_device_cgroup_rules(self): @@ -2756,7 +2811,7 @@ web: '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( ['c 7:128 rwm', 'x 3:244 rw', 'f 0:128 n'] ) @@ -2771,7 +2826,7 @@ web: 'isolation': 'hyperv', } - actual = config.merge_service_dicts(base, override, V2_3) + actual = config.merge_service_dicts(base, override, VERSION) assert actual == { 'image': 'bar', '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'] == { 'size': '2G', 'readonly': 'false', @@ -3350,6 +3405,7 @@ class PortsTest(unittest.TestCase): 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): for invalid_ports in self.INVALID_PORT_MAPPINGS: with pytest.raises(ConfigurationError) as exc: @@ -4369,7 +4425,8 @@ class EnvTest(unittest.TestCase): service_dict = config.load( build_config_details( - {'foo': {'build': '.', 'volumes': ['$HOSTENV:$CONTAINERENV']}}, + {'services': { + 'foo': {'build': '.', 'volumes': ['$HOSTENV:$CONTAINERENV']}}}, "tests/fixtures/env", ) ).services[0] @@ -4377,7 +4434,8 @@ class EnvTest(unittest.TestCase): service_dict = config.load( build_config_details( - {'foo': {'build': '.', 'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}}, + {'services': { + 'foo': {'build': '.', 'volumes': ['/opt${HOSTENV}:/opt${CONTAINERENV}']}}}, "tests/fixtures/env", ) ).services[0] @@ -4498,7 +4556,11 @@ class ExtendsTest(unittest.TestCase): config.load( build_config_details( { - 'web': {'image': 'busybox', 'extends': {}}, + 'version': '3', + 'services': + { + 'web': {'image': 'busybox', 'extends': {}}, + } }, 'tests/fixtures/extends', 'filename.yml' @@ -4512,7 +4574,14 @@ class ExtendsTest(unittest.TestCase): config.load( build_config_details( { - 'web': {'image': 'busybox', 'extends': {'file': 'common.yml'}}, + 'version': '3', + 'services': + { + 'web': { + 'image': 'busybox', + 'extends': {'file': 'common.yml'} + } + } }, 'tests/fixtures/extends', 'filename.yml' @@ -4526,14 +4595,18 @@ class ExtendsTest(unittest.TestCase): config.load( build_config_details( { - 'web': { - 'image': 'busybox', - 'extends': { - 'file': 'common.yml', - 'service': 'web', - 'rogue_key': 'is not allowed' - } - }, + 'version': '3', + 'services': + { + 'web': { + 'image': 'busybox', + 'extends': { + 'file': 'common.yml', + 'service': 'web', + 'rogue_key': 'is not allowed' + } + }, + } }, 'tests/fixtures/extends', 'filename.yml' @@ -4548,11 +4621,14 @@ class ExtendsTest(unittest.TestCase): config.load( build_config_details( { - 'web': { - 'image': 'busybox', - 'extends': { - 'file': 1, - 'service': 'web', + 'version': '3', + 'services': { + 'web': { + 'image': 'busybox', + 'extends': { + 'file': 1, + 'service': 'web', + } } }, }, @@ -5205,7 +5281,7 @@ class SerializeTest(unittest.TestCase): } } - assert denormalize_service_dict(service_dict, V3_0) == { + assert denormalize_service_dict(service_dict, VERSION) == { 'image': 'busybox', 'command': 'true', 'depends_on': ['service2', 'service3'] @@ -5221,7 +5297,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): data = { @@ -5255,7 +5335,7 @@ class SerializeTest(unittest.TestCase): processed_service = config.process_service(config.ServiceConfig( '.', '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']['timeout'] == '30s' assert denormalized_service['healthcheck']['start_period'] == '2090ms' @@ -5266,7 +5346,7 @@ class SerializeTest(unittest.TestCase): } 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' } @@ -5275,7 +5355,7 @@ class SerializeTest(unittest.TestCase): 'image': 'busybox' } - assert denormalize_service_dict(service_dict, V3_0) == { + assert denormalize_service_dict(service_dict, VERSION) == { 'image': 'busybox' } @@ -5308,10 +5388,10 @@ class SerializeTest(unittest.TestCase): serialized_service = serialized_config['services']['web'] assert secret_sort(serialized_service['secrets']) == secret_sort(service_dict['secrets']) 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): - config_dict = config.Config(version=V2_0, services=[ + config_dict = config.Config(version=VERSION, services=[ { 'ports': [types.ServicePort('80', '8080', None, None, None)], 'image': 'alpine', @@ -5320,10 +5400,10 @@ class SerializeTest(unittest.TestCase): ], volumes={}, networks={}, secrets={}, configs={}) 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): - 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')], 'image': 'alpine', @@ -5363,7 +5443,7 @@ class SerializeTest(unittest.TestCase): serialized_service = serialized_config['services']['web'] assert secret_sort(serialized_service['configs']) == secret_sort(service_dict['configs']) 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): cfg = { diff --git a/tests/unit/config/interpolation_test.py b/tests/unit/config/interpolation_test.py index 4efcf865a..dfc7beb2e 100644 --- a/tests/unit/config/interpolation_test.py +++ b/tests/unit/config/interpolation_test.py @@ -8,9 +8,7 @@ from compose.config.interpolation import Interpolator from compose.config.interpolation import InvalidInterpolation from compose.config.interpolation import TemplateWithDefaults from compose.config.interpolation import UnsetRequiredSubstitution -from compose.const import COMPOSEFILE_V2_0 as V2_0 -from compose.const import COMPOSEFILE_V2_3 as V2_3 -from compose.const import COMPOSEFILE_V3_4 as V3_4 +from compose.const import COMPOSEFILE_V4 as VERSION @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 @@ -88,7 +86,7 @@ def test_interpolate_environment_variables_in_volumes(mock_env): }, 'other': {}, } - value = interpolate_environment_variables(V2_0, volumes, 'volume', mock_env) + value = interpolate_environment_variables(VERSION, volumes, 'volume', mock_env) assert value == expected @@ -113,7 +111,7 @@ def test_interpolate_environment_variables_in_secrets(mock_env): }, 'other': {}, } - value = interpolate_environment_variables(V3_4, secrets, 'secret', mock_env) + value = interpolate_environment_variables(VERSION, secrets, 'secret', mock_env) 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 @@ -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 @@ -265,21 +263,21 @@ def test_interpolate_environment_services_convert_types_invalid(mock_env): entry = {'service1': {'privileged': '${POSINT}'}} 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 '\ 'appropriate type: "50" is not a valid boolean value' in exc.exconly() entry = {'service1': {'cpus': '${TRUE}'}} 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 '\ 'appropriate type: "True" is not a valid float' in exc.exconly() entry = {'service1': {'ulimits': {'nproc': '${FLOAT}'}}} 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 '\ '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 @@ -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 - value = interpolate_environment_variables(V3_4, entry, 'volume', mock_env) + value = interpolate_environment_variables(VERSION, entry, 'volume', mock_env) 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 - value = interpolate_environment_variables(V3_4, entry, 'config', mock_env) + value = interpolate_environment_variables(VERSION, entry, 'config', mock_env) 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 diff --git a/tests/unit/config/types_test.py b/tests/unit/config/types_test.py index c0991b9dc..5271c0d27 100644 --- a/tests/unit/config/types_test.py +++ b/tests/unit/config/types_test.py @@ -6,7 +6,7 @@ from compose.config.types import ServicePort from compose.config.types import VolumeFromSpec from compose.config.types import VolumeSpec from compose.const import COMPOSEFILE_V1 as V1 -from compose.const import COMPOSEFILE_V2_0 as V2_0 +from compose.const import COMPOSEFILE_V4 as VERSION def test_parse_extra_hosts_list(): @@ -233,26 +233,26 @@ class TestVolumesFromSpec(object): VolumeFromSpec.parse('unknown:format:ro', self.services, V1) 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') 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') 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') 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') def test_parse_v2_invalid_type(self): 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() def test_parse_v2_invalid(self): with pytest.raises(ConfigurationError): - VolumeFromSpec.parse('unknown:format:ro', self.services, V2_0) + VolumeFromSpec.parse('unknown:format:ro', self.services, VERSION) diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index 1ad49c1aa..e80d46b2b 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -14,9 +14,7 @@ from compose.config import ConfigurationError from compose.config.config import Config from compose.config.types import VolumeFromSpec 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 COMPOSEFILE_V4 as VERSION from compose.const import DEFAULT_TIMEOUT from compose.const import LABEL_SERVICE from compose.container import Container @@ -29,6 +27,17 @@ from compose.service import ImageType 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): def setUp(self): 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 def test_from_config_v1(self): - config = Config( + config = build_config( version=V1, services=[ { @@ -67,8 +76,7 @@ class ProjectTest(unittest.TestCase): @mock.patch('compose.network.Network.true_name', lambda n: n.full_name) def test_from_config_v2(self): - config = Config( - version=V2_0, + config = build_config( services=[ { 'name': 'web', @@ -174,8 +182,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[{ 'name': 'test', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -202,8 +209,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[ { 'name': 'vol', @@ -230,8 +236,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=None, - config_data=Config( - version=V2_0, + config_data=build_config( services=[ { 'name': 'vol', @@ -540,7 +545,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( + config_data=build_config( version=V1, services=[ { @@ -565,8 +570,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[ { 'name': 'test', @@ -596,8 +600,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[ { 'name': 'aaa', @@ -623,8 +626,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[ { 'name': 'foo', @@ -644,8 +646,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[ { 'name': 'foo', @@ -679,8 +680,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -697,8 +697,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, @@ -748,8 +747,8 @@ class ProjectTest(unittest.TestCase): 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, } - config_data = Config( - version=V2_4, services=[service_config], networks={}, volumes={}, secrets=None, configs=None + config_data = build_config( + services=[service_config], networks={}, volumes={}, secrets=None, configs=None ) 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' def test_build_container_operation_with_timeout_func_does_not_mutate_options_with_timeout(self): - config_data = Config( - version=V3_7, + config_data = build_config( services=[ {'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG}, {'name': 'db', 'image': BUSYBOX_IMAGE_WITH_TAG, 'stop_grace_period': '1s'}, @@ -802,8 +800,7 @@ class ProjectTest(unittest.TestCase): project = Project.from_config( name='test', client=self.mock_client, - config_data=Config( - version=V2_0, + config_data=build_config( services=[{ 'name': 'web', 'image': BUSYBOX_IMAGE_WITH_TAG, From c06b30548d4edbb4612cbe6eccc345c358508821 Mon Sep 17 00:00:00 2001 From: aiordache Date: Wed, 8 Jul 2020 15:02:07 +0200 Subject: [PATCH 2/7] cleanup compatibility and tests Signed-off-by: aiordache --- compose/cli/command.py | 16 +------ compose/config/config.py | 80 +++----------------------------- compose/project.py | 19 ++++++++ tests/unit/config/config_test.py | 2 + 4 files changed, 29 insertions(+), 88 deletions(-) diff --git a/compose/cli/command.py b/compose/cli/command.py index f18f76639..e907a05cc 100644 --- a/compose/cli/command.py +++ b/compose/cli/command.py @@ -66,7 +66,6 @@ def project_from_options(project_dir, options, additional_options=None): context=context, environment=environment, override_dir=override_dir, - compatibility=compatibility_from_options(project_dir, options, environment), interpolate=(not additional_options.get('--no-interpolate')), environment_file=environment_file ) @@ -98,7 +97,6 @@ def get_config_from_options(base_dir, options, additional_options=None): ) return config.load( config.find(base_dir, config_path, environment, override_dir), - compatibility_from_options(config_path, options, environment), 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, context=None, environment=None, override_dir=None, - compatibility=False, interpolate=True, environment_file=None): + interpolate=True, environment_file=None): if not environment: environment = Environment.from_env_file(project_dir) config_details = config.find(project_dir, config_path, environment, override_dir) project_name = get_project_name( 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( 'COMPOSE_API_VERSION', @@ -188,13 +186,3 @@ def get_project_name(working_dir, project_name=None, environment=None): return normalize_name(project) 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 diff --git a/compose/config/config.py b/compose/config/config.py index 1557e52d0..5eed6b59a 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -365,7 +365,7 @@ def find_candidates_in_parent_dirs(filenames, path): return (candidates, path) -def check_swarm_only_config(service_dicts, compatibility=False): +def check_swarm_only_config(service_dicts): warning_template = ( "Some services ({services}) use the '{key}' key, which will be ignored. " "Compose does not support '{key}' configuration - use " @@ -386,7 +386,7 @@ def check_swarm_only_config(service_dicts, compatibility=False): 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 configuration files. Files are loaded in order, and merged on top of each other to create the final configuration. @@ -416,13 +416,13 @@ def load(config_details, compatibility=False, interpolate=True): configs = load_mapping( 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: for service_dict in service_dicts: match_named_volumes(service_dict, volumes) - check_swarm_only_config(service_dicts, compatibility) + check_swarm_only_config(service_dicts) version = main_file.version @@ -469,7 +469,7 @@ def validate_external(entity_type, name, config, version): 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): service_config = ServiceConfig.with_abs_paths( config_details.working_dir, @@ -488,7 +488,6 @@ def load_services(config_details, config_file, compatibility=False, interpolate= service_names, config_file.version, config_details.environment, - compatibility, interpolate ) return service_dict @@ -887,7 +886,7 @@ def finalize_service_volumes(service_dict, environment): return service_dict -def finalize_service(service_config, service_names, version, environment, compatibility, +def finalize_service(service_config, service_names, version, environment, interpolate=True): service_dict = dict(service_config.config) @@ -929,17 +928,6 @@ def finalize_service(service_config, service_names, version, environment, compat 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 return normalize_v1_service_format(service_dict) @@ -973,62 +961,6 @@ def convert_credential_spec_to_security_opt(credential_spec): 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): if 'log_driver' in service_dict or 'log_opt' in service_dict: if 'logging' not in service_dict: diff --git a/compose/project.py b/compose/project.py index 4c8b197f2..216615aee 100644 --- a/compose/project.py +++ b/compose/project.py @@ -123,6 +123,8 @@ class Project(object): service_dict.pop('secrets', None) or [], config_data.secrets) + service_dict['scale'] = project.get_service_scale(service_dict) + project.services.append( Service( service_dict.pop('name'), @@ -262,6 +264,23 @@ class Project(object): 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', 1) + deploy_dict = service_dict.get('deploy', None) + if deploy_dict: + if deploy_dict.get('mode', 'replicated') == 'replicated': + scale = deploy_dict.get('replicas', scale) + # 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): containers = [] diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 612d0f38f..4196d8d83 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -3655,6 +3655,7 @@ class InterpolationTest(unittest.TestCase): assert 'BAR' in warnings[0] assert 'FOO' in warnings[1] + @pytest.mark.skip(reason='compatibility mode was removed internally') def test_compatibility_mode_warnings(self): config_details = build_config_details({ 'version': '3.5', @@ -3693,6 +3694,7 @@ class InterpolationTest(unittest.TestCase): assert 'restart_policy.delay' 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): config_details = build_config_details({ 'version': '3.5', From cb56036a6e7ed1f3e0957032b7c6973a0a08cc02 Mon Sep 17 00:00:00 2001 From: aiordache Date: Thu, 9 Jul 2020 15:07:09 +0200 Subject: [PATCH 3/7] rename schema to compose_spec Signed-off-by: aiordache --- compose/config/config.py | 4 ++-- ...g_schema_v4.0.json => config_schema_compose_spec.json} | 2 +- compose/config/serialize.py | 2 +- compose/config/validation.py | 8 ++++++-- compose/const.py | 6 +++--- docker-compose.spec | 4 ++-- docker-compose_darwin.spec | 4 ++-- tests/acceptance/cli_test.py | 2 +- tests/integration/project_test.py | 2 +- tests/integration/testcases.py | 2 +- tests/unit/config/config_test.py | 2 +- tests/unit/config/interpolation_test.py | 2 +- tests/unit/config/types_test.py | 2 +- tests/unit/project_test.py | 2 +- 14 files changed, 24 insertions(+), 20 deletions(-) rename compose/config/{config_schema_v4.0.json => config_schema_compose_spec.json} (99%) diff --git a/compose/config/config.py b/compose/config/config.py index 5eed6b59a..a3110682c 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -12,8 +12,8 @@ import yaml from cached_property import cached_property from . import types +from ..const import COMPOSE_SPEC as VERSION from ..const import COMPOSEFILE_V1 as V1 -from ..const import COMPOSEFILE_V4 as VERSION from ..utils import build_string_dict from ..utils import json_hash from ..utils import parse_bytes @@ -214,7 +214,7 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')): .format(self.filename)) if isinstance(version, str): - version_pattern = re.compile(r"^[1-4]+(\.\d+)?$") + version_pattern = re.compile(r"^[1-3]+(\.\d+)?$") if not version_pattern.match(version): raise ConfigurationError( 'Version "{}" in "{}" is invalid.' diff --git a/compose/config/config_schema_v4.0.json b/compose/config/config_schema_compose_spec.json similarity index 99% rename from compose/config/config_schema_v4.0.json rename to compose/config/config_schema_compose_spec.json index 316fbf9a0..0400cd866 100644 --- a/compose/config/config_schema_v4.0.json +++ b/compose/config/config_schema_compose_spec.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema#", - "id": "config_schema_v4.0.json", + "id": "config_schema_compose_spec.json", "type": "object", "properties": { "version": { diff --git a/compose/config/serialize.py b/compose/config/serialize.py index a162e3873..e41e1ba4f 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -1,8 +1,8 @@ import yaml 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_V4 as VERSION def serialize_config_type(dumper, data): diff --git a/compose/config/validation.py b/compose/config/validation.py index f52de03de..942c4e036 100644 --- a/compose/config/validation.py +++ b/compose/config/validation.py @@ -282,7 +282,7 @@ def handle_error_for_schema_with_id(error, path): invalid_config_key = parse_key_from_error_msg(error) 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) return ('Invalid top-level property "{key}". Valid top-level ' 'sections for this Compose file are: {properties}, and ' @@ -487,9 +487,13 @@ def get_schema_path(): def load_jsonschema(version): + suffix = "compose_spec" + if version == V1: + suffix = "v1" + filename = os.path.join( get_schema_path(), - "config_schema_v{0}.json".format(version)) + "config_schema_{0}.json".format(suffix)) if not os.path.exists(filename): raise ConfigurationError( diff --git a/compose/const.py b/compose/const.py index 9623769d1..c51e6ac0b 100644 --- a/compose/const.py +++ b/compose/const.py @@ -24,16 +24,16 @@ SECRETS_PATH = '/run/secrets' WINDOWS_LONGPATH_PREFIX = '\\\\?\\' COMPOSEFILE_V1 = ComposeVersion('1') -COMPOSEFILE_V4 = ComposeVersion('4.0') +COMPOSE_SPEC = ComposeVersion('3') # minimum DOCKER ENGINE API version needed to support # features for each compose schema version API_VERSIONS = { COMPOSEFILE_V1: '1.21', - COMPOSEFILE_V4: '1.38', + COMPOSE_SPEC: '1.38', } API_VERSION_TO_ENGINE_VERSION = { API_VERSIONS[COMPOSEFILE_V1]: '1.9.0', - API_VERSIONS[COMPOSEFILE_V4]: '18.06.0', + API_VERSIONS[COMPOSE_SPEC]: '18.06.0', } diff --git a/docker-compose.spec b/docker-compose.spec index 35195f9b4..ff739ab0d 100644 --- a/docker-compose.spec +++ b/docker-compose.spec @@ -23,8 +23,8 @@ exe = EXE(pyz, 'DATA' ), ( - 'compose/config/config_schema_v4.0.json', - 'compose/config/config_schema_v4.0.json', + 'compose/config/config_schema_compose_spec.json', + 'compose/config/config_schema_compose_spec.json', 'DATA' ), ( diff --git a/docker-compose_darwin.spec b/docker-compose_darwin.spec index f4642314e..6f96b29a4 100644 --- a/docker-compose_darwin.spec +++ b/docker-compose_darwin.spec @@ -32,8 +32,8 @@ coll = COLLECT(exe, 'DATA' ), ( - 'compose/config/config_schema_v4.0.json', - 'compose/config/config_schema_v4.0.json', + 'compose/config/config_schema_compose_spec.json', + 'compose/config/config_schema_compose_spec.json', 'DATA' ), ( diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 51f6428be..180c3824f 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -20,8 +20,8 @@ from ..helpers import BUSYBOX_IMAGE_WITH_TAG from ..helpers import create_host_file from compose.cli.command import get_project from compose.config.errors import DuplicateOverrideFileFound +from compose.const import COMPOSE_SPEC as VERSION from compose.const import COMPOSEFILE_V1 as V1 -from compose.const import COMPOSEFILE_V4 as VERSION from compose.container import Container from compose.project import OneOffFilter from compose.utils import nanoseconds_from_time_seconds diff --git a/tests/integration/project_test.py b/tests/integration/project_test.py index cb40dbd43..4848c53ee 100644 --- a/tests/integration/project_test.py +++ b/tests/integration/project_test.py @@ -21,7 +21,7 @@ from compose.config import ConfigurationError from compose.config import types from compose.config.types import VolumeFromSpec from compose.config.types import VolumeSpec -from compose.const import COMPOSEFILE_V4 as VERSION +from compose.const import COMPOSE_SPEC as VERSION from compose.const import LABEL_PROJECT from compose.const import LABEL_SERVICE from compose.container import Container diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index e6de0b921..e84eadc93 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -11,8 +11,8 @@ from compose.cli.docker_client import docker_client from compose.config.config import resolve_environment from compose.config.environment import Environment 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_V4 as VERSION from compose.const import LABEL_PROJECT from compose.progress_stream import stream_output from compose.service import Service diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index 4196d8d83..0e01dc738 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -26,8 +26,8 @@ from compose.config.serialize import denormalize_service_dict from compose.config.serialize import serialize_config from compose.config.serialize import serialize_ns_time_value 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_V4 as VERSION from compose.const import IS_WINDOWS_PLATFORM from tests import mock from tests import unittest diff --git a/tests/unit/config/interpolation_test.py b/tests/unit/config/interpolation_test.py index dfc7beb2e..f8ff8c662 100644 --- a/tests/unit/config/interpolation_test.py +++ b/tests/unit/config/interpolation_test.py @@ -8,7 +8,7 @@ from compose.config.interpolation import Interpolator from compose.config.interpolation import InvalidInterpolation from compose.config.interpolation import TemplateWithDefaults from compose.config.interpolation import UnsetRequiredSubstitution -from compose.const import COMPOSEFILE_V4 as VERSION +from compose.const import COMPOSE_SPEC as VERSION @pytest.fixture diff --git a/tests/unit/config/types_test.py b/tests/unit/config/types_test.py index 5271c0d27..23b9b6767 100644 --- a/tests/unit/config/types_test.py +++ b/tests/unit/config/types_test.py @@ -5,8 +5,8 @@ from compose.config.types import parse_extra_hosts from compose.config.types import ServicePort from compose.config.types import VolumeFromSpec 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_V4 as VERSION def test_parse_extra_hosts_list(): diff --git a/tests/unit/project_test.py b/tests/unit/project_test.py index e80d46b2b..b889e5e2c 100644 --- a/tests/unit/project_test.py +++ b/tests/unit/project_test.py @@ -13,8 +13,8 @@ from ..helpers import BUSYBOX_IMAGE_WITH_TAG from compose.config import ConfigurationError from compose.config.config import Config 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_V4 as VERSION from compose.const import DEFAULT_TIMEOUT from compose.const import LABEL_SERVICE from compose.container import Container From 48232603109e30d8020d2920265f06e83251088d Mon Sep 17 00:00:00 2001 From: aiordache Date: Thu, 9 Jul 2020 18:22:55 +0200 Subject: [PATCH 4/7] align schema with compose-spec Signed-off-by: aiordache --- .../config/config_schema_compose_spec.json | 1006 ++++++----------- 1 file changed, 325 insertions(+), 681 deletions(-) diff --git a/compose/config/config_schema_compose_spec.json b/compose/config/config_schema_compose_spec.json index 0400cd866..dee9826fc 100644 --- a/compose/config/config_schema_compose_spec.json +++ b/compose/config/config_schema_compose_spec.json @@ -1,10 +1,13 @@ { - "$schema": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft/2019-09/schema#", "id": "config_schema_compose_spec.json", "type": "object", + "title": "Compose Specification", + "description": "The Compose file is a YAML file defining a multi-containers based application.", "properties": { "version": { - "type": "string" + "type": "string", + "description": "Version of the Compose specification used. Tools not implementing required version MUST reject the configuration file." }, "services": { "id": "#/properties/services", @@ -56,135 +59,111 @@ "additionalProperties": false } }, - "patternProperties": { - "^x-": {} - }, + "patternProperties": {"^x-": {}}, "additionalProperties": false, "definitions": { "service": { "id": "#/definitions/service", "type": "object", "properties": { - "deploy": { - "$ref": "#/definitions/deployment" - }, + "deploy": {"$ref": "#/definitions/deployment"}, "build": { "oneOf": [ - { - "type": "string" - }, + {"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" - }, - "network": { - "type": "string" - }, - "target": { - "type": "string" - }, - "shm_size": { - "type": [ - "integer", - "string" - ] - } + "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"}, + "network": {"type": "string"}, + "target": {"type": "string"}, + "shm_size": {"type": ["integer", "string"]}, + "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "isolation": {"type": "string"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } ] }, - "cap_add": { - "type": "array", - "items": { - "type": "string" + "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"} + } }, - "uniqueItems": true - }, - "cap_drop": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "cgroup_parent": { - "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" - } - } + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} ] }, "configs": { "type": "array", "items": { "oneOf": [ - { - "type": "string" - }, + {"type": "string"}, { "type": "object", "properties": { - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "uid": { - "type": "string" - }, - "gid": { - "type": "string" - }, - "mode": { - "type": "number" - } - } + "source": {"type": "string"}, + "target": {"type": "string"}, + "uid": {"type": "string"}, + "gid": {"type": "string"}, + "mode": {"type": "number"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} } ] } }, - "container_name": { - "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"}, "credential_spec": { "type": "object", "properties": { - "config": { - "type": "string" - }, - "file": { - "type": "string" - }, - "registry": { - "type": "string" - } + "config": {"type": "string"}, + "file": {"type": "string"}, + "registry": {"type": "string"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "depends_on": { "oneOf": [ @@ -208,67 +187,36 @@ } ] }, - "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" - }, + "device_cgroup_rules": {"$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" - } - } + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} ] }, - "env_file": { - "$ref": "#/definitions/string_or_list" - }, - "environment": { - "$ref": "#/definitions/list_or_dict" - }, + "env_file": {"$ref": "#/definitions/string_or_list"}, + "environment": {"$ref": "#/definitions/list_or_dict"}, + "expose": { "type": "array", "items": { - "type": [ - "string", - "number" - ], + "type": ["string", "number"], "format": "expose" }, "uniqueItems": true }, + "extends": { "oneOf": [ - { - "type": "string" - }, + {"type": "string"}, { "type": "object", - "properties": { "service": {"type": "string"}, "file": {"type": "string"} @@ -278,80 +226,46 @@ } ] }, - "external_links": { + "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "group_add": { "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" - }, - "ipc": { - "type": "string" - }, - "isolation": { - "type": "string" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "links": { - "type": "array", - "items": { - "type": "string" + "type": ["string", "number"] }, "uniqueItems": true }, + "healthcheck": {"$ref": "#/definitions/healthcheck"}, + "hostname": {"type": "string"}, + "image": {"type": "string"}, + "init": {"type": "boolean"}, + "ipc": {"type": "string"}, + "isolation": {"type": "string"}, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, "logging": { "type": "object", "properties": { - "driver": { - "type": "string" - }, + "driver": {"type": "string"}, "options": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number", - "null" - ] - } + "^.+$": {"type": ["string", "number", "null"]} } } }, - "additionalProperties": false - }, - "mac_address": { - "type": "string" + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, + "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": { "oneOf": [ - { - "$ref": "#/definitions/list_of_strings" - }, + {"$ref": "#/definitions/list_of_strings"}, { "type": "object", "patternProperties": { @@ -360,25 +274,16 @@ { "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" - }, + "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 + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, - { - "type": "null" - } + {"type": "null"} ] } }, @@ -388,207 +293,122 @@ }, "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" - ] - }, + "pid": {"type": ["string", "null"]}, + "pids_limit": {"type": ["number", "string"]}, + "platform": {"type": "string"}, "ports": { "type": "array", "items": { "oneOf": [ - { - "type": "number", - "format": "ports" - }, - { - "type": "string", - "format": "ports" - }, + {"type": "number", "format": "ports"}, + {"type": "string", "format": "ports"}, { "type": "object", "properties": { - "mode": { - "type": "string" - }, - "target": { - "type": "integer" - }, - "published": { - "type": "integer" - }, - "protocol": { - "type": "string" - } + "mode": {"type": "string"}, + "target": {"type": "integer"}, + "published": {"type": "integer"}, + "protocol": {"type": "string"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } ] }, "uniqueItems": true }, - "privileged": { - "type": "boolean" - }, - "read_only": { - "type": "boolean" - }, - "restart": { - "type": "string" - }, + "privileged": {"type": "boolean"}, + "pull_policy": {"type": "string", "enum": [ + "always", "never", "if_not_present" + ]}, + "read_only": {"type": "boolean"}, + "restart": {"type": "string"}, "runtime": { + "deprecated": true, "type": "string" }, "scale": { "type": "integer" }, - "security_opt": { - "type": "array", - "items": { - "type": "string" - }, - "uniqueItems": true - }, - "shm_size": { - "type": [ - "number", - "string" - ] - }, + "security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "shm_size": {"type": ["number", "string"]}, "secrets": { "type": "array", "items": { "oneOf": [ - { - "type": "string" - }, + {"type": "string"}, { "type": "object", "properties": { - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "uid": { - "type": "string" - }, - "gid": { - "type": "string" - }, - "mode": { - "type": "number" - } - } + "source": {"type": "string"}, + "target": {"type": "string"}, + "uid": {"type": "string"}, + "gid": {"type": "string"}, + "mode": {"type": "number"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} } ] } }, - "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" - }, + "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": "integer"}, { "type": "object", "properties": { - "hard": { - "type": "integer" - }, - "soft": { - "type": "integer" - } + "hard": {"type": "integer"}, + "soft": {"type": "integer"} }, - "required": [ - "soft", - "hard" - ], - "additionalProperties": false + "required": ["soft", "hard"], + "additionalProperties": false, + "patternProperties": {"^x-": {}} } ] } } }, - "user": { - "type": "string" - }, - "userns_mode": { - "type": "string" - }, + "user": {"type": "string"}, + "userns_mode": {"type": "string"}, "volumes": { "type": "array", "items": { "oneOf": [ - { - "type": "string" - }, + {"type": "string"}, { "type": "object", - "required": [ - "type" - ], + "required": ["type"], "properties": { - "type": { - "type": "string" - }, - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "read_only": { - "type": "boolean" - }, - "consistency": { - "type": "string" - }, + "type": {"type": "string"}, + "source": {"type": "string"}, + "target": {"type": "string"}, + "read_only": {"type": "boolean"}, + "consistency": {"type": "string"}, "bind": { "type": "object", "properties": { - "propagation": { - "type": "string" - } - } + "propagation": {"type": "string"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "volume": { "type": "object", "properties": { - "nocopy": { - "type": "boolean" - } - } + "nocopy": {"type": "boolean"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "tmpfs": { "type": "object", @@ -597,10 +417,13 @@ "type": "integer", "minimum": 0 } - } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } ], "uniqueItems": true @@ -608,136 +431,71 @@ }, "volumes_from": { "type": "array", - "items": { - "type": "string" - }, + "items": {"type": "string"}, "uniqueItems": true }, - "working_dir": { - "type": "string" - } - }, - "patternProperties": { - "^x-": {} + "working_dir": {"type": "string"} }, + "patternProperties": {"^x-": {}}, "additionalProperties": false }, + "healthcheck": { "id": "#/definitions/healthcheck", "type": "object", - "additionalProperties": false, "properties": { - "disable": { - "type": "boolean" - }, - "interval": { - "type": "string", - "format": "duration" - }, - "retries": { - "type": "number" - }, + "disable": {"type": "boolean"}, + "interval": {"type": "string", "format": "duration"}, + "retries": {"type": "number"}, "test": { "oneOf": [ - { - "type": "string" - }, - { - "type": "array", - "items": { - "type": "string" - } - } + {"type": "string"}, + {"type": "array", "items": {"type": "string"}} ] }, - "timeout": { - "type": "string", - "format": "duration" - }, - "start_period": { - "type": "string", - "format": "duration" - } - } + "timeout": {"type": "string", "format": "duration"}, + "start_period": {"type": "string", "format": "duration"} + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "deployment": { "id": "#/definitions/deployment", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "properties": { - "mode": { - "type": "string" - }, - "endpoint_mode": { - "type": "string" - }, - "replicas": { - "type": "integer" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, + "mode": {"type": "string"}, + "endpoint_mode": {"type": "string"}, + "replicas": {"type": "integer"}, + "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" - ] - } + "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 + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "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" - ] - } + "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 + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "resources": { "type": "object", @@ -745,82 +503,60 @@ "limits": { "type": "object", "properties": { - "cpus": { - "type": "string" - }, - "memory": { - "type": "string" - } + "cpus": {"type": "string"}, + "memory": {"type": "string"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "reservations": { "type": "object", "properties": { - "cpus": { - "type": "string" - }, - "memory": { - "type": "string" - }, - "generic_resources": { - "$ref": "#/definitions/generic_resources" - } + "cpus": {"type": "string"}, + "memory": {"type": "string"}, + "generic_resources": {"$ref": "#/definitions/generic_resources"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "restart_policy": { "type": "object", "properties": { - "condition": { - "type": "string" - }, - "delay": { - "type": "string", - "format": "duration" - }, - "max_attempts": { - "type": "integer" - }, - "window": { - "type": "string", - "format": "duration" - } + "condition": {"type": "string"}, + "delay": {"type": "string", "format": "duration"}, + "max_attempts": {"type": "integer"}, + "window": {"type": "string", "format": "duration"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "placement": { "type": "object", "properties": { - "constraints": { - "type": "array", - "items": { - "type": "string" - } - }, + "constraints": {"type": "array", "items": {"type": "string"}}, "preferences": { "type": "array", "items": { "type": "object", "properties": { - "spread": { - "type": "string" - } + "spread": {"type": "string"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, - "max_replicas_per_node": { - "type": "integer" - } + "max_replicas_per_node": {"type": "integer"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "generic_resources": { "id": "#/definitions/generic_resources", @@ -831,248 +567,160 @@ "discrete_resource_spec": { "type": "object", "properties": { - "kind": { - "type": "string" - }, - "value": { - "type": "number" - } + "kind": {"type": "string"}, + "value": {"type": "number"} }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} } }, "network": { "id": "#/definitions/network", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "properties": { - "name": { - "type": "string" - }, - "driver": { - "type": "string" - }, + "name": {"type": "string"}, + "driver": {"type": "string"}, "driver_opts": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number" - ] - } + "^.+$": {"type": ["string", "number"]} } }, "ipam": { "type": "object", "properties": { - "driver": { - "type": "string" - }, + "driver": {"type": "string"}, "config": { "type": "array", "items": { "type": "object", "properties": { - "subnet": { - "type": "string", - "format": "subnet_ip_address" - }, + "subnet": {"type": "string", "format": "subnet_ip_address"}, "ip_range": {"type": "string"}, "gateway": {"type": "string"}, "aux_addresses": { "type": "object", - "patternProperties": { - "^.+$": {"type": "string"} - }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^.+$": {"type": "string"}} } - }, - "additionalProperties": false - } + } + }, + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "options": { "type": "object", - "patternProperties": { - "^.+$": {"type": "string"} - }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^.+$": {"type": "string"}} } }, "additionalProperties": false }, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { "name": { + "deprecated": true, "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, - "internal": { - "type": "boolean" - }, - "attachable": { - "type": "boolean" - }, - "labels": { - "$ref": "#/definitions/list_or_dict" - } + "internal": {"type": "boolean"}, + "attachable": {"type": "boolean"}, + "labels": {"$ref": "#/definitions/list_or_dict"} }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "volume": { "id": "#/definitions/volume", - "type": [ - "object", - "null" - ], + "type": ["object", "null"], "properties": { - "name": { - "type": "string" - }, - "driver": { - "type": "string" - }, + "name": {"type": "string"}, + "driver": {"type": "string"}, "driver_opts": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number" - ] - } + "^.+$": {"type": ["string", "number"]} } }, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { "name": { + "deprecated": true, "type": "string" } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, - "labels": { - "$ref": "#/definitions/list_or_dict" - } + "labels": {"$ref": "#/definitions/list_or_dict"} }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "secret": { "id": "#/definitions/secret", "type": "object", "properties": { - "name": { - "type": "string" - }, - "file": { - "type": "string" - }, + "name": {"type": "string"}, + "file": {"type": "string"}, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { - "name": { - "type": "string" - } + "name": {"type": "string"} } }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "driver": { - "type": "string" - }, + "labels": {"$ref": "#/definitions/list_or_dict"}, + "driver": {"type": "string"}, "driver_opts": { "type": "object", "patternProperties": { - "^.+$": { - "type": [ - "string", - "number" - ] - } + "^.+$": {"type": ["string", "number"]} } }, - "template_driver": { - "type": "string" - } + "template_driver": {"type": "string"} }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "config": { "id": "#/definitions/config", "type": "object", "properties": { - "name": { - "type": "string" - }, - "file": { - "type": "string" - }, + "name": {"type": "string"}, + "file": {"type": "string"}, "external": { - "type": [ - "boolean", - "object" - ], + "type": ["boolean", "object"], "properties": { "name": { + "deprecated": true, "type": "string" } } }, - "labels": { - "$ref": "#/definitions/list_or_dict" - }, - "template_driver": { - "type": "string" - } + "labels": {"$ref": "#/definitions/list_or_dict"}, + "template_driver": {"type": "string"} }, - "patternProperties": { - "^x-": {} - }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "string_or_list": { "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/list_of_strings" - } + {"type": "string"}, + {"$ref": "#/definitions/list_of_strings"} ] }, "list_of_strings": { "type": "array", - "items": { - "type": "string" - }, + "items": {"type": "string"}, "uniqueItems": true }, "list_or_dict": { @@ -1081,44 +729,40 @@ "type": "object", "patternProperties": { ".+": { - "type": [ - "string", - "number", - "null" - ] + "type": ["string", "number", "null"] } }, "additionalProperties": false }, - { - "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": { "service": { "id": "#/definitions/constraints/service", "anyOf": [ - { - "required": [ - "build" - ] - }, - { - "required": [ - "image" - ] - } + {"required": ["build"]}, + {"required": ["image"]} ], "properties": { "build": { - "required": [ - "context" - ] + "required": ["context"] } } } From 945faab54d27924fe78461a29ddb3477135c9165 Mon Sep 17 00:00:00 2001 From: aiordache Date: Fri, 10 Jul 2020 11:39:39 +0200 Subject: [PATCH 5/7] parse deploy.resources Signed-off-by: aiordache --- compose/config/config.py | 29 -------------- compose/project.py | 85 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 29 deletions(-) diff --git a/compose/config/config.py b/compose/config/config.py index a3110682c..c5791e153 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -932,35 +932,6 @@ def finalize_service(service_config, service_names, version, environment, 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 normalize_v1_service_format(service_dict): if 'log_driver' in service_dict or 'log_opt' in service_dict: if 'logging' not in service_dict: diff --git a/compose/project.py b/compose/project.py index 216615aee..c02a1d8dd 100644 --- a/compose/project.py +++ b/compose/project.py @@ -125,6 +125,16 @@ class Project(object): 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( Service( service_dict.pop('name'), @@ -796,6 +806,81 @@ class Project(object): 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): volumes_from = service_dict.pop('volumes_from', None) if not volumes_from: From faedc4aa9e219f0bb9f70096873cf04ee49cf60c Mon Sep 17 00:00:00 2001 From: aiordache Date: Fri, 10 Jul 2020 13:31:12 +0200 Subject: [PATCH 6/7] Add ipam.patternProperties to schema Signed-off-by: aiordache --- compose/config/config_schema_compose_spec.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compose/config/config_schema_compose_spec.json b/compose/config/config_schema_compose_spec.json index dee9826fc..3b26ba825 100644 --- a/compose/config/config_schema_compose_spec.json +++ b/compose/config/config_schema_compose_spec.json @@ -618,7 +618,8 @@ "patternProperties": {"^.+$": {"type": "string"}} } }, - "additionalProperties": false + "additionalProperties": false, + "patternProperties": {"^x-": {}} }, "external": { "type": ["boolean", "object"], From 6cf72381f9359c3040153324540f478c907311b3 Mon Sep 17 00:00:00 2001 From: aiordache Date: Fri, 10 Jul 2020 17:28:02 +0200 Subject: [PATCH 7/7] error out on both scale and deploy.replicas being set Signed-off-by: aiordache --- compose/project.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/compose/project.py b/compose/project.py index c02a1d8dd..af6b9ce0c 100644 --- a/compose/project.py +++ b/compose/project.py @@ -276,19 +276,31 @@ class Project(object): def get_service_scale(self, service_dict): # service.scale for v2 and deploy.replicas for v3 - scale = service_dict.get('scale', 1) + scale = service_dict.get('scale', None) deploy_dict = service_dict.get('deploy', None) - if deploy_dict: - if deploy_dict.get('mode', 'replicated') == 'replicated': - scale = deploy_dict.get('replicas', scale) - # 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)) + 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):