Repository: tensorchord/envd (language: python)

Issue:
feat(CLI): Support SSH(attach) command
## Description

midi ssh


Discussion / hints:
Is it done though sshd?
This issue is to support a new command `envd ssh` to attach into the env.
> This issue is to support a new command `envd ssh` to attach into the env.

What's the main difference between `ssh <envd-container>` and `envd ssh <envd-container>`? Any feature we should support through this new command?
envd ssh or envd attach does not require the target if it is run in the build context dir.
For envd-server runner:

`envd ssh` will be introduced as the new command. It creates the ssh client config, then connect to the ssh proxy server `containerssh`. The username should be the contains the identitytoken and project name.

The `envd-server` should:

- Get the identity token and the project name, then find the correct pod to attach.
- Update `containerssh` app config with the `/v1/config` endpoint.

Fix patch:
diff --git a/pkg/app/build.go b/pkg/app/build.go
index 8b58e268d..a5cc7a925 100644
--- a/pkg/app/build.go
+++ b/pkg/app/build.go
@@ -125,7 +125,14 @@ func build(clicontext *cli.Context) error {
 }
 
 func DetectEnvironment(clicontext *cli.Context, buildOpt builder.Options) error {
-	engine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	engine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return errors.Wrap(err, "failed to create the docker client")
 	}
diff --git a/pkg/app/destroy.go b/pkg/app/destroy.go
index 131d56ea7..60c2131a7 100644
--- a/pkg/app/destroy.go
+++ b/pkg/app/destroy.go
@@ -25,6 +25,7 @@ import (
 
 	"github.com/tensorchord/envd/pkg/docker"
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	sshconfig "github.com/tensorchord/envd/pkg/ssh/config"
 )
 
@@ -101,7 +102,14 @@ func destroy(clicontext *cli.Context) error {
 
 func getContainerTag(clicontext *cli.Context, name string) ([]string, error) {
 	tags := []string{}
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return tags, err
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return tags, err
 	}
diff --git a/pkg/app/env.go b/pkg/app/env.go
index ff4f34842..d6ddd1a5c 100644
--- a/pkg/app/env.go
+++ b/pkg/app/env.go
@@ -21,11 +21,13 @@ import (
 	"strconv"
 	"strings"
 
+	"github.com/cockroachdb/errors"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/olekukonko/tablewriter"
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/types"
 )
 
@@ -49,7 +51,14 @@ var CommandListEnv = &cli.Command{
 }
 
 func getEnvironment(clicontext *cli.Context) error {
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/app/env_describe.go b/pkg/app/env_describe.go
index 0e51e27dd..048cb34e0 100644
--- a/pkg/app/env_describe.go
+++ b/pkg/app/env_describe.go
@@ -23,6 +23,7 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/types"
 )
 
@@ -43,7 +44,14 @@ var CommandDescribeEnvironment = &cli.Command{
 
 func getEnvironmentDescriptions(clicontext *cli.Context) error {
 	envName := clicontext.String("env")
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return errors.Wrap(err, "failed to create envd engine")
 	}
diff --git a/pkg/app/image.go b/pkg/app/image.go
index b18b1c017..47e1f2008 100644
--- a/pkg/app/image.go
+++ b/pkg/app/image.go
@@ -20,12 +20,14 @@ import (
 	"strconv"
 	"time"
 
+	"github.com/cockroachdb/errors"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/go-units"
 	"github.com/olekukonko/tablewriter"
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/types"
 )
 
@@ -49,7 +51,14 @@ var CommandListImage = &cli.Command{
 }
 
 func getImage(clicontext *cli.Context) error {
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/app/image_describe.go b/pkg/app/image_describe.go
index 7f6e2163d..d2d086f49 100644
--- a/pkg/app/image_describe.go
+++ b/pkg/app/image_describe.go
@@ -21,6 +21,7 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 )
 
 var CommandDescribeImage = &cli.Command{
@@ -42,7 +43,14 @@ func getImageDependency(clicontext *cli.Context) error {
 	if envName == "" {
 		return errors.New("image is required")
 	}
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return errors.Wrap(err, "failed to create envd engine")
 	}
diff --git a/pkg/app/pause.go b/pkg/app/pause.go
index b2322b2d3..8d252e09d 100644
--- a/pkg/app/pause.go
+++ b/pkg/app/pause.go
@@ -20,6 +20,7 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 )
 
 var CommandPause = &cli.Command{
@@ -43,7 +44,14 @@ func pause(clicontext *cli.Context) error {
 	if env == "" {
 		return errors.New("env is required")
 	}
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return errors.Wrap(err, "failed to create envd engine")
 	}
diff --git a/pkg/app/resume.go b/pkg/app/resume.go
index b3558cbe2..91514f914 100644
--- a/pkg/app/resume.go
+++ b/pkg/app/resume.go
@@ -20,6 +20,7 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 )
 
 var CommandResume = &cli.Command{
@@ -43,7 +44,14 @@ func resume(clicontext *cli.Context) error {
 	if env == "" {
 		return errors.New("env is required")
 	}
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return errors.Wrap(err, "failed to create envd engine")
 	}
diff --git a/pkg/app/run.go b/pkg/app/run.go
index fd9222917..411cef25b 100644
--- a/pkg/app/run.go
+++ b/pkg/app/run.go
@@ -23,6 +23,7 @@ import (
 
 	"github.com/tensorchord/envd/pkg/builder"
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/lang/ir"
 	"github.com/tensorchord/envd/pkg/ssh"
 	"github.com/tensorchord/envd/pkg/util/fileutil"
@@ -116,7 +117,14 @@ func run(clicontext *cli.Context) error {
 	}
 
 	// Check if the container is running.
-	engine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	envdOpt := envd.Options{
+		Context: context,
+	}
+	engine, err := envd.New(clicontext.Context, envdOpt)
 	if err != nil {
 		return errors.Wrap(err, "failed to create the docker client")
 	}
diff --git a/pkg/app/top.go b/pkg/app/top.go
index dabf76deb..ac7105fa4 100644
--- a/pkg/app/top.go
+++ b/pkg/app/top.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/tensorchord/envd/pkg/docker"
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/metrics"
 	"github.com/tensorchord/envd/pkg/types"
 )
@@ -42,7 +43,14 @@ func top(clicontext *cli.Context) error {
 		return err
 	}
 
-	envdEngine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	envdEngine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/app/up.go b/pkg/app/up.go
index 9f838ddd6..bb7eb8358 100644
--- a/pkg/app/up.go
+++ b/pkg/app/up.go
@@ -24,6 +24,7 @@ import (
 
 	"github.com/tensorchord/envd/pkg/builder"
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/lang/ir"
 	"github.com/tensorchord/envd/pkg/ssh"
 	sshconfig "github.com/tensorchord/envd/pkg/ssh/config"
@@ -180,7 +181,14 @@ func up(clicontext *cli.Context) error {
 }
 
 func StartEnvd(clicontext *cli.Context, buildOpt builder.Options, gpu bool, numGPUs int) (int, error) {
-	engine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return 0, errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	engine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return 0, errors.Wrap(err, "failed to create the docker client")
 	}
diff --git a/pkg/app/version.go b/pkg/app/version.go
index 21eefc80c..1da611c23 100644
--- a/pkg/app/version.go
+++ b/pkg/app/version.go
@@ -22,6 +22,7 @@ import (
 	"github.com/urfave/cli/v2"
 
 	"github.com/tensorchord/envd/pkg/envd"
+	"github.com/tensorchord/envd/pkg/home"
 	"github.com/tensorchord/envd/pkg/types"
 	"github.com/tensorchord/envd/pkg/version"
 )
@@ -84,7 +85,14 @@ func printVersion(ctx *cli.Context) error {
 }
 
 func getDetailedVersion(clicontext *cli.Context) (detailedVersion, error) {
-	engine, err := envd.New(clicontext.Context, "docker")
+	context, err := home.GetManager().ContextGetCurrent()
+	if err != nil {
+		return detailedVersion{}, errors.Wrap(err, "failed to get the current context")
+	}
+	opt := envd.Options{
+		Context: context,
+	}
+	engine, err := envd.New(clicontext.Context, opt)
 	if err != nil {
 		return detailedVersion{}, errors.Wrap(
 			err, "failed to create engine for docker server",
diff --git a/pkg/buildkitd/buildkitd.go b/pkg/buildkitd/buildkitd.go
index 7daaa6495..da28703cb 100644
--- a/pkg/buildkitd/buildkitd.go
+++ b/pkg/buildkitd/buildkitd.go
@@ -118,7 +118,12 @@ func (c *generalClient) maybeStart(ctx context.Context,
 		if err != nil {
 			return "", err
 		}
-		engine, err := envd.New(ctx, "docker")
+		opt := envd.Options{
+			Context: &types.Context{
+				Runner: types.RunnerTypeDocker,
+			},
+		}
+		engine, err := envd.New(ctx, opt)
 		if err != nil {
 			return "", err
 		}
diff --git a/pkg/envd/factory.go b/pkg/envd/factory.go
index f68e8fed8..2053493b4 100644
--- a/pkg/envd/factory.go
+++ b/pkg/envd/factory.go
@@ -17,25 +17,36 @@ package envd
 import (
 	"context"
 
+	"github.com/cockroachdb/errors"
 	"github.com/docker/docker/client"
 	envdclient "github.com/tensorchord/envd-server/client"
+
+	"github.com/tensorchord/envd/pkg/types"
 )
 
-func New(ctx context.Context, backend string) (Engine, error) {
-	if backend == "docker" {
-		cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
+type Options struct {
+	Context *types.Context
+}
+
+func New(ctx context.Context, opt Options) (Engine, error) {
+	if opt.Context == nil {
+		return nil, errors.New("failed to get the context")
+	}
+	if opt.Context.Runner == types.RunnerTypeEnvdServer {
+		cli, err := envdclient.NewClientWithOpts(envdclient.FromEnv)
 		if err != nil {
 			return nil, err
 		}
-		return &dockerEngine{
+		return &envdServerEngine{
 			Client: cli,
 		}, nil
 	} else {
-		cli, err := envdclient.NewClientWithOpts(envdclient.FromEnv)
+		cli, err := client.NewClientWithOpts(
+			client.FromEnv, client.WithAPIVersionNegotiation())
 		if err != nil {
 			return nil, err
 		}
-		return &envdServerEngine{
+		return &dockerEngine{
 			Client: cli,
 		}, nil
 	}


Test patch:
diff --git a/e2e/e2e_helper.go b/e2e/e2e_helper.go
index fe075a597..6ff42be46 100644
--- a/e2e/e2e_helper.go
+++ b/e2e/e2e_helper.go
@@ -27,6 +27,7 @@ import (
 	"github.com/tensorchord/envd/pkg/docker"
 	"github.com/tensorchord/envd/pkg/envd"
 	"github.com/tensorchord/envd/pkg/lang/ir"
+	"github.com/tensorchord/envd/pkg/types"
 )
 
 func BuildContextDirWithName(name string) string {
@@ -70,7 +71,12 @@ func (e *Example) RemoveImage() func() {
 }
 
 func GetEngine(ctx context.Context) envd.Engine {
-	engine, err := envd.New(ctx, "docker")
+	opt := envd.Options{
+		Context: &types.Context{
+			Runner: types.RunnerTypeDocker,
+		},
+	}
+	engine, err := envd.New(ctx, opt)
 	if err != nil {
 		panic(err)
 	}