From c986d09bca1393ee8da5c34f81947052b1af0182 Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Wed, 7 Feb 2024 14:55:01 +0000 Subject: [PATCH] docker stack: allow '=' separator in extra_hosts extra_hosts in the compose file format allows '=' as a separator, and brackets around IP addresses, the engine API doesn't. So, transform the values when reading a compose file for 'docker stack'. Signed-off-by: Rob Murray --- cli/compose/loader/loader.go | 54 ++++++++++++++++++++++++------- cli/compose/loader/loader_test.go | 13 +++++++- 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index c33bd9d114..84090075a7 100644 --- a/cli/compose/loader/loader.go +++ b/cli/compose/loader/loader.go @@ -328,7 +328,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec reflect.TypeOf(types.MappingWithEquals{}): transformMappingOrListFunc("=", true), reflect.TypeOf(types.Labels{}): transformMappingOrListFunc("=", false), reflect.TypeOf(types.MappingWithColon{}): transformMappingOrListFunc(":", false), - reflect.TypeOf(types.HostsList{}): transformListOrMappingFunc(":", false), + reflect.TypeOf(types.HostsList{}): transformHostsList, reflect.TypeOf(types.ServiceVolumeConfig{}): transformServiceVolumeConfig, reflect.TypeOf(types.BuildConfig{}): transformBuildConfig, reflect.TypeOf(types.Duration(0)): transformStringToDuration, @@ -808,28 +808,58 @@ var transformStringList TransformerFunc = func(data any) (any, error) { } } -func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc { - return func(data any) (any, error) { - return transformMappingOrList(data, sep, allowNil), nil +var transformHostsList TransformerFunc = func(data any) (any, error) { + hl := transformListOrMapping(data, ":", false, []string{"=", ":"}) + + // Remove brackets from IP addresses if present (for example "[::1]" -> "::1"). + result := make([]string, 0, len(hl)) + for _, hip := range hl { + host, ip, _ := strings.Cut(hip, ":") + if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' { + ip = ip[1 : len(ip)-1] + } + result = append(result, fmt.Sprintf("%s:%s", host, ip)) } + return result, nil } -func transformListOrMappingFunc(sep string, allowNil bool) TransformerFunc { - return func(data any) (any, error) { - return transformListOrMapping(data, sep, allowNil), nil - } -} - -func transformListOrMapping(listOrMapping any, sep string, allowNil bool) any { +// transformListOrMapping transforms pairs of strings that may be represented as +// a map, or a list of '=' or ':' separated strings, into a list of ':' separated +// strings. +func transformListOrMapping(listOrMapping any, sep string, allowNil bool, allowSeps []string) []string { switch value := listOrMapping.(type) { case map[string]any: return toStringList(value, sep, allowNil) case []any: - return listOrMapping + result := make([]string, 0, len(value)) + for _, entry := range value { + for i, allowSep := range allowSeps { + entry := fmt.Sprint(entry) + k, v, ok := strings.Cut(entry, allowSep) + if ok { + // Entry uses this allowed separator. Add it to the result, using + // sep as a separator. + result = append(result, fmt.Sprintf("%s%s%s", k, sep, v)) + break + } else if i == len(allowSeps)-1 { + // No more separators to try, keep the entry if allowNil. + if allowNil { + result = append(result, k) + } + } + } + } + return result } panic(errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping)) } +func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc { + return func(data any) (any, error) { + return transformMappingOrList(data, sep, allowNil), nil + } +} + func transformMappingOrList(mappingOrList any, sep string, allowNil bool) any { switch values := mappingOrList.(type) { case map[string]any: diff --git a/cli/compose/loader/loader_test.go b/cli/compose/loader/loader_test.go index 94128ac465..cdeaa6d601 100644 --- a/cli/compose/loader/loader_test.go +++ b/cli/compose/loader/loader_test.go @@ -1302,12 +1302,14 @@ services: extra_hosts: "zulu": "162.242.195.82" "alpha": "50.31.209.229" + "beta": "[fd20:f8a7:6e5b::2]" "host.docker.internal": "host-gateway" `) assert.NilError(t, err) expected := types.HostsList{ "alpha:50.31.209.229", + "beta:fd20:f8a7:6e5b::2", "host.docker.internal:host-gateway", "zulu:162.242.195.82", } @@ -1324,16 +1326,25 @@ services: image: busybox extra_hosts: - "zulu:162.242.195.82" + - "whiskey=162.242.195.83" - "alpha:50.31.209.229" - "zulu:ff02::1" - - "host.docker.internal:host-gateway" + - "whiskey=ff02::2" + - "foxtrot=[ff02::3]" + - "bravo:[ff02::4]" + - "host.docker.internal=host-gateway" + - "noaddress" `) assert.NilError(t, err) expected := types.HostsList{ "zulu:162.242.195.82", + "whiskey:162.242.195.83", "alpha:50.31.209.229", "zulu:ff02::1", + "whiskey:ff02::2", + "foxtrot:ff02::3", + "bravo:ff02::4", "host.docker.internal:host-gateway", }