Skip to content

Commit 42420b1

Browse files
authored
Merge branch 'main' into cj/prebuild-template-upgrade
2 parents 611db66 + 56ff0fb commit 42420b1

File tree

15 files changed

+657
-44
lines changed

15 files changed

+657
-44
lines changed

agent/agentcontainers/acmock/acmock.go

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

agent/agentcontainers/api.go

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

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

159+
// WithManifestInfo sets the owner name, and workspace name
160+
// for the sub-agent.
161+
func WithManifestInfo(owner, workspace string) Option {
162+
return func(api *API) {
163+
api.ownerName = owner
164+
api.workspaceName = workspace
165+
}
166+
}
167+
156168
// WithDevcontainers sets the known devcontainers for the API. This
157169
// allows the API to be aware of devcontainers defined in the workspace
158170
// agent manifest.
@@ -1051,6 +1063,10 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
10511063
)
10521064
return nil
10531065
}
1066+
if proc.agent.ID == uuid.Nil {
1067+
proc.agent.Architecture = arch
1068+
}
1069+
10541070
agentBinaryPath, err := os.Executable()
10551071
if err != nil {
10561072
return xerrors.Errorf("get agent binary path: %w", err)
@@ -1095,6 +1111,8 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
10951111

10961112
subAgentConfig := proc.agent.CloneConfig(dc)
10971113
if proc.agent.ID == uuid.Nil || maybeRecreateSubAgent {
1114+
subAgentConfig.Architecture = arch
1115+
10981116
// Detect workspace folder by executing `pwd` in the container.
10991117
// NOTE(mafredri): This is a quick and dirty way to detect the
11001118
// workspace folder inside the container. In the future we will
@@ -1127,7 +1145,16 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11271145
codersdk.DisplayAppPortForward: true,
11281146
}
11291147

1130-
if config, err := api.dccli.ReadConfig(ctx, dc.WorkspaceFolder, dc.ConfigPath); err != nil {
1148+
var appsWithPossibleDuplicates []SubAgentApp
1149+
1150+
if config, err := api.dccli.ReadConfig(ctx, dc.WorkspaceFolder, dc.ConfigPath,
1151+
[]string{
1152+
fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s", dc.Name),
1153+
fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s", api.ownerName),
1154+
fmt.Sprintf("CODER_WORKSPACE_NAME=%s", api.workspaceName),
1155+
fmt.Sprintf("CODER_URL=%s", api.subAgentURL),
1156+
},
1157+
); err != nil {
11311158
api.logger.Error(ctx, "unable to read devcontainer config", slog.Error(err))
11321159
} else {
11331160
coderCustomization := config.MergedConfiguration.Customizations.Coder
@@ -1143,6 +1170,8 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11431170
}
11441171
displayAppsMap[app] = enabled
11451172
}
1173+
1174+
appsWithPossibleDuplicates = append(appsWithPossibleDuplicates, customization.Apps...)
11461175
}
11471176
}
11481177

@@ -1154,7 +1183,27 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11541183
}
11551184
slices.Sort(displayApps)
11561185

1186+
appSlugs := make(map[string]struct{})
1187+
apps := make([]SubAgentApp, 0, len(appsWithPossibleDuplicates))
1188+
1189+
// We want to deduplicate the apps based on their slugs here.
1190+
// As we want to prioritize later apps, we will walk through this
1191+
// backwards.
1192+
for _, app := range slices.Backward(appsWithPossibleDuplicates) {
1193+
if _, slugAlreadyExists := appSlugs[app.Slug]; slugAlreadyExists {
1194+
continue
1195+
}
1196+
1197+
appSlugs[app.Slug] = struct{}{}
1198+
apps = append(apps, app)
1199+
}
1200+
1201+
// Apps is currently in reverse order here, so by reversing it we restore
1202+
// it to the original order.
1203+
slices.Reverse(apps)
1204+
11571205
subAgentConfig.DisplayApps = displayApps
1206+
subAgentConfig.Apps = apps
11581207
}
11591208

11601209
deleteSubAgent := proc.agent.ID != uuid.Nil && maybeRecreateSubAgent && !proc.agent.EqualConfig(subAgentConfig)

agent/agentcontainers/api_test.go

Lines changed: 141 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ type fakeDevcontainerCLI struct {
6868
execErrC chan func(cmd string, args ...string) error // If set, send fn to return err, nil or close to return execErr.
6969
readConfig agentcontainers.DevcontainerConfig
7070
readConfigErr error
71-
readConfigErrC chan error
71+
readConfigErrC chan func(envs []string) error
7272
}
7373

7474
func (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,
9999
return 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) {
103103
if f.readConfigErrC != nil {
104104
select {
105105
case <-ctx.Done():
106106
return agentcontainers.DevcontainerConfig{}, ctx.Err()
107-
case err, ok := <-f.readConfigErrC:
107+
case fn, ok := <-f.readConfigErrC:
108108
if ok {
109-
return f.readConfig, err
109+
return f.readConfig, fn(envs)
110110
}
111111
}
112112
}
@@ -252,6 +252,15 @@ func (m *fakeSubAgentClient) Create(ctx context.Context, agent agentcontainers.S
252252
}
253253
}
254254
}
255+
if agent.Name == "" {
256+
return agentcontainers.SubAgent{}, xerrors.New("name must be set")
257+
}
258+
if agent.Architecture == "" {
259+
return agentcontainers.SubAgent{}, xerrors.New("architecture must be set")
260+
}
261+
if agent.OperatingSystem == "" {
262+
return agentcontainers.SubAgent{}, xerrors.New("operating system must be set")
263+
}
255264
agent.ID = uuid.New()
256265
agent.AuthToken = uuid.New()
257266
if m.agents == nil {
@@ -1253,7 +1262,8 @@ func TestAPI(t *testing.T) {
12531262
deleteErrC: make(chan error, 1),
12541263
}
12551264
fakeDCCLI = &fakeDevcontainerCLI{
1256-
execErrC: make(chan func(cmd string, args ...string) error, 1),
1265+
execErrC: make(chan func(cmd string, args ...string) error, 1),
1266+
readConfigErrC: make(chan func(envs []string) error, 1),
12571267
}
12581268

12591269
testContainer = codersdk.WorkspaceAgentContainer{
@@ -1293,13 +1303,15 @@ func TestAPI(t *testing.T) {
12931303
agentcontainers.WithSubAgentClient(fakeSAC),
12941304
agentcontainers.WithSubAgentURL("test-subagent-url"),
12951305
agentcontainers.WithDevcontainerCLI(fakeDCCLI),
1306+
agentcontainers.WithManifestInfo("test-user", "test-workspace"),
12961307
)
12971308
apiClose := func() {
12981309
closeOnce.Do(func() {
12991310
// Close before api.Close() defer to avoid deadlock after test.
13001311
close(fakeSAC.createErrC)
13011312
close(fakeSAC.deleteErrC)
13021313
close(fakeDCCLI.execErrC)
1314+
close(fakeDCCLI.readConfigErrC)
13031315

13041316
_ = api.Close()
13051317
})
@@ -1313,6 +1325,13 @@ func TestAPI(t *testing.T) {
13131325
assert.Empty(t, args)
13141326
return nil
13151327
}) // Exec pwd.
1328+
testutil.RequireSend(ctx, t, fakeDCCLI.readConfigErrC, func(envs []string) error {
1329+
assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=test-container")
1330+
assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace")
1331+
assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user")
1332+
assert.Contains(t, envs, "CODER_URL=test-subagent-url")
1333+
return nil
1334+
})
13161335

13171336
// Make sure the ticker function has been registered
13181337
// before advancing the clock.
@@ -1453,6 +1472,13 @@ func TestAPI(t *testing.T) {
14531472
assert.Empty(t, args)
14541473
return nil
14551474
}) // Exec pwd.
1475+
testutil.RequireSend(ctx, t, fakeDCCLI.readConfigErrC, func(envs []string) error {
1476+
assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=test-container")
1477+
assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace")
1478+
assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user")
1479+
assert.Contains(t, envs, "CODER_URL=test-subagent-url")
1480+
return nil
1481+
})
14561482

14571483
err = api.RefreshContainers(ctx)
14581484
require.NoError(t, err, "refresh containers should not fail")
@@ -1603,6 +1629,116 @@ func TestAPI(t *testing.T) {
16031629
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppPortForward)
16041630
},
16051631
},
1632+
{
1633+
name: "WithApps",
1634+
customization: []agentcontainers.CoderCustomization{
1635+
{
1636+
Apps: []agentcontainers.SubAgentApp{
1637+
{
1638+
Slug: "web-app",
1639+
DisplayName: "Web Application",
1640+
URL: "http://localhost:8080",
1641+
OpenIn: codersdk.WorkspaceAppOpenInTab,
1642+
Share: codersdk.WorkspaceAppSharingLevelOwner,
1643+
Icon: "/icons/web.svg",
1644+
Order: int32(1),
1645+
},
1646+
{
1647+
Slug: "api-server",
1648+
DisplayName: "API Server",
1649+
URL: "http://localhost:3000",
1650+
OpenIn: codersdk.WorkspaceAppOpenInSlimWindow,
1651+
Share: codersdk.WorkspaceAppSharingLevelAuthenticated,
1652+
Icon: "/icons/api.svg",
1653+
Order: int32(2),
1654+
Hidden: true,
1655+
},
1656+
{
1657+
Slug: "docs",
1658+
DisplayName: "Documentation",
1659+
URL: "http://localhost:4000",
1660+
OpenIn: codersdk.WorkspaceAppOpenInTab,
1661+
Share: codersdk.WorkspaceAppSharingLevelPublic,
1662+
Icon: "/icons/book.svg",
1663+
Order: int32(3),
1664+
},
1665+
},
1666+
},
1667+
},
1668+
afterCreate: func(t *testing.T, subAgent agentcontainers.SubAgent) {
1669+
require.Len(t, subAgent.Apps, 3)
1670+
1671+
// Verify first app
1672+
assert.Equal(t, "web-app", subAgent.Apps[0].Slug)
1673+
assert.Equal(t, "Web Application", subAgent.Apps[0].DisplayName)
1674+
assert.Equal(t, "http://localhost:8080", subAgent.Apps[0].URL)
1675+
assert.Equal(t, codersdk.WorkspaceAppOpenInTab, subAgent.Apps[0].OpenIn)
1676+
assert.Equal(t, codersdk.WorkspaceAppSharingLevelOwner, subAgent.Apps[0].Share)
1677+
assert.Equal(t, "/icons/web.svg", subAgent.Apps[0].Icon)
1678+
assert.Equal(t, int32(1), subAgent.Apps[0].Order)
1679+
1680+
// Verify second app
1681+
assert.Equal(t, "api-server", subAgent.Apps[1].Slug)
1682+
assert.Equal(t, "API Server", subAgent.Apps[1].DisplayName)
1683+
assert.Equal(t, "http://localhost:3000", subAgent.Apps[1].URL)
1684+
assert.Equal(t, codersdk.WorkspaceAppOpenInSlimWindow, subAgent.Apps[1].OpenIn)
1685+
assert.Equal(t, codersdk.WorkspaceAppSharingLevelAuthenticated, subAgent.Apps[1].Share)
1686+
assert.Equal(t, "/icons/api.svg", subAgent.Apps[1].Icon)
1687+
assert.Equal(t, int32(2), subAgent.Apps[1].Order)
1688+
assert.Equal(t, true, subAgent.Apps[1].Hidden)
1689+
1690+
// Verify third app
1691+
assert.Equal(t, "docs", subAgent.Apps[2].Slug)
1692+
assert.Equal(t, "Documentation", subAgent.Apps[2].DisplayName)
1693+
assert.Equal(t, "http://localhost:4000", subAgent.Apps[2].URL)
1694+
assert.Equal(t, codersdk.WorkspaceAppOpenInTab, subAgent.Apps[2].OpenIn)
1695+
assert.Equal(t, codersdk.WorkspaceAppSharingLevelPublic, subAgent.Apps[2].Share)
1696+
assert.Equal(t, "/icons/book.svg", subAgent.Apps[2].Icon)
1697+
assert.Equal(t, int32(3), subAgent.Apps[2].Order)
1698+
},
1699+
},
1700+
{
1701+
name: "AppDeduplication",
1702+
customization: []agentcontainers.CoderCustomization{
1703+
{
1704+
Apps: []agentcontainers.SubAgentApp{
1705+
{
1706+
Slug: "foo-app",
1707+
Hidden: true,
1708+
Order: 1,
1709+
},
1710+
{
1711+
Slug: "bar-app",
1712+
},
1713+
},
1714+
},
1715+
{
1716+
Apps: []agentcontainers.SubAgentApp{
1717+
{
1718+
Slug: "foo-app",
1719+
Order: 2,
1720+
},
1721+
{
1722+
Slug: "baz-app",
1723+
},
1724+
},
1725+
},
1726+
},
1727+
afterCreate: func(t *testing.T, subAgent agentcontainers.SubAgent) {
1728+
require.Len(t, subAgent.Apps, 3)
1729+
1730+
// As the original "foo-app" gets overridden by the later "foo-app",
1731+
// we expect "bar-app" to be first in the order.
1732+
assert.Equal(t, "bar-app", subAgent.Apps[0].Slug)
1733+
assert.Equal(t, "foo-app", subAgent.Apps[1].Slug)
1734+
assert.Equal(t, "baz-app", subAgent.Apps[2].Slug)
1735+
1736+
// We do not expect the properties from the original "foo-app" to be
1737+
// carried over.
1738+
assert.Equal(t, false, subAgent.Apps[1].Hidden)
1739+
assert.Equal(t, int32(2), subAgent.Apps[1].Order)
1740+
},
1741+
},
16061742
}
16071743

16081744
for _, tt := range tests {

agent/agentcontainers/devcontainercli.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"errors"
99
"io"
10+
"os"
1011

1112
"golang.org/x/xerrors"
1213

@@ -32,13 +33,14 @@ type DevcontainerCustomizations struct {
3233

3334
type CoderCustomization struct {
3435
DisplayApps map[codersdk.DisplayApp]bool `json:"displayApps,omitempty"`
36+
Apps []SubAgentApp `json:"apps,omitempty"`
3537
}
3638

3739
// DevcontainerCLI is an interface for the devcontainer CLI.
3840
type DevcontainerCLI interface {
3941
Up(ctx context.Context, workspaceFolder, configPath string, opts ...DevcontainerCLIUpOptions) (id string, err error)
4042
Exec(ctx context.Context, workspaceFolder, configPath string, cmd string, cmdArgs []string, opts ...DevcontainerCLIExecOptions) error
41-
ReadConfig(ctx context.Context, workspaceFolder, configPath string, opts ...DevcontainerCLIReadConfigOptions) (DevcontainerConfig, error)
43+
ReadConfig(ctx context.Context, workspaceFolder, configPath string, env []string, opts ...DevcontainerCLIReadConfigOptions) (DevcontainerConfig, error)
4244
}
4345

4446
// DevcontainerCLIUpOptions are options for the devcontainer CLI Up
@@ -113,8 +115,8 @@ type devcontainerCLIReadConfigConfig struct {
113115
stderr io.Writer
114116
}
115117

116-
// WithExecOutput sets additional stdout and stderr writers for logs
117-
// during Exec operations.
118+
// WithReadConfigOutput sets additional stdout and stderr writers for logs
119+
// during ReadConfig operations.
118120
func WithReadConfigOutput(stdout, stderr io.Writer) DevcontainerCLIReadConfigOptions {
119121
return func(o *devcontainerCLIReadConfigConfig) {
120122
o.stdout = stdout
@@ -250,7 +252,7 @@ func (d *devcontainerCLI) Exec(ctx context.Context, workspaceFolder, configPath
250252
return nil
251253
}
252254

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

@@ -263,6 +265,8 @@ func (d *devcontainerCLI) ReadConfig(ctx context.Context, workspaceFolder, confi
263265
}
264266

265267
c := d.execer.CommandContext(ctx, "devcontainer", args...)
268+
c.Env = append(c.Env, "PATH="+os.Getenv("PATH"))
269+
c.Env = append(c.Env, env...)
266270

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

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