Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit213ba4a

Browse files
committed
Merge branch 'main' of github.com:/coder/coder into dk/coder-ai-task-res
2 parents84caed7 +bacdc28 commit213ba4a

File tree

52 files changed

+3303
-1020
lines changed

Some content is hidden

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

52 files changed

+3303
-1020
lines changed

‎agent/agentcontainers/api.go

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,18 +1147,49 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11471147
}
11481148

11491149
varappsWithPossibleDuplicates []SubAgentApp
1150-
varpossibleAgentNamestring
1151-
1152-
ifconfig,err:=api.dccli.ReadConfig(ctx,dc.WorkspaceFolder,dc.ConfigPath,
1153-
[]string{
1154-
fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s",dc.Name),
1155-
fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s",api.ownerName),
1156-
fmt.Sprintf("CODER_WORKSPACE_NAME=%s",api.workspaceName),
1157-
fmt.Sprintf("CODER_URL=%s",api.subAgentURL),
1158-
},
1159-
);err!=nil {
1160-
api.logger.Error(ctx,"unable to read devcontainer config",slog.Error(err))
1161-
}else {
1150+
1151+
iferr:=func()error {
1152+
var (
1153+
configDevcontainerConfig
1154+
configOutdatedbool
1155+
)
1156+
1157+
readConfig:=func() (DevcontainerConfig,error) {
1158+
returnapi.dccli.ReadConfig(ctx,dc.WorkspaceFolder,dc.ConfigPath, []string{
1159+
fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s",subAgentConfig.Name),
1160+
fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s",api.ownerName),
1161+
fmt.Sprintf("CODER_WORKSPACE_NAME=%s",api.workspaceName),
1162+
fmt.Sprintf("CODER_URL=%s",api.subAgentURL),
1163+
})
1164+
}
1165+
1166+
ifconfig,err=readConfig();err!=nil {
1167+
returnerr
1168+
}
1169+
1170+
// NOTE(DanielleMaywood):
1171+
// We only want to take an agent name specified in the root customization layer.
1172+
// This restricts the ability for a feature to specify the agent name. We may revisit
1173+
// this in the future, but for now we want to restrict this behavior.
1174+
ifname:=config.Configuration.Customizations.Coder.Name;name!="" {
1175+
// We only want to pick this name if it is a valid name.
1176+
ifprovisioner.AgentNameRegex.Match([]byte(name)) {
1177+
subAgentConfig.Name=name
1178+
configOutdated=true
1179+
}else {
1180+
logger.Warn(ctx,"invalid name in devcontainer customization, ignoring",
1181+
slog.F("name",name),
1182+
slog.F("regex",provisioner.AgentNameRegex.String()),
1183+
)
1184+
}
1185+
}
1186+
1187+
ifconfigOutdated {
1188+
ifconfig,err=readConfig();err!=nil {
1189+
returnerr
1190+
}
1191+
}
1192+
11621193
coderCustomization:=config.MergedConfiguration.Customizations.Coder
11631194

11641195
for_,customization:=rangecoderCustomization {
@@ -1176,18 +1207,9 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
11761207
appsWithPossibleDuplicates=append(appsWithPossibleDuplicates,customization.Apps...)
11771208
}
11781209

1179-
// NOTE(DanielleMaywood):
1180-
// We only want to take an agent name specified in the root customization layer.
1181-
// This restricts the ability for a feature to specify the agent name. We may revisit
1182-
// this in the future, but for now we want to restrict this behavior.
1183-
ifname:=config.Configuration.Customizations.Coder.Name;name!="" {
1184-
// We only want to pick this name if it is a valid name.
1185-
ifprovisioner.AgentNameRegex.Match([]byte(name)) {
1186-
possibleAgentName=name
1187-
}else {
1188-
logger.Warn(ctx,"invalid agent name in devcontainer customization, ignoring",slog.F("name",name))
1189-
}
1190-
}
1210+
returnnil
1211+
}();err!=nil {
1212+
api.logger.Error(ctx,"unable to read devcontainer config",slog.Error(err))
11911213
}
11921214

11931215
displayApps:=make([]codersdk.DisplayApp,0,len(displayAppsMap))
@@ -1219,10 +1241,6 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
12191241

12201242
subAgentConfig.DisplayApps=displayApps
12211243
subAgentConfig.Apps=apps
1222-
1223-
ifpossibleAgentName!="" {
1224-
subAgentConfig.Name=possibleAgentName
1225-
}
12261244
}
12271245

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

‎agent/agentcontainers/api_test.go

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,6 +1884,111 @@ func TestAPI(t *testing.T) {
18841884
})
18851885
}
18861886
})
1887+
1888+
t.Run("CreateReadsConfigTwice",func(t*testing.T) {
1889+
t.Parallel()
1890+
1891+
ifruntime.GOOS=="windows" {
1892+
t.Skip("Dev Container tests are not supported on Windows (this test uses mocks but fails due to Windows paths)")
1893+
}
1894+
1895+
var (
1896+
ctx=testutil.Context(t,testutil.WaitMedium)
1897+
logger=testutil.Logger(t)
1898+
mClock=quartz.NewMock(t)
1899+
mCCLI=acmock.NewMockContainerCLI(gomock.NewController(t))
1900+
fSAC=&fakeSubAgentClient{
1901+
logger:logger.Named("fakeSubAgentClient"),
1902+
createErrC:make(chanerror,1),
1903+
}
1904+
fDCCLI=&fakeDevcontainerCLI{
1905+
readConfig: agentcontainers.DevcontainerConfig{
1906+
Configuration: agentcontainers.DevcontainerConfiguration{
1907+
Customizations: agentcontainers.DevcontainerCustomizations{
1908+
Coder: agentcontainers.CoderCustomization{
1909+
// We want to specify a custom name for this agent.
1910+
Name:"custom-name",
1911+
},
1912+
},
1913+
},
1914+
},
1915+
readConfigErrC:make(chanfunc(envs []string)error,2),
1916+
execErrC:make(chanfunc(cmdstring,args...string)error,1),
1917+
}
1918+
1919+
testContainer= codersdk.WorkspaceAgentContainer{
1920+
ID:"test-container-id",
1921+
FriendlyName:"test-container",
1922+
Image:"test-image",
1923+
Running:true,
1924+
CreatedAt:time.Now(),
1925+
Labels:map[string]string{
1926+
agentcontainers.DevcontainerLocalFolderLabel:"/workspaces",
1927+
agentcontainers.DevcontainerConfigFileLabel:"/workspace/.devcontainer/devcontainer.json",
1928+
},
1929+
}
1930+
)
1931+
1932+
coderBin,err:=os.Executable()
1933+
require.NoError(t,err)
1934+
1935+
// Mock the `List` function to always return out test container.
1936+
mCCLI.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{
1937+
Containers: []codersdk.WorkspaceAgentContainer{testContainer},
1938+
},nil).AnyTimes()
1939+
1940+
// Mock the steps used for injecting the coder agent.
1941+
gomock.InOrder(
1942+
mCCLI.EXPECT().DetectArchitecture(gomock.Any(),testContainer.ID).Return(runtime.GOARCH,nil),
1943+
mCCLI.EXPECT().ExecAs(gomock.Any(),testContainer.ID,"root","mkdir","-p","/.coder-agent").Return(nil,nil),
1944+
mCCLI.EXPECT().Copy(gomock.Any(),testContainer.ID,coderBin,"/.coder-agent/coder").Return(nil),
1945+
mCCLI.EXPECT().ExecAs(gomock.Any(),testContainer.ID,"root","chmod","0755","/.coder-agent","/.coder-agent/coder").Return(nil,nil),
1946+
)
1947+
1948+
mClock.Set(time.Now()).MustWait(ctx)
1949+
tickerTrap:=mClock.Trap().TickerFunc("updaterLoop")
1950+
1951+
api:=agentcontainers.NewAPI(logger,
1952+
agentcontainers.WithClock(mClock),
1953+
agentcontainers.WithContainerCLI(mCCLI),
1954+
agentcontainers.WithDevcontainerCLI(fDCCLI),
1955+
agentcontainers.WithSubAgentClient(fSAC),
1956+
agentcontainers.WithSubAgentURL("test-subagent-url"),
1957+
agentcontainers.WithWatcher(watcher.NewNoop()),
1958+
)
1959+
deferapi.Close()
1960+
1961+
// Close before api.Close() defer to avoid deadlock after test.
1962+
deferclose(fSAC.createErrC)
1963+
deferclose(fDCCLI.execErrC)
1964+
deferclose(fDCCLI.readConfigErrC)
1965+
1966+
// Given: We allow agent creation and injection to succeed.
1967+
testutil.RequireSend(ctx,t,fSAC.createErrC,nil)
1968+
testutil.RequireSend(ctx,t,fDCCLI.execErrC,func(cmdstring,args...string)error {
1969+
assert.Equal(t,"pwd",cmd)
1970+
assert.Empty(t,args)
1971+
returnnil
1972+
})
1973+
testutil.RequireSend(ctx,t,fDCCLI.readConfigErrC,func(env []string)error {
1974+
// We expect the wrong workspace agent name passed in first.
1975+
assert.Contains(t,env,"CODER_WORKSPACE_AGENT_NAME=test-container")
1976+
returnnil
1977+
})
1978+
testutil.RequireSend(ctx,t,fDCCLI.readConfigErrC,func(env []string)error {
1979+
// We then expect the agent name passed here to have been read from the config.
1980+
assert.Contains(t,env,"CODER_WORKSPACE_AGENT_NAME=custom-name")
1981+
assert.NotContains(t,env,"CODER_WORKSPACE_AGENT_NAME=test-container")
1982+
returnnil
1983+
})
1984+
1985+
// Wait until the ticker has been registered.
1986+
tickerTrap.MustWait(ctx).MustRelease(ctx)
1987+
tickerTrap.Close()
1988+
1989+
// Then: We expected it to succeed
1990+
require.Len(t,fSAC.created,1)
1991+
})
18871992
}
18881993

18891994
// mustFindDevcontainerByPath returns the devcontainer with the given workspace

‎cli/ssh.go

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -925,36 +925,33 @@ func getWorkspaceAndAgent(ctx context.Context, inv *serpent.Invocation, client *
925925
funcgetWorkspaceAgent(workspace codersdk.Workspace,agentNamestring) (workspaceAgent codersdk.WorkspaceAgent,errerror) {
926926
resources:=workspace.LatestBuild.Resources
927927

928-
agents:=make([]codersdk.WorkspaceAgent,0)
928+
var (
929+
availableNames []string
930+
agents []codersdk.WorkspaceAgent
931+
)
929932
for_,resource:=rangeresources {
930-
agents=append(agents,resource.Agents...)
933+
for_,agent:=rangeresource.Agents {
934+
availableNames=append(availableNames,agent.Name)
935+
agents=append(agents,agent)
936+
}
931937
}
932938
iflen(agents)==0 {
933939
return codersdk.WorkspaceAgent{},xerrors.Errorf("workspace %q has no agents",workspace.Name)
934940
}
941+
slices.Sort(availableNames)
935942
ifagentName!="" {
936943
for_,otherAgent:=rangeagents {
937944
ifotherAgent.Name!=agentName {
938945
continue
939946
}
940-
workspaceAgent=otherAgent
941-
break
942-
}
943-
ifworkspaceAgent.ID==uuid.Nil {
944-
return codersdk.WorkspaceAgent{},xerrors.Errorf("agent not found by name %q",agentName)
947+
returnotherAgent,nil
945948
}
949+
return codersdk.WorkspaceAgent{},xerrors.Errorf("agent not found by name %q, available agents: %v",agentName,availableNames)
946950
}
947-
ifworkspaceAgent.ID==uuid.Nil {
948-
iflen(agents)>1 {
949-
workspaceAgent,err=cryptorand.Element(agents)
950-
iferr!=nil {
951-
return codersdk.WorkspaceAgent{},err
952-
}
953-
}else {
954-
workspaceAgent=agents[0]
955-
}
951+
iflen(agents)==1 {
952+
returnagents[0],nil
956953
}
957-
returnworkspaceAgent,nil
954+
returncodersdk.WorkspaceAgent{},xerrors.Errorf("multiple agents found, please specify the agent name, available agents: %v",availableNames)
958955
}
959956

960957
// Attempt to poll workspace autostop. We write a per-workspace lockfile to

‎cli/ssh_internal_test.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"time"
1212

1313
gliderssh"github.com/gliderlabs/ssh"
14+
"github.com/google/uuid"
1415
"github.com/stretchr/testify/assert"
1516
"github.com/stretchr/testify/require"
1617
"golang.org/x/crypto/ssh"
@@ -346,3 +347,97 @@ func newAsyncCloser(ctx context.Context, t *testing.T) *asyncCloser {
346347
started:make(chanstruct{}),
347348
}
348349
}
350+
351+
funcTest_getWorkspaceAgent(t*testing.T) {
352+
t.Parallel()
353+
354+
createWorkspaceWithAgents:=func(agents []codersdk.WorkspaceAgent) codersdk.Workspace {
355+
return codersdk.Workspace{
356+
Name:"test-workspace",
357+
LatestBuild: codersdk.WorkspaceBuild{
358+
Resources: []codersdk.WorkspaceResource{
359+
{
360+
Agents:agents,
361+
},
362+
},
363+
},
364+
}
365+
}
366+
367+
createAgent:=func(namestring) codersdk.WorkspaceAgent {
368+
return codersdk.WorkspaceAgent{
369+
ID:uuid.New(),
370+
Name:name,
371+
}
372+
}
373+
374+
t.Run("SingleAgent_NoNameSpecified",func(t*testing.T) {
375+
t.Parallel()
376+
agent:=createAgent("main")
377+
workspace:=createWorkspaceWithAgents([]codersdk.WorkspaceAgent{agent})
378+
379+
result,err:=getWorkspaceAgent(workspace,"")
380+
require.NoError(t,err)
381+
assert.Equal(t,agent.ID,result.ID)
382+
assert.Equal(t,"main",result.Name)
383+
})
384+
385+
t.Run("MultipleAgents_NoNameSpecified",func(t*testing.T) {
386+
t.Parallel()
387+
agent1:=createAgent("main1")
388+
agent2:=createAgent("main2")
389+
workspace:=createWorkspaceWithAgents([]codersdk.WorkspaceAgent{agent1,agent2})
390+
391+
_,err:=getWorkspaceAgent(workspace,"")
392+
require.Error(t,err)
393+
assert.Contains(t,err.Error(),"multiple agents found")
394+
assert.Contains(t,err.Error(),"available agents: [main1 main2]")
395+
})
396+
397+
t.Run("AgentNameSpecified_Found",func(t*testing.T) {
398+
t.Parallel()
399+
agent1:=createAgent("main1")
400+
agent2:=createAgent("main2")
401+
workspace:=createWorkspaceWithAgents([]codersdk.WorkspaceAgent{agent1,agent2})
402+
403+
result,err:=getWorkspaceAgent(workspace,"main1")
404+
require.NoError(t,err)
405+
assert.Equal(t,agent1.ID,result.ID)
406+
assert.Equal(t,"main1",result.Name)
407+
})
408+
409+
t.Run("AgentNameSpecified_NotFound",func(t*testing.T) {
410+
t.Parallel()
411+
agent1:=createAgent("main1")
412+
agent2:=createAgent("main2")
413+
workspace:=createWorkspaceWithAgents([]codersdk.WorkspaceAgent{agent1,agent2})
414+
415+
_,err:=getWorkspaceAgent(workspace,"nonexistent")
416+
require.Error(t,err)
417+
assert.Contains(t,err.Error(),`agent not found by name "nonexistent"`)
418+
assert.Contains(t,err.Error(),"available agents: [main1 main2]")
419+
})
420+
421+
t.Run("NoAgents",func(t*testing.T) {
422+
t.Parallel()
423+
workspace:=createWorkspaceWithAgents([]codersdk.WorkspaceAgent{})
424+
425+
_,err:=getWorkspaceAgent(workspace,"")
426+
require.Error(t,err)
427+
assert.Contains(t,err.Error(),`workspace "test-workspace" has no agents`)
428+
})
429+
430+
t.Run("AvailableAgentNames_SortedCorrectly",func(t*testing.T) {
431+
t.Parallel()
432+
// Define agents in non-alphabetical order.
433+
agent2:=createAgent("zod")
434+
agent1:=createAgent("clark")
435+
agent3:=createAgent("krypton")
436+
workspace:=createWorkspaceWithAgents([]codersdk.WorkspaceAgent{agent2,agent1,agent3})
437+
438+
_,err:=getWorkspaceAgent(workspace,"nonexistent")
439+
require.Error(t,err)
440+
// Available agents should be sorted alphabetically.
441+
assert.Contains(t,err.Error(),"available agents: [clark krypton zod]")
442+
})
443+
}

‎coderd/agentapi/subagent_test.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -875,14 +875,9 @@ func TestSubAgentAPI(t *testing.T) {
875875
require.NoError(t,err)
876876
})
877877

878-
t.Run("DeletesWorkspaceApps",func(t*testing.T) {
878+
t.Run("DeleteRetainsWorkspaceApps",func(t*testing.T) {
879879
t.Parallel()
880880

881-
// Skip test on in-memory database since CASCADE DELETE is not implemented
882-
if!dbtestutil.WillUsePostgres() {
883-
t.Skip("CASCADE DELETE behavior requires PostgreSQL")
884-
}
885-
886881
log:=testutil.Logger(t)
887882
ctx:=testutil.Context(t,testutil.WaitShort)
888883
clock:=quartz.NewMock(t)
@@ -931,11 +926,11 @@ func TestSubAgentAPI(t *testing.T) {
931926
_,err=api.Database.GetWorkspaceAgentByID(dbauthz.AsSystemRestricted(ctx),subAgentID)//nolint:gocritic // this is a test.
932927
require.ErrorIs(t,err,sql.ErrNoRows)
933928

934-
// And: The apps arealso deleted (duetoCASCADE DELETE)
935-
//Use raw database since authorization layer requires agenttoexist
929+
// And: The apps are*retained*toavoid causing issues
930+
//where the resources are expectedtobe present.
936931
appsAfterDeletion,err:=db.GetWorkspaceAppsByAgentID(ctx,subAgentID)
937932
require.NoError(t,err)
938-
require.Empty(t,appsAfterDeletion)
933+
require.NotEmpty(t,appsAfterDeletion)
939934
})
940935
})
941936

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp