diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index d2bfcbaa5534..7c98244d8e1e 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -158,6 +158,7 @@ func Service( Placement: &swarm.Placement{ Constraints: service.Deploy.Placement.Constraints, Preferences: getPlacementPreference(service.Deploy.Placement.Preferences), + MaxReplicas: service.Deploy.Placement.MaxReplicas, }, }, EndpointSpec: endpoint, diff --git a/cli/compose/loader/full-example.yml b/cli/compose/loader/full-example.yml index 2dd5799a1991..518c5d8c3dcf 100644 --- a/cli/compose/loader/full-example.yml +++ b/cli/compose/loader/full-example.yml @@ -1,4 +1,4 @@ -version: "3.7" +version: "3.8" services: foo: @@ -82,6 +82,7 @@ services: window: 120s placement: constraints: [node=foo] + max_replicas_per_node: 5 preferences: - spread: node.labels.az endpoint_mode: dnsrr diff --git a/cli/compose/loader/full-struct_test.go b/cli/compose/loader/full-struct_test.go index 758557fcf49b..355c3894479c 100644 --- a/cli/compose/loader/full-struct_test.go +++ b/cli/compose/loader/full-struct_test.go @@ -10,7 +10,7 @@ import ( func fullExampleConfig(workingDir, homeDir string) *types.Config { return &types.Config{ - Version: "3.7", + Version: "3.8", Services: services(workingDir, homeDir), Networks: networks(), Volumes: volumes(), @@ -111,6 +111,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig { }, Placement: types.Placement{ Constraints: []string{"node=foo"}, + MaxReplicas: uint64(5), Preferences: []types.PlacementPreferences{ { Spread: "node.labels.az", @@ -507,7 +508,7 @@ func secrets(workingDir string) map[string]types.SecretConfig { } func fullExampleYAML(workingDir string) string { - return fmt.Sprintf(`version: "3.7" + return fmt.Sprintf(`version: "3.8" services: foo: build: @@ -588,6 +589,7 @@ services: - node=foo preferences: - spread: node.labels.az + max_replicas_per_node: 5 endpoint_mode: dnsrr devices: - /dev/ttyUSB0:/dev/ttyUSB0 @@ -1083,7 +1085,8 @@ func fullExampleJSON(workingDir string) string { { "spread": "node.labels.az" } - ] + ], + "max_replicas_per_node": 5 }, "endpoint_mode": "dnsrr" }, @@ -1383,7 +1386,7 @@ func fullExampleJSON(workingDir string) string { "working_dir": "/code" } }, - "version": "3.7", + "version": "3.8", "volumes": { "another-volume": { "name": "user_specified_name", diff --git a/cli/compose/schema/bindata.go b/cli/compose/schema/bindata.go index dc01e6788fb9..3595e6ed0c97 100644 --- a/cli/compose/schema/bindata.go +++ b/cli/compose/schema/bindata.go @@ -508,6 +508,50 @@ bnBpPlHfjORjkTRf1wyAwiYqMXd9/G6313QfoXs6/sbZ66r6e179PwAA//8ZL3SpvkUAAA== `, }, + "/data/config_schema_v3.8.json": { + local: "data/config_schema_v3.8.json", + size: 17835, + modtime: 1518458244, + compressed: ` +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 +fwAAAP//uQ4+qatFAAA= +`, + }, + "/": { isDir: true, local: "", diff --git a/cli/compose/schema/data/config_schema_v3.8.json b/cli/compose/schema/data/config_schema_v3.8.json new file mode 100644 index 000000000000..a12a40ef8fbc --- /dev/null +++ b/cli/compose/schema/data/config_schema_v3.8.json @@ -0,0 +1,603 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "config_schema_v3.8.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 + } + }, + "max_replicas_per_node": {"type": "integer"} + }, + "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/cli/compose/types/types.go b/cli/compose/types/types.go index e427cdba4209..437289949541 100644 --- a/cli/compose/types/types.go +++ b/cli/compose/types/types.go @@ -346,6 +346,7 @@ type RestartPolicy struct { type Placement struct { Constraints []string `yaml:",omitempty" json:"constraints,omitempty"` Preferences []PlacementPreferences `yaml:",omitempty" json:"preferences,omitempty"` + MaxReplicas uint64 `mapstructure:"max_replicas_per_node" yaml:"max_replicas_per_node,omitempty" json:"max_replicas_per_node,omitempty"` } // PlacementPreferences is the preferences for a service placement