chore: refactor to directly create Client in Command Handlers (#19760)

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.
This commit is contained in:
Spike Curtis
2025-09-22 17:14:07 +04:00
committed by GitHub
parent 12496830d6
commit 606ae897b7
67 changed files with 605 additions and 431 deletions
+6 -3
View File
@@ -12,18 +12,21 @@ import (
)
func (r *RootCmd) autoupdate() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Annotations: workspaceCommand,
Use: "autoupdate <workspace> <always|never>",
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)
}
+5 -3
View File
@@ -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)
+5 -3
View File
@@ -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 <username>/<workspace_name>",
},
),
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])
+5 -2
View File
@@ -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 <workspace>",
@@ -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
+5 -4
View File
@@ -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",
+5 -5
View File
@@ -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{
{
+20 -18
View File
@@ -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),
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.
@@ -1155,8 +1157,6 @@ func (r *RootCmd) scaletestDashboard() *serpent.Command {
headless bool
randSeed int64
targetUsers string
client = &codersdk.Client{}
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")
}
+5 -2
View File
@@ -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)
+5 -4
View File
@@ -16,20 +16,21 @@ import (
)
func (r *RootCmd) taskDelete() *serpent.Command {
client := new(codersdk.Client)
cmd := &serpent.Command{
Use: "delete <task> [<task> ...]",
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,
+5 -2
View File
@@ -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)
+5 -2
View File
@@ -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]
+10 -5
View File
@@ -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)
+5 -2
View File
@@ -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
+5 -6
View File
@@ -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,
+5 -6
View File
@@ -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()
+22 -11
View File
@@ -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 <title> <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],
+10 -8
View File
@@ -43,22 +43,23 @@ func (r *RootCmd) openVSCode() *serpent.Command {
var (
generateToken bool
testOpenError bool
appearanceConfig codersdk.AppearanceConfig
)
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()
+5 -3
View File
@@ -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) {
+6 -4
View File
@@ -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)
}
+17 -13
View File
@@ -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 {
+14 -8
View File
@@ -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 {
+10 -9
View File
@@ -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 {
+5 -5
View File
@@ -89,23 +89,23 @@ func (r *RootCmd) ping() *serpent.Command {
pingWait time.Duration
pingTimeLocal bool
pingTimeUTC bool
appearanceConfig codersdk.AppearanceConfig
)
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()
+5 -4
View File
@@ -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 {
+9 -7
View File
@@ -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)
+4 -3
View File
@@ -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)
+4 -2
View File
@@ -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),
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.
+6 -4
View File
@@ -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)
+5 -2
View File
@@ -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
+23 -31
View File
@@ -494,12 +494,9 @@ 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 {
func (r *RootCmd) InitClient(inv *serpent.Invocation) (*codersdk.Client, error) {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
@@ -511,15 +508,15 @@ func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc {
if err != nil {
binPath = "coder"
}
return xerrors.Errorf(notLoggedInMessage, binPath)
return nil, xerrors.Errorf(notLoggedInMessage, binPath)
}
if err != nil {
return err
return nil, err
}
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return err
return nil, err
}
}
// Read the token stored on disk.
@@ -528,13 +525,14 @@ func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc {
// 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
return nil, err
}
}
client := &codersdk.Client{}
err = r.configureClient(inv.Context(), client, r.clientURL, inv)
if err != nil {
return err
return nil, err
}
client.SetSessionToken(r.token)
@@ -543,16 +541,12 @@ func (r *RootCmd) InitClient(client *codersdk.Client) serpent.MiddlewareFunc {
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
return next(inv)
}
}
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 {
func (r *RootCmd) TryInitClient(inv *serpent.Invocation) (*codersdk.Client, error) {
conf := r.createConfig()
var err error
// Read the client URL stored on disk.
@@ -562,12 +556,12 @@ func (r *RootCmd) TryInitClient(client *codersdk.Client) serpent.MiddlewareFunc
if err != nil {
// Continue with a nil or empty URL
if !os.IsNotExist(err) {
return err
return nil, err
}
} else {
r.clientURL, err = url.Parse(strings.TrimSpace(rawURL))
if err != nil {
return err
return nil, err
}
}
}
@@ -577,15 +571,16 @@ func (r *RootCmd) TryInitClient(client *codersdk.Client) serpent.MiddlewareFunc
// 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
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 err
return nil, err
}
client.SetSessionToken(r.token)
@@ -594,10 +589,11 @@ func (r *RootCmd) TryInitClient(client *codersdk.Client) serpent.MiddlewareFunc
client.SetLogBodies(true)
}
client.DisableDirectConnections = r.disableDirect
return client, nil
}
return next(inv)
}
}
// 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())
func initAppearance(ctx context.Context, client *codersdk.Client) codersdk.AppearanceConfig {
// best effort
cfg, _ := client.Appearance(ctx)
if cfg.DocsURL == "" {
cfg.DocsURL = codersdk.DefaultDocsURL()
}
*outConfig = cfg
return next(inv)
}
}
return cfg
}
// createConfig consumes the global configuration flag to produce a config root.
+16 -8
View File
@@ -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
+14 -7
View File
@@ -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 {
+5 -2
View File
@@ -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)
+5 -5
View File
@@ -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"
)
@@ -40,7 +39,6 @@ func (r *RootCmd) speedtest() *serpent.Command {
duration time.Duration
direction string
pcapFile string
appearanceConfig codersdk.AppearanceConfig
formatter = cliui.NewOutputFormatter(
cliui.ChangeFormatterData(cliui.TableFormat([]speedtestTableItem{}, []string{"Interval", "Throughput"}), func(data any) (any, error) {
res, ok := data.(SpeedtestResult)
@@ -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)
+7 -5
View File
@@ -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
+5 -2
View File
@@ -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
+8 -5
View File
@@ -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
+6 -3
View File
@@ -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,
})
+4 -2
View File
@@ -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)
+4 -2
View File
@@ -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 {
+4 -5
View File
@@ -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
+4 -2
View File
@@ -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 ||
+4 -4
View File
@@ -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
+4 -2
View File
@@ -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)
+4 -2
View File
@@ -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]
+4 -2
View File
@@ -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)
+9 -9
View File
@@ -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
+8 -6
View File
@@ -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
+15 -7
View File
@@ -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
+5 -2
View File
@@ -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
+5 -2
View File
@@ -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
+4 -3
View File
@@ -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)
+6 -2
View File
@@ -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])
+10 -4
View File
@@ -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
+4 -3
View File
@@ -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")
+5 -2
View File
@@ -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)
+17 -8
View File
@@ -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 == "" {
+5 -4
View File
@@ -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),
),
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 {
+4 -2
View File
@@ -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 {
+4 -3
View File
@@ -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 {
+4 -2
View File
@@ -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 {
+4 -2
View File
@@ -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 {
+15 -7
View File
@@ -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])
+12 -6
View File
@@ -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 {
+4 -7
View File
@@ -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()
+12 -6
View File
@@ -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 {
+20 -11
View File
@@ -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)