diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md new file mode 100644 index 00000000000..00000c23975 --- /dev/null +++ b/docs/resources/grant_privileges_to_database_role.md @@ -0,0 +1,303 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_grant_privileges_to_database_role Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_grant_privileges_to_database_role (Resource) + + + +## Example Usage + +```terraform +resource "snowflake_database_role" "db_role" { + database = "database" + name = "db_role_name" +} + +################################## +### on database privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["CREATE", "MONITOR"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + all_privileges = true + with_grant_option = true +} + +# all privileges + grant option + always apply +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + always_apply = true + all_privileges = true + with_grant_option = true +} + +################################## +### schema privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + all_schemas_in_database = snowflake_database_role.db_role.database + } +} + +# future schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + future_schemas_in_database = snowflake_database_role.db_role.database + } +} + +################################## +### schema object privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "REFERENCES"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# all in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} + +# future in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# future in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} +``` + + +## Schema + +### Required + +- `database_role_name` (String) The fully qualified name of the database role to which privileges will be granted. + +### Optional + +- `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. +- `on_database` (String) The fully qualified name of the database on which privileges will be granted. If the identifier is not fully qualified (in the form of .≤database_role_name>), the command looks for the database role in the current database for the session. All privileges are limited to the database that contains the database role, as well as other objects in the same database. +- `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)) +- `privileges` (Set of String) The privileges to grant on the database role. +- `with_grant_option` (Boolean) If specified, allows the recipient role to grant the privileges to other roles. + +### Read-Only + +- `id` (String) The ID of this resource. + + +### Nested Schema for `on_schema` + +Optional: + +- `all_schemas_in_database` (String) The fully qualified name of the database. +- `future_schemas_in_database` (String) The fully qualified name of the database. +- `schema_name` (String) The fully qualified name of the schema. + + + +### Nested Schema for `on_schema_object` + +Optional: + +- `all` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on_schema_object--all)) +- `future` (Block List, Max: 1) Configures the privilege to be granted on future objects in either a database or schema. (see [below for nested schema](#nestedblock--on_schema_object--future)) +- `object_name` (String) The fully qualified name of the object on which privileges will be granted. +- `object_type` (String) The object type of the schema object on which privileges will be granted. Valid values are: ALERT | DYNAMIC TABLE | EVENT TABLE | FILE FORMAT | FUNCTION | PROCEDURE | SECRET | SEQUENCE | PIPE | MASKING POLICY | PASSWORD POLICY | ROW ACCESS POLICY | SESSION POLICY | TAG | STAGE | STREAM | TABLE | EXTERNAL TABLE | TASK | VIEW | MATERIALIZED VIEW | NETWORK RULE | PACKAGES POLICY | ICEBERG TABLE + + +### Nested Schema for `on_schema_object.all` + +Required: + +- `object_type_plural` (String) The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES + +Optional: + +- `in_database` (String) +- `in_schema` (String) + + + +### Nested Schema for `on_schema_object.future` + +Required: + +- `object_type_plural` (String) The plural object type of the schema object on which privileges will be granted. Valid values are: ALERTS | DYNAMIC TABLES | EVENT TABLES | FILE FORMATS | FUNCTIONS | PROCEDURES | SECRETS | SEQUENCES | PIPES | MASKING POLICIES | PASSWORD POLICIES | ROW ACCESS POLICIES | SESSION POLICIES | TAGS | STAGES | STREAMS | TABLES | EXTERNAL TABLES | TASKS | VIEWS | MATERIALIZED VIEWS | NETWORK RULES | PACKAGES POLICIES | ICEBERG TABLES + +Optional: + +- `in_database` (String) +- `in_schema` (String) + +## Import + +~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` +~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` + +Import is supported using the following syntax: + +`terraform import "|||||"` + +where: +- database_role_name - fully qualified identifier +- with_grant_option - boolean +- always_apply - boolean +- privileges - list of privileges, comma separated; to import all_privileges write "ALL" or "ALL PRIVILEGES" +- grant_type - enum +- grant_data - enum data + +It has varying number of parts, depending on . All the possible types are: + +### OnDatabase +`terraform import "||||OnDatabase|"` + +### OnSchema + +On schema contains inner types for all options. + +#### OnSchema +`terraform import "||||OnSchema|OnSchema|"` + +#### OnAllSchemasInDatabase +`terraform import "||||OnSchema|OnAllSchemasInDatabase|"` + +#### OnFutureSchemasInDatabase +`terraform import "||||OnSchema|OnFutureSchemasInDatabase|"` + +### OnSchemaObject + +On schema object contains inner types for all options. + +#### OnObject +`terraform import "||||OnSchemaObject|OnObject||"` + +#### OnAll + +On all contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnAll||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnAll||InSchema|"` + +#### OnFuture + +On future contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnFuture||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnFuture||InSchema|"` + +### Import examples + +#### Grant all privileges OnDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|ALL|OnDatabase|\"test_db\""` + +#### Grant list of privileges OnAllSchemasInDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|CREATE TAG,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"test_db\""` + +#### Grant list of privileges OnAll tables in schema +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` + diff --git a/examples/resources/snowflake_grant_privileges_to_database_role/resource.tf b/examples/resources/snowflake_grant_privileges_to_database_role/resource.tf new file mode 100644 index 00000000000..a5de908db67 --- /dev/null +++ b/examples/resources/snowflake_grant_privileges_to_database_role/resource.tf @@ -0,0 +1,146 @@ +resource "snowflake_database_role" "db_role" { + database = "database" + name = "db_role_name" +} + +################################## +### on database privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["CREATE", "MONITOR"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + all_privileges = true + with_grant_option = true +} + +# all privileges + grant option + always apply +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_database = snowflake_database_role.db_role.database + always_apply = true + all_privileges = true + with_grant_option = true +} + +################################## +### schema privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + schema_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + all_schemas_in_database = snowflake_database_role.db_role.database + } +} + +# future schemas in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["MODIFY", "CREATE TABLE"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema { + future_schemas_in_database = snowflake_database_role.db_role.database + } +} + +################################## +### schema object privileges +################################## + +# list of privileges +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "REFERENCES"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } +} + +# all privileges + grant option +resource "snowflake_grant_privileges_to_database_role" "example" { + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + object_type = "VIEW" + object_name = "\"${snowflake_database_role.db_role.database}\".\"my_schema\".\"my_view\"" # note this is a fully qualified name! + } + all_privileges = true + with_grant_option = true +} + +# all in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# all in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + all { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} + +# future in database +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_database = snowflake_database_role.db_role.database + } + } +} + +# future in schema +resource "snowflake_grant_privileges_to_database_role" "example" { + privileges = ["SELECT", "INSERT"] + database_role_name = "\"${snowflake_database_role.db_role.database}\".\"${snowflake_database_role.db_role.name}\"" + on_schema_object { + future { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database_role.db_role.database}\".\"my_schema\"" # note this is a fully qualified name! + } + } +} diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 5cbe46c1cd9..44b91bb1619 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -10,17 +10,11 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - "log" "slices" "strings" ) -// TODO: -// - resolve todos -// - remove logs -// - make error messages consistent -// - write documentation (document on_all, always_apply etc.) -// - test import +// TODO: Imported privileges (after second account will be added) var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "database_role_name": { @@ -65,18 +59,11 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ Optional: true, Default: false, Description: "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).", - // TODO: conflicts with - //AtLeastOneOf: []string{ - // "all_privileges", - // "on_schema.0.all_schemas_in_database", - // "on_schema_object.0.all", - //}, }, "always_apply_trigger": { - Type: schema.TypeString, - Optional: true, - Default: "", - // TODO: Fix desc + Type: schema.TypeString, + Optional: true, + Default: "", Description: "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.", }, "on_database": { @@ -176,8 +163,7 @@ var grantPrivilegesToDatabaseRoleSchema = map[string]*schema.Schema{ "FUNCTION", "PROCEDURE", "SECRET", - "SEQUENCE", - "PIPE", + "SEQUENCE", "PIPE", "MASKING POLICY", "PASSWORD POLICY", "ROW ACCESS POLICY", @@ -333,7 +319,7 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if err != nil { return nil, err } - if err := d.Set("database_role_name", id.DatabaseRoleName); err != nil { + if err := d.Set("database_role_name", id.DatabaseRoleName.FullyQualifiedName()); err != nil { return nil, err } if err := d.Set("with_grant_option", id.WithGrantOption); err != nil { @@ -356,7 +342,7 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } case OnSchemaDatabaseRoleGrantKind: data := id.Data.(*OnSchemaGrantData) - var onSchema map[string]any + onSchema := make(map[string]any) switch data.Kind { case OnSchemaSchemaGrantKind: @@ -372,16 +358,16 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } case OnSchemaObjectDatabaseRoleGrantKind: data := id.Data.(*OnSchemaObjectGrantData) - var onSchemaObject map[string]any + onSchemaObject := make(map[string]any) switch data.Kind { case OnObjectSchemaObjectGrantKind: onSchemaObject["object_type"] = data.Object.ObjectType.String() onSchemaObject["object_name"] = data.Object.Name.FullyQualifiedName() case OnAllSchemaObjectGrantKind: - var onAll map[string]any + onAll := make(map[string]any) - onAll["object_name_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() + onAll["object_type_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() switch data.OnAllOrFuture.Kind { case InDatabaseBulkOperationGrantKind: onAll["in_database"] = data.OnAllOrFuture.Database.FullyQualifiedName() @@ -391,9 +377,9 @@ func ImportGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource onSchemaObject["all"] = []any{onAll} case OnFutureSchemaObjectGrantKind: - var onFuture map[string]any + onFuture := make(map[string]any) - onFuture["object_name_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() + onFuture["object_type_plural"] = data.OnAllOrFuture.ObjectNamePlural.String() switch data.OnAllOrFuture.Kind { case InDatabaseBulkOperationGrantKind: onFuture["in_database"] = data.OnAllOrFuture.Database.FullyQualifiedName() @@ -451,7 +437,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } @@ -474,7 +460,6 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } } - log.Println("PRIVILEGES TO CHANGE:", privilegesToAdd, privilegesToRemove) grantOn := getDatabaseRoleGrantOn(d) if len(privilegesToAdd) > 0 { @@ -495,7 +480,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to grant added privileges", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err.Error()), }) } } @@ -518,7 +503,7 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to revoke removed privileges", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nPrivileges to remove: %v\nError: %s", d.Id(), privilegesToRemove, err.Error()), }) } } @@ -526,14 +511,11 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource id.Privileges = privilegesAfterChange } - log.Println("UPDATE always_apply has change:", d.HasChange("always_apply")) if d.HasChange("always_apply") { - log.Println("UPDATE always_apply get:", d.Get("always_apply")) id.AlwaysApply = d.Get("always_apply").(bool) } if id.AlwaysApply { - log.Println("UPDATE applying grants") err := client.Grants.GrantPrivilegesToDatabaseRole( ctx, getDatabaseRolePrivilegesFromSchema(d), @@ -546,8 +528,8 @@ func UpdateGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource if err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, - Summary: "An error occurred when granting privileges to database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + Summary: "Always apply. An error occurred when granting privileges to database role", + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), }) } } @@ -567,7 +549,7 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } @@ -582,7 +564,7 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when revoking privileges from database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", id.DatabaseRoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nDatabase role name: %s\nError: %s", d.Id(), id.DatabaseRoleName, err.Error()), }) } @@ -592,7 +574,6 @@ func DeleteGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.Resource } func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { - log.Println("READ BEGINS") var diags diag.Diagnostics id, err := ParseGrantPrivilegesToDatabaseRoleId(d.Id()) @@ -600,20 +581,26 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } - log.Println("READ id.AlwaysApply:", id.AlwaysApply) if id.AlwaysApply { + triggerId, err := uuid.GenerateUUID() + if err != nil { + return append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to generate UUID", + Detail: fmt.Sprintf("Original error: %s", err.Error()), + }) + } + // Change the value of always_apply_trigger to produce a plan - triggerId, _ := uuid.GenerateUUID() // TODO handle error - log.Printf("READ applying triggerId: %s, was: %s\n", triggerId, d.Get("always_apply_trigger")) if err := d.Set("always_apply_trigger", triggerId); err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting always_apply_trigger for database role", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } } @@ -621,8 +608,9 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa if id.AllPrivileges { return append(diags, diag.Diagnostic{ Severity: diag.Warning, - Summary: "Show with all_privileges option is skipped for now.", // TODO: Details - Detail: "", // TODO: link to the design decisions doc + Summary: "Show with all_privileges option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", }) } @@ -638,46 +626,41 @@ func ReadGrantPrivilegesToDatabaseRole(ctx context.Context, d *schema.ResourceDa return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to retrieve grants", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), }) } var privileges []string - // TODO: Refactor - check if correct with new conventions - // TODO: Compare privileges for _, grant := range grants { - log.Println("GRANT:", grant) // Accept only DATABASE ROLEs if grant.GrantTo != sdk.ObjectTypeDatabaseRole && grant.GrantedTo != sdk.ObjectTypeDatabaseRole { continue } - // TODO: What about all_privileges, right now we cannot assure that the list of privileges is correct - // Only consider privileges that are already present in the ID so we + // Only consider privileges that are already present in the ID, so we // don't delete privileges managed by other resources. if !slices.Contains(id.Privileges, grant.Privilege) { continue } - // TODO: What about GranteeName with database roles is it fully qualified or not ? if yes, refactor GranteeName. if id.WithGrantOption == grant.GrantOption && id.DatabaseRoleName.Name() == grant.GranteeName.Name() { - // future grants do not have grantedBy, only current grants do. If grantedby - // is an empty string it means the grant could not have been created by terraform + // Future grants do not have grantedBy, only current grants do. + // If grantedby is an empty string, it means terraform could not have created the grant if (opts.Future == nil || *opts.Future == false) && grant.GrantedBy.Name() == "" { continue } - // grant_on is for future grants, granted_on is for current grants. They function the same way though in a test for matching the object type + // grant_on is for future grants, granted_on is for current grants. + // They function the same way though in a test for matching the object type if grantedOn == grant.GrantedOn || grantedOn == grant.GrantOn { privileges = append(privileges, grant.Privilege) } } } - log.Println("PRIVILEGES:", id.Privileges, expandStringList(d.Get("privileges").(*schema.Set).List()), privileges) if err := d.Set("privileges", privileges); err != nil { return append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting privileges for database role", - Detail: fmt.Sprintf("Id: %s\nErr: %s", d.Id(), err.Error()), // TODO: link to the documentation (?). It should describe how the identifier looks. + Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), privileges, err.Error()), }) } @@ -712,14 +695,13 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran }, } case OnAllSchemasInDatabaseSchemaGrantKind: - // TODO: Document return nil, "", append(diags, diag.Diagnostic{ Severity: diag.Warning, - Summary: "Skipping", - Detail: "TODO", + Summary: "Show with OnAllSchemasInDatabase option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", }) case OnFutureSchemasInDatabaseSchemaGrantKind: - // TODO: show future on database (collisions with other on future triggers and over fetching is ok ?) opts.Future = sdk.Bool(true) opts.In = &sdk.ShowGrantsIn{ Database: data.DatabaseName, @@ -735,11 +717,11 @@ func prepareShowGrantsRequest(id GrantPrivilegesToDatabaseRoleId) (*sdk.ShowGran Object: data.Object, } case OnAllSchemaObjectGrantKind: - // TODO: Document return nil, "", append(diags, diag.Diagnostic{ Severity: diag.Warning, - Summary: "Skipping", - Detail: "TODO", + Summary: "Show with OnAll option is skipped.", + // TODO: link to the design decisions doc + Detail: "See our document on design decisions for grants: ", }) case OnFutureSchemaObjectGrantKind: grantedOn = data.OnAllOrFuture.ObjectNamePlural.Singular() @@ -860,7 +842,7 @@ func getDatabaseRoleGrantOn(d *schema.ResourceData) *sdk.DatabaseRoleGrantOn { switch { case objectTypeOk && objectNameOk: grantOnSchemaObject.SchemaObject = &sdk.Object{ - ObjectType: sdk.ObjectType(objectType), // TODO: Should we validate it or just cast it + ObjectType: sdk.ObjectType(objectType), Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(objectName), } case allOk: diff --git a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go index f8a0ad7ac8c..4d5dde8d8cd 100644 --- a/pkg/resources/grant_privileges_to_database_role_acceptance_test.go +++ b/pkg/resources/grant_privileges_to_database_role_acceptance_test.go @@ -15,10 +15,6 @@ import ( "testing" ) -// TODO Use cases to cover in acc tests -// - import - check import -// - different paths to parse (on database, on schema, on schema object) - func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { name := "test_database_role_name" configVariables := config.Variables{ @@ -55,6 +51,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnDatabase(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "with_grant_option", "true"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -95,6 +98,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchema(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -134,6 +144,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnAllSchemasInDatabase(t *testing.T) resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -173,6 +190,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnFutureSchemasInDatabase(t *testing. resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -216,6 +240,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnObject(t *testing.T) resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -289,6 +320,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnAll_InDatabase(t *te resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } @@ -330,6 +368,13 @@ func TestAcc_GrantPrivilegesToDatabaseRole_OnSchemaObject_OnFuture_InDatabase(t resource.TestCheckResourceAttr(resourceName, "with_grant_option", "false"), ), }, + { + ConfigDirectory: config.TestNameDirectory(), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } diff --git a/pkg/resources/grant_privileges_to_database_role_identifier.go b/pkg/resources/grant_privileges_to_database_role_identifier.go index 29f3c9d2b8f..cc9ec6284d5 100644 --- a/pkg/resources/grant_privileges_to_database_role_identifier.go +++ b/pkg/resources/grant_privileges_to_database_role_identifier.go @@ -8,8 +8,6 @@ import ( "strings" ) -// TODO: Add unit tests for marshaling / unmarshalling - type DatabaseRoleGrantKind string const ( @@ -18,7 +16,6 @@ const ( OnSchemaObjectDatabaseRoleGrantKind DatabaseRoleGrantKind = "OnSchemaObject" ) -// TODO: Move to the shareable file between this and grant_priv_to_role.go file type OnSchemaGrantKind string const ( @@ -27,7 +24,6 @@ const ( OnFutureSchemasInDatabaseSchemaGrantKind OnSchemaGrantKind = "OnFutureSchemasInDatabase" ) -// TODO: Move to the shareable file between this and grant_priv_to_role.go file type OnSchemaObjectGrantKind string const ( @@ -126,7 +122,6 @@ type BulkOperationGrantData struct { Schema *sdk.DatabaseObjectIdentifier } -// TODO: Describe how to put a right identifier in the documentation (so the users will be able to use it in the import) func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseRoleId, error) { var databaseRoleId GrantPrivilegesToDatabaseRoleId @@ -139,7 +134,7 @@ func ParseGrantPrivilegesToDatabaseRoleId(id string) (GrantPrivilegesToDatabaseR databaseRoleId.WithGrantOption = parts[1] == "true" databaseRoleId.AlwaysApply = parts[2] == "true" privileges := strings.Split(parts[3], ",") - if len(privileges) == 1 && privileges[0] == "ALL" { + if len(privileges) == 1 && (privileges[0] == "ALL" || privileges[0] == "ALL PRIVILEGES") { databaseRoleId.AllPrivileges = true } else { if len(privileges) == 1 && privileges[0] == "" { diff --git a/templates/resources/grant_privileges_to_database_role.md.tmpl b/templates/resources/grant_privileges_to_database_role.md.tmpl new file mode 100644 index 00000000000..05bc4b9c611 --- /dev/null +++ b/templates/resources/grant_privileges_to_database_role.md.tmpl @@ -0,0 +1,93 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +## Import + +~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` +~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` + +Import is supported using the following syntax: + +`terraform import "|||||"` + +where: +- database_role_name - fully qualified identifier +- with_grant_option - boolean +- always_apply - boolean +- privileges - list of privileges, comma separated; to import all_privileges write "ALL" or "ALL PRIVILEGES" +- grant_type - enum +- grant_data - enum data + +It has varying number of parts, depending on grant_type. All the possible types are: + +### OnDatabase +`terraform import "||||OnDatabase|"` + +### OnSchema + +On schema contains inner types for all options. + +#### OnSchema +`terraform import "||||OnSchema|OnSchema|"` + +#### OnAllSchemasInDatabase +`terraform import "||||OnSchema|OnAllSchemasInDatabase|"` + +#### OnFutureSchemasInDatabase +`terraform import "||||OnSchema|OnFutureSchemasInDatabase|"` + +### OnSchemaObject + +On schema object contains inner types for all options. + +#### OnObject +`terraform import "||||OnSchemaObject|OnObject||"` + +#### OnAll + +On all contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnAll||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnAll||InSchema|"` + +#### OnFuture + +On future contains inner types for all options. + +##### InDatabase +`terraform import "||||OnSchemaObject|OnFuture||InDatabase|"` + +##### InSchema +`terraform import "||||OnSchemaObject|OnFuture||InSchema|"` + +### Import examples + +#### Grant all privileges OnDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|ALL|OnDatabase|\"test_db\""` + +#### Grant list of privileges OnAllSchemasInDatabase +`terraform import "\"test_db\".\"test_db_role\"|false|false|CREATE TAG,CREATE TABLE|OnSchema|OnAllSchemasInDatabase|\"test_db\""` + +#### Grant list of privileges OnAll tables in schema +`terraform import "\"test_db\".\"test_db_role\"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|\"test_db\".\"test_schema\""` +