Skip to content

Commit b039b93

Browse files
authored
Add commands for workspace providers (coder#238)
1 parent 82f5615 commit b039b93

File tree

7 files changed

+268
-99
lines changed

7 files changed

+268
-99
lines changed

.golangci.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ linters:
3939
- structcheck
4040
- stylecheck
4141
- typecheck
42-
- noctx
4342
- nolintlint
4443
- rowserrcheck
4544
- scopelint

coder-sdk/resourcepools.go

Lines changed: 0 additions & 65 deletions
This file was deleted.

coder-sdk/workspace_providers.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package coder
2+
3+
import (
4+
"context"
5+
"net/http"
6+
)
7+
8+
// WorkspaceProvider defines an entity capable of deploying and acting as an ingress for Coder environments.
9+
type WorkspaceProvider struct {
10+
ID string `json:"id" table:"-"`
11+
Name string `json:"name" table:"Name"`
12+
Status WorkspaceProviderStatus `json:"status" table:"Status"`
13+
Local bool `json:"local" table:"-"`
14+
ClusterAddress string `json:"cluster_address" table:"Cluster Address"`
15+
DefaultNamespace string `json:"default_namespace" table:"Namespace"`
16+
StorageClass string `json:"storage_class" table:"Storage Class"`
17+
ClusterDomainSuffix string `json:"cluster_domain_suffix" table:"Cluster Domain Suffix"`
18+
EnvproxyAccessURL string `json:"envproxy_access_url" validate:"required" table:"Access URL"`
19+
DevurlHost string `json:"devurl_host" table:"Devurl Host"`
20+
SSHEnabled bool `json:"ssh_enabled" table:"SSH Enabled"`
21+
NamespaceWhitelist []string `json:"namespace_whitelist" table:"Namespace Allowlist"`
22+
OrgWhitelist []string `json:"org_whitelist" table:"-"`
23+
}
24+
25+
// WorkspaceProviderStatus represents the configuration state of a workspace provider.
26+
type WorkspaceProviderStatus string
27+
28+
// Workspace Provider statuses.
29+
const (
30+
WorkspaceProviderPending WorkspaceProviderStatus = "pending"
31+
WorkspaceProviderReady WorkspaceProviderStatus = "ready"
32+
)
33+
34+
// WorkspaceProviderByID fetches a workspace provider entity by its unique ID.
35+
func (c *Client) WorkspaceProviderByID(ctx context.Context, id string) (*WorkspaceProvider, error) {
36+
var wp WorkspaceProvider
37+
err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools/"+id, nil, &wp)
38+
if err != nil {
39+
return nil, err
40+
}
41+
return &wp, nil
42+
}
43+
44+
// WorkspaceProviders fetches all workspace providers known to the Coder control plane.
45+
func (c *Client) WorkspaceProviders(ctx context.Context) ([]WorkspaceProvider, error) {
46+
var providers []WorkspaceProvider
47+
err := c.requestBody(ctx, http.MethodGet, "/api/private/resource-pools", nil, &providers)
48+
if err != nil {
49+
return nil, err
50+
}
51+
return providers, nil
52+
}
53+
54+
// CreateWorkspaceProviderReq defines the request parameters for creating a new workspace provider entity.
55+
type CreateWorkspaceProviderReq struct {
56+
Name string `json:"name"`
57+
}
58+
59+
// CreateWorkspaceProviderRes defines the response from creating a new workspace provider entity.
60+
type CreateWorkspaceProviderRes struct {
61+
ID string `json:"id" table:"ID"`
62+
Name string `json:"name" table:"Name"`
63+
Status WorkspaceProviderStatus `json:"status" table:"Status"`
64+
EnvproxyToken string `json:"envproxy_token" table:"Envproxy Token"`
65+
}
66+
67+
// CreateWorkspaceProvider creates a new WorkspaceProvider entity.
68+
func (c *Client) CreateWorkspaceProvider(ctx context.Context, req CreateWorkspaceProviderReq) (*CreateWorkspaceProviderRes, error) {
69+
var res CreateWorkspaceProviderRes
70+
err := c.requestBody(ctx, http.MethodPost, "/api/private/resource-pools", req, &res)
71+
if err != nil {
72+
return nil, err
73+
}
74+
return &res, nil
75+
}
76+
77+
// DeleteWorkspaceProviderByID deletes a workspace provider entity from the Coder control plane.
78+
func (c *Client) DeleteWorkspaceProviderByID(ctx context.Context, id string) error {
79+
return c.requestBody(ctx, http.MethodDelete, "/api/private/resource-pools/"+id, nil, nil)
80+
}

internal/cmd/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func Make() *cobra.Command {
3737
resourceCmd(),
3838
completionCmd(),
3939
imgsCmd(),
40+
providersCmd(),
4041
genDocsCmd(app),
4142
)
4243
app.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show verbose output")

internal/cmd/configssh.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -104,16 +104,16 @@ func configSSH(configpath *string, remove *bool) func(cmd *cobra.Command, _ []st
104104
return xerrors.New("no environments found")
105105
}
106106

107-
envsWithPools, err := coderutil.EnvsWithPool(ctx, client, envs)
107+
envsWithProviders, err := coderutil.EnvsWithProvider(ctx, client, envs)
108108
if err != nil {
109-
return xerrors.Errorf("resolve env pools: %w", err)
109+
return xerrors.Errorf("resolve env workspace providers: %w", err)
110110
}
111111

112-
if !sshAvailable(envsWithPools) {
112+
if !sshAvailable(envsWithProviders) {
113113
return xerrors.New("SSH is disabled or not available for any environments in your Coder Enterprise deployment.")
114114
}
115115

116-
newConfig := makeNewConfigs(user.Username, envsWithPools, privateKeyFilepath)
116+
newConfig := makeNewConfigs(user.Username, envsWithProviders, privateKeyFilepath)
117117

118118
err = os.MkdirAll(filepath.Dir(*configpath), os.ModePerm)
119119
if err != nil {
@@ -157,9 +157,9 @@ func removeOldConfig(config string) (string, bool) {
157157
}
158158

159159
// sshAvailable returns true if SSH is available for at least one environment.
160-
func sshAvailable(envs []coderutil.EnvWithPool) bool {
160+
func sshAvailable(envs []coderutil.EnvWithWorkspaceProvider) bool {
161161
for _, env := range envs {
162-
if env.Pool.SSHEnabled {
162+
if env.WorkspaceProvider.SSHEnabled {
163163
return true
164164
}
165165
}
@@ -174,19 +174,19 @@ func writeSSHKey(ctx context.Context, client *coder.Client, privateKeyPath strin
174174
return ioutil.WriteFile(privateKeyPath, []byte(key.PrivateKey), 0600)
175175
}
176176

177-
func makeNewConfigs(userName string, envs []coderutil.EnvWithPool, privateKeyFilepath string) string {
177+
func makeNewConfigs(userName string, envs []coderutil.EnvWithWorkspaceProvider, privateKeyFilepath string) string {
178178
newConfig := fmt.Sprintf("\n%s\n%s\n\n", sshStartToken, sshStartMessage)
179179
for _, env := range envs {
180-
if !env.Pool.SSHEnabled {
181-
clog.LogWarn(fmt.Sprintf("SSH is not enabled for pool %q", env.Pool.Name),
180+
if !env.WorkspaceProvider.SSHEnabled {
181+
clog.LogWarn(fmt.Sprintf("SSH is not enabled for workspace provider %q", env.WorkspaceProvider.Name),
182182
clog.BlankLine,
183-
clog.Tipf("ask an infrastructure administrator to enable SSH for this resource pool"),
183+
clog.Tipf("ask an infrastructure administrator to enable SSH for this workspace provider"),
184184
)
185185
continue
186186
}
187-
u, err := url.Parse(env.Pool.AccessURL)
187+
u, err := url.Parse(env.WorkspaceProvider.EnvproxyAccessURL)
188188
if err != nil {
189-
clog.LogWarn("invalid access url", clog.Causef("malformed url: %q", env.Pool.AccessURL))
189+
clog.LogWarn("invalid access url", clog.Causef("malformed url: %q", env.WorkspaceProvider.EnvproxyAccessURL))
190190
continue
191191
}
192192
newConfig += makeSSHConfig(u.Host, userName, env.Env.Name, privateKeyFilepath)

internal/cmd/providers.go

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/spf13/cobra"
7+
"golang.org/x/xerrors"
8+
9+
"cdr.dev/coder-cli/coder-sdk"
10+
"cdr.dev/coder-cli/internal/x/xcobra"
11+
"cdr.dev/coder-cli/pkg/clog"
12+
"cdr.dev/coder-cli/pkg/tablewriter"
13+
)
14+
15+
func providersCmd() *cobra.Command {
16+
cmd := &cobra.Command{
17+
Use: "providers",
18+
Short: "Interact with Coder workspace providers",
19+
Long: "Perform operations on the Coder Workspace Providers for the platform.",
20+
Hidden: true,
21+
}
22+
23+
cmd.AddCommand(
24+
createProviderCmd(),
25+
listProviderCmd(),
26+
deleteProviderCmd(),
27+
)
28+
return cmd
29+
}
30+
31+
func createProviderCmd() *cobra.Command {
32+
cmd := &cobra.Command{
33+
Use: "create [workspace_provider_name]",
34+
Short: "create a new workspace provider.",
35+
Args: xcobra.ExactArgs(1),
36+
Long: "Create a new Coder workspace provider.",
37+
Example: `# create a new workspace provider in a pending state
38+
coder providers create my-new-workspace-provider`,
39+
RunE: func(cmd *cobra.Command, args []string) error {
40+
ctx := cmd.Context()
41+
42+
client, err := newClient(ctx)
43+
if err != nil {
44+
return err
45+
}
46+
47+
// ExactArgs(1) ensures our name value can't panic on an out of bounds.
48+
createReq := &coder.CreateWorkspaceProviderReq{
49+
Name: args[0],
50+
}
51+
52+
wp, err := client.CreateWorkspaceProvider(ctx, *createReq)
53+
if err != nil {
54+
return xerrors.Errorf("create workspace provider: %w", err)
55+
}
56+
57+
err = tablewriter.WriteTable(1, func(i int) interface{} {
58+
return *wp
59+
})
60+
if err != nil {
61+
return xerrors.Errorf("write table: %w", err)
62+
}
63+
return nil
64+
},
65+
}
66+
return cmd
67+
}
68+
69+
func listProviderCmd() *cobra.Command {
70+
cmd := &cobra.Command{
71+
Use: "ls",
72+
Short: "list workspace providers.",
73+
Long: "List all Coder workspace providers.",
74+
Example: `# list workspace providers
75+
coder providers ls`,
76+
RunE: func(cmd *cobra.Command, args []string) error {
77+
ctx := cmd.Context()
78+
79+
client, err := newClient(ctx)
80+
if err != nil {
81+
return err
82+
}
83+
84+
wps, err := client.WorkspaceProviders(ctx)
85+
if err != nil {
86+
return xerrors.Errorf("list workspace providers: %w", err)
87+
}
88+
89+
err = tablewriter.WriteTable(len(wps), func(i int) interface{} {
90+
return wps[i]
91+
})
92+
if err != nil {
93+
return xerrors.Errorf("write table: %w", err)
94+
}
95+
return nil
96+
},
97+
}
98+
return cmd
99+
}
100+
101+
func deleteProviderCmd() *cobra.Command {
102+
cmd := &cobra.Command{
103+
Use: "rm [workspace_provider_name]",
104+
Short: "remove a workspace provider.",
105+
Long: "Remove an existing Coder workspace provider by name.",
106+
Example: `# remove an existing workspace provider by name
107+
coder providers rm my-workspace-provider`,
108+
RunE: func(cmd *cobra.Command, args []string) error {
109+
ctx := cmd.Context()
110+
client, err := newClient(ctx)
111+
if err != nil {
112+
return err
113+
}
114+
115+
wps, err := client.WorkspaceProviders(ctx)
116+
if err != nil {
117+
return xerrors.Errorf("listing workspace providers: %w", err)
118+
}
119+
120+
egroup := clog.LoggedErrGroup()
121+
for _, wpName := range args {
122+
name := wpName
123+
egroup.Go(func() error {
124+
var id string
125+
for _, wp := range wps {
126+
if wp.Name == name {
127+
id = wp.ID
128+
}
129+
}
130+
if id == "" {
131+
return clog.Error(
132+
fmt.Sprintf(`failed to remove workspace provider "%s"`, name),
133+
clog.Causef(`no workspace provider found by name "%s"`, name),
134+
)
135+
}
136+
137+
err = client.DeleteWorkspaceProviderByID(ctx, id)
138+
if err != nil {
139+
return clog.Error(
140+
fmt.Sprintf(`failed to remove workspace provider "%s"`, name),
141+
clog.Causef(err.Error()),
142+
)
143+
}
144+
145+
clog.LogSuccess(fmt.Sprintf(`removed workspace provider with name "%s"`, name))
146+
147+
return nil
148+
})
149+
}
150+
return egroup.Wait()
151+
},
152+
}
153+
return cmd
154+
}

0 commit comments

Comments
 (0)