diff --git a/loader/loader_test.go b/loader/loader_test.go index 01ae3ebe..cba477f6 100644 --- a/loader/loader_test.go +++ b/loader/loader_test.go @@ -3377,3 +3377,35 @@ services: }, }) } + +func TestLoadExtraHostsRepeated(t *testing.T) { + p, err := loadYAML(` +name: load-extra-hosts +services: + test: + extra_hosts: + - "myhost=0.0.0.1,0.0.0.2" +`) + assert.NilError(t, err) + hosts := p.Services["test"].ExtraHosts + assert.DeepEqual(t, hosts, types.HostsList{ + "myhost": []string{"0.0.0.1", "0.0.0.2"}, + }) +} + +func TestLoadExtraHostsLongSyntax(t *testing.T) { + p, err := loadYAML(` +name: load-extra-hosts +services: + test: + extra_hosts: + myhost: + - "0.0.0.1" + - "0.0.0.2" +`) + assert.NilError(t, err) + hosts := p.Services["test"].ExtraHosts + assert.DeepEqual(t, hosts, types.HostsList{ + "myhost": []string{"0.0.0.1", "0.0.0.2"}, + }) +} diff --git a/override/merge_build_test.go b/override/merge_build_test.go index 8f0c057c..c1411660 100644 --- a/override/merge_build_test.go +++ b/override/merge_build_test.go @@ -82,6 +82,7 @@ services: - "registry/username/myrepos:my-other-tag" extra_hosts: - "somehost=162.242.195.82" + - "otherhost=50.31.209.229" - "otherhost=50.31.209.230" - "myhostv6=::1" `) diff --git a/override/uncity.go b/override/uncity.go index 9d1b895a..3b0c63d3 100644 --- a/override/uncity.go +++ b/override/uncity.go @@ -36,7 +36,6 @@ func init() { unique["services.*.annotations"] = keyValueIndexer unique["services.*.build.args"] = keyValueIndexer unique["services.*.build.additional_contexts"] = keyValueIndexer - unique["services.*.build.extra_hosts"] = keyValueIndexer unique["services.*.build.platform"] = keyValueIndexer unique["services.*.build.tags"] = keyValueIndexer unique["services.*.build.labels"] = keyValueIndexer @@ -51,7 +50,6 @@ func init() { unique["services.*.environment"] = keyValueIndexer unique["services.*.env_file"] = envFileIndexer unique["services.*.expose"] = exposeIndexer - unique["services.*.extra_hosts"] = keyValueIndexer unique["services.*.labels"] = keyValueIndexer unique["services.*.links"] = keyValueIndexer unique["services.*.networks.*.aliases"] = keyValueIndexer @@ -109,16 +107,16 @@ func enforceUnicity(value any, p tree.Path) (any, error) { return value, nil } -func keyValueIndexer(y any, p tree.Path) (string, error) { - switch value := y.(type) { +func keyValueIndexer(v any, p tree.Path) (string, error) { + switch value := v.(type) { case string: key, _, found := strings.Cut(value, "=") - if !found { - return value, nil + if found { + return key, nil } - return key, nil + return value, nil default: - return "", fmt.Errorf("%s: unexpected type %T", p, y) + return "", fmt.Errorf("%s: unexpected type %T", p, v) } } diff --git a/schema/compose-spec.json b/schema/compose-spec.json index cb4f89e0..df3cf320 100644 --- a/schema/compose-spec.json +++ b/schema/compose-spec.json @@ -115,7 +115,7 @@ "pull": {"type": ["boolean", "string"]}, "target": {"type": "string"}, "shm_size": {"type": ["integer", "string"]}, - "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "extra_hosts": {"$ref": "#/definitions/extra_hosts"}, "isolation": {"type": "string"}, "privileged": {"type": ["boolean", "string"]}, "secrets": {"$ref": "#/definitions/service_config_or_secret"}, @@ -267,7 +267,7 @@ ] }, "external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true}, - "extra_hosts": {"$ref": "#/definitions/list_or_dict"}, + "extra_hosts": {"$ref": "#/definitions/extra_hosts"}, "group_add": { "type": "array", "items": { @@ -872,6 +872,21 @@ ] }, + "extra_hosts": { + "oneOf": [ + { + "type": "object", + "patternProperties": { + ".+": { + "type": ["string", "array"] + } + }, + "additionalProperties": false + }, + {"type": "array", "items": {"type": "string"}, "uniqueItems": true} + ] + }, + "blkio_limit": { "type": "object", "properties": { diff --git a/types/hostList.go b/types/hostList.go index e224b816..9bc0fbc5 100644 --- a/types/hostList.go +++ b/types/hostList.go @@ -36,9 +36,9 @@ func NewHostsList(hosts []string) (HostsList, error) { if ok { // Mapping found with this separator, stop here. if ips, ok := list[host]; ok { - list[host] = append(ips, ip) + list[host] = append(ips, strings.Split(ip, ",")...) } else { - list[host] = []string{ip} + list[host] = strings.Split(ip, ",") } found = true break @@ -89,7 +89,18 @@ func (h *HostsList) DecodeMapstructure(value interface{}) error { if e == nil { e = "" } - list[i] = []string{fmt.Sprint(e)} + switch t := e.(type) { + case string: + list[i] = []string{t} + case []any: + hosts := make([]string, len(t)) + for j, h := range t { + hosts[j] = fmt.Sprint(h) + } + list[i] = hosts + default: + return fmt.Errorf("unexpected value type %T for extra_hosts entry", value) + } } err := list.cleanup() if err != nil { @@ -109,7 +120,7 @@ func (h *HostsList) DecodeMapstructure(value interface{}) error { *h = list return nil default: - return fmt.Errorf("unexpected value type %T for mapping", value) + return fmt.Errorf("unexpected value type %T for extra_hosts", value) } } diff --git a/types/hostList_test.go b/types/hostList_test.go index b32e3503..2b0264ff 100644 --- a/types/hostList_test.go +++ b/types/hostList_test.go @@ -107,6 +107,13 @@ func testHostsList(t *testing.T, sep string) { }, expectedOut: "foo:127.0.0.2 foo:ff02::1", }, + { + doc: "list of values", + input: []string{ + "foo=127.0.0.2,127.0.0.3", + }, + expectedOut: "foo:127.0.0.2 foo:127.0.0.3", + }, } for _, tc := range testCases {