Repository: tensorchord/envd (language: python)

Issue:
feat(envd-server): Implement envd engine interface for envd-server
## Description

```
type EnvironmentClient interface {
	PauseEnvironment(ctx context.Context, env string) (string, error)
	ResumeEnvironment(ctx context.Context, env string) (string, error)
	ListEnvironment(ctx context.Context) ([]types.EnvdEnvironment, error)
	ListEnvDependency(ctx context.Context, env string) (*types.Dependency, error)
	ListEnvPortBinding(ctx context.Context, env string) ([]types.PortBinding, error)

	CleanEnvdIfExists(ctx context.Context, name string, force bool) error
	// StartEnvd creates the container for the given tag and container name.
	StartEnvd(ctx context.Context, so StartOptions) (*StartResult, error)

	IsRunning(ctx context.Context, name string) (bool, error)
	Exists(ctx context.Context, name string) (bool, error)
	WaitUntilRunning(ctx context.Context, name string, timeout time.Duration) error
}
```

---
<!-- 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:
Implement
```
	ListEnvironment(ctx context.Context) ([]types.EnvdEnvironment, error)
	ListEnvDependency(ctx context.Context, env string) (*types.Dependency, error)
	ListEnvPortBinding(ctx context.Context, env string) ([]types.PortBinding, error)
	Exists(ctx context.Context, name string) (bool, error)
	CleanEnvdIfExists(ctx context.Context, name string, force bool) error
```
#dibs
I will try to use https://github.com/containers/image to get the image manifest in envd-server, then add image labels to the pod.

It is to support funcs like `ListEnvDependency(ctx context.Context, env string) (*types.Dependency, error)`
The code works

```go
package main

import (
	"context"
	"encoding/json"
	"fmt"

	"github.com/containers/image/v5/docker"
	"github.com/containers/image/v5/image"
)

func main() {
	ref, err := docker.ParseReference("//docker.io/gaocegege/test-envd")
	if err != nil {
		panic(err)
	}
	ctx := context.Background()
	src, err := ref.NewImageSource(ctx, nil)
	if err != nil {
		panic(err)
	}
	// b, _, err := src.GetManifest(ctx, nil)
	// if err != nil {
	// 	panic(err)
	// }
	img, err := image.FromUnparsedImage(ctx, nil, image.UnparsedInstance(src, nil))
	if err != nil {
		panic(err)
	}
	config, err := img.OCIConfig(ctx)
	if err != nil {
		panic(err)
	}
	
	var out []byte
	out, err = json.MarshalIndent(config, "", "    ")
	if err == nil {
		fmt.Println(string(out))
	}
	// fmt.Printf("%s", string(b))
}
```

Fix patch:
diff --git a/go.mod b/go.mod
index a4633e6ec..d88f5cb8e 100644
--- a/go.mod
+++ b/go.mod
@@ -23,21 +23,21 @@ require (
 	github.com/onsi/ginkgo/v2 v2.2.0
 	github.com/onsi/gomega v1.21.1
 	github.com/opencontainers/go-digest v1.0.0
-	github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799
+	github.com/opencontainers/image-spec v1.1.0-rc1
 	github.com/pkg/sftp v1.13.5
 	github.com/sirupsen/logrus v1.9.0
 	github.com/spf13/viper v1.13.0
 	github.com/stretchr/testify v1.8.0
-	github.com/tensorchord/envd-server v0.0.3
+	github.com/tensorchord/envd-server v0.0.4
 	github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea
 	github.com/tonistiigi/vt100 v0.0.0-20210615222946-8066bb97264f
-	github.com/urfave/cli/v2 v2.19.2
+	github.com/urfave/cli/v2 v2.20.2
 	go.starlark.net v0.0.0-20220328144851-d1966c6b9fcd
-	golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0
+	golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	golang.org/x/term v0.0.0-20220919170432-7a66f970e087
 	golang.org/x/time v0.0.0-20220920022843-2ce7c2934d45
-	k8s.io/api v0.25.2
+	k8s.io/api v0.25.3
 )
 
 require (
@@ -48,7 +48,7 @@ require (
 	github.com/op/go-logging v0.0.0-20160211212156-b2cb9fa56473 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
-	k8s.io/apimachinery v0.25.2 // indirect
+	k8s.io/apimachinery v0.25.3 // indirect
 	k8s.io/klog/v2 v2.80.1 // indirect
 	k8s.io/utils v0.0.0-20220823124924-e9cbc92d1a73 // indirect
 	sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
@@ -70,7 +70,7 @@ require (
 	github.com/davecgh/go-spew v1.1.1 // indirect
 	github.com/docker/cli v20.10.13+incompatible // indirect
 	github.com/docker/distribution v2.8.1+incompatible // indirect
-	github.com/docker/docker-credential-helpers v0.6.4 // indirect
+	github.com/docker/docker-credential-helpers v0.7.0 // indirect
 	github.com/emirpasic/gods v1.12.0 // indirect
 	github.com/fsnotify/fsnotify v1.5.4 // indirect
 	github.com/getsentry/sentry-go v0.12.0 // indirect
@@ -120,8 +120,8 @@ require (
 	go.opentelemetry.io/otel/sdk v1.4.1 // indirect
 	go.opentelemetry.io/otel/trace v1.4.1 // indirect
 	go.opentelemetry.io/proto/otlp v0.12.0 // indirect
-	golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 // indirect
-	golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
+	golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
+	golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect
 	golang.org/x/text v0.3.7 // indirect
 	google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
 	google.golang.org/grpc v1.46.2 // indirect
diff --git a/go.sum b/go.sum
index 4d6a53d8f..8dba7c097 100644
--- a/go.sum
+++ b/go.sum
@@ -158,7 +158,6 @@ github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ
 github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
 github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
-github.com/danieljoos/wincred v1.1.0/go.mod h1:XYlo+eRTsVA9aHGp7NGjFkPla4m+DCL7hqDjlFjiygg=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -172,8 +171,8 @@ github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6
 github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
 github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible h1:Ptj2To+ezU/mCBUKdYXBQ2r3/2EJojAlOZrsgprF+is=
 github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-github.com/docker/docker-credential-helpers v0.6.4 h1:axCks+yV+2MR3/kZhAmy07yC56WZ2Pwu/fKWtKuZB0o=
-github.com/docker/docker-credential-helpers v0.6.4/go.mod h1:ofX3UI0Gz1TteYBjtgs07O36Pyasyp66D2uKT7H8W1c=
+github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A=
+github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0=
 github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
 github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
 github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
@@ -406,7 +405,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
 github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
-github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A=
+github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c=
 github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
 github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -462,7 +461,7 @@ github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
 github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM=
 github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
 github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
-github.com/moby/sys/mountinfo v0.6.0 h1:gUDhXQx58YNrpHlK4nSL+7y2pxFZkUcXqzFDKWdC0Oo=
+github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
 github.com/moby/sys/signal v0.6.0 h1:aDpY94H8VlhTGa9sNYUFCFsMZIUh5wm0B6XkIoJj/iY=
 github.com/moby/sys/signal v0.6.0/go.mod h1:GQ6ObYZfqacOwTtlXvcmh9A26dVRul/hbOZn88Kg8Tg=
 github.com/moby/term v0.0.0-20201110203204-bea5bbe245bf/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
@@ -511,12 +510,12 @@ github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1
 github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
 github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
-github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799 h1:rc3tiVYb5z54aKaDfakKn0dDjIyPpTtszkjuMzyt7ec=
-github.com/opencontainers/image-spec v1.0.3-0.20211202183452-c5a74bcca799/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
+github.com/opencontainers/image-spec v1.1.0-rc1 h1:lfG+OTa7V8PD3PKvkocSG9KAcA9MANqJn53m31Fvwkc=
+github.com/opencontainers/image-spec v1.1.0-rc1/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
 github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
 github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0=
-github.com/opencontainers/runc v1.1.1 h1:PJ9DSs2sVwE0iVr++pAHE6QkS9tzcVWozlPifdwMgrU=
+github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg=
 github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417 h1:3snG66yBm59tKhhSPQrQ/0bCrv1LQbKt40LnUPiUxdc=
 github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
@@ -617,8 +616,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
 github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
 github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
-github.com/tensorchord/envd-server v0.0.3 h1:4QzvkM6f3nJRqFN/aVsDkEp1F/vxAEJ3xF1wNhDF9qc=
-github.com/tensorchord/envd-server v0.0.3/go.mod h1:AWUk101rs4xl7wHmQmafZ8X5TSV/ZR9AD7BysLkIMIo=
+github.com/tensorchord/envd-server v0.0.4 h1:4RkSchgzxqhWGI9vAfYuUPtLWlPTK/FkELXGqsQmy+k=
+github.com/tensorchord/envd-server v0.0.4/go.mod h1:V76OpMczrgBeu19K+rgFqSf4ZK3DXo/U82/0xel/jYg=
 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
 github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274 h1:wbyZxD6IPFp0sl5uscMOJRsz5UKGFiNiD16e+MVfKZY=
 github.com/tonistiigi/fsutil v0.0.0-20220115021204-b19f7f9cb274/go.mod h1:oPAfvw32vlUJSjyDcQ3Bu0nb2ON2B+G0dtVN/SZNJiA=
@@ -633,8 +632,8 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
 github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
 github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
 github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
-github.com/urfave/cli/v2 v2.19.2 h1:eXu5089gqqiDQKSnFW+H/FhjrxRGztwSxlTsVK7IuqQ=
-github.com/urfave/cli/v2 v2.19.2/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
+github.com/urfave/cli/v2 v2.20.2 h1:dKA0LUjznZpwmmbrc0pOgcLTEilnHeM8Av9Yng77gHM=
+github.com/urfave/cli/v2 v2.20.2/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
 github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
 github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
 github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
@@ -706,8 +705,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0 h1:a5Yg6ylndHHYJqIPrdq0AhvR6KTvDTAvgBtaidhEevY=
-golang.org/x/crypto v0.0.0-20220919173607-35f4265a4bc0/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI=
+golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -787,8 +786,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
 golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
-golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1 h1:TWZxd/th7FbRSMret2MVQdlI8uT49QEtwZdvJrxjEHU=
-golang.org/x/net v0.0.0-20220919232410-f2f64ebce3c1/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
+golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
+golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -890,8 +889,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
-golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=
+golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -1135,10 +1134,10 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
 honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
 honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
-k8s.io/api v0.25.2 h1:v6G8RyFcwf0HR5jQGIAYlvtRNrxMJQG1xJzaSeVnIS8=
-k8s.io/api v0.25.2/go.mod h1:qP1Rn4sCVFwx/xIhe+we2cwBLTXNcheRyYXwajonhy0=
-k8s.io/apimachinery v0.25.2 h1:WbxfAjCx+AeN8Ilp9joWnyJ6xu9OMeS/fsfjK/5zaQs=
-k8s.io/apimachinery v0.25.2/go.mod h1:hqqA1X0bsgsxI6dXsJ4HnNTBOmJNxyPp8dw3u2fSHwA=
+k8s.io/api v0.25.3 h1:Q1v5UFfYe87vi5H7NU0p4RXC26PPMT8KOpr1TLQbCMQ=
+k8s.io/api v0.25.3/go.mod h1:o42gKscFrEVjHdQnyRenACrMtbuJsVdP+WVjqejfzmI=
+k8s.io/apimachinery v0.25.3 h1:7o9ium4uyUOM76t6aunP0nZuex7gDf8VGwkR5RcJnQc=
+k8s.io/apimachinery v0.25.3/go.mod h1:jaF9C/iPNM1FuLl7Zuy5b9v+n35HGSh6AQ4HYRkCqwo=
 k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
 k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
 k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
diff --git a/pkg/app/env.go b/pkg/app/env.go
index d6ddd1a5c..bee716600 100644
--- a/pkg/app/env.go
+++ b/pkg/app/env.go
@@ -15,20 +15,7 @@
 package app
 
 import (
-	"fmt"
-	"io"
-	"os"
-	"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"
 )
 
 var CommandEnvironment = &cli.Command{
@@ -42,76 +29,3 @@ var CommandEnvironment = &cli.Command{
 		CommandListEnv,
 	},
 }
-
-var CommandListEnv = &cli.Command{
-	Name:    "list",
-	Aliases: []string{"ls", "l"},
-	Usage:   "List envd environments",
-	Action:  getEnvironment,
-}
-
-func getEnvironment(clicontext *cli.Context) error {
-	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
-	}
-	envs, err := envdEngine.ListEnvironment(clicontext.Context)
-	if err != nil {
-		return err
-	}
-	renderEnvironments(envs, os.Stdout)
-	return nil
-}
-
-func renderEnvironments(envs []types.EnvdEnvironment, w io.Writer) {
-	table := tablewriter.NewWriter(w)
-	table.SetHeader([]string{
-		"Name", "Endpoint", "SSH Target", "Image",
-		"GPU", "CUDA", "CUDNN", "Status", "Container ID",
-	})
-
-	table.SetAutoWrapText(false)
-	table.SetAutoFormatHeaders(true)
-	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
-	table.SetAlignment(tablewriter.ALIGN_LEFT)
-	table.SetCenterSeparator("")
-	table.SetColumnSeparator("")
-	table.SetRowSeparator("")
-	table.SetHeaderLine(false)
-	table.SetBorder(false)
-	table.SetTablePadding("\t") // pad with tabs
-	table.SetNoWhiteSpace(true)
-
-	for _, env := range envs {
-		envRow := make([]string, 9)
-		envRow[0] = env.Name
-		envRow[1] = endpointOrNone(env)
-		envRow[2] = fmt.Sprintf("%s.envd", env.Name)
-		envRow[3] = env.Container.Image
-		envRow[4] = strconv.FormatBool(env.GPU)
-		envRow[5] = stringOrNone(env.CUDA)
-		envRow[6] = stringOrNone(env.CUDNN)
-		envRow[7] = env.Status
-		envRow[8] = stringid.TruncateID(env.Container.ID)
-		table.Append(envRow)
-	}
-	table.Render()
-}
-
-func endpointOrNone(env types.EnvdEnvironment) string {
-	var res strings.Builder
-	if env.JupyterAddr != nil {
-		res.WriteString(fmt.Sprintf("jupyter: %s", *env.JupyterAddr))
-	}
-	if env.RStudioServerAddr != nil {
-		res.WriteString(fmt.Sprintf("rstudio: %s", *env.RStudioServerAddr))
-	}
-	return res.String()
-}
diff --git a/pkg/app/env_ls.go b/pkg/app/env_ls.go
new file mode 100644
index 000000000..b6419812e
--- /dev/null
+++ b/pkg/app/env_ls.go
@@ -0,0 +1,103 @@
+// 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 (
+	"fmt"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+
+	"github.com/cockroachdb/errors"
+	"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"
+)
+
+var CommandListEnv = &cli.Command{
+	Name:    "list",
+	Aliases: []string{"ls", "l"},
+	Usage:   "List envd environments",
+	Action:  getEnvironment,
+}
+
+func getEnvironment(clicontext *cli.Context) error {
+	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
+	}
+	envs, err := envdEngine.ListEnvironment(clicontext.Context)
+	if err != nil {
+		return err
+	}
+	renderEnvironments(envs, os.Stdout)
+	return nil
+}
+
+func renderEnvironments(envs []types.EnvdEnvironment, w io.Writer) {
+	table := tablewriter.NewWriter(w)
+	table.SetHeader([]string{
+		"Name", "Endpoint", "SSH Target", "Image",
+		"GPU", "CUDA", "CUDNN", "Status",
+	})
+
+	table.SetAutoWrapText(false)
+	table.SetAutoFormatHeaders(true)
+	table.SetHeaderAlignment(tablewriter.ALIGN_LEFT)
+	table.SetAlignment(tablewriter.ALIGN_LEFT)
+	table.SetCenterSeparator("")
+	table.SetColumnSeparator("")
+	table.SetRowSeparator("")
+	table.SetHeaderLine(false)
+	table.SetBorder(false)
+	table.SetTablePadding("\t") // pad with tabs
+	table.SetNoWhiteSpace(true)
+
+	for _, env := range envs {
+		envRow := make([]string, 9)
+		envRow[0] = env.Name
+		envRow[1] = endpointOrNone(env)
+		envRow[2] = fmt.Sprintf("%s.envd", env.Name)
+		envRow[3] = env.Image
+		envRow[4] = strconv.FormatBool(env.GPU)
+		envRow[5] = stringOrNone(env.CUDA)
+		envRow[6] = stringOrNone(env.CUDNN)
+		envRow[7] = env.Status
+		table.Append(envRow)
+	}
+	table.Render()
+}
+
+func endpointOrNone(env types.EnvdEnvironment) string {
+	var res strings.Builder
+	if env.JupyterAddr != nil {
+		res.WriteString(fmt.Sprintf("jupyter: %s", *env.JupyterAddr))
+	}
+	if env.RStudioServerAddr != nil {
+		res.WriteString(fmt.Sprintf("rstudio: %s", *env.RStudioServerAddr))
+	}
+	return res.String()
+}
diff --git a/pkg/app/top.go b/pkg/app/top.go
index ac7105fa4..12907566d 100644
--- a/pkg/app/top.go
+++ b/pkg/app/top.go
@@ -99,7 +99,6 @@ func initGrid(ctx context.Context, envs []types.EnvdEnvironment, collector metri
 	rows := make([]*metrics.WidgetRow, rowNumber)
 	header := metrics.NewWidgetRow(0)
 	header.Add(metrics.NewNameCol("Name"))
-	header.Add(metrics.NewNameCol("ID"))
 	header.Add(metrics.NewNameCol("CPU"))
 	header.Add(metrics.NewNameCol("Memory"))
 	rows[0] = header
@@ -107,7 +106,6 @@ func initGrid(ctx context.Context, envs []types.EnvdEnvironment, collector metri
 		row := metrics.NewWidgetRow(i + 1)
 		metricsChan := collector.Watch(ctx, env.Name)
 		row.Add(metrics.NewNameCol(env.Name))
-		row.Add(metrics.NewNameCol(env.ID[0:12]))
 		row.Add(metrics.NewCPUCol(metricsChan))
 		row.Add(metrics.NewMEMCol(metricsChan))
 		rows[i+1] = row
diff --git a/pkg/envd/docker.go b/pkg/envd/docker.go
index 2daf512ce..e16f2c807 100644
--- a/pkg/envd/docker.go
+++ b/pkg/envd/docker.go
@@ -88,7 +88,7 @@ func (e dockerEngine) ListEnvironment(
 
 	envs := make([]types.EnvdEnvironment, 0)
 	for _, ctr := range ctrs {
-		env, err := types.NewEnvironment(ctr)
+		env, err := types.NewEnvironmentFromContainer(ctr)
 		if err != nil {
 			return nil, errors.Wrap(err, "failed to create env from the container")
 		}
diff --git a/pkg/envd/envdserver.go b/pkg/envd/envdserver.go
index 9ad0a23c4..9902f5fae 100644
--- a/pkg/envd/envdserver.go
+++ b/pkg/envd/envdserver.go
@@ -23,6 +23,7 @@ import (
 	"github.com/sirupsen/logrus"
 	servertypes "github.com/tensorchord/envd-server/api/types"
 	"github.com/tensorchord/envd-server/client"
+	"github.com/tensorchord/envd-server/errdefs"
 	v1 "k8s.io/api/core/v1"
 
 	"github.com/tensorchord/envd/pkg/types"
@@ -58,27 +59,83 @@ func (e *envdServerEngine) GPUEnabled(ctx context.Context) (bool, error) {
 }
 
 func (e *envdServerEngine) PauseEnvironment(ctx context.Context, env string) (string, error) {
-	return "", errors.New("not implemented")
+	return "", errors.New("pausing/resuming environments is not supported for the runner envd-server")
 }
 
 func (e *envdServerEngine) ResumeEnvironment(ctx context.Context, env string) (string, error) {
-	return "", errors.New("not implemented")
+	return "", errors.New("pausing/resuming environments is not supported for the runner envd-server")
 }
 
 func (e *envdServerEngine) ListEnvironment(ctx context.Context) ([]types.EnvdEnvironment, error) {
-	return nil, errors.New("not implemented")
+	req := servertypes.EnvironmentListRequest{
+		IdentityToken: e.IdentityToken,
+	}
+
+	ctr, err := e.EnvironmentList(ctx, req)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get environment")
+	}
+	env, err := types.NewEnvironmentFromPod(ctr.Pod)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create env from the container")
+	}
+	return []types.EnvdEnvironment{*env}, nil
 }
 
 func (e *envdServerEngine) ListEnvDependency(ctx context.Context, env string) (*types.Dependency, error) {
-	return nil, errors.New("not implemented")
+	req := servertypes.EnvironmentListRequest{
+		IdentityToken: e.IdentityToken,
+	}
+
+	logger := logrus.WithFields(logrus.Fields{
+		"env": env,
+	})
+	logger.Debug("getting dependencies")
+	ctr, err := e.EnvironmentList(ctx, req)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get environment")
+	}
+	dep, err := types.NewDependencyFromLabels(ctr.Pod.Annotations)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to create dependency from the container")
+	}
+	return dep, nil
 }
 
 func (e *envdServerEngine) ListEnvPortBinding(ctx context.Context, env string) ([]types.PortBinding, error) {
-	return nil, errors.New("not implemented")
+	req := servertypes.EnvironmentListRequest{
+		IdentityToken: e.IdentityToken,
+	}
+
+	_, err := e.EnvironmentList(ctx, req)
+	if err != nil {
+		return nil, errors.Wrap(err, "failed to get environment")
+	}
+	// TODO(gaocegege): Remove hard coded.
+	res := []types.PortBinding{
+		{
+			Port:     "2222",
+			Protocol: "TCP",
+			HostIP:   "localhost",
+			HostPort: "2222",
+		},
+	}
+	return res, nil
 }
 
 func (e *envdServerEngine) CleanEnvdIfExists(ctx context.Context, name string, force bool) error {
-	return errors.New("not implemented")
+	created, err := e.Exists(ctx, name)
+	if err != nil {
+		return err
+	}
+	if !created {
+		return nil
+	}
+
+	req := servertypes.EnvironmentRemoveRequest{
+		IdentityToken: e.IdentityToken,
+	}
+	return e.EnvironmentRemove(ctx, req)
 }
 
 // StartEnvd creates the container for the given tag and container name.
@@ -123,7 +180,18 @@ func (e *envdServerEngine) IsRunning(ctx context.Context, name string) (bool, er
 }
 
 func (e *envdServerEngine) Exists(ctx context.Context, name string) (bool, error) {
-	return false, errors.New("not implemented")
+	req := servertypes.EnvironmentListRequest{
+		IdentityToken: e.IdentityToken,
+	}
+
+	_, err := e.EnvironmentList(ctx, req)
+	if err != nil {
+		if errdefs.IsNotFound(err) {
+			return false, nil
+		}
+		return false, errors.Wrap(err, "failed to list the environment")
+	}
+	return true, nil
 }
 
 func (e *envdServerEngine) WaitUntilRunning(ctx context.Context, name string, timeout time.Duration) error {
diff --git a/pkg/types/envd.go b/pkg/types/envd.go
index 8e194efdb..940431b9e 100644
--- a/pkg/types/envd.go
+++ b/pkg/types/envd.go
@@ -21,6 +21,7 @@ import (
 
 	"github.com/docker/docker/api/types"
 	"github.com/moby/buildkit/util/system"
+	v1 "k8s.io/api/core/v1"
 
 	"github.com/tensorchord/envd/pkg/util/netutil"
 	"github.com/tensorchord/envd/pkg/version"
@@ -83,9 +84,10 @@ type EnvdImage struct {
 }
 
 type EnvdEnvironment struct {
-	types.Container
+	Image string `json:"image,omitempty"`
+	Name  string `json:"name,omitempty"`
 
-	Name              string  `json:"name,omitempty"`
+	Status            string  `json:"status,omitempty"`
 	JupyterAddr       *string `json:"jupyter_addr,omitempty"`
 	RStudioServerAddr *string `json:"rstudio_server_addr,omitempty"`
 	EnvdManifest      `json:",inline,omitempty"`
@@ -165,9 +167,10 @@ func NewImage(image types.ImageSummary) (*EnvdImage, error) {
 	return &img, nil
 }
 
-func NewEnvironment(ctr types.Container) (*EnvdEnvironment, error) {
+func NewEnvironmentFromContainer(ctr types.Container) (*EnvdEnvironment, error) {
 	env := EnvdEnvironment{
-		Container: ctr,
+		Image:  ctr.Image,
+		Status: ctr.Status,
 	}
 	if name, ok := ctr.Labels[ContainerLabelName]; ok {
 		env.Name = name
@@ -187,6 +190,27 @@ func NewEnvironment(ctr types.Container) (*EnvdEnvironment, error) {
 	return &env, nil
 }
 
+func NewEnvironmentFromPod(ctr v1.Pod) (*EnvdEnvironment, error) {
+	env := EnvdEnvironment{
+		Image:  ctr.Spec.Containers[0].Image,
+		Status: string(ctr.Status.Phase),
+		Name:   ctr.Name,
+	}
+	if jupyterAddr, ok := ctr.Annotations[ContainerLabelJupyterAddr]; ok {
+		env.JupyterAddr = &jupyterAddr
+	}
+	if rstudioServerAddr, ok := ctr.Annotations[ContainerLabelRStudioServerAddr]; ok {
+		env.RStudioServerAddr = &rstudioServerAddr
+	}
+
+	m, err := newManifest(ctr.Annotations)
+	if err != nil {
+		return nil, err
+	}
+	env.EnvdManifest = m
+	return &env, nil
+}
+
 func newManifest(labels map[string]string) (EnvdManifest, error) {
 	manifest := EnvdManifest{}
 	if gpuEnabled, ok := labels[ImageLabelGPU]; ok {
@@ -201,7 +225,7 @@ func newManifest(labels map[string]string) (EnvdManifest, error) {
 	if context, ok := labels[ImageLabelContext]; ok {
 		manifest.BuildContext = context
 	}
-	dep, err := newDependencyFromLabels(labels)
+	dep, err := NewDependencyFromLabels(labels)
 	if err != nil {
 		return manifest, err
 	}
@@ -210,11 +234,11 @@ func newManifest(labels map[string]string) (EnvdManifest, error) {
 }
 
 func NewDependencyFromContainerJSON(ctr types.ContainerJSON) (*Dependency, error) {
-	return newDependencyFromLabels(ctr.Config.Labels)
+	return NewDependencyFromLabels(ctr.Config.Labels)
 }
 
 func NewDependencyFromImage(img types.ImageSummary) (*Dependency, error) {
-	return newDependencyFromLabels(img.Labels)
+	return NewDependencyFromLabels(img.Labels)
 }
 
 func NewPortBindingFromContainerJSON(ctr types.ContainerJSON) []PortBinding {
@@ -242,7 +266,7 @@ func GetImageName(image EnvdImage) string {
 	return "<none>"
 }
 
-func newDependencyFromLabels(label map[string]string) (*Dependency, error) {
+func NewDependencyFromLabels(label map[string]string) (*Dependency, error) {
 	dep := Dependency{}
 	if pkgs, ok := label[ImageLabelAPT]; ok {
 		lst, err := parseAPTPackages(pkgs)


Test patch:
diff --git a/pkg/types/envd_test.go b/pkg/types/envd_test.go
index 9a3fb0d99..8190250d4 100644
--- a/pkg/types/envd_test.go
+++ b/pkg/types/envd_test.go
@@ -27,7 +27,7 @@ var _ = g.Describe("pypi label", func() {
 			labels := make(map[string]string)
 			s, _ := json.Marshal([]string{"numpy", "torch", "--extra-url", "https://download.pytorch.org/whl/torch_stable.html"})
 			labels[ImageLabelPyPI] = string(s)
-			d, err := newDependencyFromLabels(labels)
+			d, err := NewDependencyFromLabels(labels)
 			Expect(err).NotTo(HaveOccurred())
 			Expect(d.PyPIPackages).To(Equal([]string{"numpy", "torch"}))
 		})