Skip to content

Commit 7d07857

Browse files
committed
Add beforeCreate and afterCreate to create handler, apply review suggestions
1 parent c019a31 commit 7d07857

12 files changed

+106
-137
lines changed

cli/create.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,12 @@ const PresetNone = "none"
2929

3030
var ErrNoPresetFound = xerrors.New("no preset found")
3131

32-
func (r *RootCmd) create() *serpent.Command {
32+
type createOptions struct {
33+
beforeCreate func(ctx context.Context, client *codersdk.Client, template codersdk.Template, templateVersionID uuid.UUID) error
34+
afterCreate func(ctx context.Context, inv *serpent.Invocation, client *codersdk.Client, workspace codersdk.Workspace) error
35+
}
36+
37+
func (r *RootCmd) create(opts createOptions) *serpent.Command {
3338
var (
3439
templateName string
3540
templateVersion string
@@ -305,6 +310,13 @@ func (r *RootCmd) create() *serpent.Command {
305310
_, _ = fmt.Fprintf(inv.Stdout, "%s", cliui.Bold("No preset applied."))
306311
}
307312

313+
if opts.beforeCreate != nil {
314+
err = opts.beforeCreate(inv.Context(), client, template, templateVersionID)
315+
if err != nil {
316+
return xerrors.Errorf("before create: %w", err)
317+
}
318+
}
319+
308320
richParameters, err := prepWorkspaceBuild(inv, client, prepWorkspaceBuildArgs{
309321
Action: WorkspaceCreate,
310322
TemplateVersionID: templateVersionID,
@@ -366,6 +378,14 @@ func (r *RootCmd) create() *serpent.Command {
366378
cliui.Keyword(workspace.Name),
367379
cliui.Timestamp(time.Now()),
368380
)
381+
382+
if opts.afterCreate != nil {
383+
err = opts.afterCreate(inv.Context(), inv, client, workspace)
384+
if err != nil {
385+
return err
386+
}
387+
}
388+
369389
return nil
370390
},
371391
}

cli/external_workspaces.go

Lines changed: 72 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"fmt"
56
"strings"
67

@@ -15,18 +16,19 @@ import (
1516
)
1617

1718
type externalAgent struct {
18-
AgentName string `json:"-"`
19-
AuthType string `json:"auth_type"`
20-
AuthToken string `json:"auth_token"`
21-
InitScript string `json:"init_script"`
19+
WorkspaceName string `json:"-"`
20+
AgentName string `json:"-"`
21+
AuthType string `json:"auth_type"`
22+
AuthToken string `json:"auth_token"`
23+
InitScript string `json:"init_script"`
2224
}
2325

2426
func (r *RootCmd) externalWorkspaces() *serpent.Command {
2527
orgContext := NewOrganizationContext()
2628

2729
cmd := &serpent.Command{
2830
Use: "external-workspaces [subcommand]",
29-
Short: "External workspace related commands",
31+
Short: "Create or manage external workspaces",
3032
Handler: func(inv *serpent.Invocation) error {
3133
return inv.Command.HelpHandler(inv)
3234
},
@@ -43,88 +45,61 @@ func (r *RootCmd) externalWorkspaces() *serpent.Command {
4345

4446
// externalWorkspaceCreate extends `coder create` to create an external workspace.
4547
func (r *RootCmd) externalWorkspaceCreate() *serpent.Command {
46-
var (
47-
orgContext = NewOrganizationContext()
48-
client = new(codersdk.Client)
49-
)
50-
51-
cmd := r.create()
52-
cmd.Use = "create [workspace]"
53-
cmd.Short = "Create a new external workspace"
54-
cmd.Middleware = serpent.Chain(
55-
cmd.Middleware,
56-
r.InitClient(client),
57-
serpent.RequireNArgs(1),
58-
)
59-
60-
createHandler := cmd.Handler
61-
cmd.Handler = func(inv *serpent.Invocation) error {
62-
workspaceName := inv.Args[0]
63-
templateVersion := inv.ParsedFlags().Lookup("template-version")
64-
templateName := inv.ParsedFlags().Lookup("template")
65-
if templateName == nil || templateName.Value.String() == "" {
66-
return xerrors.Errorf("template name is required for external workspace creation. Use --template=<template_name>")
67-
}
48+
opts := createOptions{
49+
beforeCreate: func(ctx context.Context, client *codersdk.Client, _ codersdk.Template, templateVersionID uuid.UUID) error {
50+
resources, err := client.TemplateVersionResources(ctx, templateVersionID)
51+
if err != nil {
52+
return xerrors.Errorf("get template version resources: %w", err)
53+
}
54+
if len(resources) == 0 {
55+
return xerrors.Errorf("no resources found for template version %q", templateVersionID)
56+
}
6857

69-
organization, err := orgContext.Selected(inv, client)
70-
if err != nil {
71-
return xerrors.Errorf("get current organization: %w", err)
72-
}
58+
var hasExternalAgent bool
59+
for _, resource := range resources {
60+
if resource.Type == "coder_external_agent" {
61+
hasExternalAgent = true
62+
break
63+
}
64+
}
7365

74-
template, err := client.TemplateByName(inv.Context(), organization.ID, templateName.Value.String())
75-
if err != nil {
76-
return xerrors.Errorf("get template by name: %w", err)
77-
}
66+
if !hasExternalAgent {
67+
return xerrors.Errorf("template version %q does not have an external agent. Only templates with external agents can be used for external workspace creation", templateVersionID)
68+
}
7869

79-
var resources []codersdk.WorkspaceResource
80-
var templateVersionID uuid.UUID
81-
if templateVersion == nil || templateVersion.Value.String() == "" {
82-
templateVersionID = template.ActiveVersionID
83-
} else {
84-
version, err := client.TemplateVersionByName(inv.Context(), template.ID, templateVersion.Value.String())
70+
return nil
71+
},
72+
afterCreate: func(ctx context.Context, inv *serpent.Invocation, client *codersdk.Client, workspace codersdk.Workspace) error {
73+
workspace, err := client.WorkspaceByOwnerAndName(ctx, codersdk.Me, workspace.Name, codersdk.WorkspaceOptions{})
8574
if err != nil {
86-
return xerrors.Errorf("get template version by name: %w", err)
75+
return xerrors.Errorf("get workspace by name: %w", err)
8776
}
88-
templateVersionID = version.ID
89-
}
90-
91-
resources, err = client.TemplateVersionResources(inv.Context(), templateVersionID)
92-
if err != nil {
93-
return xerrors.Errorf("get template version resources: %w", err)
94-
}
95-
if len(resources) == 0 {
96-
return xerrors.Errorf("no resources found for template version %q", templateVersion.Value.String())
97-
}
9877

99-
var hasExternalAgent bool
100-
for _, resource := range resources {
101-
if resource.Type == "coder_external_agent" {
102-
hasExternalAgent = true
103-
break
78+
externalAgents, err := fetchExternalAgents(inv, client, workspace, workspace.LatestBuild.Resources)
79+
if err != nil {
80+
return xerrors.Errorf("fetch external agents: %w", err)
10481
}
105-
}
10682

107-
if !hasExternalAgent {
108-
return xerrors.Errorf("template version %q does not have an external agent. Only templates with external agents can be used for external workspace creation", templateVersion.Value.String())
109-
}
110-
111-
err = createHandler(inv)
112-
if err != nil {
83+
formatted := formatExternalAgent(workspace.Name, externalAgents)
84+
_, err = fmt.Fprintln(inv.Stdout, formatted)
11385
return err
114-
}
86+
},
87+
}
11588

116-
workspace, err := client.WorkspaceByOwnerAndName(inv.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
117-
if err != nil {
118-
return xerrors.Errorf("get workspace by name: %w", err)
119-
}
89+
cmd := r.create(opts)
90+
cmd.Use = "create [workspace]"
91+
cmd.Short = "Create a new external workspace"
92+
cmd.Middleware = serpent.Chain(
93+
cmd.Middleware,
94+
serpent.RequireNArgs(1),
95+
)
12096

121-
externalAgents, err := fetchExternalAgents(inv, client, workspace, workspace.LatestBuild.Resources)
122-
if err != nil {
123-
return xerrors.Errorf("fetch external agents: %w", err)
97+
for i := range cmd.Options {
98+
if cmd.Options[i].Flag == "template" {
99+
cmd.Options[i].Required = true
124100
}
125-
126-
return printExternalAgents(inv, workspace.Name, externalAgents)
127101
}
102+
128103
return cmd
129104
}
130105

@@ -138,57 +113,37 @@ func (r *RootCmd) externalWorkspaceAgentInstructions() *serpent.Command {
138113
return "", xerrors.Errorf("expected externalAgent, got %T", data)
139114
}
140115

141-
var output strings.Builder
142-
_, _ = output.WriteString(fmt.Sprintf("Please run the following commands to attach agent %s:\n", cliui.Keyword(agent.AgentName)))
143-
_, _ = output.WriteString(fmt.Sprintf("%s\n", pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("export CODER_AGENT_TOKEN=%s", agent.AuthToken))))
144-
_, _ = output.WriteString(pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("curl -fsSL %s | sh", agent.InitScript)))
145-
146-
return output.String(), nil
116+
return formatExternalAgent(agent.WorkspaceName, []externalAgent{agent}), nil
147117
}),
148118
cliui.JSONFormat(),
149119
)
150120

151121
cmd := &serpent.Command{
152-
Use: "agent-instructions [workspace name] [agent name]",
122+
Use: "agent-instructions [user/]workspace[.agent]",
153123
Short: "Get the instructions for an external agent",
154-
Middleware: serpent.Chain(r.InitClient(client), serpent.RequireNArgs(2)),
124+
Middleware: serpent.Chain(r.InitClient(client), serpent.RequireNArgs(1)),
155125
Handler: func(inv *serpent.Invocation) error {
156-
workspaceName := inv.Args[0]
157-
agentName := inv.Args[1]
158-
159-
workspace, err := client.WorkspaceByOwnerAndName(inv.Context(), codersdk.Me, workspaceName, codersdk.WorkspaceOptions{})
126+
workspace, workspaceAgent, _, err := getWorkspaceAndAgent(inv.Context(), inv, client, false, inv.Args[0])
160127
if err != nil {
161-
return xerrors.Errorf("get workspace by name: %w", err)
128+
return xerrors.Errorf("find workspace and agent: %w", err)
162129
}
163130

164-
credential, err := client.WorkspaceExternalAgentCredential(inv.Context(), workspace.ID, agentName)
131+
credential, err := client.WorkspaceExternalAgentCredential(inv.Context(), workspace.ID, workspaceAgent.Name)
165132
if err != nil {
166-
return xerrors.Errorf("get external agent token for agent %q: %w", agentName, err)
167-
}
168-
169-
var agent codersdk.WorkspaceAgent
170-
for _, resource := range workspace.LatestBuild.Resources {
171-
for _, a := range resource.Agents {
172-
if a.Name == agentName {
173-
agent = a
174-
break
175-
}
176-
}
177-
if agent.ID != uuid.Nil {
178-
break
179-
}
133+
return xerrors.Errorf("get external agent token for agent %q: %w", workspaceAgent.Name, err)
180134
}
181135

182136
initScriptURL := fmt.Sprintf("%s/api/v2/init-script", client.URL)
183-
if agent.OperatingSystem != "linux" || agent.Architecture != "amd64" {
184-
initScriptURL = fmt.Sprintf("%s/api/v2/init-script?os=%s&arch=%s", client.URL, agent.OperatingSystem, agent.Architecture)
137+
if workspaceAgent.OperatingSystem != "linux" || workspaceAgent.Architecture != "amd64" {
138+
initScriptURL = fmt.Sprintf("%s/api/v2/init-script?os=%s&arch=%s", client.URL, workspaceAgent.OperatingSystem, workspaceAgent.Architecture)
185139
}
186140

187141
agentInfo := externalAgent{
188-
AgentName: agentName,
189-
AuthType: "token",
190-
AuthToken: credential.AgentToken,
191-
InitScript: initScriptURL,
142+
WorkspaceName: workspace.Name,
143+
AgentName: workspaceAgent.Name,
144+
AuthType: "token",
145+
AuthToken: credential.AgentToken,
146+
InitScript: initScriptURL,
192147
}
193148

194149
out, err := formatter.Format(inv.Context(), agentInfo)
@@ -305,22 +260,23 @@ func fetchExternalAgents(inv *serpent.Invocation, client *codersdk.Client, works
305260
return externalAgents, nil
306261
}
307262

308-
// printExternalAgents prints the instructions for an external agent.
309-
func printExternalAgents(inv *serpent.Invocation, workspaceName string, externalAgents []externalAgent) error {
310-
_, _ = fmt.Fprintf(inv.Stdout, "\nPlease run the following commands to attach external agent to the workspace %s:\n\n", cliui.Keyword(workspaceName))
263+
// formatExternalAgent formats the instructions for an external agent.
264+
func formatExternalAgent(workspaceName string, externalAgents []externalAgent) string {
265+
var output strings.Builder
266+
_, _ = output.WriteString(fmt.Sprintf("\nPlease run the following commands to attach external agent to the workspace %s:\n\n", cliui.Keyword(workspaceName)))
311267

312268
for i, agent := range externalAgents {
313269
if len(externalAgents) > 1 {
314-
_, _ = fmt.Fprintf(inv.Stdout, "For agent %s:\n", cliui.Keyword(agent.AgentName))
270+
_, _ = output.WriteString(fmt.Sprintf("For agent %s:\n", cliui.Keyword(agent.AgentName)))
315271
}
316272

317-
_, _ = fmt.Fprintf(inv.Stdout, "%s\n", pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("export CODER_AGENT_TOKEN=%s", agent.AuthToken)))
318-
_, _ = fmt.Fprintf(inv.Stdout, "%s\n", pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("curl -fsSL %s | sh", agent.InitScript)))
273+
_, _ = output.WriteString(fmt.Sprintf("%s\n", pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("export CODER_AGENT_TOKEN=%s", agent.AuthToken))))
274+
_, _ = output.WriteString(fmt.Sprintf("%s\n", pretty.Sprint(cliui.DefaultStyles.Code, fmt.Sprintf("curl -fsSL %s | sh", agent.InitScript))))
319275

320276
if i < len(externalAgents)-1 {
321-
_, _ = fmt.Fprintf(inv.Stdout, "\n")
277+
_, _ = output.WriteString("\n")
322278
}
323279
}
324280

325-
return nil
281+
return output.String()
326282
}

cli/external_workspaces_test.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ func TestExternalWorkspaces(t *testing.T) {
179179

180180
err := inv.Run()
181181
require.Error(t, err)
182-
assert.Contains(t, err.Error(), "template name is required for external workspace creation")
182+
assert.Contains(t, err.Error(), "Missing values for the required flags: template")
183183
})
184184

185185
t.Run("CreateWithRegularTemplate", func(t *testing.T) {
@@ -320,7 +320,6 @@ func TestExternalWorkspaces(t *testing.T) {
320320
"external-workspaces",
321321
"agent-instructions",
322322
ws.Name,
323-
"external-agent",
324323
}
325324
inv, root := clitest.New(t, args...)
326325
clitest.SetupConfig(t, member, root)
@@ -334,7 +333,7 @@ func TestExternalWorkspaces(t *testing.T) {
334333
assert.NoError(t, errC)
335334
close(done)
336335
}()
337-
pty.ExpectMatch("Please run the following commands to attach agent external-agent:")
336+
pty.ExpectMatch("Please run the following commands to attach external agent to the workspace")
338337
pty.ExpectMatch("export CODER_AGENT_TOKEN=")
339338
pty.ExpectMatch("curl -fsSL")
340339
cancelFunc()
@@ -358,7 +357,6 @@ func TestExternalWorkspaces(t *testing.T) {
358357
"external-workspaces",
359358
"agent-instructions",
360359
ws.Name,
361-
"external-agent",
362360
"--output=json",
363361
}
364362
inv, root := clitest.New(t, args...)
@@ -389,7 +387,6 @@ func TestExternalWorkspaces(t *testing.T) {
389387
"external-workspaces",
390388
"agent-instructions",
391389
"non-existent-workspace",
392-
"external-agent",
393390
}
394391
inv, root := clitest.New(t, args...)
395392
clitest.SetupConfig(t, member, root)
@@ -415,15 +412,14 @@ func TestExternalWorkspaces(t *testing.T) {
415412
args := []string{
416413
"external-workspaces",
417414
"agent-instructions",
418-
ws.Name,
419-
"non-existent-agent",
415+
ws.Name + ".non-existent-agent",
420416
}
421417
inv, root := clitest.New(t, args...)
422418
clitest.SetupConfig(t, member, root)
423419

424420
err := inv.Run()
425421
require.Error(t, err)
426-
assert.Contains(t, err.Error(), "get external agent token for agent")
422+
assert.Contains(t, err.Error(), "agent not found by name")
427423
})
428424

429425
t.Run("CreateWithTemplateVersion", func(t *testing.T) {

cli/root.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
108108
// Workspace Commands
109109
r.autoupdate(),
110110
r.configSSH(),
111-
r.create(),
111+
r.create(createOptions{}),
112112
r.deleteWorkspace(),
113113
r.favorite(),
114114
r.list(),
@@ -126,8 +126,6 @@ func (r *RootCmd) CoreSubcommands() []*serpent.Command {
126126
r.unfavorite(),
127127
r.update(),
128128
r.whoami(),
129-
130-
// External Workspace Commands
131129
r.externalWorkspaces(),
132130

133131
// Hidden

cli/testdata/coder_--help.golden

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ SUBCOMMANDS:
2424
dotfiles Personalize your workspace by applying a canonical
2525
dotfiles repository
2626
external-auth Manage external authentication
27-
external-workspaces External workspace related commands
27+
external-workspaces Create or manage external workspaces
2828
favorite Add a workspace to your favorites
2929
list List workspaces
3030
login Authenticate with Coder deployment

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

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

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


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy