@@ -25,6 +25,7 @@ import (
2525"github.com/coder/coder/v2/agent/agentcontainers"
2626"github.com/coder/coder/v2/agent/agentcontainers/acmock"
2727"github.com/coder/coder/v2/agent/agentcontainers/watcher"
28+ "github.com/coder/coder/v2/coderd/util/ptr"
2829"github.com/coder/coder/v2/codersdk"
2930"github.com/coder/coder/v2/testutil"
3031"github.com/coder/quartz"
@@ -67,7 +68,7 @@ type fakeDevcontainerCLI struct {
6768execErrC chan func (cmd string ,args ... string )error // If set, send fn to return err, nil or close to return execErr.
6869readConfig agentcontainers.DevcontainerConfig
6970readConfigErr error
70- readConfigErrC chan error
71+ readConfigErrC chan func ( envs [] string ) (agentcontainers. DevcontainerConfig , error )
7172}
7273
7374func (f * fakeDevcontainerCLI )Up (ctx context.Context ,_ ,_ string ,_ ... agentcontainers.DevcontainerCLIUpOptions ) (string ,error ) {
@@ -98,14 +99,14 @@ func (f *fakeDevcontainerCLI) Exec(ctx context.Context, _, _ string, cmd string,
9899return f .execErr
99100}
100101
101- func (f * fakeDevcontainerCLI )ReadConfig (ctx context.Context ,_ ,_ string ,_ ... agentcontainers.DevcontainerCLIReadConfigOptions ) (agentcontainers.DevcontainerConfig ,error ) {
102+ func (f * fakeDevcontainerCLI )ReadConfig (ctx context.Context ,_ ,_ string ,envs [] string , _ ... agentcontainers.DevcontainerCLIReadConfigOptions ) (agentcontainers.DevcontainerConfig ,error ) {
102103if f .readConfigErrC != nil {
103104select {
104105case <- ctx .Done ():
105106return agentcontainers.DevcontainerConfig {},ctx .Err ()
106- case err ,ok := <- f .readConfigErrC :
107+ case fn ,ok := <- f .readConfigErrC :
107108if ok {
108- return f . readConfig , err
109+ return fn ( envs )
109110}
110111}
111112}
@@ -1268,7 +1269,8 @@ func TestAPI(t *testing.T) {
12681269deleteErrC :make (chan error ,1 ),
12691270}
12701271fakeDCCLI = & fakeDevcontainerCLI {
1271- execErrC :make (chan func (cmd string ,args ... string )error ,1 ),
1272+ execErrC :make (chan func (cmd string ,args ... string )error ,1 ),
1273+ readConfigErrC :make (chan func (envs []string ) (agentcontainers.DevcontainerConfig ,error ),1 ),
12721274}
12731275
12741276testContainer = codersdk.WorkspaceAgentContainer {
@@ -1307,13 +1309,16 @@ func TestAPI(t *testing.T) {
13071309agentcontainers .WithSubAgentClient (fakeSAC ),
13081310agentcontainers .WithSubAgentURL ("test-subagent-url" ),
13091311agentcontainers .WithDevcontainerCLI (fakeDCCLI ),
1312+ agentcontainers .WithUserName ("test-user" ),
1313+ agentcontainers .WithWorkspaceName ("test-workspace" ),
13101314)
13111315defer api .Close ()
13121316
13131317// Close before api.Close() defer to avoid deadlock after test.
13141318defer close (fakeSAC .createErrC )
13151319defer close (fakeSAC .deleteErrC )
13161320defer close (fakeDCCLI .execErrC )
1321+ defer close (fakeDCCLI .readConfigErrC )
13171322
13181323// Allow initial agent creation and injection to succeed.
13191324testutil .RequireSend (ctx ,t ,fakeSAC .createErrC ,nil )
@@ -1322,6 +1327,13 @@ func TestAPI(t *testing.T) {
13221327assert .Empty (t ,args )
13231328return nil
13241329})// Exec pwd.
1330+ testutil .RequireSend (ctx ,t ,fakeDCCLI .readConfigErrC ,func (envs []string ) (agentcontainers.DevcontainerConfig ,error ) {
1331+ assert .Contains (t ,envs ,"CODER_AGENT_NAME=test-container" )
1332+ assert .Contains (t ,envs ,"CODER_WORKSPACE_NAME=test-workspace" )
1333+ assert .Contains (t ,envs ,"CODER_USER_NAME=test-user" )
1334+ assert .Contains (t ,envs ,"CODER_DEPLOYMENT_URL=test-subagent-url" )
1335+ return agentcontainers.DevcontainerConfig {},nil
1336+ })
13251337
13261338// Make sure the ticker function has been registered
13271339// before advancing the clock.
@@ -1374,6 +1386,13 @@ func TestAPI(t *testing.T) {
13741386assert .Empty (t ,args )
13751387return nil
13761388})// Exec pwd.
1389+ testutil .RequireSend (ctx ,t ,fakeDCCLI .readConfigErrC ,func (envs []string ) (agentcontainers.DevcontainerConfig ,error ) {
1390+ assert .Contains (t ,envs ,"CODER_AGENT_NAME=test-container" )
1391+ assert .Contains (t ,envs ,"CODER_WORKSPACE_NAME=test-workspace" )
1392+ assert .Contains (t ,envs ,"CODER_USER_NAME=test-user" )
1393+ assert .Contains (t ,envs ,"CODER_DEPLOYMENT_URL=test-subagent-url" )
1394+ return agentcontainers.DevcontainerConfig {},nil
1395+ })
13771396
13781397// Wait until the agent recreation is started.
13791398for len (fakeSAC .createErrC )> 0 {
@@ -1473,6 +1492,72 @@ func TestAPI(t *testing.T) {
14731492assert .Equal (t ,codersdk .DisplayAppVSCodeInsiders ,subAgent .DisplayApps [2 ])
14741493},
14751494},
1495+ {
1496+ name :"WithApps" ,
1497+ customization :& agentcontainers.CoderCustomization {
1498+ Apps : []agentcontainers.SubAgentApp {
1499+ {
1500+ Slug :"web-app" ,
1501+ DisplayName :ptr .Ref ("Web Application" ),
1502+ URL :ptr .Ref ("http://localhost:8080" ),
1503+ OpenIn :codersdk .WorkspaceAppOpenInTab ,
1504+ Share :codersdk .WorkspaceAppSharingLevelOwner ,
1505+ Icon :ptr .Ref ("/icons/web.svg" ),
1506+ Order :ptr .Ref (int32 (1 )),
1507+ },
1508+ {
1509+ Slug :"api-server" ,
1510+ DisplayName :ptr .Ref ("API Server" ),
1511+ URL :ptr .Ref ("http://localhost:3000" ),
1512+ OpenIn :codersdk .WorkspaceAppOpenInSlimWindow ,
1513+ Share :codersdk .WorkspaceAppSharingLevelAuthenticated ,
1514+ Icon :ptr .Ref ("/icons/api.svg" ),
1515+ Order :ptr .Ref (int32 (2 )),
1516+ Hidden :ptr .Ref (true ),
1517+ },
1518+ {
1519+ Slug :"docs" ,
1520+ DisplayName :ptr .Ref ("Documentation" ),
1521+ URL :ptr .Ref ("http://localhost:4000" ),
1522+ OpenIn :codersdk .WorkspaceAppOpenInTab ,
1523+ Share :codersdk .WorkspaceAppSharingLevelPublic ,
1524+ Icon :ptr .Ref ("/icons/book.svg" ),
1525+ Order :ptr .Ref (int32 (3 )),
1526+ },
1527+ },
1528+ },
1529+ afterCreate :func (t * testing.T ,subAgent agentcontainers.SubAgent ) {
1530+ require .Len (t ,subAgent .Apps ,3 )
1531+
1532+ // Verify first app
1533+ assert .Equal (t ,"web-app" ,subAgent .Apps [0 ].Slug )
1534+ assert .Equal (t ,"Web Application" ,* subAgent .Apps [0 ].DisplayName )
1535+ assert .Equal (t ,"http://localhost:8080" ,* subAgent .Apps [0 ].URL )
1536+ assert .Equal (t ,codersdk .WorkspaceAppOpenInTab ,subAgent .Apps [0 ].OpenIn )
1537+ assert .Equal (t ,codersdk .WorkspaceAppSharingLevelOwner ,subAgent .Apps [0 ].Share )
1538+ assert .Equal (t ,"/icons/web.svg" ,* subAgent .Apps [0 ].Icon )
1539+ assert .Equal (t ,int32 (1 ),* subAgent .Apps [0 ].Order )
1540+
1541+ // Verify second app
1542+ assert .Equal (t ,"api-server" ,subAgent .Apps [1 ].Slug )
1543+ assert .Equal (t ,"API Server" ,* subAgent .Apps [1 ].DisplayName )
1544+ assert .Equal (t ,"http://localhost:3000" ,* subAgent .Apps [1 ].URL )
1545+ assert .Equal (t ,codersdk .WorkspaceAppOpenInSlimWindow ,subAgent .Apps [1 ].OpenIn )
1546+ assert .Equal (t ,codersdk .WorkspaceAppSharingLevelAuthenticated ,subAgent .Apps [1 ].Share )
1547+ assert .Equal (t ,"/icons/api.svg" ,* subAgent .Apps [1 ].Icon )
1548+ assert .Equal (t ,int32 (2 ),* subAgent .Apps [1 ].Order )
1549+ assert .Equal (t ,true ,* subAgent .Apps [1 ].Hidden )
1550+
1551+ // Verify third app
1552+ assert .Equal (t ,"docs" ,subAgent .Apps [2 ].Slug )
1553+ assert .Equal (t ,"Documentation" ,* subAgent .Apps [2 ].DisplayName )
1554+ assert .Equal (t ,"http://localhost:4000" ,* subAgent .Apps [2 ].URL )
1555+ assert .Equal (t ,codersdk .WorkspaceAppOpenInTab ,subAgent .Apps [2 ].OpenIn )
1556+ assert .Equal (t ,codersdk .WorkspaceAppSharingLevelPublic ,subAgent .Apps [2 ].Share )
1557+ assert .Equal (t ,"/icons/book.svg" ,* subAgent .Apps [2 ].Icon )
1558+ assert .Equal (t ,int32 (3 ),* subAgent .Apps [2 ].Order )
1559+ },
1560+ },
14761561}
14771562
14781563for _ ,tt := range tests {