From ab9d208408aa3cb02352b91b4cd0776c0d2ec4ee 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 | 52 ++++++++++++++++++++++++------- cli/compose/loader/loader_test.go | 11 ++++++- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/cli/compose/loader/loader.go b/cli/compose/loader/loader.go index c33bd9d1146d..0e876cfe6ebb 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) { + // Transform pairs of host and IP addr that may be represented as a map, or a list + // of '=' or ':' separated strings, into a list of ':' separated strings. + l := transformListOrMapping(data, ":", false, []string{"=", ":"}) -func transformListOrMappingFunc(sep string, allowNil bool) TransformerFunc { - return func(data any) (any, error) { - return transformListOrMapping(data, sep, allowNil), nil + // Remove brackets from IP addresses if present (for example "[::1]" -> "::1"). + hl := l.([]string) + 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 transformListOrMapping(listOrMapping any, sep string, allowNil bool) any { +func transformListOrMapping(listOrMapping any, sep string, allowNil bool, allowSeps []string) any { 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 94128ac465c4..d1f1ca5ca9f7 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,23 @@ 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]" + - "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", "host.docker.internal:host-gateway", }