From 566e3de17f5ad3698896056d42e96d0a4a9664a9 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 19 Feb 2025 05:26:25 +0000 Subject: [PATCH] fix!: enforce agent names be case-insensitive-unique per-workspace --- .../provisionerdserver/provisionerdserver.go | 6 ++-- .../provisionerdserver_test.go | 26 +++++++++++++++ provisioner/terraform/resources.go | 6 ++-- provisioner/terraform/resources_test.go | 33 +++++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/coderd/provisionerdserver/provisionerdserver.go b/coderd/provisionerdserver/provisionerdserver.go index b928be1b52481..1734ea53f0782 100644 --- a/coderd/provisionerdserver/provisionerdserver.go +++ b/coderd/provisionerdserver/provisionerdserver.go @@ -1891,10 +1891,12 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid. appSlugs = make(map[string]struct{}) ) for _, prAgent := range protoResource.Agents { - if _, ok := agentNames[prAgent.Name]; ok { + // Agent names must be case-insensitive-unique, to be unambiguous in + // `coder_app`s and CoderVPN DNS names. + if _, ok := agentNames[strings.ToLower(prAgent.Name)]; ok { return xerrors.Errorf("duplicate agent name %q", prAgent.Name) } - agentNames[prAgent.Name] = struct{}{} + agentNames[strings.ToLower(prAgent.Name)] = struct{}{} var instanceID sql.NullString if prAgent.GetInstanceId() != "" { diff --git a/coderd/provisionerdserver/provisionerdserver_test.go b/coderd/provisionerdserver/provisionerdserver_test.go index 21ba8c6fad358..9f18260745d5a 100644 --- a/coderd/provisionerdserver/provisionerdserver_test.go +++ b/coderd/provisionerdserver/provisionerdserver_test.go @@ -1905,6 +1905,32 @@ func TestInsertWorkspaceResource(t *testing.T) { }) require.ErrorContains(t, err, "duplicate app slug") }) + t.Run("DuplicateAgentNames", func(t *testing.T) { + t.Parallel() + db := dbmem.New() + job := uuid.New() + // case-insensitive-unique + err := insert(db, job, &sdkproto.Resource{ + Name: "something", + Type: "aws_instance", + Agents: []*sdkproto.Agent{{ + Name: "dev", + }, { + Name: "Dev", + }}, + }) + require.ErrorContains(t, err, "duplicate agent name") + err = insert(db, job, &sdkproto.Resource{ + Name: "something", + Type: "aws_instance", + Agents: []*sdkproto.Agent{{ + Name: "dev", + }, { + Name: "dev", + }}, + }) + require.ErrorContains(t, err, "duplicate agent name") + }) t.Run("Success", func(t *testing.T) { t.Parallel() db := dbmem.New() diff --git a/provisioner/terraform/resources.go b/provisioner/terraform/resources.go index 77c92da87b066..65a0a1a988752 100644 --- a/provisioner/terraform/resources.go +++ b/provisioner/terraform/resources.go @@ -215,10 +215,12 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s return nil, xerrors.Errorf("decode agent attributes: %w", err) } - if _, ok := agentNames[tfResource.Name]; ok { + // Agent names must be case-insensitive-unique, to be unambiguous in + // `coder_app`s and CoderVPN DNS names. + if _, ok := agentNames[strings.ToLower(tfResource.Name)]; ok { return nil, xerrors.Errorf("duplicate agent name: %s", tfResource.Name) } - agentNames[tfResource.Name] = struct{}{} + agentNames[strings.ToLower(tfResource.Name)] = struct{}{} // Handling for deprecated attributes. login_before_ready was replaced // by startup_script_behavior, but we still need to support it for diff --git a/provisioner/terraform/resources_test.go b/provisioner/terraform/resources_test.go index 1f1a03dfae212..7527ba6dacd5a 100644 --- a/provisioner/terraform/resources_test.go +++ b/provisioner/terraform/resources_test.go @@ -1026,6 +1026,39 @@ func TestAppSlugValidation(t *testing.T) { require.ErrorContains(t, err, "duplicate app slug") } +func TestAgentNameDuplicate(t *testing.T) { + t.Parallel() + ctx, logger := ctxAndLogger(t) + + // nolint:dogsled + _, filename, _, _ := runtime.Caller(0) + + dir := filepath.Join(filepath.Dir(filename), "testdata", "multiple-agents") + tfPlanRaw, err := os.ReadFile(filepath.Join(dir, "multiple-agents.tfplan.json")) + require.NoError(t, err) + var tfPlan tfjson.Plan + err = json.Unmarshal(tfPlanRaw, &tfPlan) + require.NoError(t, err) + tfPlanGraph, err := os.ReadFile(filepath.Join(dir, "multiple-agents.tfplan.dot")) + require.NoError(t, err) + + for _, resource := range tfPlan.PlannedValues.RootModule.Resources { + if resource.Type == "coder_agent" { + switch resource.Name { + case "dev1": + resource.Name = "dev" + case "dev2": + resource.Name = "Dev" + } + } + } + + state, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger) + require.Nil(t, state) + require.Error(t, err) + require.ErrorContains(t, err, "duplicate agent name") +} + func TestMetadataResourceDuplicate(t *testing.T) { t.Parallel() ctx, logger := ctxAndLogger(t) 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