From 5e8927e73e4d2492b9cbc65f1f1b04134d37ae58 Mon Sep 17 00:00:00 2001 From: Garrett Date: Fri, 5 Feb 2021 21:50:20 +0000 Subject: [PATCH] Add commands for workspace providers --- .golangci.yml | 1 - coder-sdk/resourcepools.go | 65 ------------- coder-sdk/workspace_providers.go | 80 ++++++++++++++++ internal/cmd/cmd.go | 1 + internal/cmd/configssh.go | 24 ++--- internal/cmd/providers.go | 154 +++++++++++++++++++++++++++++++ internal/coderutil/env.go | 42 ++++----- 7 files changed, 268 insertions(+), 99 deletions(-) delete mode 100644 coder-sdk/resourcepools.go create mode 100644 coder-sdk/workspace_providers.go create mode 100644 internal/cmd/providers.go diff --git a/.golangci.yml b/.golangci.yml index bfe48ebf..423540d5 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,7 +39,6 @@ linters: - structcheck - stylecheck - typecheck - - noctx - nolintlint - rowserrcheck - scopelint diff --git a/coder-sdk/resourcepools.go b/coder-sdk/resourcepools.go deleted file mode 100644 index 2556aa37..00000000 --- a/coder-sdk/resourcepools.go +++ /dev/null @@ -1,65 +0,0 @@ -package coder - -import ( - "context" - "net/http" -) - -// ResourcePool defines an entity capable of deploying and acting as an ingress for Coder environments. -type ResourcePool struct { - ID string `json:"id"` - Name string `json:"name"` - Local bool `json:"local"` - ClusterAddress string `json:"cluster_address"` - DefaultNamespace string `json:"default_namespace"` - StorageClass string `json:"storage_class"` - ClusterDomainSuffix string `json:"cluster_domain_suffix"` - DevurlHost string `json:"devurl_host"` - NamespaceWhitelist []string `json:"namespace_whitelist"` - OrgWhitelist []string `json:"org_whitelist"` - SSHEnabled bool `json:"ssh_enabled"` - AccessURL string `json:"envproxy_access_url"` -} - -// ResourcePoolByID fetches a resource pool entity by its unique ID. -func (c *Client) ResourcePoolByID(ctx context.Context, id string) (*ResourcePool, error) { - var rp ResourcePool - if err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools/"+id, nil, &rp); err != nil { - return nil, err - } - return &rp, nil -} - -// DeleteResourcePoolByID deletes a resource pool entity from the Coder control plane. -func (c *Client) DeleteResourcePoolByID(ctx context.Context, id string) error { - return c.requestBody(ctx, http.MethodDelete, "/api/private/resource-pools/"+id, nil, nil) -} - -// ResourcePools fetches all resource pools known to the Coder control plane. -func (c *Client) ResourcePools(ctx context.Context) ([]ResourcePool, error) { - var pools []ResourcePool - if err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools", nil, &pools); err != nil { - return nil, err - } - return pools, nil -} - -// CreateResourcePoolReq defines the request parameters for creating a new resource pool entity. -type CreateResourcePoolReq struct { - Name string `json:"name"` - Local bool `json:"local"` - ClusterCA string `json:"cluster_ca"` - ClusterAddress string `json:"cluster_address"` - SAToken string `json:"sa_token"` - DefaultNamespace string `json:"default_namespace"` - StorageClass string `json:"storage_class"` - ClusterDomainSuffix string `json:"cluster_domain_suffix"` - DevurlHost string `json:"devurl_host"` - NamespaceWhitelist []string `json:"namespace_whitelist"` - OrgWhitelist []string `json:"org_whitelist"` -} - -// CreateResourcePool creates a new ResourcePool entity. -func (c *Client) CreateResourcePool(ctx context.Context, req CreateResourcePoolReq) error { - return c.requestBody(ctx, http.MethodPost, "/api/private/resource-pools", req, nil) -} diff --git a/coder-sdk/workspace_providers.go b/coder-sdk/workspace_providers.go new file mode 100644 index 00000000..d397c9f5 --- /dev/null +++ b/coder-sdk/workspace_providers.go @@ -0,0 +1,80 @@ +package coder + +import ( + "context" + "net/http" +) + +// WorkspaceProvider defines an entity capable of deploying and acting as an ingress for Coder environments. +type WorkspaceProvider struct { + ID string `json:"id" table:"-"` + Name string `json:"name" table:"Name"` + Status WorkspaceProviderStatus `json:"status" table:"Status"` + Local bool `json:"local" table:"-"` + ClusterAddress string `json:"cluster_address" table:"Cluster Address"` + DefaultNamespace string `json:"default_namespace" table:"Namespace"` + StorageClass string `json:"storage_class" table:"Storage Class"` + ClusterDomainSuffix string `json:"cluster_domain_suffix" table:"Cluster Domain Suffix"` + EnvproxyAccessURL string `json:"envproxy_access_url" validate:"required" table:"Access URL"` + DevurlHost string `json:"devurl_host" table:"Devurl Host"` + SSHEnabled bool `json:"ssh_enabled" table:"SSH Enabled"` + NamespaceWhitelist []string `json:"namespace_whitelist" table:"Namespace Allowlist"` + OrgWhitelist []string `json:"org_whitelist" table:"-"` +} + +// WorkspaceProviderStatus represents the configuration state of a workspace provider. +type WorkspaceProviderStatus string + +// Workspace Provider statuses. +const ( + WorkspaceProviderPending WorkspaceProviderStatus = "pending" + WorkspaceProviderReady WorkspaceProviderStatus = "ready" +) + +// WorkspaceProviderByID fetches a workspace provider entity by its unique ID. +func (c *Client) WorkspaceProviderByID(ctx context.Context, id string) (*WorkspaceProvider, error) { + var wp WorkspaceProvider + err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools/"+id, nil, &wp) + if err != nil { + return nil, err + } + return &wp, nil +} + +// WorkspaceProviders fetches all workspace providers known to the Coder control plane. +func (c *Client) WorkspaceProviders(ctx context.Context) ([]WorkspaceProvider, error) { + var providers []WorkspaceProvider + err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools", nil, &providers) + if err != nil { + return nil, err + } + return providers, nil +} + +// CreateWorkspaceProviderReq defines the request parameters for creating a new workspace provider entity. +type CreateWorkspaceProviderReq struct { + Name string `json:"name"` +} + +// CreateWorkspaceProviderRes defines the response from creating a new workspace provider entity. +type CreateWorkspaceProviderRes struct { + ID string `json:"id" table:"ID"` + Name string `json:"name" table:"Name"` + Status WorkspaceProviderStatus `json:"status" table:"Status"` + EnvproxyToken string `json:"envproxy_token" table:"Envproxy Token"` +} + +// CreateWorkspaceProvider creates a new WorkspaceProvider entity. +func (c *Client) CreateWorkspaceProvider(ctx context.Context, req CreateWorkspaceProviderReq) (*CreateWorkspaceProviderRes, error) { + var res CreateWorkspaceProviderRes + err := c.requestBody(ctx, http.MethodPost, "/api/private/resource-pools", req, &res) + if err != nil { + return nil, err + } + return &res, nil +} + +// DeleteWorkspaceProviderByID deletes a workspace provider entity from the Coder control plane. +func (c *Client) DeleteWorkspaceProviderByID(ctx context.Context, id string) error { + return c.requestBody(ctx, http.MethodDelete, "/api/private/resource-pools/"+id, nil, nil) +} diff --git a/internal/cmd/cmd.go b/internal/cmd/cmd.go index c268ff49..ddc39c9c 100644 --- a/internal/cmd/cmd.go +++ b/internal/cmd/cmd.go @@ -37,6 +37,7 @@ func Make() *cobra.Command { resourceCmd(), completionCmd(), imgsCmd(), + providersCmd(), genDocsCmd(app), ) app.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show verbose output") diff --git a/internal/cmd/configssh.go b/internal/cmd/configssh.go index 26cb0afe..a63d2b3a 100644 --- a/internal/cmd/configssh.go +++ b/internal/cmd/configssh.go @@ -104,16 +104,16 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st return xerrors.New("no environments found") } - envsWithPools, err := coderutil.EnvsWithPool(ctx, client, envs) + envsWithProviders, err := coderutil.EnvsWithProvider(ctx, client, envs) if err != nil { - return xerrors.Errorf("resolve env pools: %w", err) + return xerrors.Errorf("resolve env workspace providers: %w", err) } - if !sshAvailable(envsWithPools) { + if !sshAvailable(envsWithProviders) { return xerrors.New("SSH is disabled or not available for any environments in your Coder Enterprise deployment.") } - newConfig := makeNewConfigs(user.Username, envsWithPools, privateKeyFilepath) + newConfig := makeNewConfigs(user.Username, envsWithProviders, privateKeyFilepath) err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm) if err != nil { @@ -157,9 +157,9 @@ func removeOldConfig(config string) (string, bool) { } // sshAvailable returns true if SSH is available for at least one environment. -func sshAvailable(envs []coderutil.EnvWithPool) bool { +func sshAvailable(envs []coderutil.EnvWithWorkspaceProvider) bool { for _, env := range envs { - if env.Pool.SSHEnabled { + if env.WorkspaceProvider.SSHEnabled { return true } } @@ -174,19 +174,19 @@ func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath strin return ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKey), 0600) } -func makeNewConfigs(userName string, envs []coderutil.EnvWithPool, privateKeyFilepath string) string { +func makeNewConfigs(userName string, envs []coderutil.EnvWithWorkspaceProvider, privateKeyFilepath string) string { newConfig := fmt.Sprintf("\n%s\n%s\n\n", sshStartToken, sshStartMessage) for _, env := range envs { - if !env.Pool.SSHEnabled { - clog.LogWarn(fmt.Sprintf("SSH is not enabled for pool %q", env.Pool.Name), + if !env.WorkspaceProvider.SSHEnabled { + clog.LogWarn(fmt.Sprintf("SSH is not enabled for workspace provider %q", env.WorkspaceProvider.Name), clog.BlankLine, - clog.Tipf("ask an infrastructure administrator to enable SSH for this resource pool"), + clog.Tipf("ask an infrastructure administrator to enable SSH for this workspace provider"), ) continue } - u, err := url.Parse(env.Pool.AccessURL) + u, err := url.Parse(env.WorkspaceProvider.EnvproxyAccessURL) if err != nil { - clog.LogWarn("invalid access url", clog.Causef("malformed url: %q", env.Pool.AccessURL)) + clog.LogWarn("invalid access url", clog.Causef("malformed url: %q", env.WorkspaceProvider.EnvproxyAccessURL)) continue } newConfig += makeSSHConfig(u.Host, userName, env.Env.Name, privateKeyFilepath) diff --git a/internal/cmd/providers.go b/internal/cmd/providers.go new file mode 100644 index 00000000..8d5ff12b --- /dev/null +++ b/internal/cmd/providers.go @@ -0,0 +1,154 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" + "golang.org/x/xerrors" + + "cdr.dev/coder-cli/coder-sdk" + "cdr.dev/coder-cli/internal/x/xcobra" + "cdr.dev/coder-cli/pkg/clog" + "cdr.dev/coder-cli/pkg/tablewriter" +) + +func providersCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "providers", + Short: "Interact with Coder workspace providers", + Long: "Perform operations on the Coder Workspace Providers for the platform.", + Hidden: true, + } + + cmd.AddCommand( + createProviderCmd(), + listProviderCmd(), + deleteProviderCmd(), + ) + return cmd +} + +func createProviderCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "create [workspace_provider_name]", + Short: "create a new workspace provider.", + Args: xcobra.ExactArgs(1), + Long: "Create a new Coder workspace provider.", + Example: `# create a new workspace provider in a pending state +coder providers create my-new-workspace-provider`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + client, err := newClient(ctx) + if err != nil { + return err + } + + // ExactArgs(1) ensures our name value can't panic on an out of bounds. + createReq := &coder.CreateWorkspaceProviderReq{ + Name: args[0], + } + + wp, err := client.CreateWorkspaceProvider(ctx, *createReq) + if err != nil { + return xerrors.Errorf("create workspace provider: %w", err) + } + + err = tablewriter.WriteTable(1, func(i int) interface{} { + return *wp + }) + if err != nil { + return xerrors.Errorf("write table: %w", err) + } + return nil + }, + } + return cmd +} + +func listProviderCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "ls", + Short: "list workspace providers.", + Long: "List all Coder workspace providers.", + Example: `# list workspace providers +coder providers ls`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + client, err := newClient(ctx) + if err != nil { + return err + } + + wps, err := client.WorkspaceProviders(ctx) + if err != nil { + return xerrors.Errorf("list workspace providers: %w", err) + } + + err = tablewriter.WriteTable(len(wps), func(i int) interface{} { + return wps[i] + }) + if err != nil { + return xerrors.Errorf("write table: %w", err) + } + return nil + }, + } + return cmd +} + +func deleteProviderCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "rm [workspace_provider_name]", + Short: "remove a workspace provider.", + Long: "Remove an existing Coder workspace provider by name.", + Example: `# remove an existing workspace provider by name +coder providers rm my-workspace-provider`, + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + client, err := newClient(ctx) + if err != nil { + return err + } + + wps, err := client.WorkspaceProviders(ctx) + if err != nil { + return xerrors.Errorf("listing workspace providers: %w", err) + } + + egroup := clog.LoggedErrGroup() + for _, wpName := range args { + name := wpName + egroup.Go(func() error { + var id string + for _, wp := range wps { + if wp.Name == name { + id = wp.ID + } + } + if id == "" { + return clog.Error( + fmt.Sprintf(`failed to remove workspace provider "%s"`, name), + clog.Causef(`no workspace provider found by name "%s"`, name), + ) + } + + err = client.DeleteWorkspaceProviderByID(ctx, id) + if err != nil { + return clog.Error( + fmt.Sprintf(`failed to remove workspace provider "%s"`, name), + clog.Causef(err.Error()), + ) + } + + clog.LogSuccess(fmt.Sprintf(`removed workspace provider with name "%s"`, name)) + + return nil + }) + } + return egroup.Wait() + }, + } + return cmd +} diff --git a/internal/coderutil/env.go b/internal/coderutil/env.go index c961fa56..784b606d 100644 --- a/internal/coderutil/env.go +++ b/internal/coderutil/env.go @@ -11,15 +11,15 @@ import ( ) // DialEnvWsep dials the executor endpoint using the https://github.com/cdr/wsep message protocol. -// The proper resource pool access URL is used. +// The proper workspace provider envproxy access URL is used. func DialEnvWsep(ctx context.Context, client *coder.Client, env *coder.Environment) (*websocket.Conn, error) { - resourcePool, err := client.ResourcePoolByID(ctx, env.ResourcePoolID) + workspaceProvider, err := client.WorkspaceProviderByID(ctx, env.ResourcePoolID) if err != nil { - return nil, xerrors.Errorf("get env resource pool: %w", err) + return nil, xerrors.Errorf("get env workspace provider: %w", err) } - accessURL, err := url.Parse(resourcePool.AccessURL) + accessURL, err := url.Parse(workspaceProvider.EnvproxyAccessURL) if err != nil { - return nil, xerrors.Errorf("invalid resource pool access url: %w", err) + return nil, xerrors.Errorf("invalid workspace provider envproxy access url: %w", err) } conn, err := client.DialWsep(ctx, accessURL, env.ID) @@ -29,31 +29,31 @@ func DialEnvWsep(ctx context.Context, client *coder.Client, env *coder.Environme return conn, nil } -// EnvWithPool composes an Environment entity with its associated ResourcePool. -type EnvWithPool struct { - Env coder.Environment - Pool coder.ResourcePool +// EnvWithWorkspaceProvider composes an Environment entity with its associated WorkspaceProvider. +type EnvWithWorkspaceProvider struct { + Env coder.Environment + WorkspaceProvider coder.WorkspaceProvider } -// EnvsWithPool performs the composition of each Environment with its associated ResourcePool. -func EnvsWithPool(ctx context.Context, client *coder.Client, envs []coder.Environment) ([]EnvWithPool, error) { - pooledEnvs := make([]EnvWithPool, 0, len(envs)) - pools, err := client.ResourcePools(ctx) +// EnvsWithProvider performs the composition of each Environment with its associated WorkspaceProvider. +func EnvsWithProvider(ctx context.Context, client *coder.Client, envs []coder.Environment) ([]EnvWithWorkspaceProvider, error) { + pooledEnvs := make([]EnvWithWorkspaceProvider, 0, len(envs)) + providers, err := client.WorkspaceProviders(ctx) if err != nil { return nil, err } - poolMap := make(map[string]coder.ResourcePool, len(pools)) - for _, p := range pools { - poolMap[p.ID] = p + providerMap := make(map[string]coder.WorkspaceProvider, len(providers)) + for _, p := range providers { + providerMap[p.ID] = p } for _, e := range envs { - envPool, ok := poolMap[e.ResourcePoolID] + envProvider, ok := providerMap[e.ResourcePoolID] if !ok { - return nil, xerrors.Errorf("fetch env resource pool: %w", coder.ErrNotFound) + return nil, xerrors.Errorf("fetch env workspace provider: %w", coder.ErrNotFound) } - pooledEnvs = append(pooledEnvs, EnvWithPool{ - Env: e, - Pool: envPool, + pooledEnvs = append(pooledEnvs, EnvWithWorkspaceProvider{ + Env: e, + WorkspaceProvider: envProvider, }) } return pooledEnvs, nil pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy