From 98708c341d36ef40a38cf54c74b95314b875c92f Mon Sep 17 00:00:00 2001 From: Kern Walster Date: Wed, 4 Sep 2024 13:15:03 -0700 Subject: [PATCH] feat(macOS): Support host DNS aliases for macos This change adds: 1) `host.finch.internal` 2) `host.docker.internal` as DNS names that are aliased to the host's loopback address. This allows service and containers inside the VM to connect to services bound to the host's loopback device. Signed-off-by: Kern Walster --- e2e/vm/soci_remote_test.go | 20 +------- e2e/vm/vm_darwin_test.go | 1 + e2e/vm/vm_network_test.go | 99 ++++++++++++++++++++++++++++++++++++++ e2e/vm/vm_util_test.go | 38 +++++++++++++++ finch.yaml.d/mac.yaml | 5 ++ go.mod | 2 +- 6 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 e2e/vm/vm_network_test.go create mode 100644 e2e/vm/vm_util_test.go diff --git a/e2e/vm/soci_remote_test.go b/e2e/vm/soci_remote_test.go index 2c3cab3ae..f060bbb00 100644 --- a/e2e/vm/soci_remote_test.go +++ b/e2e/vm/soci_remote_test.go @@ -8,8 +8,6 @@ package vm import ( "fmt" "os" - "os/exec" - "path/filepath" "runtime" "strings" @@ -31,29 +29,15 @@ const ( var testSoci = func(o *option.Option, installed bool) { ginkgo.Describe("SOCI", func() { var limactlO *option.Option - var fpath, realFinchPath, limactlPath, limaHomePathEnv, wd, vmType string + var vmType string var err error var port int ginkgo.BeforeEach(func() { // Find lima paths. limactl is used to shell into the Finch VM and verify // mounted snapshots match the expected SOCI snapshotter behavior. - if installed { - fpath, err = exec.LookPath("finch") - gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - realFinchPath, err = filepath.EvalSymlinks(fpath) - gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - limactlPath = filepath.Join(realFinchPath, "../../lima/bin/limactl") - limaHomePathEnv = "LIMA_HOME=" + filepath.Join(realFinchPath, "../../lima/data") - } else { - wd, err = os.Getwd() - gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) - limactlPath = filepath.Join(wd, "../../_output/lima/bin/limactl") - limaHomePathEnv = "LIMA_HOME=" + filepath.Join(wd, "../../_output/lima/data") - } - limactlO, err = option.New([]string{limactlPath}, - option.Env([]string{limaHomePathEnv})) + limactlO, err = limaCtlOpt(installed) gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) if runtime.GOOS == "windows" { vmType = "wsl2" diff --git a/e2e/vm/vm_darwin_test.go b/e2e/vm/vm_darwin_test.go index a928c5bec..9354e727d 100644 --- a/e2e/vm/vm_darwin_test.go +++ b/e2e/vm/vm_darwin_test.go @@ -70,6 +70,7 @@ func TestVM(t *testing.T) { testSupportBundle(o) testCredHelper(o, *e2e.Installed, *e2e.Registry) testSoci(o, *e2e.Installed) + testVMNetwork(o, *e2e.Installed) }) gomega.RegisterFailHandler(ginkgo.Fail) diff --git a/e2e/vm/vm_network_test.go b/e2e/vm/vm_network_test.go new file mode 100644 index 000000000..7c41904c3 --- /dev/null +++ b/e2e/vm/vm_network_test.go @@ -0,0 +1,99 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//go:build darwin + +package vm + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/onsi/ginkgo/v2" + "github.com/onsi/gomega" + "github.com/onsi/gomega/gbytes" + "github.com/runfinch/common-tests/command" + "github.com/runfinch/common-tests/option" + "golang.org/x/sync/errgroup" +) + +const ( + addr = "localhost" + port = "8888" + responseBody = "ack" + finchHost = "host.finch.internal" + dockerHost = "host.docker.internal" +) + +func hostWithPort(host, port string) string { + return fmt.Sprintf("%s:%s", host, port) +} + +func vmDNSValidationCommand(host, port string) []string { + // The finch rootfs has curl but not wget + return []string{ + "shell", + "finch", + "curl", + "--silent", + "--connect-timeout", "1", + hostWithPort(host, port), + } +} + +func containerDNSValidationCommand(host, port string) []string { + // The container rootfs has wget, but not curl + return []string{ + "run", + "public.ecr.aws/docker/library/alpine:latest", + "wget", + "-O", "-", // output to stdout + "-q", // quiet + "-T", "1", // read timeout + hostWithPort(host, port), + } +} + +func testVMNetwork(finchO *option.Option, installed bool) { + limaCtlO, err := limaCtlOpt(installed) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + eg, _ := errgroup.WithContext(context.Background()) + var srv *http.Server + ginkgo.Describe("vm networking", func() { + ginkgo.BeforeEach(func() { + srv = &http.Server{ + Addr: hostWithPort(addr, port), + ReadHeaderTimeout: 1 * time.Second, + Handler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, err := w.Write([]byte(responseBody)) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + }), + } + eg.Go(srv.ListenAndServe) + }) + ginkgo.AfterEach(func() { + err := srv.Shutdown(context.Background()) + gomega.Expect(err).ShouldNot(gomega.HaveOccurred()) + err = eg.Wait() + gomega.Expect(err).To(gomega.MatchError(http.ErrServerClosed)) + }) + ginkgo.It("should resolve host.finch.internal in the vm", func() { + out := command.New(limaCtlO, vmDNSValidationCommand(finchHost, port)...).WithStdout(gbytes.NewBuffer()).Run().Out + gomega.Expect(out).To(gbytes.Say(responseBody)) + }) + ginkgo.It("should resolve host.docker.internal in the vm", func() { + out := command.New(limaCtlO, vmDNSValidationCommand(dockerHost, port)...).WithStdout(gbytes.NewBuffer()).Run().Out + gomega.Expect(out).To(gbytes.Say(responseBody)) + }) + ginkgo.It("should resolve host.finch.internal in a container", func() { + out := command.New(finchO, containerDNSValidationCommand(finchHost, port)...).WithStdout(gbytes.NewBuffer()).Run().Out + gomega.Expect(out).To(gbytes.Say(responseBody)) + }) + ginkgo.It("should resolve host.docker.internal in a container", func() { + out := command.New(finchO, containerDNSValidationCommand(dockerHost, port)...).WithStdout(gbytes.NewBuffer()).Run().Out + gomega.Expect(out).To(gbytes.Say(responseBody)) + }) + }) +} diff --git a/e2e/vm/vm_util_test.go b/e2e/vm/vm_util_test.go new file mode 100644 index 000000000..343d0134e --- /dev/null +++ b/e2e/vm/vm_util_test.go @@ -0,0 +1,38 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//go:build darwin || windows + +package vm + +import ( + "os" + "os/exec" + "path/filepath" + + "github.com/runfinch/common-tests/option" +) + +func limaCtlOpt(installed bool) (*option.Option, error) { + var limactlPath, limaHomePathEnv string + if installed { + fpath, err := exec.LookPath("finch") + if err != nil { + return nil, err + } + realFinchPath, err := filepath.EvalSymlinks(fpath) + if err != nil { + return nil, err + } + limactlPath = filepath.Join(realFinchPath, "../../lima/bin/limactl") + limaHomePathEnv = "LIMA_HOME=" + filepath.Join(realFinchPath, "../../lima/data") + } else { + wd, err := os.Getwd() + if err != nil { + return nil, err + } + limactlPath = filepath.Join(wd, "../../_output/lima/bin/limactl") + limaHomePathEnv = "LIMA_HOME=" + filepath.Join(wd, "../../_output/lima/data") + } + return option.New([]string{limactlPath}, option.Env([]string{limaHomePathEnv})) +} diff --git a/finch.yaml.d/mac.yaml b/finch.yaml.d/mac.yaml index 3264e38bc..56f80ad35 100644 --- a/finch.yaml.d/mac.yaml +++ b/finch.yaml.d/mac.yaml @@ -39,3 +39,8 @@ firmware: video: display: "none" + +hostResolver: + hosts: + host.finch.internal: host.lima.internal + host.docker.internal: host.lima.internal diff --git a/go.mod b/go.mod index 58b2fb2aa..05697edb1 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/xorcare/pointer v1.2.2 golang.org/x/crypto v0.27.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 + golang.org/x/sync v0.8.0 golang.org/x/tools v0.25.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.31.1 @@ -71,7 +72,6 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/image v0.18.0 // indirect - golang.org/x/sync v0.8.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240805194559-2c9e96a0b5d4 // indirect google.golang.org/grpc v1.66.0 // indirect google.golang.org/protobuf v1.34.2 // indirect