mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(agent/agentcontainers): refactor Lister to ContainerCLI and implement new methods (#18243)
This commit is contained in:
Generated
+65
-16
@@ -1,9 +1,9 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: .. (interfaces: Lister,DevcontainerCLI)
|
||||
// Source: .. (interfaces: ContainerCLI,DevcontainerCLI)
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -destination ./acmock.go -package acmock .. Lister,DevcontainerCLI
|
||||
// mockgen -destination ./acmock.go -package acmock .. ContainerCLI,DevcontainerCLI
|
||||
//
|
||||
|
||||
// Package acmock is a generated GoMock package.
|
||||
@@ -18,32 +18,81 @@ import (
|
||||
gomock "go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
// MockLister is a mock of Lister interface.
|
||||
type MockLister struct {
|
||||
// MockContainerCLI is a mock of ContainerCLI interface.
|
||||
type MockContainerCLI struct {
|
||||
ctrl *gomock.Controller
|
||||
recorder *MockListerMockRecorder
|
||||
recorder *MockContainerCLIMockRecorder
|
||||
isgomock struct{}
|
||||
}
|
||||
|
||||
// MockListerMockRecorder is the mock recorder for MockLister.
|
||||
type MockListerMockRecorder struct {
|
||||
mock *MockLister
|
||||
// MockContainerCLIMockRecorder is the mock recorder for MockContainerCLI.
|
||||
type MockContainerCLIMockRecorder struct {
|
||||
mock *MockContainerCLI
|
||||
}
|
||||
|
||||
// NewMockLister creates a new mock instance.
|
||||
func NewMockLister(ctrl *gomock.Controller) *MockLister {
|
||||
mock := &MockLister{ctrl: ctrl}
|
||||
mock.recorder = &MockListerMockRecorder{mock}
|
||||
// NewMockContainerCLI creates a new mock instance.
|
||||
func NewMockContainerCLI(ctrl *gomock.Controller) *MockContainerCLI {
|
||||
mock := &MockContainerCLI{ctrl: ctrl}
|
||||
mock.recorder = &MockContainerCLIMockRecorder{mock}
|
||||
return mock
|
||||
}
|
||||
|
||||
// EXPECT returns an object that allows the caller to indicate expected use.
|
||||
func (m *MockLister) EXPECT() *MockListerMockRecorder {
|
||||
func (m *MockContainerCLI) EXPECT() *MockContainerCLIMockRecorder {
|
||||
return m.recorder
|
||||
}
|
||||
|
||||
// Copy mocks base method.
|
||||
func (m *MockContainerCLI) Copy(ctx context.Context, containerName, src, dst string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "Copy", ctx, containerName, src, dst)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// Copy indicates an expected call of Copy.
|
||||
func (mr *MockContainerCLIMockRecorder) Copy(ctx, containerName, src, dst any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Copy", reflect.TypeOf((*MockContainerCLI)(nil).Copy), ctx, containerName, src, dst)
|
||||
}
|
||||
|
||||
// DetectArchitecture mocks base method.
|
||||
func (m *MockContainerCLI) DetectArchitecture(ctx context.Context, containerName string) (string, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DetectArchitecture", ctx, containerName)
|
||||
ret0, _ := ret[0].(string)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DetectArchitecture indicates an expected call of DetectArchitecture.
|
||||
func (mr *MockContainerCLIMockRecorder) DetectArchitecture(ctx, containerName any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DetectArchitecture", reflect.TypeOf((*MockContainerCLI)(nil).DetectArchitecture), ctx, containerName)
|
||||
}
|
||||
|
||||
// ExecAs mocks base method.
|
||||
func (m *MockContainerCLI) ExecAs(ctx context.Context, containerName, user string, args ...string) ([]byte, error) {
|
||||
m.ctrl.T.Helper()
|
||||
varargs := []any{ctx, containerName, user}
|
||||
for _, a := range args {
|
||||
varargs = append(varargs, a)
|
||||
}
|
||||
ret := m.ctrl.Call(m, "ExecAs", varargs...)
|
||||
ret0, _ := ret[0].([]byte)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// ExecAs indicates an expected call of ExecAs.
|
||||
func (mr *MockContainerCLIMockRecorder) ExecAs(ctx, containerName, user any, args ...any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
varargs := append([]any{ctx, containerName, user}, args...)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExecAs", reflect.TypeOf((*MockContainerCLI)(nil).ExecAs), varargs...)
|
||||
}
|
||||
|
||||
// List mocks base method.
|
||||
func (m *MockLister) List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
func (m *MockContainerCLI) List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "List", ctx)
|
||||
ret0, _ := ret[0].(codersdk.WorkspaceAgentListContainersResponse)
|
||||
@@ -52,9 +101,9 @@ func (m *MockLister) List(ctx context.Context) (codersdk.WorkspaceAgentListConta
|
||||
}
|
||||
|
||||
// List indicates an expected call of List.
|
||||
func (mr *MockListerMockRecorder) List(ctx any) *gomock.Call {
|
||||
func (mr *MockContainerCLIMockRecorder) List(ctx any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockLister)(nil).List), ctx)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockContainerCLI)(nil).List), ctx)
|
||||
}
|
||||
|
||||
// MockDevcontainerCLI is a mock of DevcontainerCLI interface.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Package acmock contains a mock implementation of agentcontainers.Lister for use in tests.
|
||||
package acmock
|
||||
|
||||
//go:generate mockgen -destination ./acmock.go -package acmock .. Lister,DevcontainerCLI
|
||||
//go:generate mockgen -destination ./acmock.go -package acmock .. ContainerCLI,DevcontainerCLI
|
||||
|
||||
@@ -43,7 +43,7 @@ type API struct {
|
||||
logger slog.Logger
|
||||
watcher watcher.Watcher
|
||||
execer agentexec.Execer
|
||||
cl Lister
|
||||
ccli ContainerCLI
|
||||
dccli DevcontainerCLI
|
||||
clock quartz.Clock
|
||||
scriptLogger func(logSourceID uuid.UUID) ScriptLogger
|
||||
@@ -80,11 +80,11 @@ func WithExecer(execer agentexec.Execer) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithLister sets the agentcontainers.Lister implementation to use.
|
||||
// The default implementation uses the Docker CLI to list containers.
|
||||
func WithLister(cl Lister) Option {
|
||||
// WithContainerCLI sets the agentcontainers.ContainerCLI implementation
|
||||
// to use. The default implementation uses the Docker CLI.
|
||||
func WithContainerCLI(ccli ContainerCLI) Option {
|
||||
return func(api *API) {
|
||||
api.cl = cl
|
||||
api.ccli = ccli
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,8 +186,8 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
|
||||
for _, opt := range options {
|
||||
opt(api)
|
||||
}
|
||||
if api.cl == nil {
|
||||
api.cl = NewDocker(api.execer)
|
||||
if api.ccli == nil {
|
||||
api.ccli = NewDockerCLI(api.execer)
|
||||
}
|
||||
if api.dccli == nil {
|
||||
api.dccli = NewDevcontainerCLI(logger.Named("devcontainer-cli"), api.execer)
|
||||
@@ -363,7 +363,7 @@ func (api *API) updateContainers(ctx context.Context) error {
|
||||
listCtx, listCancel := context.WithTimeout(ctx, listContainersTimeout)
|
||||
defer listCancel()
|
||||
|
||||
updated, err := api.cl.List(listCtx)
|
||||
updated, err := api.ccli.List(listCtx)
|
||||
if err != nil {
|
||||
// If the context was canceled, we hold off on clearing the
|
||||
// containers cache. This is to avoid clearing the cache if
|
||||
|
||||
@@ -28,15 +28,31 @@ import (
|
||||
"github.com/coder/quartz"
|
||||
)
|
||||
|
||||
// fakeLister implements the agentcontainers.Lister interface for
|
||||
// fakeContainerCLI implements the agentcontainers.ContainerCLI interface for
|
||||
// testing.
|
||||
type fakeLister struct {
|
||||
type fakeContainerCLI struct {
|
||||
containers codersdk.WorkspaceAgentListContainersResponse
|
||||
err error
|
||||
listErr error
|
||||
arch string
|
||||
archErr error
|
||||
copyErr error
|
||||
execErr error
|
||||
}
|
||||
|
||||
func (f *fakeLister) List(_ context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
return f.containers, f.err
|
||||
func (f *fakeContainerCLI) List(_ context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
return f.containers, f.listErr
|
||||
}
|
||||
|
||||
func (f *fakeContainerCLI) DetectArchitecture(_ context.Context, _ string) (string, error) {
|
||||
return f.arch, f.archErr
|
||||
}
|
||||
|
||||
func (f *fakeContainerCLI) Copy(ctx context.Context, name, src, dst string) error {
|
||||
return f.copyErr
|
||||
}
|
||||
|
||||
func (f *fakeContainerCLI) ExecAs(ctx context.Context, name, user string, args ...string) ([]byte, error) {
|
||||
return nil, f.execErr
|
||||
}
|
||||
|
||||
// fakeDevcontainerCLI implements the agentcontainers.DevcontainerCLI
|
||||
@@ -180,7 +196,7 @@ func TestAPI(t *testing.T) {
|
||||
// initialData to be stored in the handler
|
||||
initialData initialDataPayload
|
||||
// function to set up expectations for the mock
|
||||
setupMock func(mcl *acmock.MockLister, preReq *gomock.Call)
|
||||
setupMock func(mcl *acmock.MockContainerCLI, preReq *gomock.Call)
|
||||
// expected result
|
||||
expected codersdk.WorkspaceAgentListContainersResponse
|
||||
// expected error
|
||||
@@ -189,7 +205,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "no initial data",
|
||||
initialData: initialDataPayload{makeResponse(), nil},
|
||||
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, preReq *gomock.Call) {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt), nil).After(preReq).AnyTimes()
|
||||
},
|
||||
expected: makeResponse(fakeCt),
|
||||
@@ -207,7 +223,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "lister error only during initial data",
|
||||
initialData: initialDataPayload{makeResponse(), assert.AnError},
|
||||
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, preReq *gomock.Call) {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt), nil).After(preReq).AnyTimes()
|
||||
},
|
||||
expected: makeResponse(fakeCt),
|
||||
@@ -215,7 +231,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "lister error after initial data",
|
||||
initialData: initialDataPayload{makeResponse(fakeCt), nil},
|
||||
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, preReq *gomock.Call) {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(), assert.AnError).After(preReq).AnyTimes()
|
||||
},
|
||||
expectedErr: assert.AnError.Error(),
|
||||
@@ -223,7 +239,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "updated data",
|
||||
initialData: initialDataPayload{makeResponse(fakeCt), nil},
|
||||
setupMock: func(mcl *acmock.MockLister, preReq *gomock.Call) {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, preReq *gomock.Call) {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(makeResponse(fakeCt2), nil).After(preReq).AnyTimes()
|
||||
},
|
||||
expected: makeResponse(fakeCt2),
|
||||
@@ -236,7 +252,7 @@ func TestAPI(t *testing.T) {
|
||||
mClock = quartz.NewMock(t)
|
||||
tickerTrap = mClock.Trap().TickerFunc("updaterLoop")
|
||||
mCtrl = gomock.NewController(t)
|
||||
mLister = acmock.NewMockLister(mCtrl)
|
||||
mLister = acmock.NewMockContainerCLI(mCtrl)
|
||||
logger = slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
||||
r = chi.NewRouter()
|
||||
)
|
||||
@@ -250,7 +266,7 @@ func TestAPI(t *testing.T) {
|
||||
|
||||
api := agentcontainers.NewAPI(logger,
|
||||
agentcontainers.WithClock(mClock),
|
||||
agentcontainers.WithLister(mLister),
|
||||
agentcontainers.WithContainerCLI(mLister),
|
||||
)
|
||||
defer api.Close()
|
||||
r.Mount("/", api.Routes())
|
||||
@@ -326,7 +342,7 @@ func TestAPI(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
containerID string
|
||||
lister *fakeLister
|
||||
lister *fakeContainerCLI
|
||||
devcontainerCLI *fakeDevcontainerCLI
|
||||
wantStatus []int
|
||||
wantBody []string
|
||||
@@ -334,7 +350,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "Missing container ID",
|
||||
containerID: "",
|
||||
lister: &fakeLister{},
|
||||
lister: &fakeContainerCLI{},
|
||||
devcontainerCLI: &fakeDevcontainerCLI{},
|
||||
wantStatus: []int{http.StatusBadRequest},
|
||||
wantBody: []string{"Missing container ID or name"},
|
||||
@@ -342,8 +358,8 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "List error",
|
||||
containerID: "container-id",
|
||||
lister: &fakeLister{
|
||||
err: xerrors.New("list error"),
|
||||
lister: &fakeContainerCLI{
|
||||
listErr: xerrors.New("list error"),
|
||||
},
|
||||
devcontainerCLI: &fakeDevcontainerCLI{},
|
||||
wantStatus: []int{http.StatusInternalServerError},
|
||||
@@ -352,7 +368,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "Container not found",
|
||||
containerID: "nonexistent-container",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{validContainer},
|
||||
},
|
||||
@@ -364,7 +380,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "Missing workspace folder label",
|
||||
containerID: "missing-folder-container",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{missingFolderContainer},
|
||||
},
|
||||
@@ -376,7 +392,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "Devcontainer CLI error",
|
||||
containerID: "container-id",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{validContainer},
|
||||
},
|
||||
@@ -390,7 +406,7 @@ func TestAPI(t *testing.T) {
|
||||
{
|
||||
name: "OK",
|
||||
containerID: "container-id",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{validContainer},
|
||||
},
|
||||
@@ -423,7 +439,7 @@ func TestAPI(t *testing.T) {
|
||||
api := agentcontainers.NewAPI(
|
||||
logger,
|
||||
agentcontainers.WithClock(mClock),
|
||||
agentcontainers.WithLister(tt.lister),
|
||||
agentcontainers.WithContainerCLI(tt.lister),
|
||||
agentcontainers.WithDevcontainerCLI(tt.devcontainerCLI),
|
||||
agentcontainers.WithWatcher(watcher.NewNoop()),
|
||||
)
|
||||
@@ -559,7 +575,7 @@ func TestAPI(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
lister *fakeLister
|
||||
lister *fakeContainerCLI
|
||||
knownDevcontainers []codersdk.WorkspaceAgentDevcontainer
|
||||
wantStatus int
|
||||
wantCount int
|
||||
@@ -567,20 +583,20 @@ func TestAPI(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "List error",
|
||||
lister: &fakeLister{
|
||||
err: xerrors.New("list error"),
|
||||
lister: &fakeContainerCLI{
|
||||
listErr: xerrors.New("list error"),
|
||||
},
|
||||
wantStatus: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "Empty containers",
|
||||
lister: &fakeLister{},
|
||||
lister: &fakeContainerCLI{},
|
||||
wantStatus: http.StatusOK,
|
||||
wantCount: 0,
|
||||
},
|
||||
{
|
||||
name: "Only known devcontainers, no containers",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{},
|
||||
},
|
||||
@@ -597,7 +613,7 @@ func TestAPI(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Runtime-detected devcontainer",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
{
|
||||
@@ -631,7 +647,7 @@ func TestAPI(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Mixed known and runtime-detected devcontainers",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
{
|
||||
@@ -679,7 +695,7 @@ func TestAPI(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Both running and non-running containers have container references",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
{
|
||||
@@ -723,7 +739,7 @@ func TestAPI(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Config path update",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
{
|
||||
@@ -759,7 +775,7 @@ func TestAPI(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Name generation and uniqueness",
|
||||
lister: &fakeLister{
|
||||
lister: &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
{
|
||||
@@ -831,7 +847,7 @@ func TestAPI(t *testing.T) {
|
||||
r := chi.NewRouter()
|
||||
apiOptions := []agentcontainers.Option{
|
||||
agentcontainers.WithClock(mClock),
|
||||
agentcontainers.WithLister(tt.lister),
|
||||
agentcontainers.WithContainerCLI(tt.lister),
|
||||
agentcontainers.WithWatcher(watcher.NewNoop()),
|
||||
}
|
||||
|
||||
@@ -914,7 +930,7 @@ func TestAPI(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
fLister := &fakeLister{
|
||||
fLister := &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{container},
|
||||
},
|
||||
@@ -926,7 +942,7 @@ func TestAPI(t *testing.T) {
|
||||
|
||||
api := agentcontainers.NewAPI(logger,
|
||||
agentcontainers.WithClock(mClock),
|
||||
agentcontainers.WithLister(fLister),
|
||||
agentcontainers.WithContainerCLI(fLister),
|
||||
agentcontainers.WithWatcher(fWatcher),
|
||||
agentcontainers.WithDevcontainers(
|
||||
[]codersdk.WorkspaceAgentDevcontainer{dc},
|
||||
@@ -1013,7 +1029,7 @@ func TestAPI(t *testing.T) {
|
||||
mClock.Set(startTime)
|
||||
tickerTrap := mClock.Trap().TickerFunc("updaterLoop")
|
||||
fWatcher := newFakeWatcher(t)
|
||||
fLister := &fakeLister{
|
||||
fLister := &fakeContainerCLI{
|
||||
containers: codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{container},
|
||||
},
|
||||
@@ -1022,7 +1038,7 @@ func TestAPI(t *testing.T) {
|
||||
logger := slogtest.Make(t, nil).Leveled(slog.LevelDebug)
|
||||
api := agentcontainers.NewAPI(
|
||||
logger,
|
||||
agentcontainers.WithLister(fLister),
|
||||
agentcontainers.WithContainerCLI(fLister),
|
||||
agentcontainers.WithWatcher(fWatcher),
|
||||
agentcontainers.WithClock(mClock),
|
||||
)
|
||||
|
||||
@@ -6,19 +6,32 @@ import (
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
)
|
||||
|
||||
// Lister is an interface for listing containers visible to the
|
||||
// workspace agent.
|
||||
type Lister interface {
|
||||
// ContainerCLI is an interface for interacting with containers in a workspace.
|
||||
type ContainerCLI interface {
|
||||
// List returns a list of containers visible to the workspace agent.
|
||||
// This should include running and stopped containers.
|
||||
List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error)
|
||||
// DetectArchitecture detects the architecture of a container.
|
||||
DetectArchitecture(ctx context.Context, containerName string) (string, error)
|
||||
// Copy copies a file from the host to a container.
|
||||
Copy(ctx context.Context, containerName, src, dst string) error
|
||||
// ExecAs executes a command in a container as a specific user.
|
||||
ExecAs(ctx context.Context, containerName, user string, args ...string) ([]byte, error)
|
||||
}
|
||||
|
||||
// NoopLister is a Lister interface that never returns any containers.
|
||||
type NoopLister struct{}
|
||||
// noopContainerCLI is a ContainerCLI that does nothing.
|
||||
type noopContainerCLI struct{}
|
||||
|
||||
var _ Lister = NoopLister{}
|
||||
var _ ContainerCLI = noopContainerCLI{}
|
||||
|
||||
func (NoopLister) List(_ context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
func (noopContainerCLI) List(_ context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
return codersdk.WorkspaceAgentListContainersResponse{}, nil
|
||||
}
|
||||
|
||||
func (noopContainerCLI) DetectArchitecture(_ context.Context, _ string) (string, error) {
|
||||
return "<none>", nil
|
||||
}
|
||||
func (noopContainerCLI) Copy(_ context.Context, _ string, _ string, _ string) error { return nil }
|
||||
func (noopContainerCLI) ExecAs(_ context.Context, _ string, _ string, _ ...string) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -228,23 +228,23 @@ func run(ctx context.Context, execer agentexec.Execer, cmd string, args ...strin
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
// DockerCLILister is a ContainerLister that lists containers using the docker CLI
|
||||
type DockerCLILister struct {
|
||||
// dockerCLI is an implementation for Docker CLI that lists containers.
|
||||
type dockerCLI struct {
|
||||
execer agentexec.Execer
|
||||
}
|
||||
|
||||
var _ Lister = &DockerCLILister{}
|
||||
var _ ContainerCLI = (*dockerCLI)(nil)
|
||||
|
||||
func NewDocker(execer agentexec.Execer) Lister {
|
||||
return &DockerCLILister{
|
||||
execer: agentexec.DefaultExecer,
|
||||
func NewDockerCLI(execer agentexec.Execer) ContainerCLI {
|
||||
return &dockerCLI{
|
||||
execer: execer,
|
||||
}
|
||||
}
|
||||
|
||||
func (dcl *DockerCLILister) List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
func (dcli *dockerCLI) List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
// List all container IDs, one per line, with no truncation
|
||||
cmd := dcl.execer.CommandContext(ctx, "docker", "ps", "--all", "--quiet", "--no-trunc")
|
||||
cmd := dcli.execer.CommandContext(ctx, "docker", "ps", "--all", "--quiet", "--no-trunc")
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
if err := cmd.Run(); err != nil {
|
||||
@@ -288,7 +288,7 @@ func (dcl *DockerCLILister) List(ctx context.Context) (codersdk.WorkspaceAgentLi
|
||||
// will still contain valid JSON. We will just end up missing
|
||||
// information about the removed container. We could potentially
|
||||
// log this error, but I'm not sure it's worth it.
|
||||
dockerInspectStdout, dockerInspectStderr, err := runDockerInspect(ctx, dcl.execer, ids...)
|
||||
dockerInspectStdout, dockerInspectStderr, err := runDockerInspect(ctx, dcli.execer, ids...)
|
||||
if err != nil {
|
||||
return codersdk.WorkspaceAgentListContainersResponse{}, xerrors.Errorf("run docker inspect: %w: %s", err, dockerInspectStderr)
|
||||
}
|
||||
@@ -517,3 +517,71 @@ func isLoopbackOrUnspecified(ips string) bool {
|
||||
}
|
||||
return nip.IsLoopback() || nip.IsUnspecified()
|
||||
}
|
||||
|
||||
// DetectArchitecture detects the architecture of a container by inspecting its
|
||||
// image.
|
||||
func (dcli *dockerCLI) DetectArchitecture(ctx context.Context, containerName string) (string, error) {
|
||||
// Inspect the container to get the image name, which contains the architecture.
|
||||
stdout, stderr, err := runCmd(ctx, dcli.execer, "docker", "inspect", "--format", "{{.Config.Image}}", containerName)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("inspect container %s: %w: %s", containerName, err, stderr)
|
||||
}
|
||||
imageName := string(stdout)
|
||||
if imageName == "" {
|
||||
return "", xerrors.Errorf("no image found for container %s", containerName)
|
||||
}
|
||||
|
||||
stdout, stderr, err = runCmd(ctx, dcli.execer, "docker", "inspect", "--format", "{{.Architecture}}", imageName)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("inspect image %s: %w: %s", imageName, err, stderr)
|
||||
}
|
||||
arch := string(stdout)
|
||||
if arch == "" {
|
||||
return "", xerrors.Errorf("no architecture found for image %s", imageName)
|
||||
}
|
||||
return arch, nil
|
||||
}
|
||||
|
||||
// Copy copies a file from the host to a container.
|
||||
func (dcli *dockerCLI) Copy(ctx context.Context, containerName, src, dst string) error {
|
||||
_, stderr, err := runCmd(ctx, dcli.execer, "docker", "cp", src, containerName+":"+dst)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("copy %s to %s:%s: %w: %s", src, containerName, dst, err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExecAs executes a command in a container as a specific user.
|
||||
func (dcli *dockerCLI) ExecAs(ctx context.Context, containerName, uid string, args ...string) ([]byte, error) {
|
||||
execArgs := []string{"exec"}
|
||||
if uid != "" {
|
||||
altUID := uid
|
||||
if uid == "root" {
|
||||
// UID 0 is more portable than the name root, so we use that
|
||||
// because some containers may not have a user named "root".
|
||||
altUID = "0"
|
||||
}
|
||||
execArgs = append(execArgs, "--user", altUID)
|
||||
}
|
||||
execArgs = append(execArgs, containerName)
|
||||
execArgs = append(execArgs, args...)
|
||||
|
||||
stdout, stderr, err := runCmd(ctx, dcli.execer, "docker", execArgs...)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("exec in container %s as user %s: %w: %s", containerName, uid, err, stderr)
|
||||
}
|
||||
return stdout, nil
|
||||
}
|
||||
|
||||
// runCmd is a helper function that runs a command with the given
|
||||
// arguments and returns the stdout and stderr output.
|
||||
func runCmd(ctx context.Context, execer agentexec.Execer, cmd string, args ...string) (stdout, stderr []byte, err error) {
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
c := execer.CommandContext(ctx, cmd, args...)
|
||||
c.Stdout = &stdoutBuf
|
||||
c.Stderr = &stderrBuf
|
||||
err = c.Run()
|
||||
stdout = bytes.TrimSpace(stdoutBuf.Bytes())
|
||||
stderr = bytes.TrimSpace(stderrBuf.Bytes())
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
package agentcontainers_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ory/dockertest/v3"
|
||||
"github.com/ory/dockertest/v3/docker"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/coder/coder/v2/agent/agentcontainers"
|
||||
"github.com/coder/coder/v2/agent/agentexec"
|
||||
"github.com/coder/coder/v2/testutil"
|
||||
)
|
||||
|
||||
// TestIntegrationDockerCLI tests the DetectArchitecture, Copy, and
|
||||
// ExecAs methods using a real Docker container. All tests share a
|
||||
// single container to avoid setup overhead.
|
||||
//
|
||||
// Run manually with: CODER_TEST_USE_DOCKER=1 go test ./agent/agentcontainers -run TestIntegrationDockerCLI
|
||||
//
|
||||
//nolint:tparallel,paralleltest // Docker integration tests don't run in parallel to avoid flakiness.
|
||||
func TestIntegrationDockerCLI(t *testing.T) {
|
||||
if ctud, ok := os.LookupEnv("CODER_TEST_USE_DOCKER"); !ok || ctud != "1" {
|
||||
t.Skip("Set CODER_TEST_USE_DOCKER=1 to run this test")
|
||||
}
|
||||
|
||||
pool, err := dockertest.NewPool("")
|
||||
require.NoError(t, err, "Could not connect to docker")
|
||||
|
||||
// Start a simple busybox container for all subtests to share.
|
||||
ct, err := pool.RunWithOptions(&dockertest.RunOptions{
|
||||
Repository: "busybox",
|
||||
Tag: "latest",
|
||||
Cmd: []string{"sleep", "infinity"},
|
||||
}, func(config *docker.HostConfig) {
|
||||
config.AutoRemove = true
|
||||
config.RestartPolicy = docker.RestartPolicy{Name: "no"}
|
||||
})
|
||||
require.NoError(t, err, "Could not start test docker container")
|
||||
t.Logf("Created container %q", ct.Container.Name)
|
||||
t.Cleanup(func() {
|
||||
assert.NoError(t, pool.Purge(ct), "Could not purge resource %q", ct.Container.Name)
|
||||
t.Logf("Purged container %q", ct.Container.Name)
|
||||
})
|
||||
|
||||
// Wait for container to start.
|
||||
require.Eventually(t, func() bool {
|
||||
ct, ok := pool.ContainerByName(ct.Container.Name)
|
||||
return ok && ct.Container.State.Running
|
||||
}, testutil.WaitShort, testutil.IntervalSlow, "Container did not start in time")
|
||||
|
||||
dcli := agentcontainers.NewDockerCLI(agentexec.DefaultExecer)
|
||||
ctx := testutil.Context(t, testutil.WaitMedium) // Longer timeout for multiple subtests
|
||||
containerName := strings.TrimPrefix(ct.Container.Name, "/")
|
||||
|
||||
t.Run("DetectArchitecture", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
arch, err := dcli.DetectArchitecture(ctx, containerName)
|
||||
require.NoError(t, err, "DetectArchitecture failed")
|
||||
require.NotEmpty(t, arch, "arch has no content")
|
||||
require.Equal(t, runtime.GOARCH, arch, "architecture does not match runtime, did you run this test with a remote Docker socket?")
|
||||
|
||||
t.Logf("Detected architecture: %s", arch)
|
||||
})
|
||||
|
||||
t.Run("Copy", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
want := "Help, I'm trapped!"
|
||||
tempFile := filepath.Join(t.TempDir(), "test-file.txt")
|
||||
err := os.WriteFile(tempFile, []byte(want), 0o600)
|
||||
require.NoError(t, err, "create test file failed")
|
||||
|
||||
destPath := "/tmp/copied-file.txt"
|
||||
err = dcli.Copy(ctx, containerName, tempFile, destPath)
|
||||
require.NoError(t, err, "Copy failed")
|
||||
|
||||
got, err := dcli.ExecAs(ctx, containerName, "", "cat", destPath)
|
||||
require.NoError(t, err, "ExecAs failed after Copy")
|
||||
require.Equal(t, want, string(got), "copied file content did not match original")
|
||||
|
||||
t.Logf("Successfully copied file from %s to container %s:%s", tempFile, containerName, destPath)
|
||||
})
|
||||
|
||||
t.Run("ExecAs", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test ExecAs without specifying user (should use container's default).
|
||||
want := "root"
|
||||
got, err := dcli.ExecAs(ctx, containerName, "", "whoami")
|
||||
require.NoError(t, err, "ExecAs without user should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs without user should output expected string")
|
||||
|
||||
// Test ExecAs with numeric UID (non root).
|
||||
want = "1000"
|
||||
_, err = dcli.ExecAs(ctx, containerName, want, "whoami")
|
||||
require.Error(t, err, "ExecAs with UID 1000 should fail as user does not exist in busybox")
|
||||
require.Contains(t, err.Error(), "whoami: unknown uid 1000", "ExecAs with UID 1000 should return 'unknown uid' error")
|
||||
|
||||
// Test ExecAs with root user (should convert "root" to "0", which still outputs root due to passwd).
|
||||
want = "root"
|
||||
got, err = dcli.ExecAs(ctx, containerName, "root", "whoami")
|
||||
require.NoError(t, err, "ExecAs with root user should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs with root user should output expected string")
|
||||
|
||||
// Test ExecAs with numeric UID.
|
||||
want = "root"
|
||||
got, err = dcli.ExecAs(ctx, containerName, "0", "whoami")
|
||||
require.NoError(t, err, "ExecAs with UID 0 should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs with UID 0 should output expected string")
|
||||
|
||||
// Test ExecAs with multiple arguments.
|
||||
want = "multiple args test"
|
||||
got, err = dcli.ExecAs(ctx, containerName, "", "sh", "-c", "echo '"+want+"'")
|
||||
require.NoError(t, err, "ExecAs with multiple arguments should succeed")
|
||||
require.Equal(t, want, string(got), "ExecAs with multiple arguments should output expected string")
|
||||
|
||||
t.Logf("Successfully executed commands in container %s", containerName)
|
||||
})
|
||||
}
|
||||
@@ -78,7 +78,7 @@ func TestIntegrationDocker(t *testing.T) {
|
||||
return ok && ct.Container.State.Running
|
||||
}, testutil.WaitShort, testutil.IntervalSlow, "Container did not start in time")
|
||||
|
||||
dcl := agentcontainers.NewDocker(agentexec.DefaultExecer)
|
||||
dcl := agentcontainers.NewDockerCLI(agentexec.DefaultExecer)
|
||||
ctx := testutil.Context(t, testutil.WaitShort)
|
||||
actual, err := dcl.List(ctx)
|
||||
require.NoError(t, err, "Could not list containers")
|
||||
|
||||
+4
-4
@@ -306,7 +306,7 @@ func TestOpenVSCodeDevContainer(t *testing.T) {
|
||||
containerFolder := "/workspace/coder"
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mcl := acmock.NewMockLister(ctrl)
|
||||
mcl := acmock.NewMockContainerCLI(ctrl)
|
||||
mcl.EXPECT().List(gomock.Any()).Return(
|
||||
codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
@@ -337,7 +337,7 @@ func TestOpenVSCodeDevContainer(t *testing.T) {
|
||||
|
||||
_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
|
||||
o.ExperimentalDevcontainersEnabled = true
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithLister(mcl))
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithContainerCLI(mcl))
|
||||
})
|
||||
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()
|
||||
|
||||
@@ -481,7 +481,7 @@ func TestOpenVSCodeDevContainer_NoAgentDirectory(t *testing.T) {
|
||||
containerFolder := "/workspace/coder"
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mcl := acmock.NewMockLister(ctrl)
|
||||
mcl := acmock.NewMockContainerCLI(ctrl)
|
||||
mcl.EXPECT().List(gomock.Any()).Return(
|
||||
codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
@@ -511,7 +511,7 @@ func TestOpenVSCodeDevContainer_NoAgentDirectory(t *testing.T) {
|
||||
|
||||
_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
|
||||
o.ExperimentalDevcontainersEnabled = true
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithLister(mcl))
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithContainerCLI(mcl))
|
||||
})
|
||||
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()
|
||||
|
||||
|
||||
+2
-2
@@ -2057,7 +2057,7 @@ func TestSSH_Container(t *testing.T) {
|
||||
ctx := testutil.Context(t, testutil.WaitLong)
|
||||
client, workspace, agentToken := setupWorkspaceForAgent(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
mLister := acmock.NewMockLister(ctrl)
|
||||
mLister := acmock.NewMockContainerCLI(ctrl)
|
||||
mLister.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{
|
||||
{
|
||||
@@ -2069,7 +2069,7 @@ func TestSSH_Container(t *testing.T) {
|
||||
}, nil).AnyTimes()
|
||||
_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
|
||||
o.ExperimentalDevcontainersEnabled = true
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithLister(mLister))
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithContainerCLI(mLister))
|
||||
})
|
||||
_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()
|
||||
|
||||
|
||||
@@ -1320,18 +1320,18 @@ func TestWorkspaceAgentContainers(t *testing.T) {
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
setupMock func(*acmock.MockLister) (codersdk.WorkspaceAgentListContainersResponse, error)
|
||||
setupMock func(*acmock.MockContainerCLI) (codersdk.WorkspaceAgentListContainersResponse, error)
|
||||
}{
|
||||
{
|
||||
name: "test response",
|
||||
setupMock: func(mcl *acmock.MockLister) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(testResponse, nil).AnyTimes()
|
||||
return testResponse, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error response",
|
||||
setupMock: func(mcl *acmock.MockLister) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI) (codersdk.WorkspaceAgentListContainersResponse, error) {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, assert.AnError).AnyTimes()
|
||||
return codersdk.WorkspaceAgentListContainersResponse{}, assert.AnError
|
||||
},
|
||||
@@ -1342,7 +1342,7 @@ func TestWorkspaceAgentContainers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mcl := acmock.NewMockLister(ctrl)
|
||||
mcl := acmock.NewMockContainerCLI(ctrl)
|
||||
expected, expectedErr := tc.setupMock(mcl)
|
||||
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
||||
client, db := coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
||||
@@ -1358,7 +1358,7 @@ func TestWorkspaceAgentContainers(t *testing.T) {
|
||||
_ = 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))
|
||||
o.ContainerAPIOptions = append(o.ContainerAPIOptions, agentcontainers.WithContainerCLI(mcl))
|
||||
})
|
||||
resources := coderdtest.NewWorkspaceAgentWaiter(t, client, r.Workspace.ID).Wait()
|
||||
require.Len(t, resources, 1, "expected one resource")
|
||||
@@ -1419,11 +1419,11 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
setupMock func(*acmock.MockLister, *acmock.MockDevcontainerCLI) (status int)
|
||||
setupMock func(*acmock.MockContainerCLI, *acmock.MockDevcontainerCLI) (status int)
|
||||
}{
|
||||
{
|
||||
name: "Recreate",
|
||||
setupMock: func(mcl *acmock.MockLister, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{devContainer},
|
||||
}, nil).AnyTimes()
|
||||
@@ -1433,14 +1433,14 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "Container does not exist",
|
||||
setupMock: func(mcl *acmock.MockLister, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{}, nil).AnyTimes()
|
||||
return http.StatusNotFound
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Not a devcontainer",
|
||||
setupMock: func(mcl *acmock.MockLister, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
setupMock: func(mcl *acmock.MockContainerCLI, mdccli *acmock.MockDevcontainerCLI) int {
|
||||
mcl.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
|
||||
Containers: []codersdk.WorkspaceAgentContainer{plainContainer},
|
||||
}, nil).AnyTimes()
|
||||
@@ -1452,7 +1452,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctrl := gomock.NewController(t)
|
||||
mcl := acmock.NewMockLister(ctrl)
|
||||
mcl := acmock.NewMockContainerCLI(ctrl)
|
||||
mdccli := acmock.NewMockDevcontainerCLI(ctrl)
|
||||
wantStatus := tc.setupMock(mcl, mdccli)
|
||||
logger := slogtest.Make(t, &slogtest.Options{IgnoreErrors: true}).Leveled(slog.LevelDebug)
|
||||
@@ -1471,7 +1471,7 @@ func TestWorkspaceAgentRecreateDevcontainer(t *testing.T) {
|
||||
o.ExperimentalDevcontainersEnabled = true
|
||||
o.ContainerAPIOptions = append(
|
||||
o.ContainerAPIOptions,
|
||||
agentcontainers.WithLister(mcl),
|
||||
agentcontainers.WithContainerCLI(mcl),
|
||||
agentcontainers.WithDevcontainerCLI(mdccli),
|
||||
agentcontainers.WithWatcher(watcher.NewNoop()),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user