@@ -68,7 +68,7 @@ type fakeDevcontainerCLI struct {
6868execErrC chan func (cmd string ,args ... string )error // If set, send fn to return err, nil or close to return execErr.
6969readConfig agentcontainers.DevcontainerConfig
7070readConfigErr error
71- readConfigErrC chan error
71+ readConfigErrC chan func ( envs [] string ) error
7272}
7373
7474func (f * fakeDevcontainerCLI )Up (ctx context.Context ,_ ,_ string ,_ ... agentcontainers.DevcontainerCLIUpOptions ) (string ,error ) {
@@ -99,14 +99,14 @@ func (f *fakeDevcontainerCLI) Exec(ctx context.Context, _, _ string, cmd string,
9999return f .execErr
100100}
101101
102- 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 ) {
103103if f .readConfigErrC != nil {
104104select {
105105case <- ctx .Done ():
106106return agentcontainers.DevcontainerConfig {},ctx .Err ()
107- case err ,ok := <- f .readConfigErrC :
107+ case fn ,ok := <- f .readConfigErrC :
108108if ok {
109- return f .readConfig ,err
109+ return f .readConfig ,fn ( envs )
110110}
111111}
112112}
@@ -1253,7 +1253,8 @@ func TestAPI(t *testing.T) {
12531253deleteErrC :make (chan error ,1 ),
12541254}
12551255fakeDCCLI = & fakeDevcontainerCLI {
1256- execErrC :make (chan func (cmd string ,args ... string )error ,1 ),
1256+ execErrC :make (chan func (cmd string ,args ... string )error ,1 ),
1257+ readConfigErrC :make (chan func (envs []string )error ,1 ),
12571258}
12581259
12591260testContainer = codersdk.WorkspaceAgentContainer {
@@ -1293,13 +1294,15 @@ func TestAPI(t *testing.T) {
12931294agentcontainers .WithSubAgentClient (fakeSAC ),
12941295agentcontainers .WithSubAgentURL ("test-subagent-url" ),
12951296agentcontainers .WithDevcontainerCLI (fakeDCCLI ),
1297+ agentcontainers .WithManifestInfo ("test-user" ,"test-workspace" ),
12961298)
12971299apiClose := func () {
12981300closeOnce .Do (func () {
12991301// Close before api.Close() defer to avoid deadlock after test.
13001302close (fakeSAC .createErrC )
13011303close (fakeSAC .deleteErrC )
13021304close (fakeDCCLI .execErrC )
1305+ close (fakeDCCLI .readConfigErrC )
13031306
13041307_ = api .Close ()
13051308})
@@ -1313,6 +1316,13 @@ func TestAPI(t *testing.T) {
13131316assert .Empty (t ,args )
13141317return nil
13151318})// Exec pwd.
1319+ testutil .RequireSend (ctx ,t ,fakeDCCLI .readConfigErrC ,func (envs []string )error {
1320+ assert .Contains (t ,envs ,"CODER_WORKSPACE_AGENT_NAME=test-container" )
1321+ assert .Contains (t ,envs ,"CODER_WORKSPACE_NAME=test-workspace" )
1322+ assert .Contains (t ,envs ,"CODER_WORKSPACE_OWNER_NAME=test-user" )
1323+ assert .Contains (t ,envs ,"CODER_URL=test-subagent-url" )
1324+ return nil
1325+ })
13161326
13171327// Make sure the ticker function has been registered
13181328// before advancing the clock.
@@ -1453,6 +1463,13 @@ func TestAPI(t *testing.T) {
14531463assert .Empty (t ,args )
14541464return nil
14551465})// Exec pwd.
1466+ testutil .RequireSend (ctx ,t ,fakeDCCLI .readConfigErrC ,func (envs []string )error {
1467+ assert .Contains (t ,envs ,"CODER_WORKSPACE_AGENT_NAME=test-container" )
1468+ assert .Contains (t ,envs ,"CODER_WORKSPACE_NAME=test-workspace" )
1469+ assert .Contains (t ,envs ,"CODER_WORKSPACE_OWNER_NAME=test-user" )
1470+ assert .Contains (t ,envs ,"CODER_URL=test-subagent-url" )
1471+ return nil
1472+ })
14561473
14571474err = api .RefreshContainers (ctx )
14581475require .NoError (t ,err ,"refresh containers should not fail" )
@@ -1603,6 +1620,116 @@ func TestAPI(t *testing.T) {
16031620assert .Contains (t ,subAgent .DisplayApps ,codersdk .DisplayAppPortForward )
16041621},
16051622},
1623+ {
1624+ name :"WithApps" ,
1625+ customization : []agentcontainers.CoderCustomization {
1626+ {
1627+ Apps : []agentcontainers.SubAgentApp {
1628+ {
1629+ Slug :"web-app" ,
1630+ DisplayName :"Web Application" ,
1631+ URL :"http://localhost:8080" ,
1632+ OpenIn :codersdk .WorkspaceAppOpenInTab ,
1633+ Share :codersdk .WorkspaceAppSharingLevelOwner ,
1634+ Icon :"/icons/web.svg" ,
1635+ Order :int32 (1 ),
1636+ },
1637+ {
1638+ Slug :"api-server" ,
1639+ DisplayName :"API Server" ,
1640+ URL :"http://localhost:3000" ,
1641+ OpenIn :codersdk .WorkspaceAppOpenInSlimWindow ,
1642+ Share :codersdk .WorkspaceAppSharingLevelAuthenticated ,
1643+ Icon :"/icons/api.svg" ,
1644+ Order :int32 (2 ),
1645+ Hidden :true ,
1646+ },
1647+ {
1648+ Slug :"docs" ,
1649+ DisplayName :"Documentation" ,
1650+ URL :"http://localhost:4000" ,
1651+ OpenIn :codersdk .WorkspaceAppOpenInTab ,
1652+ Share :codersdk .WorkspaceAppSharingLevelPublic ,
1653+ Icon :"/icons/book.svg" ,
1654+ Order :int32 (3 ),
1655+ },
1656+ },
1657+ },
1658+ },
1659+ afterCreate :func (t * testing.T ,subAgent agentcontainers.SubAgent ) {
1660+ require .Len (t ,subAgent .Apps ,3 )
1661+
1662+ // Verify first app
1663+ assert .Equal (t ,"web-app" ,subAgent .Apps [0 ].Slug )
1664+ assert .Equal (t ,"Web Application" ,subAgent .Apps [0 ].DisplayName )
1665+ assert .Equal (t ,"http://localhost:8080" ,subAgent .Apps [0 ].URL )
1666+ assert .Equal (t ,codersdk .WorkspaceAppOpenInTab ,subAgent .Apps [0 ].OpenIn )
1667+ assert .Equal (t ,codersdk .WorkspaceAppSharingLevelOwner ,subAgent .Apps [0 ].Share )
1668+ assert .Equal (t ,"/icons/web.svg" ,subAgent .Apps [0 ].Icon )
1669+ assert .Equal (t ,int32 (1 ),subAgent .Apps [0 ].Order )
1670+
1671+ // Verify second app
1672+ assert .Equal (t ,"api-server" ,subAgent .Apps [1 ].Slug )
1673+ assert .Equal (t ,"API Server" ,subAgent .Apps [1 ].DisplayName )
1674+ assert .Equal (t ,"http://localhost:3000" ,subAgent .Apps [1 ].URL )
1675+ assert .Equal (t ,codersdk .WorkspaceAppOpenInSlimWindow ,subAgent .Apps [1 ].OpenIn )
1676+ assert .Equal (t ,codersdk .WorkspaceAppSharingLevelAuthenticated ,subAgent .Apps [1 ].Share )
1677+ assert .Equal (t ,"/icons/api.svg" ,subAgent .Apps [1 ].Icon )
1678+ assert .Equal (t ,int32 (2 ),subAgent .Apps [1 ].Order )
1679+ assert .Equal (t ,true ,subAgent .Apps [1 ].Hidden )
1680+
1681+ // Verify third app
1682+ assert .Equal (t ,"docs" ,subAgent .Apps [2 ].Slug )
1683+ assert .Equal (t ,"Documentation" ,subAgent .Apps [2 ].DisplayName )
1684+ assert .Equal (t ,"http://localhost:4000" ,subAgent .Apps [2 ].URL )
1685+ assert .Equal (t ,codersdk .WorkspaceAppOpenInTab ,subAgent .Apps [2 ].OpenIn )
1686+ assert .Equal (t ,codersdk .WorkspaceAppSharingLevelPublic ,subAgent .Apps [2 ].Share )
1687+ assert .Equal (t ,"/icons/book.svg" ,subAgent .Apps [2 ].Icon )
1688+ assert .Equal (t ,int32 (3 ),subAgent .Apps [2 ].Order )
1689+ },
1690+ },
1691+ {
1692+ name :"AppDeduplication" ,
1693+ customization : []agentcontainers.CoderCustomization {
1694+ {
1695+ Apps : []agentcontainers.SubAgentApp {
1696+ {
1697+ Slug :"foo-app" ,
1698+ Hidden :true ,
1699+ Order :1 ,
1700+ },
1701+ {
1702+ Slug :"bar-app" ,
1703+ },
1704+ },
1705+ },
1706+ {
1707+ Apps : []agentcontainers.SubAgentApp {
1708+ {
1709+ Slug :"foo-app" ,
1710+ Order :2 ,
1711+ },
1712+ {
1713+ Slug :"baz-app" ,
1714+ },
1715+ },
1716+ },
1717+ },
1718+ afterCreate :func (t * testing.T ,subAgent agentcontainers.SubAgent ) {
1719+ require .Len (t ,subAgent .Apps ,3 )
1720+
1721+ // As the original "foo-app" gets overridden by the later "foo-app",
1722+ // we expect "bar-app" to be first in the order.
1723+ assert .Equal (t ,"bar-app" ,subAgent .Apps [0 ].Slug )
1724+ assert .Equal (t ,"foo-app" ,subAgent .Apps [1 ].Slug )
1725+ assert .Equal (t ,"baz-app" ,subAgent .Apps [2 ].Slug )
1726+
1727+ // We do not expect the properties from the original "foo-app" to be
1728+ // carried over.
1729+ assert .Equal (t ,false ,subAgent .Apps [1 ].Hidden )
1730+ assert .Equal (t ,int32 (2 ),subAgent .Apps [1 ].Order )
1731+ },
1732+ },
16061733}
16071734
16081735for _ ,tt := range tests {