From a7317226529fb53c83cd653f52fd358abee914fc Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Fri, 7 Jun 2024 09:05:56 +0100 Subject: [PATCH 1/2] cli/compose: add schema 3.13 (no changes from 3.12 yet) Signed-off-by: Rob Murray --- cli/compose/loader/full-example.yml | 2 +- cli/compose/loader/full-struct_test.go | 2 +- cli/compose/loader/loader_test.go | 2 +- .../loader/testdata/full-example.json.golden | 2 +- .../loader/testdata/full-example.yaml.golden | 2 +- .../schema/data/config_schema_v3.13.json | 673 ++++++++++++++++++ cli/compose/schema/schema.go | 4 +- cli/compose/schema/schema_test.go | 1 + 8 files changed, 681 insertions(+), 7 deletions(-) create mode 100644 cli/compose/schema/data/config_schema_v3.13.json diff --git a/cli/compose/loader/full-example.yml b/cli/compose/loader/full-example.yml index 76447d203a..542f7706bd 100644 --- a/cli/compose/loader/full-example.yml +++ b/cli/compose/loader/full-example.yml @@ -1,4 +1,4 @@ -version: "3.12" +version: "3.13" services: foo: diff --git a/cli/compose/loader/full-struct_test.go b/cli/compose/loader/full-struct_test.go index f93951ac29..1487e8adc6 100644 --- a/cli/compose/loader/full-struct_test.go +++ b/cli/compose/loader/full-struct_test.go @@ -11,7 +11,7 @@ import ( func fullExampleConfig(workingDir, homeDir string) *types.Config { return &types.Config{ - Version: "3.12", + Version: "3.13", Services: services(workingDir, homeDir), Networks: networks(), Volumes: volumes(), diff --git a/cli/compose/loader/loader_test.go b/cli/compose/loader/loader_test.go index cdeaa6d601..78be36937b 100644 --- a/cli/compose/loader/loader_test.go +++ b/cli/compose/loader/loader_test.go @@ -184,7 +184,7 @@ func strPtr(val string) *string { } var sampleConfig = types.Config{ - Version: "3.12", + Version: "3.13", Services: []types.ServiceConfig{ { Name: "foo", diff --git a/cli/compose/loader/testdata/full-example.json.golden b/cli/compose/loader/testdata/full-example.json.golden index 13c82de2fa..758777d33f 100644 --- a/cli/compose/loader/testdata/full-example.json.golden +++ b/cli/compose/loader/testdata/full-example.json.golden @@ -509,7 +509,7 @@ "working_dir": "/code" } }, - "version": "3.12", + "version": "3.13", "volumes": { "another-volume": { "name": "user_specified_name", diff --git a/cli/compose/loader/testdata/full-example.yaml.golden b/cli/compose/loader/testdata/full-example.yaml.golden index 83e2b8342f..546d1764aa 100644 --- a/cli/compose/loader/testdata/full-example.yaml.golden +++ b/cli/compose/loader/testdata/full-example.yaml.golden @@ -1,4 +1,4 @@ -version: "3.12" +version: "3.13" services: foo: build: diff --git a/cli/compose/schema/data/config_schema_v3.13.json b/cli/compose/schema/data/config_schema_v3.13.json new file mode 100644 index 0000000000..3fd3672204 --- /dev/null +++ b/cli/compose/schema/data/config_schema_v3.13.json @@ -0,0 +1,673 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "id": "config_schema_v3.13.json", + "type": "object", + + "properties": { + "version": { + "type": "string", + "default": "3.13" + }, + + "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"]}, + "extra_hosts": {"$ref": "#/definitions/list_or_dict"} + }, + "additionalProperties": true + } + ] + }, + "cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, + "cgroupns_mode": {"type": "string"}, + "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": { + "config": {"type": "string"}, + "file": {"type": "string"}, + "registry": {"type": "string"} + }, + "additionalProperties": false + }, + "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"}, + "start_interval": {"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"}, + "pids": {"type": "integer"} + }, + "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"}, + "x-cluster-spec": { + "type": "object", + "properties": { + "group": {"type": "string"}, + "access_mode": { + "type": "object", + "properties": { + "scope": {"type": "string"}, + "sharing": {"type": "string"}, + "block_volume": {"type": "object"}, + "mount_volume": { + "type": "object", + "properties": { + "fs_type": {"type": "string"}, + "mount_flags": {"type": "array", "items": {"type": "string"}} + } + } + } + }, + "accessibility_requirements": { + "type": "object", + "properties": { + "requisite": { + "type": "array", + "items": { + "type": "object", + "properties": { + "segments": {"$ref": "#/definitions/list_or_dict"} + } + } + }, + "preferred": { + "type": "array", + "items": { + "type": "object", + "properties": { + "segments": {"$ref": "#/definitions/list_or_dict"} + } + } + } + } + }, + "capacity_range": { + "type": "object", + "properties": { + "required_bytes": {"type": "string"}, + "limit_bytes": {"type": "string"} + } + }, + "availability": {"type": "string"} + } + } + }, + "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"}, + "driver": {"type": "string"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": {"type": ["string", "number"]} + } + }, + "template_driver": {"type": "string"} + }, + "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"}, + "template_driver": {"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} + ] + }, + + "constraints": { + "service": { + "id": "#/definitions/constraints/service", + "anyOf": [ + {"required": ["build"]}, + {"required": ["image"]} + ], + "properties": { + "build": { + "required": ["context"] + } + } + } + } + } +} diff --git a/cli/compose/schema/schema.go b/cli/compose/schema/schema.go index 3ef704afa6..6d11f5d817 100644 --- a/cli/compose/schema/schema.go +++ b/cli/compose/schema/schema.go @@ -14,7 +14,7 @@ import ( ) const ( - defaultVersion = "3.12" + defaultVersion = "3.13" versionField = "version" ) @@ -43,7 +43,7 @@ func init() { } // Version returns the version of the config, defaulting to the latest "3.x" -// version (3.12). If only the major version "3" is specified, it is used as +// version (3.13). If only the major version "3" is specified, it is used as // version "3.x" and returns the default version (latest 3.x). func Version(config map[string]any) string { version, ok := config[versionField] diff --git a/cli/compose/schema/schema_test.go b/cli/compose/schema/schema_test.go index 01fb98508d..d3de1b9a71 100644 --- a/cli/compose/schema/schema_test.go +++ b/cli/compose/schema/schema_test.go @@ -104,6 +104,7 @@ func TestValidateCredentialSpecs(t *testing.T) { {version: "3.10"}, {version: "3.11"}, {version: "3.12"}, + {version: "3.13"}, {version: "3"}, {version: ""}, } From 94f9de59280a6147516ed0fcfc915ebf9eb0e563 Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Fri, 7 Jun 2024 09:13:21 +0100 Subject: [PATCH 2/2] Handle networks.driver_opts for a service These are endpoint-specific driver options... services: myservice: image: myimage networks: mynet: driver_opts: "option1": "value1" The API has had support for a long time, it's only recently been added to compose (unreleased right now). Signed-off-by: Rob Murray --- cli/compose/convert/service.go | 7 +++++-- cli/compose/convert/service_test.go | 8 ++++++++ cli/compose/loader/full-example.yml | 3 +++ cli/compose/loader/full-struct_test.go | 4 ++++ cli/compose/loader/testdata/full-example.json.golden | 6 +++++- cli/compose/loader/testdata/full-example.yaml.golden | 3 +++ cli/compose/schema/data/config_schema_v3.13.json | 6 ++++++ cli/compose/types/types.go | 7 ++++--- 8 files changed, 38 insertions(+), 6 deletions(-) diff --git a/cli/compose/convert/service.go b/cli/compose/convert/service.go index 4cc4e4a143..ccff547ce9 100644 --- a/cli/compose/convert/service.go +++ b/cli/compose/convert/service.go @@ -215,16 +215,19 @@ func convertServiceNetworks( return nil, errors.Errorf("undefined network %q", networkName) } var aliases []string + var driverOpts map[string]string if network != nil { aliases = network.Aliases + driverOpts = network.DriverOpts } target := namespace.Scope(networkName) if networkConfig.Name != "" { target = networkConfig.Name } netAttachConfig := swarm.NetworkAttachmentConfig{ - Target: target, - Aliases: aliases, + Target: target, + Aliases: aliases, + DriverOpts: driverOpts, } // Only add default aliases to user defined networks. Other networks do // not support aliases. diff --git a/cli/compose/convert/service_test.go b/cli/compose/convert/service_test.go index c4a01d8bf2..22f585f87c 100644 --- a/cli/compose/convert/service_test.go +++ b/cli/compose/convert/service_test.go @@ -240,6 +240,10 @@ func TestConvertServiceNetworks(t *testing.T) { networks := map[string]*composetypes.ServiceNetworkConfig{ "front": { Aliases: []string{"something"}, + DriverOpts: map[string]string{ + "driver.opt1": "optval1", + "driver.opt2": "optval2", + }, }, "back": { Aliases: []string{"other"}, @@ -257,6 +261,10 @@ func TestConvertServiceNetworks(t *testing.T) { { Target: "fronttier", Aliases: []string{"something", "service"}, + DriverOpts: map[string]string{ + "driver.opt1": "optval1", + "driver.opt2": "optval2", + }, }, } diff --git a/cli/compose/loader/full-example.yml b/cli/compose/loader/full-example.yml index 542f7706bd..36ebf833e7 100644 --- a/cli/compose/loader/full-example.yml +++ b/cli/compose/loader/full-example.yml @@ -207,6 +207,9 @@ services: aliases: - alias1 - alias3 + driver_opts: + "driveropt1": "optval1" + "driveropt2": "optval2" other-network: ipv4_address: 172.16.238.10 ipv6_address: 2001:3984:3989::10 diff --git a/cli/compose/loader/full-struct_test.go b/cli/compose/loader/full-struct_test.go index 1487e8adc6..cd3304eff3 100644 --- a/cli/compose/loader/full-struct_test.go +++ b/cli/compose/loader/full-struct_test.go @@ -190,6 +190,10 @@ func services(workingDir, homeDir string) []types.ServiceConfig { Aliases: []string{"alias1", "alias3"}, Ipv4Address: "", Ipv6Address: "", + DriverOpts: map[string]string{ + "driveropt1": "optval1", + "driveropt2": "optval2", + }, }, "other-network": { Ipv4Address: "172.16.238.10", diff --git a/cli/compose/loader/testdata/full-example.json.golden b/cli/compose/loader/testdata/full-example.json.golden index 758777d33f..c0ef39dabe 100644 --- a/cli/compose/loader/testdata/full-example.json.golden +++ b/cli/compose/loader/testdata/full-example.json.golden @@ -285,7 +285,11 @@ "aliases": [ "alias1", "alias3" - ] + ], + "driver_opts": { + "driveropt1": "optval1", + "driveropt2": "optval2" + } } }, "pid": "host", diff --git a/cli/compose/loader/testdata/full-example.yaml.golden b/cli/compose/loader/testdata/full-example.yaml.golden index 546d1764aa..ec925790ad 100644 --- a/cli/compose/loader/testdata/full-example.yaml.golden +++ b/cli/compose/loader/testdata/full-example.yaml.golden @@ -152,6 +152,9 @@ services: aliases: - alias1 - alias3 + driver_opts: + driveropt1: optval1 + driveropt2: optval2 pid: host ports: - mode: ingress diff --git a/cli/compose/schema/data/config_schema_v3.13.json b/cli/compose/schema/data/config_schema_v3.13.json index 3fd3672204..0a85b3e6c5 100644 --- a/cli/compose/schema/data/config_schema_v3.13.json +++ b/cli/compose/schema/data/config_schema_v3.13.json @@ -197,6 +197,12 @@ "type": "object", "properties": { "aliases": {"$ref": "#/definitions/list_of_strings"}, + "driver_opts": { + "type": "object", + "patternProperties": { + "^.+$": { "type": ["string", "number"] } + } + }, "ipv4_address": {"type": "string"}, "ipv6_address": {"type": "string"} }, diff --git a/cli/compose/types/types.go b/cli/compose/types/types.go index f6954ff7f0..f2f68641d3 100644 --- a/cli/compose/types/types.go +++ b/cli/compose/types/types.go @@ -374,9 +374,10 @@ type PlacementPreferences struct { // ServiceNetworkConfig is the network configuration for a service type ServiceNetworkConfig struct { - Aliases []string `yaml:",omitempty" json:"aliases,omitempty"` - Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"` - Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"` + Aliases []string `yaml:",omitempty" json:"aliases,omitempty"` + DriverOpts map[string]string `mapstructure:"driver_opts" yaml:"driver_opts,omitempty" json:"driver_opts,omitempty"` + Ipv4Address string `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"` + Ipv6Address string `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"` } // ServicePortConfig is the port configuration for a service