Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add snowflake grant privileges to account role #2365

Merged
merged 11 commits into from
Jan 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
396 changes: 396 additions & 0 deletions docs/resources/grant_privileges_to_account_role.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion docs/resources/grant_privileges_to_database_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ description: |-

---

~> **Note** This is a preview resource. It's ready for general use. In case of any errors, please file an issue in our GitHub repository.


!> **Warning** Be careful when using `always_apply` field. It will always produce a plan (even when no changes were made) and can be harmful in some setups. For more details why we decided to introduce it to go our document explaining those design decisions (coming soon).

Expand Down Expand Up @@ -175,7 +177,7 @@ resource "snowflake_grant_privileges_to_database_role" "example" {

- `all_privileges` (Boolean) Grant all privileges on the database role.
- `always_apply` (Boolean) If true, the resource will always produce a “plan” and on “apply” it will re-grant defined privileges. It is supposed to be used only in “grant privileges on all X’s in database / schema Y” or “grant all privileges to X” scenarios to make sure that every new object in a given database / schema is granted by the account role and every new privilege is granted to the database role. Important note: this flag is not compliant with the Terraform assumptions of the config being eventually convergent (producing an empty plan).
- `always_apply_trigger` (String) This field should not be set and its main purpose is to achieve the functionality described by always_apply field. This is value will be flipped to the opposite value on every terraform apply, thus creating a new plan that will re-apply grants.
- `always_apply_trigger` (String) This is a helper field and should not be set. Its main purpose is to help to achieve the functionality described by the always_apply field.
- `on_database` (String) The fully qualified name of the database on which privileges will be granted.
- `on_schema` (Block List, Max: 1) Specifies the schema on which privileges will be granted. (see [below for nested schema](#nestedblock--on_schema))
- `on_schema_object` (Block List, Max: 1) Specifies the schema object on which privileges will be granted. (see [below for nested schema](#nestedblock--on_schema_object))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
resource "snowflake_database" "db" {
name = "database"
}

resource "snowflake_role" "db_role" {
name = "role_name"
}

##################################
### on account privileges
##################################

# list of privileges
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["CREATE DATABASE", "CREATE USER"]
account_role_name = snowflake_role.db_role.name
on_account = true
}

## ID: "\"role_name\"|false|false|CREATE DATABASE,CREATE USER|OnAccount"

# all privileges + grant option
resource "snowflake_grant_privileges_to_account_role" "example" {
account_role_name = snowflake_role.db_role.name
on_account = true
all_privileges = true
with_grant_option = true
}

## ID: "\"role_name\"|true|false|ALL|OnAccount"

# all privileges + grant option + always apply
resource "snowflake_grant_privileges_to_account_role" "example" {
account_role_name = snowflake_role.db_role.name
on_account = true
always_apply = true
all_privileges = true
with_grant_option = true
}

## ID: "\"role_name\"|true|true|ALL|OnAccount"

##################################
### on account object privileges
##################################

# list of privileges
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["CREATE SCHEMA", "CREATE DATABASE ROLE"]
account_role_name = snowflake_role.db_role.name
on_account_object {
object_type = "DATABASE"
object_name = snowflake_database.db.name
}
}

## ID: "\"role_name\"|false|false|CREATE SCHEMA,CREATE DATABASE ROLE|OnAccountObject|DATABASE|\"database\""

# all privileges + grant option
resource "snowflake_grant_privileges_to_account_role" "example" {
account_role_name = snowflake_role.db_role.name
on_account_object {
object_type = "DATABASE"
object_name = snowflake_database.db.name
}
all_privileges = true
with_grant_option = true
}

## ID: "\"role_name\"|true|false|ALL|OnAccountObject|DATABASE|\"database\""

# all privileges + grant option + always apply
resource "snowflake_grant_privileges_to_account_role" "example" {
account_role_name = snowflake_role.db_role.name
on_account_object {
object_type = "DATABASE"
object_name = snowflake_database.db.name
}
always_apply = true
all_privileges = true
with_grant_option = true
}

## ID: "\"role_name\"|true|true|ALL|OnAccountObject|DATABASE|\"database\""

##################################
### schema privileges
##################################

# list of privileges
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["MODIFY", "CREATE TABLE"]
account_role_name = snowflake_role.db_role.name
on_schema {
schema_name = "\"${snowflake_database.db.name}\".\"my_schema\"" # note this is a fully qualified name!
}
}

## ID: "\"role_name\"|false|false|MODIFY,CREATE TABLE|OnSchema|OnSchema|\"database\".\"my_schema\""

# all privileges + grant option
resource "snowflake_grant_privileges_to_account_role" "example" {
account_role_name = snowflake_role.db_role.name
on_schema {
schema_name = "\"${snowflake_database.db.name}\".\"my_schema\"" # note this is a fully qualified name!
}
all_privileges = true
with_grant_option = true
}

## ID: "\"role_name\"|true|false|MODIFY,CREATE TABLE|OnSchema|OnSchema|\"database\".\"my_schema\""

# all schemas in database
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["MODIFY", "CREATE TABLE"]
account_role_name = snowflake_role.db_role.name
on_schema {
all_schemas_in_database = snowflake_database.db.name
}
}

## ID: "\"role_name\"|false|false|MODIFY,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"database\""

# future schemas in database
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["MODIFY", "CREATE TABLE"]
account_role_name = snowflake_role.db_role.name
on_schema {
future_schemas_in_database = snowflake_database.db.name
}
}

## ID: "\"role_name\"|false|false|MODIFY,CREATE TABLE|OnSchema|OnFutureSchemasInDatabase|\"database\""

##################################
### schema object privileges
##################################

# list of privileges
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["SELECT", "REFERENCES"]
account_role_name = snowflake_role.db_role.name
on_schema_object {
object_type = "VIEW"
object_name = "\"${snowflake_database.db.name}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name!
}
}

## ID: "\"role_name\"|false|false|SELECT,REFERENCES|OnSchemaObject|VIEW|\"database\".\"my_schema\".\"my_view\""

# all privileges + grant option
resource "snowflake_grant_privileges_to_account_role" "example" {
account_role_name = snowflake_role.db_role.name
on_schema_object {
object_type = "VIEW"
object_name = "\"${snowflake_database.db.name}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name!
}
all_privileges = true
with_grant_option = true
}

## ID: "\"role_name\"|true|false|ALL|OnSchemaObject|OnObject|VIEW|\"database\".\"my_schema\".\"my_view\""

# all in database
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["SELECT", "INSERT"]
account_role_name = snowflake_role.db_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_database = snowflake_database.db.name
}
}
}

## ID: "\"role_name\"|false|false|SELECT,INSERT|OnSchemaObject|OnAll|TABLES|InDatabase|\"database\""

# all in schema
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["SELECT", "INSERT"]
account_role_name = snowflake_role.db_role.name
on_schema_object {
all {
object_type_plural = "TABLES"
in_schema = "\"${snowflake_database.db.name}\".\"my_schema\"" # note this is a fully qualified name!
}
}
}

## ID: "\"role_name\"|false|false|SELECT,INSERT|OnSchemaObject|OnAll|TABLES|InSchema|\"database\".\"my_schema\""

# future in database
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["SELECT", "INSERT"]
account_role_name = snowflake_role.db_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_database = snowflake_database.db.name
}
}
}

## ID: "\"role_name\"|false|false|SELECT,INSERT|OnSchemaObject|OnFuture|TABLES|InDatabase|\"database\""

# future in schema
resource "snowflake_grant_privileges_to_account_role" "example" {
privileges = ["SELECT", "INSERT"]
account_role_name = snowflake_role.db_role.name
on_schema_object {
future {
object_type_plural = "TABLES"
in_schema = "\"${snowflake_database.db.name}\".\"my_schema\"" # note this is a fully qualified name!
}
}
}

## ID: "\"role_name\"|false|false|SELECT,INSERT|OnSchemaObject|OnFuture|TABLES|InSchema|\"database\".\"my_schema\""
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_grant_account_role": resources.GrantAccountRole(),
"snowflake_grant_database_role": resources.GrantDatabaseRole(),
"snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(),
"snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(),
"snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(),
"snowflake_managed_account": resources.ManagedAccount(),
"snowflake_masking_policy": resources.MaskingPolicy(),
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/account_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func AccountGrant() *TerraformGrantResource {
Delete: DeleteAccountGrant,
Update: UpdateAccountGrant,

DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_role instead.",
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_account_role instead.",
Schema: accountGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
Expand Down
3 changes: 2 additions & 1 deletion pkg/resources/external_table_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ func ExternalTableGrant() *TerraformGrantResource {
Read: ReadExternalTableGrant,
Delete: DeleteExternalTableGrant,
Update: UpdateExternalTableGrant,
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_role instead.", Schema: externalTableGrantSchema,
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_account_role instead.",
Schema: externalTableGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), helpers.IDDelimiter)
Expand Down
3 changes: 2 additions & 1 deletion pkg/resources/failover_group_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ func FailoverGroupGrant() *TerraformGrantResource {
Read: ReadFailoverGroupGrant,
Delete: DeleteFailoverGroupGrant,
Update: UpdateFailoverGroupGrant,
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_role instead.", Schema: failoverGroupGrantSchema,
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_account_role instead.",
Schema: failoverGroupGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), helpers.IDDelimiter)
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/file_format_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func FileFormatGrant() *TerraformGrantResource {
Read: ReadFileFormatGrant,
Delete: DeleteFileFormatGrant,
Update: UpdateFileFormatGrant,
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_role instead.",
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_account_role instead.",
Schema: fileFormatGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/function_grant.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func FunctionGrant() *TerraformGrantResource {
Read: ReadFunctionGrant,
Delete: DeleteFunctionGrant,
Update: UpdateFunctionGrant,
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_role instead.",
DeprecationMessage: "This resource is deprecated and will be removed in a future major version release. Please use snowflake_grant_privileges_to_account_role instead.",
Schema: functionGrantSchema,
Importer: &schema.ResourceImporter{
StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
Expand Down
79 changes: 79 additions & 0 deletions pkg/resources/grant_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
"strings"
"time"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk"
"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/snowflake"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/jmoiron/sqlx"
Expand Down Expand Up @@ -386,3 +390,78 @@ func changeDiff(d *schema.ResourceData, key string) (toAdd []string, toRemove []
toRemove = expandStringList(oldSet.Difference(newSet).List())
return
}

func isNotOwnershipGrant() func(value any, path cty.Path) diag.Diagnostics {
return func(value any, path cty.Path) diag.Diagnostics {
var diags diag.Diagnostics
if privilege, ok := value.(string); ok && strings.ToUpper(privilege) == "OWNERSHIP" {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Unsupported privilege 'OWNERSHIP'",
// TODO: Change when a new resource for granting ownership will be available (SNOW-991423)
Detail: "Granting ownership is only allowed in dedicated resources (snowflake_user_ownership_grant, snowflake_role_ownership_grant)",
AttributePath: nil,
})
}
return diags
}
}

func ValidGrantedObjectType() schema.SchemaValidateDiagFunc {
return StringInSlice([]string{
sdk.ObjectTypeAlert.String(),
sdk.ObjectTypeDynamicTable.String(),
sdk.ObjectTypeEventTable.String(),
sdk.ObjectTypeFileFormat.String(),
sdk.ObjectTypeFunction.String(),
sdk.ObjectTypeProcedure.String(),
sdk.ObjectTypeSecret.String(),
sdk.ObjectTypeSequence.String(),
sdk.ObjectTypePipe.String(),
sdk.ObjectTypeMaskingPolicy.String(),
sdk.ObjectTypePasswordPolicy.String(),
sdk.ObjectTypeRowAccessPolicy.String(),
sdk.ObjectTypeSessionPolicy.String(),
sdk.ObjectTypeTag.String(),
sdk.ObjectTypeStage.String(),
sdk.ObjectTypeStream.String(),
sdk.ObjectTypeTable.String(),
sdk.ObjectTypeExternalTable.String(),
sdk.ObjectTypeTask.String(),
sdk.ObjectTypeView.String(),
sdk.ObjectTypeMaterializedView.String(),
sdk.ObjectTypeNetworkRule.String(),
sdk.ObjectTypePackagesPolicy.String(),
sdk.ObjectTypeIcebergTable.String(),
}, true)
}

func ValidGrantedPluralObjectType() schema.SchemaValidateDiagFunc {
return StringInSlice(
[]string{
sdk.PluralObjectTypeAlerts.String(),
sdk.PluralObjectTypeDynamicTables.String(),
sdk.PluralObjectTypeEventTables.String(),
sdk.PluralObjectTypeFileFormats.String(),
sdk.PluralObjectTypeFunctions.String(),
sdk.PluralObjectTypeProcedures.String(),
sdk.PluralObjectTypeSecrets.String(),
sdk.PluralObjectTypeSequences.String(),
sdk.PluralObjectTypePipes.String(),
sdk.PluralObjectTypeMaskingPolicies.String(),
sdk.PluralObjectTypePasswordPolicies.String(),
sdk.PluralObjectTypeRowAccessPolicies.String(),
sdk.PluralObjectTypeSessionPolicies.String(),
sdk.PluralObjectTypeTags.String(),
sdk.PluralObjectTypeStages.String(),
sdk.PluralObjectTypeStreams.String(),
sdk.PluralObjectTypeTables.String(),
sdk.PluralObjectTypeExternalTables.String(),
sdk.PluralObjectTypeTasks.String(),
sdk.PluralObjectTypeViews.String(),
sdk.PluralObjectTypeMaterializedViews.String(),
sdk.PluralObjectTypeNetworkRules.String(),
sdk.PluralObjectTypePackagesPolicies.String(),
sdk.PluralObjectTypeIcebergTables.String(),
}, true)
}
Loading
Loading