From 606ae897b7347e7efaab9fd53482f3ea8b517dd3 Mon Sep 17 00:00:00 2001 From: Spike Curtis Date: Mon, 22 Sep 2025 17:14:07 +0400 Subject: [PATCH] chore: refactor to directly create Client in Command Handlers (#19760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactors the CLI to create the `*codersdk.Client` in the handlers. This is groundwork for changing the `rootCmd.InitClient()` to use the new `ClientOption`​s. It also improves variable locality, scoping the Client to the handler. This makes misuse less likely and reduces the memory allocations to just the command being executed, rather than allocating a Client for every command regardless of whether it is executed. --- cli/autoupdate.go | 9 +- cli/configssh.go | 8 +- cli/create.go | 8 +- cli/delete.go | 7 +- cli/exp_mcp.go | 9 +- cli/exp_rpty.go | 10 +- cli/exp_scaletest.go | 54 ++++--- cli/exp_task_create.go | 7 +- cli/exp_task_delete.go | 9 +- cli/exp_task_list.go | 7 +- cli/exp_task_status.go | 7 +- cli/favorite.go | 15 +- cli/list.go | 7 +- cli/logout.go | 11 +- cli/netcheck.go | 11 +- cli/notifications.go | 33 ++-- cli/open.go | 22 +-- cli/organization.go | 8 +- cli/organizationmanage.go | 10 +- cli/organizationmembers.go | 30 ++-- cli/organizationroles.go | 22 ++- cli/organizationsettings.go | 19 +-- cli/ping.go | 20 +-- cli/portforward.go | 9 +- cli/provisionerjobs.go | 16 +- cli/provisioners.go | 7 +- cli/publickey.go | 12 +- cli/rename.go | 10 +- cli/restart.go | 7 +- cli/root.go | 192 +++++++++++------------ cli/schedule.go | 24 ++- cli/sharing.go | 21 ++- cli/show.go | 7 +- cli/speedtest.go | 20 +-- cli/ssh.go | 12 +- cli/start.go | 7 +- cli/state.go | 13 +- cli/stop.go | 9 +- cli/support.go | 6 +- cli/templatecreate.go | 6 +- cli/templatedelete.go | 9 +- cli/templateedit.go | 6 +- cli/templatelist.go | 8 +- cli/templatepresets.go | 6 +- cli/templatepull.go | 6 +- cli/templatepush.go | 6 +- cli/templateversionarchive.go | 18 +-- cli/templateversions.go | 14 +- cli/tokens.go | 22 ++- cli/update.go | 7 +- cli/usercreate.go | 7 +- cli/userdelete.go | 7 +- cli/usereditroles.go | 8 +- cli/userlist.go | 14 +- cli/userstatus.go | 7 +- cli/whoami.go | 7 +- enterprise/cli/externalworkspaces.go | 25 ++- enterprise/cli/features.go | 13 +- enterprise/cli/groupcreate.go | 6 +- enterprise/cli/groupdelete.go | 7 +- enterprise/cli/groupedit.go | 6 +- enterprise/cli/grouplist.go | 6 +- enterprise/cli/licenses.go | 22 ++- enterprise/cli/prebuilds.go | 18 ++- enterprise/cli/provisionerdaemonstart.go | 11 +- enterprise/cli/provisionerkeys.go | 18 ++- enterprise/cli/workspaceproxy.go | 31 ++-- 67 files changed, 605 insertions(+), 431 deletions(-) diff --git a/cli/autoupdate.go b/cli/autoupdate.go index 5e3db8f2fe..52ed0ffd64 100644 --- a/cli/autoupdate.go +++ b/cli/autoupdate.go @@ -12,18 +12,21 @@ import ( ) func (r *RootCmd) autoupdate() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "autoupdate ", Short: "Toggle auto-update policy for a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + policy := strings.ToLower(inv.Args[1]) - err := validateAutoUpdatePolicy(policy) + err = validateAutoUpdatePolicy(policy) if err != nil { return xerrors.Errorf("validate policy: %w", err) } diff --git a/cli/configssh.go b/cli/configssh.go index b12b9d5c3d..7676e82c4a 100644 --- a/cli/configssh.go +++ b/cli/configssh.go @@ -236,7 +236,6 @@ func (r *RootCmd) configSSH() *serpent.Command { dryRun bool coderCliPath string ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "config-ssh", @@ -253,9 +252,13 @@ func (r *RootCmd) configSSH() *serpent.Command { ), Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() if sshConfigOpts.waitEnum != "auto" && sshConfigOpts.skipProxyCommand { @@ -280,7 +283,6 @@ func (r *RootCmd) configSSH() *serpent.Command { out = inv.Stderr } - var err error coderBinary := coderCliPath if coderBinary == "" { coderBinary, err = currentBinPath(out) diff --git a/cli/create.go b/cli/create.go index 59ab0ba0fa..05fe0824b5 100644 --- a/cli/create.go +++ b/cli/create.go @@ -50,7 +50,6 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command { // shares the same name across multiple organizations. orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "create [workspace]", @@ -61,9 +60,12 @@ func (r *RootCmd) Create(opts CreateOptions) *serpent.Command { Command: "coder create /", }, ), - Middleware: serpent.Chain(r.InitClient(client)), Handler: func(inv *serpent.Invocation) error { - var err error + client, err := r.InitClient(inv) + if err != nil { + return err + } + workspaceOwner := codersdk.Me if len(inv.Args) >= 1 { workspaceOwner, workspaceName, err = splitNamedWorkspace(inv.Args[0]) diff --git a/cli/delete.go b/cli/delete.go index a0988bb4cd..88e56405d6 100644 --- a/cli/delete.go +++ b/cli/delete.go @@ -16,7 +16,6 @@ func (r *RootCmd) deleteWorkspace() *serpent.Command { orphan bool prov buildFlags ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "delete ", @@ -29,9 +28,13 @@ func (r *RootCmd) deleteWorkspace() *serpent.Command { ), Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return err diff --git a/cli/exp_mcp.go b/cli/exp_mcp.go index ddc31d9b61..dfeac3669e 100644 --- a/cli/exp_mcp.go +++ b/cli/exp_mcp.go @@ -399,7 +399,6 @@ type mcpServer struct { func (r *RootCmd) mcpServer() *serpent.Command { var ( - client = new(codersdk.Client) instructions string allowedTools []string appStatusSlug string @@ -409,6 +408,11 @@ func (r *RootCmd) mcpServer() *serpent.Command { cmd := &serpent.Command{ Use: "server", Handler: func(inv *serpent.Invocation) error { + client, err := r.TryInitClient(inv) + if err != nil { + return err + } + var lastReport taskReport // Create a queue that skips duplicates and preserves summaries. queue := cliutil.NewQueue[taskReport](512).WithPredicate(func(report taskReport) (taskReport, bool) { @@ -548,9 +552,6 @@ func (r *RootCmd) mcpServer() *serpent.Command { return srv.startServer(ctx, inv, instructions, allowedTools) }, Short: "Start the Coder MCP server.", - Middleware: serpent.Chain( - r.TryInitClient(client), - ), Options: []serpent.Option{ { Name: "instructions", diff --git a/cli/exp_rpty.go b/cli/exp_rpty.go index 196328b647..e289a793b8 100644 --- a/cli/exp_rpty.go +++ b/cli/exp_rpty.go @@ -22,16 +22,17 @@ import ( ) func (r *RootCmd) rptyCommand() *serpent.Command { - var ( - client = new(codersdk.Client) - args handleRPTYArgs - ) + var args handleRPTYArgs cmd := &serpent.Command{ Handler: func(inv *serpent.Invocation) error { if r.disableDirect { return xerrors.New("direct connections are disabled, but you can try websocat ;-)") } + client, err := r.InitClient(inv) + if err != nil { + return err + } args.NamedWorkspace = inv.Args[0] args.Command = inv.Args[1:] return handleRPTY(inv, client, args) @@ -39,7 +40,6 @@ func (r *RootCmd) rptyCommand() *serpent.Command { Long: "Establish an RPTY session with a workspace/agent. This uses the same mechanism as the Web Terminal.", Middleware: serpent.Chain( serpent.RequireRangeArgs(1, -1), - r.InitClient(client), ), Options: []serpent.Option{ { diff --git a/cli/exp_scaletest.go b/cli/exp_scaletest.go index 9f032b2070..a4b8b3f53d 100644 --- a/cli/exp_scaletest.go +++ b/cli/exp_scaletest.go @@ -395,18 +395,17 @@ func (r *userCleanupRunner) Run(ctx context.Context, _ string, _ io.Writer) erro func (r *RootCmd) scaletestCleanup() *serpent.Command { var template string - cleanupStrategy := &scaletestStrategyFlags{cleanup: true} - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "cleanup", Short: "Cleanup scaletest workspaces, then cleanup scaletest users.", Long: "The strategy flags will apply to each stage of the cleanup process.", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() me, err := requireAdmin(ctx, client) @@ -551,14 +550,16 @@ func (r *RootCmd) scaletestCreateWorkspaces() *serpent.Command { output = &scaletestOutputFlags{} ) - client := new(codersdk.Client) - cmd := &serpent.Command{ - Use: "create-workspaces", - Short: "Creates many users, then creates a workspace for each user and waits for them finish building and fully come online. Optionally runs a command inside each workspace, and connects to the workspace over WireGuard.", - Long: `It is recommended that all rate limits are disabled on the server before running this scaletest. This test generates many login events which will be rate limited against the (most likely single) IP.`, - Middleware: r.InitClient(client), + Use: "create-workspaces", + Short: "Creates many users, then creates a workspace for each user and waits for them finish building and fully come online. Optionally runs a command inside each workspace, and connects to the workspace over WireGuard.", + Long: `It is recommended that all rate limits are disabled on the server before running this scaletest. This test generates many login events which will be rate limited against the (most likely single) IP.`, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() me, err := requireAdmin(ctx, client) @@ -861,7 +862,6 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { targetWorkspaces string workspaceProxyURL string - client = &codersdk.Client{} tracingFlags = &scaletestTracingFlags{} strategy = &scaletestStrategyFlags{} cleanupStrategy = &scaletestStrategyFlags{cleanup: true} @@ -872,10 +872,12 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { cmd := &serpent.Command{ Use: "workspace-traffic", Short: "Generate traffic to scaletest workspaces through coderd", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) (err error) { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() notifyCtx, stop := signal.NotifyContext(ctx, StopSignals...) // Checked later. @@ -1150,13 +1152,11 @@ func (r *RootCmd) scaletestWorkspaceTraffic() *serpent.Command { func (r *RootCmd) scaletestDashboard() *serpent.Command { var ( - interval time.Duration - jitter time.Duration - headless bool - randSeed int64 - targetUsers string - - client = &codersdk.Client{} + interval time.Duration + jitter time.Duration + headless bool + randSeed int64 + targetUsers string tracingFlags = &scaletestTracingFlags{} strategy = &scaletestStrategyFlags{} cleanupStrategy = &scaletestStrategyFlags{cleanup: true} @@ -1167,10 +1167,12 @@ func (r *RootCmd) scaletestDashboard() *serpent.Command { cmd := &serpent.Command{ Use: "dashboard", Short: "Generate traffic to the HTTP API to simulate use of the dashboard.", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + if !(interval > 0) { return xerrors.Errorf("--interval must be greater than zero") } diff --git a/cli/exp_task_create.go b/cli/exp_task_create.go index ad5d51a3a4..992be74ffc 100644 --- a/cli/exp_task_create.go +++ b/cli/exp_task_create.go @@ -16,7 +16,6 @@ import ( func (r *RootCmd) taskCreate() *serpent.Command { var ( orgContext = NewOrganizationContext() - client = new(codersdk.Client) templateName string templateVersionName string @@ -30,7 +29,6 @@ func (r *RootCmd) taskCreate() *serpent.Command { Short: "Create an experimental task", Middleware: serpent.Chain( serpent.RequireRangeArgs(0, 1), - r.InitClient(client), ), Options: serpent.OptionSet{ { @@ -67,6 +65,11 @@ func (r *RootCmd) taskCreate() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + var ( ctx = inv.Context() expClient = codersdk.NewExperimentalClient(client) diff --git a/cli/exp_task_delete.go b/cli/exp_task_delete.go index 5429ef2809..361a0cfe7c 100644 --- a/cli/exp_task_delete.go +++ b/cli/exp_task_delete.go @@ -16,20 +16,21 @@ import ( ) func (r *RootCmd) taskDelete() *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "delete [ ...]", Short: "Delete experimental tasks", Middleware: serpent.Chain( serpent.RequireRangeArgs(1, -1), - r.InitClient(client), ), Options: serpent.OptionSet{ cliui.SkipPromptOption(), }, Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } exp := codersdk.NewExperimentalClient(client) type toDelete struct { @@ -70,7 +71,7 @@ func (r *RootCmd) taskDelete() *serpent.Command { for _, it := range items { displayList = append(displayList, it.Display) } - _, err := cliui.Prompt(inv, cliui.PromptOptions{ + _, err = cliui.Prompt(inv, cliui.PromptOptions{ Text: fmt.Sprintf("Delete these tasks: %s?", pretty.Sprint(cliui.DefaultStyles.Code, strings.Join(displayList, ", "))), IsConfirm: true, Default: cliui.ConfirmNo, diff --git a/cli/exp_task_list.go b/cli/exp_task_list.go index 70d68f8121..18b2bec95d 100644 --- a/cli/exp_task_list.go +++ b/cli/exp_task_list.go @@ -38,7 +38,6 @@ func (r *RootCmd) taskList() *serpent.Command { user string quiet bool - client = new(codersdk.Client) formatter = cliui.NewOutputFormatter( cliui.TableFormat( []taskListRow{}, @@ -73,7 +72,6 @@ func (r *RootCmd) taskList() *serpent.Command { Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Options: serpent.OptionSet{ { @@ -108,6 +106,11 @@ func (r *RootCmd) taskList() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() exp := codersdk.NewExperimentalClient(client) diff --git a/cli/exp_task_status.go b/cli/exp_task_status.go index 7b4b75c1a8..328499d9d3 100644 --- a/cli/exp_task_status.go +++ b/cli/exp_task_status.go @@ -15,7 +15,6 @@ import ( func (r *RootCmd) taskStatus() *serpent.Command { var ( - client = new(codersdk.Client) formatter = cliui.NewOutputFormatter( cliui.TableFormat( []taskStatusRow{}, @@ -66,9 +65,13 @@ func (r *RootCmd) taskStatus() *serpent.Command { }, Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(i *serpent.Invocation) error { + client, err := r.InitClient(i) + if err != nil { + return err + } + ctx := i.Context() ec := codersdk.NewExperimentalClient(client) identifier := i.Args[0] diff --git a/cli/favorite.go b/cli/favorite.go index efb731abb3..7fdf47270e 100644 --- a/cli/favorite.go +++ b/cli/favorite.go @@ -5,12 +5,10 @@ import ( "golang.org/x/xerrors" - "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" ) func (r *RootCmd) favorite() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Aliases: []string{"fav", "favou" + "rite"}, Annotations: workspaceCommand, @@ -18,9 +16,13 @@ func (r *RootCmd) favorite() *serpent.Command { Short: "Add a workspace to your favorites", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ws, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) @@ -37,7 +39,6 @@ func (r *RootCmd) favorite() *serpent.Command { } func (r *RootCmd) unfavorite() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Aliases: []string{"unfav", "unfavou" + "rite"}, Annotations: workspaceCommand, @@ -45,9 +46,13 @@ func (r *RootCmd) unfavorite() *serpent.Command { Short: "Remove a workspace from your favorites", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ws, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) diff --git a/cli/list.go b/cli/list.go index 278895dfd7..be80843534 100644 --- a/cli/list.go +++ b/cli/list.go @@ -96,7 +96,6 @@ func (r *RootCmd) list() *serpent.Command { cliui.JSONFormat(), ) ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "list", @@ -104,9 +103,13 @@ func (r *RootCmd) list() *serpent.Command { Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + res, err := QueryConvertWorkspaces(inv.Context(), client, filter.Filter(), WorkspaceListRowFromWorkspace) if err != nil { return err diff --git a/cli/logout.go b/cli/logout.go index 6540003650..33cd55cc81 100644 --- a/cli/logout.go +++ b/cli/logout.go @@ -8,24 +8,23 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/cli/cliui" - "github.com/coder/coder/v2/codersdk" "github.com/coder/serpent" ) func (r *RootCmd) logout() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "logout", Short: "Unauthenticate your local session", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + var errors []error config := r.createConfig() - var err error _, err = cliui.Prompt(inv, cliui.PromptOptions{ Text: "Are you sure you want to log out?", IsConfirm: true, diff --git a/cli/netcheck.go b/cli/netcheck.go index 490ed25ce2..58a3dfe2ad 100644 --- a/cli/netcheck.go +++ b/cli/netcheck.go @@ -9,22 +9,21 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/healthcheck/derphealth" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/healthsdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) func (r *RootCmd) netcheck() *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "netcheck", Short: "Print network debug information for DERP and STUN", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx, cancel := context.WithTimeout(inv.Context(), 30*time.Second) defer cancel() diff --git a/cli/notifications.go b/cli/notifications.go index a0f1550537..5cd06c7f38 100644 --- a/cli/notifications.go +++ b/cli/notifications.go @@ -47,16 +47,19 @@ func (r *RootCmd) notifications() *serpent.Command { } func (r *RootCmd) pauseNotifications() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "pause", Short: "Pause notifications", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{ + client, err := r.InitClient(inv) + if err != nil { + return err + } + + err = client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{ NotifierPaused: true, }) if err != nil { @@ -71,16 +74,19 @@ func (r *RootCmd) pauseNotifications() *serpent.Command { } func (r *RootCmd) resumeNotifications() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "resume", Short: "Resume notifications", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - err := client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{ + client, err := r.InitClient(inv) + if err != nil { + return err + } + + err = client.PutNotificationsSettings(inv.Context(), codersdk.NotificationsSettings{ NotifierPaused: false, }) if err != nil { @@ -95,15 +101,18 @@ func (r *RootCmd) resumeNotifications() *serpent.Command { } func (r *RootCmd) testNotifications() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "test", Short: "Send a test notification", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + if err := client.PostTestNotification(inv.Context()); err != nil { return xerrors.Errorf("unable to post test notification: %w", err) } @@ -116,16 +125,18 @@ func (r *RootCmd) testNotifications() *serpent.Command { } func (r *RootCmd) customNotifications() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "custom <message>", Short: "Send a custom notification", Middleware: serpent.Chain( serpent.RequireNArgs(2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - err := client.PostCustomNotification(inv.Context(), codersdk.CustomNotificationRequest{ + client, err := r.InitClient(inv) + if err != nil { + return err + } + err = client.PostCustomNotification(inv.Context(), codersdk.CustomNotificationRequest{ Content: &codersdk.CustomNotificationContent{ Title: inv.Args[0], Message: inv.Args[1], diff --git a/cli/open.go b/cli/open.go index 83569e87e2..89e30e4c6d 100644 --- a/cli/open.go +++ b/cli/open.go @@ -41,24 +41,25 @@ const vscodeDesktopName = "VS Code Desktop" func (r *RootCmd) openVSCode() *serpent.Command { var ( - generateToken bool - testOpenError bool - appearanceConfig codersdk.AppearanceConfig + generateToken bool + testOpenError bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "vscode <workspace> [<directory in workspace>]", Short: fmt.Sprintf("Open a workspace in %s", vscodeDesktopName), Middleware: serpent.Chain( serpent.RequireRangeArgs(1, 2), - r.InitClient(client), - initAppearance(client, &appearanceConfig), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } ctx, cancel := context.WithCancel(inv.Context()) defer cancel() + appearanceConfig := initAppearance(ctx, client) // Check if we're inside a workspace, and especially inside _this_ // workspace so we can perform path resolution/expansion. Generally, @@ -299,15 +300,16 @@ func (r *RootCmd) openApp() *serpent.Command { testOpenError bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "app <workspace> <app slug>", Short: "Open a workspace application.", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx, cancel := context.WithCancel(inv.Context()) defer cancel() diff --git a/cli/organization.go b/cli/organization.go index 941219a0a6..9395b21b00 100644 --- a/cli/organization.go +++ b/cli/organization.go @@ -37,7 +37,6 @@ func (r *RootCmd) organizations() *serpent.Command { func (r *RootCmd) showOrganization(orgContext *OrganizationContext) *serpent.Command { var ( stringFormat func(orgs []codersdk.Organization) (string, error) - client = new(codersdk.Client) formatter = cliui.NewOutputFormatter( cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) { typed, ok := data.([]codersdk.Organization) @@ -77,7 +76,6 @@ func (r *RootCmd) showOrganization(orgContext *OrganizationContext) *serpent.Com }, ), Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireRangeArgs(0, 1), ), Options: serpent.OptionSet{ @@ -90,13 +88,17 @@ func (r *RootCmd) showOrganization(orgContext *OrganizationContext) *serpent.Com }, }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + orgArg := "selected" if len(inv.Args) >= 1 { orgArg = inv.Args[0] } var orgs []codersdk.Organization - var err error switch strings.ToLower(orgArg) { case "selected": stringFormat = func(orgs []codersdk.Organization) (string, error) { diff --git a/cli/organizationmanage.go b/cli/organizationmanage.go index 7baf323aa1..ce196a1682 100644 --- a/cli/organizationmanage.go +++ b/cli/organizationmanage.go @@ -12,22 +12,24 @@ import ( ) func (r *RootCmd) createOrganization() *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "create <organization name>", Short: "Create a new organization.", Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireNArgs(1), ), Options: serpent.OptionSet{ cliui.SkipPromptOption(), }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + orgName := inv.Args[0] - err := codersdk.NameValid(orgName) + err = codersdk.NameValid(orgName) if err != nil { return xerrors.Errorf("organization name %q is invalid: %w", orgName, err) } diff --git a/cli/organizationmembers.go b/cli/organizationmembers.go index 26208cb5db..60dca731da 100644 --- a/cli/organizationmembers.go +++ b/cli/organizationmembers.go @@ -31,16 +31,17 @@ func (r *RootCmd) organizationMembers(orgContext *OrganizationContext) *serpent. } func (r *RootCmd) removeOrganizationMember(orgContext *OrganizationContext) *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "remove <username | user_id>", Short: "Remove a new member to the current organization", Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } ctx := inv.Context() organization, err := orgContext.Selected(inv, client) if err != nil { @@ -62,16 +63,17 @@ func (r *RootCmd) removeOrganizationMember(orgContext *OrganizationContext) *ser } func (r *RootCmd) addOrganizationMember(orgContext *OrganizationContext) *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "add <username | user_id>", Short: "Add a new member to the current organization", Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } ctx := inv.Context() organization, err := orgContext.Selected(inv, client) if err != nil { @@ -93,16 +95,15 @@ func (r *RootCmd) addOrganizationMember(orgContext *OrganizationContext) *serpen } func (r *RootCmd) assignOrganizationRoles(orgContext *OrganizationContext) *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "edit-roles <username | user_id> [roles...]", Aliases: []string{"edit-role"}, Short: "Edit organization member's roles", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } ctx := inv.Context() organization, err := orgContext.Selected(inv, client) if err != nil { @@ -141,15 +142,18 @@ func (r *RootCmd) listOrganizationMembers(orgContext *OrganizationContext) *serp cliui.JSONFormat(), ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", Short: "List all organization members", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() organization, err := orgContext.Selected(inv, client) if err != nil { diff --git a/cli/organizationroles.go b/cli/organizationroles.go index 3651baea88..d6d867c6ee 100644 --- a/cli/organizationroles.go +++ b/cli/organizationroles.go @@ -54,14 +54,15 @@ func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpen cliui.JSONFormat(), ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "show [role_names ...]", Short: "Show role(s)", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() org, err := orgContext.Selected(inv, client) if err != nil { @@ -117,7 +118,6 @@ func (r *RootCmd) createOrganizationRole(orgContext *OrganizationContext) *serpe jsonInput bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create <role_name>", Short: "Create a new organization custom role", @@ -144,10 +144,13 @@ func (r *RootCmd) createOrganizationRole(orgContext *OrganizationContext) *serpe }, Middleware: serpent.Chain( serpent.RequireRangeArgs(0, 1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { return err @@ -240,7 +243,6 @@ func (r *RootCmd) updateOrganizationRole(orgContext *OrganizationContext) *serpe jsonInput bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "update <role_name>", Short: "Update an organization custom role", @@ -267,9 +269,13 @@ func (r *RootCmd) updateOrganizationRole(orgContext *OrganizationContext) *serpe }, Middleware: serpent.Chain( serpent.RequireRangeArgs(0, 1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() org, err := orgContext.Selected(inv, client) if err != nil { diff --git a/cli/organizationsettings.go b/cli/organizationsettings.go index 391a4f72e2..b2934ef006 100644 --- a/cli/organizationsettings.go +++ b/cli/organizationsettings.go @@ -95,7 +95,6 @@ type organizationSetting struct { } func (r *RootCmd) setOrganizationSettings(orgContext *OrganizationContext, settings []organizationSetting) *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "set", Short: "Update specified organization setting.", @@ -108,7 +107,6 @@ func (r *RootCmd) setOrganizationSettings(orgContext *OrganizationContext, setti Options: []serpent.Option{}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { return inv.Command.HelpHandler(inv) @@ -124,12 +122,15 @@ func (r *RootCmd) setOrganizationSettings(orgContext *OrganizationContext, setti Options: []serpent.Option{}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() var org codersdk.Organization - var err error if !set.DisableOrgContext { org, err = orgContext.Selected(inv, client) @@ -170,7 +171,6 @@ func (r *RootCmd) setOrganizationSettings(orgContext *OrganizationContext, setti } func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext, settings []organizationSetting) *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "show", Short: "Outputs specified organization setting.", @@ -183,7 +183,6 @@ func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext, sett Options: []serpent.Option{}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { return inv.Command.HelpHandler(inv) @@ -199,13 +198,15 @@ func (r *RootCmd) printOrganizationSetting(orgContext *OrganizationContext, sett Options: []serpent.Option{}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() var org codersdk.Organization - var err error - if !set.DisableOrgContext { org, err = orgContext.Selected(inv, client) if err != nil { diff --git a/cli/ping.go b/cli/ping.go index 29af06affe..f97f9ec0ae 100644 --- a/cli/ping.go +++ b/cli/ping.go @@ -84,28 +84,28 @@ func (s *pingSummary) Write(w io.Writer) { func (r *RootCmd) ping() *serpent.Command { var ( - pingNum int64 - pingTimeout time.Duration - pingWait time.Duration - pingTimeLocal bool - pingTimeUTC bool - appearanceConfig codersdk.AppearanceConfig + pingNum int64 + pingTimeout time.Duration + pingWait time.Duration + pingTimeLocal bool + pingTimeUTC bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "ping <workspace>", Short: "Ping a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), - initAppearance(client, &appearanceConfig), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } ctx, cancel := context.WithCancel(inv.Context()) defer cancel() - + appearanceConfig := initAppearance(ctx, client) notifyCtx, notifyCancel := inv.SignalNotifyContext(ctx, StopSignals...) defer notifyCancel() diff --git a/cli/portforward.go b/cli/portforward.go index 1b055d9e43..8c07eee2fe 100644 --- a/cli/portforward.go +++ b/cli/portforward.go @@ -38,9 +38,7 @@ func (r *RootCmd) portForward() *serpent.Command { tcpForwards []string // <port>:<port> udpForwards []string // <port>:<port> disableAutostart bool - appearanceConfig codersdk.AppearanceConfig ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "port-forward <workspace>", Short: `Forward ports from a workspace to the local machine. For reverse port forwarding, use "coder ssh -R".`, @@ -69,12 +67,15 @@ func (r *RootCmd) portForward() *serpent.Command { ), Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), - initAppearance(client, &appearanceConfig), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } ctx, cancel := context.WithCancel(inv.Context()) defer cancel() + appearanceConfig := initAppearance(ctx, client) specs, err := parsePortForwards(tcpForwards, udpForwards) if err != nil { diff --git a/cli/provisionerjobs.go b/cli/provisionerjobs.go index 2ddd04c5b6..3ce7da20b7 100644 --- a/cli/provisionerjobs.go +++ b/cli/provisionerjobs.go @@ -38,7 +38,6 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command { } var ( - client = new(codersdk.Client) orgContext = NewOrganizationContext() formatter = cliui.NewOutputFormatter( cliui.TableFormat([]provisionerJobRow{}, []string{"created at", "id", "type", "template display name", "status", "queue", "tags"}), @@ -54,10 +53,13 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command { Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) @@ -129,19 +131,19 @@ func (r *RootCmd) provisionerJobsList() *serpent.Command { } func (r *RootCmd) provisionerJobsCancel() *serpent.Command { - var ( - client = new(codersdk.Client) - orgContext = NewOrganizationContext() - ) + orgContext := NewOrganizationContext() cmd := &serpent.Command{ Use: "cancel <job_id>", Short: "Cancel a provisioner job", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) diff --git a/cli/provisioners.go b/cli/provisioners.go index 77f5e7705e..4198809c1f 100644 --- a/cli/provisioners.go +++ b/cli/provisioners.go @@ -35,7 +35,6 @@ func (r *RootCmd) provisionerList() *serpent.Command { OrganizationName string `json:"organization_name" table:"organization"` } var ( - client = new(codersdk.Client) orgContext = NewOrganizationContext() formatter = cliui.NewOutputFormatter( cliui.TableFormat([]provisionerDaemonRow{}, []string{"created at", "last seen at", "key name", "name", "version", "status", "tags"}), @@ -53,11 +52,13 @@ func (r *RootCmd) provisionerList() *serpent.Command { Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() - + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("current organization: %w", err) diff --git a/cli/publickey.go b/cli/publickey.go index 320ed86b2c..4862edf760 100644 --- a/cli/publickey.go +++ b/cli/publickey.go @@ -14,13 +14,15 @@ import ( func (r *RootCmd) publickey() *serpent.Command { var reset bool - client := new(codersdk.Client) cmd := &serpent.Command{ - Use: "publickey", - Aliases: []string{"pubkey"}, - Short: "Output your Coder public key used for Git operations", - Middleware: r.InitClient(client), + Use: "publickey", + Aliases: []string{"pubkey"}, + Short: "Output your Coder public key used for Git operations", Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } if reset { // Confirm prompt if using --reset. We don't want to accidentally // reset our public key. diff --git a/cli/rename.go b/cli/rename.go index 3bafa176d2..1e7413fed5 100644 --- a/cli/rename.go +++ b/cli/rename.go @@ -13,18 +13,20 @@ import ( ) func (r *RootCmd) rename() *serpent.Command { - var appearanceConfig codersdk.AppearanceConfig - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "rename <workspace> <new name>", Short: "Rename a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(2), - r.InitClient(client), - initAppearance(client, &appearanceConfig), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + appearanceConfig := initAppearance(inv.Context(), client) + workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return xerrors.Errorf("get workspace: %w", err) diff --git a/cli/restart.go b/cli/restart.go index 20ee0b9b9d..dff3897221 100644 --- a/cli/restart.go +++ b/cli/restart.go @@ -19,17 +19,20 @@ func (r *RootCmd) restart() *serpent.Command { bflags buildFlags ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "restart <workspace>", Short: "Restart a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Options: serpent.OptionSet{cliui.SkipPromptOption()}, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() out := inv.Stdout diff --git a/cli/root.go b/cli/root.go index cb11545ce4..09763fce44 100644 --- a/cli/root.go +++ b/cli/root.go @@ -494,110 +494,106 @@ type RootCmd struct { noFeatureWarning bool } -// InitClient authenticates the client with files from disk -// and injects header middlewares for telemetry, authentication, +// InitClient creates and configures a new client with authentication, telemetry, // and version checks. -func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc { - return func(next serpent.HandlerFunc) serpent.HandlerFunc { - return func(inv *serpent.Invocation) error { - conf := r.createConfig() - var err error - // Read the client URL stored on disk. - if r.clientURL == nil || r.clientURL.String() == "" { - rawURL, err := conf.URL().Read() - // If the configuration files are absent, the user is logged out - if os.IsNotExist(err) { - binPath, err := os.Executable() - if err != nil { - binPath = "coder" - } - return xerrors.Errorf(notLoggedInMessage, binPath) - } - if err != nil { - return err - } - - r.clientURL, err = url.Parse(strings.TrimSpace(rawURL)) - if err != nil { - return err - } - } - // Read the token stored on disk. - if r.token == "" { - r.token, err = conf.Session().Read() - // Even if there isn't a token, we don't care. - // Some API routes can be unauthenticated. - if err != nil && !os.IsNotExist(err) { - return err - } - } - - err = r.configureClient(inv.Context(), client, r.clientURL, inv) +func (r *RootCmd) InitClient(inv *serpent.Invocation) (*codersdk.Client, error) { + conf := r.createConfig() + var err error + // Read the client URL stored on disk. + if r.clientURL == nil || r.clientURL.String() == "" { + rawURL, err := conf.URL().Read() + // If the configuration files are absent, the user is logged out + if os.IsNotExist(err) { + binPath, err := os.Executable() if err != nil { - return err + binPath = "coder" } - client.SetSessionToken(r.token) + return nil, xerrors.Errorf(notLoggedInMessage, binPath) + } + if err != nil { + return nil, err + } - if r.debugHTTP { - client.PlainLogger = os.Stderr - client.SetLogBodies(true) - } - client.DisableDirectConnections = r.disableDirect - return next(inv) + r.clientURL, err = url.Parse(strings.TrimSpace(rawURL)) + if err != nil { + return nil, err } } + // Read the token stored on disk. + if r.token == "" { + r.token, err = conf.Session().Read() + // Even if there isn't a token, we don't care. + // Some API routes can be unauthenticated. + if err != nil && !os.IsNotExist(err) { + return nil, err + } + } + + client := &codersdk.Client{} + err = r.configureClient(inv.Context(), client, r.clientURL, inv) + if err != nil { + return nil, err + } + client.SetSessionToken(r.token) + + if r.debugHTTP { + client.PlainLogger = os.Stderr + client.SetLogBodies(true) + } + client.DisableDirectConnections = r.disableDirect + return client, nil } // TryInitClient is similar to InitClient but doesn't error when credentials are missing. // This allows commands to run without requiring authentication, but still use auth if available. -func (r *RootCmd) TryInitClient(client *codersdk.Client) serpent.MiddlewareFunc { - return func(next serpent.HandlerFunc) serpent.HandlerFunc { - return func(inv *serpent.Invocation) error { - conf := r.createConfig() - var err error - // Read the client URL stored on disk. - if r.clientURL == nil || r.clientURL.String() == "" { - rawURL, err := conf.URL().Read() - // If the configuration files are absent, just continue without URL - if err != nil { - // Continue with a nil or empty URL - if !os.IsNotExist(err) { - return err - } - } else { - r.clientURL, err = url.Parse(strings.TrimSpace(rawURL)) - if err != nil { - return err - } - } +func (r *RootCmd) TryInitClient(inv *serpent.Invocation) (*codersdk.Client, error) { + conf := r.createConfig() + var err error + // Read the client URL stored on disk. + if r.clientURL == nil || r.clientURL.String() == "" { + rawURL, err := conf.URL().Read() + // If the configuration files are absent, just continue without URL + if err != nil { + // Continue with a nil or empty URL + if !os.IsNotExist(err) { + return nil, err } - // Read the token stored on disk. - if r.token == "" { - r.token, err = conf.Session().Read() - // Even if there isn't a token, we don't care. - // Some API routes can be unauthenticated. - if err != nil && !os.IsNotExist(err) { - return err - } + } else { + r.clientURL, err = url.Parse(strings.TrimSpace(rawURL)) + if err != nil { + return nil, err } - - // Only configure the client if we have a URL - if r.clientURL != nil && r.clientURL.String() != "" { - err = r.configureClient(inv.Context(), client, r.clientURL, inv) - if err != nil { - return err - } - client.SetSessionToken(r.token) - - if r.debugHTTP { - client.PlainLogger = os.Stderr - client.SetLogBodies(true) - } - client.DisableDirectConnections = r.disableDirect - } - return next(inv) } } + // Read the token stored on disk. + if r.token == "" { + r.token, err = conf.Session().Read() + // Even if there isn't a token, we don't care. + // Some API routes can be unauthenticated. + if err != nil && !os.IsNotExist(err) { + return nil, err + } + } + + // Only configure the client if we have a URL + if r.clientURL != nil && r.clientURL.String() != "" { + client := &codersdk.Client{} + err = r.configureClient(inv.Context(), client, r.clientURL, inv) + if err != nil { + return nil, err + } + client.SetSessionToken(r.token) + + if r.debugHTTP { + client.PlainLogger = os.Stderr + client.SetLogBodies(true) + } + client.DisableDirectConnections = r.disableDirect + return client, nil + } + + // Return a minimal client if no URL is available + return &codersdk.Client{}, nil } // HeaderTransport creates a new transport that executes `--header-command` @@ -817,17 +813,13 @@ func namedWorkspace(ctx context.Context, client *codersdk.Client, identifier str return client.WorkspaceByOwnerAndName(ctx, owner, name, codersdk.WorkspaceOptions{}) } -func initAppearance(client *codersdk.Client, outConfig *codersdk.AppearanceConfig) serpent.MiddlewareFunc { - return func(next serpent.HandlerFunc) serpent.HandlerFunc { - return func(inv *serpent.Invocation) error { - cfg, _ := client.Appearance(inv.Context()) - if cfg.DocsURL == "" { - cfg.DocsURL = codersdk.DefaultDocsURL() - } - *outConfig = cfg - return next(inv) - } +func initAppearance(ctx context.Context, client *codersdk.Client) codersdk.AppearanceConfig { + // best effort + cfg, _ := client.Appearance(ctx) + if cfg.DocsURL == "" { + cfg.DocsURL = codersdk.DefaultDocsURL() } + return cfg } // createConfig consumes the global configuration flag to produce a config root. diff --git a/cli/schedule.go b/cli/schedule.go index b7d1ff9b1f..15f837bc16 100644 --- a/cli/schedule.go +++ b/cli/schedule.go @@ -90,16 +90,18 @@ func (r *RootCmd) scheduleShow() *serpent.Command { cliui.JSONFormat(), ) ) - client := new(codersdk.Client) showCmd := &serpent.Command{ Use: "show <workspace | --search <query> | --all>", Short: "Show workspace schedules", Long: scheduleShowDescriptionLong, Middleware: serpent.Chain( serpent.RequireRangeArgs(0, 1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } // To preserve existing behavior, if an argument is passed we will // only show the schedule for that workspace. // This will clobber the search query if one is passed. @@ -137,7 +139,6 @@ func (r *RootCmd) scheduleShow() *serpent.Command { } func (r *RootCmd) scheduleStart() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "start <workspace-name> { <start-time> [day-of-week] [location] | manual }", Long: scheduleStartDescriptionLong + "\n" + FormatExamples( @@ -149,9 +150,12 @@ func (r *RootCmd) scheduleStart() *serpent.Command { Short: "Edit workspace start schedule", Middleware: serpent.Chain( serpent.RequireRangeArgs(2, 4), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return err @@ -193,7 +197,6 @@ func (r *RootCmd) scheduleStart() *serpent.Command { } func (r *RootCmd) scheduleStop() *serpent.Command { - client := new(codersdk.Client) return &serpent.Command{ Use: "stop <workspace-name> { <duration> | manual }", Long: scheduleStopDescriptionLong + "\n" + FormatExamples( @@ -204,9 +207,12 @@ func (r *RootCmd) scheduleStop() *serpent.Command { Short: "Edit workspace stop schedule", Middleware: serpent.Chain( serpent.RequireNArgs(2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return err @@ -244,7 +250,6 @@ func (r *RootCmd) scheduleStop() *serpent.Command { } func (r *RootCmd) scheduleExtend() *serpent.Command { - client := new(codersdk.Client) extendCmd := &serpent.Command{ Use: "extend <workspace-name> <duration from now>", Aliases: []string{"override-stop"}, @@ -256,9 +261,12 @@ func (r *RootCmd) scheduleExtend() *serpent.Command { ), Middleware: serpent.Chain( serpent.RequireNArgs(2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } extendDuration, err := parseDuration(inv.Args[1]) if err != nil { return err diff --git a/cli/sharing.go b/cli/sharing.go index accc930ea4..f0f067fec0 100644 --- a/cli/sharing.go +++ b/cli/sharing.go @@ -36,17 +36,19 @@ func (r *RootCmd) sharing() *serpent.Command { } func (r *RootCmd) statusWorkspaceSharing() *serpent.Command { - client := new(codersdk.Client) - cmd := &serpent.Command{ Use: "status <workspace>", Short: "List all users and groups the given Workspace is shared with.", Aliases: []string{"list"}, Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return xerrors.Errorf("unable to fetch Workspace %s: %w", inv.Args[0], err) @@ -72,7 +74,6 @@ func (r *RootCmd) statusWorkspaceSharing() *serpent.Command { func (r *RootCmd) shareWorkspace() *serpent.Command { var ( - client = new(codersdk.Client) users []string groups []string @@ -98,10 +99,14 @@ func (r *RootCmd) shareWorkspace() *serpent.Command { }, }, Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + if len(users) == 0 && len(groups) == 0 { return xerrors.New("at least one user or group must be provided") } @@ -171,7 +176,6 @@ func (r *RootCmd) shareWorkspace() *serpent.Command { func (r *RootCmd) unshareWorkspace() *serpent.Command { var ( - client = new(codersdk.Client) users []string groups []string ) @@ -194,13 +198,16 @@ func (r *RootCmd) unshareWorkspace() *serpent.Command { }, }, Middleware: serpent.Chain( - r.InitClient(client), serpent.RequireNArgs(1), ), Handler: func(inv *serpent.Invocation) error { if len(users) == 0 && len(groups) == 0 { return xerrors.New("at least one user or group must be provided") } + client, err := r.InitClient(inv) + if err != nil { + return err + } workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { diff --git a/cli/show.go b/cli/show.go index 284e8581f5..0a78a9e861 100644 --- a/cli/show.go +++ b/cli/show.go @@ -15,7 +15,6 @@ import ( ) func (r *RootCmd) show() *serpent.Command { - client := new(codersdk.Client) var details bool return &serpent.Command{ Use: "show <workspace>", @@ -30,9 +29,13 @@ func (r *RootCmd) show() *serpent.Command { }, Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + buildInfo, err := client.BuildInfo(inv.Context()) if err != nil { return xerrors.Errorf("get server version: %w", err) diff --git a/cli/speedtest.go b/cli/speedtest.go index 86d0e6a9ee..29f991bbcc 100644 --- a/cli/speedtest.go +++ b/cli/speedtest.go @@ -13,7 +13,6 @@ import ( "cdr.dev/slog" "cdr.dev/slog/sloggers/sloghuman" "github.com/coder/coder/v2/cli/cliui" - "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/codersdk/workspacesdk" "github.com/coder/serpent" ) @@ -36,12 +35,11 @@ type speedtestTableItem struct { func (r *RootCmd) speedtest() *serpent.Command { var ( - direct bool - duration time.Duration - direction string - pcapFile string - appearanceConfig codersdk.AppearanceConfig - formatter = cliui.NewOutputFormatter( + direct bool + duration time.Duration + direction string + pcapFile string + formatter = cliui.NewOutputFormatter( cliui.ChangeFormatterData(cliui.TableFormat([]speedtestTableItem{}, []string{"Interval", "Throughput"}), func(data any) (any, error) { res, ok := data.(SpeedtestResult) if !ok { @@ -65,19 +63,21 @@ func (r *RootCmd) speedtest() *serpent.Command { cliui.JSONFormat(), ) ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "speedtest <workspace>", Short: "Run upload and download tests from your machine to a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), - initAppearance(client, &appearanceConfig), ), Handler: func(inv *serpent.Invocation) error { ctx, cancel := context.WithCancel(inv.Context()) defer cancel() + client, err := r.InitClient(inv) + if err != nil { + return err + } + appearanceConfig := initAppearance(ctx, client) if direct && r.disableDirect { return xerrors.Errorf("--direct (-d) is incompatible with --%s", varDisableDirect) diff --git a/cli/ssh.go b/cli/ssh.go index a2f0db7327..609670b690 100644 --- a/cli/ssh.go +++ b/cli/ssh.go @@ -79,15 +79,12 @@ func (r *RootCmd) ssh() *serpent.Command { env []string usageApp string disableAutostart bool - appearanceConfig codersdk.AppearanceConfig networkInfoDir string networkInfoInterval time.Duration containerName string containerUser string ) - client := new(codersdk.Client) - wsClient := workspacesdk.New(client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "ssh <workspace> [command]", @@ -111,10 +108,15 @@ func (r *RootCmd) ssh() *serpent.Command { return next(i) } }, - r.InitClient(client), - initAppearance(client, &appearanceConfig), ), Handler: func(inv *serpent.Invocation) (retErr error) { + client, err := r.InitClient(inv) + if err != nil { + return err + } + appearanceConfig := initAppearance(inv.Context(), client) + wsClient := workspacesdk.New(client) + command := strings.Join(inv.Args[1:], " ") // Before dialing the SSH server over TCP, capture Interrupt signals diff --git a/cli/start.go b/cli/start.go index 66c96cc9c4..28fc151206 100644 --- a/cli/start.go +++ b/cli/start.go @@ -21,14 +21,12 @@ func (r *RootCmd) start() *serpent.Command { noWait bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "start <workspace>", Short: "Start a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Options: serpent.OptionSet{ { @@ -40,6 +38,11 @@ func (r *RootCmd) start() *serpent.Command { cliui.SkipPromptOption(), }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return err diff --git a/cli/state.go b/cli/state.go index 7469c77d6f..2b8e7f8cc6 100644 --- a/cli/state.go +++ b/cli/state.go @@ -28,16 +28,17 @@ func (r *RootCmd) state() *serpent.Command { func (r *RootCmd) statePull() *serpent.Command { var buildNumber int64 - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "pull <workspace> [file]", Short: "Pull a Terraform state file from a workspace.", Middleware: serpent.Chain( serpent.RequireRangeArgs(1, 2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - var err error + client, err := r.InitClient(inv) + if err != nil { + return err + } var build codersdk.WorkspaceBuild if buildNumber == 0 { workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) @@ -86,15 +87,17 @@ func buildNumberOption(n *int64) serpent.Option { func (r *RootCmd) statePush() *serpent.Command { var buildNumber int64 - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "push <workspace> <file>", Short: "Push a Terraform state file to a workspace.", Middleware: serpent.Chain( serpent.RequireNArgs(2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return err diff --git a/cli/stop.go b/cli/stop.go index ef4813ff0a..fb35e4a5e0 100644 --- a/cli/stop.go +++ b/cli/stop.go @@ -12,20 +12,23 @@ import ( func (r *RootCmd) stop() *serpent.Command { var bflags buildFlags - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "stop <workspace>", Short: "Stop a workspace", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Options: serpent.OptionSet{ cliui.SkipPromptOption(), }, Handler: func(inv *serpent.Invocation) error { - _, err := cliui.Prompt(inv, cliui.PromptOptions{ + client, err := r.InitClient(inv) + if err != nil { + return err + } + + _, err = cliui.Prompt(inv, cliui.PromptOptions{ Text: "Confirm stop workspace?", IsConfirm: true, }) diff --git a/cli/support.go b/cli/support.go index c55bab92cd..9e55c1d6d9 100644 --- a/cli/support.go +++ b/cli/support.go @@ -62,16 +62,18 @@ var supportBundleBlurb = cliui.Bold("This will collect the following information func (r *RootCmd) supportBundle() *serpent.Command { var outputPath string var coderURLOverride string - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "bundle <workspace> [<agent>]", Short: "Generate a support bundle to troubleshoot issues connecting to a workspace.", Long: `This command generates a file containing detailed troubleshooting information about the Coder deployment and workspace connections. You must specify a single workspace (and optionally an agent name).`, Middleware: serpent.Chain( serpent.RequireRangeArgs(0, 2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } var cliLogBuf bytes.Buffer cliLogW := sloghuman.Sink(&cliLogBuf) cliLog := slog.Make(cliLogW).Leveled(slog.LevelDebug) diff --git a/cli/templatecreate.go b/cli/templatecreate.go index c45277bec5..bd4f076d17 100644 --- a/cli/templatecreate.go +++ b/cli/templatecreate.go @@ -33,7 +33,6 @@ func (r *RootCmd) templateCreate() *serpent.Command { uploadFlags templateUploadFlags orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create [name]", Short: "DEPRECATED: Create a template from the current directory or as specified by flag", @@ -43,9 +42,12 @@ func (r *RootCmd) templateCreate() *serpent.Command { "Use `coder templates push` command for creating and updating templates. \n"+ "Use `coder templates edit` command for editing template settings. ", ), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } isTemplateSchedulingOptionsSet := failureTTL != 0 || dormancyThreshold != 0 || dormancyAutoDeletion != 0 if isTemplateSchedulingOptionsSet || requireActiveVersion { diff --git a/cli/templatedelete.go b/cli/templatedelete.go index 120693b952..0b2d0b91d0 100644 --- a/cli/templatedelete.go +++ b/cli/templatedelete.go @@ -16,13 +16,9 @@ import ( func (r *RootCmd) templateDelete() *serpent.Command { orgContext := NewOrganizationContext() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete [name...]", Short: "Delete templates", - Middleware: serpent.Chain( - r.InitClient(client), - ), Options: serpent.OptionSet{ cliui.SkipPromptOption(), }, @@ -32,7 +28,10 @@ func (r *RootCmd) templateDelete() *serpent.Command { templateNames = []string{} templates = []codersdk.Template{} ) - + client, err := r.InitClient(inv) + if err != nil { + return err + } organization, err := orgContext.Selected(inv, client) if err != nil { return err diff --git a/cli/templateedit.go b/cli/templateedit.go index fe0323449c..1f8c7ff5b1 100644 --- a/cli/templateedit.go +++ b/cli/templateedit.go @@ -37,16 +37,18 @@ func (r *RootCmd) templateEdit() *serpent.Command { disableEveryone bool orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "edit <template>", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Short: "Edit the metadata of a template by name.", Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } unsetAutostopRequirementDaysOfWeek := len(autostopRequirementDaysOfWeek) == 1 && autostopRequirementDaysOfWeek[0] == "none" requiresScheduling := (len(autostopRequirementDaysOfWeek) > 0 && !unsetAutostopRequirementDaysOfWeek) || autostopRequirementWeeks > 0 || diff --git a/cli/templatelist.go b/cli/templatelist.go index abd9a3600d..feb2809816 100644 --- a/cli/templatelist.go +++ b/cli/templatelist.go @@ -16,15 +16,15 @@ func (r *RootCmd) templateList() *serpent.Command { cliui.JSONFormat(), ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", Short: "List all the templates available for the organization", Aliases: []string{"ls"}, - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } templates, err := client.Templates(inv.Context(), codersdk.TemplateFilter{}) if err != nil { return err diff --git a/cli/templatepresets.go b/cli/templatepresets.go index 240abec313..2a2270b44c 100644 --- a/cli/templatepresets.go +++ b/cli/templatepresets.go @@ -50,7 +50,6 @@ func (r *RootCmd) templatePresetsList() *serpent.Command { cliui.TableFormat([]TemplatePresetRow{}, defaultColumns), cliui.JSONFormat(), ) - client := new(codersdk.Client) orgContext := NewOrganizationContext() var templateVersion string @@ -59,7 +58,6 @@ func (r *RootCmd) templatePresetsList() *serpent.Command { Use: "list <template>", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Short: "List all presets of the specified template. Defaults to the active template version.", Options: serpent.OptionSet{ @@ -71,6 +69,10 @@ func (r *RootCmd) templatePresetsList() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } organization, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) diff --git a/cli/templatepull.go b/cli/templatepull.go index 3170e3cd58..322a11d8e3 100644 --- a/cli/templatepull.go +++ b/cli/templatepull.go @@ -23,13 +23,11 @@ func (r *RootCmd) templatePull() *serpent.Command { orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "pull <name> [destination]", Short: "Download the active, latest, or specified version of a template to a path.", Middleware: serpent.Chain( serpent.RequireRangeArgs(1, 2), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { var ( @@ -37,6 +35,10 @@ func (r *RootCmd) templatePull() *serpent.Command { templateName = inv.Args[0] dest string ) + client, err := r.InitClient(inv) + if err != nil { + return err + } if len(inv.Args) > 1 { dest = inv.Args[1] diff --git a/cli/templatepush.go b/cli/templatepush.go index d008df52b3..7a21a0f8de 100644 --- a/cli/templatepush.go +++ b/cli/templatepush.go @@ -37,15 +37,17 @@ func (r *RootCmd) templatePush() *serpent.Command { activate bool orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "push [template]", Short: "Create or update a template from the current directory or as specified by flag", Middleware: serpent.Chain( serpent.RequireRangeArgs(0, 1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } uploadFlags.setWorkdir(workdir) organization, err := orgContext.Selected(inv, client) diff --git a/cli/templateversionarchive.go b/cli/templateversionarchive.go index e9b41ff31d..964f2723b4 100644 --- a/cli/templateversionarchive.go +++ b/cli/templateversionarchive.go @@ -32,13 +32,9 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { } orgContext := NewOrganizationContext() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: presentVerb + " <template-name> [template-version-names...] ", Short: strings.ToUpper(string(presentVerb[0])) + presentVerb[1:] + " a template version(s).", - Middleware: serpent.Chain( - r.InitClient(client), - ), Options: serpent.OptionSet{ cliui.SkipPromptOption(), }, @@ -48,6 +44,11 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { versions []codersdk.TemplateVersion ) + client, err := r.InitClient(inv) + if err != nil { + return err + } + organization, err := orgContext.Selected(inv, client) if err != nil { return err @@ -100,14 +101,10 @@ func (r *RootCmd) setArchiveTemplateVersion(archive bool) *serpent.Command { func (r *RootCmd) archiveTemplateVersions() *serpent.Command { var all serpent.Bool - client := new(codersdk.Client) orgContext := NewOrganizationContext() cmd := &serpent.Command{ Use: "archive [template-name...] ", Short: "Archive unused or failed template versions from a given template(s)", - Middleware: serpent.Chain( - r.InitClient(client), - ), Options: serpent.OptionSet{ cliui.SkipPromptOption(), serpent.Option{ @@ -123,7 +120,10 @@ func (r *RootCmd) archiveTemplateVersions() *serpent.Command { templateNames = []string{} templates = []codersdk.Template{} ) - + client, err := r.InitClient(inv) + if err != nil { + return err + } organization, err := orgContext.Selected(inv, client) if err != nil { return err diff --git a/cli/templateversions.go b/cli/templateversions.go index c90903a7c4..c1323883eb 100644 --- a/cli/templateversions.go +++ b/cli/templateversions.go @@ -51,7 +51,6 @@ func (r *RootCmd) templateVersionsList() *serpent.Command { cliui.TableFormat([]templateVersionRow{}, defaultColumns), cliui.JSONFormat(), ) - client := new(codersdk.Client) orgContext := NewOrganizationContext() var includeArchived serpent.Bool @@ -60,7 +59,6 @@ func (r *RootCmd) templateVersionsList() *serpent.Command { Use: "list <template>", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), func(next serpent.HandlerFunc) serpent.HandlerFunc { return func(i *serpent.Invocation) error { // This is the only way to dynamically add the "archived" @@ -95,6 +93,10 @@ func (r *RootCmd) templateVersionsList() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } organization, err := orgContext.Selected(inv, client) if err != nil { return xerrors.Errorf("get current organization: %w", err) @@ -177,15 +179,15 @@ func (r *RootCmd) templateVersionsPromote() *serpent.Command { templateVersionName string orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "promote --template=<template_name> --template-version=<template_version_name>", Short: "Promote a template version to active.", Long: "Promote an existing template version to be the active version for the specified template.", - Middleware: serpent.Chain( - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } organization, err := orgContext.Selected(inv, client) if err != nil { return err diff --git a/cli/tokens.go b/cli/tokens.go index 7873882e3a..5d63f2e1ae 100644 --- a/cli/tokens.go +++ b/cli/tokens.go @@ -51,22 +51,24 @@ func (r *RootCmd) createToken() *serpent.Command { name string user string ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create", Short: "Create a token", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + userID := codersdk.Me if user != "" { userID = user } var parsedLifetime time.Duration - var err error tokenConfig, err := client.GetTokenConfig(inv.Context(), userID) if err != nil { @@ -168,16 +170,19 @@ func (r *RootCmd) listTokens() *serpent.Command { ) ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List tokens", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + tokens, err := client.Tokens(inv.Context(), codersdk.Me, codersdk.TokensFilter{ IncludeAll: all, }) @@ -222,16 +227,19 @@ func (r *RootCmd) listTokens() *serpent.Command { } func (r *RootCmd) removeToken() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "remove <name|id|token>", Aliases: []string{"delete"}, Short: "Delete a token", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + token, err := client.APIKeyByName(inv.Context(), codersdk.Me, inv.Args[0]) if err != nil { // If it's a token, we need to extract the ID diff --git a/cli/update.go b/cli/update.go index 21f090508d..5eda1b5598 100644 --- a/cli/update.go +++ b/cli/update.go @@ -15,7 +15,6 @@ func (r *RootCmd) update() *serpent.Command { parameterFlags workspaceParameterFlags bflags buildFlags ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "update <workspace>", @@ -23,9 +22,13 @@ func (r *RootCmd) update() *serpent.Command { Long: "Use --always-prompt to change the parameter values of the workspace.", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + workspace, err := namedWorkspace(inv.Context(), client, inv.Args[0]) if err != nil { return err diff --git a/cli/usercreate.go b/cli/usercreate.go index 643e355465..c818ce5c26 100644 --- a/cli/usercreate.go +++ b/cli/usercreate.go @@ -26,15 +26,18 @@ func (r *RootCmd) userCreate() *serpent.Command { loginType string orgContext = NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create", Short: "Create a new user.", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + organization, err := orgContext.Selected(inv, client) if err != nil { return err diff --git a/cli/userdelete.go b/cli/userdelete.go index cb356bdc16..3154326264 100644 --- a/cli/userdelete.go +++ b/cli/userdelete.go @@ -6,22 +6,23 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/cli/cliui" - "github.com/coder/coder/v2/codersdk" "github.com/coder/pretty" "github.com/coder/serpent" ) func (r *RootCmd) userDelete() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete <username|user_id>", Short: "Delete a user by username or user_id.", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } user, err := client.User(ctx, inv.Args[0]) if err != nil { return xerrors.Errorf("fetch user: %w", err) diff --git a/cli/usereditroles.go b/cli/usereditroles.go index 5bdad7a668..12dae9b455 100644 --- a/cli/usereditroles.go +++ b/cli/usereditroles.go @@ -12,7 +12,6 @@ import ( ) func (r *RootCmd) userEditRoles() *serpent.Command { - client := new(codersdk.Client) var givenRoles []string cmd := &serpent.Command{ Use: "edit-roles <username|user_id>", @@ -26,8 +25,13 @@ func (r *RootCmd) userEditRoles() *serpent.Command { Value: serpent.StringArrayOf(&givenRoles), }, }, - Middleware: serpent.Chain(serpent.RequireNArgs(1), r.InitClient(client)), + Middleware: serpent.Chain(serpent.RequireNArgs(1)), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() user, err := client.User(ctx, inv.Args[0]) diff --git a/cli/userlist.go b/cli/userlist.go index e24281ad76..536290e656 100644 --- a/cli/userlist.go +++ b/cli/userlist.go @@ -18,7 +18,6 @@ func (r *RootCmd) userList() *serpent.Command { cliui.TableFormat([]codersdk.User{}, []string{"username", "email", "created at", "status"}), cliui.JSONFormat(), ) - client := new(codersdk.Client) var githubUserID int64 cmd := &serpent.Command{ @@ -27,7 +26,6 @@ func (r *RootCmd) userList() *serpent.Command { Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Options: serpent.OptionSet{ { @@ -40,6 +38,11 @@ func (r *RootCmd) userList() *serpent.Command { }, }, Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + req := codersdk.UsersRequest{} if githubUserID != 0 { req.Search = fmt.Sprintf("github_com_user_id:%d", githubUserID) @@ -69,7 +72,6 @@ func (r *RootCmd) userSingle() *serpent.Command { &userShowFormat{}, cliui.JSONFormat(), ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "show <username|user_id|'me'>", @@ -81,9 +83,13 @@ func (r *RootCmd) userSingle() *serpent.Command { ), Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + user, err := client.User(inv.Context(), inv.Args[0]) if err != nil { return err diff --git a/cli/userstatus.go b/cli/userstatus.go index 600eade312..54bbfdea66 100644 --- a/cli/userstatus.go +++ b/cli/userstatus.go @@ -33,8 +33,6 @@ func (r *RootCmd) createUserStatusCommand(sdkStatus codersdk.UserStatus) *serpen panic(fmt.Sprintf("%s is not supported", sdkStatus)) } - client := new(codersdk.Client) - var columns []string allColumns := []string{"username", "email", "created at", "status"} cmd := &serpent.Command{ @@ -48,9 +46,12 @@ func (r *RootCmd) createUserStatusCommand(sdkStatus codersdk.UserStatus) *serpen ), Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } identifier := inv.Args[0] if identifier == "" { return xerrors.Errorf("user identifier cannot be an empty string") diff --git a/cli/whoami.go b/cli/whoami.go index 9da5a674cf..2765652ebf 100644 --- a/cli/whoami.go +++ b/cli/whoami.go @@ -10,16 +10,19 @@ import ( ) func (r *RootCmd) whoami() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: workspaceCommand, Use: "whoami", Short: "Fetch authenticated user info for Coder deployment", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + ctx := inv.Context() // Fetch the user info resp, err := client.User(ctx, codersdk.Me) diff --git a/enterprise/cli/externalworkspaces.go b/enterprise/cli/externalworkspaces.go index 081cbb765e..4de11b0092 100644 --- a/enterprise/cli/externalworkspaces.go +++ b/enterprise/cli/externalworkspaces.go @@ -77,10 +77,12 @@ func (r *RootCmd) externalWorkspaceCreate() *serpent.Command { cmd := r.Create(opts) cmd.Use = "create [workspace]" cmd.Short = "Create a new external workspace" - cmd.Middleware = serpent.Chain( - cmd.Middleware, - serpent.RequireNArgs(1), - ) + newMiddlewares := []serpent.MiddlewareFunc{} + if cmd.Middleware != nil { + newMiddlewares = append(newMiddlewares, cmd.Middleware) + } + newMiddlewares = append(newMiddlewares, serpent.RequireNArgs(1)) + cmd.Middleware = serpent.Chain(newMiddlewares...) for i := range cmd.Options { if cmd.Options[i].Flag == "template" { @@ -93,7 +95,6 @@ func (r *RootCmd) externalWorkspaceCreate() *serpent.Command { // externalWorkspaceAgentInstructions prints the instructions for an external agent. func (r *RootCmd) externalWorkspaceAgentInstructions() *serpent.Command { - client := new(codersdk.Client) formatter := cliui.NewOutputFormatter( cliui.ChangeFormatterData(cliui.TextFormat(), func(data any) (any, error) { agent, ok := data.(externalAgent) @@ -109,8 +110,13 @@ func (r *RootCmd) externalWorkspaceAgentInstructions() *serpent.Command { cmd := &serpent.Command{ Use: "agent-instructions [user/]workspace[.agent]", Short: "Get the instructions for an external agent", - Middleware: serpent.Chain(r.InitClient(client), serpent.RequireNArgs(1)), + Middleware: serpent.Chain(serpent.RequireNArgs(1)), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + workspace, workspaceAgent, _, err := agpl.GetWorkspaceAndAgent(inv.Context(), inv, client, false, inv.Args[0]) if err != nil { return xerrors.Errorf("find workspace and agent: %w", err) @@ -162,7 +168,6 @@ func (r *RootCmd) externalWorkspaceList() *serpent.Command { cliui.JSONFormat(), ) ) - client := new(codersdk.Client) cmd := &serpent.Command{ Annotations: map[string]string{ "workspaces": "", @@ -172,9 +177,13 @@ func (r *RootCmd) externalWorkspaceList() *serpent.Command { Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + baseFilter := filter.Filter() if baseFilter.FilterQuery == "" { diff --git a/enterprise/cli/features.go b/enterprise/cli/features.go index 3796156149..6790ec7406 100644 --- a/enterprise/cli/features.go +++ b/enterprise/cli/features.go @@ -36,15 +36,16 @@ func (r *RootCmd) featuresList() *serpent.Command { columns []string outputFormat string ) - client := new(codersdk.Client) cmd := &serpent.Command{ - Use: "list", - Aliases: []string{"ls"}, - Middleware: serpent.Chain( - r.InitClient(client), - ), + Use: "list", + Aliases: []string{"ls"}, + Middleware: serpent.Chain(), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } entitlements, err := client.Entitlements(inv.Context()) var apiError *codersdk.Error if errors.As(err, &apiError) && apiError.StatusCode() == http.StatusNotFound { diff --git a/enterprise/cli/groupcreate.go b/enterprise/cli/groupcreate.go index 4222ddffc7..5b7c127b0e 100644 --- a/enterprise/cli/groupcreate.go +++ b/enterprise/cli/groupcreate.go @@ -19,16 +19,18 @@ func (r *RootCmd) groupCreate() *serpent.Command { orgContext = agpl.NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create <name>", Short: "Create a user group", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { diff --git a/enterprise/cli/groupdelete.go b/enterprise/cli/groupdelete.go index 7729f961c2..15d3473352 100644 --- a/enterprise/cli/groupdelete.go +++ b/enterprise/cli/groupdelete.go @@ -7,26 +7,27 @@ import ( agpl "github.com/coder/coder/v2/cli" "github.com/coder/coder/v2/cli/cliui" - "github.com/coder/coder/v2/codersdk" "github.com/coder/pretty" "github.com/coder/serpent" ) func (r *RootCmd) groupDelete() *serpent.Command { orgContext := agpl.NewOrganizationContext() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete <name>", Short: "Delete a user group", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { var ( ctx = inv.Context() groupName = inv.Args[0] ) + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { diff --git a/enterprise/cli/groupedit.go b/enterprise/cli/groupedit.go index 73dd3e13be..5d6a6b5cdb 100644 --- a/enterprise/cli/groupedit.go +++ b/enterprise/cli/groupedit.go @@ -24,19 +24,21 @@ func (r *RootCmd) groupEdit() *serpent.Command { rmUsers []string orgContext = agpl.NewOrganizationContext() ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "edit <name>", Short: "Edit a user group", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { var ( ctx = inv.Context() groupName = inv.Args[0] ) + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { diff --git a/enterprise/cli/grouplist.go b/enterprise/cli/grouplist.go index 3596fe7fe1..f038a8f018 100644 --- a/enterprise/cli/grouplist.go +++ b/enterprise/cli/grouplist.go @@ -20,16 +20,18 @@ func (r *RootCmd) groupList() *serpent.Command { ) orgContext := agpl.NewOrganizationContext() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", Short: "List user groups", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { diff --git a/enterprise/cli/licenses.go b/enterprise/cli/licenses.go index 1a730e1e82..8dd1a6c162 100644 --- a/enterprise/cli/licenses.go +++ b/enterprise/cli/licenses.go @@ -42,16 +42,18 @@ func (r *RootCmd) licenseAdd() *serpent.Command { license string debug bool ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "add [-f file | -l license]", Short: "Add license to Coder deployment", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - var err error + client, err := r.InitClient(inv) + if err != nil { + return err + } + switch { case filename != "" && license != "": return xerrors.New("only one of (--file, --license) may be specified") @@ -137,16 +139,19 @@ func validJWT(s string) error { func (r *RootCmd) licensesList() *serpent.Command { formatter := cliutil.NewLicenseFormatter() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", Short: "List licenses (including expired)", Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + licenses, err := client.Licenses(inv.Context()) if err != nil { return err @@ -170,16 +175,19 @@ func (r *RootCmd) licensesList() *serpent.Command { } func (r *RootCmd) licenseDelete() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete <id>", Short: "Delete license by ID", Aliases: []string{"del"}, Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + id, err := strconv.ParseInt(inv.Args[0], 10, 32) if err != nil { return xerrors.Errorf("license ID must be an integer: %s", inv.Args[0]) diff --git a/enterprise/cli/prebuilds.go b/enterprise/cli/prebuilds.go index a75b14dd70..305621903f 100644 --- a/enterprise/cli/prebuilds.go +++ b/enterprise/cli/prebuilds.go @@ -38,16 +38,19 @@ func (r *RootCmd) prebuilds() *serpent.Command { } func (r *RootCmd) pausePrebuilds() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "pause", Short: "Pause prebuilds", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - err := client.PutPrebuildsSettings(inv.Context(), codersdk.PrebuildsSettings{ + client, err := r.InitClient(inv) + if err != nil { + return err + } + + err = client.PutPrebuildsSettings(inv.Context(), codersdk.PrebuildsSettings{ ReconciliationPaused: true, }) if err != nil { @@ -62,16 +65,19 @@ func (r *RootCmd) pausePrebuilds() *serpent.Command { } func (r *RootCmd) resumePrebuilds() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "resume", Short: "Resume prebuilds", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { - err := client.PutPrebuildsSettings(inv.Context(), codersdk.PrebuildsSettings{ + client, err := r.InitClient(inv) + if err != nil { + return err + } + + err = client.PutPrebuildsSettings(inv.Context(), codersdk.PrebuildsSettings{ ReconciliationPaused: false, }) if err != nil { diff --git a/enterprise/cli/provisionerdaemonstart.go b/enterprise/cli/provisionerdaemonstart.go index 582e14e1c8..1869007a85 100644 --- a/enterprise/cli/provisionerdaemonstart.go +++ b/enterprise/cli/provisionerdaemonstart.go @@ -53,19 +53,16 @@ func (r *RootCmd) provisionerDaemonStart() *serpent.Command { prometheusAddress string ) orgContext := agpl.NewOrganizationContext() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "start", Short: "Run a provisioner daemon", - Middleware: serpent.Chain( - // disable checks and warnings because this command starts a daemon; it is - // not meant for humans typing commands. Furthermore, the checks are - // incompatible with PSK auth that this command uses - r.InitClient(client), - ), Handler: func(inv *serpent.Invocation) error { ctx, cancel := context.WithCancel(inv.Context()) defer cancel() + client, err := r.InitClient(inv) + if err != nil { + return err + } stopCtx, stopCancel := inv.SignalNotifyContext(ctx, agpl.StopSignalsNoInterrupt...) defer stopCancel() diff --git a/enterprise/cli/provisionerkeys.go b/enterprise/cli/provisionerkeys.go index f88a5ffe85..1a09797811 100644 --- a/enterprise/cli/provisionerkeys.go +++ b/enterprise/cli/provisionerkeys.go @@ -37,16 +37,18 @@ func (r *RootCmd) provisionerKeysCreate() *serpent.Command { rawTags []string ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create <name>", Short: "Create a new provisioner key", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { @@ -100,17 +102,19 @@ func (r *RootCmd) provisionerKeysList() *serpent.Command { ) ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "list", Short: "List provisioner keys in an organization", Aliases: []string{"ls"}, Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { @@ -147,16 +151,18 @@ func (r *RootCmd) provisionerKeysList() *serpent.Command { func (r *RootCmd) provisionerKeysDelete() *serpent.Command { orgContext := agpl.NewOrganizationContext() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete <name>", Short: "Delete a provisioner key", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } org, err := orgContext.Selected(inv, client) if err != nil { diff --git a/enterprise/cli/workspaceproxy.go b/enterprise/cli/workspaceproxy.go index 4c1ca829b6..8738497f9e 100644 --- a/enterprise/cli/workspaceproxy.go +++ b/enterprise/cli/workspaceproxy.go @@ -42,17 +42,19 @@ func (r *RootCmd) workspaceProxy() *serpent.Command { func (r *RootCmd) regenerateProxyToken() *serpent.Command { formatter := newUpdateProxyResponseFormatter() - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "regenerate-token <name|id>", Short: "Regenerate a workspace proxy authentication token. " + "This will invalidate the existing authentication token.", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } formatter.primaryAccessURL = client.URL.String() // This is cheeky, but you can also use a uuid string in // 'DeleteWorkspaceProxyByName' and it will work. @@ -112,16 +114,18 @@ func (r *RootCmd) patchProxy() *serpent.Command { }), ) ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "edit <name|id>", Short: "Edit a workspace proxy", Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } if proxyIcon == "" && displayName == "" && proxyName == "" { _ = inv.Command.HelpHandler(inv) return xerrors.Errorf("specify at least one field to update") @@ -187,7 +191,6 @@ func (r *RootCmd) patchProxy() *serpent.Command { } func (r *RootCmd) deleteProxy() *serpent.Command { - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "delete <name|id>", Short: "Delete a workspace proxy", @@ -196,10 +199,13 @@ func (r *RootCmd) deleteProxy() *serpent.Command { }, Middleware: serpent.Chain( serpent.RequireNArgs(1), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } wsproxy, err := client.WorkspaceProxyByName(ctx, inv.Args[0]) if err != nil { @@ -244,18 +250,19 @@ func (r *RootCmd) createProxy() *serpent.Command { return nil } - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "create", Short: "Create a workspace proxy", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } formatter.primaryAccessURL = client.URL.String() - var err error if proxyName == "" && !noPrompts { proxyName, err = cliui.Prompt(inv, cliui.PromptOptions{ Text: "Proxy Name:", @@ -362,17 +369,19 @@ func (r *RootCmd) listProxies() *serpent.Command { }), ) - client := new(codersdk.Client) cmd := &serpent.Command{ Use: "ls", Aliases: []string{"list"}, Short: "List all workspace proxies", Middleware: serpent.Chain( serpent.RequireNArgs(0), - r.InitClient(client), ), Handler: func(inv *serpent.Invocation) error { ctx := inv.Context() + client, err := r.InitClient(inv) + if err != nil { + return err + } proxies, err := client.WorkspaceProxies(ctx) if err != nil { return xerrors.Errorf("list workspace proxies: %w", err)