diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d70949..a952aca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +## 1.7.2 (May 1, 2024) + +BUG FIXES: + +* resource/platform_permission: Make `name` attribute trigger resource replacement if changed. Issue: [#64](https://github.com/jfrog/terraform-provider-platform/issues/64) PR: [#66](https://github.com/jfrog/terraform-provider-platform/pull/66) + ## 1.7.1 (Apr 15, 2024) BUG FIXES: diff --git a/pkg/platform/resource_global_role.go b/pkg/platform/resource_global_role.go index 074c2a5..f166c80 100644 --- a/pkg/platform/resource_global_role.go +++ b/pkg/platform/resource_global_role.go @@ -14,6 +14,7 @@ import ( "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/setplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/types" utilfw "github.com/jfrog/terraform-provider-shared/util/fw" @@ -75,6 +76,9 @@ func (r *globalRoleResource) Schema(ctx context.Context, req resource.SchemaRequ Validators: []validator.String{ stringvalidator.LengthAtLeast(1), }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Name of the role", }, "description": schema.StringAttribute{ diff --git a/pkg/platform/resource_global_role_test.go b/pkg/platform/resource_global_role_test.go index ec943fd..4adb7bf 100644 --- a/pkg/platform/resource_global_role_test.go +++ b/pkg/platform/resource_global_role_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/jfrog/terraform-provider-shared/testutil" ) @@ -85,3 +86,62 @@ func TestAccGlobalRole_full(t *testing.T) { }, }) } + +func TestAccGlobalRole_name_change(t *testing.T) { + _, fqrn, roleName := testutil.MkNames("test-global-role", "platform_global_role") + + temp := ` + resource "platform_global_role" "{{ .name }}" { + name = "{{ .name }}" + description = "Test description" + type = "{{ .type }}" + environments = ["{{ .environment }}"] + actions = ["{{ .action }}"] + }` + + testData := map[string]string{ + "name": roleName, + "type": "CUSTOM_GLOBAL", + "environment": "DEV", + "action": "READ_REPOSITORY", + } + + config := testutil.ExecuteTemplate(roleName, temp, testData) + + nameChangeTemp := ` + resource "platform_global_role" "{{ .name }}" { + name = "foobar" + description = "Test description" + type = "{{ .type }}" + environments = ["{{ .environment }}"] + actions = ["{{ .action }}"] + }` + + updatedConfig := testutil.ExecuteTemplate(roleName, nameChangeTemp, testData) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "name", testData["name"]), + resource.TestCheckResourceAttr(fqrn, "type", testData["type"]), + resource.TestCheckResourceAttr(fqrn, "environments.#", "1"), + resource.TestCheckResourceAttr(fqrn, "environments.0", "DEV"), + resource.TestCheckResourceAttr(fqrn, "actions.#", "1"), + resource.TestCheckResourceAttr(fqrn, "actions.0", "READ_REPOSITORY"), + ), + }, + { + Config: updatedConfig, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(fqrn, plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + }, + }) +} diff --git a/pkg/platform/resource_permission.go b/pkg/platform/resource_permission.go index 17d1389..487992b 100644 --- a/pkg/platform/resource_permission.go +++ b/pkg/platform/resource_permission.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setdefault" "github.com/hashicorp/terraform-plugin-framework/resource/schema/setplanmodifier" + "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" @@ -142,6 +143,9 @@ func (r *permissionResource) Schema(ctx context.Context, req resource.SchemaRequ Validators: []validator.String{ stringvalidator.LengthBetween(1, 255), }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, Description: "Permission name", }, "artifact": schema.SingleNestedAttribute{ diff --git a/pkg/platform/resource_permission_test.go b/pkg/platform/resource_permission_test.go index d936110..7680a4c 100644 --- a/pkg/platform/resource_permission_test.go +++ b/pkg/platform/resource_permission_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/jfrog/terraform-provider-platform/pkg/platform" "github.com/jfrog/terraform-provider-shared/testutil" @@ -299,6 +300,154 @@ func TestAccPermission_full(t *testing.T) { }) } +func TestAccPermission_name_change(t *testing.T) { + _, fqrn, permissionName := testutil.MkNames("test-permission", "platform_permission") + _, _, userName := testutil.MkNames("test-user", "artifactory_managed_user") + _, _, groupName := testutil.MkNames("test-group", "artifactory_group") + _, _, repoName := testutil.MkNames("test-local-repo", "artifactory_local_generic_repository") + + temp := ` + resource "artifactory_managed_user" "{{ .userName }}" { + name = "{{ .userName }}" + email = "{{ .userName }}@tempurl.org" + password = "Password!123" + } + + resource "artifactory_group" "{{ .groupName }}" { + name = "{{ .groupName }}" + } + + resource "artifactory_local_generic_repository" "{{ .repoName }}" { + key = "{{ .repoName }}" + } + + resource "platform_permission" "{{ .name }}" { + name = "{{ .name }}" + + artifact = { + actions = { + users = [ + { + name = artifactory_managed_user.{{ .userName }}.name + permissions = ["READ"] + } + ] + + groups = [ + { + name = artifactory_group.{{ .groupName }}.name + permissions = ["READ"] + } + ] + } + + targets = [ + { + name = artifactory_local_generic_repository.{{ .repoName }}.key + include_patterns = ["**"] + exclude_patterns = ["{{ .excludePattern }}"] + } + ] + } + }` + + updatedTemp := ` + resource "artifactory_managed_user" "{{ .userName }}" { + name = "{{ .userName }}" + email = "{{ .userName }}@tempurl.org" + password = "Password!123" + } + + resource "artifactory_group" "{{ .groupName }}" { + name = "{{ .groupName }}" + } + + resource "artifactory_local_generic_repository" "{{ .repoName }}" { + key = "{{ .repoName }}" + } + + resource "platform_permission" "{{ .name }}" { + name = "foobar" + + artifact = { + actions = { + users = [ + { + name = artifactory_managed_user.{{ .userName }}.name + permissions = ["READ"] + } + ] + + groups = [ + { + name = artifactory_group.{{ .groupName }}.name + permissions = ["READ"] + } + ] + } + + targets = [ + { + name = artifactory_local_generic_repository.{{ .repoName }}.key + include_patterns = ["**"] + exclude_patterns = ["{{ .excludePattern }}"] + } + ] + } + }` + + testData := map[string]string{ + "name": permissionName, + "userName": userName, + "groupName": groupName, + "repoName": repoName, + "excludePattern": "foo", + } + + config := testutil.ExecuteTemplate(permissionName, temp, testData) + + updatedConfig := testutil.ExecuteTemplate(permissionName, updatedTemp, testData) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "jfrog/artifactory", + }, + }, + CheckDestroy: testAccCheckPermissionDestroy(fqrn), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "name", testData["name"]), + resource.TestCheckResourceAttr(fqrn, "artifact.actions.users.#", "1"), + resource.TestCheckResourceAttr(fqrn, "artifact.actions.users.0.name", testData["userName"]), + resource.TestCheckTypeSetElemAttr(fqrn, "artifact.actions.users.0.permissions.*", "READ"), + resource.TestCheckResourceAttr(fqrn, "artifact.actions.groups.#", "1"), + resource.TestCheckResourceAttr(fqrn, "artifact.actions.groups.0.name", testData["groupName"]), + resource.TestCheckTypeSetElemAttr(fqrn, "artifact.actions.groups.0.permissions.*", "READ"), + resource.TestCheckResourceAttr(fqrn, "artifact.targets.#", "1"), + resource.TestCheckResourceAttr(fqrn, "artifact.targets.0.name", testData["repoName"]), + resource.TestCheckResourceAttr(fqrn, "artifact.targets.0.include_patterns.#", "1"), + resource.TestCheckResourceAttr(fqrn, "artifact.targets.0.include_patterns.0", "**"), + resource.TestCheckResourceAttr(fqrn, "artifact.targets.0.exclude_patterns.#", "1"), + resource.TestCheckResourceAttr(fqrn, "artifact.targets.0.exclude_patterns.0", testData["excludePattern"]), + ), + }, + { + Config: updatedConfig, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(fqrn, plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + }, + }) +} + func testAccCheckPermissionDestroy(id string) func(*terraform.State) error { return func(s *terraform.State) error { client := TestProvider.(*platform.PlatformProvider).Meta.Client diff --git a/pkg/platform/resource_workers_service.go b/pkg/platform/resource_workers_service.go index 4b2918a..88ebac7 100644 --- a/pkg/platform/resource_workers_service.go +++ b/pkg/platform/resource_workers_service.go @@ -54,6 +54,7 @@ func (r *workersServiceResource) Schema(ctx context.Context, req resource.Schema Description: "The unique ID of the worker.", PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), }, }, "description": schema.StringAttribute{ diff --git a/pkg/platform/resource_workers_service_test.go b/pkg/platform/resource_workers_service_test.go index af6be64..7d39ee7 100644 --- a/pkg/platform/resource_workers_service_test.go +++ b/pkg/platform/resource_workers_service_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/jfrog/terraform-provider-platform/pkg/platform" "github.com/jfrog/terraform-provider-shared/testutil" @@ -159,6 +160,129 @@ func TestAccWorkersService_full(t *testing.T) { }) } +func TestAccWorkersService_name_change(t *testing.T) { + jfrogURL := os.Getenv("JFROG_URL") + if !strings.HasSuffix(jfrogURL, "jfrog.io") { + t.Skipf("JFROG_URL '%s' is not a cloud instance. Workers Service is only available on cloud.", jfrogURL) + } + + _, fqrn, workersServiceName := testutil.MkNames("test-workers-service-", "platform_workers_service") + _, _, repoKey := testutil.MkNames("test-repo-local-", "artifactory_local_generic_repository") + + temp := ` + resource "artifactory_local_generic_repository" "{{ .repoKey }}" { + key = "{{ .repoKey }}" + } + + resource "platform_workers_service" "{{ .key }}" { + key = "{{ .key }}" + enabled = {{ .enabled }} + description = "{{ .description }}" + source_code = "{{ .sourceCode }}" + action = "{{ .action }}" + + filter_criteria = { + artifact_filter_criteria = { + repo_keys = ["{{ .repoKey }}"] + } + } + + secrets = [ + { + key = "{{ .secretKey }}" + value = "{{ .secretValue }}" + }, + { + key = "{{ .secretKey2 }}" + value = "{{ .secretValue2 }}" + } + ] + }` + testData := map[string]string{ + "key": workersServiceName, + "enabled": "true", + "description": "Description", + "sourceCode": testSourceCode, + "action": "BEFORE_DOWNLOAD", + "repoKey": repoKey, + "secretKey": "test-secret-key", + "secretValue": "test-secret-value", + "secretKey2": "test-secret-key-2", + "secretValue2": "test-secret-value-2", + } + + config := testutil.ExecuteTemplate(workersServiceName, temp, testData) + + nameChangeTemp := ` + resource "artifactory_local_generic_repository" "{{ .repoKey }}" { + key = "{{ .repoKey }}" + } + + resource "platform_workers_service" "{{ .key }}" { + key = "foobar" + enabled = {{ .enabled }} + description = "{{ .description }}" + source_code = "{{ .sourceCode }}" + action = "{{ .action }}" + + filter_criteria = { + artifact_filter_criteria = { + repo_keys = ["{{ .repoKey }}"] + } + } + + secrets = [ + { + key = "{{ .secretKey }}" + value = "{{ .secretValue }}" + }, + { + key = "{{ .secretKey2 }}" + value = "{{ .secretValue2 }}" + } + ] + }` + updatedConfig := testutil.ExecuteTemplate(workersServiceName, nameChangeTemp, testData) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + ExternalProviders: map[string]resource.ExternalProvider{ + "artifactory": { + Source: "registry.terraform.io/jfrog/artifactory", + VersionConstraint: "9.9.0", + }, + }, + CheckDestroy: testAccCheckWorkersServiceDestroy(fqrn), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "key", workersServiceName), + resource.TestCheckResourceAttr(fqrn, "enabled", testData["enabled"]), + resource.TestCheckResourceAttr(fqrn, "description", testData["description"]), + resource.TestCheckResourceAttr(fqrn, "source_code", testData["sourceCode"]), + resource.TestCheckResourceAttr(fqrn, "filter_criteria.artifact_filter_criteria.repo_keys.#", "1"), + resource.TestCheckResourceAttr(fqrn, "filter_criteria.artifact_filter_criteria.repo_keys.0", testData["repoKey"]), + resource.TestCheckResourceAttr(fqrn, "secrets.#", "2"), + resource.TestCheckResourceAttr(fqrn, "secrets.0.key", testData["secretKey"]), + resource.TestCheckResourceAttr(fqrn, "secrets.0.value", testData["secretValue"]), + resource.TestCheckResourceAttr(fqrn, "secrets.1.key", testData["secretKey2"]), + resource.TestCheckResourceAttr(fqrn, "secrets.1.value", testData["secretValue2"]), + ), + }, + { + Config: updatedConfig, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(fqrn, plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + }, + }, + }) +} + func testAccCheckWorkersServiceDestroy(id string) func(*terraform.State) error { return func(s *terraform.State) error { client := TestProvider.(*platform.PlatformProvider).Meta.Client