Repository: tensorchord/envd (language: python)

Issue:
feat(CLI-context): Support TCP socket for buildkit
## Description

Now we support docker-container and kube-pod for buildkit, we should also support TCP socket.

Ref https://github.com/moby/buildkit#expose-buildkit-as-a-tcp-service

---
<!-- Issue Author: Don't delete this message to encourage other users to support your issue! -->
**Message from the maintainers**:

Love this enhancement proposal? Give it a 👍. We prioritise the proposals with the most 👍.



Discussion / hints:
@aseaday 

Hi, this issue is to support `--builder TCP --builder-socket --builder-name` in `envd context create`

These changes should be done:

- Add a new builder for tcp
- Rename CLI flag `builder-name` to `builder-socket`
I will pick it up tomorrow if no one is working on this issue since it is in high priority.

Fix patch:
diff --git a/pkg/app/context_create.go b/pkg/app/context_create.go
index da9be4ce2..4719ad36b 100644
--- a/pkg/app/context_create.go
+++ b/pkg/app/context_create.go
@@ -28,18 +28,19 @@ var CommandContextCreate = &cli.Command{
 	Usage: "Create envd context",
 	Flags: []cli.Flag{
 		&cli.StringFlag{
-			Name:  "name",
-			Usage: "Name of the context",
-			Value: "",
+			Name:     "name",
+			Usage:    "Name of the context",
+			Value:    "",
+			Required: true,
 		},
 		&cli.StringFlag{
 			Name:  "builder",
-			Usage: "Builder to use (docker-container, kube-pod)",
+			Usage: "Builder to use (docker-container, kube-pod, tcp)",
 			Value: string(types.BuilderTypeDocker),
 		},
 		&cli.StringFlag{
-			Name:  "builder-name",
-			Usage: "Builder name",
+			Name:  "builder-socket",
+			Usage: "Builder socket",
 			Value: "envd_buildkitd",
 		},
 		&cli.BoolFlag{
@@ -53,11 +54,11 @@ var CommandContextCreate = &cli.Command{
 func contextCreate(clicontext *cli.Context) error {
 	name := clicontext.String("name")
 	builder := clicontext.String("builder")
-	builderName := clicontext.String("builder-name")
+	builderSocket := clicontext.String("builder-socket")
 	use := clicontext.Bool("use")
 
 	err := home.GetManager().ContextCreate(name,
-		types.BuilderType(builder), builderName, use)
+		types.BuilderType(builder), builderSocket, use)
 	if err != nil {
 		return errors.Wrap(err, "failed to create context")
 	}
diff --git a/pkg/app/context_rm.go b/pkg/app/context_rm.go
index 919ce9de9..5b749c686 100644
--- a/pkg/app/context_rm.go
+++ b/pkg/app/context_rm.go
@@ -27,9 +27,10 @@ var CommandContextRemove = &cli.Command{
 	Usage: "Remove envd context",
 	Flags: []cli.Flag{
 		&cli.StringFlag{
-			Name:  "name",
-			Usage: "Name of the context",
-			Value: "",
+			Name:     "name",
+			Usage:    "Name of the context",
+			Value:    "",
+			Required: true,
 		},
 	},
 	Action: contextRemove,
diff --git a/pkg/app/context_use.go b/pkg/app/context_use.go
index 306f5086a..941e09750 100644
--- a/pkg/app/context_use.go
+++ b/pkg/app/context_use.go
@@ -27,9 +27,10 @@ var CommandContextUse = &cli.Command{
 	Usage: "Use the specified envd context",
 	Flags: []cli.Flag{
 		&cli.StringFlag{
-			Name:  "name",
-			Usage: "Name of the context",
-			Value: "",
+			Name:     "name",
+			Usage:    "Name of the context",
+			Value:    "",
+			Required: true,
 		},
 	},
 	Action: contextUse,
diff --git a/pkg/home/context.go b/pkg/home/context.go
index cfb210592..d0f3fa42f 100644
--- a/pkg/home/context.go
+++ b/pkg/home/context.go
@@ -93,15 +93,15 @@ func (m generalManager) ContextGetCurrent() (types.BuilderType, string, error) {
 	return "", "", errors.New("no current context")
 }
 
-func (m generalManager) ContextCreate(
+func (m *generalManager) ContextCreate(
 	name string, builder types.BuilderType, socket string, use bool) error {
 	for _, c := range m.context.Contexts {
 		if c.Name == name {
-			return errors.New("context already exists")
+			return errors.Newf("context \"%s\" already exists", name)
 		}
 	}
 	switch builder {
-	case types.BuilderTypeDocker, types.BuilderTypeKubernetes:
+	case types.BuilderTypeDocker, types.BuilderTypeKubernetes, types.BuilderTypeTCP:
 		m.context.Contexts = append(m.context.Contexts, types.Context{
 			Name:          name,
 			Builder:       builder,
@@ -113,7 +113,7 @@ func (m generalManager) ContextCreate(
 	return m.ContextUse(name)
 }
 
-func (m generalManager) ContextRemove(name string) error {
+func (m *generalManager) ContextRemove(name string) error {
 	for i, c := range m.context.Contexts {
 		if c.Name == name {
 			if m.context.Current == name {
@@ -124,21 +124,21 @@ func (m generalManager) ContextRemove(name string) error {
 			return m.dumpContext()
 		}
 	}
-	return nil
+	return errors.Newf("cannot find context \"%s\"", name)
 }
 
 func (m generalManager) ContextList() (types.EnvdContext, error) {
 	return m.context, nil
 }
 
-func (m generalManager) ContextUse(name string) error {
+func (m *generalManager) ContextUse(name string) error {
 	for _, c := range m.context.Contexts {
 		if c.Name == name {
 			m.context.Current = name
 			return m.dumpContext()
 		}
 	}
-	return errors.New("context does not exist")
+	return errors.Newf("context \"%s\" does not exist", name)
 }
 
 func (m *generalManager) dumpContext() error {
diff --git a/pkg/types/envd.go b/pkg/types/envd.go
index 2b60fdd6b..827166433 100644
--- a/pkg/types/envd.go
+++ b/pkg/types/envd.go
@@ -63,6 +63,7 @@ type BuilderType string
 const (
 	BuilderTypeDocker     BuilderType = "docker-container"
 	BuilderTypeKubernetes BuilderType = "kube-pod"
+	BuilderTypeTCP        BuilderType = "tcp"
 )
 
 type Dependency struct {


Test patch:
diff --git a/pkg/app/context_test.go b/pkg/app/context_test.go
new file mode 100644
index 000000000..6fc75b3f7
--- /dev/null
+++ b/pkg/app/context_test.go
@@ -0,0 +1,99 @@
+// Copyright 2022 The envd Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package app
+
+import (
+	"os/exec"
+
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
+
+	"github.com/tensorchord/envd/pkg/home"
+	"github.com/tensorchord/envd/pkg/types"
+)
+
+var _ = Describe("home context", func() {
+	defaultContext := "default"
+	BeforeEach(func() {
+		Expect(home.Initialize()).To(Succeed())
+	})
+	When("check the default context", func() {
+		It("should found the default context", func() {
+			contexts, err := home.GetManager().ContextList()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(contexts.Current).To(Equal(defaultContext))
+		})
+	})
+
+	Describe("add a new context", Ordered, func() {
+		contextName := "tcp-test"
+		builder := types.BuilderTypeTCP
+		socketAddr := "0.0.0.0:12345"
+
+		BeforeAll(func() {
+			err := home.GetManager().ContextCreate(contextName, builder, socketAddr, true)
+			Expect(err).NotTo(HaveOccurred())
+		})
+
+		It("should found a new context", func() {
+			contexts, err := home.GetManager().ContextList()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(contexts.Current).To(Equal(contextName))
+		})
+
+		Describe("connect buildkit through TCP", Ordered, func() {
+			name := "envd-buildkitd-tcp-test"
+			buildContext := "testdata/build-test"
+			dockerArgs := []string{
+				"run", "-d", "-p", "12345:8000", "--rm", "--name", name,
+				"--security-opt", "seccomp=unconfined", "--security-opt", "apparmor=unconfined",
+				"moby/buildkit:rootless", "--addr", "tcp://0.0.0.0:8000", "--oci-worker-no-process-sandbox",
+			}
+			BeforeAll(func() {
+				cmd := exec.Command("docker", dockerArgs...)
+				err := cmd.Run()
+				Expect(err).ToNot(HaveOccurred())
+			})
+
+			It("should be able to build image with TCP context", func() {
+				args := []string{"envd.test", "--debug", "build", "--path", buildContext}
+				app := New()
+				err := app.Run(args)
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			AfterAll(func() {
+				cmd := exec.Command("docker", "stop", name)
+				err := cmd.Run()
+				Expect(err).ToNot(HaveOccurred())
+			})
+		})
+
+		It("fail to delete the current context", func() {
+			err := home.GetManager().ContextRemove(contextName)
+			Expect(err).To(HaveOccurred())
+		})
+
+		AfterAll(func() {
+			err := home.GetManager().ContextUse(defaultContext)
+			Expect(err).NotTo(HaveOccurred())
+			err = home.GetManager().ContextRemove(contextName)
+			Expect(err).NotTo(HaveOccurred())
+			contexts, err := home.GetManager().ContextList()
+			Expect(err).NotTo(HaveOccurred())
+			Expect(contexts.Current).To(Equal(defaultContext))
+		})
+	})
+})
diff --git a/pkg/home/manager_suite_test.go b/pkg/home/home_suite_test.go
similarity index 95%
rename from pkg/home/manager_suite_test.go
rename to pkg/home/home_suite_test.go
index 45f62c9b2..373061f87 100644
--- a/pkg/home/manager_suite_test.go
+++ b/pkg/home/home_suite_test.go
@@ -23,5 +23,5 @@ import (
 
 func TestManager(t *testing.T) {
 	RegisterFailHandler(Fail)
-	RunSpecs(t, "Home Manager Suite")
+	RunSpecs(t, "Home Suite")
 }