Skip to content

Commit fb4b02e

Browse files
authored
Merge branch 'main' into stevenmasley/file_cache_perms
2 parents 3b73371 + 8e29ee5 commit fb4b02e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1200
-177
lines changed

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,4 @@ Read [cursor rules](.cursorrules).
101101

102102
## Frontend
103103

104-
For building Frontend refer to [this document](docs/contributing/frontend.md)
104+
For building Frontend refer to [this document](docs/about/contributing/frontend.md)

CODE_OF_CONDUCT.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
<!-- markdownlint-disable MD041 -->
2-
[https://coder.com/docs/contributing/CODE_OF_CONDUCT](https://coder.com/docs/contributing/CODE_OF_CONDUCT)
2+
[https://coder.com/docs/about/contributing/CODE_OF_CONDUCT](https://coder.com/docs/about/contributing/CODE_OF_CONDUCT)

agent/agentcontainers/acmock/acmock.go

Lines changed: 20 additions & 0 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: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1099,13 +1099,44 @@ func (api *API) injectSubAgentIntoContainerLocked(ctx context.Context, dc coders
10991099
directory = DevcontainerDefaultContainerWorkspaceFolder
11001100
}
11011101

1102+
displayAppsMap := map[codersdk.DisplayApp]bool{
1103+
// NOTE(DanielleMaywood):
1104+
// We use the same defaults here as set in terraform-provider-coder.
1105+
// https://github.com/coder/terraform-provider-coder/blob/c1c33f6d556532e75662c0ca373ed8fdea220eb5/provider/agent.go#L38-L51
1106+
codersdk.DisplayAppVSCodeDesktop: true,
1107+
codersdk.DisplayAppVSCodeInsiders: false,
1108+
codersdk.DisplayAppWebTerminal: true,
1109+
codersdk.DisplayAppSSH: true,
1110+
codersdk.DisplayAppPortForward: true,
1111+
}
1112+
1113+
if config, err := api.dccli.ReadConfig(ctx, dc.WorkspaceFolder, dc.ConfigPath); err != nil {
1114+
api.logger.Error(ctx, "unable to read devcontainer config", slog.Error(err))
1115+
} else {
1116+
coderCustomization := config.MergedConfiguration.Customizations.Coder
1117+
1118+
for _, customization := range coderCustomization {
1119+
for app, enabled := range customization.DisplayApps {
1120+
displayAppsMap[app] = enabled
1121+
}
1122+
}
1123+
}
1124+
1125+
displayApps := make([]codersdk.DisplayApp, 0, len(displayAppsMap))
1126+
for app, enabled := range displayAppsMap {
1127+
if enabled {
1128+
displayApps = append(displayApps, app)
1129+
}
1130+
}
1131+
11021132
// The preparation of the subagent is done, now we can create the
11031133
// subagent record in the database to receive the auth token.
11041134
createdAgent, err := api.subAgentClient.Create(ctx, SubAgent{
11051135
Name: dc.Name,
11061136
Directory: directory,
11071137
OperatingSystem: "linux", // Assuming Linux for dev containers.
11081138
Architecture: arch,
1139+
DisplayApps: displayApps,
11091140
})
11101141
if err != nil {
11111142
return xerrors.Errorf("create agent: %w", err)

agent/agentcontainers/api_test.go

Lines changed: 197 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,14 @@ func (f *fakeContainerCLI) ExecAs(ctx context.Context, name, user string, args .
6060
// fakeDevcontainerCLI implements the agentcontainers.DevcontainerCLI
6161
// interface for testing.
6262
type fakeDevcontainerCLI struct {
63-
upID string
64-
upErr error
65-
upErrC chan error // If set, send to return err, close to return upErr.
66-
execErr error
67-
execErrC chan func(cmd string, args ...string) error // If set, send fn to return err, nil or close to return execErr.
63+
upID string
64+
upErr error
65+
upErrC chan error // If set, send to return err, close to return upErr.
66+
execErr error
67+
execErrC chan func(cmd string, args ...string) error // If set, send fn to return err, nil or close to return execErr.
68+
readConfig agentcontainers.DevcontainerConfig
69+
readConfigErr error
70+
readConfigErrC chan error
6871
}
6972

7073
func (f *fakeDevcontainerCLI) Up(ctx context.Context, _, _ string, _ ...agentcontainers.DevcontainerCLIUpOptions) (string, error) {
@@ -95,6 +98,20 @@ func (f *fakeDevcontainerCLI) Exec(ctx context.Context, _, _ string, cmd string,
9598
return f.execErr
9699
}
97100

101+
func (f *fakeDevcontainerCLI) ReadConfig(ctx context.Context, _, _ string, _ ...agentcontainers.DevcontainerCLIReadConfigOptions) (agentcontainers.DevcontainerConfig, error) {
102+
if f.readConfigErrC != nil {
103+
select {
104+
case <-ctx.Done():
105+
return agentcontainers.DevcontainerConfig{}, ctx.Err()
106+
case err, ok := <-f.readConfigErrC:
107+
if ok {
108+
return f.readConfig, err
109+
}
110+
}
111+
}
112+
return f.readConfig, f.readConfigErr
113+
}
114+
98115
// fakeWatcher implements the watcher.Watcher interface for testing.
99116
// It allows controlling what events are sent and when.
100117
type fakeWatcher struct {
@@ -1132,10 +1149,12 @@ func TestAPI(t *testing.T) {
11321149
Containers: []codersdk.WorkspaceAgentContainer{container},
11331150
},
11341151
}
1152+
fDCCLI := &fakeDevcontainerCLI{}
11351153

11361154
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
11371155
api := agentcontainers.NewAPI(
11381156
logger,
1157+
agentcontainers.WithDevcontainerCLI(fDCCLI),
11391158
agentcontainers.WithContainerCLI(fLister),
11401159
agentcontainers.WithWatcher(fWatcher),
11411160
agentcontainers.WithClock(mClock),
@@ -1421,6 +1440,179 @@ func TestAPI(t *testing.T) {
14211440
assert.Contains(t, fakeSAC.deleted, existingAgentID)
14221441
assert.Empty(t, fakeSAC.agents)
14231442
})
1443+
1444+
t.Run("Create", func(t *testing.T) {
1445+
t.Parallel()
1446+
1447+
if runtime.GOOS == "windows" {
1448+
t.Skip("Dev Container tests are not supported on Windows (this test uses mocks but fails due to Windows paths)")
1449+
}
1450+
1451+
tests := []struct {
1452+
name string
1453+
customization []agentcontainers.CoderCustomization
1454+
afterCreate func(t *testing.T, subAgent agentcontainers.SubAgent)
1455+
}{
1456+
{
1457+
name: "WithoutCustomization",
1458+
customization: nil,
1459+
},
1460+
{
1461+
name: "WithDefaultDisplayApps",
1462+
customization: []agentcontainers.CoderCustomization{},
1463+
afterCreate: func(t *testing.T, subAgent agentcontainers.SubAgent) {
1464+
require.Len(t, subAgent.DisplayApps, 4)
1465+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppVSCodeDesktop)
1466+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppWebTerminal)
1467+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppSSH)
1468+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppPortForward)
1469+
},
1470+
},
1471+
{
1472+
name: "WithAllDisplayApps",
1473+
customization: []agentcontainers.CoderCustomization{
1474+
{
1475+
DisplayApps: map[codersdk.DisplayApp]bool{
1476+
codersdk.DisplayAppSSH: true,
1477+
codersdk.DisplayAppWebTerminal: true,
1478+
codersdk.DisplayAppVSCodeDesktop: true,
1479+
codersdk.DisplayAppVSCodeInsiders: true,
1480+
codersdk.DisplayAppPortForward: true,
1481+
},
1482+
},
1483+
},
1484+
afterCreate: func(t *testing.T, subAgent agentcontainers.SubAgent) {
1485+
require.Len(t, subAgent.DisplayApps, 5)
1486+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppSSH)
1487+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppWebTerminal)
1488+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppVSCodeDesktop)
1489+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppVSCodeInsiders)
1490+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppPortForward)
1491+
},
1492+
},
1493+
{
1494+
name: "WithSomeDisplayAppsDisabled",
1495+
customization: []agentcontainers.CoderCustomization{
1496+
{
1497+
DisplayApps: map[codersdk.DisplayApp]bool{
1498+
codersdk.DisplayAppSSH: false,
1499+
codersdk.DisplayAppWebTerminal: false,
1500+
codersdk.DisplayAppVSCodeInsiders: false,
1501+
1502+
// We'll enable vscode in this layer, and disable
1503+
// it in the next layer to ensure a layer can be
1504+
// disabled.
1505+
codersdk.DisplayAppVSCodeDesktop: true,
1506+
1507+
// We disable port-forward in this layer, and
1508+
// then re-enable it in the next layer to ensure
1509+
// that behavior works.
1510+
codersdk.DisplayAppPortForward: false,
1511+
},
1512+
},
1513+
{
1514+
DisplayApps: map[codersdk.DisplayApp]bool{
1515+
codersdk.DisplayAppVSCodeDesktop: false,
1516+
codersdk.DisplayAppPortForward: true,
1517+
},
1518+
},
1519+
},
1520+
afterCreate: func(t *testing.T, subAgent agentcontainers.SubAgent) {
1521+
require.Len(t, subAgent.DisplayApps, 1)
1522+
assert.Contains(t, subAgent.DisplayApps, codersdk.DisplayAppPortForward)
1523+
},
1524+
},
1525+
}
1526+
1527+
for _, tt := range tests {
1528+
t.Run(tt.name, func(t *testing.T) {
1529+
t.Parallel()
1530+
1531+
var (
1532+
ctx = testutil.Context(t, testutil.WaitMedium)
1533+
logger = testutil.Logger(t)
1534+
mClock = quartz.NewMock(t)
1535+
mCCLI = acmock.NewMockContainerCLI(gomock.NewController(t))
1536+
fSAC = &fakeSubAgentClient{createErrC: make(chan error, 1)}
1537+
fDCCLI = &fakeDevcontainerCLI{
1538+
readConfig: agentcontainers.DevcontainerConfig{
1539+
MergedConfiguration: agentcontainers.DevcontainerConfiguration{
1540+
Customizations: agentcontainers.DevcontainerCustomizations{
1541+
Coder: tt.customization,
1542+
},
1543+
},
1544+
},
1545+
execErrC: make(chan func(cmd string, args ...string) error, 1),
1546+
}
1547+
1548+
testContainer = codersdk.WorkspaceAgentContainer{
1549+
ID: "test-container-id",
1550+
FriendlyName: "test-container",
1551+
Image: "test-image",
1552+
Running: true,
1553+
CreatedAt: time.Now(),
1554+
Labels: map[string]string{
1555+
agentcontainers.DevcontainerLocalFolderLabel: "/workspaces",
1556+
agentcontainers.DevcontainerConfigFileLabel: "/workspace/.devcontainer/devcontainer.json",
1557+
},
1558+
}
1559+
)
1560+
1561+
coderBin, err := os.Executable()
1562+
require.NoError(t, err)
1563+
1564+
// Mock the `List` function to always return out test container.
1565+
mCCLI.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
1566+
Containers: []codersdk.WorkspaceAgentContainer{testContainer},
1567+
}, nil).AnyTimes()
1568+
1569+
// Mock the steps used for injecting the coder agent.
1570+
gomock.InOrder(
1571+
mCCLI.EXPECT().DetectArchitecture(gomock.Any(), testContainer.ID).Return(runtime.GOARCH, nil),
1572+
mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil),
1573+
mCCLI.EXPECT().Copy(gomock.Any(), testContainer.ID, coderBin, "/.coder-agent/coder").Return(nil),
1574+
mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil),
1575+
)
1576+
1577+
mClock.Set(time.Now()).MustWait(ctx)
1578+
tickerTrap := mClock.Trap().TickerFunc("updaterLoop")
1579+
1580+
api := agentcontainers.NewAPI(logger,
1581+
agentcontainers.WithClock(mClock),
1582+
agentcontainers.WithContainerCLI(mCCLI),
1583+
agentcontainers.WithDevcontainerCLI(fDCCLI),
1584+
agentcontainers.WithSubAgentClient(fSAC),
1585+
agentcontainers.WithSubAgentURL("test-subagent-url"),
1586+
agentcontainers.WithWatcher(watcher.NewNoop()),
1587+
)
1588+
defer api.Close()
1589+
1590+
// Close before api.Close() defer to avoid deadlock after test.
1591+
defer close(fSAC.createErrC)
1592+
defer close(fDCCLI.execErrC)
1593+
1594+
// Given: We allow agent creation and injection to succeed.
1595+
testutil.RequireSend(ctx, t, fSAC.createErrC, nil)
1596+
testutil.RequireSend(ctx, t, fDCCLI.execErrC, func(cmd string, args ...string) error {
1597+
assert.Equal(t, "pwd", cmd)
1598+
assert.Empty(t, args)
1599+
return nil
1600+
})
1601+
1602+
// Wait until the ticker has been registered.
1603+
tickerTrap.MustWait(ctx).MustRelease(ctx)
1604+
tickerTrap.Close()
1605+
1606+
// Then: We expected it to succeed
1607+
require.Len(t, fSAC.created, 1)
1608+
assert.Equal(t, testContainer.FriendlyName, fSAC.created[0].Name)
1609+
1610+
if tt.afterCreate != nil {
1611+
tt.afterCreate(t, fSAC.created[0])
1612+
}
1613+
})
1614+
}
1615+
})
14241616
}
14251617

14261618
// mustFindDevcontainerByPath returns the devcontainer with the given workspace

0 commit comments

Comments
 (0)