diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md
index dbfef7d7f6..188a3aeccc 100644
--- a/MIGRATION_GUIDE.md
+++ b/MIGRATION_GUIDE.md
@@ -9,6 +9,9 @@ across different versions.
## v0.99.0 ➞ v0.100.0
+### snowflake_roles data source deprecation
+`snowflake_roles` is now deprecated in favor of `snowflake_account_roles` with the same schema and behavior. It will be removed with the v1 release. Please adjust your configuration files.
+
### snowflake_tag_association resource changes
#### *(behavior change)* new id format
In order to provide more functionality for tagging objects, we have changed the resource id from `"TAG_DATABASE"."TAG_SCHEMA"."TAG_NAME"` to `"TAG_DATABASE"."TAG_SCHEMA"."TAG_NAME"|TAG_VALUE|OBJECT_TYPE`. This allows to group tags associations per tag ID, tag value and object type in one resource.
diff --git a/docs/data-sources/account_roles.md b/docs/data-sources/account_roles.md
new file mode 100644
index 0000000000..a29302625f
--- /dev/null
+++ b/docs/data-sources/account_roles.md
@@ -0,0 +1,99 @@
+---
+page_title: "snowflake_account_roles Data Source - terraform-provider-snowflake"
+subcategory: ""
+description: |-
+ Datasource used to get details of filtered account roles. Filtering is aligned with the current possibilities for SHOW ROLES https://docs.snowflake.com/en/sql-reference/sql/show-roles query (like and in_class are all supported). The results of SHOW are encapsulated in one output collection.
+---
+
+# snowflake_account_roles (Data Source)
+
+Datasource used to get details of filtered account roles. Filtering is aligned with the current possibilities for [SHOW ROLES](https://docs.snowflake.com/en/sql-reference/sql/show-roles) query (`like` and `in_class` are all supported). The results of SHOW are encapsulated in one output collection.
+
+## Example Usage
+
+```terraform
+# Simple usage
+data "snowflake_account_roles" "simple" {
+}
+
+output "simple_output" {
+ value = data.snowflake_account_roles.simple.roles
+}
+
+# Filtering (like)
+data "snowflake_account_roles" "like" {
+ like = "role-name"
+}
+
+output "like_output" {
+ value = data.snowflake_account_roles.like.roles
+}
+
+# Filtering (in class)
+data "snowflake_account_roles" "in_class" {
+ in_class = "SNOWFLAKE.CORE.BUDGET"
+}
+
+output "in_class_output" {
+ value = data.snowflake_account_roles.in_class.roles
+}
+
+# Ensure the number of roles is equal to at least one element (with the use of postcondition)
+data "snowflake_account_roles" "assert_with_postcondition" {
+ like = "role-name-%"
+ lifecycle {
+ postcondition {
+ condition = length(self.roles) > 0
+ error_message = "there should be at least one role"
+ }
+ }
+}
+
+# Ensure the number of roles is equal to at exactly one element (with the use of check block)
+check "role_check" {
+ data "snowflake_account_roles" "assert_with_check_block" {
+ like = "role-name"
+ }
+
+ assert {
+ condition = length(data.snowflake_account_roles.assert_with_check_block.roles) == 1
+ error_message = "Roles filtered by '${data.snowflake_account_roles.assert_with_check_block.like}' returned ${length(data.snowflake_account_roles.assert_with_check_block.roles)} roles where one was expected"
+ }
+}
+```
+
+
+## Schema
+
+### Optional
+
+- `in_class` (String) Filters the SHOW GRANTS output by class name.
+- `like` (String) Filters the output with **case-insensitive** pattern, with support for SQL wildcard characters (`%` and `_`).
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+- `roles` (List of Object) Holds the aggregated output of all role details queries. (see [below for nested schema](#nestedatt--roles))
+
+
+### Nested Schema for `roles`
+
+Read-Only:
+
+- `show_output` (List of Object) (see [below for nested schema](#nestedobjatt--roles--show_output))
+
+
+### Nested Schema for `roles.show_output`
+
+Read-Only:
+
+- `assigned_to_users` (Number)
+- `comment` (String)
+- `created_on` (String)
+- `granted_roles` (Number)
+- `granted_to_roles` (Number)
+- `is_current` (Boolean)
+- `is_default` (Boolean)
+- `is_inherited` (Boolean)
+- `name` (String)
+- `owner` (String)
diff --git a/docs/data-sources/roles.md b/docs/data-sources/roles.md
index 8382bffa5b..5b65dc7520 100644
--- a/docs/data-sources/roles.md
+++ b/docs/data-sources/roles.md
@@ -9,6 +9,8 @@ description: |-
# snowflake_roles (Data Source)
+~> **Deprecation** This resource is deprecated and will be removed in a future major version release. Please use [snowflake_account_roles](./account_roles) instead.
+
Datasource used to get details of filtered roles. Filtering is aligned with the current possibilities for [SHOW ROLES](https://docs.snowflake.com/en/sql-reference/sql/show-roles) query (`like` and `in_class` are all supported). The results of SHOW are encapsulated in one output collection.
## Example Usage
diff --git a/docs/index.md b/docs/index.md
index 518af87d7c..c0ca5fe097 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -371,3 +371,4 @@ provider "snowflake" {
## Currently deprecated datasources
- [snowflake_role](./docs/data-sources/role) - use [snowflake_roles](./docs/data-sources/roles) instead
+- [snowflake_roles](./docs/data-sources/roles) - use [snowflake_account_roles](./docs/data-sources/account_roles) instead
diff --git a/examples/additional/deprecated_datasources.MD b/examples/additional/deprecated_datasources.MD
index 935ebcfd54..f2eddff9f2 100644
--- a/examples/additional/deprecated_datasources.MD
+++ b/examples/additional/deprecated_datasources.MD
@@ -1,3 +1,4 @@
## Currently deprecated datasources
- [snowflake_role](./docs/data-sources/role) - use [snowflake_roles](./docs/data-sources/roles) instead
+- [snowflake_roles](./docs/data-sources/roles) - use [snowflake_account_roles](./docs/data-sources/account_roles) instead
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..2f48b3067c
--- /dev/null
+++ b/examples/data-sources/snowflake_account_roles/data-source.tf
@@ -0,0 +1,48 @@
+# Simple usage
+data "snowflake_account_roles" "simple" {
+}
+
+output "simple_output" {
+ value = data.snowflake_account_roles.simple.roles
+}
+
+# Filtering (like)
+data "snowflake_account_roles" "like" {
+ like = "role-name"
+}
+
+output "like_output" {
+ value = data.snowflake_account_roles.like.roles
+}
+
+# Filtering (in class)
+data "snowflake_account_roles" "in_class" {
+ in_class = "SNOWFLAKE.CORE.BUDGET"
+}
+
+output "in_class_output" {
+ value = data.snowflake_account_roles.in_class.roles
+}
+
+# Ensure the number of roles is equal to at least one element (with the use of postcondition)
+data "snowflake_account_roles" "assert_with_postcondition" {
+ like = "role-name-%"
+ lifecycle {
+ postcondition {
+ condition = length(self.roles) > 0
+ error_message = "there should be at least one role"
+ }
+ }
+}
+
+# Ensure the number of roles is equal to at exactly one element (with the use of check block)
+check "role_check" {
+ data "snowflake_account_roles" "assert_with_check_block" {
+ like = "role-name"
+ }
+
+ assert {
+ condition = length(data.snowflake_account_roles.assert_with_check_block.roles) == 1
+ error_message = "Roles filtered by '${data.snowflake_account_roles.assert_with_check_block.like}' returned ${length(data.snowflake_account_roles.assert_with_check_block.roles)} roles where one was expected"
+ }
+}
diff --git a/pkg/datasources/account_roles.go b/pkg/datasources/account_roles.go
new file mode 100644
index 0000000000..1cfafaa2d5
--- /dev/null
+++ b/pkg/datasources/account_roles.go
@@ -0,0 +1,100 @@
+package datasources
+
+import (
+ "context"
+ "fmt"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/datasources"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"
+
+ "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{
+ "like": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "Filters the output with **case-insensitive** pattern, with support for SQL wildcard characters (`%` and `_`).",
+ },
+ "in_class": {
+ Type: schema.TypeString,
+ Optional: true,
+ ValidateDiagFunc: resources.IsValidIdentifier[sdk.SchemaObjectIdentifier](),
+ Description: "Filters the SHOW GRANTS output by class name.",
+ },
+ "roles": {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "Holds the aggregated output of all role details queries.",
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ resources.ShowOutputAttributeName: {
+ Type: schema.TypeList,
+ Computed: true,
+ Description: "Holds the output of SHOW ROLES.",
+ Elem: &schema.Resource{
+ Schema: schemas.ShowRoleSchema,
+ },
+ },
+ },
+ },
+ },
+}
+
+func AccountRoles() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: TrackingReadWrapper(datasources.Roles, ReadRoles),
+ Schema: accountRolesSchema,
+ Description: "Datasource used to get details of filtered account roles. Filtering is aligned with the current possibilities for [SHOW ROLES](https://docs.snowflake.com/en/sql-reference/sql/show-roles) query (`like` and `in_class` are all supported). The results of SHOW are encapsulated in one output collection.",
+ }
+}
+
+func ReadAccountRoles(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
+ client := meta.(*provider.Context).Client
+
+ req := sdk.NewShowRoleRequest()
+
+ if likePattern, ok := d.GetOk("like"); ok {
+ req.WithLike(sdk.NewLikeRequest(likePattern.(string)))
+ }
+
+ if className, ok := d.GetOk("in_class"); ok {
+ req.WithInClass(sdk.RolesInClass{
+ Class: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(className.(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("Error: %s", err),
+ },
+ }
+ }
+
+ d.SetId("account_roles_read")
+
+ flattenedAccountRoles := make([]map[string]any, len(roles))
+ for i, role := range roles {
+ role := role
+ flattenedAccountRoles[i] = map[string]any{
+ resources.ShowOutputAttributeName: []map[string]any{schemas.RoleToSchema(&role)},
+ }
+ }
+
+ err = d.Set("account_roles", flattenedAccountRoles)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ 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..a8bca1f544
--- /dev/null
+++ b/pkg/datasources/account_roles_acceptance_test.go
@@ -0,0 +1,116 @@
+package datasources_test
+
+import (
+ "fmt"
+ "strconv"
+ "testing"
+
+ acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance"
+
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random"
+ "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs"
+ "github.com/hashicorp/terraform-plugin-testing/config"
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/hashicorp/terraform-plugin-testing/tfversion"
+)
+
+func TestAcc_AccountRoles_Complete(t *testing.T) {
+ _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance)
+ acc.TestAccPreCheck(t)
+
+ accountRoleNamePrefix := random.AlphaN(10)
+ accountRoleName1 := acc.TestClient().Ids.AlphaWithPrefix(accountRoleNamePrefix + "1")
+ accountRoleName2 := acc.TestClient().Ids.AlphaWithPrefix(accountRoleNamePrefix + "2")
+ accountRoleName3 := acc.TestClient().Ids.Alpha()
+ dbRoleName := acc.TestClient().Ids.AlphaWithPrefix(accountRoleNamePrefix + "db")
+ comment := random.Comment()
+
+ // Proof that database role with the same prefix is not in the output of SHOW ROLES.
+ dbRole, dbRoleCleanup := acc.TestClient().DatabaseRole.CreateDatabaseRoleWithName(t, dbRoleName)
+ t.Cleanup(dbRoleCleanup)
+
+ likeVariables := config.Variables{
+ "account_role_name_1": config.StringVariable(accountRoleName1),
+ "account_role_name_2": config.StringVariable(accountRoleName2),
+ "account_role_name_3": config.StringVariable(accountRoleName3),
+ "comment": config.StringVariable(comment),
+ "like": config.StringVariable(accountRoleNamePrefix + "%"),
+ }
+
+ resource.Test(t, resource.TestCase{
+ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
+ TerraformVersionChecks: []tfversion.TerraformVersionCheck{
+ tfversion.RequireAbove(tfversion.Version1_5_0),
+ },
+ Steps: []resource.TestStep{
+ {
+ ConfigDirectory: config.TestStepDirectory(),
+ ConfigVariables: likeVariables,
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttr("data.snowflake_account_roles.test", "roles.#", "2"),
+ accountRolesDataSourceContainsRole(accountRoleName1, comment),
+ accountRolesDataSourceContainsRole(accountRoleName2, comment),
+ accountRolesDataSourceDoesNotContainRole(accountRoleName3, comment),
+ accountRolesDataSourceDoesNotContainRole(dbRole.ID().FullyQualifiedName(), comment),
+ ),
+ },
+ {
+ ConfigDirectory: config.TestStepDirectory(),
+ ConfigVariables: config.Variables{},
+ Check: resource.ComposeAggregateTestCheckFunc(
+ resource.TestCheckResourceAttrWith("data.snowflake_account_roles.test", "roles.#", func(value string) error {
+ numberOfRoles, err := strconv.ParseInt(value, 10, 8)
+ if err != nil {
+ return err
+ }
+
+ if numberOfRoles == 0 {
+ return fmt.Errorf("expected roles to be non-empty")
+ }
+
+ return nil
+ }),
+ ),
+ },
+ },
+ })
+}
+
+func accountRolesDataSourceDoesNotContainRole(name string, comment string) func(s *terraform.State) error {
+ return func(state *terraform.State) error {
+ err := accountRolesDataSourceContainsRole(name, comment)(state)
+ if err != nil && err.Error() == fmt.Sprintf("role %s not found", name) {
+ return nil
+ }
+ return fmt.Errorf("expected %s not to be present", name)
+ }
+}
+
+func accountRolesDataSourceContainsRole(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.show_output.0.name", i)] == name {
+ actualComment := rs.Primary.Attributes[fmt.Sprintf("roles.%d.show_output.0.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/roles.go b/pkg/datasources/roles.go
index 7278988f7d..56df651724 100644
--- a/pkg/datasources/roles.go
+++ b/pkg/datasources/roles.go
@@ -49,9 +49,10 @@ var rolesSchema = map[string]*schema.Schema{
func Roles() *schema.Resource {
return &schema.Resource{
- ReadContext: TrackingReadWrapper(datasources.Roles, ReadRoles),
- Schema: rolesSchema,
- Description: "Datasource used to get details of filtered roles. Filtering is aligned with the current possibilities for [SHOW ROLES](https://docs.snowflake.com/en/sql-reference/sql/show-roles) query (`like` and `in_class` are all supported). The results of SHOW are encapsulated in one output collection.",
+ ReadContext: TrackingReadWrapper(datasources.Roles, ReadRoles),
+ Schema: rolesSchema,
+ Description: "Datasource used to get details of filtered roles. Filtering is aligned with the current possibilities for [SHOW ROLES](https://docs.snowflake.com/en/sql-reference/sql/show-roles) query (`like` and `in_class` are all supported). The results of SHOW are encapsulated in one output collection.",
+ DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_account_roles instead.",
}
}
diff --git a/pkg/datasources/roles_acceptance_test.go b/pkg/datasources/roles_acceptance_test.go
index af29e8fd11..8ffdc84133 100644
--- a/pkg/datasources/roles_acceptance_test.go
+++ b/pkg/datasources/roles_acceptance_test.go
@@ -41,9 +41,9 @@ func TestAcc_Roles_Complete(t *testing.T) {
ConfigVariables: likeVariables,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("data.snowflake_roles.test", "roles.#", "2"),
- containsAccountRole(accountRoleName1, comment),
- containsAccountRole(accountRoleName2, comment),
- doesntContainAccountRole(accountRoleName3, comment),
+ containsRole(accountRoleName1, comment),
+ containsRole(accountRoleName2, comment),
+ doesntContainRole(accountRoleName3, comment),
),
},
{
@@ -68,17 +68,17 @@ func TestAcc_Roles_Complete(t *testing.T) {
})
}
-func doesntContainAccountRole(name string, comment string) func(s *terraform.State) error {
+func doesntContainRole(name string, comment string) func(s *terraform.State) error {
return func(state *terraform.State) error {
- err := containsAccountRole(name, comment)(state)
- if err != nil && err.Error() == fmt.Sprintf("role %s not found", name) {
+ err := containsRole(name, comment)(state)
+ if err != nil && err.Error() == fmt.Sprintf("account 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 {
+func containsRole(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" {
diff --git a/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/1/test.tf b/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/1/test.tf
new file mode 100644
index 0000000000..68afff3b58
--- /dev/null
+++ b/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/1/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,
+ ]
+ like = var.like
+}
diff --git a/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/1/variables.tf b/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/1/variables.tf
new file mode 100644
index 0000000000..fcd75c445f
--- /dev/null
+++ b/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/1/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 "like" {
+ type = string
+}
diff --git a/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/2/test.tf b/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/2/test.tf
new file mode 100644
index 0000000000..8a45ee2f6e
--- /dev/null
+++ b/pkg/datasources/testdata/TestAcc_AccountRoles_Complete/2/test.tf
@@ -0,0 +1,3 @@
+data "snowflake_account_roles" "test" {
+ in_class = "SNOWFLAKE.CORE.BUDGET"
+}
diff --git a/pkg/provider/datasources/datasources.go b/pkg/provider/datasources/datasources.go
index 56ea68b1c3..c31b954a22 100644
--- a/pkg/provider/datasources/datasources.go
+++ b/pkg/provider/datasources/datasources.go
@@ -4,6 +4,7 @@ type datasource string
const (
Accounts datasource = "snowflake_accounts"
+ AccountRoles datasource = "snowflake_account_roles"
Alerts datasource = "snowflake_alerts"
Connections datasource = "snowflake_connections"
CortexSearchServices datasource = "snowflake_cortex_search_services"
diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go
index 7e5f4c9370..35b375ed62 100644
--- a/pkg/provider/provider.go
+++ b/pkg/provider/provider.go
@@ -569,6 +569,7 @@ func getResources() map[string]*schema.Resource {
func getDataSources() map[string]*schema.Resource {
return map[string]*schema.Resource{
"snowflake_accounts": datasources.Accounts(),
+ "snowflake_account_roles": datasources.AccountRoles(),
"snowflake_alerts": datasources.Alerts(),
"snowflake_connections": datasources.Connections(),
"snowflake_cortex_search_services": datasources.CortexSearchServices(),