fix!: enforce regex for agent names (#16641)

Underscores and double hyphens are now blocked. The regex is almost the
exact same as the `coder_app` `slug` regex, but uppercase characters are
still permitted.
This commit is contained in:
Dean Sheather
2025-02-20 16:09:26 +11:00
committed by GitHub
parent 92870f0642
commit 9469b78290
17 changed files with 368 additions and 104 deletions

View File

@ -984,6 +984,7 @@ func TestInvalidTerraformAddress(t *testing.T) {
require.Equal(t, state.Resources[0].ModulePath, "invalid terraform address")
}
//nolint:tparallel
func TestAppSlugValidation(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
@ -1001,31 +1002,116 @@ func TestAppSlugValidation(t *testing.T) {
tfPlanGraph, err := os.ReadFile(filepath.Join(dir, "multiple-apps.tfplan.dot"))
require.NoError(t, err)
// Change all slugs to be invalid.
cases := []struct {
slug string
errContains string
}{
{slug: "$$$ invalid slug $$$", errContains: "does not match regex"},
{slug: "invalid--slug", errContains: "does not match regex"},
{slug: "invalid_slug", errContains: "does not match regex"},
{slug: "Invalid-slug", errContains: "does not match regex"},
{slug: "valid", errContains: ""},
}
//nolint:paralleltest
for i, c := range cases {
c := c
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
// Change the first app slug to match the current case.
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
if resource.Type == "coder_app" {
resource.AttributeValues["slug"] = c.slug
break
}
}
_, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
if c.errContains != "" {
require.ErrorContains(t, err, c.errContains)
} else {
require.NoError(t, err)
}
})
}
}
func TestAppSlugDuplicate(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)
// nolint:dogsled
_, filename, _, _ := runtime.Caller(0)
dir := filepath.Join(filepath.Dir(filename), "testdata", "multiple-apps")
tfPlanRaw, err := os.ReadFile(filepath.Join(dir, "multiple-apps.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-apps.tfplan.dot"))
require.NoError(t, err)
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
if resource.Type == "coder_app" {
resource.AttributeValues["slug"] = "$$$ invalid slug $$$"
resource.AttributeValues["slug"] = "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, "invalid app slug")
// Change all slugs to be identical and valid.
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
if resource.Type == "coder_app" {
resource.AttributeValues["slug"] = "valid"
}
}
state, err = terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
require.Nil(t, state)
_, err = terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
require.Error(t, err)
require.ErrorContains(t, err, "duplicate app slug")
}
//nolint:tparallel
func TestAgentNameInvalid(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)
cases := []struct {
name string
errContains string
}{
{name: "bad--name", errContains: "does not match regex"},
{name: "bad_name", errContains: "contains underscores"}, // custom error for underscores
{name: "valid-name-123", errContains: ""},
{name: "valid", errContains: ""},
{name: "UppercaseValid", errContains: ""},
}
//nolint:paralleltest
for i, c := range cases {
c := c
t.Run(fmt.Sprintf("case-%d", i), func(t *testing.T) {
// Change the first agent name to match the current case.
for _, resource := range tfPlan.PlannedValues.RootModule.Resources {
if resource.Type == "coder_agent" {
resource.Name = c.name
break
}
}
_, err := terraform.ConvertState(ctx, []*tfjson.StateModule{tfPlan.PlannedValues.RootModule}, string(tfPlanGraph), logger)
if c.errContains != "" {
require.ErrorContains(t, err, c.errContains)
} else {
require.NoError(t, err)
}
})
}
}
func TestAgentNameDuplicate(t *testing.T) {
t.Parallel()
ctx, logger := ctxAndLogger(t)