feat(agent/agentcontainers): update containers periodically (#17972)

This change introduces a significant refactor to the agentcontainers API
and enables periodic updates of Docker containers rather than on-demand.
Consequently this change also allows us to move away from using a
locking channel and replace it with a mutex, which simplifies usage.

Additionally a previous oversight was fixed, and testing added, to clear
devcontainer running/dirty status when the container has been removed.

Updates coder/coder#16424
Updates coder/internal#621
This commit is contained in:
Mathias Fredriksson
2025-05-22 19:44:33 +03:00
committed by GitHub
parent 13b41c200c
commit d6c14f3d8a
6 changed files with 530 additions and 339 deletions

View File

@ -1325,14 +1325,14 @@ func TestWorkspaceAgentContainers(t *testing.T) {
{
name: "test response",
setupMock: func(mcl *acmock.MockLister) (codersdk.WorkspaceAgentListContainersResponse, error) {
mcl.EXPECT().List(gomock.Any()).Return(testResponse, nil).Times(1)
mcl.EXPECT().List(gomock.Any()).Return(testResponse, nil).AnyTimes()
return testResponse, nil
},
},
{
name: "error response",
setupMock: func(mcl *acmock.MockLister) (codersdk.WorkspaceAgentListContainersResponse, error) {
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, assert.AnError).Times(1)
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, assert.AnError).AnyTimes()
return codersdk.WorkspaceAgentListContainersResponse{}, assert.AnError
},
},
@ -1344,7 +1344,10 @@ func TestWorkspaceAgentContainers(t *testing.T) {
ctrl := gomock.NewController(t)
mcl := acmock.NewMockLister(ctrl)
expected, expectedErr := tc.setupMock(mcl)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{})
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
Logger: &logger,
})
user := coderdtest.CreateFirstUser(t, client)
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OrganizationID: user.OrganizationID,
@ -1353,6 +1356,7 @@ func TestWorkspaceAgentContainers(t *testing.T) {
return agents
}).Do()
_ = agenttest.New(t, client.URL, r.AgentToken, func(o *agent.Options) {
o.Logger = logger.Named("agent")
o.ExperimentalDevcontainersEnabled = true
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithLister(mcl))
})
@ -1422,7 +1426,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
setupMock: func(mcl *acmock.MockLister, mdccli *acmock.MockDevcontainerCLI) int {
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
Containers: []codersdk.WorkspaceAgentContainer{devContainer},
}, nil).Times(1)
}, nil).AnyTimes()
mdccli.EXPECT().Up(gomock.Any(), workspaceFolder, configFile, gomock.Any()).Return("someid", nil).Times(1)
return 0
},
@ -1430,7 +1434,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
{
name: "Container does not exist",
setupMock: func(mcl *acmock.MockLister, mdccli *acmock.MockDevcontainerCLI) int {
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, nil).Times(1)
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, nil).AnyTimes()
return http.StatusNotFound
},
},
@ -1439,7 +1443,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
setupMock: func(mcl *acmock.MockLister, mdccli *acmock.MockDevcontainerCLI) int {
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
Containers: []codersdk.WorkspaceAgentContainer{plainContainer},
}, nil).Times(1)
}, nil).AnyTimes()
return http.StatusNotFound
},
},
@ -1451,7 +1455,10 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
mcl := acmock.NewMockLister(ctrl)
mdccli := acmock.NewMockDevcontainerCLI(ctrl)
wantStatus := tc.setupMock(mcl, mdccli)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{})
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
Logger: &logger,
})
user := coderdtest.CreateFirstUser(t, client)
r := dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
OrganizationID: user.OrganizationID,
@ -1460,6 +1467,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
return agents
}).Do()
_ = agenttest.New(t, client.URL, r.AgentToken, func(o *agent.Options) {
o.Logger = logger.Named("agent")
o.ExperimentalDevcontainersEnabled = true
o.ContainerAPIOptions = append(
o.ContainerAPIOptions,