Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit169e24d

Browse files
committed
feat(cli): add external-workspaces CLI command to create, list and manage external workspaces
1 parent6352a4a commit169e24d

15 files changed

+1219
-95
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
varErrNoPresetFound=xerrors.New("no preset found")
3131

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

313+
ifopts.beforeCreate!=nil {
314+
err=opts.beforeCreate(inv.Context(),client,template,templateVersionID)
315+
iferr!=nil {
316+
returnxerrors.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+
ifopts.afterCreate!=nil {
383+
err=opts.afterCreate(inv.Context(),inv,client,workspace)
384+
iferr!=nil {
385+
returnerr
386+
}
387+
}
388+
369389
returnnil
370390
},
371391
}

‎cli/externalworkspaces.go‎

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
package cli
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/google/uuid"
9+
"golang.org/x/xerrors"
10+
11+
"github.com/coder/coder/v2/cli/cliui"
12+
"github.com/coder/coder/v2/codersdk"
13+
"github.com/coder/pretty"
14+
"github.com/coder/serpent"
15+
)
16+
17+
typeexternalAgentstruct {
18+
WorkspaceNamestring`json:"-"`
19+
AgentNamestring`json:"-"`
20+
AuthTypestring`json:"auth_type"`
21+
AuthTokenstring`json:"auth_token"`
22+
InitScriptstring`json:"init_script"`
23+
}
24+
25+
func (r*RootCmd)externalWorkspaces()*serpent.Command {
26+
orgContext:=NewOrganizationContext()
27+
28+
cmd:=&serpent.Command{
29+
Use:"external-workspaces [subcommand]",
30+
Short:"Create or manage external workspaces",
31+
Handler:func(inv*serpent.Invocation)error {
32+
returninv.Command.HelpHandler(inv)
33+
},
34+
Children: []*serpent.Command{
35+
r.externalWorkspaceCreate(),
36+
r.externalWorkspaceAgentInstructions(),
37+
r.externalWorkspaceList(),
38+
},
39+
}
40+
41+
orgContext.AttachOptions(cmd)
42+
returncmd
43+
}
44+
45+
// externalWorkspaceCreate extends `coder create` to create an external workspace.
46+
func (r*RootCmd)externalWorkspaceCreate()*serpent.Command {
47+
opts:=createOptions{
48+
beforeCreate:func(ctx context.Context,client*codersdk.Client,_ codersdk.Template,templateVersionID uuid.UUID)error {
49+
resources,err:=client.TemplateVersionResources(ctx,templateVersionID)
50+
iferr!=nil {
51+
returnxerrors.Errorf("get template version resources: %w",err)
52+
}
53+
iflen(resources)==0 {
54+
returnxerrors.Errorf("no resources found for template version %q",templateVersionID)
55+
}
56+
57+
varhasExternalAgentbool
58+
for_,resource:=rangeresources {
59+
ifresource.Type=="coder_external_agent" {
60+
hasExternalAgent=true
61+
break
62+
}
63+
}
64+
65+
if!hasExternalAgent {
66+
returnxerrors.Errorf("template version %q does not have an external agent. Only templates with external agents can be used for external workspace creation",templateVersionID)
67+
}
68+
69+
returnnil
70+
},
71+
afterCreate:func(ctx context.Context,inv*serpent.Invocation,client*codersdk.Client,workspace codersdk.Workspace)error {
72+
workspace,err:=client.WorkspaceByOwnerAndName(ctx,codersdk.Me,workspace.Name, codersdk.WorkspaceOptions{})
73+
iferr!=nil {
74+
returnxerrors.Errorf("get workspace by name: %w",err)
75+
}
76+
77+
externalAgents,err:=fetchExternalAgents(inv,client,workspace,workspace.LatestBuild.Resources)
78+
iferr!=nil {
79+
returnxerrors.Errorf("fetch external agents: %w",err)
80+
}
81+
82+
formatted:=formatExternalAgent(workspace.Name,externalAgents)
83+
_,err=fmt.Fprintln(inv.Stdout,formatted)
84+
returnerr
85+
},
86+
}
87+
88+
cmd:=r.create(opts)
89+
cmd.Use="create [workspace]"
90+
cmd.Short="Create a new external workspace"
91+
cmd.Middleware=serpent.Chain(
92+
cmd.Middleware,
93+
serpent.RequireNArgs(1),
94+
)
95+
96+
fori:=rangecmd.Options {
97+
ifcmd.Options[i].Flag=="template" {
98+
cmd.Options[i].Required=true
99+
}
100+
}
101+
102+
returncmd
103+
}
104+
105+
// externalWorkspaceAgentInstructions prints the instructions for an external agent.
106+
func (r*RootCmd)externalWorkspaceAgentInstructions()*serpent.Command {
107+
client:=new(codersdk.Client)
108+
formatter:=cliui.NewOutputFormatter(
109+
cliui.ChangeFormatterData(cliui.TextFormat(),func(dataany) (any,error) {
110+
agent,ok:=data.(externalAgent)
111+
if!ok {
112+
return"",xerrors.Errorf("expected externalAgent, got %T",data)
113+
}
114+
115+
returnformatExternalAgent(agent.WorkspaceName, []externalAgent{agent}),nil
116+
}),
117+
cliui.JSONFormat(),
118+
)
119+
120+
cmd:=&serpent.Command{
121+
Use:"agent-instructions [user/]workspace[.agent]",
122+
Short:"Get the instructions for an external agent",
123+
Middleware:serpent.Chain(r.InitClient(client),serpent.RequireNArgs(1)),
124+
Handler:func(inv*serpent.Invocation)error {
125+
workspace,workspaceAgent,_,err:=getWorkspaceAndAgent(inv.Context(),inv,client,false,inv.Args[0])
126+
iferr!=nil {
127+
returnxerrors.Errorf("find workspace and agent: %w",err)
128+
}
129+
130+
credentials,err:=client.WorkspaceExternalAgentCredentials(inv.Context(),workspace.ID,workspaceAgent.Name)
131+
iferr!=nil {
132+
returnxerrors.Errorf("get external agent token for agent %q: %w",workspaceAgent.Name,err)
133+
}
134+
135+
agentInfo:=externalAgent{
136+
WorkspaceName:workspace.Name,
137+
AgentName:workspaceAgent.Name,
138+
AuthType:"token",
139+
AuthToken:credentials.AgentToken,
140+
InitScript:credentials.Command,
141+
}
142+
143+
out,err:=formatter.Format(inv.Context(),agentInfo)
144+
iferr!=nil {
145+
returnerr
146+
}
147+
148+
_,err=fmt.Fprintln(inv.Stdout,out)
149+
returnerr
150+
},
151+
}
152+
153+
formatter.AttachOptions(&cmd.Options)
154+
returncmd
155+
}
156+
157+
func (r*RootCmd)externalWorkspaceList()*serpent.Command {
158+
var (
159+
filter cliui.WorkspaceFilter
160+
formatter=cliui.NewOutputFormatter(
161+
cliui.TableFormat(
162+
[]workspaceListRow{},
163+
[]string{
164+
"workspace",
165+
"template",
166+
"status",
167+
"healthy",
168+
"last built",
169+
"current version",
170+
"outdated",
171+
},
172+
),
173+
cliui.JSONFormat(),
174+
)
175+
)
176+
client:=new(codersdk.Client)
177+
cmd:=&serpent.Command{
178+
Annotations:workspaceCommand,
179+
Use:"list",
180+
Short:"List external workspaces",
181+
Aliases: []string{"ls"},
182+
Middleware:serpent.Chain(
183+
serpent.RequireNArgs(0),
184+
r.InitClient(client),
185+
),
186+
Handler:func(inv*serpent.Invocation)error {
187+
baseFilter:=filter.Filter()
188+
189+
ifbaseFilter.FilterQuery=="" {
190+
baseFilter.FilterQuery="has-external-agent:true"
191+
}else {
192+
baseFilter.FilterQuery+=" has-external-agent:true"
193+
}
194+
195+
res,err:=queryConvertWorkspaces(inv.Context(),client,baseFilter,workspaceListRowFromWorkspace)
196+
iferr!=nil {
197+
returnerr
198+
}
199+
200+
iflen(res)==0&&formatter.FormatID()!=cliui.JSONFormat().ID() {
201+
pretty.Fprintf(inv.Stderr,cliui.DefaultStyles.Prompt,"No workspaces found! Create one:\n")
202+
_,_=fmt.Fprintln(inv.Stderr)
203+
_,_=fmt.Fprintln(inv.Stderr," "+pretty.Sprint(cliui.DefaultStyles.Code,"coder external-workspaces create <name>"))
204+
_,_=fmt.Fprintln(inv.Stderr)
205+
returnnil
206+
}
207+
208+
out,err:=formatter.Format(inv.Context(),res)
209+
iferr!=nil {
210+
returnerr
211+
}
212+
213+
_,err=fmt.Fprintln(inv.Stdout,out)
214+
returnerr
215+
},
216+
}
217+
filter.AttachOptions(&cmd.Options)
218+
formatter.AttachOptions(&cmd.Options)
219+
returncmd
220+
}
221+
222+
// fetchExternalAgents fetches the external agents for a workspace.
223+
funcfetchExternalAgents(inv*serpent.Invocation,client*codersdk.Client,workspace codersdk.Workspace,resources []codersdk.WorkspaceResource) ([]externalAgent,error) {
224+
iflen(resources)==0 {
225+
returnnil,xerrors.Errorf("no resources found for workspace")
226+
}
227+
228+
varexternalAgents []externalAgent
229+
230+
for_,resource:=rangeresources {
231+
ifresource.Type!="coder_external_agent"||len(resource.Agents)==0 {
232+
continue
233+
}
234+
235+
agent:=resource.Agents[0]
236+
credentials,err:=client.WorkspaceExternalAgentCredentials(inv.Context(),workspace.ID,agent.Name)
237+
iferr!=nil {
238+
returnnil,xerrors.Errorf("get external agent token for agent %q: %w",agent.Name,err)
239+
}
240+
241+
externalAgents=append(externalAgents,externalAgent{
242+
AgentName:agent.Name,
243+
AuthType:"token",
244+
AuthToken:credentials.AgentToken,
245+
InitScript:credentials.Command,
246+
})
247+
}
248+
249+
returnexternalAgents,nil
250+
}
251+
252+
// formatExternalAgent formats the instructions for an external agent.
253+
funcformatExternalAgent(workspaceNamestring,externalAgents []externalAgent)string {
254+
varoutput strings.Builder
255+
_,_=output.WriteString(fmt.Sprintf("\nPlease run the following commands to attach external agent to the workspace %s:\n\n",cliui.Keyword(workspaceName)))
256+
257+
fori,agent:=rangeexternalAgents {
258+
iflen(externalAgents)>1 {
259+
_,_=output.WriteString(fmt.Sprintf("For agent %s:\n",cliui.Keyword(agent.AgentName)))
260+
}
261+
262+
_,_=output.WriteString(fmt.Sprintf("%s\n",pretty.Sprint(cliui.DefaultStyles.Code,fmt.Sprintf("export CODER_AGENT_TOKEN=%s",agent.AuthToken))))
263+
_,_=output.WriteString(fmt.Sprintf("%s\n",pretty.Sprint(cliui.DefaultStyles.Code,fmt.Sprintf("curl -fsSL %s | sh",agent.InitScript))))
264+
265+
ifi<len(externalAgents)-1 {
266+
_,_=output.WriteString("\n")
267+
}
268+
}
269+
270+
returnoutput.String()
271+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp