Skip to content

Commit 566e3de

Browse files
committed
fix!: enforce agent names be case-insensitive-unique per-workspace
1 parent 2a248b1 commit 566e3de

File tree

4 files changed

+67
-4
lines changed

4 files changed

+67
-4
lines changed

coderd/provisionerdserver/provisionerdserver.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1891,10 +1891,12 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
18911891
appSlugs = make(map[string]struct{})
18921892
)
18931893
for _, prAgent := range protoResource.Agents {
1894-
if _, ok := agentNames[prAgent.Name]; ok {
1894+
// Agent names must be case-insensitive-unique, to be unambiguous in
1895+
// `coder_app`s and CoderVPN DNS names.
1896+
if _, ok := agentNames[strings.ToLower(prAgent.Name)]; ok {
18951897
return xerrors.Errorf("duplicate agent name %q", prAgent.Name)
18961898
}
1897-
agentNames[prAgent.Name] = struct{}{}
1899+
agentNames[strings.ToLower(prAgent.Name)] = struct{}{}
18981900

18991901
var instanceID sql.NullString
19001902
if prAgent.GetInstanceId() != "" {

coderd/provisionerdserver/provisionerdserver_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1905,6 +1905,32 @@ func TestInsertWorkspaceResource(t *testing.T) {
19051905
})
19061906
require.ErrorContains(t, err, "duplicate app slug")
19071907
})
1908+
t.Run("DuplicateAgentNames", func(t *testing.T) {
1909+
t.Parallel()
1910+
db := dbmem.New()
1911+
job := uuid.New()
1912+
// case-insensitive-unique
1913+
err := insert(db, job, &sdkproto.Resource{
1914+
Name: "something",
1915+
Type: "aws_instance",
1916+
Agents: []*sdkproto.Agent{{
1917+
Name: "dev",
1918+
}, {
1919+
Name: "Dev",
1920+
}},
1921+
})
1922+
require.ErrorContains(t, err, "duplicate agent name")
1923+
err = insert(db, job, &sdkproto.Resource{
1924+
Name: "something",
1925+
Type: "aws_instance",
1926+
Agents: []*sdkproto.Agent{{
1927+
Name: "dev",
1928+
}, {
1929+
Name: "dev",
1930+
}},
1931+
})
1932+
require.ErrorContains(t, err, "duplicate agent name")
1933+
})
19081934
t.Run("Success", func(t *testing.T) {
19091935
t.Parallel()
19101936
db := dbmem.New()

provisioner/terraform/resources.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,12 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
215215
return nil, xerrors.Errorf("decode agent attributes: %w", err)
216216
}
217217

218-
if _, ok := agentNames[tfResource.Name]; ok {
218+
// Agent names must be case-insensitive-unique, to be unambiguous in
219+
// `coder_app`s and CoderVPN DNS names.
220+
if _, ok := agentNames[strings.ToLower(tfResource.Name)]; ok {
219221
return nil, xerrors.Errorf("duplicate agent name: %s", tfResource.Name)
220222
}
221-
agentNames[tfResource.Name] = struct{}{}
223+
agentNames[strings.ToLower(tfResource.Name)] = struct{}{}
222224

223225
// Handling for deprecated attributes. login_before_ready was replaced
224226
// by startup_script_behavior, but we still need to support it for

provisioner/terraform/resources_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,6 +1026,39 @@ func TestAppSlugValidation(t *testing.T) {
10261026
require.ErrorContains(t, err, "duplicate app slug")
10271027
}
10281028

1029+
func TestAgentNameDuplicate(t *testing.T) {
1030+
t.Parallel()
1031+
ctx, logger := ctxAndLogger(t)
1032+
1033+
// nolint:dogsled
1034+
_, filename, _, _ := runtime.Caller(0)
1035+
1036+
dir := filepath.Join(filepath.Dir(filename), "testdata", "multiple-agents")
1037+
tfPlanRaw, err := os.ReadFile(filepath.Join(dir, "multiple-agents.tfplan.json"))
1038+
require.NoError(t, err)
1039+
var tfPlan tfjson.Plan
1040+
err = json.Unmarshal(tfPlanRaw, &tfPlan)
1041+
require.NoError(t, err)
1042+
tfPlanGraph, err := os.ReadFile(filepath.Join(dir, "multiple-agents.tfplan.dot"))
1043+
require.NoError(t, err)
1044+
1045+
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
1046+
if resource.Type == "coder_agent" {
1047+
switch resource.Name {
1048+
case "dev1":
1049+
resource.Name = "dev"
1050+
case "dev2":
1051+
resource.Name = "Dev"
1052+
}
1053+
}
1054+
}
1055+
1056+
state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
1057+
require.Nil(t, state)
1058+
require.Error(t, err)
1059+
require.ErrorContains(t, err, "duplicate agent name")
1060+
}
1061+
10291062
func TestMetadataResourceDuplicate(t *testing.T) {
10301063
t.Parallel()
10311064
ctx, logger := ctxAndLogger(t)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy