diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 91bae7945e422..bb26591f1d669 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -7002,6 +7002,9 @@ const docTemplate = `{ }, "codersdk.CreateWorkspaceProxyRequest": { "type": "object", + "required": [ + "name" + ], "properties": { "display_name": { "type": "string" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 7e279b3643e56..c52f011a18a87 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -6226,6 +6226,7 @@ }, "codersdk.CreateWorkspaceProxyRequest": { "type": "object", + "required": ["name"], "properties": { "display_name": { "type": "string" diff --git a/codersdk/workspaceproxy.go b/codersdk/workspaceproxy.go index 23a275f53d9b2..8c8e49e63aebc 100644 --- a/codersdk/workspaceproxy.go +++ b/codersdk/workspaceproxy.go @@ -29,7 +29,7 @@ const ( ) type WorkspaceProxyStatus struct { - Status ProxyHealthStatus `json:"status" table:"status"` + Status ProxyHealthStatus `json:"status" table:"status,default_sort"` // Report provides more information about the health of the workspace proxy. Report ProxyHealthReport `json:"report,omitempty" table:"report"` CheckedAt time.Time `json:"checked_at" table:"checked_at" format:"date-time"` @@ -60,11 +60,11 @@ type WorkspaceProxy struct { // Status is the latest status check of the proxy. This will be empty for deleted // proxies. This value can be used to determine if a workspace proxy is healthy // and ready to use. - Status WorkspaceProxyStatus `json:"status,omitempty" table:"status"` + Status WorkspaceProxyStatus `json:"status,omitempty" table:"status,recursive"` } type CreateWorkspaceProxyRequest struct { - Name string `json:"name"` + Name string `json:"name" validate:"required"` DisplayName string `json:"display_name"` Icon string `json:"icon"` } diff --git a/docs/api/schemas.md b/docs/api/schemas.md index ee8e52e07a4a4..17e1dfe857efe 100644 --- a/docs/api/schemas.md +++ b/docs/api/schemas.md @@ -1615,7 +1615,7 @@ CreateParameterRequest is a structure used to create a new parameter value for a | -------------- | ------ | -------- | ------------ | ----------- | | `display_name` | string | false | | | | `icon` | string | false | | | -| `name` | string | false | | | +| `name` | string | true | | | ## codersdk.CreateWorkspaceRequest diff --git a/enterprise/cli/workspaceproxy.go b/enterprise/cli/workspaceproxy.go index 959ab3b509dce..54ecc21928595 100644 --- a/enterprise/cli/workspaceproxy.go +++ b/enterprise/cli/workspaceproxy.go @@ -2,7 +2,9 @@ package cli import ( "fmt" + "strings" + "github.com/fatih/color" "golang.org/x/xerrors" "github.com/coder/coder/cli/clibase" @@ -23,6 +25,7 @@ func (r *RootCmd) workspaceProxy() *clibase.Cmd { r.proxyServer(), r.createProxy(), r.deleteProxy(), + r.listProxies(), }, } @@ -66,7 +69,8 @@ func (r *RootCmd) createProxy() *clibase.Cmd { if !ok { return nil, xerrors.Errorf("unexpected type %T", data) } - return fmt.Sprintf("Workspace Proxy %q registered successfully\nToken: %s", response.Proxy.Name, response.ProxyToken), nil + return fmt.Sprintf("Workspace Proxy %q created successfully. Save this token, it will not be shown again."+ + "\nToken: %s", response.Proxy.Name, response.ProxyToken), nil }), cliui.JSONFormat(), // Table formatter expects a slice, make a slice of one. @@ -91,6 +95,10 @@ func (r *RootCmd) createProxy() *clibase.Cmd { ), Handler: func(inv *clibase.Invocation) error { ctx := inv.Context() + if proxyName == "" { + return xerrors.Errorf("proxy name is required") + } + resp, err := client.CreateWorkspaceProxy(ctx, codersdk.CreateWorkspaceProxyRequest{ Name: proxyName, DisplayName: displayName, @@ -140,3 +148,60 @@ func (r *RootCmd) createProxy() *clibase.Cmd { ) return cmd } + +func (r *RootCmd) listProxies() *clibase.Cmd { + formatter := cliui.NewOutputFormatter( + cliui.TableFormat([]codersdk.WorkspaceProxy{}, []string{"name", "url", "status status"}), + cliui.JSONFormat(), + cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) { + resp, ok := data.([]codersdk.WorkspaceProxy) + if !ok { + return nil, xerrors.Errorf("unexpected type %T", data) + } + var str strings.Builder + _, _ = str.WriteString("Workspace Proxies:\n") + sep := "" + for i, proxy := range resp { + _, _ = str.WriteString(sep) + _, _ = str.WriteString(fmt.Sprintf("%d: %s %s %s", i, proxy.Name, proxy.URL, proxy.Status.Status)) + for _, errMsg := range proxy.Status.Report.Errors { + _, _ = str.WriteString(color.RedString("\n\tErr: %s", errMsg)) + } + for _, warnMsg := range proxy.Status.Report.Errors { + _, _ = str.WriteString(color.YellowString("\n\tWarn: %s", warnMsg)) + } + sep = "\n" + } + return str.String(), nil + }), + ) + + client := new(codersdk.Client) + cmd := &clibase.Cmd{ + Use: "ls", + Aliases: []string{"list"}, + Short: "List all workspace proxies", + Middleware: clibase.Chain( + clibase.RequireNArgs(0), + r.InitClient(client), + ), + Handler: func(inv *clibase.Invocation) error { + ctx := inv.Context() + proxies, err := client.WorkspaceProxies(ctx) + if err != nil { + return xerrors.Errorf("list workspace proxies: %w", err) + } + + output, err := formatter.Format(ctx, proxies) + if err != nil { + return err + } + + _, err = fmt.Fprintln(inv.Stdout, output) + return err + }, + } + + formatter.AttachOptions(&cmd.Options) + return cmd +} diff --git a/enterprise/cli/workspaceproxy_test.go b/enterprise/cli/workspaceproxy_test.go index 6e486b4c94d3b..31989b047dd54 100644 --- a/enterprise/cli/workspaceproxy_test.go +++ b/enterprise/cli/workspaceproxy_test.go @@ -65,6 +65,21 @@ func Test_ProxyCRUD(t *testing.T) { _, err = uuid.Parse(parts[0]) require.NoError(t, err, "expected token to be a uuid") + // Fetch proxies and check output + inv, conf = newCLI( + t, + "proxy", "ls", + ) + + pty = ptytest.New(t) + inv.Stdout = pty.Output() + clitest.SetupConfig(t, client, conf) + + err = inv.WithContext(ctx).Run() + require.NoError(t, err) + pty.ExpectMatch(expectedName) + + // Also check via the api proxies, err := client.WorkspaceProxies(ctx) require.NoError(t, err, "failed to get workspace proxies") require.Len(t, proxies, 1, "expected 1 proxy")