diff --git a/docs/resources/grant_privileges_to_share.md b/docs/resources/grant_privileges_to_share.md new file mode 100644 index 0000000000..0beac830ba --- /dev/null +++ b/docs/resources/grant_privileges_to_share.md @@ -0,0 +1,142 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_grant_privileges_to_share Resource - terraform-provider-snowflake" +subcategory: "" +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. + +# snowflake_grant_privileges_to_share (Resource) + + + +## Example Usage + +```terraform +################################## +### on database +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["USAGE"] + on_database = snowflake_database.example.name +} + +## ID: "\"share_name\"|USAGE|OnDatabase|\"database_name\"" + +################################## +### on schema +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["USAGE"] + on_schema = "${snowflake_database.example.name}.${snowflake_schema.example.name}" +} + +## ID: "\"share_name\"|USAGE|OnSchema|\"database_name\".\"schema_name\"" + +################################## +### on table +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["SELECT"] + on_table = "${snowflake_database.example.name}.${snowflake_schema.example.name}.${snowflake_table.example.name}" +} + +## ID: "\"share_name\"|SELECT|OnTable|\"database_name\".\"schema_name\".\"table_name\"" + +################################## +### on all tables in schema +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["SELECT"] + on_all_tables_in_schema = "${snowflake_database.example.name}.${snowflake_schema.example.name}" +} + +## ID: "\"share_name\"|SELECT|OnAllTablesInSchema|\"database_name\".\"schema_name\"" + +################################## +### on tag +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["READ"] + on_tag = "${snowflake_database.example.name}.${snowflake_schema.example.name}.${snowflake_tag.example.name}" +} + +## ID: "\"share_name\"|READ|OnTag|\"database_name\".\"schema_name\".\"tag_name\"" + +################################## +### on view +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["SELECT"] + on_view = "${snowflake_database.example.name}.${snowflake_schema.example.name}.${snowflake_view.example.name}" +} + +## ID: "\"share_name\"|SELECT|OnView|\"database_name\".\"schema_name\".\"view_name\"" +``` + + +## Schema + +### Required + +- `privileges` (Set of String) The privileges to grant on the share. See available list of privileges: https://docs.snowflake.com/en/sql-reference/sql/grant-privilege-share#syntax +- `to_share` (String) The fully qualified name of the share on which privileges will be granted. + +### Optional + +- `on_all_tables_in_schema` (String) The fully qualified identifier for the schema for which the specified privilege will be granted for all tables. +- `on_database` (String) The fully qualified name of the database on which privileges will be granted. +- `on_schema` (String) The fully qualified name of the schema on which privileges will be granted. +- `on_table` (String) The fully qualified name of the table on which privileges will be granted. +- `on_tag` (String) The fully qualified name of the tag on which privileges will be granted. +- `on_view` (String) The fully qualified name of the view on which privileges will be granted. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` + +Import is supported using the following syntax: + +`terraform import "|||"` + +where: +- share_name - fully qualified identifier +- privileges - list of privileges, comma separated. See the available privileges for given object types: https://docs.snowflake.com/en/sql-reference/sql/grant-privilege-share#syntax +- grant_type - enum +- grant_identifier - fully qualified identifier + +### OnDatabase +`terraform import "||OnDatabase|"` + +### OnSchema +`terraform import "||OnSchema|."` + +### OnTable +`terraform import "||OnTable|.."` + +### OnSchema +`terraform import "||OnAllTablesInSchema|."` + +### OnTag +`terraform import "||OnTag|.."` + +### OnView +`terraform import "||OnView|.."` diff --git a/examples/resources/snowflake_grant_privileges_to_share/resource.tf b/examples/resources/snowflake_grant_privileges_to_share/resource.tf new file mode 100644 index 0000000000..a213e6cc8f --- /dev/null +++ b/examples/resources/snowflake_grant_privileges_to_share/resource.tf @@ -0,0 +1,71 @@ +################################## +### on database +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["USAGE"] + on_database = snowflake_database.example.name +} + +## ID: "\"share_name\"|USAGE|OnDatabase|\"database_name\"" + +################################## +### on schema +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["USAGE"] + on_schema = "${snowflake_database.example.name}.${snowflake_schema.example.name}" +} + +## ID: "\"share_name\"|USAGE|OnSchema|\"database_name\".\"schema_name\"" + +################################## +### on table +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["SELECT"] + on_table = "${snowflake_database.example.name}.${snowflake_schema.example.name}.${snowflake_table.example.name}" +} + +## ID: "\"share_name\"|SELECT|OnTable|\"database_name\".\"schema_name\".\"table_name\"" + +################################## +### on all tables in schema +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["SELECT"] + on_all_tables_in_schema = "${snowflake_database.example.name}.${snowflake_schema.example.name}" +} + +## ID: "\"share_name\"|SELECT|OnAllTablesInSchema|\"database_name\".\"schema_name\"" + +################################## +### on tag +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["READ"] + on_tag = "${snowflake_database.example.name}.${snowflake_schema.example.name}.${snowflake_tag.example.name}" +} + +## ID: "\"share_name\"|READ|OnTag|\"database_name\".\"schema_name\".\"tag_name\"" + +################################## +### on view +################################## + +resource "snowflake_grant_privileges_to_share" "example" { + to_share = snowflake_share.example.name + privileges = ["SELECT"] + on_view = "${snowflake_database.example.name}.${snowflake_schema.example.name}.${snowflake_view.example.name}" +} + +## ID: "\"share_name\"|SELECT|OnView|\"database_name\".\"schema_name\".\"view_name\"" diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index ac77870a3c..92a0e0dc49 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -448,6 +448,7 @@ func getResources() map[string]*schema.Resource { "snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(), "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), + "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), "snowflake_managed_account": resources.ManagedAccount(), "snowflake_masking_policy": resources.MaskingPolicy(), "snowflake_materialized_view": resources.MaterializedView(), diff --git a/pkg/resources/grant_privileges_to_account_role.go b/pkg/resources/grant_privileges_to_account_role.go index 01b42e18c3..77a464a01b 100644 --- a/pkg/resources/grant_privileges_to_account_role.go +++ b/pkg/resources/grant_privileges_to_account_role.go @@ -656,7 +656,7 @@ func DeleteGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when revoking privileges from account role", - Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName.FullyQualifiedName(), err.Error()), }, } } diff --git a/pkg/resources/grant_privileges_to_share.go b/pkg/resources/grant_privileges_to_share.go new file mode 100644 index 0000000000..bfdb865d41 --- /dev/null +++ b/pkg/resources/grant_privileges_to_share.go @@ -0,0 +1,477 @@ +package resources + +import ( + "context" + "database/sql" + "fmt" + "log" + "slices" + + "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 grantPrivilegesToShareGrantExactlyOneOfValidation = []string{ + "on_database", + "on_schema", + // TODO(SNOW-990811): "function_name", + "on_table", + "on_all_tables_in_schema", + "on_tag", + "on_view", +} + +var grantPrivilegesToShareSchema = map[string]*schema.Schema{ + "to_share": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "The fully qualified name of the share on which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + }, + "privileges": { + Type: schema.TypeSet, + Required: true, + Description: "The privileges to grant on the share. See available list of privileges: https://docs.snowflake.com/en/sql-reference/sql/grant-privilege-share#syntax", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "on_database": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database on which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, + "on_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the schema on which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, + // TODO(SNOW-1021686): Because function identifier contains arguments which are not supported right now + // "function_name": { + // Type: schema.TypeString, + // Optional: true, + // ForceNew: true, + // Description: "The fully qualified name of the function on which privileges will be granted.", + // ValidateDiagFunc: IsValidIdentifier[sdk.FunctionIdentifier](), + // ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + // }, + "on_table": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the table on which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, + "on_all_tables_in_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified identifier for the schema for which the specified privilege will be granted for all tables.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, + "on_tag": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the tag on which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, + "on_view": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the view on which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.SchemaObjectIdentifier](), + ExactlyOneOf: grantPrivilegesToShareGrantExactlyOneOfValidation, + }, +} + +func GrantPrivilegesToShare() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateGrantPrivilegesToShare, + UpdateContext: UpdateGrantPrivilegesToShare, + DeleteContext: DeleteGrantPrivilegesToShare, + ReadContext: ReadGrantPrivilegesToShare, + + Schema: grantPrivilegesToShareSchema, + Importer: &schema.ResourceImporter{ + StateContext: ImportGrantPrivilegesToShare(), + }, + } +} + +func ImportGrantPrivilegesToShare() func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + return func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + id, err := ParseGrantPrivilegesToShareId(d.Id()) + if err != nil { + return nil, err + } + if err := d.Set("to_share", id.ShareName.Name()); err != nil { + return nil, err + } + if err := d.Set("privileges", id.Privileges); err != nil { + return nil, err + } + + switch id.Kind { + case OnDatabaseShareGrantKind: + if err := d.Set("on_database", id.Identifier.Name()); err != nil { + return nil, err + } + case OnSchemaShareGrantKind: + if err := d.Set("on_schema", id.Identifier.FullyQualifiedName()); err != nil { + return nil, err + } + // TODO(SNOW-990811) case OnFunctionShareGrantKind: + // if err := d.Set("function_name", id.Identifier.FullyQualifiedName()); err != nil { + // return nil, err + // } + case OnTableShareGrantKind: + if err := d.Set("on_table", id.Identifier.FullyQualifiedName()); err != nil { + return nil, err + } + case OnAllTablesInSchemaShareGrantKind: + if err := d.Set("on_all_tables_in_schema", id.Identifier.FullyQualifiedName()); err != nil { + return nil, err + } + case OnTagShareGrantKind: + if err := d.Set("on_tag", id.Identifier.FullyQualifiedName()); err != nil { + return nil, err + } + case OnViewShareGrantKind: + if err := d.Set("on_view", id.Identifier.FullyQualifiedName()); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil + } +} + +func CreateGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + id := createGrantPrivilegesToShareIdFromSchema(d) + log.Printf("[DEBUG] created identifier from schema: %s", id.String()) + + err := client.Grants.GrantPrivilegeToShare(ctx, getObjectPrivilegesFromSchema(d), getShareGrantOn(d), sdk.NewAccountObjectIdentifier(id.ShareName.Name())) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when granting privileges to share", + Detail: fmt.Sprintf("Id: %s\nShare name: %s\nError: %s", id.String(), id.ShareName, err.Error()), + }, + } + } + + d.SetId(id.String()) + + return ReadGrantPrivilegesToShare(ctx, d, meta) +} + +func UpdateGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + id, err := ParseGrantPrivilegesToShareId(d.Id()) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + }, + } + } + + if d.HasChange("privileges") { + oldPrivileges, newPrivileges := d.GetChange("privileges") + privilegesBeforeChange := expandStringList(oldPrivileges.(*schema.Set).List()) + privilegesAfterChange := expandStringList(newPrivileges.(*schema.Set).List()) + + var privilegesToAdd, privilegesToRemove []sdk.ObjectPrivilege + + for _, privilegeBeforeChange := range privilegesBeforeChange { + if !slices.Contains(privilegesAfterChange, privilegeBeforeChange) { + privilegesToRemove = append(privilegesToRemove, sdk.ObjectPrivilege(privilegeBeforeChange)) + } + } + + for _, privilegeAfterChange := range privilegesAfterChange { + if !slices.Contains(privilegesBeforeChange, privilegeAfterChange) { + privilegesToAdd = append(privilegesToAdd, sdk.ObjectPrivilege(privilegeAfterChange)) + } + } + + grantOn := getShareGrantOn(d) + + if len(privilegesToAdd) > 0 { + err = client.Grants.GrantPrivilegeToShare( + ctx, + privilegesToAdd, + grantOn, + sdk.NewAccountObjectIdentifier(id.ShareName.Name()), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to grant added privileges", + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err.Error()), + }, + } + } + } + + if len(privilegesToRemove) > 0 { + err = client.Grants.RevokePrivilegeFromShare( + ctx, + privilegesToRemove, + grantOn, + sdk.NewAccountObjectIdentifier(id.ShareName.Name()), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to revoke removed privileges", + Detail: fmt.Sprintf("Id: %s\nPrivileges to remove: %v\nError: %s", d.Id(), privilegesToRemove, err.Error()), + }, + } + } + } + + id.Privileges = privilegesAfterChange + d.SetId(id.String()) + } + + return ReadGrantPrivilegesToShare(ctx, d, meta) +} + +func DeleteGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + + id, err := ParseGrantPrivilegesToShareId(d.Id()) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + }, + } + } + + err = client.Grants.RevokePrivilegeFromShare(ctx, getObjectPrivilegesFromSchema(d), getShareGrantOn(d), sdk.NewAccountObjectIdentifier(id.ShareName.Name())) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when revoking privileges from share", + Detail: fmt.Sprintf("Id: %s\nShare name: %s\nError: %s", d.Id(), id.ShareName.FullyQualifiedName(), err.Error()), + }, + } + } + + d.SetId("") + + return nil +} + +func ReadGrantPrivilegesToShare(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + id, err := ParseGrantPrivilegesToShareId(d.Id()) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to parse internal identifier", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + }, + } + } + + opts, grantedOn, diags := prepareShowGrantsRequestForShare(id) + if len(diags) != 0 { + return diags + } + + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + grants, err := client.Grants.Show(ctx, opts) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Failed to retrieve grants", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + }, + } + } + + var privileges []string + for _, grant := range grants { + if grant.GrantedTo != sdk.ObjectTypeShare { + continue + } + // 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 + } + if grant.GranteeName.Name() == id.ShareName.Name() { + if grantedOn == grant.GrantedOn { + privileges = append(privileges, grant.Privilege) + } + } + } + + // REFERENCE_USAGE is a special pseudo-privilege that you can grant or revoke, + // but it won't show up when querying privileges (not returned by show grants ... query). + // That's why we have to check it manually outside the loop and append it whenever it's specified in the configuration. + if slices.Contains(id.Privileges, sdk.ObjectPrivilegeReferenceUsage.String()) { + privileges = append(privileges, sdk.ObjectPrivilegeReferenceUsage.String()) + } + + if err := d.Set("privileges", privileges); err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error setting privileges for account role", + Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), privileges, err.Error()), + }, + } + } + + return nil +} + +func createGrantPrivilegesToShareIdFromSchema(d *schema.ResourceData) *GrantPrivilegesToShareId { + id := new(GrantPrivilegesToShareId) + id.ShareName = sdk.NewAccountObjectIdentifier(d.Get("to_share").(string)) + id.Privileges = expandStringList(d.Get("privileges").(*schema.Set).List()) + + databaseName, databaseNameOk := d.GetOk("on_database") + schemaName, schemaNameOk := d.GetOk("on_schema") + // TODO(SNOW-990811) functionName, functionNameOk := d.GetOk("function_name") + tableName, tableNameOk := d.GetOk("on_table") + allTablesInSchema, allTablesInSchemaOk := d.GetOk("on_all_tables_in_schema") + tagName, tagNameOk := d.GetOk("on_tag") + viewName, viewNameOk := d.GetOk("on_view") + + switch { + case databaseNameOk: + id.Kind = OnDatabaseShareGrantKind + id.Identifier = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(databaseName.(string)) + case schemaNameOk: + id.Kind = OnSchemaShareGrantKind + id.Identifier = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string)) + // TODO(SNOW-990811) case functionNameOk: + // id.Kind = OnFunctionShareGrantKind + // id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(functionName.(string)) + case tableNameOk: + id.Kind = OnTableShareGrantKind + id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(tableName.(string)) + case allTablesInSchemaOk: + id.Kind = OnAllTablesInSchemaShareGrantKind + id.Identifier = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(allTablesInSchema.(string)) + case tagNameOk: + id.Kind = OnTagShareGrantKind + id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(tagName.(string)) + case viewNameOk: + id.Kind = OnViewShareGrantKind + id.Identifier = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(viewName.(string)) + } + + return id +} + +func getObjectPrivilegesFromSchema(d *schema.ResourceData) []sdk.ObjectPrivilege { + privileges := expandStringList(d.Get("privileges").(*schema.Set).List()) + objectPrivileges := make([]sdk.ObjectPrivilege, len(privileges)) + for i, privilege := range privileges { + objectPrivileges[i] = sdk.ObjectPrivilege(privilege) + } + return objectPrivileges +} + +func getShareGrantOn(d *schema.ResourceData) *sdk.ShareGrantOn { + grantOn := new(sdk.ShareGrantOn) + + databaseName, databaseNameOk := d.GetOk("on_database") + schemaName, schemaNameOk := d.GetOk("on_schema") + // TODO(SNOW-990811) functionName, functionNameOk := d.GetOk("on_function") + tableName, tableNameOk := d.GetOk("on_table") + allTablesInSchema, allTablesInSchemaOk := d.GetOk("on_all_tables_in_schema") + tagName, tagNameOk := d.GetOk("on_tag") + viewName, viewNameOk := d.GetOk("on_view") + + switch { + case len(databaseName.(string)) > 0 && databaseNameOk: + grantOn.Database = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(databaseName.(string)) + case len(schemaName.(string)) > 0 && schemaNameOk: + grantOn.Schema = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(schemaName.(string)) + // TODO(SNOW-990811) case len(functionName.(string)) > 0 && functionNameOk: + // grantOn.Function = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(functionName.(string)) + case len(tableName.(string)) > 0 && tableNameOk: + grantOn.Table = &sdk.OnTable{ + Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(tableName.(string)), + } + case len(allTablesInSchema.(string)) > 0 && allTablesInSchemaOk: + grantOn.Table = &sdk.OnTable{ + AllInSchema: sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(allTablesInSchema.(string)), + } + case len(tagName.(string)) > 0 && tagNameOk: + grantOn.Tag = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(tagName.(string)) + case len(viewName.(string)) > 0 && viewNameOk: + grantOn.View = sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(viewName.(string)) + } + + return grantOn +} + +func prepareShowGrantsRequestForShare(id GrantPrivilegesToShareId) (*sdk.ShowGrantOptions, sdk.ObjectType, diag.Diagnostics) { + opts := new(sdk.ShowGrantOptions) + var objectType sdk.ObjectType + + switch id.Kind { + case OnDatabaseShareGrantKind: + objectType = sdk.ObjectTypeDatabase + case OnSchemaShareGrantKind: + objectType = sdk.ObjectTypeSchema + case OnTableShareGrantKind: + objectType = sdk.ObjectTypeTable + case OnAllTablesInSchemaShareGrantKind: + return nil, "", diag.Diagnostics{ + diag.Diagnostic{ + // TODO: link to the design decisions doc (SNOW-990811) + Severity: diag.Warning, + Summary: "Show with OnAll option is skipped.", + Detail: "See our document on design decisions for grants: ", + }, + } + case OnTagShareGrantKind: + objectType = sdk.ObjectTypeTag + case OnViewShareGrantKind: + objectType = sdk.ObjectTypeView + } + + opts.On = &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: objectType, + Name: id.Identifier, + }, + } + + return opts, objectType, nil +} diff --git a/pkg/resources/grant_privileges_to_share_acceptance_test.go b/pkg/resources/grant_privileges_to_share_acceptance_test.go new file mode 100644 index 0000000000..35d52fc0e4 --- /dev/null +++ b/pkg/resources/grant_privileges_to_share_acceptance_test.go @@ -0,0 +1,552 @@ +package resources_test + +import ( + "context" + "database/sql" + "fmt" + "regexp" + "strings" + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_GrantPrivilegesToShare_OnDatabase(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeUsage.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeUsage.String()), + resource.TestCheckResourceAttr(resourceName, "on_database", databaseName.Name()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnSchema(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + schemaName := sdk.NewDatabaseObjectIdentifier(databaseName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + "schema": config.StringVariable(schemaName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeUsage.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnSchema"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeUsage.String()), + resource.TestCheckResourceAttr(resourceName, "on_schema", schemaName.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnSchema"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +// TODO(SNOW-1021686): Add on_function test + +func TestAcc_GrantPrivilegesToShare_OnTable(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + schemaName := sdk.NewDatabaseObjectIdentifier(databaseName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + tableName := sdk.NewSchemaObjectIdentifier(databaseName.Name(), schemaName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + "schema": config.StringVariable(schemaName.Name()), + "on_table": config.StringVariable(tableName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeSelect.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTable"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeSelect.String()), + resource.TestCheckResourceAttr(resourceName, "on_table", tableName.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTable"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTable_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnAllTablesInSchema(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + schemaName := sdk.NewDatabaseObjectIdentifier(databaseName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + "schema": config.StringVariable(schemaName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeSelect.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeSelect.String()), + resource.TestCheckResourceAttr(resourceName, "on_all_tables_in_schema", schemaName.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnView(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + schemaName := sdk.NewDatabaseObjectIdentifier(databaseName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + tableName := sdk.NewSchemaObjectIdentifier(databaseName.Name(), schemaName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + viewName := sdk.NewSchemaObjectIdentifier(databaseName.Name(), schemaName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + "schema": config.StringVariable(schemaName.Name()), + "on_table": config.StringVariable(tableName.Name()), + "on_view": config.StringVariable(viewName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeSelect.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnView"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeSelect.String()), + resource.TestCheckResourceAttr(resourceName, "on_view", viewName.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnView"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnView_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnTag(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + schemaName := sdk.NewDatabaseObjectIdentifier(databaseName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + tagName := sdk.NewSchemaObjectIdentifier(databaseName.Name(), schemaName.Name(), strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + "schema": config.StringVariable(schemaName.Name()), + "on_tag": config.StringVariable(tagName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeRead.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTag"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeRead.String()), + resource.TestCheckResourceAttr(resourceName, "on_tag", tagName.FullyQualifiedName()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTag"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnTag_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnPrivilegeUpdate(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool, privileges []sdk.ObjectPrivilege) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + } + if withGrant { + if len(privileges) > 0 { + configPrivileges := make([]config.Variable, len(privileges)) + for i, privilege := range privileges { + configPrivileges[i] = config.StringVariable(privilege.String()) + } + variables["privileges"] = config.ListVariable(configPrivileges...) + } + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true, []sdk.ObjectPrivilege{ + sdk.ObjectPrivilegeReferenceUsage, + }), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeReferenceUsage.String()), + resource.TestCheckResourceAttr(resourceName, "on_database", databaseName.Name()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true, []sdk.ObjectPrivilege{ + sdk.ObjectPrivilegeUsage, + }), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeUsage.String()), + resource.TestCheckResourceAttr(resourceName, "on_database", databaseName.Name()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true, []sdk.ObjectPrivilege{ + sdk.ObjectPrivilegeUsage, + }), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant"), + ConfigVariables: configVariables(false, []sdk.ObjectPrivilege{}), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_OnDatabaseWithReferenceUsagePrivilege(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func(withGrant bool) config.Variables { + variables := config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + } + if withGrant { + variables["privileges"] = config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeReferenceUsage.String()), + ) + } + return variables + } + resourceName := "snowflake_grant_privileges_to_share.test" + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "to_share", shareName.Name()), + resource.TestCheckResourceAttr(resourceName, "privileges.#", "1"), + resource.TestCheckResourceAttr(resourceName, "privileges.0", sdk.ObjectPrivilegeReferenceUsage.String()), + resource.TestCheckResourceAttr(resourceName, "on_database", databaseName.Name()), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase"), + ConfigVariables: configVariables(true), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant"), + ConfigVariables: configVariables(false), + Check: testAccCheckSharePrivilegesRevoked(), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_NoPrivileges(t *testing.T) { + databaseName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func() config.Variables { + return config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "database": config.StringVariable(databaseName.Name()), + } + } + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges"), + ConfigVariables: configVariables(), + ExpectError: regexp.MustCompile(`The argument "privileges" is required, but no definition was found.`), + }, + }, + }) +} + +func TestAcc_GrantPrivilegesToShare_NoOnOption(t *testing.T) { + shareName := sdk.NewAccountObjectIdentifier(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))) + + configVariables := func() config.Variables { + return config.Variables{ + "to_share": config.StringVariable(shareName.Name()), + "privileges": config.ListVariable( + config.StringVariable(sdk.ObjectPrivilegeReferenceUsage.String()), + ), + } + } + + 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: acc.ConfigurationDirectory("TestAcc_GrantPrivilegesToShare/NoOnOption"), + ConfigVariables: configVariables(), + ExpectError: regexp.MustCompile(`Invalid combination of arguments`), + }, + }, + }) +} + +func testAccCheckSharePrivilegesRevoked() func(*terraform.State) error { + return func(state *terraform.State) error { + for _, rs := range state.RootModule().Resources { + if rs.Type != "snowflake_grant_privileges_to_share" { + continue + } + db := acc.TestAccProvider.Meta().(*sql.DB) + client := sdk.NewClientFromDB(db) + ctx := context.Background() + + id := sdk.NewExternalObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["to_share"]) + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Share: sdk.NewAccountObjectIdentifier(id.Name()), + }, + }) + if err != nil { + return err + } + var grantedPrivileges []string + for _, grant := range grants { + grantedPrivileges = append(grantedPrivileges, grant.Privilege) + } + if len(grantedPrivileges) > 0 { + return fmt.Errorf("share (%s) is still granted with privileges: %v", id.FullyQualifiedName(), grantedPrivileges) + } + } + return nil + } +} diff --git a/pkg/resources/grant_privileges_to_share_identifier.go b/pkg/resources/grant_privileges_to_share_identifier.go new file mode 100644 index 0000000000..744facc1b2 --- /dev/null +++ b/pkg/resources/grant_privileges_to_share_identifier.go @@ -0,0 +1,97 @@ +package resources + +import ( + "fmt" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +type ShareGrantKind string + +const ( + OnDatabaseShareGrantKind ShareGrantKind = "OnDatabase" + OnSchemaShareGrantKind ShareGrantKind = "OnSchema" + // TODO(SNOW-1021686): Because function identifier contains arguments which are not supported right now + // OnFunctionShareGrantKind ShareGrantKind = "OnFunction" + OnTableShareGrantKind ShareGrantKind = "OnTable" + OnAllTablesInSchemaShareGrantKind ShareGrantKind = "OnAllTablesInSchema" + OnTagShareGrantKind ShareGrantKind = "OnTag" + OnViewShareGrantKind ShareGrantKind = "OnView" +) + +type GrantPrivilegesToShareId struct { + ShareName sdk.AccountObjectIdentifier + Privileges []string + Kind ShareGrantKind + Identifier sdk.ObjectIdentifier +} + +func (id *GrantPrivilegesToShareId) String() string { + return strings.Join([]string{ + id.ShareName.FullyQualifiedName(), + strings.Join(id.Privileges, ","), + string(id.Kind), + id.Identifier.FullyQualifiedName(), + }, helpers.IDDelimiter) +} + +func ParseGrantPrivilegesToShareId(idString string) (GrantPrivilegesToShareId, error) { + var grantPrivilegesToShareId GrantPrivilegesToShareId + + parts := strings.Split(idString, helpers.IDDelimiter) + if len(parts) != 4 { + return grantPrivilegesToShareId, sdk.NewError(fmt.Sprintf(`snowflake_grant_privileges_to_share id is composed out of 4 parts "|||", but got %d parts: %v`, len(parts), parts)) + } + + grantPrivilegesToShareId.ShareName = sdk.NewAccountObjectIdentifier(parts[0]) + privileges := strings.Split(parts[1], ",") + if len(privileges) == 0 || (len(privileges) == 1 && privileges[0] == "") { + return grantPrivilegesToShareId, sdk.NewError(fmt.Sprintf(`invalid Privileges value: %s, should be comma separated list of privileges`, privileges)) + } + grantPrivilegesToShareId.Privileges = privileges + grantPrivilegesToShareId.Kind = ShareGrantKind(parts[2]) + + id, err := helpers.DecodeSnowflakeParameterID(parts[3]) + if err != nil { + return grantPrivilegesToShareId, err + } + + switch grantPrivilegesToShareId.Kind { + case OnDatabaseShareGrantKind: + if typedIdentifier, ok := id.(sdk.AccountObjectIdentifier); ok { + grantPrivilegesToShareId.Identifier = typedIdentifier + } else { + return grantPrivilegesToShareId, fmt.Errorf( + "invalid identifier, expected fully qualified name of account object: %s, but instead got: %s", + getExpectedIdentifierRepresentationFromGeneric[sdk.AccountObjectIdentifier](), + getExpectedIdentifierRepresentationFromParam(id), + ) + } + case OnSchemaShareGrantKind, OnAllTablesInSchemaShareGrantKind: + if typedIdentifier, ok := id.(sdk.DatabaseObjectIdentifier); ok { + grantPrivilegesToShareId.Identifier = typedIdentifier + } else { + return grantPrivilegesToShareId, fmt.Errorf( + "invalid identifier, expected fully qualified name of database object: %s, but instead got: %s", + getExpectedIdentifierRepresentationFromGeneric[sdk.DatabaseObjectIdentifier](), + getExpectedIdentifierRepresentationFromParam(id), + ) + } + case OnTableShareGrantKind, OnViewShareGrantKind, OnTagShareGrantKind: // TODO(SNOW-1021686) , OnFunctionShareGrantKind: + if typedIdentifier, ok := id.(sdk.SchemaObjectIdentifier); ok { + grantPrivilegesToShareId.Identifier = typedIdentifier + } else { + return grantPrivilegesToShareId, fmt.Errorf( + "invalid identifier, expected fully qualified name of schema object: %s, but instead got: %s", + getExpectedIdentifierRepresentationFromGeneric[sdk.SchemaObjectIdentifier](), + getExpectedIdentifierRepresentationFromParam(id), + ) + } + default: + return grantPrivilegesToShareId, fmt.Errorf("unexpected share grant kind: %v", grantPrivilegesToShareId.Kind) + } + + return grantPrivilegesToShareId, nil +} diff --git a/pkg/resources/grant_privileges_to_share_identifier_test.go b/pkg/resources/grant_privileges_to_share_identifier_test.go new file mode 100644 index 0000000000..d892d2800d --- /dev/null +++ b/pkg/resources/grant_privileges_to_share_identifier_test.go @@ -0,0 +1,225 @@ +package resources + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" +) + +func TestParseGrantPrivilegesToShareId(t *testing.T) { + testCases := []struct { + Name string + Identifier string + Expected GrantPrivilegesToShareId + Error string + }{ + { + Name: "grant privileges on database to share", + Identifier: `"share-name"|REFERENCE_USAGE|OnDatabase|"on-database-name"`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"REFERENCE_USAGE"}, + Kind: OnDatabaseShareGrantKind, + Identifier: sdk.NewAccountObjectIdentifier("on-database-name"), + }, + }, + { + Name: "grant privileges on schema to share", + Identifier: `"share-name"|USAGE|OnSchema|"on-database-name"."on-schema-name"`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"USAGE"}, + Kind: OnSchemaShareGrantKind, + Identifier: sdk.NewDatabaseObjectIdentifier("on-database-name", "on-schema-name"), + }, + }, + // TODO(SNOW-1021686): This is wrong and should be fixed (function's last part of identifier cannot be enclosed with quotes like that) + //{ + // Name: "grant privileges on function to share", + // Identifier: `"share-name"|USAGE|OnFunction|"on-database-name"."on-schema-name".on-function-name(INT, VARCHAR)`, + // Expected: GrantPrivilegesToShareId{ + // ShareName: sdk.NewExternalObjectIdentifierFromFullyQualifiedName("share-name"), + // Privileges: []string{"USAGE"}, + // Kind: OnFunctionShareGrantKind, + // Identifier: sdk.NewSchemaObjectIdentifier("on-database-name", "on-schema-name", "on-function-name(INT, VARCHAR)"), + // }, + // }, + { + Name: "grant privileges on table to share", + Identifier: `"share-name"|EVOLVE SCHEMA|OnTable|"on-database-name"."on-schema-name"."on-table-name"`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"EVOLVE SCHEMA"}, + Kind: OnTableShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifier("on-database-name", "on-schema-name", "on-table-name"), + }, + }, + { + Name: "grant privileges on all tables in schema to share", + Identifier: `"share-name"|EVOLVE SCHEMA,SELECT|OnAllTablesInSchema|"on-database-name"."on-schema-name"`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"EVOLVE SCHEMA", "SELECT"}, + Kind: OnAllTablesInSchemaShareGrantKind, + Identifier: sdk.NewDatabaseObjectIdentifier("on-database-name", "on-schema-name"), + }, + }, + { + Name: "grant privileges on tag to share", + Identifier: `"share-name"|READ|OnTag|"database-name"."schema-name"."on-tag-name"`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"READ"}, + Kind: OnTagShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "on-tag-name"), + }, + }, + { + Name: "grant privileges on view to share", + Identifier: `"share-name"|READ|OnView|"on-database-name"."on-schema-name"."on-view-name"`, + Expected: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"READ"}, + Kind: OnViewShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifier("on-database-name", "on-schema-name", "on-view-name"), + }, + }, + { + Name: "validation: not enough parts", + Identifier: `"share-name"|SELECT|OnDatabase`, + Error: `snowflake_grant_privileges_to_share id is composed out of 4 parts "|||", but got 3 parts: ["share-name" SELECT OnDatabase]`, + }, + { + Name: "validation: empty privileges", + Identifier: `"share-name"||OnDatabase|"database-name"`, + Error: `invalid Privileges value: [], should be comma separated list of privileges`, + }, + { + Name: "validation: unsupported kind", + Identifier: `"share-name"|SELECT|OnSomething|"object-name"`, + Error: `unexpected share grant kind: OnSomething`, + }, + { + Name: "validation: invalid identifier", + Identifier: `"share-name"|SELECT|OnDatabase|one.two.three.four.five.six.seven.eight.nine.ten`, + Error: `unable to classify identifier: one.two.three.four.five.six.seven.eight.nine.ten`, + }, + { + Name: "validation: invalid account object identifier", + Identifier: `"share-name"|SELECT|OnDatabase|one.two`, + Error: `invalid identifier, expected fully qualified name of account object: , but instead got: .`, + }, + { + Name: "validation: invalid database object identifier", + Identifier: `"share-name"|SELECT|OnSchema|one.two.three`, + Error: `invalid identifier, expected fully qualified name of database object: ., but instead got: ..`, + }, + { + Name: "validation: invalid schema object identifier", + Identifier: `"share-name"|SELECT|OnTable|one`, + Error: `invalid identifier, expected fully qualified name of schema object: .., but instead got: `, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + id, err := ParseGrantPrivilegesToShareId(tt.Identifier) + if tt.Error == "" { + assert.NoError(t, err) + assert.Equal(t, tt.Expected, id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestGrantPrivilegesToShareIdString(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantPrivilegesToShareId + Expected string + Error string + }{ + { + Name: "grant privileges on database to share", + Identifier: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"REFERENCE_USAGE"}, + Kind: OnDatabaseShareGrantKind, + Identifier: sdk.NewAccountObjectIdentifier("database-name"), + }, + Expected: `"share-name"|REFERENCE_USAGE|OnDatabase|"database-name"`, + }, + { + Name: "grant privileges on schema to share", + Identifier: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"USAGE"}, + Kind: OnSchemaShareGrantKind, + Identifier: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), + }, + Expected: `"share-name"|USAGE|OnSchema|"database-name"."schema-name"`, + }, + // TODO(SNOW-1021686): This is wrong and should be fixed (function's last part of identifier cannot be enclosed with quotes like that) + //{ + // Name: "grant privileges on function to share", + // Identifier: GrantPrivilegesToShareId{ + // ShareName: sdk.NewExternalObjectIdentifierFromFullyQualifiedName("share-name"), + // Privileges: []string{"USAGE"}, + // Kind: OnFunctionShareGrantKind, + // Identifier: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "function-name(INT, VARCHAR)"), + // }, + // Expected: `"share-name"|USAGE|OnFunction|"database-name"."schema-name".\"function-name(INT, VARCHAR)\"`, + // }, + { + Name: "grant privileges on table to share", + Identifier: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"EVOLVE SCHEMA", "SELECT"}, + Kind: OnTableShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), + }, + Expected: `"share-name"|EVOLVE SCHEMA,SELECT|OnTable|"database-name"."schema-name"."table-name"`, + }, + { + Name: "grant privileges on all tables in schema to share", + Identifier: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"EVOLVE SCHEMA", "SELECT"}, + Kind: OnAllTablesInSchemaShareGrantKind, + Identifier: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), + }, + Expected: `"share-name"|EVOLVE SCHEMA,SELECT|OnAllTablesInSchema|"database-name"."schema-name"`, + }, + { + Name: "grant privileges on tag to share", + Identifier: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"READ"}, + Kind: OnTagShareGrantKind, + Identifier: sdk.NewAccountObjectIdentifier("tag-name"), + }, + Expected: `"share-name"|READ|OnTag|"tag-name"`, + }, + { + Name: "grant privileges on view to share", + Identifier: GrantPrivilegesToShareId{ + ShareName: sdk.NewAccountObjectIdentifier("share-name"), + Privileges: []string{"SELECT"}, + Kind: OnViewShareGrantKind, + Identifier: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "view-name"), + }, + Expected: `"share-name"|SELECT|OnView|"database-name"."schema-name"."view-name"`, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + assert.Equal(t, tt.Expected, tt.Identifier.String()) + }) + } +} diff --git a/pkg/resources/share.go b/pkg/resources/share.go index f7d0d8cb63..57664010c7 100644 --- a/pkg/resources/share.go +++ b/pkg/resources/share.go @@ -123,7 +123,7 @@ func setShareAccounts(ctx context.Context, client *sdk.Client, shareID sdk.Accou // case where the main db doesn't already exist, so it will need to be revoked // before deleting the temp db. Where USAGE hasn't been already granted it is not // an error to revoke it, so it's ok to just do the revoke every time. - err = client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeReferenceUsage, &sdk.GrantPrivilegeToShareOn{ + err = client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeReferenceUsage}, &sdk.ShareGrantOn{ Database: tempDatabaseID, }, shareID) if err != nil { @@ -131,14 +131,14 @@ func setShareAccounts(ctx context.Context, client *sdk.Client, shareID sdk.Accou } defer func() { // revoke the REFERENCE_USAGE privilege during cleanup - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeReferenceUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeReferenceUsage}, &sdk.ShareGrantOn{ Database: tempDatabaseID, }, shareID) if err != nil { log.Printf("[WARN] error revoking privilege from share (%v) err = %v", shareID.Name(), err) } // revoke the maybe automatically granted USAGE privilege during cleanup - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: tempDatabaseID, }, shareID) if err != nil { diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/NoOnOption/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/NoOnOption/test.tf new file mode 100644 index 0000000000..12a5632170 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/NoOnOption/test.tf @@ -0,0 +1,8 @@ +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/NoOnOption/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/NoOnOption/variables.tf new file mode 100644 index 0000000000..cf7961587f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/NoOnOption/variables.tf @@ -0,0 +1,7 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema/test.tf new file mode 100644 index 0000000000..1805c18714 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema/test.tf @@ -0,0 +1,24 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test_setup" { + to_share = snowflake_share.test.name + privileges = ["USAGE"] + on_database = snowflake_database.test.name +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges + on_all_tables_in_schema = "\"${snowflake_schema.test.database}\".\"${snowflake_schema.test.name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema/variables.tf new file mode 100644 index 0000000000..f0753857d4 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema/variables.tf @@ -0,0 +1,15 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant/test.tf new file mode 100644 index 0000000000..beb3470c6a --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_share" "test" { + name = var.to_share +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant/variables.tf new file mode 100644 index 0000000000..f5a1391a49 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnAllTablesInSchema_NoGrant/variables.tf @@ -0,0 +1,11 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase/test.tf new file mode 100644 index 0000000000..b4fa3ebc17 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase/test.tf @@ -0,0 +1,13 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges + on_database = snowflake_database.test.name +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase/variables.tf new file mode 100644 index 0000000000..8f3b5923f0 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase/variables.tf @@ -0,0 +1,11 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant/test.tf new file mode 100644 index 0000000000..50bdc08e8d --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant/test.tf @@ -0,0 +1,7 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_share" "test" { + name = var.to_share +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant/variables.tf new file mode 100644 index 0000000000..405daac5da --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoGrant/variables.tf @@ -0,0 +1,7 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges/test.tf new file mode 100644 index 0000000000..6026bc53b1 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + on_database = snowflake_database.test.name +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges/variables.tf new file mode 100644 index 0000000000..405daac5da --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnDatabase_NoPrivileges/variables.tf @@ -0,0 +1,7 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema/test.tf new file mode 100644 index 0000000000..6fedcefe97 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema/test.tf @@ -0,0 +1,24 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test_setup" { + to_share = snowflake_share.test.name + privileges = ["USAGE"] + on_database = snowflake_database.test.name +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges + on_schema = "\"${snowflake_schema.test.database}\".\"${snowflake_schema.test.name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema/variables.tf new file mode 100644 index 0000000000..f0753857d4 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema/variables.tf @@ -0,0 +1,15 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant/test.tf new file mode 100644 index 0000000000..beb3470c6a --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_share" "test" { + name = var.to_share +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant/variables.tf new file mode 100644 index 0000000000..f5a1391a49 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnSchema_NoGrant/variables.tf @@ -0,0 +1,11 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable/test.tf new file mode 100644 index 0000000000..58e3beddbf --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable/test.tf @@ -0,0 +1,34 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.on_table + database = snowflake_database.test.name + schema = snowflake_schema.test.name + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test_setup" { + to_share = snowflake_share.test.name + privileges = ["USAGE"] + on_database = snowflake_database.test.name +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges + on_table = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable/variables.tf new file mode 100644 index 0000000000..c4968d159c --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable/variables.tf @@ -0,0 +1,19 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "on_table" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable_NoGrant/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable_NoGrant/test.tf new file mode 100644 index 0000000000..9b65cdd148 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable_NoGrant/test.tf @@ -0,0 +1,22 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.on_table + database = snowflake_database.test.name + schema = snowflake_schema.test.name + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_share" "test" { + name = var.to_share +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable_NoGrant/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable_NoGrant/variables.tf new file mode 100644 index 0000000000..29606a0a04 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTable_NoGrant/variables.tf @@ -0,0 +1,15 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "on_table" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag/test.tf new file mode 100644 index 0000000000..728b944946 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag/test.tf @@ -0,0 +1,30 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_tag" "test" { + name = var.on_tag + database = snowflake_database.test.name + schema = snowflake_schema.test.name +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test_setup" { + to_share = snowflake_share.test.name + privileges = ["USAGE"] + on_database = snowflake_database.test.name +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges + on_tag = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_tag.test.name}\"" +} \ No newline at end of file diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag/variables.tf new file mode 100644 index 0000000000..336fc788e2 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag/variables.tf @@ -0,0 +1,19 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "on_tag" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag_NoGrant/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag_NoGrant/test.tf new file mode 100644 index 0000000000..58f711ee44 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag_NoGrant/test.tf @@ -0,0 +1,18 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_tag" "test" { + name = var.on_tag + database = snowflake_database.test.name + schema = snowflake_schema.test.name +} + +resource "snowflake_share" "test" { + name = var.to_share +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag_NoGrant/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag_NoGrant/variables.tf new file mode 100644 index 0000000000..af41461f2a --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnTag_NoGrant/variables.tf @@ -0,0 +1,15 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "on_tag" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView/test.tf new file mode 100644 index 0000000000..5c504320f9 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView/test.tf @@ -0,0 +1,42 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.on_table + database = snowflake_database.test.name + schema = snowflake_schema.test.name + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_view" "test" { + name = var.on_view + database = snowflake_database.test.name + schema = snowflake_schema.test.name + is_secure = true + statement = "select \"id\" from \"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" +} + +resource "snowflake_share" "test" { + name = var.to_share +} + +resource "snowflake_grant_privileges_to_share" "test_setup" { + to_share = snowflake_share.test.name + privileges = ["USAGE"] + on_database = snowflake_database.test.name +} + +resource "snowflake_grant_privileges_to_share" "test" { + to_share = snowflake_share.test.name + privileges = var.privileges + on_view = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_view.test.name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView/variables.tf new file mode 100644 index 0000000000..84cd501eb9 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView/variables.tf @@ -0,0 +1,23 @@ +variable "to_share" { + type = string +} + +variable "privileges" { + type = list(string) +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "on_table" { + type = string +} + +variable "on_view" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView_NoGrant/test.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView_NoGrant/test.tf new file mode 100644 index 0000000000..91137560e5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView_NoGrant/test.tf @@ -0,0 +1,26 @@ +resource "snowflake_database" "test" { + name = var.database +} + +resource "snowflake_schema" "test" { + name = var.schema + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.on_table + database = snowflake_database.test.name + schema = snowflake_schema.test.name + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_view" "test" { + name = var.on_view + database = snowflake_database.test.name + schema = snowflake_schema.test.name + is_secure = true + statement = "select \"id\" from \"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView_NoGrant/variables.tf b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView_NoGrant/variables.tf new file mode 100644 index 0000000000..6800bf8359 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantPrivilegesToShare/OnView_NoGrant/variables.tf @@ -0,0 +1,19 @@ +variable "to_share" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "on_table" { + type = string +} + +variable "on_view" { + type = string +} diff --git a/pkg/sdk/grants.go b/pkg/sdk/grants.go index 2eb79f7372..f06debc1eb 100644 --- a/pkg/sdk/grants.go +++ b/pkg/sdk/grants.go @@ -13,8 +13,8 @@ type Grants interface { RevokePrivilegesFromAccountRole(ctx context.Context, privileges *AccountRoleGrantPrivileges, on *AccountRoleGrantOn, role AccountObjectIdentifier, opts *RevokePrivilegesFromAccountRoleOptions) error GrantPrivilegesToDatabaseRole(ctx context.Context, privileges *DatabaseRoleGrantPrivileges, on *DatabaseRoleGrantOn, role DatabaseObjectIdentifier, opts *GrantPrivilegesToDatabaseRoleOptions) error RevokePrivilegesFromDatabaseRole(ctx context.Context, privileges *DatabaseRoleGrantPrivileges, on *DatabaseRoleGrantOn, role DatabaseObjectIdentifier, opts *RevokePrivilegesFromDatabaseRoleOptions) error - GrantPrivilegeToShare(ctx context.Context, privilege ObjectPrivilege, on *GrantPrivilegeToShareOn, to AccountObjectIdentifier) error - RevokePrivilegeFromShare(ctx context.Context, privilege ObjectPrivilege, on *RevokePrivilegeFromShareOn, from AccountObjectIdentifier) error + GrantPrivilegeToShare(ctx context.Context, privileges []ObjectPrivilege, on *ShareGrantOn, to AccountObjectIdentifier) error + RevokePrivilegeFromShare(ctx context.Context, privileges []ObjectPrivilege, on *ShareGrantOn, from AccountObjectIdentifier) error GrantOwnership(ctx context.Context, on OwnershipGrantOn, to OwnershipGrantTo, opts *GrantOwnershipOptions) error Show(ctx context.Context, opts *ShowGrantOptions) ([]Grant, error) @@ -120,17 +120,18 @@ type RevokePrivilegesFromDatabaseRoleOptions struct { // grantPrivilegeToShareOptions is based on https://docs.snowflake.com/en/sql-reference/sql/grant-privilege-share. type grantPrivilegeToShareOptions struct { - grant bool `ddl:"static" sql:"GRANT"` - privilege ObjectPrivilege `ddl:"keyword"` - On *GrantPrivilegeToShareOn `ddl:"keyword" sql:"ON"` - to AccountObjectIdentifier `ddl:"identifier" sql:"TO SHARE"` + grant bool `ddl:"static" sql:"GRANT"` + privileges []ObjectPrivilege `ddl:"-"` + On *ShareGrantOn `ddl:"keyword" sql:"ON"` + to AccountObjectIdentifier `ddl:"identifier" sql:"TO SHARE"` } -type GrantPrivilegeToShareOn struct { +type ShareGrantOn struct { Database AccountObjectIdentifier `ddl:"identifier" sql:"DATABASE"` Schema DatabaseObjectIdentifier `ddl:"identifier" sql:"SCHEMA"` Function SchemaObjectIdentifier `ddl:"identifier" sql:"FUNCTION"` Table *OnTable `ddl:"-"` + Tag SchemaObjectIdentifier `ddl:"identifier" sql:"TAG"` View SchemaObjectIdentifier `ddl:"identifier" sql:"VIEW"` } @@ -141,17 +142,10 @@ type OnTable struct { // revokePrivilegeFromShareOptions is based on https://docs.snowflake.com/en/sql-reference/sql/revoke-privilege-share. type revokePrivilegeFromShareOptions struct { - revoke bool `ddl:"static" sql:"REVOKE"` - privilege ObjectPrivilege `ddl:"keyword"` - On *RevokePrivilegeFromShareOn `ddl:"keyword" sql:"ON"` - from AccountObjectIdentifier `ddl:"identifier" sql:"FROM SHARE"` -} - -type RevokePrivilegeFromShareOn struct { - Database AccountObjectIdentifier `ddl:"identifier" sql:"DATABASE"` - Schema DatabaseObjectIdentifier `ddl:"identifier" sql:"SCHEMA"` - Table *OnTable `ddl:"-"` - View *OnView `ddl:"-"` + revoke bool `ddl:"static" sql:"REVOKE"` + privileges []ObjectPrivilege `ddl:"-"` + On *ShareGrantOn `ddl:"keyword" sql:"ON"` + from AccountObjectIdentifier `ddl:"identifier" sql:"FROM SHARE"` } type OnView struct { diff --git a/pkg/sdk/grants_impl.go b/pkg/sdk/grants_impl.go index 31f978dd64..5df299e877 100644 --- a/pkg/sdk/grants_impl.go +++ b/pkg/sdk/grants_impl.go @@ -56,20 +56,20 @@ func (v *grants) RevokePrivilegesFromDatabaseRole(ctx context.Context, privilege return validateAndExec(v.client, ctx, opts) } -func (v *grants) GrantPrivilegeToShare(ctx context.Context, privilege ObjectPrivilege, on *GrantPrivilegeToShareOn, to AccountObjectIdentifier) error { +func (v *grants) GrantPrivilegeToShare(ctx context.Context, privileges []ObjectPrivilege, on *ShareGrantOn, to AccountObjectIdentifier) error { opts := &grantPrivilegeToShareOptions{ - privilege: privilege, - On: on, - to: to, + privileges: privileges, + On: on, + to: to, } return validateAndExec(v.client, ctx, opts) } -func (v *grants) RevokePrivilegeFromShare(ctx context.Context, privilege ObjectPrivilege, on *RevokePrivilegeFromShareOn, id AccountObjectIdentifier) error { +func (v *grants) RevokePrivilegeFromShare(ctx context.Context, privileges []ObjectPrivilege, on *ShareGrantOn, id AccountObjectIdentifier) error { opts := &revokePrivilegeFromShareOptions{ - privilege: privilege, - On: on, - from: id, + privileges: privileges, + On: on, + from: id, } return validateAndExec(v.client, ctx, opts) } diff --git a/pkg/sdk/grants_test.go b/pkg/sdk/grants_test.go index 9bda91c4d1..69ccf6b44c 100644 --- a/pkg/sdk/grants_test.go +++ b/pkg/sdk/grants_test.go @@ -728,8 +728,8 @@ func TestGrantPrivilegeToShare(t *testing.T) { t.Run("on database", func(t *testing.T) { otherID := RandomAccountObjectIdentifier() opts := &grantPrivilegeToShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &GrantPrivilegeToShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Database: otherID, }, to: id, @@ -740,8 +740,8 @@ func TestGrantPrivilegeToShare(t *testing.T) { t.Run("on schema", func(t *testing.T) { otherID := RandomDatabaseObjectIdentifier() opts := &grantPrivilegeToShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &GrantPrivilegeToShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Schema: otherID, }, to: id, @@ -752,8 +752,8 @@ func TestGrantPrivilegeToShare(t *testing.T) { t.Run("on table", func(t *testing.T) { otherID := RandomSchemaObjectIdentifier() opts := &grantPrivilegeToShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &GrantPrivilegeToShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Table: &OnTable{ Name: otherID, }, @@ -766,8 +766,8 @@ func TestGrantPrivilegeToShare(t *testing.T) { t.Run("on all tables", func(t *testing.T) { otherID := RandomDatabaseObjectIdentifier() opts := &grantPrivilegeToShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &GrantPrivilegeToShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Table: &OnTable{ AllInSchema: otherID, }, @@ -780,8 +780,8 @@ func TestGrantPrivilegeToShare(t *testing.T) { t.Run("on view", func(t *testing.T) { otherID := RandomSchemaObjectIdentifier() opts := &grantPrivilegeToShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &GrantPrivilegeToShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ View: otherID, }, to: id, @@ -795,8 +795,8 @@ func TestRevokePrivilegeFromShare(t *testing.T) { t.Run("on database", func(t *testing.T) { otherID := RandomAccountObjectIdentifier() opts := &revokePrivilegeFromShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &RevokePrivilegeFromShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Database: otherID, }, from: id, @@ -807,8 +807,8 @@ func TestRevokePrivilegeFromShare(t *testing.T) { t.Run("on schema", func(t *testing.T) { otherID := RandomDatabaseObjectIdentifier() opts := &revokePrivilegeFromShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &RevokePrivilegeFromShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Schema: otherID, }, from: id, @@ -819,8 +819,8 @@ func TestRevokePrivilegeFromShare(t *testing.T) { t.Run("on table", func(t *testing.T) { otherID := RandomSchemaObjectIdentifier() opts := &revokePrivilegeFromShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &RevokePrivilegeFromShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Table: &OnTable{ Name: otherID, }, @@ -833,8 +833,8 @@ func TestRevokePrivilegeFromShare(t *testing.T) { t.Run("on all tables", func(t *testing.T) { otherID := RandomDatabaseObjectIdentifier() opts := &revokePrivilegeFromShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &RevokePrivilegeFromShareOn{ + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ Table: &OnTable{ AllInSchema: otherID, }, @@ -847,29 +847,24 @@ func TestRevokePrivilegeFromShare(t *testing.T) { t.Run("on view", func(t *testing.T) { otherID := RandomSchemaObjectIdentifier() opts := &revokePrivilegeFromShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &RevokePrivilegeFromShareOn{ - View: &OnView{ - Name: otherID, - }, + privileges: []ObjectPrivilege{ObjectPrivilegeUsage}, + On: &ShareGrantOn{ + View: otherID, }, from: id, } assertOptsValidAndSQLEquals(t, opts, "REVOKE USAGE ON VIEW %s FROM SHARE %s", otherID.FullyQualifiedName(), id.FullyQualifiedName()) }) - t.Run("on all views", func(t *testing.T) { - otherID := RandomDatabaseObjectIdentifier() + t.Run("on tag", func(t *testing.T) { opts := &revokePrivilegeFromShareOptions{ - privilege: ObjectPrivilegeUsage, - On: &RevokePrivilegeFromShareOn{ - View: &OnView{ - AllInSchema: otherID, - }, + privileges: []ObjectPrivilege{ObjectPrivilegeRead}, + On: &ShareGrantOn{ + Tag: NewSchemaObjectIdentifier("database-name", "schema-name", "tag-name"), }, from: id, } - assertOptsValidAndSQLEquals(t, opts, "REVOKE USAGE ON ALL VIEWS IN SCHEMA %s FROM SHARE %s", otherID.FullyQualifiedName(), id.FullyQualifiedName()) + assertOptsValidAndSQLEquals(t, opts, "REVOKE READ ON TAG \"database-name\".\"schema-name\".\"tag-name\" FROM SHARE %s", id.FullyQualifiedName()) }) } diff --git a/pkg/sdk/grants_validations.go b/pkg/sdk/grants_validations.go index 3f7317afbf..844580cc87 100644 --- a/pkg/sdk/grants_validations.go +++ b/pkg/sdk/grants_validations.go @@ -233,7 +233,7 @@ func (opts *grantPrivilegeToShareOptions) validate() error { if !ValidObjectIdentifier(opts.to) { errs = append(errs, ErrInvalidObjectIdentifier) } - if !valueSet(opts.On) || opts.privilege == "" { + if !valueSet(opts.On) || len(opts.privileges) == 0 { errs = append(errs, fmt.Errorf("on and privilege are required")) } if valueSet(opts.On) { @@ -244,10 +244,10 @@ func (opts *grantPrivilegeToShareOptions) validate() error { return errors.Join(errs...) } -func (v *GrantPrivilegeToShareOn) validate() error { +func (v *ShareGrantOn) validate() error { var errs []error - if !exactlyOneValueSet(v.Database, v.Schema, v.Function, v.Table, v.View) { - errs = append(errs, errExactlyOneOf("GrantPrivilegeToShareOn", "Database", "Schema", "Function", "Table", "View")) + if !exactlyOneValueSet(v.Database, v.Schema, v.Function, v.Table, v.Tag, v.View) { + errs = append(errs, errExactlyOneOf("ShareGrantOn", "Database", "Schema", "Function", "Table", "Tag", "View")) } if valueSet(v.Table) { if err := v.Table.validate(); err != nil { @@ -272,13 +272,10 @@ func (opts *revokePrivilegeFromShareOptions) validate() error { if !ValidObjectIdentifier(opts.from) { errs = append(errs, ErrInvalidObjectIdentifier) } - if !valueSet(opts.On) || opts.privilege == "" { - errs = append(errs, errNotSet("revokePrivilegeFromShareOptions", "On", "privilege")) + if !valueSet(opts.On) || len(opts.privileges) == 0 { + errs = append(errs, errNotSet("revokePrivilegeFromShareOptions", "On", "privileges")) } if valueSet(opts.On) { - if !exactlyOneValueSet(opts.On.Database, opts.On.Schema, opts.On.Table, opts.On.View) { - errs = append(errs, errExactlyOneOf("revokePrivilegeFromShareOptions", "On.Database", "On.Schema", "On.Table", "On.View")) - } if err := opts.On.validate(); err != nil { errs = append(errs, err) } @@ -286,24 +283,6 @@ func (opts *revokePrivilegeFromShareOptions) validate() error { return errors.Join(errs...) } -func (v *RevokePrivilegeFromShareOn) validate() error { - var errs []error - if !exactlyOneValueSet(v.Database, v.Schema, v.Table, v.View) { - errs = append(errs, errExactlyOneOf("RevokePrivilegeFromShareOn", "Database", "Schema", "Table", "View")) - } - if valueSet(v.Table) { - if err := v.Table.validate(); err != nil { - errs = append(errs, err) - } - } - if valueSet(v.View) { - if err := v.View.validate(); err != nil { - errs = append(errs, err) - } - } - return errors.Join(errs...) -} - func (v *OnView) validate() error { if !exactlyOneValueSet(v.Name, v.AllInSchema) { return errExactlyOneOf("OnView", "Name", "AllInSchema") diff --git a/pkg/sdk/privileges.go b/pkg/sdk/privileges.go index f07c190b90..201ff6f110 100644 --- a/pkg/sdk/privileges.go +++ b/pkg/sdk/privileges.go @@ -243,9 +243,10 @@ func (p SchemaObjectPrivilege) String() string { type ObjectPrivilege string const ( + ObjectPrivilegeReferenceUsage ObjectPrivilege = "REFERENCE_USAGE" ObjectPrivilegeUsage ObjectPrivilege = "USAGE" ObjectPrivilegeSelect ObjectPrivilege = "SELECT" - ObjectPrivilegeReferenceUsage ObjectPrivilege = "REFERENCE_USAGE" + ObjectPrivilegeRead ObjectPrivilege = "READ" ) func (p ObjectPrivilege) String() string { diff --git a/pkg/sdk/testint/database_role_integration_test.go b/pkg/sdk/testint/database_role_integration_test.go index c4c9a76e42..b6b883c10e 100644 --- a/pkg/sdk/testint/database_role_integration_test.go +++ b/pkg/sdk/testint/database_role_integration_test.go @@ -262,7 +262,7 @@ func TestInt_DatabaseRoles(t *testing.T) { share, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{Database: testDb(t).ID()}, share.ID()) + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{Database: testDb(t).ID()}, share.ID()) require.NoError(t, err) grantRequest := sdk.NewGrantDatabaseRoleToShareRequest(roleId, share.ID()) diff --git a/pkg/sdk/testint/databases_integration_test.go b/pkg/sdk/testint/databases_integration_test.go index 7441ff7040..b577b7ffd4 100644 --- a/pkg/sdk/testint/databases_integration_test.go +++ b/pkg/sdk/testint/databases_integration_test.go @@ -119,12 +119,12 @@ func TestInt_CreateShared(t *testing.T) { shareTest, shareCleanup := createShare(t, secondaryClient) t.Cleanup(shareCleanup) - err := secondaryClient.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := secondaryClient.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: databaseTest.ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err := secondaryClient.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err := secondaryClient.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: databaseTest.ID(), }, shareTest.ID()) require.NoError(t, err) diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index a4c6cb7063..90e8d1a539 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -409,39 +409,60 @@ func TestInt_GrantPrivilegeToShare(t *testing.T) { ctx := testContext(t) shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - t.Run("without options", func(t *testing.T) { - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, nil, shareTest.ID()) - require.Error(t, err) - }) + + assertGrant := func(t *testing.T, grants []sdk.Grant, onId sdk.ObjectIdentifier, privilege sdk.ObjectPrivilege) { + t.Helper() + var shareGrant *sdk.Grant + for i, grant := range grants { + if grant.GranteeName.Name() == shareTest.ID().Name() && grant.Privilege == string(privilege) { + shareGrant = &grants[i] + break + } + } + assert.NotNil(t, shareGrant) + assert.Equal(t, sdk.ObjectTypeTable, shareGrant.GrantedOn) + assert.Equal(t, sdk.ObjectTypeShare, shareGrant.GrantedTo) + assert.Equal(t, onId.FullyQualifiedName(), shareGrant.Name.FullyQualifiedName()) + } + t.Run("with options", func(t *testing.T) { - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) + + t.Cleanup(func() { + err := client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ + Database: testDb(t).ID(), + }, shareTest.ID()) + assert.NoError(t, err) + }) + + table, tableCleanup := createTable(t, client, testDb(t), testSchema(t)) + t.Cleanup(tableCleanup) + + err = client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeSelect}, &sdk.ShareGrantOn{ + Table: &sdk.OnTable{ + AllInSchema: testSchema(t).ID(), + }, + }, shareTest.ID()) + require.NoError(t, err) + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ On: &sdk.ShowGrantsOn{ Object: &sdk.Object{ - ObjectType: sdk.ObjectTypeDatabase, - Name: testDb(t).ID(), + ObjectType: sdk.ObjectTypeTable, + Name: table.ID(), }, }, }) require.NoError(t, err) - assert.LessOrEqual(t, 2, len(grants)) - var shareGrant *sdk.Grant - for i, grant := range grants { - if grant.GranteeName.Name() == shareTest.ID().Name() { - shareGrant = &grants[i] - break - } - } - assert.NotNil(t, shareGrant) - assert.Equal(t, string(sdk.ObjectPrivilegeUsage), shareGrant.Privilege) - assert.Equal(t, sdk.ObjectTypeDatabase, shareGrant.GrantedOn) - assert.Equal(t, sdk.ObjectTypeShare, shareGrant.GrantedTo) - assert.Equal(t, testDb(t).ID().Name(), shareGrant.Name.Name()) - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ - Database: testDb(t).ID(), + assertGrant(t, grants, table.ID(), sdk.ObjectPrivilegeSelect) + + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeSelect}, &sdk.ShareGrantOn{ + Table: &sdk.OnTable{ + AllInSchema: testSchema(t).ID(), + }, }, shareTest.ID()) require.NoError(t, err) }) @@ -452,16 +473,16 @@ func TestInt_RevokePrivilegeToShare(t *testing.T) { ctx := testContext(t) shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) t.Run("without options", func(t *testing.T) { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, nil, shareTest.ID()) + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, nil, shareTest.ID()) require.Error(t, err) }) t.Run("with options", func(t *testing.T) { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) @@ -587,12 +608,12 @@ func TestInt_ShowGrants(t *testing.T) { ctx := testContext(t) shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) diff --git a/pkg/sdk/testint/shares_integration_test.go b/pkg/sdk/testint/shares_integration_test.go index 0050a1e43c..8b643e6467 100644 --- a/pkg/sdk/testint/shares_integration_test.go +++ b/pkg/sdk/testint/shares_integration_test.go @@ -131,12 +131,12 @@ func TestInt_SharesAlter(t *testing.T) { t.Run("add and remove accounts", func(t *testing.T) { shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) }) @@ -189,12 +189,12 @@ func TestInt_SharesAlter(t *testing.T) { shareTest, shareCleanup := createShare(t, secondaryClient) t.Cleanup(shareCleanup) - err := secondaryClient.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := secondaryClient.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: db.ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err := secondaryClient.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err := secondaryClient.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: db.ID(), }, shareTest.ID()) require.NoError(t, err) @@ -229,12 +229,12 @@ func TestInt_SharesAlter(t *testing.T) { shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) @@ -284,12 +284,12 @@ func TestInt_SharesAlter(t *testing.T) { t.Run("set and unset tags", func(t *testing.T) { shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) @@ -345,12 +345,12 @@ func TestInt_ShareDescribeProvider(t *testing.T) { shareTest, shareCleanup := createShare(t, client) t.Cleanup(shareCleanup) - err := client.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := client.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err = client.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = client.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: testDb(t).ID(), }, shareTest.ID()) require.NoError(t, err) @@ -378,12 +378,12 @@ func TestInt_ShareDescribeConsumer(t *testing.T) { shareTest, shareCleanup := createShare(t, providerClient) t.Cleanup(shareCleanup) - err := providerClient.Grants.GrantPrivilegeToShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.GrantPrivilegeToShareOn{ + err := providerClient.Grants.GrantPrivilegeToShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: db.ID(), }, shareTest.ID()) require.NoError(t, err) t.Cleanup(func() { - err = providerClient.Grants.RevokePrivilegeFromShare(ctx, sdk.ObjectPrivilegeUsage, &sdk.RevokePrivilegeFromShareOn{ + err = providerClient.Grants.RevokePrivilegeFromShare(ctx, []sdk.ObjectPrivilege{sdk.ObjectPrivilegeUsage}, &sdk.ShareGrantOn{ Database: db.ID(), }, shareTest.ID()) require.NoError(t, err) diff --git a/templates/resources/grant_privileges_to_share.md.tmpl b/templates/resources/grant_privileges_to_share.md.tmpl new file mode 100644 index 0000000000..f2639461b1 --- /dev/null +++ b/templates/resources/grant_privileges_to_share.md.tmpl @@ -0,0 +1,53 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +~> **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. + +# {{.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 `"".""` + +Import is supported using the following syntax: + +`terraform import "|||"` + +where: +- share_name - fully qualified identifier +- privileges - list of privileges, comma separated. See the available privileges for given object types: https://docs.snowflake.com/en/sql-reference/sql/grant-privilege-share#syntax +- grant_type - enum +- grant_identifier - fully qualified identifier + +### OnDatabase +`terraform import "||OnDatabase|"` + +### OnSchema +`terraform import "||OnSchema|."` + +### OnTable +`terraform import "||OnTable|.."` + +### OnSchema +`terraform import "||OnAllTablesInSchema|."` + +### OnTag +`terraform import "||OnTag|.."` + +### OnView +`terraform import "||OnView|.."`