From eeeff1cf23dc728a0c8b40222e6ac93861072756 Mon Sep 17 00:00:00 2001 From: Rob Murray Date: Thu, 16 Nov 2023 09:28:42 +0000 Subject: [PATCH] Permit '=' separator and '[ipv6]' in --add-host Fixes docker/cli#4648 Make it easier to specify IPv6 addresses in the '--add-host' option by permitting 'host=ip' in addition to 'host:ip', and allowing square brackets around the address. For example: --add-host=hostname:127.0.0.1 --add-host=hostname:::1 --add-host=hostname=::1 --add-host=hostname=[::1] Signed-off-by: Rob Murray --- build/utils.go | 17 ++++- build/utils_test.go | 148 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 build/utils_test.go diff --git a/build/utils.go b/build/utils.go index ee86ff93..96cb15ad 100644 --- a/build/utils.go +++ b/build/utils.go @@ -65,7 +65,10 @@ func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver. } hosts := make([]string, 0, len(inp)) for _, h := range inp { - host, ip, ok := strings.Cut(h, ":") + host, ip, ok := strings.Cut(h, "=") + if !ok { + host, ip, ok = strings.Cut(h, ":") + } if !ok || host == "" || ip == "" { return "", errors.Errorf("invalid host %s", h) } @@ -77,8 +80,16 @@ func toBuildkitExtraHosts(ctx context.Context, inp []string, nodeDriver *driver. return "", errors.Wrap(err, "unable to derive the IP value for host-gateway") } ip = hgip.String() - } else if net.ParseIP(ip) == nil { - return "", errors.Errorf("invalid host %s", h) + } else { + // If the address is enclosed in square brackets, extract it (for IPv6, but + // permit it for IPv4 as well; we don't know the address family here, but it's + // unambiguous). + if len(ip) > 2 && ip[0] == '[' && ip[len(ip)-1] == ']' { + ip = ip[1 : len(ip)-1] + } + if net.ParseIP(ip) == nil { + return "", errors.Errorf("invalid host %s", h) + } } hosts = append(hosts, host+"="+ip) } diff --git a/build/utils_test.go b/build/utils_test.go new file mode 100644 index 00000000..375c1e7e --- /dev/null +++ b/build/utils_test.go @@ -0,0 +1,148 @@ +package build + +import ( + "context" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestToBuildkitExtraHosts(t *testing.T) { + tests := []struct { + doc string + input []string + expectedOut string // Expect output==input if not set. + expectedErr string // Expect success if not set. + }{ + { + doc: "IPv4, colon sep", + input: []string{`myhost:192.168.0.1`}, + expectedOut: `myhost=192.168.0.1`, + }, + { + doc: "IPv4, eq sep", + input: []string{`myhost=192.168.0.1`}, + }, + { + doc: "Weird but permitted, IPv4 with brackets", + input: []string{`myhost=[192.168.0.1]`}, + expectedOut: `myhost=192.168.0.1`, + }, + { + doc: "Host and domain", + input: []string{`host.and.domain.invalid:10.0.2.1`}, + expectedOut: `host.and.domain.invalid=10.0.2.1`, + }, + { + doc: "IPv6, colon sep", + input: []string{`anipv6host:2003:ab34:e::1`}, + expectedOut: `anipv6host=2003:ab34:e::1`, + }, + { + doc: "IPv6, colon sep, brackets", + input: []string{`anipv6host:[2003:ab34:e::1]`}, + expectedOut: `anipv6host=2003:ab34:e::1`, + }, + { + doc: "IPv6, eq sep, brackets", + input: []string{`anipv6host=[2003:ab34:e::1]`}, + expectedOut: `anipv6host=2003:ab34:e::1`, + }, + { + doc: "IPv6 localhost, colon sep", + input: []string{`ipv6local:::1`}, + expectedOut: `ipv6local=::1`, + }, + { + doc: "IPv6 localhost, eq sep", + input: []string{`ipv6local=::1`}, + }, + { + doc: "IPv6 localhost, eq sep, brackets", + input: []string{`ipv6local=[::1]`}, + expectedOut: `ipv6local=::1`, + }, + { + doc: "IPv6 localhost, non-canonical, colon sep", + input: []string{`ipv6local:0:0:0:0:0:0:0:1`}, + expectedOut: `ipv6local=0:0:0:0:0:0:0:1`, + }, + { + doc: "IPv6 localhost, non-canonical, eq sep", + input: []string{`ipv6local=0:0:0:0:0:0:0:1`}, + }, + { + doc: "IPv6 localhost, non-canonical, eq sep, brackets", + input: []string{`ipv6local=[0:0:0:0:0:0:0:1]`}, + expectedOut: `ipv6local=0:0:0:0:0:0:0:1`, + }, + { + doc: "Bad address, colon sep", + input: []string{`myhost:192.notanipaddress.1`}, + expectedErr: `invalid IP address in add-host: "192.notanipaddress.1"`, + }, + { + doc: "Bad address, eq sep", + input: []string{`myhost=192.notanipaddress.1`}, + expectedErr: `invalid IP address in add-host: "192.notanipaddress.1"`, + }, + { + doc: "No sep", + input: []string{`thathost-nosemicolon10.0.0.1`}, + expectedErr: `bad format for add-host: "thathost-nosemicolon10.0.0.1"`, + }, + { + doc: "Bad IPv6", + input: []string{`anipv6host:::::1`}, + expectedErr: `invalid IP address in add-host: "::::1"`, + }, + { + doc: "Bad IPv6, trailing colons", + input: []string{`ipv6local:::0::`}, + expectedErr: `invalid IP address in add-host: "::0::"`, + }, + { + doc: "Bad IPv6, missing close bracket", + input: []string{`ipv6addr=[::1`}, + expectedErr: `invalid IP address in add-host: "[::1"`, + }, + { + doc: "Bad IPv6, missing open bracket", + input: []string{`ipv6addr=::1]`}, + expectedErr: `invalid IP address in add-host: "::1]"`, + }, + { + doc: "Missing address, colon sep", + input: []string{`myhost.invalid:`}, + expectedErr: `invalid IP address in add-host: ""`, + }, + { + doc: "Missing address, eq sep", + input: []string{`myhost.invalid=`}, + expectedErr: `invalid IP address in add-host: ""`, + }, + { + doc: "No input", + input: []string{``}, + expectedErr: `bad format for add-host: ""`, + }, + } + + for _, tc := range tests { + tc := tc + if tc.expectedOut == "" { + tc.expectedOut = strings.Join(tc.input, ",") + } + t.Run(tc.doc, func(t *testing.T) { + actualOut, actualErr := toBuildkitExtraHosts(context.TODO(), tc.input, nil) + if tc.expectedErr == "" { + require.Equal(t, tc.expectedOut, actualOut) + require.Nil(t, actualErr) + } else { + require.Zero(t, actualOut) + require.Error(t, actualErr, tc.expectedErr) + } + }) + } +}