diff --git a/pkg/client/host.go b/pkg/client/host.go index b0382418..36d4160f 100644 --- a/pkg/client/host.go +++ b/pkg/client/host.go @@ -28,10 +28,10 @@ import ( "net" "regexp" goruntime "runtime" - "strings" l "github.com/rancher/k3d/v5/pkg/logger" "github.com/rancher/k3d/v5/pkg/runtimes" + "github.com/rancher/k3d/v5/pkg/runtimes/docker" k3d "github.com/rancher/k3d/v5/pkg/types" "github.com/rancher/k3d/v5/pkg/util" ) @@ -64,15 +64,11 @@ func GetHostIP(ctx context.Context, runtime runtimes.Runtime, cluster *k3d.Clust l.Log().Tracef("GOOS: %s / Runtime OS: %s (%s)", goruntime.GOOS, rtimeInfo.OSType, rtimeInfo.OS) - isDockerDesktop := func(os string) bool { - return strings.ToLower(os) == "docker desktop" - } - // Docker Runtime if runtime == runtimes.Docker { // Docker (for Desktop) on MacOS or Windows - if isDockerDesktop(rtimeInfo.OS) { + if docker.IsDockerDesktop(rtimeInfo.OS) { toolsNode, err := EnsureToolsNode(ctx, runtime, cluster) if err != nil { diff --git a/pkg/runtimes/docker/docker.go b/pkg/runtimes/docker/docker.go index cf242f99..b81c859b 100644 --- a/pkg/runtimes/docker/docker.go +++ b/pkg/runtimes/docker/docker.go @@ -23,6 +23,7 @@ THE SOFTWARE. package docker import ( + "net" "net/url" "os" @@ -42,13 +43,43 @@ func (d Docker) ID() string { // GetHost returns the docker daemon host func (d Docker) GetHost() string { + // a) DOCKER_HOST env var dockerHost := os.Getenv("DOCKER_HOST") + if dockerHost == "" { + l.Log().Traceln("[Docker] GetHost: DOCKER_HOST empty/unset") + info, err := d.Info() + if err != nil { + l.Log().Errorf("[Docker] error getting runtime information: %v", err) + return "" + } + // b) Docker for Desktop (Win/Mac) and it's a local connection + if IsDockerDesktop(info.OS) && IsLocalConnection(info.Endpoint) { + // b.1) local DfD connection, but inside WSL, where host.docker.internal resolves to an IP, but it's not reachable + if _, ok := os.LookupEnv("WSL_DISTRO_NAME"); ok { + l.Log().Debugln("[Docker] wanted to use 'host.docker.internal' as docker host, but it's not reachable in WSL2") + return "" + } + l.Log().Debugln("[Docker] Local DfD: using 'host.docker.internal'") + dockerHost = "host.docker.internal" + if _, err := net.LookupHost(dockerHost); err != nil { + l.Log().Debugf("[Docker] wanted to use 'host.docker.internal' as docker host, but it's not resolvable locally: %v", err) + return "" + } + } + } url, err := url.Parse(dockerHost) if err != nil { + l.Log().Debugf("[Docker] GetHost: error parsing '%s' as URL: %#v", dockerHost, url) return "" } - l.Log().Debugf("DockerHost: %s", url.Host) - return url.Host + dockerHost = url.Host + // apparently, host.docker.internal is not parsed as host but + if dockerHost == "" && url.String() != "" { + dockerHost = url.String() + } + l.Log().Debugf("[Docker] DockerHost: '%s' (%+v)", dockerHost, url) + + return dockerHost } // GetRuntimePath returns the path of the docker socket diff --git a/pkg/runtimes/docker/util.go b/pkg/runtimes/docker/util.go index f9d34bd9..42eea95e 100644 --- a/pkg/runtimes/docker/util.go +++ b/pkg/runtimes/docker/util.go @@ -28,6 +28,8 @@ import ( "fmt" "io" "os" + "regexp" + "strings" "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/flags" @@ -42,6 +44,22 @@ import ( "github.com/spf13/pflag" ) +func IsDockerDesktop(os string) bool { + return strings.ToLower(os) == "docker desktop" +} + +/* + * Simple Matching to detect local connection: + * - file (socket): starts with / (absolute path) + * - tcp://(localhost|127.0.0.1) + * - ssh://(localhost|127.0.0.1) + */ +var LocalConnectionRegexp = regexp.MustCompile(`^(/|((tcp|ssh)://(localhost|127\.0\.0\.1))).*`) + +func IsLocalConnection(endpoint string) bool { + return LocalConnectionRegexp.Match([]byte(endpoint)) +} + // GetDefaultObjectLabelsFilter returns docker type filters created from k3d labels func GetDefaultObjectLabelsFilter(clusterName string) filters.Args { filters := filters.NewArgs()