From 81728584942167dd6ac55cd6ea08f14814266a44 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Thu, 11 Mar 2021 17:42:45 -0600 Subject: [PATCH 1/4] feat: add provider name and repo name to envs ls --- coder-sdk/env.go | 4 +-- coder-sdk/image.go | 9 ++++++ coder-sdk/interface.go | 3 ++ coder-sdk/workspace_providers.go | 14 ++++---- internal/cmd/envs.go | 6 +++- internal/coderutil/env.go | 55 ++++++++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 10 deletions(-) diff --git a/coder-sdk/env.go b/coder-sdk/env.go index 80e8caaa..af798717 100644 --- a/coder-sdk/env.go +++ b/coder-sdk/env.go @@ -26,8 +26,8 @@ type Environment struct { CPUCores float32 `json:"cpu_cores" table:"CPUCores"` MemoryGB float32 `json:"memory_gb" table:"MemoryGB"` DiskGB int `json:"disk_gb" table:"DiskGB"` - GPUs int `json:"gpus" table:"GPUs"` - Updating bool `json:"updating" table:"Updating"` + GPUs int `json:"gpus" table:"-"` + Updating bool `json:"updating" table:"-"` LatestStat EnvironmentStat `json:"latest_stat" table:"Status"` RebuildMessages []RebuildMessage `json:"rebuild_messages" table:"-"` CreatedAt time.Time `json:"created_at" table:"-"` diff --git a/coder-sdk/image.go b/coder-sdk/image.go index d2840b24..c5101c87 100644 --- a/coder-sdk/image.go +++ b/coder-sdk/image.go @@ -66,6 +66,15 @@ func (c *DefaultClient) ImportImage(ctx context.Context, req ImportImageReq) (*I return &img, nil } +// ImageByID returns an image entity, fetched by its ID. +func (c *DefaultClient) ImageByID(ctx context.Context, id string) (*Image, error) { + var img Image + if err := c.requestBody(ctx, http.MethodGet, "/api/v0/images/"+id, nil, &img); err != nil { + return nil, err + } + return &img, nil +} + // OrganizationImages returns all of the images imported for orgID. func (c *DefaultClient) OrganizationImages(ctx context.Context, orgID string) ([]Image, error) { var ( diff --git a/coder-sdk/interface.go b/coder-sdk/interface.go index 235666a7..23ba876f 100644 --- a/coder-sdk/interface.go +++ b/coder-sdk/interface.go @@ -136,6 +136,9 @@ type Client interface { // ImportImage creates a new image and optionally a new registry. ImportImage(ctx context.Context, req ImportImageReq) (*Image, error) + // ImageByID returns an image entity, fetched by its ID. + ImageByID(ctx context.Context, id string) (*Image, error) + // OrganizationImages returns all of the images imported for orgID. OrganizationImages(ctx context.Context, orgID string) ([]Image, error) diff --git a/coder-sdk/workspace_providers.go b/coder-sdk/workspace_providers.go index b252a1b9..ca44d64c 100644 --- a/coder-sdk/workspace_providers.go +++ b/coder-sdk/workspace_providers.go @@ -12,13 +12,13 @@ type WorkspaceProviders struct { // KubernetesProvider defines an entity capable of deploying and acting as an ingress for Coder environments. type KubernetesProvider struct { - ID string `json:"id" table:"-"` - Name string `json:"name" table:"Name"` - Status WorkspaceProviderStatus `json:"status" table:"Status"` - BuiltIn bool `json:"built_in" table:"-"` - EnvproxyAccessURL string `json:"envproxy_access_url" validate:"required" table:"Access URL"` - DevurlHost string `json:"devurl_host" table:"Devurl Host"` - OrgWhitelist []string `json:"org_whitelist" table:"-"` + ID string `json:"id" table:"-"` + Name string `json:"name" table:"Name"` + Status WorkspaceProviderStatus `json:"status" table:"Status"` + BuiltIn bool `json:"built_in" table:"-"` + EnvproxyAccessURL string `json:"envproxy_access_url" table:"Access URL" validate:"required"` + DevurlHost string `json:"devurl_host" table:"Devurl Host"` + OrgWhitelist []string `json:"org_whitelist" table:"-"` KubeProviderConfig `json:"config" table:"_"` } diff --git a/internal/cmd/envs.go b/internal/cmd/envs.go index 018a8539..5c5345c1 100644 --- a/internal/cmd/envs.go +++ b/internal/cmd/envs.go @@ -82,7 +82,11 @@ func lsEnvsCommand() *cobra.Command { switch outputFmt { case humanOutput: - err := tablewriter.WriteTable(cmd.OutOrStdout(), len(envs), func(i int) interface{} { + envs, err := coderutil.EnvsHumanTable(ctx, client, envs) + if err != nil { + return err + } + err = tablewriter.WriteTable(cmd.OutOrStdout(), len(envs), func(i int) interface{} { return envs[i] }) if err != nil { diff --git a/internal/coderutil/env.go b/internal/coderutil/env.go index 1d0e3f8a..f0bd4514 100644 --- a/internal/coderutil/env.go +++ b/internal/coderutil/env.go @@ -2,6 +2,7 @@ package coderutil import ( "context" + "fmt" "net/url" "golang.org/x/xerrors" @@ -72,3 +73,57 @@ func DefaultWorkspaceProvider(ctx context.Context, c coder.Client) (*coder.Kuber } return nil, coder.ErrNotFound } + +type EnvTable struct { + Name string `table:"Name"` + Image string `table:"Image"` + CPU float32 `table:"vCPU"` + MemoryGB float32 `table:"MemoryGB"` + DiskGB int `table:"DiskGB"` + Status string `table:"Status"` + Provider string `table:"Provider"` + CVM bool `table:"CVM"` +} + +// EnvsHumanTable performs the composition of each Environment with its associated ProviderName and ImageRepo. +func EnvsHumanTable(ctx context.Context, client coder.Client, envs []coder.Environment) ([]EnvTable, error) { + imageMap := make(map[string]*coder.Image) + for _, e := range envs { + imageMap[e.ImageID] = nil + } + // TODO: make this concurrent + for id := range imageMap { + img, err := client.ImageByID(ctx, id) + if err != nil { + return nil, err + } + imageMap[id] = img + } + + pooledEnvs := make([]EnvTable, 0, len(envs)) + providers, err := client.WorkspaceProviders(ctx) + if err != nil { + return nil, err + } + providerMap := make(map[string]coder.KubernetesProvider, len(providers.Kubernetes)) + for _, p := range providers.Kubernetes { + providerMap[p.ID] = p + } + for _, e := range envs { + envProvider, ok := providerMap[e.ResourcePoolID] + if !ok { + return nil, xerrors.Errorf("fetch env workspace provider: %w", coder.ErrNotFound) + } + pooledEnvs = append(pooledEnvs, EnvTable{ + Name: e.Name, + Image: fmt.Sprintf("%s:%s", imageMap[e.ImageID].Repository, e.ImageTag), + CPU: e.CPUCores, + MemoryGB: e.MemoryGB, + DiskGB: e.DiskGB, + Status: string(e.LatestStat.ContainerStatus), + Provider: envProvider.Name, + CVM: e.UseContainerVM, + }) + } + return pooledEnvs, nil +} From 6048493b0a45a3f27f85f9bc66af5086f6b4b22b Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Thu, 11 Mar 2021 17:44:14 -0600 Subject: [PATCH 2/4] fixup! feat: add provider name and repo name to envs ls --- internal/coderutil/env.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/coderutil/env.go b/internal/coderutil/env.go index f0bd4514..6b11f48e 100644 --- a/internal/coderutil/env.go +++ b/internal/coderutil/env.go @@ -74,6 +74,8 @@ func DefaultWorkspaceProvider(ctx context.Context, c coder.Client) (*coder.Kuber return nil, coder.ErrNotFound } +// EnvTable defines an Environment-like structure with associated entities composed in a human +// readable form. type EnvTable struct { Name string `table:"Name"` Image string `table:"Image"` From 6126c22b49b3064fdeb12bde1e09061a6e71b863 Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Thu, 11 Mar 2021 19:57:30 -0600 Subject: [PATCH 3/4] fixup! feat: add provider name and repo name to envs ls --- internal/coderutil/env.go | 43 +++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/internal/coderutil/env.go b/internal/coderutil/env.go index 6b11f48e..a372b730 100644 --- a/internal/coderutil/env.go +++ b/internal/coderutil/env.go @@ -4,7 +4,9 @@ import ( "context" "fmt" "net/url" + "sync" + "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "nhooyr.io/websocket" @@ -89,17 +91,9 @@ type EnvTable struct { // EnvsHumanTable performs the composition of each Environment with its associated ProviderName and ImageRepo. func EnvsHumanTable(ctx context.Context, client coder.Client, envs []coder.Environment) ([]EnvTable, error) { - imageMap := make(map[string]*coder.Image) - for _, e := range envs { - imageMap[e.ImageID] = nil - } - // TODO: make this concurrent - for id := range imageMap { - img, err := client.ImageByID(ctx, id) - if err != nil { - return nil, err - } - imageMap[id] = img + imageMap, err := makeImageMap(ctx, client, envs) + if err != nil { + return nil, err } pooledEnvs := make([]EnvTable, 0, len(envs)) @@ -129,3 +123,30 @@ func EnvsHumanTable(ctx context.Context, client coder.Client, envs []coder.Envir } return pooledEnvs, nil } + +func makeImageMap(ctx context.Context, client coder.Client, envs []coder.Environment) (map[string]*coder.Image, error) { + var mu sync.Mutex + var egroup errgroup.Group + imageMap := make(map[string]*coder.Image) + for _, e := range envs { + imageMap[e.ImageID] = nil + } + for id := range imageMap { + id := id + egroup.Go(func() error { + img, err := client.ImageByID(ctx, id) + if err != nil { + return err + } + mu.Lock() + defer mu.Unlock() + imageMap[id] = img + + return nil + }) + } + if err := egroup.Wait(); err != nil { + return nil, err + } + return imageMap, nil +} From 4af33c293523899e4b12970aa554c7016fc5192c Mon Sep 17 00:00:00 2001 From: Charlie Moog Date: Fri, 12 Mar 2021 10:17:22 -0600 Subject: [PATCH 4/4] fixup! feat: add provider name and repo name to envs ls --- internal/coderutil/env.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/internal/coderutil/env.go b/internal/coderutil/env.go index a372b730..cb141958 100644 --- a/internal/coderutil/env.go +++ b/internal/coderutil/env.go @@ -6,11 +6,11 @@ import ( "net/url" "sync" - "golang.org/x/sync/errgroup" "golang.org/x/xerrors" "nhooyr.io/websocket" "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/pkg/clog" ) // DialEnvWsep dials the executor endpoint using the https://github.com/cdr/wsep message protocol. @@ -125,13 +125,22 @@ func EnvsHumanTable(ctx context.Context, client coder.Client, envs []coder.Envir } func makeImageMap(ctx context.Context, client coder.Client, envs []coder.Environment) (map[string]*coder.Image, error) { - var mu sync.Mutex - var egroup errgroup.Group + var ( + mu sync.Mutex + egroup = clog.LoggedErrGroup() + ) imageMap := make(map[string]*coder.Image) for _, e := range envs { + // put all the image IDs into a map to remove duplicates imageMap[e.ImageID] = nil } + ids := make([]string, 0, len(imageMap)) for id := range imageMap { + // put the deduplicated back into a slice + // so we can write to the map while iterating + ids = append(ids, id) + } + for _, id := range ids { id := id egroup.Go(func() error { img, err := client.ImageByID(ctx, id)