From 4b8a4b1c3f06618c87a590424d7827394a3299d9 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 7 Nov 2024 11:03:12 -0700 Subject: [PATCH 01/14] chore: replace `PtrTo` with `ptr.Ref` (#132) --- internal/provider/group_data_source_test.go | 11 +- internal/provider/group_resource_test.go | 27 ++-- .../provider/organization_data_source_test.go | 11 +- .../provider/template_data_source_test.go | 29 ++--- internal/provider/template_resource.go | 3 +- internal/provider/template_resource_test.go | 115 +++++++++--------- internal/provider/user_data_source_test.go | 5 +- internal/provider/user_resource_test.go | 19 +-- internal/provider/util.go | 4 - .../provider/workspace_proxy_resource_test.go | 17 +-- 10 files changed, 123 insertions(+), 118 deletions(-) diff --git a/internal/provider/group_data_source_test.go b/internal/provider/group_data_source_test.go index 349e855..5a081af 100644 --- a/internal/provider/group_data_source_test.go +++ b/internal/provider/group_data_source_test.go @@ -8,6 +8,7 @@ import ( "testing" "text/template" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -74,7 +75,7 @@ func TestAccGroupDataSource(t *testing.T) { cfg := testAccGroupDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - ID: PtrTo(group.ID.String()), + ID: ptr.Ref(group.ID.String()), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -92,8 +93,8 @@ func TestAccGroupDataSource(t *testing.T) { cfg := testAccGroupDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - OrganizationID: PtrTo(firstUser.OrganizationIDs[0].String()), - Name: PtrTo("example-group"), + OrganizationID: ptr.Ref(firstUser.OrganizationIDs[0].String()), + Name: ptr.Ref("example-group"), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -111,7 +112,7 @@ func TestAccGroupDataSource(t *testing.T) { cfg := testAccGroupDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-group"), + Name: ptr.Ref("example-group"), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -129,7 +130,7 @@ func TestAccGroupDataSource(t *testing.T) { cfg := testAccGroupDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - OrganizationID: PtrTo(firstUser.OrganizationIDs[0].String()), + OrganizationID: ptr.Ref(firstUser.OrganizationIDs[0].String()), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/internal/provider/group_resource_test.go b/internal/provider/group_resource_test.go index 159856f..865851a 100644 --- a/internal/provider/group_resource_test.go +++ b/internal/provider/group_resource_test.go @@ -8,6 +8,7 @@ import ( "testing" "text/template" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -44,17 +45,17 @@ func TestAccGroupResource(t *testing.T) { cfg1 := testAccGroupResourceconfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-group"), - DisplayName: PtrTo("Example Group"), - AvatarUrl: PtrTo("https://google.com"), - QuotaAllowance: PtrTo(int32(100)), - Members: PtrTo([]string{user1.ID.String()}), + Name: ptr.Ref("example-group"), + DisplayName: ptr.Ref("Example Group"), + AvatarUrl: ptr.Ref("https://google.com"), + QuotaAllowance: ptr.Ref(int32(100)), + Members: ptr.Ref([]string{user1.ID.String()}), } cfg2 := cfg1 - cfg2.Name = PtrTo("example-group-new") - cfg2.DisplayName = PtrTo("Example Group New") - cfg2.Members = PtrTo([]string{user2.ID.String()}) + cfg2.Name = ptr.Ref("example-group-new") + cfg2.DisplayName = ptr.Ref("Example Group New") + cfg2.Members = ptr.Ref([]string{user2.ID.String()}) cfg3 := cfg2 cfg3.Members = nil @@ -143,11 +144,11 @@ func TestAccGroupResourceAGPL(t *testing.T) { cfg1 := testAccGroupResourceconfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-group"), - DisplayName: PtrTo("Example Group"), - AvatarUrl: PtrTo("https://google.com"), - QuotaAllowance: PtrTo(int32(100)), - Members: PtrTo([]string{firstUser.ID.String()}), + Name: ptr.Ref("example-group"), + DisplayName: ptr.Ref("Example Group"), + AvatarUrl: ptr.Ref("https://google.com"), + QuotaAllowance: ptr.Ref(int32(100)), + Members: ptr.Ref([]string{firstUser.ID.String()}), } resource.Test(t, resource.TestCase{ diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go index 7f9bef3..c7bb982 100644 --- a/internal/provider/organization_data_source_test.go +++ b/internal/provider/organization_data_source_test.go @@ -8,6 +8,7 @@ import ( "testing" "text/template" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -37,7 +38,7 @@ func TestAccOrganizationDataSource(t *testing.T) { cfg := testAccOrganizationDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - ID: PtrTo(firstUser.OrganizationIDs[0].String()), + ID: ptr.Ref(firstUser.OrganizationIDs[0].String()), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -55,7 +56,7 @@ func TestAccOrganizationDataSource(t *testing.T) { cfg := testAccOrganizationDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("coder"), + Name: ptr.Ref("coder"), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -73,7 +74,7 @@ func TestAccOrganizationDataSource(t *testing.T) { cfg := testAccOrganizationDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - IsDefault: PtrTo(true), + IsDefault: ptr.Ref(true), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -91,8 +92,8 @@ func TestAccOrganizationDataSource(t *testing.T) { cfg := testAccOrganizationDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - IsDefault: PtrTo(true), - Name: PtrTo("coder"), + IsDefault: ptr.Ref(true), + Name: ptr.Ref("coder"), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/internal/provider/template_data_source_test.go b/internal/provider/template_data_source_test.go index cebdba7..f6759b3 100644 --- a/internal/provider/template_data_source_test.go +++ b/internal/provider/template_data_source_test.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/require" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" ) @@ -49,8 +50,8 @@ func TestAccTemplateDataSource(t *testing.T) { Description: "An example template", Icon: "/path/to/icon.png", VersionID: version.ID, - DefaultTTLMillis: PtrTo((10 * time.Hour).Milliseconds()), - ActivityBumpMillis: PtrTo((4 * time.Hour).Milliseconds()), + DefaultTTLMillis: ptr.Ref((10 * time.Hour).Milliseconds()), + ActivityBumpMillis: ptr.Ref((4 * time.Hour).Milliseconds()), AutostopRequirement: &codersdk.TemplateAutostopRequirement{ DaysOfWeek: []string{"sunday"}, Weeks: 1, @@ -58,12 +59,12 @@ func TestAccTemplateDataSource(t *testing.T) { AutostartRequirement: &codersdk.TemplateAutostartRequirement{ DaysOfWeek: []string{"monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"}, }, - AllowUserCancelWorkspaceJobs: PtrTo(true), - AllowUserAutostart: PtrTo(true), - AllowUserAutostop: PtrTo(true), - FailureTTLMillis: PtrTo((1 * time.Hour).Milliseconds()), - TimeTilDormantMillis: PtrTo((7 * 24 * time.Hour).Milliseconds()), - TimeTilDormantAutoDeleteMillis: PtrTo((30 * 24 * time.Hour).Milliseconds()), + AllowUserCancelWorkspaceJobs: ptr.Ref(true), + AllowUserAutostart: ptr.Ref(true), + AllowUserAutostop: ptr.Ref(true), + FailureTTLMillis: ptr.Ref((1 * time.Hour).Milliseconds()), + TimeTilDormantMillis: ptr.Ref((7 * 24 * time.Hour).Milliseconds()), + TimeTilDormantAutoDeleteMillis: ptr.Ref((30 * 24 * time.Hour).Milliseconds()), DisableEveryoneGroupAccess: true, RequireActiveVersion: true, }) @@ -93,9 +94,9 @@ func TestAccTemplateDataSource(t *testing.T) { UpdateWorkspaceLastUsedAt: false, UpdateWorkspaceDormantAt: false, RequireActiveVersion: tpl.RequireActiveVersion, - DeprecationMessage: PtrTo("This template is deprecated"), + DeprecationMessage: ptr.Ref("This template is deprecated"), DisableEveryoneGroupAccess: true, - MaxPortShareLevel: PtrTo(codersdk.WorkspaceAgentPortShareLevelOwner), + MaxPortShareLevel: ptr.Ref(codersdk.WorkspaceAgentPortShareLevelOwner), }) require.NoError(t, err) @@ -153,8 +154,8 @@ func TestAccTemplateDataSource(t *testing.T) { cfg := testAccTemplateDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - OrganizationID: PtrTo(orgID.String()), - Name: PtrTo(tpl.Name), + OrganizationID: ptr.Ref(orgID.String()), + Name: ptr.Ref(tpl.Name), } resource.Test(t, resource.TestCase{ IsUnitTest: true, @@ -173,7 +174,7 @@ func TestAccTemplateDataSource(t *testing.T) { cfg := testAccTemplateDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - ID: PtrTo(tpl.ID.String()), + ID: ptr.Ref(tpl.ID.String()), } resource.Test(t, resource.TestCase{ IsUnitTest: true, @@ -210,7 +211,7 @@ func TestAccTemplateDataSource(t *testing.T) { cfg := testAccTemplateDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo(tpl.Name), + Name: ptr.Ref(tpl.Name), } resource.Test(t, resource.TestCase{ IsUnitTest: true, diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go index f1d7adc..1d1edf2 100644 --- a/internal/provider/template_resource.go +++ b/internal/provider/template_resource.go @@ -10,6 +10,7 @@ import ( "strings" "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisionersdk" "github.com/google/uuid" @@ -1253,7 +1254,7 @@ func (r *TemplateResourceModel) toUpdateRequest(ctx context.Context, diag *diag. TimeTilDormantAutoDeleteMillis: r.TimeTilDormantAutoDeleteMillis.ValueInt64(), RequireActiveVersion: r.RequireActiveVersion.ValueBool(), DeprecationMessage: r.DeprecationMessage.ValueStringPointer(), - MaxPortShareLevel: PtrTo(codersdk.WorkspaceAgentPortShareLevel(r.MaxPortShareLevel.ValueString())), + MaxPortShareLevel: ptr.Ref(codersdk.WorkspaceAgentPortShareLevel(r.MaxPortShareLevel.ValueString())), // If we're managing ACL, we want to delete the everyone group DisableEveryoneGroupAccess: !r.ACL.IsNull(), } diff --git a/internal/provider/template_resource_test.go b/internal/provider/template_resource_test.go index acb07f2..b9d7ae3 100644 --- a/internal/provider/template_resource_test.go +++ b/internal/provider/template_resource_test.go @@ -17,6 +17,7 @@ import ( cp "github.com/otiai10/copy" "github.com/stretchr/testify/require" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" ) @@ -42,12 +43,12 @@ func TestAccTemplateResource(t *testing.T) { cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-template"), + Name: ptr.Ref("example-template"), Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name Directory: &exTemplateOne, - Active: PtrTo(true), + Active: ptr.Ref(true), }, }, ACL: testAccTemplateACLConfig{ @@ -57,28 +58,28 @@ func TestAccTemplateResource(t *testing.T) { cfg2 := cfg1 cfg2.Versions = slices.Clone(cfg2.Versions) - cfg2.Name = PtrTo("example-template-new") + cfg2.Name = ptr.Ref("example-template-new") cfg2.Versions[0].Directory = &exTemplateTwo - cfg2.Versions[0].Name = PtrTo("new") + cfg2.Versions[0].Name = ptr.Ref("new") cfg3 := cfg2 cfg3.Versions = slices.Clone(cfg3.Versions) cfg3.Versions = append(cfg3.Versions, testAccTemplateVersionConfig{ - Name: PtrTo("legacy-template"), + Name: ptr.Ref("legacy-template"), Directory: &exTemplateOne, - Active: PtrTo(false), + Active: ptr.Ref(false), TerraformVariables: []testAccTemplateKeyValueConfig{ { - Key: PtrTo("name"), - Value: PtrTo("world"), + Key: ptr.Ref("name"), + Value: ptr.Ref("world"), }, }, }) cfg4 := cfg3 cfg4.Versions = slices.Clone(cfg4.Versions) - cfg4.Versions[0].Active = PtrTo(false) - cfg4.Versions[1].Active = PtrTo(true) + cfg4.Versions[0].Active = ptr.Ref(false) + cfg4.Versions[1].Active = ptr.Ref(true) cfg5 := cfg4 cfg5.Versions = slices.Clone(cfg5.Versions) @@ -240,26 +241,26 @@ func TestAccTemplateResource(t *testing.T) { cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-template2"), + Name: ptr.Ref("example-template2"), Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: PtrTo("../../integration/template-test/example-template-2/"), + Directory: ptr.Ref("../../integration/template-test/example-template-2/"), TerraformVariables: []testAccTemplateKeyValueConfig{ { - Key: PtrTo("name"), - Value: PtrTo("world"), + Key: ptr.Ref("name"), + Value: ptr.Ref("world"), }, }, - Active: PtrTo(true), + Active: ptr.Ref(true), }, { // Auto-generated version name - Directory: PtrTo("../../integration/template-test/example-template-2/"), + Directory: ptr.Ref("../../integration/template-test/example-template-2/"), TerraformVariables: []testAccTemplateKeyValueConfig{ { - Key: PtrTo("name"), - Value: PtrTo("world"), + Key: ptr.Ref("name"), + Value: ptr.Ref("world"), }, }, }, @@ -271,28 +272,28 @@ func TestAccTemplateResource(t *testing.T) { cfg2 := cfg1 cfg2.Versions = slices.Clone(cfg2.Versions) - cfg2.Versions[1].Name = PtrTo("new-name") + cfg2.Versions[1].Name = ptr.Ref("new-name") cfg3 := cfg2 cfg3.Versions = slices.Clone(cfg3.Versions) - cfg3.Versions[0].Name = PtrTo("new-name-one") - cfg3.Versions[1].Name = PtrTo("new-name-two") + cfg3.Versions[0].Name = ptr.Ref("new-name-one") + cfg3.Versions[1].Name = ptr.Ref("new-name-two") cfg3.Versions[0], cfg3.Versions[1] = cfg3.Versions[1], cfg3.Versions[0] cfg4 := cfg1 cfg4.Versions = slices.Clone(cfg4.Versions) - cfg4.Versions[0].Directory = PtrTo("../../integration/template-test/example-template/") + cfg4.Versions[0].Directory = ptr.Ref("../../integration/template-test/example-template/") cfg5 := cfg4 cfg5.Versions = slices.Clone(cfg5.Versions) - cfg5.Versions[1].Directory = PtrTo("../../integration/template-test/example-template/") + cfg5.Versions[1].Directory = ptr.Ref("../../integration/template-test/example-template/") cfg6 := cfg5 cfg6.Versions = slices.Clone(cfg6.Versions) cfg6.Versions[0].TerraformVariables = []testAccTemplateKeyValueConfig{ { - Key: PtrTo("name"), - Value: PtrTo("world2"), + Key: ptr.Ref("name"), + Value: ptr.Ref("world2"), }, } @@ -368,18 +369,18 @@ func TestAccTemplateResource(t *testing.T) { cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-template3"), + Name: ptr.Ref("example-template3"), Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: PtrTo("../../integration/template-test/example-template-2/"), + Directory: ptr.Ref("../../integration/template-test/example-template-2/"), TerraformVariables: []testAccTemplateKeyValueConfig{ { - Key: PtrTo("name"), - Value: PtrTo("world"), + Key: ptr.Ref("name"), + Value: ptr.Ref("world"), }, }, - Active: PtrTo(true), + Active: ptr.Ref(true), }, }, ACL: testAccTemplateACLConfig{ @@ -391,8 +392,8 @@ func TestAccTemplateResource(t *testing.T) { cfg2.Versions = slices.Clone(cfg2.Versions) cfg2.Versions[0].TerraformVariables = []testAccTemplateKeyValueConfig{ { - Key: PtrTo("name"), - Value: PtrTo("world2"), + Key: ptr.Ref("name"), + Value: ptr.Ref("world2"), }, } @@ -436,29 +437,29 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-template"), + Name: ptr.Ref("example-template"), Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: PtrTo("../../integration/template-test/example-template"), - Active: PtrTo(true), + Directory: ptr.Ref("../../integration/template-test/example-template"), + Active: ptr.Ref(true), }, }, ACL: testAccTemplateACLConfig{ GroupACL: []testAccTemplateKeyValueConfig{ { - Key: PtrTo(firstUser.OrganizationIDs[0].String()), - Value: PtrTo("use"), + Key: ptr.Ref(firstUser.OrganizationIDs[0].String()), + Value: ptr.Ref("use"), }, { - Key: PtrTo(group.ID.String()), - Value: PtrTo("admin"), + Key: ptr.Ref(group.ID.String()), + Value: ptr.Ref("admin"), }, }, UserACL: []testAccTemplateKeyValueConfig{ { - Key: PtrTo(firstUser.ID.String()), - Value: PtrTo("admin"), + Key: ptr.Ref(firstUser.ID.String()), + Value: ptr.Ref("admin"), }, }, }, @@ -466,17 +467,17 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { cfg2 := cfg1 cfg2.ACL.GroupACL = slices.Clone(cfg2.ACL.GroupACL[1:]) - cfg2.MaxPortShareLevel = PtrTo("owner") + cfg2.MaxPortShareLevel = ptr.Ref("owner") cfg3 := cfg2 cfg3.ACL.null = true - cfg3.MaxPortShareLevel = PtrTo("public") + cfg3.MaxPortShareLevel = ptr.Ref("public") cfg4 := cfg3 - cfg4.AllowUserAutostart = PtrTo(false) + cfg4.AllowUserAutostart = ptr.Ref(false) cfg4.AutostopRequirement = testAccAutostopRequirementConfig{ - DaysOfWeek: PtrTo([]string{"monday", "tuesday"}), - Weeks: PtrTo(int64(2)), + DaysOfWeek: ptr.Ref([]string{"monday", "tuesday"}), + Weeks: ptr.Ref(int64(2)), } resource.Test(t, resource.TestCase{ @@ -571,47 +572,47 @@ func TestAccTemplateResourceAGPL(t *testing.T) { cfg1 := testAccTemplateResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example-template"), + Name: ptr.Ref("example-template"), Versions: []testAccTemplateVersionConfig{ { // Auto-generated version name - Directory: PtrTo("../../integration/template-test/example-template/"), - Active: PtrTo(true), + Directory: ptr.Ref("../../integration/template-test/example-template/"), + Active: ptr.Ref(true), }, }, - AllowUserAutostart: PtrTo(false), + AllowUserAutostart: ptr.Ref(false), } cfg2 := cfg1 cfg2.AllowUserAutostart = nil - cfg2.AutostopRequirement.DaysOfWeek = PtrTo([]string{"monday", "tuesday"}) + cfg2.AutostopRequirement.DaysOfWeek = ptr.Ref([]string{"monday", "tuesday"}) cfg3 := cfg2 cfg3.AutostopRequirement.null = true - cfg3.AutostartRequirement = PtrTo([]string{}) + cfg3.AutostartRequirement = ptr.Ref([]string{}) cfg4 := cfg3 - cfg4.FailureTTL = PtrTo(int64(1)) + cfg4.FailureTTL = ptr.Ref(int64(1)) cfg5 := cfg4 cfg5.FailureTTL = nil cfg5.AutostartRequirement = nil - cfg5.RequireActiveVersion = PtrTo(true) + cfg5.RequireActiveVersion = ptr.Ref(true) cfg6 := cfg5 cfg6.RequireActiveVersion = nil cfg6.ACL = testAccTemplateACLConfig{ GroupACL: []testAccTemplateKeyValueConfig{ { - Key: PtrTo(firstUser.OrganizationIDs[0].String()), - Value: PtrTo("use"), + Key: ptr.Ref(firstUser.OrganizationIDs[0].String()), + Value: ptr.Ref("use"), }, }, } cfg7 := cfg6 cfg7.ACL.null = true - cfg7.MaxPortShareLevel = PtrTo("owner") + cfg7.MaxPortShareLevel = ptr.Ref("owner") for _, cfg := range []testAccTemplateResourceConfig{cfg1, cfg2, cfg3, cfg4} { resource.Test(t, resource.TestCase{ diff --git a/internal/provider/user_data_source_test.go b/internal/provider/user_data_source_test.go index 2d69d13..ebdab83 100644 --- a/internal/provider/user_data_source_test.go +++ b/internal/provider/user_data_source_test.go @@ -8,6 +8,7 @@ import ( "testing" "text/template" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -53,7 +54,7 @@ func TestAccUserDataSource(t *testing.T) { cfg := testAccUserDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Username: PtrTo(user.Username), + Username: ptr.Ref(user.Username), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -71,7 +72,7 @@ func TestAccUserDataSource(t *testing.T) { cfg := testAccUserDataSourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - ID: PtrTo(user.ID.String()), + ID: ptr.Ref(user.ID.String()), } resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/internal/provider/user_resource_test.go b/internal/provider/user_resource_test.go index a7bb470..b9c6bb7 100644 --- a/internal/provider/user_resource_test.go +++ b/internal/provider/user_resource_test.go @@ -7,6 +7,7 @@ import ( "testing" "text/template" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/require" @@ -22,22 +23,22 @@ func TestAccUserResource(t *testing.T) { cfg1 := testAccUserResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Username: PtrTo("example"), - Name: PtrTo("Example User"), - Email: PtrTo("example@coder.com"), - Roles: PtrTo([]string{"owner", "auditor"}), - LoginType: PtrTo("password"), - Password: PtrTo("SomeSecurePassword!"), + Username: ptr.Ref("example"), + Name: ptr.Ref("Example User"), + Email: ptr.Ref("example@coder.com"), + Roles: ptr.Ref([]string{"owner", "auditor"}), + LoginType: ptr.Ref("password"), + Password: ptr.Ref("SomeSecurePassword!"), } cfg2 := cfg1 - cfg2.Username = PtrTo("exampleNew") + cfg2.Username = ptr.Ref("exampleNew") cfg3 := cfg2 - cfg3.Name = PtrTo("Example New") + cfg3.Name = ptr.Ref("Example New") cfg4 := cfg3 - cfg4.LoginType = PtrTo("github") + cfg4.LoginType = ptr.Ref("github") cfg4.Password = nil resource.Test(t, resource.TestCase{ diff --git a/internal/provider/util.go b/internal/provider/util.go index 12be3f3..2d998cc 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -20,10 +20,6 @@ var ( displayNameRegex = regexp.MustCompile(`^[^\s](.*[^\s])?$`) ) -func PtrTo[T any](v T) *T { - return &v -} - func PrintOrNull(v any) string { if v == nil { return "null" diff --git a/internal/provider/workspace_proxy_resource_test.go b/internal/provider/workspace_proxy_resource_test.go index a2447ea..88d7013 100644 --- a/internal/provider/workspace_proxy_resource_test.go +++ b/internal/provider/workspace_proxy_resource_test.go @@ -8,6 +8,7 @@ import ( "testing" "text/template" + "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/terraform-provider-coderd/integration" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/require" @@ -23,14 +24,14 @@ func TestAccWorkspaceProxyResource(t *testing.T) { cfg1 := testAccWorkspaceProxyResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example"), - DisplayName: PtrTo("Example WS Proxy"), - Icon: PtrTo("/emojis/1f407.png"), + Name: ptr.Ref("example"), + DisplayName: ptr.Ref("Example WS Proxy"), + Icon: ptr.Ref("/emojis/1f407.png"), } cfg2 := cfg1 - cfg2.Name = PtrTo("example-new") - cfg2.DisplayName = PtrTo("Example WS Proxy New") + cfg2.Name = ptr.Ref("example-new") + cfg2.DisplayName = ptr.Ref("Example WS Proxy New") resource.Test(t, resource.TestCase{ IsUnitTest: true, @@ -64,9 +65,9 @@ func TestAccWorkspaceProxyResourceAGPL(t *testing.T) { cfg1 := testAccWorkspaceProxyResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), - Name: PtrTo("example"), - DisplayName: PtrTo("Example WS Proxy"), - Icon: PtrTo("/emojis/1f407.png"), + Name: ptr.Ref("example"), + DisplayName: ptr.Ref("Example WS Proxy"), + Icon: ptr.Ref("/emojis/1f407.png"), } resource.Test(t, resource.TestCase{ From d0f5e2114ef8a73881ebe3e687b64504beec39d4 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 7 Nov 2024 11:34:10 -0700 Subject: [PATCH 02/14] fix: use `codersdk` functions for validating name attributes (#130) --- .golangci.yml | 6 +-- internal/codersdkvalidator/display_name.go | 10 ++++ internal/codersdkvalidator/group_name.go | 10 ++++ internal/codersdkvalidator/name.go | 10 ++++ .../template_version_name.go | 10 ++++ internal/codersdkvalidator/user_real_name.go | 10 ++++ .../codersdkvalidator/validator_from_func.go | 51 +++++++++++++++++++ internal/provider/group_resource.go | 8 ++- internal/provider/template_resource.go | 10 ++-- internal/provider/user_resource.go | 6 +-- internal/provider/util.go | 7 --- 11 files changed, 114 insertions(+), 24 deletions(-) create mode 100644 internal/codersdkvalidator/display_name.go create mode 100644 internal/codersdkvalidator/group_name.go create mode 100644 internal/codersdkvalidator/name.go create mode 100644 internal/codersdkvalidator/template_version_name.go create mode 100644 internal/codersdkvalidator/user_real_name.go create mode 100644 internal/codersdkvalidator/validator_from_func.go diff --git a/.golangci.yml b/.golangci.yml index 223cf95..679a35a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,5 @@ -# Visit https://golangci-lint.run/ for usage documentation -# and information on other useful linters +# Visit https://golangci-lint.run/ for usage documentation and information on +# other useful linters issues: max-per-linter: 0 max-same-issues: 0 @@ -24,4 +24,4 @@ linters: - unconvert - unparam - unused - - vet \ No newline at end of file + - vet diff --git a/internal/codersdkvalidator/display_name.go b/internal/codersdkvalidator/display_name.go new file mode 100644 index 0000000..1000c32 --- /dev/null +++ b/internal/codersdkvalidator/display_name.go @@ -0,0 +1,10 @@ +package codersdkvalidator + +import ( + "github.com/coder/coder/v2/codersdk" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func DisplayName() validator.String { + return validatorFromFunc(codersdk.DisplayNameValid, "value must be a valid display name") +} diff --git a/internal/codersdkvalidator/group_name.go b/internal/codersdkvalidator/group_name.go new file mode 100644 index 0000000..20313db --- /dev/null +++ b/internal/codersdkvalidator/group_name.go @@ -0,0 +1,10 @@ +package codersdkvalidator + +import ( + "github.com/coder/coder/v2/codersdk" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func GroupName() validator.String { + return validatorFromFunc(codersdk.GroupNameValid, "value must be a valid group name") +} diff --git a/internal/codersdkvalidator/name.go b/internal/codersdkvalidator/name.go new file mode 100644 index 0000000..14adb25 --- /dev/null +++ b/internal/codersdkvalidator/name.go @@ -0,0 +1,10 @@ +package codersdkvalidator + +import ( + "github.com/coder/coder/v2/codersdk" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func Name() validator.String { + return validatorFromFunc(codersdk.NameValid, "value must be a valid name") +} diff --git a/internal/codersdkvalidator/template_version_name.go b/internal/codersdkvalidator/template_version_name.go new file mode 100644 index 0000000..32c69d6 --- /dev/null +++ b/internal/codersdkvalidator/template_version_name.go @@ -0,0 +1,10 @@ +package codersdkvalidator + +import ( + "github.com/coder/coder/v2/codersdk" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func TemplateVersionName() validator.String { + return validatorFromFunc(codersdk.TemplateVersionNameValid, "value must be a valid template version name") +} diff --git a/internal/codersdkvalidator/user_real_name.go b/internal/codersdkvalidator/user_real_name.go new file mode 100644 index 0000000..5bf9686 --- /dev/null +++ b/internal/codersdkvalidator/user_real_name.go @@ -0,0 +1,10 @@ +package codersdkvalidator + +import ( + "github.com/coder/coder/v2/codersdk" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func UserRealName() validator.String { + return validatorFromFunc(codersdk.UserRealNameValid, "value must be a valid name for a user") +} diff --git a/internal/codersdkvalidator/validator_from_func.go b/internal/codersdkvalidator/validator_from_func.go new file mode 100644 index 0000000..9d5e631 --- /dev/null +++ b/internal/codersdkvalidator/validator_from_func.go @@ -0,0 +1,51 @@ +package codersdkvalidator + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +type functionValidator struct { + check func(string) error + defaultMessage string + err error +} + +func validatorFromFunc(check func(string) error, defaultMessage string) functionValidator { + return functionValidator{ + check: check, + defaultMessage: defaultMessage, + } +} + +var _ validator.String = functionValidator{} + +func (v functionValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + name := req.ConfigValue.ValueString() + if v.err = v.check(name); v.err != nil { + resp.Diagnostics.Append(validatordiag.InvalidAttributeValueDiagnostic( + req.Path, + v.Description(ctx), + name, + )) + } +} + +var _ validator.Describer = functionValidator{} + +func (v functionValidator) Description(_ context.Context) string { + if v.err != nil { + return v.err.Error() + } + return v.defaultMessage +} + +func (v functionValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} diff --git a/internal/provider/group_resource.go b/internal/provider/group_resource.go index a7dcd6a..fa370a0 100644 --- a/internal/provider/group_resource.go +++ b/internal/provider/group_resource.go @@ -6,8 +6,8 @@ import ( "strings" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" "github.com/google/uuid" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" @@ -77,8 +77,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest, MarkdownDescription: "The unique name of the group.", Required: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 36), - stringvalidator.RegexMatches(nameValidRegex, "Group names must be alpahnumeric with hyphens."), + codersdkvalidator.GroupName(), }, }, "display_name": schema.StringAttribute{ @@ -86,8 +85,7 @@ func (r *GroupResource) Schema(ctx context.Context, req resource.SchemaRequest, Computed: true, Optional: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 64), - stringvalidator.RegexMatches(displayNameRegex, "Group display names must be alphanumeric with spaces"), + codersdkvalidator.DisplayName(), }, Default: stringdefault.StaticString(""), }, diff --git a/internal/provider/template_resource.go b/internal/provider/template_resource.go index 1d1edf2..a5834db 100644 --- a/internal/provider/template_resource.go +++ b/internal/provider/template_resource.go @@ -13,6 +13,7 @@ import ( "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisionersdk" + "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/setvalidator" @@ -258,8 +259,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques MarkdownDescription: "The name of the template.", Required: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 32), - stringvalidator.RegexMatches(nameValidRegex, "Template names must be alphanumeric with hyphens."), + codersdkvalidator.Name(), }, }, "display_name": schema.StringAttribute{ @@ -267,8 +267,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Optional: true, Computed: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 64), - stringvalidator.RegexMatches(displayNameRegex, "Template display names must be alphanumeric with spaces."), + codersdkvalidator.DisplayName(), }, }, "description": schema.StringAttribute{ @@ -418,8 +417,7 @@ func (r *TemplateResource) Schema(ctx context.Context, req resource.SchemaReques Optional: true, Computed: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 64), - stringvalidator.RegexMatches(templateVersionNameRegex, "Template version names must be alphanumeric with underscores and dots."), + codersdkvalidator.TemplateVersionName(), }, }, "message": schema.StringAttribute{ diff --git a/internal/provider/user_resource.go b/internal/provider/user_resource.go index a560df5..3fa570e 100644 --- a/internal/provider/user_resource.go +++ b/internal/provider/user_resource.go @@ -22,6 +22,7 @@ import ( "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" ) // Ensure provider defined types fully satisfy framework interfaces. @@ -71,8 +72,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r MarkdownDescription: "Username of the user.", Required: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 32), - stringvalidator.RegexMatches(nameValidRegex, "Username must be alphanumeric with hyphens."), + codersdkvalidator.Name(), }, }, "name": schema.StringAttribute{ @@ -80,7 +80,7 @@ func (r *UserResource) Schema(ctx context.Context, req resource.SchemaRequest, r Computed: true, Optional: true, Validators: []validator.String{ - stringvalidator.LengthBetween(1, 128), + codersdkvalidator.UserRealName(), }, }, "email": schema.StringAttribute{ diff --git a/internal/provider/util.go b/internal/provider/util.go index 2d998cc..720259c 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -8,18 +8,11 @@ import ( "net/http" "os" "path/filepath" - "regexp" "github.com/coder/coder/v2/codersdk" "github.com/google/uuid" ) -var ( - nameValidRegex = regexp.MustCompile("^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*$") - templateVersionNameRegex = regexp.MustCompile(`^[a-zA-Z0-9]+(?:[_.-]{1}[a-zA-Z0-9]+)*$`) - displayNameRegex = regexp.MustCompile(`^[^\s](.*[^\s])?$`) -) - func PrintOrNull(v any) string { if v == nil { return "null" From 05655ef9c288dbb36d4caa5c5c02fe31e2d5eeba Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 7 Nov 2024 11:47:55 -0700 Subject: [PATCH 03/14] chore: pin github actions to exact commits (#126) --- .github/workflows/release.yml | 59 +++++++++++++++++------------------ .github/workflows/test.yml | 41 ++++++++++++++---------- 2 files changed, 54 insertions(+), 46 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2755e44..7ffb4e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,37 +1,36 @@ name: release on: - push: - tags: - - 'v*' + push: + tags: + - "v*" permissions: - contents: write + contents: write jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Unshallow - run: git fetch --prune --unshallow - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: 1.22.4 - - name: Import GPG Key - id: import_gpg - uses: crazy-max/ghaction-import-gpg@v6.2.0 - with: - gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6.0.0 - with: - version: latest - args: release --clean - env: - GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} - # GitHub sets this automatically - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - name: Unshallow + run: git fetch --prune --unshallow + - name: Setup Go + uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 + with: + go-version: "1.22" + - name: Import GPG Key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@cb9bde2e2525e640591a934b1fd28eef1dcaf5e5 # v6.2.0 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + with: + version: latest + args: release --clean + env: + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + # GitHub sets this automatically + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b348a86..e8db37f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -24,27 +24,34 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version-file: "go.mod" cache: true + - run: go mod download + - run: go build -v . + - name: Run linters - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@971e284b6050e8a5849b72094c50ab08da042db8 # v6.1.1 with: version: latest generate: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version-file: "go.mod" cache: true + - run: go generate ./... + - name: git diff run: | git diff --compact-summary --exit-code || \ @@ -72,20 +79,24 @@ jobs: - "1.8.*" - "1.9.*" steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version-file: "go.mod" cache: true - - uses: hashicorp/setup-terraform@v3 + + - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: terraform_version: ${{ matrix.terraform }} terraform_wrapper: false + - run: go mod download - - env: + + - run: go test -v -cover ./internal/provider/ + env: TF_ACC: "1" CODER_ENTERPRISE_LICENSE: ${{ secrets.CODER_ENTERPRISE_LICENSE }} - run: go test -v -cover ./internal/provider/ timeout-minutes: 10 lint: @@ -93,20 +104,18 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - name: Set up Go - uses: actions/setup-go@v5 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed # v5.1.0 with: go-version: "1.22" id: go - - uses: hashicorp/setup-terraform@v3 + - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2 with: terraform_version: "1.9.*" terraform_wrapper: false - - name: Check out code into the Go module directory - uses: actions/checkout@v4 - - name: Get dependencies run: | go mod download From f27019dc9a2c52b46d0f774361068b0976bb7cdd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:05:55 +1100 Subject: [PATCH 04/14] chore(deps): bump goreleaser/goreleaser-action from 6.0.0 to 6.1.0 (#135) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 6.0.0 to 6.1.0.
Release notes

Sourced from goreleaser/goreleaser-action's releases.

v6.1.0

What's Changed

New Contributors

Full Changelog: https://github.com/goreleaser/goreleaser-action/compare/v6.0.0...v6.1.0

Commits
  • 9ed2f89 chore: update generated content (#480)
  • cf63508 docs: bump upload-artifact version (#479)
  • f7623f3 chore(deps): bump @​actions/core from 1.10.1 to 1.11.1 (#478)
  • 006a7a4 chore: update
  • e4066e6 chore(deps): bump micromatch from 4.0.5 to 4.0.8 (#475)
  • 22f558e chore(deps): bump @​actions/http-client from 2.2.2 to 2.2.3 (#474)
  • 6e33108 chore(deps): bump @​actions/http-client from 2.2.1 to 2.2.2 (#473)
  • 7ca6450 chore(deps): bump semver from 7.6.2 to 7.6.3 (#470)
  • d33b6f6 chore(deps): bump docker/bake-action from 4 to 5 (#468)
  • 85d0b9d chore(deps): bump braces from 3.0.2 to 3.0.3 (#467)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=goreleaser/goreleaser-action&package-manager=github_actions&previous-version=6.0.0&new-version=6.1.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ffb4e1..4d5635f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: with: gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} - name: Run GoReleaser - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 + uses: goreleaser/goreleaser-action@9ed2f89a662bf1735a48bc8557fd212fa902bebf # v6.1.0 with: version: latest args: release --clean From d3394fa0dd43838f139b79254bd8f2791eba5acb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:06:30 +1100 Subject: [PATCH 05/14] chore(deps): bump github.com/hashicorp/terraform-plugin-docs from 0.19.4 to 0.20.0 (#137) Bumps [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) from 0.19.4 to 0.20.0.
Release notes

Sourced from github.com/hashicorp/terraform-plugin-docs's releases.

v0.20.0

NOTES:

  • all: This Go module has been updated to Go 1.22 per the Go support policy. It is recommended to review the Go 1.22 release notes before upgrading. Any consumers building on earlier Go versions may experience errors. (#400)

FEATURES:

  • generate: Add support for ephemeral resources (#415)
  • migrate: Add support for ephemeral resources (#415)
  • validate: Add support for ephemeral resources (#415)

BUG FIXES:

  • validate: File extension check now runs on index.* files instead of just index.md files. (#413)
  • validate: File extension check now specifies the correct valid extensions in the error message. (#413)
  • validate: Front matter check now runs with the correct options on legacy index files. (#413)
Changelog

Sourced from github.com/hashicorp/terraform-plugin-docs's changelog.

0.20.0 (November 06, 2024)

NOTES:

  • all: This Go module has been updated to Go 1.22 per the Go support policy. It is recommended to review the Go 1.22 release notes before upgrading. Any consumers building on earlier Go versions may experience errors. (#400)

FEATURES:

  • generate: Add support for ephemeral resources (#415)
  • migrate: Add support for ephemeral resources (#415)
  • validate: Add support for ephemeral resources (#415)

BUG FIXES:

  • validate: File extension check now runs on index.* files instead of just index.md files. (#413)
  • validate: File extension check now specifies the correct valid extensions in the error message. (#413)
  • validate: Front matter check now runs with the correct options on legacy index files. (#413)
Commits
  • 439e333 Update changelog
  • 1350b10 Bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 in /tools (#417)
  • 933b4ae Result of tsccr-helper -log-level=info gha update -latest .github/ (#416)
  • 97bd6c7 all: add support for ephemeral resources (#415)
  • 6c67ef2 Validate: refactor internal methods to use fs.FS interface for file handlin...
  • d70aced Bump github.com/hashicorp/terraform-json from 0.22.1 to 0.23.0 (#414)
  • 78dfe49 Result of tsccr-helper -log-level=info gha update -latest . (#412)
  • 75372b2 Bump github.com/yuin/goldmark from 1.7.4 to 1.7.7 (#410)
  • 6e81fd4 Result of tsccr-helper -log-level=info gha update -latest . (#408)
  • 377bb21 Bump github.com/bmatcuk/doublestar/v4 from 4.6.1 to 4.7.1 (#407)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/hashicorp/terraform-plugin-docs&package-manager=go_modules&previous-version=0.19.4&new-version=0.20.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 8 ++++---- go.sum | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/go.mod b/go.mod index c62b2b8..0cb6211 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/docker v27.2.1+incompatible github.com/docker/go-connections v0.5.0 github.com/google/uuid v1.6.0 - github.com/hashicorp/terraform-plugin-docs v0.19.4 + github.com/hashicorp/terraform-plugin-docs v0.20.0 github.com/hashicorp/terraform-plugin-framework v1.13.0 github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 github.com/hashicorp/terraform-plugin-go v0.25.0 @@ -40,7 +40,7 @@ require ( github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.2.0 // indirect - github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect + github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect @@ -83,7 +83,7 @@ require ( github.com/hashicorp/hcl/v2 v2.22.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect @@ -133,7 +133,7 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yuin/goldmark v1.7.4 // indirect + github.com/yuin/goldmark v1.7.7 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect github.com/zeebo/errs v1.3.0 // indirect diff --git a/go.sum b/go.sum index 55c9312..bcd1dd9 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E= github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bool64/shared v0.1.5 h1:fp3eUhBsrSjNCQPcSdQqZxxh9bBwrYiZ+zOKFkM0/2E= github.com/bool64/shared v0.1.5/go.mod h1:081yz68YC9jeFB3+Bbmno2RFWvGKv1lPKkMP6MHJlPs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= @@ -249,10 +249,10 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= -github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= -github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= +github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= +github.com/hashicorp/terraform-plugin-docs v0.20.0 h1:ox7rm1FN0dVZaJBUzkVVh10R1r3+FeMQWL0QopQ9d7o= +github.com/hashicorp/terraform-plugin-docs v0.20.0/go.mod h1:A/+4SVMdAkQYtIBtaxV0H7AU862TxVZk/hhKaMDQB6Y= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= @@ -413,8 +413,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= @@ -489,8 +489,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= -github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= +github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= From 03a98cd8fb0a13fad7b5ec778920003458d0c6b8 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 14 Nov 2024 11:06:01 -0700 Subject: [PATCH 06/14] chore: improve `coderd_license` docs (#140) --- docs/resources/license.md | 14 ++++++++++++-- examples/resources/coderd_license/resource.tf | 7 +++++++ internal/provider/license_resource.go | 9 ++++++--- 3 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 examples/resources/coderd_license/resource.tf diff --git a/docs/resources/license.md b/docs/resources/license.md index 58c773c..ec29706 100644 --- a/docs/resources/license.md +++ b/docs/resources/license.md @@ -4,7 +4,7 @@ page_title: "coderd_license Resource - terraform-provider-coderd" subcategory: "" description: |- A license for a Coder deployment. - It's recommended to create multiple instances of this resource when updating a license. Modifying an existing license will cause the resource to be replaced, which may result in a brief unlicensed period. + It's recommended to set create_before_destroy https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy on license resources. Without setting this, Terraform will remove the old license before adding the updated license. This will result in a temporary disruption to your users; during which they may be unable to use features that require a license. Terraform does not guarantee this resource will be created before other resources or attributes that require a licensed deployment. The depends_on meta-argument is instead recommended. --- @@ -12,11 +12,21 @@ description: |- A license for a Coder deployment. -It's recommended to create multiple instances of this resource when updating a license. Modifying an existing license will cause the resource to be replaced, which may result in a brief unlicensed period. +It's recommended to set [`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy) on license resources. Without setting this, Terraform will remove the old license before adding the updated license. This will result in a temporary disruption to your users; during which they may be unable to use features that require a license. Terraform does not guarantee this resource will be created before other resources or attributes that require a licensed deployment. The `depends_on` meta-argument is instead recommended. +## Example Usage +```terraform +resource "coderd_license" "license" { + license = "<…>" + + lifecycle { + create_before_destroy = true + } +} +``` ## Schema diff --git a/examples/resources/coderd_license/resource.tf b/examples/resources/coderd_license/resource.tf new file mode 100644 index 0000000..3a25e9c --- /dev/null +++ b/examples/resources/coderd_license/resource.tf @@ -0,0 +1,7 @@ +resource "coderd_license" "license" { + license = "<…>" + + lifecycle { + create_before_destroy = true + } +} diff --git a/internal/provider/license_resource.go b/internal/provider/license_resource.go index 2dec235..5cb4778 100644 --- a/internal/provider/license_resource.go +++ b/internal/provider/license_resource.go @@ -39,9 +39,12 @@ func (r *LicenseResource) Metadata(ctx context.Context, req resource.MetadataReq func (r *LicenseResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ - MarkdownDescription: "A license for a Coder deployment.\n\nIt's recommended to create multiple instances of this " + - "resource when updating a license. Modifying an existing license will cause the resource to be replaced, " + - "which may result in a brief unlicensed period.\n\n" + + MarkdownDescription: "A license for a Coder deployment.\n\nIt's recommended to set " + + "[`create_before_destroy`](https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy) " + + "on license resources. Without setting this, Terraform will remove the old " + + "license before adding the updated license. This will result in a temporary " + + "disruption to your users; during which they may be unable to use features " + + "that require a license.\n\n" + "Terraform does not guarantee this resource " + "will be created before other resources or attributes that require a licensed deployment. " + "The `depends_on` meta-argument is instead recommended.", From 1ef2a69fe56969e75b1e560b8eb2babaecc61137 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Thu, 14 Nov 2024 13:21:20 -0700 Subject: [PATCH 07/14] feat: add organization resource (#131) --- Makefile | 4 + docs/resources/organization.md | 39 +++ .../resources/coderd_organization/import.sh | 2 + internal/provider/organization_resource.go | 262 ++++++++++++++++++ .../provider/organization_resource_test.go | 115 ++++++++ internal/provider/provider.go | 2 +- internal/provider/util.go | 8 +- 7 files changed, 427 insertions(+), 5 deletions(-) create mode 100644 docs/resources/organization.md create mode 100644 examples/resources/coderd_organization/import.sh create mode 100644 internal/provider/organization_resource.go create mode 100644 internal/provider/organization_resource_test.go diff --git a/Makefile b/Makefile index 54a7a12..b1f903b 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,12 @@ default: testacc fmt: + go fmt ./... terraform fmt -recursive +vet: + go vet ./... + gen: go generate ./... diff --git a/docs/resources/organization.md b/docs/resources/organization.md new file mode 100644 index 0000000..a5e2402 --- /dev/null +++ b/docs/resources/organization.md @@ -0,0 +1,39 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coderd_organization Resource - terraform-provider-coderd" +subcategory: "" +description: |- + An organization on the Coder deployment +--- + +# coderd_organization (Resource) + +An organization on the Coder deployment + + + + +## Schema + +### Required + +- `name` (String) Name of the organization. + +### Optional + +- `description` (String) +- `display_name` (String) Display name of the organization. Defaults to name. +- `icon` (String) + +### Read-Only + +- `id` (String) Organization ID + +## Import + +Import is supported using the following syntax: + +```shell +# Organizations can be imported by their name +terraform import coderd_organization.our_org our_org +``` diff --git a/examples/resources/coderd_organization/import.sh b/examples/resources/coderd_organization/import.sh new file mode 100644 index 0000000..882dce6 --- /dev/null +++ b/examples/resources/coderd_organization/import.sh @@ -0,0 +1,2 @@ +# Organizations can be imported by their name +terraform import coderd_organization.our_org our_org diff --git a/internal/provider/organization_resource.go b/internal/provider/organization_resource.go new file mode 100644 index 0000000..1575ce3 --- /dev/null +++ b/internal/provider/organization_resource.go @@ -0,0 +1,262 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &OrganizationResource{} +var _ resource.ResourceWithImportState = &OrganizationResource{} + +type OrganizationResource struct { + *CoderdProviderData +} + +// OrganizationResourceModel describes the resource data model. +type OrganizationResourceModel struct { + ID UUID `tfsdk:"id"` + + Name types.String `tfsdk:"name"` + DisplayName types.String `tfsdk:"display_name"` + Description types.String `tfsdk:"description"` + Icon types.String `tfsdk:"icon"` +} + +func NewOrganizationResource() resource.Resource { + return &OrganizationResource{} +} + +func (r *OrganizationResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization" +} + +func (r *OrganizationResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "An organization on the Coder deployment", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + CustomType: UUIDType, + Computed: true, + MarkdownDescription: "Organization ID", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "Name of the organization.", + Required: true, + Validators: []validator.String{ + codersdkvalidator.Name(), + }, + }, + "display_name": schema.StringAttribute{ + MarkdownDescription: "Display name of the organization. Defaults to name.", + Computed: true, + Optional: true, + Default: stringdefault.StaticString(""), + Validators: []validator.String{ + codersdkvalidator.DisplayName(), + }, + }, + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + "icon": schema.StringAttribute{ + Optional: true, + Computed: true, + Default: stringdefault.StaticString(""), + }, + }, + } +} + +func (r *OrganizationResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + data, ok := req.ProviderData.(*CoderdProviderData) + + if !ok { + resp.Diagnostics.AddError( + "Unable to configure provider data", + fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.CoderdProviderData = data +} + +func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Read Terraform prior state data into the model + var data OrganizationResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var org codersdk.Organization + var err error + if data.ID.IsNull() { + orgName := data.Name.ValueString() + org, err = r.Client.OrganizationByName(ctx, orgName) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by name, got error: %s", err)) + return + } + data.ID = UUIDValue(org.ID) + } else { + orgID := data.ID.ValueUUID() + org, err = r.Client.Organization(ctx, orgID) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to get organization by ID, got error: %s", err)) + return + } + } + + // We've fetched the organization ID from state, and the latest values for + // everything else from the backend. Ensure that any mutable data is synced + // with the backend. + data.Name = types.StringValue(org.Name) + data.DisplayName = types.StringValue(org.DisplayName) + data.Description = types.StringValue(org.Description) + data.Icon = types.StringValue(org.Icon) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Read Terraform plan data into the model + var data OrganizationResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Trace(ctx, "creating organization", map[string]any{ + "id": data.ID.ValueUUID(), + "name": data.Name.ValueString(), + "display_name": data.DisplayName.ValueString(), + "description": data.Description.ValueString(), + "icon": data.Icon.ValueString(), + }) + org, err := r.Client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{ + Name: data.Name.ValueString(), + DisplayName: data.DisplayName.ValueString(), + Description: data.Description.ValueString(), + Icon: data.Icon.ValueString(), + }) + if err != nil { + resp.Diagnostics.AddError("Failed to create organization", err.Error()) + return + } + tflog.Trace(ctx, "successfully created organization", map[string]any{ + "id": org.ID, + "name": org.Name, + "display_name": org.DisplayName, + "description": org.Description, + "icon": org.Icon, + }) + // Fill in `ID` since it must be "computed". + data.ID = UUIDValue(org.ID) + // We also fill in `DisplayName`, since it's optional but the backend will + // default it. + data.DisplayName = types.StringValue(org.DisplayName) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Read Terraform plan data into the model + var data OrganizationResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + orgID := data.ID.ValueUUID() + + // Update the organization metadata + tflog.Trace(ctx, "updating organization", map[string]any{ + "id": orgID, + "new_name": data.Name.ValueString(), + "new_display_name": data.DisplayName.ValueString(), + "new_description": data.Description.ValueString(), + "new_icon": data.Icon.ValueString(), + }) + org, err := r.Client.UpdateOrganization(ctx, orgID.String(), codersdk.UpdateOrganizationRequest{ + Name: data.Name.ValueString(), + DisplayName: data.DisplayName.ValueString(), + Description: data.Description.ValueStringPointer(), + Icon: data.Icon.ValueStringPointer(), + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update organization %s, got error: %s", orgID, err)) + return + } + tflog.Trace(ctx, "successfully updated organization", map[string]any{ + "id": orgID, + "name": org.Name, + "display_name": org.DisplayName, + "description": org.Description, + "icon": org.Icon, + }) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *OrganizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Read Terraform prior state data into the model + var data OrganizationResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + orgID := data.ID.ValueUUID() + + tflog.Trace(ctx, "deleting organization", map[string]any{ + "id": orgID, + "name": data.Name.ValueString(), + }) + err := r.Client.DeleteOrganization(ctx, orgID.String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete organization %s, got error: %s", orgID, err)) + return + } + tflog.Trace(ctx, "successfully deleted organization", map[string]any{ + "id": orgID, + "name": data.Name.ValueString(), + }) + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) +} + +func (r *OrganizationResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + // Terraform will eventually `Read` in the rest of the fields after we have + // set the `name` attribute. + resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp) +} diff --git a/internal/provider/organization_resource_test.go b/internal/provider/organization_resource_test.go new file mode 100644 index 0000000..b633265 --- /dev/null +++ b/internal/provider/organization_resource_test.go @@ -0,0 +1,115 @@ +package provider + +import ( + "context" + "os" + "strings" + "testing" + "text/template" + + "github.com/coder/coder/v2/coderd/util/ptr" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/terraform-provider-coderd/integration" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/stretchr/testify/require" +) + +func TestAccOrganizationResource(t *testing.T) { + if os.Getenv("TF_ACC") == "" { + t.Skip("Acceptance tests are disabled.") + } + + ctx := context.Background() + client := integration.StartCoder(ctx, t, "group_acc", true) + _, err := client.User(ctx, codersdk.Me) + require.NoError(t, err) + + cfg1 := testAccOrganizationResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + Name: ptr.Ref("example-org"), + DisplayName: ptr.Ref("Example Organization"), + Description: ptr.Ref("This is an example organization"), + Icon: ptr.Ref("/icon/coder.svg"), + } + + cfg2 := cfg1 + cfg2.Name = ptr.Ref("example-org-new") + cfg2.DisplayName = ptr.Ref("Example Organization New") + + t.Run("CreateImportUpdateReadOk", func(t *testing.T) { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: cfg1.String(t), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("name"), knownvalue.StringExact("example-org")), + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("Example Organization")), + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("icon"), knownvalue.StringExact("/icon/coder.svg")), + }, + }, + // Import + { + Config: cfg1.String(t), + ResourceName: "coderd_organization.test", + ImportState: true, + ImportStateVerify: true, + ImportStateId: *cfg1.Name, + }, + // Update and Read + { + Config: cfg2.String(t), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("name"), knownvalue.StringExact("example-org-new")), + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("Example Organization New")), + }, + }, + }, + }) + }) +} + +type testAccOrganizationResourceConfig struct { + URL string + Token string + + Name *string + DisplayName *string + Description *string + Icon *string +} + +func (c testAccOrganizationResourceConfig) String(t *testing.T) string { + t.Helper() + tpl := ` +provider coderd { + url = "{{.URL}}" + token = "{{.Token}}" +} + +resource "coderd_organization" "test" { + name = {{orNull .Name}} + display_name = {{orNull .DisplayName}} + description = {{orNull .Description}} + icon = {{orNull .Icon}} +} +` + funcMap := template.FuncMap{ + "orNull": PrintOrNull, + } + + buf := strings.Builder{} + tmpl, err := template.New("organizationResource").Funcs(funcMap).Parse(tpl) + require.NoError(t, err) + + err = tmpl.Execute(&buf, c) + require.NoError(t, err) + return buf.String() +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index bfeea5e..7b7d165 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -78,7 +78,6 @@ This provider is only compatible with Coder version [2.10.1](https://github.com/ func (p *CoderdProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { var data CoderdProviderModel - resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { @@ -139,6 +138,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour NewTemplateResource, NewWorkspaceProxyResource, NewLicenseResource, + NewOrganizationResource, } } diff --git a/internal/provider/util.go b/internal/provider/util.go index 720259c..169286f 100644 --- a/internal/provider/util.go +++ b/internal/provider/util.go @@ -85,11 +85,11 @@ func computeDirectoryHash(directory string) (string, error) { // memberDiff returns the members to add and remove from the group, given the current members and the planned members. // plannedMembers is deliberately our custom type, as Terraform cannot automatically produce `[]uuid.UUID` from a set. -func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) { - curSet := make(map[uuid.UUID]struct{}, len(curMembers)) +func memberDiff(currentMembers []uuid.UUID, plannedMembers []UUID) (add, remove []string) { + curSet := make(map[uuid.UUID]struct{}, len(currentMembers)) planSet := make(map[uuid.UUID]struct{}, len(plannedMembers)) - for _, userID := range curMembers { + for _, userID := range currentMembers { curSet[userID] = struct{}{} } for _, plannedUserID := range plannedMembers { @@ -98,7 +98,7 @@ func memberDiff(curMembers []uuid.UUID, plannedMembers []UUID) (add, remove []st add = append(add, plannedUserID.ValueString()) } } - for _, curUserID := range curMembers { + for _, curUserID := range currentMembers { if _, exists := planSet[curUserID]; !exists { remove = append(remove, curUserID.String()) } From 1c5fef0036845a685085a9164d5409c09ef82c60 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Fri, 15 Nov 2024 22:00:14 +1100 Subject: [PATCH 08/14] ci: check for unstaged files correctly during gen (#142) Discovered in #141 that CI could pass even when `make gen` could produce a new file. --- .github/workflows/test.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e8db37f..e6885d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,8 +54,10 @@ jobs: - name: git diff run: | - git diff --compact-summary --exit-code || \ - (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1) + if [[ -n $(git ls-files --other --modified --exclude-standard) ]]; then + echo "Unexpected difference in directories after code generation. Run 'make gen' and include the output in the commit." + exit 1 + fi # Run acceptance tests in a matrix with Terraform CLI versions test: From e90acf03a2d1b47438c2169bba20e1e55ec5e8b3 Mon Sep 17 00:00:00 2001 From: Kayla Washburn-Love Date: Fri, 15 Nov 2024 14:45:17 -0700 Subject: [PATCH 09/14] feat: add `coderd_provisioner_key` resource (#141) --- docs/resources/provisioner_key.md | 29 ++++ internal/provider/license_resource_test.go | 6 +- internal/provider/provider.go | 1 + internal/provider/provisioner_key_resource.go | 154 ++++++++++++++++++ .../provider/provisioner_key_resource_test.go | 114 +++++++++++++ internal/provider/template_resource_test.go | 6 +- 6 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 docs/resources/provisioner_key.md create mode 100644 internal/provider/provisioner_key_resource.go create mode 100644 internal/provider/provisioner_key_resource_test.go diff --git a/docs/resources/provisioner_key.md b/docs/resources/provisioner_key.md new file mode 100644 index 0000000..d64b724 --- /dev/null +++ b/docs/resources/provisioner_key.md @@ -0,0 +1,29 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "coderd_provisioner_key Resource - terraform-provider-coderd" +subcategory: "" +description: |- + A provisioner key for a Coder deployment. +--- + +# coderd_provisioner_key (Resource) + +A provisioner key for a Coder deployment. + + + + +## Schema + +### Required + +- `name` (String) The name of the key. +- `organization_id` (String) The organization that provisioners connected with this key will be connected to. + +### Optional + +- `tags` (Map of String) The tags that provisioners connected with this key will accept jobs for. + +### Read-Only + +- `key` (String, Sensitive) The acquired provisioner key diff --git a/internal/provider/license_resource_test.go b/internal/provider/license_resource_test.go index e2d13d6..9a4abc7 100644 --- a/internal/provider/license_resource_test.go +++ b/internal/provider/license_resource_test.go @@ -24,7 +24,7 @@ func TestAccLicenseResource(t *testing.T) { t.Skip("No license found for license resource tests, skipping") } - cfg1 := testAccLicenseResourceconfig{ + cfg1 := testAccLicenseResourceConfig{ URL: client.URL.String(), Token: client.SessionToken(), License: license, @@ -42,13 +42,13 @@ func TestAccLicenseResource(t *testing.T) { }) } -type testAccLicenseResourceconfig struct { +type testAccLicenseResourceConfig struct { URL string Token string License string } -func (c testAccLicenseResourceconfig) String(t *testing.T) string { +func (c testAccLicenseResourceConfig) String(t *testing.T) string { t.Helper() tpl := ` provider coderd { diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7b7d165..b8b9fa7 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -139,6 +139,7 @@ func (p *CoderdProvider) Resources(ctx context.Context) []func() resource.Resour NewWorkspaceProxyResource, NewLicenseResource, NewOrganizationResource, + NewProvisionerKeyResource, } } diff --git a/internal/provider/provisioner_key_resource.go b/internal/provider/provisioner_key_resource.go new file mode 100644 index 0000000..5904df0 --- /dev/null +++ b/internal/provider/provisioner_key_resource.go @@ -0,0 +1,154 @@ +package provider + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/mapplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/coder/coder/v2/codersdk" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &ProvisionerKeyResource{} + +func NewProvisionerKeyResource() resource.Resource { + return &ProvisionerKeyResource{} +} + +// ProvisionerKeyResource defines the resource implementation. +type ProvisionerKeyResource struct { + *CoderdProviderData +} + +// ProvisionerKeyResourceModel describes the resource data model. +type ProvisionerKeyResourceModel struct { + OrganizationID UUID `tfsdk:"organization_id"` + Name types.String `tfsdk:"name"` + Tags types.Map `tfsdk:"tags"` + Key types.String `tfsdk:"key"` +} + +func (r *ProvisionerKeyResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_provisioner_key" +} + +func (r *ProvisionerKeyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "A provisioner key for a Coder deployment.", + + Attributes: map[string]schema.Attribute{ + "organization_id": schema.StringAttribute{ + CustomType: UUIDType, + MarkdownDescription: "The organization that provisioners connected with this key will be connected to.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "The name of the key.", + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "tags": schema.MapAttribute{ + MarkdownDescription: "The tags that provisioners connected with this key will accept jobs for.", + ElementType: types.StringType, + Optional: true, + PlanModifiers: []planmodifier.Map{ + mapplanmodifier.RequiresReplace(), + }, + }, + "key": schema.StringAttribute{ + MarkdownDescription: "The acquired provisioner key", + Computed: true, + Sensitive: true, + }, + }, + } +} + +func (r *ProvisionerKeyResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + data, ok := req.ProviderData.(*CoderdProviderData) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *CoderdProviderData, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.CoderdProviderData = data +} + +func (r *ProvisionerKeyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + // Read Terraform plan data into the model + var data ProvisionerKeyResourceModel + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + var tags map[string]string + resp.Diagnostics.Append(data.Tags.ElementsAs(ctx, &tags, false)...) + createKeyResult, err := r.Client.CreateProvisionerKey(ctx, data.OrganizationID.ValueUUID(), codersdk.CreateProvisionerKeyRequest{ + Name: data.Name.ValueString(), + Tags: tags, + }) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to create provisioner_key, got error: %s", err)) + return + } + + data.Key = types.StringValue(createKeyResult.Key) + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ProvisionerKeyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + // Read Terraform prior state data into the model + var data ProvisionerKeyResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Provisioner keys are immutable, no reading necessary. + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *ProvisionerKeyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + // Provisioner keys are immutable, updating is always invalid. + resp.Diagnostics.AddError("Invalid Update", "Terraform is attempting to update a resource which must be replaced") +} + +func (r *ProvisionerKeyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + // Read Terraform prior state data into the model + var data ProvisionerKeyResourceModel + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + err := r.Client.DeleteProvisionerKey(ctx, data.OrganizationID.ValueUUID(), data.Name.ValueString()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to delete provisionerkey, got error: %s", err)) + return + } +} diff --git a/internal/provider/provisioner_key_resource_test.go b/internal/provider/provisioner_key_resource_test.go new file mode 100644 index 0000000..9c89647 --- /dev/null +++ b/internal/provider/provisioner_key_resource_test.go @@ -0,0 +1,114 @@ +package provider + +import ( + "context" + "os" + "strings" + "testing" + "text/template" + + "github.com/coder/terraform-provider-coderd/integration" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/stretchr/testify/require" +) + +func TestAccProvisionerKeyResource(t *testing.T) { + if os.Getenv("TF_ACC") == "" { + t.Skip("Acceptance tests are disabled.") + } + ctx := context.Background() + client := integration.StartCoder(ctx, t, "provisioner_key_acc", true) + orgs, err := client.Organizations(ctx) + require.NoError(t, err) + firstOrg := orgs[0].ID + + cfg1 := testAccProvisionerKeyResourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + + OrganizationID: firstOrg, + Name: "example-provisioner-key", + } + + cfg2 := cfg1 + cfg2.Tags = map[string]string{ + "wibble": "wobble", + } + + cfg3 := cfg2 + cfg3.Name = "different-provisioner-key" + + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg1.String(t), + }, + { + Config: cfg2.String(t), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("coderd_provisioner_key.test", plancheck.ResourceActionReplace), + }, + }, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("coderd_provisioner_key.test", tfjsonpath.New("tags").AtMapKey("wibble"), knownvalue.StringExact("wobble")), + }, + }, + { + Config: cfg3.String(t), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction("coderd_provisioner_key.test", plancheck.ResourceActionReplace), + }, + }, + }, + }, + }) +} + +type testAccProvisionerKeyResourceConfig struct { + URL string + Token string + + OrganizationID uuid.UUID + Name string + Tags map[string]string +} + +func (c testAccProvisionerKeyResourceConfig) String(t *testing.T) string { + t.Helper() + + tpl := ` +provider coderd { + url = "{{.URL}}" + token = "{{.Token}}" +} + +resource "coderd_provisioner_key" "test" { + organization_id = "{{.OrganizationID}}" + name = "{{.Name}}" + + tags = { + {{- range $key, $value := .Tags}} + {{$key}} = "{{$value}}" + {{- end}} + } +} +` + + buf := strings.Builder{} + tmpl, err := template.New("provisionerKeyResource").Parse(tpl) + require.NoError(t, err) + + err = tmpl.Execute(&buf, c) + require.NoError(t, err) + return buf.String() +} diff --git a/internal/provider/template_resource_test.go b/internal/provider/template_resource_test.go index b9d7ae3..c844da0 100644 --- a/internal/provider/template_resource_test.go +++ b/internal/provider/template_resource_test.go @@ -424,7 +424,7 @@ func TestAccTemplateResourceEnterprise(t *testing.T) { t.Skip("Acceptance tests are disabled.") } ctx := context.Background() - client := integration.StartCoder(ctx, t, "template_acc", true) + client := integration.StartCoder(ctx, t, "template_resource_acc", true) firstUser, err := client.User(ctx, codersdk.Me) require.NoError(t, err) @@ -565,7 +565,7 @@ func TestAccTemplateResourceAGPL(t *testing.T) { t.Skip("Acceptance tests are disabled.") } ctx := context.Background() - client := integration.StartCoder(ctx, t, "template_acc", false) + client := integration.StartCoder(ctx, t, "template_resource_agpl_acc", false) firstUser, err := client.User(ctx, codersdk.Me) require.NoError(t, err) @@ -689,7 +689,7 @@ resource "coderd_template" "sample" { }` ctx := context.Background() - client := integration.StartCoder(ctx, t, "template_acc", false) + client := integration.StartCoder(ctx, t, "template_resource_variables_acc", false) cfg = fmt.Sprintf(cfg, client.URL.String(), client.SessionToken()) From 1fe42ea2cc0859fa0ad7a1aae39e7272288dc8db Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:54:58 +1100 Subject: [PATCH 10/14] chore(deps): bump github.com/stretchr/testify from 1.9.0 to 1.10.0 (#145) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.9.0 to 1.10.0.
Release notes

Sourced from github.com/stretchr/testify's releases.

v1.10.0

What's Changed

Functional Changes

Fixes

Documantation, Build & CI

New Contributors

... (truncated)

Commits
  • 89cbdd9 Merge pull request #1626 from arjun-1/fix-functional-options-diff-indirect-calls
  • 07bac60 Merge pull request #1667 from sikehish/flaky
  • 716de8d Increase timeouts in Test_Mock_Called_blocks to reduce flakiness in CI
  • 118fb83 NotSame should fail if args are not pointers #1661 (#1664)
  • 7d99b2b attempt 2
  • 05f87c0 more similar
  • ea7129e better fmt
  • a1b9c9e Merge pull request #1663 from ybrustin/master
  • 8302de9 Merge branch 'master' into master
  • 89352f7 Merge pull request #1518 from hendrywiranto/adjust-readme-remove-v2
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/stretchr/testify&package-manager=go_modules&previous-version=1.9.0&new-version=1.10.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0cb6211..5ffe57f 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 github.com/otiai10/copy v1.14.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) require ( diff --git a/go.sum b/go.sum index bcd1dd9..0a3b667 100644 --- a/go.sum +++ b/go.sum @@ -454,8 +454,8 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggest/assertjson v1.9.0 h1:dKu0BfJkIxv/xe//mkCrK5yZbs79jL7OVf9Ija7o2xQ= github.com/swaggest/assertjson v1.9.0/go.mod h1:b+ZKX2VRiUjxfUIal0HDN85W0nHPAYUbYH5WkkSsFsU= github.com/tinylib/msgp v1.2.1 h1:6ypy2qcCznxpP4hpORzhtXyTqrBs7cfM9MCCWY8zsmU= From a6400985072de771535eb50af3b3720be1f41b15 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:55:15 +1100 Subject: [PATCH 11/14] chore(deps): bump github.com/hashicorp/terraform-plugin-testing from 1.10.0 to 1.11.0 (#146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [//]: # (dependabot-start) ⚠️ **Dependabot is rebasing this PR** ⚠️ Rebasing might not happen immediately, so don't worry if this takes some time. Note: if you make any changes to this PR yourself, they will take precedence over the rebase. --- [//]: # (dependabot-end) Bumps [github.com/hashicorp/terraform-plugin-testing](https://github.com/hashicorp/terraform-plugin-testing) from 1.10.0 to 1.11.0.
Release notes

Sourced from github.com/hashicorp/terraform-plugin-testing's releases.

v1.11.0

NOTES:

  • all: This Go module has been updated to Go 1.22 per the Go support policy. It is recommended to review the Go 1.22 release notes before upgrading. Any consumers building on earlier Go versions may experience errors. (#371)
  • echoprovider: The echoprovider package is considered experimental and may be altered or removed in a subsequent release (#389)

FEATURES:

  • tfversion: Added SkipIfNotAlpha version check for testing experimental features of alpha Terraform builds. (#388)
  • echoprovider: Introduced new echoprovider package, which contains a v6 Terraform provider that can be used to test ephemeral resource data. (#389)
Changelog

Sourced from github.com/hashicorp/terraform-plugin-testing's changelog.

1.11.0 (November 19, 2024)

NOTES:

  • all: This Go module has been updated to Go 1.22 per the Go support policy. It is recommended to review the Go 1.22 release notes before upgrading. Any consumers building on earlier Go versions may experience errors. (#371)
  • echoprovider: The echoprovider package is considered experimental and may be altered or removed in a subsequent release (#389)

FEATURES:

  • tfversion: Added SkipIfNotAlpha version check for testing experimental features of alpha Terraform builds. (#388)
  • echoprovider: Introduced new echoprovider package, which contains a v6 Terraform provider that can be used to test ephemeral resource data. (#389)
Commits
  • 7e23ebc Update changelog
  • f1e790c Result of tsccr-helper -log-level=info gha update -latest .github/ (#391)
  • 862e9f3 build(deps): Bump github.com/hashicorp/hcl/v2 from 2.22.0 to 2.23.0 (#392)
  • 33bf5cf echoprovider: Add a new test-only v6 provider that echoes ephemeral provider ...
  • 4a88926 build(deps): Bump github.com/golang-jwt/jwt/v4 in /tools (#390)
  • 65f3a3e build(deps): Bump golang.org/x/crypto from 0.28.0 to 0.29.0 (#387)
  • 918aea6 tfversion: Add SkipIfNotAlpha version check (#388)
  • 7f6b899 Result of tsccr-helper -log-level=info gha update -latest .github/ (#385)
  • 5a23eaa build(deps): Bump github.com/hashicorp/terraform-plugin-sdk/v2 (#384)
  • c593ce4 build(deps): Bump github.com/hashicorp/terraform-json (#381)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/hashicorp/terraform-plugin-testing&package-manager=go_modules&previous-version=1.10.0&new-version=1.11.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 16 ++++++++-------- go.sum | 32 ++++++++++++++++---------------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index 5ffe57f..0df98af 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 github.com/hashicorp/terraform-plugin-go v0.25.0 github.com/hashicorp/terraform-plugin-log v0.9.0 - github.com/hashicorp/terraform-plugin-testing v1.10.0 + github.com/hashicorp/terraform-plugin-testing v1.11.0 github.com/otiai10/copy v1.14.0 github.com/stretchr/testify v1.10.0 ) @@ -80,11 +80,11 @@ require ( github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/hc-install v0.9.0 // indirect - github.com/hashicorp/hcl/v2 v2.22.0 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.23.0 // indirect - github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -148,15 +148,15 @@ require ( go.opentelemetry.io/otel/trace v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.29.0 // indirect golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.26.0 // indirect - golang.org/x/term v0.25.0 // indirect - golang.org/x/text v0.19.0 // indirect + golang.org/x/sync v0.9.0 // indirect + golang.org/x/sys v0.27.0 // indirect + golang.org/x/term v0.26.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/time v0.7.0 // indirect golang.org/x/tools v0.26.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/go.sum b/go.sum index 0a3b667..629d5c9 100644 --- a/go.sum +++ b/go.sum @@ -243,8 +243,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= -github.com/hashicorp/hcl/v2 v2.22.0 h1:hkZ3nCtqeJsDhPRFz5EA9iwcG1hNWGePOTw6oyul12M= -github.com/hashicorp/hcl/v2 v2.22.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= @@ -261,10 +261,10 @@ github.com/hashicorp/terraform-plugin-go v0.25.0 h1:oi13cx7xXA6QciMcpcFi/rwA974r github.com/hashicorp/terraform-plugin-go v0.25.0/go.mod h1:+SYagMYadJP86Kvn+TGeV+ofr/R3g4/If0O5sO96MVw= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= -github.com/hashicorp/terraform-plugin-testing v1.10.0 h1:2+tmRNhvnfE4Bs8rB6v58S/VpqzGC6RCh9Y8ujdn+aw= -github.com/hashicorp/terraform-plugin-testing v1.10.0/go.mod h1:iWRW3+loP33WMch2P/TEyCxxct/ZEcCGMquSLSCVsrc= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 h1:wyKCCtn6pBBL46c1uIIBNUOWlNfYXfXpVo16iDyLp8Y= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0/go.mod h1:B0Al8NyYVr8Mp/KLwssKXG1RqnTk7FySqSn4fRuLNgw= +github.com/hashicorp/terraform-plugin-testing v1.11.0 h1:MeDT5W3YHbONJt2aPQyaBsgQeAIckwPX41EUHXEn29A= +github.com/hashicorp/terraform-plugin-testing v1.11.0/go.mod h1:WNAHQ3DcgV/0J+B15WTE6hDvxcUdkPPpnB1FR3M910U= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -542,8 +542,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= -golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= +golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -576,8 +576,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -603,8 +603,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= +golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -612,8 +612,8 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= -golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= +golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -623,8 +623,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= -golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= From f7eec90936c3929a20c3dbf10688b38ec6664058 Mon Sep 17 00:00:00 2001 From: Ethan <39577870+ethanndickson@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:27:33 +1100 Subject: [PATCH 12/14] fix: mark invalid UUIDs as known (#148) --- internal/provider/user_data_source_test.go | 21 +++++++++++++++++++++ internal/provider/uuid.go | 20 ++++++++++---------- internal/provider/uuid_internal_test.go | 10 +++++++--- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/internal/provider/user_data_source_test.go b/internal/provider/user_data_source_test.go index ebdab83..6a9309f 100644 --- a/internal/provider/user_data_source_test.go +++ b/internal/provider/user_data_source_test.go @@ -57,6 +57,7 @@ func TestAccUserDataSource(t *testing.T) { Username: ptr.Ref(user.Username), } resource.Test(t, resource.TestCase{ + IsUnitTest: true, PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ @@ -75,6 +76,7 @@ func TestAccUserDataSource(t *testing.T) { ID: ptr.Ref(user.ID.String()), } resource.Test(t, resource.TestCase{ + IsUnitTest: true, PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, // User by ID @@ -92,6 +94,7 @@ func TestAccUserDataSource(t *testing.T) { Token: client.SessionToken(), } resource.Test(t, resource.TestCase{ + IsUnitTest: true, PreCheck: func() { testAccPreCheck(t) }, ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, // Neither ID nor Username @@ -104,6 +107,24 @@ func TestAccUserDataSource(t *testing.T) { }) }) + t.Run("InvalidUUIDError", func(t *testing.T) { + cfg := testAccUserDataSourceConfig{ + URL: client.URL.String(), + Token: client.SessionToken(), + ID: ptr.Ref("invalid-uuid"), + } + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + { + Config: cfg.String(t), + ExpectError: regexp.MustCompile(`The provided value cannot be parsed as a UUID`), + }, + }, + }) + }) } type testAccUserDataSourceConfig struct { diff --git a/internal/provider/uuid.go b/internal/provider/uuid.go index 8cd8912..ac37b04 100644 --- a/internal/provider/uuid.go +++ b/internal/provider/uuid.go @@ -48,16 +48,16 @@ func (t uuidType) ValueFromString(ctx context.Context, in basetypes.StringValue) return NewUUIDUnknown(), diags } - value, err := uuid.Parse(in.ValueString()) - if err != nil { - // The framework doesn't want us to return validation errors here - // for some reason. They get caught by `ValidateAttribute` instead, - // and this function isn't called directly by our provider - UUIDValue - // takes a valid UUID instead of a string. - return NewUUIDUnknown(), diags - } - - return UUIDValue(value), diags + // This function deliberately does not handle invalid UUIDs. + // Instead, `ValidateAttribute` will be called + // on the stored string during `validate` `plan` and `apply`, + // which will also create an error diagnostic. + // For that reason, storing the zero UUID is fine. + v, _ := uuid.Parse(in.ValueString()) + return UUID{ + StringValue: in, + value: v, + }, diags } // ValueFromTerraform implements basetypes.StringTypable. diff --git a/internal/provider/uuid_internal_test.go b/internal/provider/uuid_internal_test.go index 6283bb9..697d9c3 100644 --- a/internal/provider/uuid_internal_test.go +++ b/internal/provider/uuid_internal_test.go @@ -7,6 +7,7 @@ import ( "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/stretchr/testify/require" ) @@ -37,9 +38,12 @@ func TestUUIDTypeValueFromTerraform(t *testing.T) { expected: UUIDValue(ValidUUID), }, { - name: "invalid UUID", - input: tftypes.NewValue(tftypes.String, "invalid"), - expected: NewUUIDUnknown(), + name: "invalid UUID", + input: tftypes.NewValue(tftypes.String, "invalid"), + expected: UUID{ + StringValue: basetypes.NewStringValue("invalid"), + value: uuid.Nil, + }, }, } From dedb7d073b5355440a2fbdec349fec6b92dde4b6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 15:01:45 +1100 Subject: [PATCH 13/14] chore(deps): bump github.com/hashicorp/terraform-plugin-docs from 0.20.0 to 0.20.1 (#149) Bumps [github.com/hashicorp/terraform-plugin-docs](https://github.com/hashicorp/terraform-plugin-docs) from 0.20.0 to 0.20.1.
Release notes

Sourced from github.com/hashicorp/terraform-plugin-docs's releases.

v0.20.1

BUG FIXES:

  • validate: Fixed a bug that caused false positive validation errors for resource types that have the same name as the provider. (#419)
  • generate: Fixed a bug that caused all generated resource documentation to have the same content when the provider has a resource type with the same name as the provider. (#419)
  • generate: Fixed a bug that would return an error when a static file exists in both templates and docs, which will now be ignored. (#421)
Changelog

Sourced from github.com/hashicorp/terraform-plugin-docs's changelog.

0.20.1 (November 26, 2024)

BUG FIXES:

  • validate: Fixed a bug that caused false positive validation errors for resource types that have the same name as the provider. (#419)
  • generate: Fixed a bug that caused all generated resource documentation to have the same content when the provider has a resource type with the same name as the provider. (#419)
  • generate: Fixed a bug that would return an error when a static file exists in both templates and docs, which will now be ignored. (#421)
Commits
  • ac7d19a Update changelog
  • 11fcc79 all: Fix various bugs related to resource type names that match the provider ...
  • 4209153 Result of tsccr-helper -log-level=info gha update -latest .github/ (#420)
  • 2004e44 Bump golang.org/x/text from 0.19.0 to 0.20.0 (#418)
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/hashicorp/terraform-plugin-docs&package-manager=go_modules&previous-version=0.20.0&new-version=0.20.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0df98af..4c43f7c 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/docker/docker v27.2.1+incompatible github.com/docker/go-connections v0.5.0 github.com/google/uuid v1.6.0 - github.com/hashicorp/terraform-plugin-docs v0.20.0 + github.com/hashicorp/terraform-plugin-docs v0.20.1 github.com/hashicorp/terraform-plugin-framework v1.13.0 github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 github.com/hashicorp/terraform-plugin-go v0.25.0 diff --git a/go.sum b/go.sum index 629d5c9..d9a08c6 100644 --- a/go.sum +++ b/go.sum @@ -251,8 +251,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= -github.com/hashicorp/terraform-plugin-docs v0.20.0 h1:ox7rm1FN0dVZaJBUzkVVh10R1r3+FeMQWL0QopQ9d7o= -github.com/hashicorp/terraform-plugin-docs v0.20.0/go.mod h1:A/+4SVMdAkQYtIBtaxV0H7AU862TxVZk/hhKaMDQB6Y= +github.com/hashicorp/terraform-plugin-docs v0.20.1 h1:Fq7E/HrU8kuZu3hNliZGwloFWSYfWEOWnylFhYQIoys= +github.com/hashicorp/terraform-plugin-docs v0.20.1/go.mod h1:Yz6HoK7/EgzSrHPB9J/lWFzwl9/xep2OPnc5jaJDV90= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= github.com/hashicorp/terraform-plugin-framework-validators v0.15.0 h1:RXMmu7JgpFjnI1a5QjMCBb11usrW2OtAG+iOTIj5c9Y= From 283e3cce33c126011f97b749f17d7b28be8ef8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B1=E3=82=A4=E3=83=A9?= Date: Tue, 3 Dec 2024 12:16:18 -0700 Subject: [PATCH 14/14] feat: add `group_sync` and `role_sync` for `coderd_organization_resource` (#147) --- docs/resources/organization.md | 21 ++ internal/codersdkvalidator/regex.go | 16 + internal/provider/organization_resource.go | 289 ++++++++++++++++++ .../provider/organization_resource_test.go | 52 +++- 4 files changed, 377 insertions(+), 1 deletion(-) create mode 100644 internal/codersdkvalidator/regex.go diff --git a/docs/resources/organization.md b/docs/resources/organization.md index a5e2402..9556be4 100644 --- a/docs/resources/organization.md +++ b/docs/resources/organization.md @@ -23,12 +23,33 @@ An organization on the Coder deployment - `description` (String) - `display_name` (String) Display name of the organization. Defaults to name. +- `group_sync` (Block, Optional) (see [below for nested schema](#nestedblock--group_sync)) - `icon` (String) +- `role_sync` (Block, Optional) (see [below for nested schema](#nestedblock--role_sync)) ### Read-Only - `id` (String) Organization ID + +### Nested Schema for `group_sync` + +Optional: + +- `auto_create_missing` (Boolean) Controls whether groups will be created if they are missing. +- `field` (String) The claim field that specifies what groups a user should be in. +- `mapping` (Map of List of String) A map from OIDC group name to Coder group ID. +- `regex_filter` (String) A regular expression that will be used to filter the groups returned by the OIDC provider. Any group not matched will be ignored. + + + +### Nested Schema for `role_sync` + +Optional: + +- `field` (String) The claim field that specifies what organization roles a user should be given. +- `mapping` (Map of List of String) A map from OIDC group name to Coder organization role. + ## Import Import is supported using the following syntax: diff --git a/internal/codersdkvalidator/regex.go b/internal/codersdkvalidator/regex.go new file mode 100644 index 0000000..0077616 --- /dev/null +++ b/internal/codersdkvalidator/regex.go @@ -0,0 +1,16 @@ +package codersdkvalidator + +import ( + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +func checkRegexp(it string) error { + _, err := regexp.Compile("") + return err +} + +func Regexp() validator.String { + return validatorFromFunc(checkRegexp, "value must be a valid regexp") +} diff --git a/internal/provider/organization_resource.go b/internal/provider/organization_resource.go index 1575ce3..397f92e 100644 --- a/internal/provider/organization_resource.go +++ b/internal/provider/organization_resource.go @@ -3,9 +3,14 @@ package provider import ( "context" "fmt" + "regexp" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/internal/codersdkvalidator" + "github.com/google/uuid" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -14,6 +19,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" "github.com/hashicorp/terraform-plugin-log/tflog" ) @@ -33,6 +39,49 @@ type OrganizationResourceModel struct { DisplayName types.String `tfsdk:"display_name"` Description types.String `tfsdk:"description"` Icon types.String `tfsdk:"icon"` + + GroupSync types.Object `tfsdk:"group_sync"` + RoleSync types.Object `tfsdk:"role_sync"` +} + +type GroupSyncModel struct { + Field types.String `tfsdk:"field"` + RegexFilter types.String `tfsdk:"regex_filter"` + AutoCreateMissing types.Bool `tfsdk:"auto_create_missing"` + Mapping types.Map `tfsdk:"mapping"` +} + +var groupSyncAttrTypes = map[string]attr.Type{ + "field": types.StringType, + "regex_filter": types.StringType, + "auto_create_missing": types.BoolType, + "mapping": types.MapType{ElemType: types.ListType{ElemType: UUIDType}}, +} + +func (m GroupSyncModel) ValueObject() types.Object { + return types.ObjectValueMust(groupSyncAttrTypes, map[string]attr.Value{ + "field": m.Field, + "regex_filter": m.RegexFilter, + "auto_create_missing": m.AutoCreateMissing, + "mapping": m.Mapping, + }) +} + +type RoleSyncModel struct { + Field types.String `tfsdk:"field"` + Mapping types.Map `tfsdk:"mapping"` +} + +var roleSyncAttrTypes = map[string]attr.Type{ + "field": types.StringType, + "mapping": types.MapType{ElemType: types.ListType{ElemType: types.StringType}}, +} + +func (m RoleSyncModel) ValueObject() types.Object { + return types.ObjectValueMust(roleSyncAttrTypes, map[string]attr.Value{ + "field": m.Field, + "mapping": m.Mapping, + }) } func NewOrganizationResource() resource.Resource { @@ -83,6 +132,59 @@ func (r *OrganizationResource) Schema(ctx context.Context, req resource.SchemaRe Default: stringdefault.StaticString(""), }, }, + + Blocks: map[string]schema.Block{ + "group_sync": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "field": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The claim field that specifies what groups " + + "a user should be in.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "regex_filter": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "A regular expression that will be used to " + + "filter the groups returned by the OIDC provider. Any group " + + "not matched will be ignored.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + codersdkvalidator.Regexp(), + }, + }, + "auto_create_missing": schema.BoolAttribute{ + Optional: true, + MarkdownDescription: "Controls whether groups will be created if " + + "they are missing.", + }, + "mapping": schema.MapAttribute{ + ElementType: types.ListType{ElemType: UUIDType}, + Optional: true, + MarkdownDescription: "A map from OIDC group name to Coder group ID.", + }, + }, + }, + "role_sync": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "field": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The claim field that specifies what " + + "organization roles a user should be given.", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + }, + "mapping": schema.MapAttribute{ + ElementType: types.ListType{ElemType: types.StringType}, + Optional: true, + MarkdownDescription: "A map from OIDC group name to Coder " + + "organization role.", + }, + }, + }, + }, } } @@ -133,6 +235,77 @@ func (r *OrganizationResource) Read(ctx context.Context, req resource.ReadReques } } + if !data.GroupSync.IsNull() { + groupSync, err := r.Client.GroupIDPSyncSettings(ctx, data.ID.ValueUUID().String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to get organization group sync settings, got error: %s", err)) + return + } + + // Read values from Terraform + var groupSyncData GroupSyncModel + resp.Diagnostics.Append(data.GroupSync.As(ctx, &groupSyncData, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + + if !groupSyncData.Field.IsNull() { + groupSyncData.Field = types.StringValue(groupSync.Field) + } + if !groupSyncData.RegexFilter.IsNull() { + groupSyncData.RegexFilter = types.StringValue(groupSync.RegexFilter.String()) + } + if !groupSyncData.AutoCreateMissing.IsNull() { + groupSyncData.AutoCreateMissing = types.BoolValue(groupSync.AutoCreateMissing) + } + if !groupSyncData.Mapping.IsNull() { + elements := make(map[string][]string) + for key, ids := range groupSync.Mapping { + for _, id := range ids { + elements[key] = append(elements[key], id.String()) + } + } + + mapping, diags := types.MapValueFrom(ctx, types.ListType{ElemType: UUIDType}, elements) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + groupSyncData.Mapping = mapping + } + + data.GroupSync = groupSyncData.ValueObject() + } + + if !data.RoleSync.IsNull() { + roleSync, err := r.Client.RoleIDPSyncSettings(ctx, data.ID.ValueUUID().String()) + if err != nil { + resp.Diagnostics.AddError("Client Error", fmt.Sprintf("unable to get organization role sync settings, got error: %s", err)) + return + } + + // Read values from Terraform + var roleSyncData RoleSyncModel + resp.Diagnostics.Append(data.RoleSync.As(ctx, &roleSyncData, basetypes.ObjectAsOptions{})...) + if resp.Diagnostics.HasError() { + return + } + + if !roleSyncData.Field.IsNull() { + roleSyncData.Field = types.StringValue(roleSync.Field) + } + if !roleSyncData.Mapping.IsNull() { + mapping, diags := types.MapValueFrom(ctx, types.ListType{ElemType: types.StringType}, roleSync.Mapping) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + roleSyncData.Mapping = mapping + } + + data.RoleSync = roleSyncData.ValueObject() + } + // We've fetched the organization ID from state, and the latest values for // everything else from the backend. Ensure that any mutable data is synced // with the backend. @@ -183,6 +356,27 @@ func (r *OrganizationResource) Create(ctx context.Context, req resource.CreateRe // default it. data.DisplayName = types.StringValue(org.DisplayName) + // Now apply group and role sync settings, if specified + orgID := data.ID.ValueUUID() + tflog.Trace(ctx, "updating group sync", map[string]any{ + "orgID": orgID, + }) + if !data.GroupSync.IsNull() { + resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...) + if resp.Diagnostics.HasError() { + return + } + } + tflog.Trace(ctx, "updating role sync", map[string]any{ + "orgID": orgID, + }) + if !data.RoleSync.IsNull() { + resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...) + if resp.Diagnostics.HasError() { + return + } + } + // Save data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -215,6 +409,7 @@ func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRe resp.Diagnostics.AddError("Client Error", fmt.Sprintf("Unable to update organization %s, got error: %s", orgID, err)) return } + tflog.Trace(ctx, "successfully updated organization", map[string]any{ "id": orgID, "name": org.Name, @@ -223,6 +418,25 @@ func (r *OrganizationResource) Update(ctx context.Context, req resource.UpdateRe "icon": org.Icon, }) + tflog.Trace(ctx, "updating group sync", map[string]any{ + "orgID": orgID, + }) + if !data.GroupSync.IsNull() { + resp.Diagnostics.Append(r.patchGroupSync(ctx, orgID, data.GroupSync)...) + if resp.Diagnostics.HasError() { + return + } + } + tflog.Trace(ctx, "updating role sync", map[string]any{ + "orgID": orgID, + }) + if !data.RoleSync.IsNull() { + resp.Diagnostics.Append(r.patchRoleSync(ctx, orgID, data.RoleSync)...) + if resp.Diagnostics.HasError() { + return + } + } + // Save updated data into Terraform state resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } @@ -260,3 +474,78 @@ func (r *OrganizationResource) ImportState(ctx context.Context, req resource.Imp // set the `name` attribute. resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp) } + +func (r *OrganizationResource) patchGroupSync( + ctx context.Context, + orgID uuid.UUID, + groupSyncObject types.Object, +) diag.Diagnostics { + var diags diag.Diagnostics + + // Read values from Terraform + var groupSyncData GroupSyncModel + diags.Append(groupSyncObject.As(ctx, &groupSyncData, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return diags + } + + // Convert that into the type used to send the PATCH to the backend + var groupSync codersdk.GroupSyncSettings + groupSync.Field = groupSyncData.Field.ValueString() + groupSync.RegexFilter = regexp.MustCompile(groupSyncData.RegexFilter.ValueString()) + groupSync.AutoCreateMissing = groupSyncData.AutoCreateMissing.ValueBool() + groupSync.Mapping = make(map[string][]uuid.UUID) + // Terraform doesn't know how to turn one our `UUID` Terraform values into a + // `uuid.UUID`, so we have to do the unwrapping manually here. + var mapping map[string][]UUID + diags.Append(groupSyncData.Mapping.ElementsAs(ctx, &mapping, false)...) + if diags.HasError() { + return diags + } + for key, ids := range mapping { + for _, id := range ids { + groupSync.Mapping[key] = append(groupSync.Mapping[key], id.ValueUUID()) + } + } + + // Perform the PATCH + _, err := r.Client.PatchGroupIDPSyncSettings(ctx, orgID.String(), groupSync) + if err != nil { + diags.AddError("Group Sync Update error", err.Error()) + return diags + } + + return diags +} + +func (r *OrganizationResource) patchRoleSync( + ctx context.Context, + orgID uuid.UUID, + roleSyncObject types.Object, +) diag.Diagnostics { + var diags diag.Diagnostics + + // Read values from Terraform + var roleSyncData RoleSyncModel + diags.Append(roleSyncObject.As(ctx, &roleSyncData, basetypes.ObjectAsOptions{})...) + if diags.HasError() { + return diags + } + + // Convert that into the type used to send the PATCH to the backend + var roleSync codersdk.RoleSyncSettings + roleSync.Field = roleSyncData.Field.ValueString() + diags.Append(roleSyncData.Mapping.ElementsAs(ctx, &roleSync.Mapping, false)...) + if diags.HasError() { + return diags + } + + // Perform the PATCH + _, err := r.Client.PatchRoleIDPSyncSettings(ctx, orgID.String(), roleSync) + if err != nil { + diags.AddError("Role Sync Update error", err.Error()) + return diags + } + + return diags +} diff --git a/internal/provider/organization_resource_test.go b/internal/provider/organization_resource_test.go index b633265..0a755c4 100644 --- a/internal/provider/organization_resource_test.go +++ b/internal/provider/organization_resource_test.go @@ -10,6 +10,7 @@ import ( "github.com/coder/coder/v2/coderd/util/ptr" "github.com/coder/coder/v2/codersdk" "github.com/coder/terraform-provider-coderd/integration" + "github.com/google/uuid" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/knownvalue" "github.com/hashicorp/terraform-plugin-testing/statecheck" @@ -23,7 +24,7 @@ func TestAccOrganizationResource(t *testing.T) { } ctx := context.Background() - client := integration.StartCoder(ctx, t, "group_acc", true) + client := integration.StartCoder(ctx, t, "organization_acc", true) _, err := client.User(ctx, codersdk.Me) require.NoError(t, err) @@ -40,6 +41,20 @@ func TestAccOrganizationResource(t *testing.T) { cfg2.Name = ptr.Ref("example-org-new") cfg2.DisplayName = ptr.Ref("Example Organization New") + cfg3 := cfg2 + cfg3.GroupSync = ptr.Ref(codersdk.GroupSyncSettings{ + Field: "wibble", + Mapping: map[string][]uuid.UUID{ + "wibble": {uuid.MustParse("6e57187f-6543-46ab-a62c-a10065dd4314")}, + }, + }) + cfg3.RoleSync = ptr.Ref(codersdk.RoleSyncSettings{ + Field: "wobble", + Mapping: map[string][]string{ + "wobble": {"wobbly"}, + }, + }) + t.Run("CreateImportUpdateReadOk", func(t *testing.T) { resource.Test(t, resource.TestCase{ IsUnitTest: true, @@ -71,6 +86,16 @@ func TestAccOrganizationResource(t *testing.T) { statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("display_name"), knownvalue.StringExact("Example Organization New")), }, }, + // Add group and role sync + { + Config: cfg3.String(t), + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("group_sync").AtMapKey("field"), knownvalue.StringExact("wibble")), + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("group_sync").AtMapKey("mapping").AtMapKey("wibble").AtSliceIndex(0), knownvalue.StringExact("6e57187f-6543-46ab-a62c-a10065dd4314")), + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("role_sync").AtMapKey("field"), knownvalue.StringExact("wobble")), + statecheck.ExpectKnownValue("coderd_organization.test", tfjsonpath.New("role_sync").AtMapKey("mapping").AtMapKey("wobble").AtSliceIndex(0), knownvalue.StringExact("wobbly")), + }, + }, }, }) }) @@ -84,6 +109,9 @@ type testAccOrganizationResourceConfig struct { DisplayName *string Description *string Icon *string + + GroupSync *codersdk.GroupSyncSettings + RoleSync *codersdk.RoleSyncSettings } func (c testAccOrganizationResourceConfig) String(t *testing.T) string { @@ -99,6 +127,28 @@ resource "coderd_organization" "test" { display_name = {{orNull .DisplayName}} description = {{orNull .Description}} icon = {{orNull .Icon}} + + {{- if .GroupSync}} + group_sync { + field = "{{.GroupSync.Field}}" + mapping = { + {{- range $key, $value := .GroupSync.Mapping}} + {{$key}} = {{printf "%q" $value}} + {{- end}} + } + } + {{- end}} + + {{- if .RoleSync}} + role_sync { + field = "{{.RoleSync.Field}}" + mapping = { + {{- range $key, $value := .RoleSync.Mapping}} + {{$key}} = {{printf "%q" $value}} + {{- end}} + } + } + {{- end}} } ` funcMap := template.FuncMap{