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

Commit72eccbf

Browse files
feat(agent/agentcontainers): support apps for dev container agents
1 parentb9ac16c commit72eccbf

File tree

8 files changed

+459
-15
lines changed

8 files changed

+459
-15
lines changed

‎agent/agentcontainers/acmock/acmock.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎agent/agentcontainers/api.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ type API struct {
6464
subAgentURLstring
6565
subAgentEnv []string
6666

67+
userNamestring
68+
workspaceNamestring
69+
6770
mu sync.RWMutex
6871
closedbool
6972
containers codersdk.WorkspaceAgentListContainersResponse// Output from the last list operation.
@@ -153,6 +156,20 @@ func WithSubAgentEnv(env ...string) Option {
153156
}
154157
}
155158

159+
// WithWorkspaceName sets the workspace name for the sub-agent.
160+
funcWithWorkspaceName(namestring)Option {
161+
returnfunc(api*API) {
162+
api.workspaceName=name
163+
}
164+
}
165+
166+
// WithUserName sets the workspace name for the sub-agent.
167+
funcWithUserName(namestring)Option {
168+
returnfunc(api*API) {
169+
api.userName=name
170+
}
171+
}
172+
156173
// WithDevcontainers sets the known devcontainers for the API. This
157174
// allows the API to be aware of devcontainers defined in the workspace
158175
// agent manifest.
@@ -1131,7 +1148,14 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11311148
codersdk.DisplayAppPortForward:true,
11321149
}
11331150

1134-
ifconfig,err:=api.dccli.ReadConfig(ctx,dc.WorkspaceFolder,dc.ConfigPath);err!=nil {
1151+
varapps []SubAgentApp
1152+
1153+
ifconfig,err:=api.dccli.ReadConfig(ctx,dc.WorkspaceFolder,dc.ConfigPath, []string{
1154+
fmt.Sprintf("CODER_AGENT_NAME=%s",dc.Name),
1155+
fmt.Sprintf("CODER_USER_NAME=%s",api.userName),
1156+
fmt.Sprintf("CODER_WORKSPACE_NAME=%s",api.workspaceName),
1157+
fmt.Sprintf("CODER_DEPLOYMENT_URL=%s",api.subAgentURL),
1158+
});err!=nil {
11351159
api.logger.Error(ctx,"unable to read devcontainer config",slog.Error(err))
11361160
}else {
11371161
coderCustomization:=config.MergedConfiguration.Customizations.Coder
@@ -1140,6 +1164,8 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11401164
forapp,enabled:=rangecustomization.DisplayApps {
11411165
displayAppsMap[app]=enabled
11421166
}
1167+
1168+
apps=append(apps,customization.Apps...)
11431169
}
11441170
}
11451171

‎agent/agentcontainers/api_test.go

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/coder/coder/v2/agent/agentcontainers"
2727
"github.com/coder/coder/v2/agent/agentcontainers/acmock"
2828
"github.com/coder/coder/v2/agent/agentcontainers/watcher"
29+
"github.com/coder/coder/v2/coderd/util/ptr"
2930
"github.com/coder/coder/v2/codersdk"
3031
"github.com/coder/coder/v2/testutil"
3132
"github.com/coder/quartz"
@@ -68,7 +69,7 @@ type fakeDevcontainerCLI struct {
6869
execErrCchanfunc(cmdstring,args...string)error// If set, send fn to return err, nil or close to return execErr.
6970
readConfig agentcontainers.DevcontainerConfig
7071
readConfigErrerror
71-
readConfigErrCchanerror
72+
readConfigErrCchanfunc(envs []string) (agentcontainers.DevcontainerConfig,error)
7273
}
7374

7475
func (f*fakeDevcontainerCLI)Up(ctx context.Context,_,_string,_...agentcontainers.DevcontainerCLIUpOptions) (string,error) {
@@ -99,14 +100,14 @@ func (f *fakeDevcontainerCLI) Exec(ctx context.Context, _, _ string, cmd string,
99100
returnf.execErr
100101
}
101102

102-
func (f*fakeDevcontainerCLI)ReadConfig(ctx context.Context,_,_string,_...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig,error) {
103+
func (f*fakeDevcontainerCLI)ReadConfig(ctx context.Context,_,_string,envs []string,_...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig,error) {
103104
iff.readConfigErrC!=nil {
104105
select {
105106
case<-ctx.Done():
106107
return agentcontainers.DevcontainerConfig{},ctx.Err()
107-
caseerr,ok:=<-f.readConfigErrC:
108+
casefn,ok:=<-f.readConfigErrC:
108109
ifok {
109-
returnf.readConfig,err
110+
returnfn(envs)
110111
}
111112
}
112113
}
@@ -1249,7 +1250,8 @@ func TestAPI(t *testing.T) {
12491250
deleteErrC:make(chanerror,1),
12501251
}
12511252
fakeDCCLI=&fakeDevcontainerCLI{
1252-
execErrC:make(chanfunc(cmdstring,args...string)error,1),
1253+
execErrC:make(chanfunc(cmdstring,args...string)error,1),
1254+
readConfigErrC:make(chanfunc(envs []string) (agentcontainers.DevcontainerConfig,error),1),
12531255
}
12541256

12551257
testContainer= codersdk.WorkspaceAgentContainer{
@@ -1289,13 +1291,16 @@ func TestAPI(t *testing.T) {
12891291
agentcontainers.WithSubAgentClient(fakeSAC),
12901292
agentcontainers.WithSubAgentURL("test-subagent-url"),
12911293
agentcontainers.WithDevcontainerCLI(fakeDCCLI),
1294+
agentcontainers.WithUserName("test-user"),
1295+
agentcontainers.WithWorkspaceName("test-workspace"),
12921296
)
12931297
apiClose:=func() {
12941298
closeOnce.Do(func() {
12951299
// Close before api.Close() defer to avoid deadlock after test.
12961300
close(fakeSAC.createErrC)
12971301
close(fakeSAC.deleteErrC)
12981302
close(fakeDCCLI.execErrC)
1303+
deferclose(fakeDCCLI.readConfigErrC)
12991304

13001305
_=api.Close()
13011306
})
@@ -1309,6 +1314,13 @@ func TestAPI(t *testing.T) {
13091314
assert.Empty(t,args)
13101315
returnnil
13111316
})// Exec pwd.
1317+
testutil.RequireSend(ctx,t,fakeDCCLI.readConfigErrC,func(envs []string) (agentcontainers.DevcontainerConfig,error) {
1318+
assert.Contains(t,envs,"CODER_AGENT_NAME=test-container")
1319+
assert.Contains(t,envs,"CODER_WORKSPACE_NAME=test-workspace")
1320+
assert.Contains(t,envs,"CODER_USER_NAME=test-user")
1321+
assert.Contains(t,envs,"CODER_DEPLOYMENT_URL=test-subagent-url")
1322+
return agentcontainers.DevcontainerConfig{},nil
1323+
})
13121324

13131325
// Make sure the ticker function has been registered
13141326
// before advancing the clock.
@@ -1413,6 +1425,13 @@ func TestAPI(t *testing.T) {
14131425
assert.Empty(t,args)
14141426
returnnil
14151427
})// Exec pwd.
1428+
testutil.RequireSend(ctx,t,fakeDCCLI.readConfigErrC,func(envs []string) (agentcontainers.DevcontainerConfig,error) {
1429+
assert.Contains(t,envs,"CODER_AGENT_NAME=test-container")
1430+
assert.Contains(t,envs,"CODER_WORKSPACE_NAME=test-workspace")
1431+
assert.Contains(t,envs,"CODER_USER_NAME=test-user")
1432+
assert.Contains(t,envs,"CODER_DEPLOYMENT_URL=test-subagent-url")
1433+
return agentcontainers.DevcontainerConfig{},nil
1434+
})
14161435

14171436
// Advance the clock to run updaterLoop.
14181437
fori:=range3 {
@@ -1566,6 +1585,74 @@ func TestAPI(t *testing.T) {
15661585
assert.Contains(t,subAgent.DisplayApps,codersdk.DisplayAppPortForward)
15671586
},
15681587
},
1588+
{
1589+
name:"WithApps",
1590+
customization: []agentcontainers.CoderCustomization{
1591+
{
1592+
Apps: []agentcontainers.SubAgentApp{
1593+
{
1594+
Slug:"web-app",
1595+
DisplayName:ptr.Ref("Web Application"),
1596+
URL:ptr.Ref("http://localhost:8080"),
1597+
OpenIn:codersdk.WorkspaceAppOpenInTab,
1598+
Share:codersdk.WorkspaceAppSharingLevelOwner,
1599+
Icon:ptr.Ref("/icons/web.svg"),
1600+
Order:ptr.Ref(int32(1)),
1601+
},
1602+
{
1603+
Slug:"api-server",
1604+
DisplayName:ptr.Ref("API Server"),
1605+
URL:ptr.Ref("http://localhost:3000"),
1606+
OpenIn:codersdk.WorkspaceAppOpenInSlimWindow,
1607+
Share:codersdk.WorkspaceAppSharingLevelAuthenticated,
1608+
Icon:ptr.Ref("/icons/api.svg"),
1609+
Order:ptr.Ref(int32(2)),
1610+
Hidden:ptr.Ref(true),
1611+
},
1612+
{
1613+
Slug:"docs",
1614+
DisplayName:ptr.Ref("Documentation"),
1615+
URL:ptr.Ref("http://localhost:4000"),
1616+
OpenIn:codersdk.WorkspaceAppOpenInTab,
1617+
Share:codersdk.WorkspaceAppSharingLevelPublic,
1618+
Icon:ptr.Ref("/icons/book.svg"),
1619+
Order:ptr.Ref(int32(3)),
1620+
},
1621+
},
1622+
},
1623+
},
1624+
afterCreate:func(t*testing.T,subAgent agentcontainers.SubAgent) {
1625+
require.Len(t,subAgent.Apps,3)
1626+
1627+
// Verify first app
1628+
assert.Equal(t,"web-app",subAgent.Apps[0].Slug)
1629+
assert.Equal(t,"Web Application",*subAgent.Apps[0].DisplayName)
1630+
assert.Equal(t,"http://localhost:8080",*subAgent.Apps[0].URL)
1631+
assert.Equal(t,codersdk.WorkspaceAppOpenInTab,subAgent.Apps[0].OpenIn)
1632+
assert.Equal(t,codersdk.WorkspaceAppSharingLevelOwner,subAgent.Apps[0].Share)
1633+
assert.Equal(t,"/icons/web.svg",*subAgent.Apps[0].Icon)
1634+
assert.Equal(t,int32(1),*subAgent.Apps[0].Order)
1635+
1636+
// Verify second app
1637+
assert.Equal(t,"api-server",subAgent.Apps[1].Slug)
1638+
assert.Equal(t,"API Server",*subAgent.Apps[1].DisplayName)
1639+
assert.Equal(t,"http://localhost:3000",*subAgent.Apps[1].URL)
1640+
assert.Equal(t,codersdk.WorkspaceAppOpenInSlimWindow,subAgent.Apps[1].OpenIn)
1641+
assert.Equal(t,codersdk.WorkspaceAppSharingLevelAuthenticated,subAgent.Apps[1].Share)
1642+
assert.Equal(t,"/icons/api.svg",*subAgent.Apps[1].Icon)
1643+
assert.Equal(t,int32(2),*subAgent.Apps[1].Order)
1644+
assert.Equal(t,true,*subAgent.Apps[1].Hidden)
1645+
1646+
// Verify third app
1647+
assert.Equal(t,"docs",subAgent.Apps[2].Slug)
1648+
assert.Equal(t,"Documentation",*subAgent.Apps[2].DisplayName)
1649+
assert.Equal(t,"http://localhost:4000",*subAgent.Apps[2].URL)
1650+
assert.Equal(t,codersdk.WorkspaceAppOpenInTab,subAgent.Apps[2].OpenIn)
1651+
assert.Equal(t,codersdk.WorkspaceAppSharingLevelPublic,subAgent.Apps[2].Share)
1652+
assert.Equal(t,"/icons/book.svg",*subAgent.Apps[2].Icon)
1653+
assert.Equal(t,int32(3),*subAgent.Apps[2].Order)
1654+
},
1655+
},
15691656
}
15701657

15711658
for_,tt:=rangetests {

‎agent/agentcontainers/devcontainercli.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ type DevcontainerCustomizations struct {
3232

3333
typeCoderCustomizationstruct {
3434
DisplayAppsmap[codersdk.DisplayApp]bool`json:"displayApps,omitempty"`
35+
Apps []SubAgentApp`json:"apps,omitempty"`
3536
}
3637

3738
// DevcontainerCLI is an interface for the devcontainer CLI.
3839
typeDevcontainerCLIinterface {
3940
Up(ctx context.Context,workspaceFolder,configPathstring,opts...DevcontainerCLIUpOptions) (idstring,errerror)
4041
Exec(ctx context.Context,workspaceFolder,configPathstring,cmdstring,cmdArgs []string,opts...DevcontainerCLIExecOptions)error
41-
ReadConfig(ctx context.Context,workspaceFolder,configPathstring,opts...DevcontainerCLIReadConfigOptions) (DevcontainerConfig,error)
42+
ReadConfig(ctx context.Context,workspaceFolder,configPathstring,env []string,opts...DevcontainerCLIReadConfigOptions) (DevcontainerConfig,error)
4243
}
4344

4445
// DevcontainerCLIUpOptions are options for the devcontainer CLI Up
@@ -113,8 +114,8 @@ type devcontainerCLIReadConfigConfig struct {
113114
stderr io.Writer
114115
}
115116

116-
//WithExecOutput sets additional stdout and stderr writers for logs
117-
// duringExec operations.
117+
//WithReadConfigOutput sets additional stdout and stderr writers for logs
118+
// duringReadConfig operations.
118119
funcWithReadConfigOutput(stdout,stderr io.Writer)DevcontainerCLIReadConfigOptions {
119120
returnfunc(o*devcontainerCLIReadConfigConfig) {
120121
o.stdout=stdout
@@ -250,7 +251,7 @@ func (d *devcontainerCLI) Exec(ctx context.Context, workspaceFolder, configPath
250251
returnnil
251252
}
252253

253-
func (d*devcontainerCLI)ReadConfig(ctx context.Context,workspaceFolder,configPathstring,opts...DevcontainerCLIReadConfigOptions) (DevcontainerConfig,error) {
254+
func (d*devcontainerCLI)ReadConfig(ctx context.Context,workspaceFolder,configPathstring,env []string,opts...DevcontainerCLIReadConfigOptions) (DevcontainerConfig,error) {
254255
conf:=applyDevcontainerCLIReadConfigOptions(opts)
255256
logger:=d.logger.With(slog.F("workspace_folder",workspaceFolder),slog.F("config_path",configPath))
256257

@@ -263,6 +264,7 @@ func (d *devcontainerCLI) ReadConfig(ctx context.Context, workspaceFolder, confi
263264
}
264265

265266
c:=d.execer.CommandContext(ctx,"devcontainer",args...)
267+
c.Env=append(c.Env,env...)
266268

267269
varstdoutBuf bytes.Buffer
268270
stdoutWriters:= []io.Writer{&stdoutBuf,&devcontainerCLILogWriter{ctx:ctx,logger:logger.With(slog.F("stdout",true))}}

‎agent/agentcontainers/devcontainercli_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ func TestDevcontainerCLI_ArgsAndParsing(t *testing.T) {
316316
}
317317

318318
dccli:=agentcontainers.NewDevcontainerCLI(logger,testExecer)
319-
config,err:=dccli.ReadConfig(ctx,tt.workspaceFolder,tt.configPath,tt.opts...)
319+
config,err:=dccli.ReadConfig(ctx,tt.workspaceFolder,tt.configPath,[]string{},tt.opts...)
320320
iftt.wantError {
321321
assert.Error(t,err,"want error")
322322
assert.Equal(t, agentcontainers.DevcontainerConfig{},config,"expected empty config on error")

‎agent/agentcontainers/subagent.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,32 @@ type SubAgent struct {
2020
Directorystring
2121
Architecturestring
2222
OperatingSystemstring
23+
Apps []SubAgentApp
2324
DisplayApps []codersdk.DisplayApp
2425
}
2526

27+
typeSubAgentAppstruct {
28+
Slugstring`json:"slug"`
29+
Command*string`json:"command"`
30+
DisplayName*string`json:"displayName"`
31+
External*bool`json:"external"`
32+
Group*string`json:"group"`
33+
HealthCheck*SubAgentHealthCheck`json:"healthCheck"`
34+
Hidden*bool`json:"hidden"`
35+
Icon*string`json:"icon"`
36+
OpenIn codersdk.WorkspaceAppOpenIn`json:"openIn"`
37+
Order*int32`json:"order"`
38+
Share codersdk.WorkspaceAppSharingLevel`json:"share"`
39+
Subdomain*bool`json:"subdomain"`
40+
URL*string`json:"url"`
41+
}
42+
43+
typeSubAgentHealthCheckstruct {
44+
Intervalint32`json:"interval"`
45+
Thresholdint32`json:"threshold"`
46+
URLstring`json:"url"`
47+
}
48+
2649
// SubAgentClient is an interface for managing sub agents and allows
2750
// changing the implementation without having to deal with the
2851
// agentproto package directly.
@@ -104,12 +127,63 @@ func (a *subAgentAPIClient) Create(ctx context.Context, agent SubAgent) (SubAgen
104127
displayApps=append(displayApps,app)
105128
}
106129

130+
apps:=make([]*agentproto.CreateSubAgentRequest_App,0,len(agent.Apps))
131+
for_,app:=rangeagent.Apps {
132+
varhealthCheck*agentproto.CreateSubAgentRequest_App_Healthcheck
133+
ifapp.HealthCheck!=nil {
134+
healthCheck=&agentproto.CreateSubAgentRequest_App_Healthcheck{
135+
Interval:app.HealthCheck.Interval,
136+
Threshold:app.HealthCheck.Threshold,
137+
Url:app.HealthCheck.URL,
138+
}
139+
}
140+
141+
varopenIn*agentproto.CreateSubAgentRequest_App_OpenIn
142+
switchapp.OpenIn {
143+
casecodersdk.WorkspaceAppOpenInSlimWindow:
144+
openIn=agentproto.CreateSubAgentRequest_App_SLIM_WINDOW.Enum()
145+
casecodersdk.WorkspaceAppOpenInTab:
146+
openIn=agentproto.CreateSubAgentRequest_App_TAB.Enum()
147+
default:
148+
returnSubAgent{},xerrors.Errorf("unexpected codersdk.WorkspaceAppOpenIn: %#v",app.OpenIn)
149+
}
150+
151+
varshare*agentproto.CreateSubAgentRequest_App_Share
152+
switchapp.Share {
153+
casecodersdk.WorkspaceAppSharingLevelAuthenticated:
154+
share=agentproto.CreateSubAgentRequest_App_AUTHENTICATED.Enum()
155+
casecodersdk.WorkspaceAppSharingLevelOwner:
156+
share=agentproto.CreateSubAgentRequest_App_OWNER.Enum()
157+
casecodersdk.WorkspaceAppSharingLevelPublic:
158+
share=agentproto.CreateSubAgentRequest_App_PUBLIC.Enum()
159+
default:
160+
returnSubAgent{},xerrors.Errorf("unexpected codersdk.WorkspaceAppSharingLevel: %#v",app.Share)
161+
}
162+
163+
apps=append(apps,&agentproto.CreateSubAgentRequest_App{
164+
Slug:app.Slug,
165+
Command:app.Command,
166+
DisplayName:app.DisplayName,
167+
External:app.External,
168+
Group:app.Group,
169+
Healthcheck:healthCheck,
170+
Hidden:app.Hidden,
171+
Icon:app.Icon,
172+
OpenIn:openIn,
173+
Order:app.Order,
174+
Share:share,
175+
Subdomain:app.Subdomain,
176+
Url:app.URL,
177+
})
178+
}
179+
107180
resp,err:=a.api.CreateSubAgent(ctx,&agentproto.CreateSubAgentRequest{
108181
Name:agent.Name,
109182
Directory:agent.Directory,
110183
Architecture:agent.Architecture,
111184
OperatingSystem:agent.OperatingSystem,
112185
DisplayApps:displayApps,
186+
Apps:apps,
113187
})
114188
iferr!=nil {
115189
returnSubAgent{},err

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp