Skip to content

Commit ee6b803

Browse files
feat(agent/agentcontainers): allow auto starting discovered dev containers
1 parent 070178c commit ee6b803

File tree

3 files changed

+131
-4
lines changed

3 files changed

+131
-4
lines changed

agent/agentcontainers/api.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,8 @@ func WithCommandEnv(ce CommandEnv) Option {
143143
strings.HasPrefix(s, "CODER_WORKSPACE_AGENT_URL=") ||
144144
strings.HasPrefix(s, "CODER_AGENT_TOKEN=") ||
145145
strings.HasPrefix(s, "CODER_AGENT_AUTH=") ||
146-
strings.HasPrefix(s, "CODER_AGENT_DEVCONTAINERS_ENABLE=")
146+
strings.HasPrefix(s, "CODER_AGENT_DEVCONTAINERS_ENABLE=") ||
147+
strings.HasPrefix(s, "CODER_AGENT_DEVCONTAINERS_PROJECT_DISCOVERY_ENABLE=")
147148
})
148149
return shell, dir, env, nil
149150
}
@@ -524,23 +525,39 @@ func (api *API) discoverDevcontainersInProject(projectPath string) error {
524525

525526
workspaceFolder := strings.TrimSuffix(path, relativeConfigPath)
526527

527-
logger.Debug(api.ctx, "discovered dev container project", slog.F("workspace_folder", workspaceFolder))
528+
logger := logger.With(slog.F("workspace_folder", workspaceFolder))
529+
logger.Debug(api.ctx, "discovered dev container project")
528530

529531
api.mu.Lock()
530532
if _, found := api.knownDevcontainers[workspaceFolder]; !found {
531-
logger.Debug(api.ctx, "adding dev container project", slog.F("workspace_folder", workspaceFolder))
533+
logger.Debug(api.ctx, "adding dev container project")
532534

533535
dc := codersdk.WorkspaceAgentDevcontainer{
534536
ID: uuid.New(),
535537
Name: "", // Updated later based on container state.
536538
WorkspaceFolder: workspaceFolder,
537539
ConfigPath: path,
538-
Status: "", // Updated later based on container state.
540+
Status: codersdk.WorkspaceAgentDevcontainerStatusStopped,
539541
Dirty: false, // Updated later based on config file changes.
540542
Container: nil,
541543
}
542544

545+
config, err := api.dccli.ReadConfig(api.ctx, workspaceFolder, path, []string{})
546+
if err != nil {
547+
logger.Error(api.ctx, "read project configuration", slog.Error(err))
548+
} else if config.Configuration.Customizations.Coder.AutoStart {
549+
dc.Status = codersdk.WorkspaceAgentDevcontainerStatusStarting
550+
}
551+
543552
api.knownDevcontainers[workspaceFolder] = dc
553+
api.broadcastUpdatesLocked()
554+
555+
if dc.Status == codersdk.WorkspaceAgentDevcontainerStatusStarting {
556+
go func() {
557+
_ = api.CreateDevcontainer(dc.WorkspaceFolder, dc.ConfigPath)
558+
}()
559+
}
560+
544561
}
545562
api.mu.Unlock()
546563
}

agent/agentcontainers/api_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3568,4 +3568,113 @@ func TestDevcontainerDiscovery(t *testing.T) {
35683568
// This is implicitly handled by `testutil.Logger` failing when it
35693569
// detects an error has been logged.
35703570
})
3571+
3572+
t.Run("AutoStart", func(t *testing.T) {
3573+
t.Parallel()
3574+
3575+
tests := []struct {
3576+
name string
3577+
agentDir string
3578+
fs map[string]string
3579+
setupMocks func(mDCCLI *acmock.MockDevcontainerCLI)
3580+
}{
3581+
{
3582+
name: "SingleEnabled",
3583+
agentDir: "/home/coder",
3584+
fs: map[string]string{
3585+
"/home/coder/.git/HEAD": "",
3586+
"/home/coder/.devcontainer/devcontainer.json": "",
3587+
},
3588+
setupMocks: func(mDCCLI *acmock.MockDevcontainerCLI) {
3589+
gomock.InOrder(
3590+
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3591+
"/home/coder",
3592+
"/home/coder/.devcontainer/devcontainer.json",
3593+
[]string{},
3594+
).Return(agentcontainers.DevcontainerConfig{
3595+
Configuration: agentcontainers.DevcontainerConfiguration{
3596+
Customizations: agentcontainers.DevcontainerCustomizations{
3597+
Coder: agentcontainers.CoderCustomization{
3598+
AutoStart: true,
3599+
},
3600+
},
3601+
},
3602+
}, nil),
3603+
mDCCLI.EXPECT().Up(gomock.Any(),
3604+
"/home/coder",
3605+
"/home/coder/.devcontainer/devcontainer.json",
3606+
gomock.Any(),
3607+
).Return("", nil),
3608+
)
3609+
},
3610+
},
3611+
{
3612+
name: "SingleDisabled",
3613+
agentDir: "/home/coder",
3614+
fs: map[string]string{
3615+
"/home/coder/.git/HEAD": "",
3616+
"/home/coder/.devcontainer/devcontainer.json": "",
3617+
},
3618+
setupMocks: func(mDCCLI *acmock.MockDevcontainerCLI) {
3619+
gomock.InOrder(
3620+
mDCCLI.EXPECT().ReadConfig(gomock.Any(),
3621+
"/home/coder",
3622+
"/home/coder/.devcontainer/devcontainer.json",
3623+
[]string{},
3624+
).Return(agentcontainers.DevcontainerConfig{
3625+
Configuration: agentcontainers.DevcontainerConfiguration{
3626+
Customizations: agentcontainers.DevcontainerCustomizations{
3627+
Coder: agentcontainers.CoderCustomization{
3628+
AutoStart: false,
3629+
},
3630+
},
3631+
},
3632+
}, nil),
3633+
)
3634+
},
3635+
},
3636+
}
3637+
3638+
for _, tt := range tests {
3639+
t.Run(tt.name, func(t *testing.T) {
3640+
t.Parallel()
3641+
3642+
var (
3643+
ctx = testutil.Context(t, testutil.WaitShort)
3644+
logger = testutil.Logger(t)
3645+
mClock = quartz.NewMock(t)
3646+
mDCCLI = acmock.NewMockDevcontainerCLI(gomock.NewController(t))
3647+
3648+
r = chi.NewRouter()
3649+
)
3650+
3651+
tt.setupMocks(mDCCLI)
3652+
3653+
api := agentcontainers.NewAPI(logger,
3654+
agentcontainers.WithClock(mClock),
3655+
agentcontainers.WithWatcher(watcher.NewNoop()),
3656+
agentcontainers.WithFileSystem(initFS(t, tt.fs)),
3657+
agentcontainers.WithManifestInfo("owner", "workspace", "parent-agent", "/home/coder"),
3658+
agentcontainers.WithContainerCLI(&fakeContainerCLI{}),
3659+
agentcontainers.WithDevcontainerCLI(mDCCLI),
3660+
agentcontainers.WithProjectDiscovery(true),
3661+
)
3662+
api.Start()
3663+
defer api.Close()
3664+
r.Mount("/", api.Routes())
3665+
3666+
require.Eventuallyf(t, func() bool {
3667+
req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(ctx)
3668+
rec := httptest.NewRecorder()
3669+
r.ServeHTTP(rec, req)
3670+
3671+
got := codersdk.WorkspaceAgentListContainersResponse{}
3672+
err := json.NewDecoder(rec.Body).Decode(&got)
3673+
require.NoError(t, err)
3674+
3675+
return len(got.Devcontainers) >= 1
3676+
}, testutil.WaitShort, testutil.IntervalFast, "dev containers never found")
3677+
})
3678+
}
3679+
})
35713680
}

agent/agentcontainers/devcontainercli.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ type CoderCustomization struct {
9191
Apps []SubAgentApp `json:"apps,omitempty"`
9292
Name string `json:"name,omitempty"`
9393
Ignore bool `json:"ignore,omitempty"`
94+
AutoStart bool `json:"autoStart,omitempty"`
9495
}
9596

9697
type DevcontainerWorkspace struct {

0 commit comments

Comments
 (0)