From acc014d0671c123a43aef5a93cd54263853b414f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Tue, 23 Jan 2024 16:23:14 +0100 Subject: [PATCH 1/4] wip --- docs/data-sources/account_roles.md | 43 +++ docs/resources/account_role.md | 57 ++++ .../snowflake_account_roles/data-source.tf | 6 + .../snowflake_account_role/import.sh | 1 + .../snowflake_account_role/resource.tf | 4 + pkg/datasources/account_roles.go | 94 +++++++ .../account_roles_acceptance_test.go | 86 ++++++ pkg/datasources/role.go | 5 +- pkg/datasources/roles.go | 80 +----- .../TestAcc_AccountRoles_basic/test.tf | 23 ++ .../TestAcc_AccountRoles_basic/variables.tf | 19 ++ pkg/provider/provider.go | 2 + pkg/resources/account_role.go | 253 ++++++++++++++++++ pkg/resources/account_role_acceptance_test.go | 127 +++++++++ .../database_role_acceptance_test.go | 12 +- pkg/resources/dynamic_table.go | 4 +- pkg/resources/external_table.go | 24 +- pkg/resources/role.go | 146 +--------- pkg/resources/schema.go | 24 +- .../TestAcc_AccountRole_basic/test.tf | 4 + .../TestAcc_AccountRole_basic/variables.tf | 7 + .../TestAcc_AccountRole_updates/test.tf | 4 + .../TestAcc_AccountRole_updates/variables.tf | 7 + 23 files changed, 784 insertions(+), 248 deletions(-) create mode 100644 docs/data-sources/account_roles.md create mode 100644 docs/resources/account_role.md create mode 100644 examples/data-sources/snowflake_account_roles/data-source.tf create mode 100644 examples/resources/snowflake_account_role/import.sh create mode 100644 examples/resources/snowflake_account_role/resource.tf create mode 100644 pkg/datasources/account_roles.go create mode 100644 pkg/datasources/account_roles_acceptance_test.go create mode 100644 pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf create mode 100644 pkg/datasources/testdata/TestAcc_AccountRoles_basic/variables.tf create mode 100644 pkg/resources/account_role.go create mode 100644 pkg/resources/account_role_acceptance_test.go create mode 100644 pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf create mode 100644 pkg/resources/testdata/TestAcc_AccountRole_basic/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf create mode 100644 pkg/resources/testdata/TestAcc_AccountRole_updates/variables.tf diff --git a/docs/data-sources/account_roles.md b/docs/data-sources/account_roles.md new file mode 100644 index 0000000000..3eca8fb8cd --- /dev/null +++ b/docs/data-sources/account_roles.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_account_roles Data Source - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_account_roles (Data Source) + + + +## Example Usage + +```terraform +data "snowflake_account_roles" "all" { +} + +data "snowflake_account_roles" "by_pattern" { + pattern = "some_prefix_%" +} +``` + + +## Schema + +### Optional + +- `pattern` (String) Filters the command output by object name. + +### Read-Only + +- `id` (String) The ID of this resource. +- `roles` (List of Object) List of all the roles which you can view across your entire account, including the system-defined roles and any custom roles that exist. (see [below for nested schema](#nestedatt--roles)) + + +### Nested Schema for `roles` + +Read-Only: + +- `comment` (String) +- `name` (String) +- `owner` (String) diff --git a/docs/resources/account_role.md b/docs/resources/account_role.md new file mode 100644 index 0000000000..631a9e9b58 --- /dev/null +++ b/docs/resources/account_role.md @@ -0,0 +1,57 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_account_role Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_account_role (Resource) + + + +## Example Usage + +```terraform +resource "snowflake_account_role" "role" { + name = "role_name" + comment = "comment" +} +``` + + +## Schema + +### Required + +- `name` (String) + +### Optional + +- `comment` (String) +- `tag` (Block List, Deprecated) Definitions of a tag to associate with the resource. (see [below for nested schema](#nestedblock--tag)) + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `tag` + +Required: + +- `name` (String) Tag name, e.g. department. +- `value` (String) Tag value, e.g. marketing_info. + +Optional: + +- `database` (String) Name of the database that the tag was created in. +- `schema` (String) Name of the schema that the tag was created in. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_account_role.example roleName +``` diff --git a/examples/data-sources/snowflake_account_roles/data-source.tf b/examples/data-sources/snowflake_account_roles/data-source.tf new file mode 100644 index 0000000000..c5846c0fab --- /dev/null +++ b/examples/data-sources/snowflake_account_roles/data-source.tf @@ -0,0 +1,6 @@ +data "snowflake_account_roles" "all" { +} + +data "snowflake_account_roles" "by_pattern" { + pattern = "some_prefix_%" +} diff --git a/examples/resources/snowflake_account_role/import.sh b/examples/resources/snowflake_account_role/import.sh new file mode 100644 index 0000000000..96c1cd4276 --- /dev/null +++ b/examples/resources/snowflake_account_role/import.sh @@ -0,0 +1 @@ +terraform import snowflake_account_role.example roleName diff --git a/examples/resources/snowflake_account_role/resource.tf b/examples/resources/snowflake_account_role/resource.tf new file mode 100644 index 0000000000..740f215598 --- /dev/null +++ b/examples/resources/snowflake_account_role/resource.tf @@ -0,0 +1,4 @@ +resource "snowflake_account_role" "role" { + name = "role_name" + comment = "comment" +} diff --git a/pkg/datasources/account_roles.go b/pkg/datasources/account_roles.go new file mode 100644 index 0000000000..e44147860c --- /dev/null +++ b/pkg/datasources/account_roles.go @@ -0,0 +1,94 @@ +package datasources + +import ( + "context" + "database/sql" + "fmt" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var accountRolesSchema = map[string]*schema.Schema{ + "pattern": { + Type: schema.TypeString, + Optional: true, + Description: "Filters the command output by object name.", + }, + "roles": { + Type: schema.TypeList, + Computed: true, + Description: "List of all the roles which you can view across your entire account, including the system-defined roles and any custom roles that exist.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + Description: "Identifier for the role.", + }, + "comment": { + Type: schema.TypeString, + Computed: true, + Description: "The comment on the role", + }, + "owner": { + Type: schema.TypeString, + Computed: true, + Description: "The owner of the role", + }, + }, + }, + }, +} + +func AccountRoles() *schema.Resource { + return &schema.Resource{ + ReadContext: ReadAccountRoles, + Schema: accountRolesSchema, + } +} + +func ReadAccountRoles(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + req := sdk.NewShowRoleRequest() + if pattern, ok := d.GetOk("pattern"); ok { + req.WithLike(sdk.NewLikeRequest(pattern.(string))) + } + + roles, err := client.Roles.Show(ctx, req) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to show account roles", + Detail: fmt.Sprintf("Search pattern: %v, err: %s", d.Get("pattern").(string), err), + }, + } + } + + mappedRoles := make([]map[string]any, len(roles)) + for i, role := range roles { + mappedRoles[i] = map[string]any{ + "name": role.Name, + "comment": role.Comment, + "owner": role.Owner, + } + } + + if err := d.Set("roles", mappedRoles); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set roles", + Detail: fmt.Sprintf("Search pattern: %v, err: %s", d.Get("pattern").(string), err), + }, + } + } + + d.SetId("roles_read") + + return nil +} diff --git a/pkg/datasources/account_roles_acceptance_test.go b/pkg/datasources/account_roles_acceptance_test.go new file mode 100644 index 0000000000..0c0cbfc2f0 --- /dev/null +++ b/pkg/datasources/account_roles_acceptance_test.go @@ -0,0 +1,86 @@ +package datasources_test + +import ( + "fmt" + "strconv" + "strings" + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_AccountRoles_basic(t *testing.T) { + accountRoleNamePrefix := "account_roles_test_prefix_" + accountRoleName1 := accountRoleNamePrefix + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleName2 := accountRoleNamePrefix + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleName3 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + configVariables := config.Variables{ + "account_role_name_1": config.StringVariable(accountRoleName1), + "account_role_name_2": config.StringVariable(accountRoleName2), + "account_role_name_3": config.StringVariable(accountRoleName3), + "pattern": config.StringVariable(accountRoleNamePrefix + "%"), + "comment": config.StringVariable(comment), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_account_roles.test", "roles.#", "2"), + containsAccountRole(accountRoleName1, comment), + containsAccountRole(accountRoleName2, comment), + func(state *terraform.State) error { + err := containsAccountRole(accountRoleName3, comment)(state) + if err.Error() == fmt.Sprintf("role %s not found", accountRoleName3) { + return nil + } + return fmt.Errorf("expected %s not to be present", accountRoleName3) + }, + ), + }, + }, + }) +} + +func containsAccountRole(name string, comment string) func(s *terraform.State) error { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "snowflake_account_roles" { + continue + } + + iter, err := strconv.ParseInt(rs.Primary.Attributes["roles.#"], 10, 32) + if err != nil { + return err + } + + for i := 0; i < int(iter); i++ { + if rs.Primary.Attributes[fmt.Sprintf("roles.%d.name", i)] == name { + actualComment := rs.Primary.Attributes[fmt.Sprintf("roles.%d.comment", i)] + if actualComment != comment { + return fmt.Errorf("expected comment: %s, but got: %s", comment, actualComment) + } + + return nil + } + } + } + + return fmt.Errorf("role %s not found", name) + } +} diff --git a/pkg/datasources/role.go b/pkg/datasources/role.go index 6cf81c4271..4792af75c2 100644 --- a/pkg/datasources/role.go +++ b/pkg/datasources/role.go @@ -25,8 +25,9 @@ var roleSchema = map[string]*schema.Schema{ // Role Snowflake Role resource. func Role() *schema.Resource { return &schema.Resource{ - Read: ReadRole, - Schema: roleSchema, + Read: ReadRole, + Schema: roleSchema, + DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_roles instead.", Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/pkg/datasources/roles.go b/pkg/datasources/roles.go index 781bed6e73..4d94fd043e 100644 --- a/pkg/datasources/roles.go +++ b/pkg/datasources/roles.go @@ -1,87 +1,13 @@ package datasources import ( - "database/sql" - "errors" - "log" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var rolesSchema = map[string]*schema.Schema{ - "pattern": { - Type: schema.TypeString, - Optional: true, - Description: "Filters the command output by object name.", - }, - "roles": { - Type: schema.TypeList, - Computed: true, - Description: "List of all the roles which you can view across your entire account, including the system-defined roles and any custom roles that exist.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Computed: true, - Description: "Identifier for the role.", - }, - "comment": { - Type: schema.TypeString, - Computed: true, - Description: "The comment on the role", - }, - "owner": { - Type: schema.TypeString, - Computed: true, - Description: "The owner of the role", - }, - }, - }, - }, -} - -// Roles Snowflake Roles resource. func Roles() *schema.Resource { return &schema.Resource{ - Read: ReadRoles, - Schema: rolesSchema, - } -} - -// ReadRoles Reads the database metadata information. -func ReadRoles(d *schema.ResourceData, meta interface{}) error { - db := meta.(*sql.DB) - d.SetId("roles_read") - rolePattern := d.Get("pattern").(string) - - listRoles, err := snowflake.ListRoles(db, rolePattern) - if errors.Is(err, sql.ErrNoRows) { - log.Printf("[DEBUG] no roles found in account (%s)", d.Id()) - d.SetId("") - return nil - } else if err != nil { - log.Println("[DEBUG] failed to list roles") - d.SetId("") - return nil - } - - log.Printf("[DEBUG] list roles: %v", listRoles) - - roles := []map[string]interface{}{} - for _, role := range listRoles { - roleMap := map[string]interface{}{} - if !role.Name.Valid { - continue - } - roleMap["name"] = role.Name.String - roleMap["comment"] = role.Comment.String - roleMap["owner"] = role.Owner.String - roles = append(roles, roleMap) - } - - if err := d.Set("roles", roles); err != nil { - return err + ReadContext: ReadAccountRoles, + Schema: accountRolesSchema, + DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_roles instead.", } - return nil } diff --git a/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf b/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf new file mode 100644 index 0000000000..68d5033a36 --- /dev/null +++ b/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf @@ -0,0 +1,23 @@ +resource "snowflake_account_role" "test1" { + name = var.account_role_name_1 + comment = var.comment +} + +resource "snowflake_account_role" "test2" { + name = var.account_role_name_2 + comment = var.comment +} + +resource "snowflake_account_role" "test3" { + name = var.account_role_name_3 + comment = var.comment +} + +data "snowflake_account_roles" "test" { + depends_on = [ + snowflake_account_role.test1, + snowflake_account_role.test2, + snowflake_account_role.test3, + ] + pattern = var.pattern +} diff --git a/pkg/datasources/testdata/TestAcc_AccountRoles_basic/variables.tf b/pkg/datasources/testdata/TestAcc_AccountRoles_basic/variables.tf new file mode 100644 index 0000000000..c632a5527e --- /dev/null +++ b/pkg/datasources/testdata/TestAcc_AccountRoles_basic/variables.tf @@ -0,0 +1,19 @@ +variable "account_role_name_1" { + type = string +} + +variable "account_role_name_2" { + type = string +} + +variable "account_role_name_3" { + type = string +} + +variable "comment" { + type = string +} + +variable "pattern" { + type = string +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index ac77870a3c..54dafdffef 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -431,6 +431,7 @@ func getResources() map[string]*schema.Resource { "snowflake_account": resources.Account(), "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), "snowflake_account_parameter": resources.AccountParameter(), + "snowflake_account_role": resources.AccountRole(), "snowflake_alert": resources.Alert(), "snowflake_api_integration": resources.APIIntegration(), "snowflake_database": resources.Database(), @@ -518,6 +519,7 @@ func getDataSources() map[string]*schema.Resource { "snowflake_resource_monitors": datasources.ResourceMonitors(), "snowflake_role": datasources.Role(), "snowflake_roles": datasources.Roles(), + "snowflake_account_roles": datasources.AccountRoles(), "snowflake_row_access_policies": datasources.RowAccessPolicies(), "snowflake_schemas": datasources.Schemas(), "snowflake_sequences": datasources.Sequences(), diff --git a/pkg/resources/account_role.go b/pkg/resources/account_role.go new file mode 100644 index 0000000000..63cf3e7205 --- /dev/null +++ b/pkg/resources/account_role.go @@ -0,0 +1,253 @@ +package resources + +import ( + "context" + "database/sql" + "fmt" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var accountRoleSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + }, + "comment": { + Type: schema.TypeString, + Optional: true, + }, + "tag": tagReferenceSchema, +} + +func AccountRole() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateAccountRole, + ReadContext: ReadAccountRole, + DeleteContext: DeleteAccountRole, + UpdateContext: UpdateAccountRole, + + Schema: accountRoleSchema, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + name := d.Get("name").(string) + id, err := helpers.DecodeSnowflakeParameterID(name) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse account role name", + Detail: fmt.Sprintf("Account role name: %s, err: %s", name, err), + }, + } + } + req := sdk.NewCreateRoleRequest(id.(sdk.AccountObjectIdentifier)) + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(v.(string)) + } + + if _, ok := d.GetOk("tag"); ok { + req.WithTag(getPropertyTags(d, "tag")) + } + + err = client.Roles.Create(ctx, req) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to create account role", + Detail: fmt.Sprintf("Account role name: %s, err: %s", name, err), + }, + } + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + + return ReadAccountRole(ctx, d, meta) +} + +func ReadAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + accountRole, err := client.Roles.ShowByID(ctx, sdk.NewShowByIdRoleRequest(id)) + if err != nil { + if err.Error() == "object does not exist" { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Account role not found; marking it as removed", + Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to show account role by id", + Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), + }, + } + } + + if err := d.Set("name", accountRole.Name); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role name", + Detail: fmt.Sprintf("Account role name: %s, err: %s", accountRole.Name, err), + }, + } + } + + if err := d.Set("comment", accountRole.Comment); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role comment", + Detail: fmt.Sprintf("Account role name: %s, comment: %s, err: %s", accountRole.Name, accountRole.Comment, err), + }, + } + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + + return nil +} + +func UpdateAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + if d.HasChange("comment") { + if v, ok := d.GetOk("comment"); ok { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithSetComment(v.(string))) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role comment", + Detail: fmt.Sprintf("Account role name: %s, comment: %s, err: %s", id.FullyQualifiedName(), v, err), + }, + } + } + } else { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithUnsetComment(true)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to unset account role comment", + Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), + }, + } + } + } + } + + if d.HasChange("tag") { + unsetTags, setTags := GetTagsDiff(d, "tag") + + if len(unsetTags) > 0 { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithUnsetTags(unsetTags)) + if err != nil { + tagNames := make([]string, len(unsetTags)) + for i, v := range unsetTags { + tagNames[i] = v.FullyQualifiedName() + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to unset account role tags", + Detail: fmt.Sprintf("Account role name: %s, tags to unset: %v, err: %s", id.FullyQualifiedName(), tagNames, err), + }, + } + } + } + + if len(setTags) > 0 { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithSetTags(setTags)) + if err != nil { + tagNames := make([]string, len(unsetTags)) + for i, v := range unsetTags { + tagNames[i] = v.FullyQualifiedName() + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role tags", + Detail: fmt.Sprintf("Account role name: %s, tags to set: %v, err: %s", id.FullyQualifiedName(), tagNames, err), + }, + } + } + } + } + + if d.HasChange("name") { + _, newName := d.GetChange("name") + + newId, err := helpers.DecodeSnowflakeParameterID(newName.(string)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse account role name", + Detail: fmt.Sprintf("Account role name: %s, err: %s", newName, err), + }, + } + } + + err = client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithRenameTo(newId.(sdk.AccountObjectIdentifier))) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to rename account role name", + Detail: fmt.Sprintf("Previous account role name: %s, new account role name: %s, err: %s", id, newName, err), + }, + } + } + + d.SetId(helpers.EncodeSnowflakeID(newId)) + } + + return nil +} + +func DeleteAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + err := client.Roles.Drop(ctx, sdk.NewDropRoleRequest(id)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to drop account role", + Detail: fmt.Sprintf("Account role name: %s, err: %s", d.Id(), err), + }, + } + } + + d.SetId("") + + return nil +} diff --git a/pkg/resources/account_role_acceptance_test.go b/pkg/resources/account_role_acceptance_test.go new file mode 100644 index 0000000000..5d58cbcfc3 --- /dev/null +++ b/pkg/resources/account_role_acceptance_test.go @@ -0,0 +1,127 @@ +package resources_test + +import ( + "context" + "database/sql" + "fmt" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAcc_AccountRole_basic(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := map[string]config.Variable{ + "name": config.StringVariable(name), + "comment": config.StringVariable(comment), + } + resourceName := "snowflake_account_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRoleDestroy(name), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "comment", comment), + resource.TestCheckResourceAttr(resourceName, "id", name), + ), + }, + // test import + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_AccountRole_updates(t *testing.T) { + configVariables := func(name string, comment string) config.Variables { + return config.Variables{ + "name": config.StringVariable(name), + "comment": config.StringVariable(comment), + } + } + + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + newName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + NewComment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + resourceName := "snowflake_account_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRoleDestroy(name), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(name, comment), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "comment", comment), + resource.TestCheckResourceAttr(resourceName, "id", name), + ), + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(newName, NewComment), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", newName), + resource.TestCheckResourceAttr(resourceName, "comment", NewComment), + resource.TestCheckResourceAttr(resourceName, "id", newName), + ), + }, + // test import + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(newName, NewComment), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAccountRoleDestroy(accountRoleName string) func(state *terraform.State) error { + return func(state *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + client := sdk.NewClientFromDB(db) + for _, rs := range state.RootModule().Resources { + if rs.Type != "snowflake_account_role" { + continue + } + ctx := context.Background() + id := sdk.NewAccountObjectIdentifier(rs.Primary.Attributes["name"]) + _, err := client.Roles.ShowByID(ctx, sdk.NewShowByIdRoleRequest(id)) + if err == nil { + return fmt.Errorf("account role %v still exists", accountRoleName) + } + } + return nil + } +} diff --git a/pkg/resources/database_role_acceptance_test.go b/pkg/resources/database_role_acceptance_test.go index 071ef0f9b1..9729a449eb 100644 --- a/pkg/resources/database_role_acceptance_test.go +++ b/pkg/resources/database_role_acceptance_test.go @@ -10,14 +10,12 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) -var ( - resourceName = "snowflake_database_role.test_db_role" - dbRoleName = "db_role_" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - comment = "dummy" - comment2 = "test comment" -) - func TestAcc_DatabaseRole(t *testing.T) { + resourceName := "snowflake_database_role.test_db_role" + dbRoleName := "db_role_" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := "dummy" + comment2 := "test comment" + resource.Test(t, resource.TestCase{ Providers: acc.TestAccProviders(), PreCheck: func() { acc.TestAccPreCheck(t) }, diff --git a/pkg/resources/dynamic_table.go b/pkg/resources/dynamic_table.go index 49446f14f4..eba737df17 100644 --- a/pkg/resources/dynamic_table.go +++ b/pkg/resources/dynamic_table.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var dynamicTableShema = map[string]*schema.Schema{ +var dynamicTableSchema = map[string]*schema.Schema{ "or_replace": { Type: schema.TypeBool, Optional: true, @@ -145,7 +145,7 @@ func DynamicTable() *schema.Resource { Update: UpdateDynamicTable, Delete: DeleteDynamicTable, - Schema: dynamicTableShema, + Schema: dynamicTableSchema, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/pkg/resources/external_table.go b/pkg/resources/external_table.go index f9d73c4237..5b1235ca16 100644 --- a/pkg/resources/external_table.go +++ b/pkg/resources/external_table.go @@ -285,18 +285,22 @@ func UpdateExternalTable(d *schema.ResourceData, meta any) error { if d.HasChange("tag") { unsetTags, setTags := GetTagsDiff(d, "tag") - err := client.ExternalTables.Alter(ctx, sdk.NewAlterExternalTableRequest(id).WithUnsetTag(unsetTags)) - if err != nil { - return fmt.Errorf("error setting tags on %v, err = %w", d.Id(), err) + if len(unsetTags) > 0 { + err := client.ExternalTables.Alter(ctx, sdk.NewAlterExternalTableRequest(id).WithUnsetTag(unsetTags)) + if err != nil { + return fmt.Errorf("error setting tags on %v, err = %w", d.Id(), err) + } } - tagAssociationRequests := make([]*sdk.TagAssociationRequest, len(setTags)) - for i, t := range setTags { - tagAssociationRequests[i] = sdk.NewTagAssociationRequest(t.Name, t.Value) - } - err = client.ExternalTables.Alter(ctx, sdk.NewAlterExternalTableRequest(id).WithSetTag(tagAssociationRequests)) - if err != nil { - return fmt.Errorf("error setting tags on %v, err = %w", d.Id(), err) + if len(setTags) > 0 { + tagAssociationRequests := make([]*sdk.TagAssociationRequest, len(setTags)) + for i, t := range setTags { + tagAssociationRequests[i] = sdk.NewTagAssociationRequest(t.Name, t.Value) + } + err := client.ExternalTables.Alter(ctx, sdk.NewAlterExternalTableRequest(id).WithSetTag(tagAssociationRequests)) + if err != nil { + return fmt.Errorf("error setting tags on %v, err = %w", d.Id(), err) + } } } diff --git a/pkg/resources/role.go b/pkg/resources/role.go index 60268121d2..d63266e781 100644 --- a/pkg/resources/role.go +++ b/pkg/resources/role.go @@ -1,154 +1,20 @@ package resources import ( - "database/sql" - "errors" - "log" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var roleSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: func(val interface{}, key string) ([]string, []error) { - additionalCharsToIgnoreValidation := []string{".", " ", ":", "(", ")"} - return sdk.ValidateIdentifier(val, additionalCharsToIgnoreValidation) - }, - }, - "comment": { - Type: schema.TypeString, - Optional: true, - // TODO validation - }, - "tag": tagReferenceSchema, -} - func Role() *schema.Resource { return &schema.Resource{ - Create: CreateRole, - Read: ReadRole, - Delete: DeleteRole, - Update: UpdateRole, + CreateContext: CreateAccountRole, + ReadContext: ReadAccountRole, + DeleteContext: DeleteAccountRole, + UpdateContext: UpdateAccountRole, + DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_role instead.", - Schema: roleSchema, + Schema: accountRoleSchema, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, } } - -func CreateRole(d *schema.ResourceData, meta interface{}) error { - name := d.Get("name").(string) - db := meta.(*sql.DB) - builder := snowflake.NewRoleBuilder(db, name) - if v, ok := d.GetOk("comment"); ok { - builder.WithComment(v.(string)) - } - if v, ok := d.GetOk("tag"); ok { - tags := getTags(v) - builder.WithTags(tags.toSnowflakeTagValues()) - } - err := builder.Create() - if err != nil { - return err - } - d.SetId(name) - return ReadRole(d, meta) -} - -func ReadRole(d *schema.ResourceData, meta interface{}) error { - db := meta.(*sql.DB) - id := d.Id() - // If the name is not set (such as during import) then use the id - name := d.Get("name").(string) - if name == "" { - name = id - } - - builder := snowflake.NewRoleBuilder(db, name) - role, err := builder.Show() - if errors.Is(err, sql.ErrNoRows) { - log.Printf("[WARN] role (%s) not found", name) - d.SetId("") - return nil - } else if err != nil { - return err - } - if err := d.Set("name", role.Name.String); err != nil { - return err - } - if err := d.Set("comment", role.Comment.String); err != nil { - return err - } - return nil -} - -func UpdateRole(d *schema.ResourceData, meta interface{}) error { - db := meta.(*sql.DB) - name := d.Get("name").(string) - builder := snowflake.NewRoleBuilder(db, name) - - if d.HasChange("name") { - o, n := d.GetChange("name") - builder.WithName(o.(string)) - err := builder.Rename(n.(string)) - if err != nil { - return err - } - builder.WithName(n.(string)) - } - - if d.HasChange("comment") { - o, n := d.GetChange("comment") - if n == nil || n.(string) == "" { - builder.WithComment(o.(string)) - err := builder.UnsetComment() - if err != nil { - return err - } - } else { - err := builder.SetComment(n.(string)) - if err != nil { - return err - } - } - } - - if d.HasChange("tag") { - o, n := d.GetChange("tag") - removed, added, changed := getTags(o).diffs(getTags(n)) - for _, tA := range removed { - err := builder.UnsetTag(tA.toSnowflakeTagValue()) - if err != nil { - return err - } - } - for _, tA := range added { - err := builder.SetTag(tA.toSnowflakeTagValue()) - if err != nil { - return err - } - } - for _, tA := range changed { - err := builder.ChangeTag(tA.toSnowflakeTagValue()) - if err != nil { - return err - } - } - } - - return nil -} - -func DeleteRole(d *schema.ResourceData, meta interface{}) error { - db := meta.(*sql.DB) - name := d.Get("name").(string) - builder := snowflake.NewRoleBuilder(db, name) - err := builder.Drop() - return err -} diff --git a/pkg/resources/schema.go b/pkg/resources/schema.go index bdd30aa3ca..7dc7474541 100644 --- a/pkg/resources/schema.go +++ b/pkg/resources/schema.go @@ -231,18 +231,22 @@ func UpdateSchema(d *schema.ResourceData, meta interface{}) error { if d.HasChange("tag") { unsetTags, setTags := GetTagsDiff(d, "tag") - err := client.Schemas.Alter(ctx, id, &sdk.AlterSchemaOptions{ - UnsetTag: unsetTags, - }) - if err != nil { - return fmt.Errorf("error occurred when dropping tags on %v, err = %w", d.Id(), err) + if len(unsetTags) > 0 { + err := client.Schemas.Alter(ctx, id, &sdk.AlterSchemaOptions{ + UnsetTag: unsetTags, + }) + if err != nil { + return fmt.Errorf("error occurred when dropping tags on %v, err = %w", d.Id(), err) + } } - err = client.Schemas.Alter(ctx, id, &sdk.AlterSchemaOptions{ - SetTag: setTags, - }) - if err != nil { - return fmt.Errorf("error occurred when setting tags on %v, err = %w", d.Id(), err) + if len(setTags) > 0 { + err := client.Schemas.Alter(ctx, id, &sdk.AlterSchemaOptions{ + SetTag: setTags, + }) + if err != nil { + return fmt.Errorf("error occurred when setting tags on %v, err = %w", d.Id(), err) + } } } diff --git a/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf b/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf new file mode 100644 index 0000000000..1aa83161f4 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf @@ -0,0 +1,4 @@ +resource "snowflake_account_role" "test" { + name = var.name + comment = var.comment +} diff --git a/pkg/resources/testdata/TestAcc_AccountRole_basic/variables.tf b/pkg/resources/testdata/TestAcc_AccountRole_basic/variables.tf new file mode 100644 index 0000000000..821eeebe89 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_AccountRole_basic/variables.tf @@ -0,0 +1,7 @@ +variable "name" { + type = string +} + +variable "comment" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf b/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf new file mode 100644 index 0000000000..1aa83161f4 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf @@ -0,0 +1,4 @@ +resource "snowflake_account_role" "test" { + name = var.name + comment = var.comment +} diff --git a/pkg/resources/testdata/TestAcc_AccountRole_updates/variables.tf b/pkg/resources/testdata/TestAcc_AccountRole_updates/variables.tf new file mode 100644 index 0000000000..821eeebe89 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_AccountRole_updates/variables.tf @@ -0,0 +1,7 @@ +variable "name" { + type = string +} + +variable "comment" { + type = string +} From 53c37c70f6c3e73a109d100a5886335c7f111916 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 25 Jan 2024 12:41:46 +0100 Subject: [PATCH 2/4] changes after review --- pkg/resources/account_role_acceptance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/account_role_acceptance_test.go b/pkg/resources/account_role_acceptance_test.go index 5d58cbcfc3..184a1d64f5 100644 --- a/pkg/resources/account_role_acceptance_test.go +++ b/pkg/resources/account_role_acceptance_test.go @@ -66,7 +66,7 @@ func TestAcc_AccountRole_updates(t *testing.T) { name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) newName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - NewComment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + NewComment := "updated comment with 'single' quotes" resourceName := "snowflake_account_role.test" resource.Test(t, resource.TestCase{ From 003a6f2f7e700cb1d3f601d5a7a41c791d756df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 25 Jan 2024 12:58:05 +0100 Subject: [PATCH 3/4] changes after review --- docs/data-sources/account_roles.md | 43 --- docs/resources/account_role.md | 57 ---- .../snowflake_account_roles/data-source.tf | 6 - .../snowflake_account_role/import.sh | 1 - .../snowflake_account_role/resource.tf | 4 - pkg/datasources/account_roles.go | 94 ------- .../account_roles_acceptance_test.go | 86 ------ pkg/datasources/role.go | 2 +- pkg/datasources/roles.go | 87 +++++- pkg/datasources/roles_acceptance_test.go | 80 ++++++ .../TestAcc_AccountRoles_basic/test.tf | 14 +- pkg/provider/provider.go | 2 - pkg/resources/account_role.go | 253 ------------------ pkg/resources/account_role_acceptance_test.go | 127 --------- pkg/resources/role.go | 233 +++++++++++++++- pkg/resources/role_acceptance_test.go | 116 ++++++++ .../TestAcc_AccountRole_basic/test.tf | 2 +- .../TestAcc_AccountRole_updates/test.tf | 2 +- 18 files changed, 518 insertions(+), 691 deletions(-) delete mode 100644 docs/data-sources/account_roles.md delete mode 100644 docs/resources/account_role.md delete mode 100644 examples/data-sources/snowflake_account_roles/data-source.tf delete mode 100644 examples/resources/snowflake_account_role/import.sh delete mode 100644 examples/resources/snowflake_account_role/resource.tf delete mode 100644 pkg/datasources/account_roles.go delete mode 100644 pkg/datasources/account_roles_acceptance_test.go delete mode 100644 pkg/resources/account_role.go delete mode 100644 pkg/resources/account_role_acceptance_test.go diff --git a/docs/data-sources/account_roles.md b/docs/data-sources/account_roles.md deleted file mode 100644 index 3eca8fb8cd..0000000000 --- a/docs/data-sources/account_roles.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "snowflake_account_roles Data Source - terraform-provider-snowflake" -subcategory: "" -description: |- - ---- - -# snowflake_account_roles (Data Source) - - - -## Example Usage - -```terraform -data "snowflake_account_roles" "all" { -} - -data "snowflake_account_roles" "by_pattern" { - pattern = "some_prefix_%" -} -``` - - -## Schema - -### Optional - -- `pattern` (String) Filters the command output by object name. - -### Read-Only - -- `id` (String) The ID of this resource. -- `roles` (List of Object) List of all the roles which you can view across your entire account, including the system-defined roles and any custom roles that exist. (see [below for nested schema](#nestedatt--roles)) - - -### Nested Schema for `roles` - -Read-Only: - -- `comment` (String) -- `name` (String) -- `owner` (String) diff --git a/docs/resources/account_role.md b/docs/resources/account_role.md deleted file mode 100644 index 631a9e9b58..0000000000 --- a/docs/resources/account_role.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -# generated by https://github.com/hashicorp/terraform-plugin-docs -page_title: "snowflake_account_role Resource - terraform-provider-snowflake" -subcategory: "" -description: |- - ---- - -# snowflake_account_role (Resource) - - - -## Example Usage - -```terraform -resource "snowflake_account_role" "role" { - name = "role_name" - comment = "comment" -} -``` - - -## Schema - -### Required - -- `name` (String) - -### Optional - -- `comment` (String) -- `tag` (Block List, Deprecated) Definitions of a tag to associate with the resource. (see [below for nested schema](#nestedblock--tag)) - -### Read-Only - -- `id` (String) The ID of this resource. - - -### Nested Schema for `tag` - -Required: - -- `name` (String) Tag name, e.g. department. -- `value` (String) Tag value, e.g. marketing_info. - -Optional: - -- `database` (String) Name of the database that the tag was created in. -- `schema` (String) Name of the schema that the tag was created in. - -## Import - -Import is supported using the following syntax: - -```shell -terraform import snowflake_account_role.example roleName -``` diff --git a/examples/data-sources/snowflake_account_roles/data-source.tf b/examples/data-sources/snowflake_account_roles/data-source.tf deleted file mode 100644 index c5846c0fab..0000000000 --- a/examples/data-sources/snowflake_account_roles/data-source.tf +++ /dev/null @@ -1,6 +0,0 @@ -data "snowflake_account_roles" "all" { -} - -data "snowflake_account_roles" "by_pattern" { - pattern = "some_prefix_%" -} diff --git a/examples/resources/snowflake_account_role/import.sh b/examples/resources/snowflake_account_role/import.sh deleted file mode 100644 index 96c1cd4276..0000000000 --- a/examples/resources/snowflake_account_role/import.sh +++ /dev/null @@ -1 +0,0 @@ -terraform import snowflake_account_role.example roleName diff --git a/examples/resources/snowflake_account_role/resource.tf b/examples/resources/snowflake_account_role/resource.tf deleted file mode 100644 index 740f215598..0000000000 --- a/examples/resources/snowflake_account_role/resource.tf +++ /dev/null @@ -1,4 +0,0 @@ -resource "snowflake_account_role" "role" { - name = "role_name" - comment = "comment" -} diff --git a/pkg/datasources/account_roles.go b/pkg/datasources/account_roles.go deleted file mode 100644 index e44147860c..0000000000 --- a/pkg/datasources/account_roles.go +++ /dev/null @@ -1,94 +0,0 @@ -package datasources - -import ( - "context" - "database/sql" - "fmt" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -var accountRolesSchema = map[string]*schema.Schema{ - "pattern": { - Type: schema.TypeString, - Optional: true, - Description: "Filters the command output by object name.", - }, - "roles": { - Type: schema.TypeList, - Computed: true, - Description: "List of all the roles which you can view across your entire account, including the system-defined roles and any custom roles that exist.", - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Computed: true, - Description: "Identifier for the role.", - }, - "comment": { - Type: schema.TypeString, - Computed: true, - Description: "The comment on the role", - }, - "owner": { - Type: schema.TypeString, - Computed: true, - Description: "The owner of the role", - }, - }, - }, - }, -} - -func AccountRoles() *schema.Resource { - return &schema.Resource{ - ReadContext: ReadAccountRoles, - Schema: accountRolesSchema, - } -} - -func ReadAccountRoles(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) - - req := sdk.NewShowRoleRequest() - if pattern, ok := d.GetOk("pattern"); ok { - req.WithLike(sdk.NewLikeRequest(pattern.(string))) - } - - roles, err := client.Roles.Show(ctx, req) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to show account roles", - Detail: fmt.Sprintf("Search pattern: %v, err: %s", d.Get("pattern").(string), err), - }, - } - } - - mappedRoles := make([]map[string]any, len(roles)) - for i, role := range roles { - mappedRoles[i] = map[string]any{ - "name": role.Name, - "comment": role.Comment, - "owner": role.Owner, - } - } - - if err := d.Set("roles", mappedRoles); err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set roles", - Detail: fmt.Sprintf("Search pattern: %v, err: %s", d.Get("pattern").(string), err), - }, - } - } - - d.SetId("roles_read") - - return nil -} diff --git a/pkg/datasources/account_roles_acceptance_test.go b/pkg/datasources/account_roles_acceptance_test.go deleted file mode 100644 index 0c0cbfc2f0..0000000000 --- a/pkg/datasources/account_roles_acceptance_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package datasources_test - -import ( - "fmt" - "strconv" - "strings" - "testing" - - acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - "github.com/hashicorp/terraform-plugin-testing/config" - "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-testing/tfversion" - - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAcc_AccountRoles_basic(t *testing.T) { - accountRoleNamePrefix := "account_roles_test_prefix_" - accountRoleName1 := accountRoleNamePrefix + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - accountRoleName2 := accountRoleNamePrefix + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - accountRoleName3 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - - configVariables := config.Variables{ - "account_role_name_1": config.StringVariable(accountRoleName1), - "account_role_name_2": config.StringVariable(accountRoleName2), - "account_role_name_3": config.StringVariable(accountRoleName3), - "pattern": config.StringVariable(accountRoleNamePrefix + "%"), - "comment": config.StringVariable(comment), - } - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - Steps: []resource.TestStep{ - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("data.snowflake_account_roles.test", "roles.#", "2"), - containsAccountRole(accountRoleName1, comment), - containsAccountRole(accountRoleName2, comment), - func(state *terraform.State) error { - err := containsAccountRole(accountRoleName3, comment)(state) - if err.Error() == fmt.Sprintf("role %s not found", accountRoleName3) { - return nil - } - return fmt.Errorf("expected %s not to be present", accountRoleName3) - }, - ), - }, - }, - }) -} - -func containsAccountRole(name string, comment string) func(s *terraform.State) error { - return func(s *terraform.State) error { - for _, rs := range s.RootModule().Resources { - if rs.Type != "snowflake_account_roles" { - continue - } - - iter, err := strconv.ParseInt(rs.Primary.Attributes["roles.#"], 10, 32) - if err != nil { - return err - } - - for i := 0; i < int(iter); i++ { - if rs.Primary.Attributes[fmt.Sprintf("roles.%d.name", i)] == name { - actualComment := rs.Primary.Attributes[fmt.Sprintf("roles.%d.comment", i)] - if actualComment != comment { - return fmt.Errorf("expected comment: %s, but got: %s", comment, actualComment) - } - - return nil - } - } - } - - return fmt.Errorf("role %s not found", name) - } -} diff --git a/pkg/datasources/role.go b/pkg/datasources/role.go index 4792af75c2..815624ee4a 100644 --- a/pkg/datasources/role.go +++ b/pkg/datasources/role.go @@ -27,7 +27,7 @@ func Role() *schema.Resource { return &schema.Resource{ Read: ReadRole, Schema: roleSchema, - DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_roles instead.", + DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_roles instead.", Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, diff --git a/pkg/datasources/roles.go b/pkg/datasources/roles.go index 4d94fd043e..e9bdd97c6d 100644 --- a/pkg/datasources/roles.go +++ b/pkg/datasources/roles.go @@ -1,13 +1,94 @@ package datasources import ( + "context" + "database/sql" + "fmt" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var accountRolesSchema = map[string]*schema.Schema{ + "pattern": { + Type: schema.TypeString, + Optional: true, + Description: "Filters the command output by object name.", + }, + "roles": { + Type: schema.TypeList, + Computed: true, + Description: "List of all the roles which you can view across your entire account, including the system-defined roles and any custom roles that exist.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + Description: "Identifier for the role.", + }, + "comment": { + Type: schema.TypeString, + Computed: true, + Description: "The comment on the role", + }, + "owner": { + Type: schema.TypeString, + Computed: true, + Description: "The owner of the role", + }, + }, + }, + }, +} + func Roles() *schema.Resource { return &schema.Resource{ - ReadContext: ReadAccountRoles, - Schema: accountRolesSchema, - DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_roles instead.", + ReadContext: ReadAccountRoles, + Schema: accountRolesSchema, + } +} + +func ReadAccountRoles(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + req := sdk.NewShowRoleRequest() + if pattern, ok := d.GetOk("pattern"); ok { + req.WithLike(sdk.NewLikeRequest(pattern.(string))) } + + roles, err := client.Roles.Show(ctx, req) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to show account roles", + Detail: fmt.Sprintf("Search pattern: %v, err: %s", d.Get("pattern").(string), err), + }, + } + } + + mappedRoles := make([]map[string]any, len(roles)) + for i, role := range roles { + mappedRoles[i] = map[string]any{ + "name": role.Name, + "comment": role.Comment, + "owner": role.Owner, + } + } + + if err := d.Set("roles", mappedRoles); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set roles", + Detail: fmt.Sprintf("Search pattern: %v, err: %s", d.Get("pattern").(string), err), + }, + } + } + + d.SetId("roles_read") + + return nil } diff --git a/pkg/datasources/roles_acceptance_test.go b/pkg/datasources/roles_acceptance_test.go index 9224ecd35c..a6dd78148a 100644 --- a/pkg/datasources/roles_acceptance_test.go +++ b/pkg/datasources/roles_acceptance_test.go @@ -2,9 +2,15 @@ package datasources_test import ( "fmt" + "strconv" "strings" "testing" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) @@ -43,6 +49,80 @@ func TestAcc_Roles(t *testing.T) { }) } +func TestAcc_AccountRoles_basic(t *testing.T) { + accountRoleNamePrefix := "account_roles_test_prefix_" + accountRoleName1 := accountRoleNamePrefix + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleName2 := accountRoleNamePrefix + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleName3 := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + configVariables := config.Variables{ + "account_role_name_1": config.StringVariable(accountRoleName1), + "account_role_name_2": config.StringVariable(accountRoleName2), + "account_role_name_3": config.StringVariable(accountRoleName3), + "pattern": config.StringVariable(accountRoleNamePrefix + "%"), + "comment": config.StringVariable(comment), + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_roles.test", "roles.#", "2"), + containsAccountRole(accountRoleName1, comment), + containsAccountRole(accountRoleName2, comment), + doesntContainAccountRole(accountRoleName3, comment), + ), + }, + }, + }) +} + +func doesntContainAccountRole(name string, comment string) func(s *terraform.State) error { + return func(state *terraform.State) error { + err := containsAccountRole(name, comment)(state) + if err.Error() == fmt.Sprintf("role %s not found", name) { + return nil + } + return fmt.Errorf("expected %s not to be present", name) + } +} + +func containsAccountRole(name string, comment string) func(s *terraform.State) error { + return func(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "snowflake_roles" { + continue + } + + iter, err := strconv.ParseInt(rs.Primary.Attributes["roles.#"], 10, 32) + if err != nil { + return err + } + + for i := 0; i < int(iter); i++ { + if rs.Primary.Attributes[fmt.Sprintf("roles.%d.name", i)] == name { + actualComment := rs.Primary.Attributes[fmt.Sprintf("roles.%d.comment", i)] + if actualComment != comment { + return fmt.Errorf("expected comment: %s, but got: %s", comment, actualComment) + } + + return nil + } + } + } + + return fmt.Errorf("role %s not found", name) + } +} + func roles(roleName, roleName2, comment string) string { return fmt.Sprintf(` resource snowflake_role "test_role" { diff --git a/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf b/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf index 68d5033a36..00ebde112b 100644 --- a/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf +++ b/pkg/datasources/testdata/TestAcc_AccountRoles_basic/test.tf @@ -1,23 +1,23 @@ -resource "snowflake_account_role" "test1" { +resource "snowflake_role" "test1" { name = var.account_role_name_1 comment = var.comment } -resource "snowflake_account_role" "test2" { +resource "snowflake_role" "test2" { name = var.account_role_name_2 comment = var.comment } -resource "snowflake_account_role" "test3" { +resource "snowflake_role" "test3" { name = var.account_role_name_3 comment = var.comment } -data "snowflake_account_roles" "test" { +data "snowflake_roles" "test" { depends_on = [ - snowflake_account_role.test1, - snowflake_account_role.test2, - snowflake_account_role.test3, + snowflake_role.test1, + snowflake_role.test2, + snowflake_role.test3, ] pattern = var.pattern } diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 54dafdffef..ac77870a3c 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -431,7 +431,6 @@ func getResources() map[string]*schema.Resource { "snowflake_account": resources.Account(), "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), "snowflake_account_parameter": resources.AccountParameter(), - "snowflake_account_role": resources.AccountRole(), "snowflake_alert": resources.Alert(), "snowflake_api_integration": resources.APIIntegration(), "snowflake_database": resources.Database(), @@ -519,7 +518,6 @@ func getDataSources() map[string]*schema.Resource { "snowflake_resource_monitors": datasources.ResourceMonitors(), "snowflake_role": datasources.Role(), "snowflake_roles": datasources.Roles(), - "snowflake_account_roles": datasources.AccountRoles(), "snowflake_row_access_policies": datasources.RowAccessPolicies(), "snowflake_schemas": datasources.Schemas(), "snowflake_sequences": datasources.Sequences(), diff --git a/pkg/resources/account_role.go b/pkg/resources/account_role.go deleted file mode 100644 index 63cf3e7205..0000000000 --- a/pkg/resources/account_role.go +++ /dev/null @@ -1,253 +0,0 @@ -package resources - -import ( - "context" - "database/sql" - "fmt" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" -) - -var accountRoleSchema = map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), - }, - "comment": { - Type: schema.TypeString, - Optional: true, - }, - "tag": tagReferenceSchema, -} - -func AccountRole() *schema.Resource { - return &schema.Resource{ - CreateContext: CreateAccountRole, - ReadContext: ReadAccountRole, - DeleteContext: DeleteAccountRole, - UpdateContext: UpdateAccountRole, - - Schema: accountRoleSchema, - Importer: &schema.ResourceImporter{ - StateContext: schema.ImportStatePassthroughContext, - }, - } -} - -func CreateAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) - - name := d.Get("name").(string) - id, err := helpers.DecodeSnowflakeParameterID(name) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to parse account role name", - Detail: fmt.Sprintf("Account role name: %s, err: %s", name, err), - }, - } - } - req := sdk.NewCreateRoleRequest(id.(sdk.AccountObjectIdentifier)) - - if v, ok := d.GetOk("comment"); ok { - req.WithComment(v.(string)) - } - - if _, ok := d.GetOk("tag"); ok { - req.WithTag(getPropertyTags(d, "tag")) - } - - err = client.Roles.Create(ctx, req) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to create account role", - Detail: fmt.Sprintf("Account role name: %s, err: %s", name, err), - }, - } - } - - d.SetId(helpers.EncodeSnowflakeID(id)) - - return ReadAccountRole(ctx, d, meta) -} - -func ReadAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - - accountRole, err := client.Roles.ShowByID(ctx, sdk.NewShowByIdRoleRequest(id)) - if err != nil { - if err.Error() == "object does not exist" { - d.SetId("") - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "Account role not found; marking it as removed", - Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), - }, - } - } - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to show account role by id", - Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), - }, - } - } - - if err := d.Set("name", accountRole.Name); err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set account role name", - Detail: fmt.Sprintf("Account role name: %s, err: %s", accountRole.Name, err), - }, - } - } - - if err := d.Set("comment", accountRole.Comment); err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set account role comment", - Detail: fmt.Sprintf("Account role name: %s, comment: %s, err: %s", accountRole.Name, accountRole.Comment, err), - }, - } - } - - d.SetId(helpers.EncodeSnowflakeID(id)) - - return nil -} - -func UpdateAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - - if d.HasChange("comment") { - if v, ok := d.GetOk("comment"); ok { - err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithSetComment(v.(string))) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set account role comment", - Detail: fmt.Sprintf("Account role name: %s, comment: %s, err: %s", id.FullyQualifiedName(), v, err), - }, - } - } - } else { - err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithUnsetComment(true)) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to unset account role comment", - Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), - }, - } - } - } - } - - if d.HasChange("tag") { - unsetTags, setTags := GetTagsDiff(d, "tag") - - if len(unsetTags) > 0 { - err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithUnsetTags(unsetTags)) - if err != nil { - tagNames := make([]string, len(unsetTags)) - for i, v := range unsetTags { - tagNames[i] = v.FullyQualifiedName() - } - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to unset account role tags", - Detail: fmt.Sprintf("Account role name: %s, tags to unset: %v, err: %s", id.FullyQualifiedName(), tagNames, err), - }, - } - } - } - - if len(setTags) > 0 { - err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithSetTags(setTags)) - if err != nil { - tagNames := make([]string, len(unsetTags)) - for i, v := range unsetTags { - tagNames[i] = v.FullyQualifiedName() - } - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to set account role tags", - Detail: fmt.Sprintf("Account role name: %s, tags to set: %v, err: %s", id.FullyQualifiedName(), tagNames, err), - }, - } - } - } - } - - if d.HasChange("name") { - _, newName := d.GetChange("name") - - newId, err := helpers.DecodeSnowflakeParameterID(newName.(string)) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to parse account role name", - Detail: fmt.Sprintf("Account role name: %s, err: %s", newName, err), - }, - } - } - - err = client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithRenameTo(newId.(sdk.AccountObjectIdentifier))) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to rename account role name", - Detail: fmt.Sprintf("Previous account role name: %s, new account role name: %s, err: %s", id, newName, err), - }, - } - } - - d.SetId(helpers.EncodeSnowflakeID(newId)) - } - - return nil -} - -func DeleteAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - db := meta.(*sql.DB) - client := sdk.NewClientFromDB(db) - id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) - - err := client.Roles.Drop(ctx, sdk.NewDropRoleRequest(id)) - if err != nil { - return diag.Diagnostics{ - diag.Diagnostic{ - Severity: diag.Error, - Summary: "Failed to drop account role", - Detail: fmt.Sprintf("Account role name: %s, err: %s", d.Id(), err), - }, - } - } - - d.SetId("") - - return nil -} diff --git a/pkg/resources/account_role_acceptance_test.go b/pkg/resources/account_role_acceptance_test.go deleted file mode 100644 index 184a1d64f5..0000000000 --- a/pkg/resources/account_role_acceptance_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package resources_test - -import ( - "context" - "database/sql" - "fmt" - "strings" - "testing" - - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/hashicorp/terraform-plugin-testing/config" - "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-testing/tfversion" - - acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAcc_AccountRole_basic(t *testing.T) { - name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - configVariables := map[string]config.Variable{ - "name": config.StringVariable(name), - "comment": config.StringVariable(comment), - } - resourceName := "snowflake_account_role.test" - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: testAccCheckAccountRoleDestroy(name), - Steps: []resource.TestStep{ - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "comment", comment), - resource.TestCheckResourceAttr(resourceName, "id", name), - ), - }, - // test import - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables, - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func TestAcc_AccountRole_updates(t *testing.T) { - configVariables := func(name string, comment string) config.Variables { - return config.Variables{ - "name": config.StringVariable(name), - "comment": config.StringVariable(comment), - } - } - - name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - newName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) - NewComment := "updated comment with 'single' quotes" - resourceName := "snowflake_account_role.test" - - resource.Test(t, resource.TestCase{ - ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - PreCheck: func() { acc.TestAccPreCheck(t) }, - TerraformVersionChecks: []tfversion.TerraformVersionCheck{ - tfversion.RequireAbove(tfversion.Version1_5_0), - }, - CheckDestroy: testAccCheckAccountRoleDestroy(name), - Steps: []resource.TestStep{ - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables(name, comment), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", name), - resource.TestCheckResourceAttr(resourceName, "comment", comment), - resource.TestCheckResourceAttr(resourceName, "id", name), - ), - }, - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables(newName, NewComment), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(resourceName, "name", newName), - resource.TestCheckResourceAttr(resourceName, "comment", NewComment), - resource.TestCheckResourceAttr(resourceName, "id", newName), - ), - }, - // test import - { - ConfigDirectory: config.TestNameDirectory(), - ConfigVariables: configVariables(newName, NewComment), - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccCheckAccountRoleDestroy(accountRoleName string) func(state *terraform.State) error { - return func(state *terraform.State) error { - db := acc.TestAccProvider.Meta().(*sql.DB) - client := sdk.NewClientFromDB(db) - for _, rs := range state.RootModule().Resources { - if rs.Type != "snowflake_account_role" { - continue - } - ctx := context.Background() - id := sdk.NewAccountObjectIdentifier(rs.Primary.Attributes["name"]) - _, err := client.Roles.ShowByID(ctx, sdk.NewShowByIdRoleRequest(id)) - if err == nil { - return fmt.Errorf("account role %v still exists", accountRoleName) - } - } - return nil - } -} diff --git a/pkg/resources/role.go b/pkg/resources/role.go index d63266e781..d9e5a599fe 100644 --- a/pkg/resources/role.go +++ b/pkg/resources/role.go @@ -1,16 +1,35 @@ package resources import ( + "context" + "database/sql" + "fmt" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var accountRoleSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + }, + "comment": { + Type: schema.TypeString, + Optional: true, + }, + "tag": tagReferenceSchema, +} + func Role() *schema.Resource { return &schema.Resource{ - CreateContext: CreateAccountRole, - ReadContext: ReadAccountRole, - DeleteContext: DeleteAccountRole, - UpdateContext: UpdateAccountRole, - DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_role instead.", + CreateContext: CreateAccountRole, + ReadContext: ReadAccountRole, + DeleteContext: DeleteAccountRole, + UpdateContext: UpdateAccountRole, Schema: accountRoleSchema, Importer: &schema.ResourceImporter{ @@ -18,3 +37,207 @@ func Role() *schema.Resource { }, } } + +func CreateAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + name := d.Get("name").(string) + id := sdk.NewAccountObjectIdentifier(name) + req := sdk.NewCreateRoleRequest(id) + + if v, ok := d.GetOk("comment"); ok { + req.WithComment(v.(string)) + } + + if _, ok := d.GetOk("tag"); ok { + req.WithTag(getPropertyTags(d, "tag")) + } + + err := client.Roles.Create(ctx, req) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to create account role", + Detail: fmt.Sprintf("Account role name: %s, err: %s", name, err), + }, + } + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + + return ReadAccountRole(ctx, d, meta) +} + +func ReadAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + accountRole, err := client.Roles.ShowByID(ctx, sdk.NewShowByIdRoleRequest(id)) + if err != nil { + if err.Error() == "object does not exist" { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Account role not found; marking it as removed", + Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to show account role by id", + Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), + }, + } + } + + if err := d.Set("name", accountRole.Name); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role name", + Detail: fmt.Sprintf("Account role name: %s, err: %s", accountRole.Name, err), + }, + } + } + + if err := d.Set("comment", accountRole.Comment); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role comment", + Detail: fmt.Sprintf("Account role name: %s, comment: %s, err: %s", accountRole.Name, accountRole.Comment, err), + }, + } + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + + return nil +} + +func UpdateAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + if d.HasChange("comment") { + if v, ok := d.GetOk("comment"); ok { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithSetComment(v.(string))) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role comment", + Detail: fmt.Sprintf("Account role name: %s, comment: %s, err: %s", id.FullyQualifiedName(), v, err), + }, + } + } + } else { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithUnsetComment(true)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to unset account role comment", + Detail: fmt.Sprintf("Account role name: %s, err: %s", id.FullyQualifiedName(), err), + }, + } + } + } + } + + if d.HasChange("tag") { + unsetTags, setTags := GetTagsDiff(d, "tag") + + if len(unsetTags) > 0 { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithUnsetTags(unsetTags)) + if err != nil { + tagNames := make([]string, len(unsetTags)) + for i, v := range unsetTags { + tagNames[i] = v.FullyQualifiedName() + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to unset account role tags", + Detail: fmt.Sprintf("Account role name: %s, tags to unset: %v, err: %s", id.FullyQualifiedName(), tagNames, err), + }, + } + } + } + + if len(setTags) > 0 { + err := client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithSetTags(setTags)) + if err != nil { + tagNames := make([]string, len(unsetTags)) + for i, v := range unsetTags { + tagNames[i] = v.FullyQualifiedName() + } + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to set account role tags", + Detail: fmt.Sprintf("Account role name: %s, tags to set: %v, err: %s", id.FullyQualifiedName(), tagNames, err), + }, + } + } + } + } + + if d.HasChange("name") { + _, newName := d.GetChange("name") + + newId, err := helpers.DecodeSnowflakeParameterID(newName.(string)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse account role name", + Detail: fmt.Sprintf("Account role name: %s, err: %s", newName, err), + }, + } + } + + err = client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(id).WithRenameTo(newId.(sdk.AccountObjectIdentifier))) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to rename account role name", + Detail: fmt.Sprintf("Previous account role name: %s, new account role name: %s, err: %s", id, newName, err), + }, + } + } + + d.SetId(helpers.EncodeSnowflakeID(newId)) + } + + return nil +} + +func DeleteAccountRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + err := client.Roles.Drop(ctx, sdk.NewDropRoleRequest(id)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to drop account role", + Detail: fmt.Sprintf("Account role name: %s, err: %s", d.Id(), err), + }, + } + } + + d.SetId("") + + return nil +} diff --git a/pkg/resources/role_acceptance_test.go b/pkg/resources/role_acceptance_test.go index 093e984198..3112aaa721 100644 --- a/pkg/resources/role_acceptance_test.go +++ b/pkg/resources/role_acceptance_test.go @@ -1,10 +1,17 @@ package resources_test import ( + "context" + "database/sql" "fmt" "strings" "testing" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -52,6 +59,115 @@ func TestAcc_Role(t *testing.T) { }) } +func TestAcc_AccountRole_basic(t *testing.T) { + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + configVariables := map[string]config.Variable{ + "name": config.StringVariable(name), + "comment": config.StringVariable(comment), + } + resourceName := "snowflake_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRoleDestroy(name), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "comment", comment), + resource.TestCheckResourceAttr(resourceName, "id", name), + ), + }, + // test import + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_AccountRole_updates(t *testing.T) { + configVariables := func(name string, comment string) config.Variables { + return config.Variables{ + "name": config.StringVariable(name), + "comment": config.StringVariable(comment), + } + } + + name := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + newName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + comment := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + NewComment := "updated comment with 'single' quotes" + resourceName := "snowflake_role.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: testAccCheckAccountRoleDestroy(name), + Steps: []resource.TestStep{ + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(name, comment), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "comment", comment), + resource.TestCheckResourceAttr(resourceName, "id", name), + ), + }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(newName, NewComment), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", newName), + resource.TestCheckResourceAttr(resourceName, "comment", NewComment), + resource.TestCheckResourceAttr(resourceName, "id", newName), + ), + }, + // test import + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables(newName, NewComment), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckAccountRoleDestroy(accountRoleName string) func(state *terraform.State) error { + return func(state *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + client := sdk.NewClientFromDB(db) + for _, rs := range state.RootModule().Resources { + if rs.Type != "snowflake_role" { + continue + } + ctx := context.Background() + id := sdk.NewAccountObjectIdentifier(rs.Primary.Attributes["name"]) + _, err := client.Roles.ShowByID(ctx, sdk.NewShowByIdRoleRequest(id)) + if err == nil { + return fmt.Errorf("account role %v still exists", accountRoleName) + } + } + return nil + } +} + func roleBasicConfig(name, comment string) string { s := ` resource "snowflake_role" "role" { diff --git a/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf b/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf index 1aa83161f4..7dac5cc2c3 100644 --- a/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf +++ b/pkg/resources/testdata/TestAcc_AccountRole_basic/test.tf @@ -1,4 +1,4 @@ -resource "snowflake_account_role" "test" { +resource "snowflake_role" "test" { name = var.name comment = var.comment } diff --git a/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf b/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf index 1aa83161f4..7dac5cc2c3 100644 --- a/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf +++ b/pkg/resources/testdata/TestAcc_AccountRole_updates/test.tf @@ -1,4 +1,4 @@ -resource "snowflake_account_role" "test" { +resource "snowflake_role" "test" { name = var.name comment = var.comment } From 2f4b83807af5125892f59b62126afc1b8487145c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 29 Jan 2024 11:23:41 +0100 Subject: [PATCH 4/4] Comment identifier validation --- pkg/resources/role.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/resources/role.go b/pkg/resources/role.go index d9e5a599fe..b834650a6c 100644 --- a/pkg/resources/role.go +++ b/pkg/resources/role.go @@ -13,9 +13,10 @@ import ( var accountRoleSchema = map[string]*schema.Schema{ "name": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + Type: schema.TypeString, + Required: true, + // TODO(SNOW-999049): Uncomment once better identifier validation will be implemented + // ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "comment": { Type: schema.TypeString,