Skip to content

Commit 3fddfef

Browse files
fix!: enforce agent names be case-insensitive-unique per-workspace (#16614)
Relates to coder/coder-desktop-macos#54 Currently, it's possible to have two agents within the same workspace whose names only differ in capitalization: This leads to an ambiguity in two cases: - For CoderVPN, we'd like to allow support to workspaces with a hostname of the form: `agent.workspace.username.coder`. - Workspace apps (`coder_app`s) currently use subdomains of the form: `<app>--<agent>--<workspace>--<username>(--<suffix>)?`. Of note is that DNS hosts must be strictly lower case, hence the ambiguity. This fix is technically a breaking change, but only for the incredibly rare use case where a user has: - A workspace with two agents - Those agent names differ only in capitalization. Those templates & workspaces will now fail to build. This can be fixed by choosing wholly unique names for the agents.
1 parent 9f5ad23 commit 3fddfef

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