diff --git a/docs/resources/grant_privileges_to_account_role.md b/docs/resources/grant_privileges_to_account_role.md index 5572b3c289..64af3480f7 100644 --- a/docs/resources/grant_privileges_to_account_role.md +++ b/docs/resources/grant_privileges_to_account_role.md @@ -331,7 +331,7 @@ Optional: ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for schema object it is `""."".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/docs/resources/grant_privileges_to_database_role.md b/docs/resources/grant_privileges_to_database_role.md index 104bcdc9ab..e853927cc6 100644 --- a/docs/resources/grant_privileges_to_database_role.md +++ b/docs/resources/grant_privileges_to_database_role.md @@ -217,8 +217,8 @@ Required: Optional: -- `in_database` (String) -- `in_schema` (String) +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. @@ -230,12 +230,12 @@ Required: Optional: -- `in_database` (String) -- `in_schema` (String) +- `in_database` (String) The fully qualified name of the database. +- `in_schema` (String) The fully qualified name of the schema. ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for database object it is `"".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/docs/technical-documentation/resource_migration.md b/docs/technical-documentation/resource_migration.md index 8afc3c83dc..d98ce1898e 100644 --- a/docs/technical-documentation/resource_migration.md +++ b/docs/technical-documentation/resource_migration.md @@ -46,7 +46,7 @@ resource "snowflake_grant_privileges_to_account_role" "new_resource" { depends_on = [snowflake_database.test, snowflake_role.a, snowflake_role.b] for_each = toset([snowflake_role.a.name, snowflake_role.b.name]) privileges = ["USAGE"] - role_name = each.key + account_role_name = each.key on_account_object { object_type = "DATABASE" object_name = snowflake_database.test.name diff --git a/pkg/resources/grant_ownership.go b/pkg/resources/grant_ownership.go new file mode 100644 index 0000000000..99e0a771a8 --- /dev/null +++ b/pkg/resources/grant_ownership.go @@ -0,0 +1,612 @@ +package resources + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var grantOwnershipSchema = map[string]*schema.Schema{ + "account_role_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the account role to which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "account_role_name", + "database_role_name", + }, + }, + "database_role_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database role to which privileges will be granted.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: []string{ + "account_role_name", + "database_role_name", + }, + }, + "outbound_privileges": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies whether to remove or transfer all existing outbound privileges on the object when ownership is transferred to a new role. Available options are: REVOKE for removing existing privileges and COPY to transfer them with ownership. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#optional-parameters).", + ValidateFunc: validation.StringInSlice([]string{ + "COPY", + "REVOKE", + }, true), + }, + "on": { + Type: schema.TypeList, + Required: true, + ForceNew: true, + Description: "Configures which object(s) should transfer their ownership to the specified role.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "object_type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: fmt.Sprintf("Specifies the type of object on which you are transferring ownership. Available values are: %s", strings.Join(sdk.ValidGrantOwnershipObjectTypesString, " | ")), + RequiredWith: []string{ + "on.0.object_name", + }, + ValidateFunc: validation.StringInSlice(sdk.ValidGrantOwnershipObjectTypesString, true), + }, + "object_name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "Specifies the identifier for the object on which you are transferring ownership.", + RequiredWith: []string{ + "on.0.object_type", + }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + }, + "all": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Configures the privilege to be granted on all objects in either a database or schema.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: grantOwnershipBulkOperationSchema("all"), + }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + }, + "future": { + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Description: "Configures the privilege to be granted on all objects in either a database or schema.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: grantOwnershipBulkOperationSchema("future"), + }, + ExactlyOneOf: []string{ + "on.0.object_name", + "on.0.all", + "on.0.future", + }, + }, + }, + }, + }, +} + +func grantOwnershipBulkOperationSchema(branchName string) map[string]*schema.Schema { + return map[string]*schema.Schema{ + "object_type_plural": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: fmt.Sprintf("Specifies the type of object in plural form on which you are transferring ownership. Available values are: %s. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters).", strings.Join(sdk.ValidGrantOwnershipPluralObjectTypesString, " | ")), + ValidateFunc: validation.StringInSlice(sdk.ValidGrantOwnershipPluralObjectTypesString, true), + }, + "in_database": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the database.", + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + fmt.Sprintf("on.0.%s.0.in_database", branchName), + fmt.Sprintf("on.0.%s.0.in_schema", branchName), + }, + }, + "in_schema": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: "The fully qualified name of the schema.", + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + ExactlyOneOf: []string{ + fmt.Sprintf("on.0.%s.0.in_database", branchName), + fmt.Sprintf("on.0.%s.0.in_schema", branchName), + }, + }, + } +} + +func GrantOwnership() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateGrantOwnership, + // There's no Update, because every field is marked as ForceNew + DeleteContext: DeleteGrantOwnership, + ReadContext: ReadGrantOwnership, + + Schema: grantOwnershipSchema, + Importer: &schema.ResourceImporter{ + StateContext: ImportGrantOwnership(), + }, + } +} + +func ImportGrantOwnership() schema.StateContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Entering import grant privileges to account role") + id, err := ParseGrantOwnershipId(d.Id()) + if err != nil { + return nil, err + } + logging.DebugLogger.Printf("[DEBUG] Imported identifier: %s", id.String()) + + switch id.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + if err := d.Set("account_role_name", id.AccountRoleName.Name()); err != nil { + return nil, err + } + case ToDatabaseGrantOwnershipTargetRoleKind: + if err := d.Set("database_role_name", id.DatabaseRoleName.FullyQualifiedName()); err != nil { + return nil, err + } + } + + if id.OutboundPrivilegesBehavior != nil { + if err := d.Set("outbound_privileges", *id.OutboundPrivilegesBehavior); err != nil { + return nil, err + } + } + + switch id.Kind { + case OnObjectGrantOwnershipKind: + data := id.Data.(*OnObjectGrantOwnershipData) + + onObject := make(map[string]any) + onObject["object_type"] = data.ObjectType.String() + if objectName, ok := any(data.ObjectName).(sdk.AccountObjectIdentifier); ok { + onObject["object_name"] = objectName.Name() + } else { + onObject["object_name"] = data.ObjectName.FullyQualifiedName() + } + + if err := d.Set("on", []any{onObject}); err != nil { + return nil, err + } + case OnAllGrantOwnershipKind, OnFutureGrantOwnershipKind: + data := id.Data.(*BulkOperationGrantData) + + on := make(map[string]any) + onAllOrFuture := make(map[string]any) + onAllOrFuture["object_type_plural"] = data.ObjectNamePlural.String() + switch data.Kind { + case InDatabaseBulkOperationGrantKind: + onAllOrFuture["in_database"] = data.Database.Name() + case InSchemaBulkOperationGrantKind: + onAllOrFuture["in_schema"] = data.Schema.FullyQualifiedName() + } + + switch id.Kind { + case OnAllGrantOwnershipKind: + on["all"] = []any{onAllOrFuture} + case OnFutureGrantOwnershipKind: + on["future"] = []any{onAllOrFuture} + } + + if err := d.Set("on", []any{on}); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil + } +} + +func CreateGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := createGrantOwnershipIdFromSchema(d) + if err != nil { + return diag.FromErr(err) + } + logging.DebugLogger.Printf("[DEBUG] created identifier from schema: %s", id.String()) + + grantOn, err := getOwnershipGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + + err = client.Grants.GrantOwnership( + ctx, + *grantOn, + getOwnershipGrantTo(d), + getOwnershipGrantOpts(id), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred during grant ownership", + Detail: fmt.Sprintf("Id: %s\nError: %s", id.String(), err), + }, + } + } + + logging.DebugLogger.Printf("[DEBUG] Setting identifier to %s", id.String()) + d.SetId(id.String()) + + return ReadGrantOwnership(ctx, d, meta) +} + +func DeleteGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + client := meta.(*provider.Context).Client + + id, err := ParseGrantOwnershipId(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), + }, + } + } + + grantOn, err := getOwnershipGrantOn(d) + if err != nil { + return diag.FromErr(err) + } + + if grantOn.Future != nil { + // TODO (SNOW-1182623): Still waiting for the response on the behavior/SQL syntax we should use here + } else { + accountRoleName, err := client.ContextFunctions.CurrentRole(ctx) + if err != nil { + return diag.FromErr(err) + } + + err = client.Grants.GrantOwnership( // TODO: Should we always set outbound privileges to COPY in delete operation or set it to the config value? + ctx, + *grantOn, + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(accountRoleName)), + }, + getOwnershipGrantOpts(id), + ) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "An error occurred when transferring ownership back to the original role", + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), + }, + } + } + } + + d.SetId("") + + return nil +} + +func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { + id, err := ParseGrantOwnershipId(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), + }, + } + } + + opts, grantedOn := prepareShowGrantsRequestForGrantOwnership(id) + if opts == nil { + return nil + } + + client := meta.(*provider.Context).Client + + 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), + }, + } + } + + ownershipFound := false + + for _, grant := range grants { + if grant.Privilege != "OWNERSHIP" { + continue + } + + // Future grants do not have grantedBy, only current grants do. + // If grantedby is an empty string, it means terraform could not have created the grant + if (opts.Future == nil || !*opts.Future) && grant.GrantedBy.Name() == "" { + continue + } + + // grant_on is for future grants, granted_on is for current grants. + // They function the same way though in a test for matching the object type + if grantedOn != grant.GrantedOn && grantedOn != grant.GrantOn { + continue + } + + switch id.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + if grant.GranteeName.Name() == id.AccountRoleName.Name() { + ownershipFound = true + } + case ToDatabaseGrantOwnershipTargetRoleKind: + if grant.GranteeName.Name() == id.DatabaseRoleName.Name() { + ownershipFound = true + } + } + } + + if !ownershipFound { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Couldn't find OWNERSHIP privilege on target object", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } + + return nil +} + +// TODO(SNOW-1229218): Make sdk.ObjectType + string objectName to sdk.ObjectIdentifier mapping available in the sdk (for all object types). +func getOnObjectIdentifier(objectType sdk.ObjectType, objectName string) (sdk.ObjectIdentifier, error) { + identifier, err := helpers.DecodeSnowflakeParameterID(objectName) + if err != nil { + return nil, err + } + + switch objectType { + case sdk.ObjectTypeComputePool, + sdk.ObjectTypeDatabase, + sdk.ObjectTypeExternalVolume, + sdk.ObjectTypeFailoverGroup, + sdk.ObjectTypeIntegration, + sdk.ObjectTypeNetworkPolicy, + sdk.ObjectTypeReplicationGroup, + sdk.ObjectTypeRole, + sdk.ObjectTypeUser, + sdk.ObjectTypeWarehouse: + return sdk.NewAccountObjectIdentifier(objectName), nil + case sdk.ObjectTypeDatabaseRole, + sdk.ObjectTypeSchema: + if _, ok := identifier.(sdk.DatabaseObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected database object identifier", objectName)) + } + case sdk.ObjectTypeAggregationPolicy, + sdk.ObjectTypeAlert, + sdk.ObjectTypeAuthenticationPolicy, + sdk.ObjectTypeDynamicTable, + sdk.ObjectTypeEventTable, + sdk.ObjectTypeExternalTable, + sdk.ObjectTypeFileFormat, + sdk.ObjectTypeFunction, + sdk.ObjectTypeHybridTable, + sdk.ObjectTypeIcebergTable, + sdk.ObjectTypeImageRepository, + sdk.ObjectTypeMaterializedView, + sdk.ObjectTypeNetworkRule, + sdk.ObjectTypePackagesPolicy, + sdk.ObjectTypePipe, + sdk.ObjectTypeProcedure, + sdk.ObjectTypeMaskingPolicy, + sdk.ObjectTypePasswordPolicy, + sdk.ObjectTypeProjectionPolicy, + sdk.ObjectTypeRowAccessPolicy, + sdk.ObjectTypeSessionPolicy, + sdk.ObjectTypeSecret, + sdk.ObjectTypeSequence, + sdk.ObjectTypeStage, + sdk.ObjectTypeStream, + sdk.ObjectTypeTable, + sdk.ObjectTypeTag, + sdk.ObjectTypeTask, + sdk.ObjectTypeView: + if _, ok := identifier.(sdk.SchemaObjectIdentifier); !ok { + return nil, sdk.NewError(fmt.Sprintf("invalid object_name %s, expected schema object identifier", objectName)) + } + default: + return nil, sdk.NewError(fmt.Sprintf("object_type %s is not supported, please create a feature request for the provider if given object_type should be supported", objectType)) + } + + return identifier, nil +} + +func getOwnershipGrantOn(d *schema.ResourceData) (*sdk.OwnershipGrantOn, error) { + ownershipGrantOn := new(sdk.OwnershipGrantOn) + + on := d.Get("on").([]any)[0].(map[string]any) + onObjectType := on["object_type"].(string) + onObjectName := on["object_name"].(string) + onAll := on["all"].([]any) + onFuture := on["future"].([]any) + + switch { + case len(onObjectType) > 0 && len(onObjectName) > 0: + objectType := sdk.ObjectType(strings.ToUpper(onObjectType)) + objectName, err := getOnObjectIdentifier(objectType, onObjectName) + if err != nil { + return nil, err + } + ownershipGrantOn.Object = &sdk.Object{ + ObjectType: objectType, + Name: objectName, + } + case len(onAll) > 0: + ownershipGrantOn.All = getGrantOnSchemaObjectIn(onAll[0].(map[string]any)) + case len(onFuture) > 0: + ownershipGrantOn.Future = getGrantOnSchemaObjectIn(onFuture[0].(map[string]any)) + } + + return ownershipGrantOn, nil +} + +func getOwnershipGrantTo(d *schema.ResourceData) sdk.OwnershipGrantTo { + var ownershipGrantTo sdk.OwnershipGrantTo + + if accountRoleName, ok := d.GetOk("account_role_name"); ok { + ownershipGrantTo.AccountRoleName = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(accountRoleName.(string))) + } + + if databaseRoleName, ok := d.GetOk("database_role_name"); ok { + ownershipGrantTo.DatabaseRoleName = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName.(string))) + } + + return ownershipGrantTo +} + +func getOwnershipGrantOpts(id *GrantOwnershipId) *sdk.GrantOwnershipOptions { + opts := new(sdk.GrantOwnershipOptions) + + if id != nil && id.OutboundPrivilegesBehavior != nil { + outboundPrivileges := id.OutboundPrivilegesBehavior.ToOwnershipCurrentGrantsOutboundPrivileges() + if outboundPrivileges != nil { + opts.CurrentGrants = &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: *outboundPrivileges, + } + } + } + + return opts +} + +func prepareShowGrantsRequestForGrantOwnership(id *GrantOwnershipId) (*sdk.ShowGrantOptions, sdk.ObjectType) { + opts := new(sdk.ShowGrantOptions) + var grantedOn sdk.ObjectType + + switch id.Kind { + case OnObjectGrantOwnershipKind: + data := id.Data.(*OnObjectGrantOwnershipData) + grantedOn = data.ObjectType + opts.On = &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: data.ObjectType, + Name: data.ObjectName, + }, + } + case OnAllGrantOwnershipKind: // TODO: discuss if we want to let users do this (lose control over ownership for all objects in x during delete operation - we can also add a flag that would skip delete operation when on_all is set) + switch data := id.Data.(*BulkOperationGrantData); data.Kind { + case InDatabaseBulkOperationGrantKind: + log.Printf("[INFO] Show with on.all option is skipped. No changes in ownership on all %s in database %s in Snowflake will be detected.", data.ObjectNamePlural, data.Database) + case InSchemaBulkOperationGrantKind: + log.Printf("[INFO] Show with on.all option is skipped. No changes in ownership on all %s in schema %s in Snowflake will be detected.", data.ObjectNamePlural, data.Schema) + } + return nil, "" + case OnFutureGrantOwnershipKind: + data := id.Data.(*BulkOperationGrantData) + grantedOn = data.ObjectNamePlural.Singular() + opts.Future = sdk.Bool(true) + + switch data.Kind { + case InDatabaseBulkOperationGrantKind: + opts.In = &sdk.ShowGrantsIn{ + Database: data.Database, + } + case InSchemaBulkOperationGrantKind: + opts.In = &sdk.ShowGrantsIn{ + Schema: data.Schema, + } + } + } + + return opts, grantedOn +} + +func createGrantOwnershipIdFromSchema(d *schema.ResourceData) (*GrantOwnershipId, error) { + id := new(GrantOwnershipId) + accountRoleName, accountRoleNameOk := d.GetOk("account_role_name") + databaseRoleName, databaseRoleNameOk := d.GetOk("database_role_name") + + switch { + case accountRoleNameOk: + id.GrantOwnershipTargetRoleKind = ToAccountGrantOwnershipTargetRoleKind + id.AccountRoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(accountRoleName.(string)) + case databaseRoleNameOk: + id.GrantOwnershipTargetRoleKind = ToDatabaseGrantOwnershipTargetRoleKind + id.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(databaseRoleName.(string)) + } + + outboundPrivileges, outboundPrivilegesOk := d.GetOk("outbound_privileges") + if outboundPrivilegesOk { + switch OutboundPrivilegesBehavior(outboundPrivileges.(string)) { + case CopyOutboundPrivilegesBehavior: + id.OutboundPrivilegesBehavior = sdk.Pointer(CopyOutboundPrivilegesBehavior) + case RevokeOutboundPrivilegesBehavior: + id.OutboundPrivilegesBehavior = sdk.Pointer(RevokeOutboundPrivilegesBehavior) + } + } + + grantedOn := d.Get("on").([]any)[0].(map[string]any) + objectType := grantedOn["object_type"].(string) + objectName := grantedOn["object_name"].(string) + all := grantedOn["all"].([]any) + future := grantedOn["future"].([]any) + + switch { + case len(objectType) > 0 && len(objectName) > 0: + id.Kind = OnObjectGrantOwnershipKind + objectType := sdk.ObjectType(objectType) + objectName, err := getOnObjectIdentifier(objectType, objectName) + if err != nil { + return nil, err + } + id.Data = &OnObjectGrantOwnershipData{ + ObjectType: objectType, + ObjectName: objectName, + } + case len(all) > 0: + id.Kind = OnAllGrantOwnershipKind + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(all[0].(map[string]any))) + case len(future) > 0: + id.Kind = OnFutureGrantOwnershipKind + id.Data = getBulkOperationGrantData(getGrantOnSchemaObjectIn(future[0].(map[string]any))) + } + + return id, nil +} diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go new file mode 100644 index 0000000000..009433a67e --- /dev/null +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -0,0 +1,606 @@ +package resources_test + +import ( + "context" + "fmt" + "regexp" + "slices" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + 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/tfversion" +) + +func TestAcc_GrantOwnership_OnObject_Database_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "DATABASE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeDatabase, accountRoleName, databaseName), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Database_IdentifiersWithDots(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + "." + acctest.RandStringFromCharSet(5, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(5, acctest.CharSetAlpha) + "." + acctest.RandStringFromCharSet(5, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "DATABASE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|DATABASE|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeDatabase, accountRoleName, databaseName), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Database_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Schema_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnObject_Schema_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "SCHEMA"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|SCHEMA|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeSchema, accountRoleName, fmt.Sprintf("%s.%s", databaseName, schemaName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Schema_ToDatabaseRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseRoleFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "database_role_name": config.StringVariable(databaseRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnObject_Schema_ToDatabaseRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "SCHEMA"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToDatabaseRole|%s||OnObject|SCHEMA|%s", databaseRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName), + }, + }, sdk.ObjectTypeSchema, databaseRoleName, fmt.Sprintf("%s.%s", databaseName, schemaName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Table_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableFullyQualifiedName := sdk.NewSchemaObjectIdentifier(databaseName, schemaName, tableName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnObject_Table_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "TABLE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", tableFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|TABLE|%s", accountRoleFullyQualifiedName, tableFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnObject_Table_ToDatabaseRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableFullyQualifiedName := sdk.NewSchemaObjectIdentifier(databaseName, schemaName, tableName).FullyQualifiedName() + + databaseRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseRoleFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "database_role_name": config.StringVariable(databaseRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnObject_Table_ToDatabaseRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "database_role_name", databaseRoleFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", "TABLE"), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", tableFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToDatabaseRole|%s||OnObject|TABLE|%s", databaseRoleFullyQualifiedName, tableFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + DatabaseRole: sdk.NewDatabaseObjectIdentifier(databaseName, databaseRoleName), + }, + }, sdk.ObjectTypeTable, databaseRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnAll_InDatabase_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + secondTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + "second_table_name": config.StringVariable(secondTableName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnAll_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.in_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnAll|TABLES|InDatabase|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName), fmt.Sprintf("%s.%s.%s", databaseName, schemaName, secondTableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnAll_InSchema_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + secondTableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + "table_name": config.StringVariable(tableName), + "second_table_name": config.StringVariable(secondTableName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnAll_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.all.0.in_schema", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnAll|TABLES|InSchema|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.%s", databaseName, schemaName, tableName), fmt.Sprintf("%s.%s.%s", databaseName, schemaName, secondTableName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnFuture_InDatabase_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + databaseFullyQualifiedName := sdk.NewAccountObjectIdentifier(databaseName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnFuture_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.in_database", databaseName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnFuture|TABLES|InDatabase|%s", accountRoleFullyQualifiedName, databaseFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier(databaseName)), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.", databaseName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnFuture_InSchema_ToAccountRole(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + databaseName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(databaseName, schemaName).FullyQualifiedName() + + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database_name": config.StringVariable(databaseName), + "schema_name": config.StringVariable(schemaName), + } + resourceName := "snowflake_grant_ownership.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_GrantOwnership/OnFuture_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.object_type_plural", "TABLES"), + resource.TestCheckResourceAttr(resourceName, "on.0.future.0.in_schema", schemaFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnFuture|TABLES|InSchema|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier(databaseName, schemaName)), + }, + }, sdk.ObjectTypeTable, accountRoleName, fmt.Sprintf("%s.%s.
", databaseName, schemaName)), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole"), + ConfigVariables: configVariables, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantOwnership_InvalidConfiguration_EmptyObjectType(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + "database_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + } + + 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_GrantOwnership/InvalidConfiguration_EmptyObjectType"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("expected on.0.object_type to be one of"), + }, + }, + }) +} + +func TestAcc_GrantOwnership_InvalidConfiguration_MultipleTargets(t *testing.T) { + t.Skip("will be unskipped in the following grant ownership prs") + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + "database_name": config.StringVariable(strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))), + } + + 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_GrantOwnership/InvalidConfiguration_MultipleTargets"), + ConfigVariables: configVariables, + ExpectError: regexp.MustCompile("only one of `on.0.all,on.0.future,on.0.object_name`"), + }, + }, + }) +} + +func checkResourceOwnershipIsGranted(opts *sdk.ShowGrantOptions, grantOn sdk.ObjectType, roleName string, objectNames ...string) func(s *terraform.State) error { + return func(s *terraform.State) error { + client := acc.TestAccProvider.Meta().(*provider.Context).Client + ctx := context.Background() + + grants, err := client.Grants.Show(ctx, opts) + if err != nil { + return err + } + + found := make([]string, 0) + for _, grant := range grants { + if grant.Privilege == "OWNERSHIP" && + (grant.GrantedOn == grantOn || grant.GrantOn == grantOn) && + grant.GranteeName.Name() == roleName && + slices.Contains(objectNames, grant.Name.Name()) { + found = append(found, grant.Name.Name()) + } + } + + if len(found) != len(objectNames) { + return fmt.Errorf("unable to find ownership privilege on %s granted to %s, expected names: %v, found: %v", grantOn, roleName, objectNames, found) + } + + return nil + } +} diff --git a/pkg/resources/grant_ownership_identifier.go b/pkg/resources/grant_ownership_identifier.go new file mode 100644 index 0000000000..a6bc3277c3 --- /dev/null +++ b/pkg/resources/grant_ownership_identifier.go @@ -0,0 +1,151 @@ +package resources + +import ( + "fmt" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +type GrantOwnershipTargetRoleKind string + +const ( + ToAccountGrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind = "ToAccountRole" + ToDatabaseGrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind = "ToDatabaseRole" +) + +type OutboundPrivilegesBehavior string + +const ( + CopyOutboundPrivilegesBehavior OutboundPrivilegesBehavior = "COPY" + RevokeOutboundPrivilegesBehavior OutboundPrivilegesBehavior = "REVOKE" +) + +func (o OutboundPrivilegesBehavior) ToOwnershipCurrentGrantsOutboundPrivileges() *sdk.OwnershipCurrentGrantsOutboundPrivileges { + switch o { + case CopyOutboundPrivilegesBehavior: + return sdk.Pointer(sdk.Copy) + case RevokeOutboundPrivilegesBehavior: + return sdk.Pointer(sdk.Revoke) + default: + return nil + } +} + +type GrantOwnershipKind string + +const ( + OnObjectGrantOwnershipKind GrantOwnershipKind = "OnObject" + OnAllGrantOwnershipKind GrantOwnershipKind = "OnAll" + OnFutureGrantOwnershipKind GrantOwnershipKind = "OnFuture" +) + +type GrantOwnershipId struct { + GrantOwnershipTargetRoleKind GrantOwnershipTargetRoleKind + AccountRoleName sdk.AccountObjectIdentifier + DatabaseRoleName sdk.DatabaseObjectIdentifier + OutboundPrivilegesBehavior *OutboundPrivilegesBehavior + Kind GrantOwnershipKind + Data fmt.Stringer +} + +type OnObjectGrantOwnershipData struct { + ObjectType sdk.ObjectType + ObjectName sdk.ObjectIdentifier +} + +func (g *OnObjectGrantOwnershipData) String() string { + var parts []string + parts = append(parts, g.ObjectType.String()) + parts = append(parts, g.ObjectName.FullyQualifiedName()) + return strings.Join(parts, helpers.IDDelimiter) +} + +func (g *GrantOwnershipId) String() string { + var parts []string + parts = append(parts, string(g.GrantOwnershipTargetRoleKind)) + switch g.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + parts = append(parts, g.AccountRoleName.FullyQualifiedName()) + case ToDatabaseGrantOwnershipTargetRoleKind: + parts = append(parts, g.DatabaseRoleName.FullyQualifiedName()) + } + if g.OutboundPrivilegesBehavior != nil { + parts = append(parts, string(*g.OutboundPrivilegesBehavior)) + } else { + parts = append(parts, "") + } + parts = append(parts, string(g.Kind)) + data := g.Data.String() + if len(data) > 0 { + parts = append(parts, data) + } + return strings.Join(parts, helpers.IDDelimiter) +} + +func ParseGrantOwnershipId(id string) (*GrantOwnershipId, error) { + grantOwnershipId := new(GrantOwnershipId) + + parts := strings.Split(id, helpers.IDDelimiter) + if len(parts) < 5 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should hold at least 5 parts "||||"`) + } + + grantOwnershipId.GrantOwnershipTargetRoleKind = GrantOwnershipTargetRoleKind(parts[0]) + switch grantOwnershipId.GrantOwnershipTargetRoleKind { + case ToAccountGrantOwnershipTargetRoleKind: + grantOwnershipId.AccountRoleName = sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[1]) + case ToDatabaseGrantOwnershipTargetRoleKind: + grantOwnershipId.DatabaseRoleName = sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[1]) + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown GrantOwnershipTargetRoleKind: %v, valid options are %v | %v", grantOwnershipId.GrantOwnershipTargetRoleKind, ToAccountGrantOwnershipTargetRoleKind, ToDatabaseGrantOwnershipTargetRoleKind)) + } + + if len(parts[2]) > 0 { + switch outboundPrivilegesBehavior := OutboundPrivilegesBehavior(parts[2]); outboundPrivilegesBehavior { + case CopyOutboundPrivilegesBehavior, RevokeOutboundPrivilegesBehavior: + grantOwnershipId.OutboundPrivilegesBehavior = sdk.Pointer(outboundPrivilegesBehavior) + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown OutboundPrivilegesBehavior: %v, valid options are %v | %v", outboundPrivilegesBehavior, CopyOutboundPrivilegesBehavior, RevokeOutboundPrivilegesBehavior)) + } + } + + grantOwnershipId.Kind = GrantOwnershipKind(parts[3]) + switch grantOwnershipId.Kind { + case OnObjectGrantOwnershipKind: + if len(parts) != 6 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should consist of 6 parts "|||OnObject||"`) + } + objectType := sdk.ObjectType(parts[4]) + objectName, err := getOnObjectIdentifier(objectType, parts[5]) + if err != nil { + return nil, err + } + grantOwnershipId.Data = &OnObjectGrantOwnershipData{ + ObjectType: objectType, + ObjectName: objectName, + } + case OnAllGrantOwnershipKind, OnFutureGrantOwnershipKind: + bulkOperationGrantData := &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectType(parts[4]), + } + if len(parts) != 7 { + return grantOwnershipId, sdk.NewError(`grant ownership identifier should consist of 7 parts "|||On[All or Future]||In[Database or Schema]|"`) + } + bulkOperationGrantData.Kind = BulkOperationGrantKind(parts[5]) + switch bulkOperationGrantData.Kind { + case InDatabaseBulkOperationGrantKind: + bulkOperationGrantData.Database = sdk.Pointer(sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[6])) + case InSchemaBulkOperationGrantKind: + bulkOperationGrantData.Schema = sdk.Pointer(sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[6])) + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("invalid BulkOperationGrantKind: %s, valid options are %v | %v", bulkOperationGrantData.Kind, InDatabaseBulkOperationGrantKind, InSchemaBulkOperationGrantKind)) + } + grantOwnershipId.Data = bulkOperationGrantData + default: + return grantOwnershipId, sdk.NewError(fmt.Sprintf("unknown GrantOwnershipKind: %v", grantOwnershipId.Kind)) + } + + return grantOwnershipId, nil +} diff --git a/pkg/resources/grant_ownership_identifier_test.go b/pkg/resources/grant_ownership_identifier_test.go new file mode 100644 index 0000000000..e0345209ef --- /dev/null +++ b/pkg/resources/grant_ownership_identifier_test.go @@ -0,0 +1,497 @@ +package resources + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" +) + +func TestParseGrantOwnershipId(t *testing.T) { + testCases := []struct { + Name string + Identifier string + Expected GrantOwnershipId + Error string + }{ + { + Name: "grant ownership on database to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|DATABASE|"database-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: sdk.NewAccountObjectIdentifier("database-name"), + }, + }, + }, + { + Name: "grant ownership on schema to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|SCHEMA|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), + }, + }, + }, + { + Name: "grant ownership on table to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|TABLE|"database-name"."schema-name"."table-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeTable, + ObjectName: sdk.NewSchemaObjectIdentifier("database-name", "schema-name", "table-name"), + }, + }, + }, + { + Name: "grant ownership on schema to database role", + Identifier: `ToDatabaseRole|"database-name"."database-role"|REVOKE|OnObject|SCHEMA|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database-name", "database-role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("database-name", "schema-name"), + }, + }, + }, + { + Name: "grant ownership on all tables in database to account role", + Identifier: `ToAccountRole|"account-role"||OnAll|TABLES|InDatabase|"database-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + }, + { + Name: "grant ownership on all tables in schema to account role", + Identifier: `ToAccountRole|"account-role"||OnAll|TABLES|InSchema|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + { + Name: "grant ownership on future tables in database to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InDatabase|"database-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database-name")), + }, + }, + }, + { + Name: "grant ownership on future tables in schema to account role", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account-role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database-name", "schema-name")), + }, + }, + }, + { + Name: "validation: not enough parts", + Identifier: `ToDatabaseRole|"database-name"."role-name"|`, + Error: "ownership identifier should hold at least 5 parts", + }, + { + Name: "validation: invalid to role enum", + Identifier: `SomeInvalidEnum|"database-name"."role-name"|OnObject|DATABASE|"some-database"`, + Error: "unknown GrantOwnershipTargetRoleKind: SomeInvalidEnum, valid options are ToAccountRole | ToDatabaseRole", + }, + { + Name: "invalid outbound privilege option resulting in no outbound privileges option set", + Identifier: `ToAccountRole|"account-role"|InvalidOption|OnFuture|TABLES|InSchema|"database-name"."schema-name"`, + Error: `unknown OutboundPrivilegesBehavior: InvalidOption, valid options are COPY | REVOKE`, + }, + { + Name: "validation: not enough parts for OnObject kind", + Identifier: `ToAccountRole|"account-role"|COPY|OnObject|DATABASE`, + Error: `grant ownership identifier should consist of 6 parts`, + }, + { + Name: "validation: not enough parts for OnAll kind", + Identifier: `ToAccountRole|"account-role"|COPY|OnAll|TABLES|InDatabase`, + Error: `grant ownership identifier should consist of 7 parts`, + }, + { + Name: "validation: OnAll in InvalidOption", + Identifier: `ToAccountRole|"account-role"|COPY|OnAll|TABLES|InvalidOption|"some-identifier"`, + Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", + }, + // { + // Name: "TODO(SNOW-999049 - no error because of bad identifiers): validation: OnAll in database - missing database identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnAll|InvalidTarget|InDatabase|`, + // Error: "TODO", + // }, + // { + // Name: "TODO(SNOW-999049 - panic because of bad identifiers): validation: OnAll in database - missing schema identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnAll|InvalidTarget|InSchema|`, + // Error: "TODO", + // }, + { + Name: "validation: not enough parts for OnFuture kind", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES`, + Error: `grant ownership identifier should consist of 7 parts`, + }, + { + Name: "validation: OnFuture in InvalidOption", + Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|TABLES|InvalidOption|"some-identifier"`, + Error: "invalid BulkOperationGrantKind: InvalidOption, valid options are InDatabase | InSchema", + }, + // { + // Name: "TODO(SNOW-999049 - no error because of bad identifiers): validation: OnFuture in database - missing database identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|InvalidTarget|InDatabase|`, + // Error: "TODO", + // }, + // { + // Name: "TODO(SNOW-999049 - panic because of bad identifiers): validation: OnFuture in database - missing schema identifier", + // Identifier: `ToAccountRole|"account-role"|COPY|OnFuture|InvalidTarget|InSchema|`, + // Error: "TODO", + // }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + id, err := ParseGrantOwnershipId(tt.Identifier) + if tt.Error == "" { + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Equal(t, tt.Expected, *id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestGrantOwnershipIdString(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantOwnershipId + Expected string + Error string + }{ + { + Name: "grant ownership on database to account role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account_role"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: sdk.NewAccountObjectIdentifier("database_name"), + }, + }, + Expected: `ToAccountRole|"account_role"|COPY|OnObject|DATABASE|"database_name"`, + }, + { + Name: "grant ownership on schema to account role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account_role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("database_name", "schema_name"), + }, + }, + Expected: `ToAccountRole|"account_role"|REVOKE|OnObject|SCHEMA|"database_name"."schema_name"`, + }, + { + Name: "grant ownership on table to account role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("account_role"), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeTable, + ObjectName: sdk.NewSchemaObjectIdentifier("database_name", "schema_name", "table_name"), + }, + }, + Expected: `ToAccountRole|"account_role"||OnObject|TABLE|"database_name"."schema_name"."table_name"`, + }, + { + Name: "grant ownership on all tables in database to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnAll|TABLES|InDatabase|"database_name"`, + }, + { + Name: "grant ownership on all tables in schema to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database_name", "schema_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnAll|TABLES|InSchema|"database_name"."schema_name"`, + }, + { + Name: "grant ownership on future tables in database to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("database_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnFuture|TABLES|InDatabase|"database_name"`, + }, + { + Name: "grant ownership on future tables in schema to database role", + Identifier: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("database_name", "database_role_name"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("database_name", "schema_name")), + }, + }, + Expected: `ToDatabaseRole|"database_name"."database_role_name"||OnFuture|TABLES|InSchema|"database_name"."schema_name"`, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + assert.Equal(t, tt.Expected, tt.Identifier.String()) + }) + } +} + +func TestCreateGrantOwnershipIdFromSchema(t *testing.T) { + testCases := []struct { + Name string + Config map[string]any + Expected GrantOwnershipId + }{ + { + Name: "grant ownership on schema to account role with copied outbound privileges", + Config: map[string]any{ + "account_role_name": "test_acc_role_name", + "outbound_privileges": "COPY", + "on": []any{ + map[string]any{ + "object_type": "SCHEMA", + "object_name": "\"test_database\".\"test_schema\"", + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role_name"), + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + { + Name: "grant ownership on schema to database role with revoked outbound privileges", + Config: map[string]any{ + "database_role_name": "\"test_database\".\"test_database_role\"", + "outbound_privileges": "REVOKE", + "on": []any{ + map[string]any{ + "object_type": "SCHEMA", + "object_name": "\"test_database\".\"test_schema\"", + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToDatabaseGrantOwnershipTargetRoleKind, + DatabaseRoleName: sdk.NewDatabaseObjectIdentifier("test_database", "test_database_role"), + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + { + Name: "grant ownership on all tables in database to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_database": "test_database", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "grant ownership on all tables in schema to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_schema": "\"test_database\".\"test_schema\"", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "grant ownership on future tables in database to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_database": "test_database", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "grant ownership on future tables in schema to account role", + Config: map[string]any{ + "account_role_name": "test_acc_role", + "on": []any{ + map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_schema": "\"test_database\".\"test_schema\"", + }, + }, + }, + }, + }, + Expected: GrantOwnershipId{ + GrantOwnershipTargetRoleKind: ToAccountGrantOwnershipTargetRoleKind, + AccountRoleName: sdk.NewAccountObjectIdentifier("test_acc_role"), + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, tt.Config) + id, err := createGrantOwnershipIdFromSchema(d) + assert.NoError(t, err) + assert.NotNil(t, id) + assert.Equal(t, tt.Expected.GrantOwnershipTargetRoleKind, id.GrantOwnershipTargetRoleKind) + assert.Equal(t, tt.Expected.AccountRoleName, id.AccountRoleName) + assert.Equal(t, tt.Expected.DatabaseRoleName, id.DatabaseRoleName) + assert.Equal(t, tt.Expected.OutboundPrivilegesBehavior, id.OutboundPrivilegesBehavior) + assert.Equal(t, tt.Expected.Kind, id.Kind) + assert.Equal(t, tt.Expected.Data.String(), id.Data.String()) + }) + } +} diff --git a/pkg/resources/grant_ownership_test.go b/pkg/resources/grant_ownership_test.go new file mode 100644 index 0000000000..25d898b9a0 --- /dev/null +++ b/pkg/resources/grant_ownership_test.go @@ -0,0 +1,527 @@ +package resources + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/stretchr/testify/assert" +) + +func TestGetOnObjectIdentifier(t *testing.T) { + testCases := []struct { + Name string + ObjectType sdk.ObjectType + ObjectName string + Expected sdk.ObjectIdentifier + Error string + }{ + { + Name: "database - account object identifier", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "test_database", + Expected: sdk.NewAccountObjectIdentifier("test_database"), + }, + { + Name: "database - account object identifier - quoted", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "\"test_database\"", + Expected: sdk.NewAccountObjectIdentifier("test_database"), + }, + { + Name: "schema - database object identifier", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "test_database.test_schema", + Expected: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + { + Name: "schema - database object identifier - quoted", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "\"test_database\".\"test_schema\"", + Expected: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + { + Name: "table - schema object identifier", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "test_database.test_schema.test_table", + Expected: sdk.NewSchemaObjectIdentifier("test_database", "test_schema", "test_table"), + }, + { + Name: "table - schema object identifier - quoted", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "\"test_database\".\"test_schema\".\"test_table\"", + Expected: sdk.NewSchemaObjectIdentifier("test_database", "test_schema", "test_table"), + }, + { + Name: "account object identifier with dots", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "database.name.with.dots", + Expected: sdk.NewAccountObjectIdentifier("database.name.with.dots"), + }, + { + Name: "validation - valid identifier", + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: "to.many.parts.in.this.identifier", + Error: "unable to classify identifier", + }, + { + Name: "validation - unsupported type", + ObjectType: sdk.ObjectTypeShare, + ObjectName: "some_share", + Error: "object_type SHARE is not supported", + }, + { + Name: "validation - invalid database object identifier", + ObjectType: sdk.ObjectTypeSchema, + ObjectName: "test_database.test_schema.test_table", + Error: "invalid object_name test_database.test_schema.test_table, expected database object identifier", + }, + { + Name: "validation - invalid schema object identifier", + ObjectType: sdk.ObjectTypeTable, + ObjectName: "test_database.test_schema.test_table.column_name", + Error: "invalid object_name test_database.test_schema.test_table.column_name, expected schema object identifier", + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + id, err := getOnObjectIdentifier(tt.ObjectType, tt.ObjectName) + if tt.Error == "" { + assert.NoError(t, err) + assert.Equal(t, tt.Expected, id) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestGetOwnershipGrantOn(t *testing.T) { + testCases := []struct { + Name string + On map[string]any + Expected sdk.OwnershipGrantOn + Error string + }{ + { + Name: "database object type", + On: map[string]any{ + "object_type": "DATABASE", + "object_name": "test_database", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + }, + { + Name: "schema object type", + On: map[string]any{ + "object_type": "SCHEMA", + "object_name": "test_database.test_schema", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeSchema, + Name: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + { + Name: "table object type", + On: map[string]any{ + "object_type": "TABLE", + "object_name": "test_database.test_schema.test_table", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeTable, + Name: sdk.NewSchemaObjectIdentifier("test_database", "test_schema", "test_table"), + }, + }, + }, + { + Name: "on all tables in database", + On: map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_database": "test_database", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "on all tables in schema", + On: map[string]any{ + "all": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_schema": "test_database.test_schema", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InSchema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "on future tables in database", + On: map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_database": "test_database", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InDatabase: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + }, + { + Name: "on future tables in schema", + On: map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "TABLES", + "in_schema": "test_database.test_schema", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InSchema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "database object type in lowercase", + On: map[string]any{ + "object_type": "database", + "object_name": "test_database", + }, + Expected: sdk.OwnershipGrantOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + }, + { + Name: "grant all in database plural object type in lowercase", + On: map[string]any{ + "future": []any{ + map[string]any{ + "object_type_plural": "tables", + "in_schema": "test_database.test_schema", + }, + }, + }, + Expected: sdk.OwnershipGrantOn{ + Future: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTables, + InSchema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + }, + { + Name: "validation - invalid schema object type", + On: map[string]any{ + "object_type": "SCHEMA", + "object_name": "test_database.test_schema.test_table", + }, + Error: "invalid object_name test_database.test_schema.test_table, expected database object identifier", + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "on": []any{tt.On}, + }) + grantOn, err := getOwnershipGrantOn(d) + if tt.Error == "" { + assert.NoError(t, err) + assert.NotNil(t, grantOn) + assert.Equal(t, tt.Expected, *grantOn) + } else { + assert.ErrorContains(t, err, tt.Error) + } + }) + } +} + +func TestPrepareShowGrantsRequestForGrantOwnership(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantOwnershipId + ExpectedShowGrantsOpts *sdk.ShowGrantOptions + ExpectedGrantedOn sdk.ObjectType + }{ + { + Name: "show for object - database", + Identifier: GrantOwnershipId{ + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeDatabase, + ObjectName: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeDatabase, + Name: sdk.NewAccountObjectIdentifier("test_database"), + }, + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeDatabase, + }, + { + Name: "show for object - schema", + Identifier: GrantOwnershipId{ + Kind: OnObjectGrantOwnershipKind, + Data: &OnObjectGrantOwnershipData{ + ObjectType: sdk.ObjectTypeSchema, + ObjectName: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeSchema, + Name: sdk.NewDatabaseObjectIdentifier("test_database", "test_schema"), + }, + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeSchema, + }, + { + Name: "show for all in database", + Identifier: GrantOwnershipId{ + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + ExpectedShowGrantsOpts: nil, + ExpectedGrantedOn: "", + }, + { + Name: "show for all in schema", + Identifier: GrantOwnershipId{ + Kind: OnAllGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + ExpectedShowGrantsOpts: nil, + ExpectedGrantedOn: "", + }, + { + Name: "show for future in database", + Identifier: GrantOwnershipId{ + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InDatabaseBulkOperationGrantKind, + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Database: sdk.Pointer(sdk.NewAccountObjectIdentifier("test_database")), + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeTable, + }, + { + Name: "show for future in schema", + Identifier: GrantOwnershipId{ + Kind: OnFutureGrantOwnershipKind, + Data: &BulkOperationGrantData{ + ObjectNamePlural: sdk.PluralObjectTypeTables, + Kind: InSchemaBulkOperationGrantKind, + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + ExpectedShowGrantsOpts: &sdk.ShowGrantOptions{ + Future: sdk.Bool(true), + In: &sdk.ShowGrantsIn{ + Schema: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "test_schema")), + }, + }, + ExpectedGrantedOn: sdk.ObjectTypeTable, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + opts, grantedOn := prepareShowGrantsRequestForGrantOwnership(&tt.Identifier) + if tt.ExpectedShowGrantsOpts == nil { + assert.Nil(t, opts) + } else { + assert.NotNil(t, opts) + assert.Equal(t, *tt.ExpectedShowGrantsOpts, *opts) + } + assert.Equal(t, tt.ExpectedGrantedOn, grantedOn) + }) + } +} + +func TestValidAccountRoleNameGetOwnershipGrantTo(t *testing.T) { + testCases := []struct { + Name string + AccountRole *string + Expected sdk.OwnershipGrantTo + }{ + { + Name: "account role name", + AccountRole: sdk.String("account_role_name"), + Expected: sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier("account_role_name")), + }, + }, + { + Name: "account role name - quoted", + AccountRole: sdk.String("\"account_role_name\""), + Expected: sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier("account_role_name")), + }, + }, + { + Name: "account role name - with dots", + AccountRole: sdk.String("account.role.with.dots"), + Expected: sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier("account.role.with.dots")), + }, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + grantTo := getOwnershipGrantTo(schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "account_role_name": *tt.AccountRole, + })) + + assert.Equal(t, *tt.Expected.AccountRoleName, *grantTo.AccountRoleName) + }) + } +} + +func TestValidDatabaseRoleNameGetOwnershipGrantTo(t *testing.T) { + testCases := []struct { + Name string + DatabaseRole *string + Expected sdk.OwnershipGrantTo + }{ + { + Name: "database role name", + DatabaseRole: sdk.String("test_database.database_role_name"), + Expected: sdk.OwnershipGrantTo{ + DatabaseRoleName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "database_role_name")), + }, + }, + { + Name: "database role name - quoted", + DatabaseRole: sdk.String("\"test_database\".\"database_role_name\""), + Expected: sdk.OwnershipGrantTo{ + DatabaseRoleName: sdk.Pointer(sdk.NewDatabaseObjectIdentifier("test_database", "database_role_name")), + }, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + grantTo := getOwnershipGrantTo(schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "database_role_name": *tt.DatabaseRole, + })) + + assert.Equal(t, *tt.Expected.DatabaseRoleName, *grantTo.DatabaseRoleName) + }) + } +} + +func TestInvalidDatabaseRoleGetOwnershipGrantTo(t *testing.T) { + d := schema.TestResourceDataRaw(t, grantOwnershipSchema, map[string]any{ + "database_role_name": "account_role_name", + }) + + assert.Panics(t, func() { + _ = getOwnershipGrantTo(d) + }) +} + +func TestGetOwnershipGrantOpts(t *testing.T) { + testCases := []struct { + Name string + Identifier GrantOwnershipId + Expected *sdk.GrantOwnershipOptions + }{ + { + Name: "outbound privileges copy", + Identifier: GrantOwnershipId{ + OutboundPrivilegesBehavior: sdk.Pointer(CopyOutboundPrivilegesBehavior), + }, + Expected: &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Copy, + }, + }, + }, + { + Name: "outbound privileges revoke", + Identifier: GrantOwnershipId{ + OutboundPrivilegesBehavior: sdk.Pointer(RevokeOutboundPrivilegesBehavior), + }, + Expected: &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Revoke, + }, + }, + }, + { + Name: "no outbound privileges option", + Identifier: GrantOwnershipId{ + OutboundPrivilegesBehavior: nil, + }, + Expected: &sdk.GrantOwnershipOptions{}, + }, + } + + for _, tt := range testCases { + tt := tt + t.Run(tt.Name, func(t *testing.T) { + opts := getOwnershipGrantOpts(&tt.Identifier) + assert.NotNil(t, opts) + assert.Equal(t, *tt.Expected, *opts) + }) + } +} diff --git a/pkg/resources/grant_privileges_identifier_commons.go b/pkg/resources/grant_privileges_identifier_commons.go index e8057f7dfb..885b5d61c5 100644 --- a/pkg/resources/grant_privileges_identifier_commons.go +++ b/pkg/resources/grant_privileges_identifier_commons.go @@ -55,14 +55,7 @@ func (d *OnSchemaObjectGrantData) String() string { case OnObjectSchemaObjectGrantKind: parts = append(parts, fmt.Sprintf("%s|%s", d.Object.ObjectType, d.Object.Name.FullyQualifiedName())) case OnAllSchemaObjectGrantKind, OnFutureSchemaObjectGrantKind: - parts = append(parts, d.OnAllOrFuture.ObjectNamePlural.String()) - parts = append(parts, string(d.OnAllOrFuture.Kind)) - switch d.OnAllOrFuture.Kind { - case InDatabaseBulkOperationGrantKind: - parts = append(parts, d.OnAllOrFuture.Database.FullyQualifiedName()) - case InSchemaBulkOperationGrantKind: - parts = append(parts, d.OnAllOrFuture.Schema.FullyQualifiedName()) - } + parts = append(parts, d.OnAllOrFuture.String()) } return strings.Join(parts, helpers.IDDelimiter) } @@ -81,6 +74,19 @@ type BulkOperationGrantData struct { Schema *sdk.DatabaseObjectIdentifier } +func (d *BulkOperationGrantData) String() string { + var parts []string + parts = append(parts, d.ObjectNamePlural.String()) + parts = append(parts, string(d.Kind)) + switch d.Kind { + case InDatabaseBulkOperationGrantKind: + parts = append(parts, d.Database.FullyQualifiedName()) + case InSchemaBulkOperationGrantKind: + parts = append(parts, d.Schema.FullyQualifiedName()) + } + return strings.Join(parts, helpers.IDDelimiter) +} + func getBulkOperationGrantData(in *sdk.GrantOnSchemaObjectIn) *BulkOperationGrantData { bulkOperationGrantData := &BulkOperationGrantData{ ObjectNamePlural: in.PluralObjectType, @@ -100,9 +106,8 @@ func getBulkOperationGrantData(in *sdk.GrantOnSchemaObjectIn) *BulkOperationGran } func getGrantOnSchemaObjectIn(allOrFuture map[string]any) *sdk.GrantOnSchemaObjectIn { - pluralObjectType := sdk.PluralObjectType(allOrFuture["object_type_plural"].(string)) grantOnSchemaObjectIn := &sdk.GrantOnSchemaObjectIn{ - PluralObjectType: pluralObjectType, + PluralObjectType: sdk.PluralObjectType(strings.ToUpper(allOrFuture["object_type_plural"].(string))), } if inDatabase, ok := allOrFuture["in_database"].(string); ok && len(inDatabase) > 0 { diff --git a/pkg/resources/grant_privileges_to_account_role.go b/pkg/resources/grant_privileges_to_account_role.go index 42f3136860..6095a5fb21 100644 --- a/pkg/resources/grant_privileges_to_account_role.go +++ b/pkg/resources/grant_privileges_to_account_role.go @@ -419,7 +419,7 @@ func CreateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "An error occurred when granting privileges to account role", - Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", id.String(), id.RoleName, err.Error()), + Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", id.String(), id.RoleName, err), }, } } @@ -440,7 +440,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -465,7 +465,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to revoke all privileges", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -529,7 +529,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD 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()), + Detail: fmt.Sprintf("Id: %s\nPrivileges to add: %v\nError: %s", d.Id(), privilegesToAdd, err), }, } } @@ -556,7 +556,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD 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()), + Detail: fmt.Sprintf("Id: %s\nPrivileges to remove: %v\nError: %s", d.Id(), privilegesToRemove, err), }, } } @@ -585,7 +585,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to grant all privileges", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -614,7 +614,7 @@ func UpdateGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Always apply. An error occurred when granting privileges to 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, err), }, } } @@ -636,7 +636,7 @@ func DeleteGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceD diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -654,7 +654,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.FullyQualifiedName(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nAccount role name: %s\nError: %s", d.Id(), id.RoleName.FullyQualifiedName(), err), }, } } @@ -672,7 +672,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to parse internal identifier", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -695,7 +695,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to generate UUID", - Detail: fmt.Sprintf("Original error: %s", err.Error()), + Detail: fmt.Sprintf("Original error: %s", err), }, } } @@ -706,7 +706,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting always_apply_trigger for database role", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -731,7 +731,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Failed to retrieve grants", - Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err.Error()), + Detail: fmt.Sprintf("Id: %s\nError: %s", d.Id(), err), }, } } @@ -794,7 +794,7 @@ func ReadGrantPrivilegesToAccountRole(ctx context.Context, d *schema.ResourceDat diag.Diagnostic{ Severity: diag.Error, Summary: "Error setting privileges for account role", - Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), actualPrivileges, err.Error()), + Detail: fmt.Sprintf("Id: %s\nPrivileges: %v\nError: %s", d.Id(), actualPrivileges, err), }, } } diff --git a/pkg/resources/grant_privileges_to_database_role.go b/pkg/resources/grant_privileges_to_database_role.go index 6608aa10d1..6dc873d323 100644 --- a/pkg/resources/grant_privileges_to_database_role.go +++ b/pkg/resources/grant_privileges_to_database_role.go @@ -225,12 +225,14 @@ var grantPrivilegesOnDatabaseRoleBulkOperationSchema = map[string]*schema.Schema Type: schema.TypeString, Optional: true, ForceNew: true, + Description: "The fully qualified name of the database.", ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), }, "in_schema": { Type: schema.TypeString, Optional: true, ForceNew: true, + Description: "The fully qualified name of the schema.", ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), }, } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf new file mode 100644 index 0000000000..989b68472d --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/test.tf @@ -0,0 +1,15 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "" + object_name = snowflake_database.test.name + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_EmptyObjectType/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf new file mode 100644 index 0000000000..a1dd26c2e9 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/test.tf @@ -0,0 +1,25 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "DATABASE" + object_name = snowflake_database.test.name + + all { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + + future { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/InvalidConfiguration_MultipleTargets/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf new file mode 100644 index 0000000000..122ad22fdb --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/test.tf @@ -0,0 +1,45 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_table" "test2" { + name = var.second_table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + depends_on = [snowflake_table.test, snowflake_table.test2] + account_role_name = snowflake_role.test.name + on { + all { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf new file mode 100644 index 0000000000..59056d2670 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InDatabase_ToAccountRole/variables.tf @@ -0,0 +1,19 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} + +variable "second_table_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf new file mode 100644 index 0000000000..b77eb8f5cf --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/test.tf @@ -0,0 +1,45 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_table" "test2" { + name = var.second_table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + depends_on = [snowflake_table.test, snowflake_table.test2] + account_role_name = snowflake_role.test.name + on { + all { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf new file mode 100644 index 0000000000..59056d2670 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAll_InSchema_ToAccountRole/variables.tf @@ -0,0 +1,19 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} + +variable "second_table_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf new file mode 100644 index 0000000000..8fd47cd829 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/test.tf @@ -0,0 +1,17 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + object_type_plural = "TABLES" + in_database = snowflake_database.test.name + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InDatabase_ToAccountRole/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf new file mode 100644 index 0000000000..db037510a9 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/test.tf @@ -0,0 +1,22 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + object_type_plural = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf new file mode 100644 index 0000000000..e86f7da400 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnFuture_InSchema_ToAccountRole/variables.tf @@ -0,0 +1,11 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf new file mode 100644 index 0000000000..a2c97c0a92 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/test.tf @@ -0,0 +1,15 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "DATABASE" + object_name = snowflake_database.test.name + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf new file mode 100644 index 0000000000..8af99038ae --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Database_ToAccountRole/variables.tf @@ -0,0 +1,7 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf new file mode 100644 index 0000000000..20e43e8375 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/test.tf @@ -0,0 +1,20 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf new file mode 100644 index 0000000000..e86f7da400 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToAccountRole/variables.tf @@ -0,0 +1,11 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf new file mode 100644 index 0000000000..4b823e16a7 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/test.tf @@ -0,0 +1,21 @@ +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_database_role" "test" { + name = var.database_role_name + database = snowflake_database.test.name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database.test.name}\".\"${snowflake_database_role.test.name}\"" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf new file mode 100644 index 0000000000..16d7031f51 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Schema_ToDatabaseRole/variables.tf @@ -0,0 +1,11 @@ +variable "database_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf new file mode 100644 index 0000000000..cb0b31622e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/test.tf @@ -0,0 +1,31 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + object_type = "TABLE" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf new file mode 100644 index 0000000000..4c7dd0d3d5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToAccountRole/variables.tf @@ -0,0 +1,15 @@ +variable "account_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf new file mode 100644 index 0000000000..d1646c0435 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/test.tf @@ -0,0 +1,32 @@ +resource "snowflake_database" "test" { + name = var.database_name +} + +resource "snowflake_database_role" "test" { + name = var.database_role_name + database = snowflake_database.test.name +} + +resource "snowflake_schema" "test" { + name = var.schema_name + database = snowflake_database.test.name +} + +resource "snowflake_table" "test" { + name = var.table_name + database = snowflake_database.test.name + schema = snowflake_schema.test.name + + column { + name = "id" + type = "NUMBER(38,0)" + } +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database.test.name}\".\"${snowflake_database_role.test.name}\"" + on { + object_type = "TABLE" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\".\"${snowflake_table.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf new file mode 100644 index 0000000000..07cb81cba3 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnObject_Table_ToDatabaseRole/variables.tf @@ -0,0 +1,15 @@ +variable "database_role_name" { + type = string +} + +variable "database_name" { + type = string +} + +variable "schema_name" { + type = string +} + +variable "table_name" { + type = string +} diff --git a/pkg/sdk/grants_validations.go b/pkg/sdk/grants_validations.go index 844580cc87..1df10d643e 100644 --- a/pkg/sdk/grants_validations.go +++ b/pkg/sdk/grants_validations.go @@ -17,6 +17,62 @@ var ( _ validatable = new(ShowGrantOptions) ) +var validGrantOwnershipObjectTypes = []ObjectType{ + ObjectTypeAggregationPolicy, + ObjectTypeAlert, + ObjectTypeAuthenticationPolicy, + ObjectTypeComputePool, + ObjectTypeDatabase, + ObjectTypeDatabaseRole, + ObjectTypeDynamicTable, + ObjectTypeEventTable, + ObjectTypeExternalTable, + ObjectTypeExternalVolume, + ObjectTypeFailoverGroup, + ObjectTypeFileFormat, + ObjectTypeFunction, + ObjectTypeHybridTable, + ObjectTypeIcebergTable, + ObjectTypeImageRepository, + ObjectTypeIntegration, + ObjectTypeMaterializedView, + ObjectTypeNetworkPolicy, + ObjectTypeNetworkRule, + ObjectTypePackagesPolicy, + ObjectTypePipe, + ObjectTypeProcedure, + ObjectTypeMaskingPolicy, + ObjectTypePasswordPolicy, + ObjectTypeProjectionPolicy, + ObjectTypeReplicationGroup, + ObjectTypeRole, + ObjectTypeRowAccessPolicy, + ObjectTypeSchema, + ObjectTypeSessionPolicy, + ObjectTypeSecret, + ObjectTypeSequence, + ObjectTypeStage, + ObjectTypeStream, + ObjectTypeTable, + ObjectTypeTag, + ObjectTypeTask, + ObjectTypeUser, + ObjectTypeView, + ObjectTypeWarehouse, +} + +var ( + ValidGrantOwnershipObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) + ValidGrantOwnershipPluralObjectTypesString = make([]string, len(validGrantOwnershipObjectTypes)) +) + +func init() { + for i, objectType := range validGrantOwnershipObjectTypes { + ValidGrantOwnershipObjectTypesString[i] = objectType.String() + ValidGrantOwnershipPluralObjectTypesString[i] = objectType.Plural().String() + } +} + func (opts *GrantPrivilegesToAccountRoleOptions) validate() error { if opts == nil { return errors.Join(ErrNilOptions) diff --git a/pkg/sdk/object_types.go b/pkg/sdk/object_types.go index c829419eb3..bea1cc9381 100644 --- a/pkg/sdk/object_types.go +++ b/pkg/sdk/object_types.go @@ -15,54 +15,59 @@ type Object struct { type ObjectType string const ( - ObjectTypeAccount ObjectType = "ACCOUNT" - ObjectTypeManagedAccount ObjectType = "MANAGED ACCOUNT" - ObjectTypeUser ObjectType = "USER" - ObjectTypeDatabaseRole ObjectType = "DATABASE ROLE" - ObjectTypeRole ObjectType = "ROLE" - ObjectTypeIntegration ObjectType = "INTEGRATION" - ObjectTypeNetworkPolicy ObjectType = "NETWORK POLICY" - ObjectTypePasswordPolicy ObjectType = "PASSWORD POLICY" - ObjectTypeSessionPolicy ObjectType = "SESSION POLICY" - ObjectTypeReplicationGroup ObjectType = "REPLICATION GROUP" - ObjectTypeFailoverGroup ObjectType = "FAILOVER GROUP" - ObjectTypeConnection ObjectType = "CONNECTION" - ObjectTypeParameter ObjectType = "PARAMETER" - ObjectTypeWarehouse ObjectType = "WAREHOUSE" - ObjectTypeResourceMonitor ObjectType = "RESOURCE MONITOR" - ObjectTypeDatabase ObjectType = "DATABASE" - ObjectTypeSchema ObjectType = "SCHEMA" - ObjectTypeShare ObjectType = "SHARE" - ObjectTypeTable ObjectType = "TABLE" - ObjectTypeDynamicTable ObjectType = "DYNAMIC TABLE" - ObjectTypeExternalTable ObjectType = "EXTERNAL TABLE" - ObjectTypeEventTable ObjectType = "EVENT TABLE" - ObjectTypeView ObjectType = "VIEW" - ObjectTypeMaterializedView ObjectType = "MATERIALIZED VIEW" - ObjectTypeSequence ObjectType = "SEQUENCE" - ObjectTypeFunction ObjectType = "FUNCTION" - ObjectTypeExternalFunction ObjectType = "EXTERNAL FUNCTION" - ObjectTypeProcedure ObjectType = "PROCEDURE" - ObjectTypeStream ObjectType = "STREAM" - ObjectTypeTask ObjectType = "TASK" - ObjectTypeMaskingPolicy ObjectType = "MASKING POLICY" - ObjectTypeRowAccessPolicy ObjectType = "ROW ACCESS POLICY" - ObjectTypeTag ObjectType = "TAG" - ObjectTypeSecret ObjectType = "SECRET" - ObjectTypeStage ObjectType = "STAGE" - ObjectTypeFileFormat ObjectType = "FILE FORMAT" - ObjectTypePipe ObjectType = "PIPE" - ObjectTypeAlert ObjectType = "ALERT" - ObjectTypeApplication ObjectType = "APPLICATION" - ObjectTypeApplicationPackage ObjectType = "APPLICATION PACKAGE" - ObjectTypeApplicationRole ObjectType = "APPLICATION ROLE" - ObjectTypeStreamlit ObjectType = "STREAMLIT" - ObjectTypeColumn ObjectType = "COLUMN" - ObjectTypeIcebergTable ObjectType = "ICEBERG TABLE" - ObjectTypeExternalVolume ObjectType = "EXTERNAL VOLUME" - ObjectTypeNetworkRule ObjectType = "NETWORK RULE" - ObjectTypePackagesPolicy ObjectType = "PACKAGES POLICY" - ObjectTypeComputePool ObjectType = "COMPUTE POOL" + ObjectTypeAccount ObjectType = "ACCOUNT" + ObjectTypeManagedAccount ObjectType = "MANAGED ACCOUNT" + ObjectTypeUser ObjectType = "USER" + ObjectTypeDatabaseRole ObjectType = "DATABASE ROLE" + ObjectTypeRole ObjectType = "ROLE" + ObjectTypeIntegration ObjectType = "INTEGRATION" + ObjectTypeNetworkPolicy ObjectType = "NETWORK POLICY" + ObjectTypePasswordPolicy ObjectType = "PASSWORD POLICY" + ObjectTypeSessionPolicy ObjectType = "SESSION POLICY" + ObjectTypeReplicationGroup ObjectType = "REPLICATION GROUP" + ObjectTypeFailoverGroup ObjectType = "FAILOVER GROUP" + ObjectTypeConnection ObjectType = "CONNECTION" + ObjectTypeParameter ObjectType = "PARAMETER" + ObjectTypeWarehouse ObjectType = "WAREHOUSE" + ObjectTypeResourceMonitor ObjectType = "RESOURCE MONITOR" + ObjectTypeDatabase ObjectType = "DATABASE" + ObjectTypeSchema ObjectType = "SCHEMA" + ObjectTypeShare ObjectType = "SHARE" + ObjectTypeTable ObjectType = "TABLE" + ObjectTypeDynamicTable ObjectType = "DYNAMIC TABLE" + ObjectTypeExternalTable ObjectType = "EXTERNAL TABLE" + ObjectTypeEventTable ObjectType = "EVENT TABLE" + ObjectTypeView ObjectType = "VIEW" + ObjectTypeMaterializedView ObjectType = "MATERIALIZED VIEW" + ObjectTypeSequence ObjectType = "SEQUENCE" + ObjectTypeFunction ObjectType = "FUNCTION" + ObjectTypeExternalFunction ObjectType = "EXTERNAL FUNCTION" + ObjectTypeProcedure ObjectType = "PROCEDURE" + ObjectTypeStream ObjectType = "STREAM" + ObjectTypeTask ObjectType = "TASK" + ObjectTypeMaskingPolicy ObjectType = "MASKING POLICY" + ObjectTypeRowAccessPolicy ObjectType = "ROW ACCESS POLICY" + ObjectTypeTag ObjectType = "TAG" + ObjectTypeSecret ObjectType = "SECRET" + ObjectTypeStage ObjectType = "STAGE" + ObjectTypeFileFormat ObjectType = "FILE FORMAT" + ObjectTypePipe ObjectType = "PIPE" + ObjectTypeAlert ObjectType = "ALERT" + ObjectTypeApplication ObjectType = "APPLICATION" + ObjectTypeApplicationPackage ObjectType = "APPLICATION PACKAGE" + ObjectTypeApplicationRole ObjectType = "APPLICATION ROLE" + ObjectTypeStreamlit ObjectType = "STREAMLIT" + ObjectTypeColumn ObjectType = "COLUMN" + ObjectTypeIcebergTable ObjectType = "ICEBERG TABLE" + ObjectTypeExternalVolume ObjectType = "EXTERNAL VOLUME" + ObjectTypeNetworkRule ObjectType = "NETWORK RULE" + ObjectTypePackagesPolicy ObjectType = "PACKAGES POLICY" + ObjectTypeComputePool ObjectType = "COMPUTE POOL" + ObjectTypeAggregationPolicy ObjectType = "AGGREGATION POLICY" + ObjectTypeAuthenticationPolicy ObjectType = "AUTHENTICATION POLICY" + ObjectTypeHybridTable ObjectType = "HYBRID TABLE" + ObjectTypeImageRepository ObjectType = "IMAGE REPOSITORY" + ObjectTypeProjectionPolicy ObjectType = "PROJECTION POLICY" ) func (o ObjectType) String() string { @@ -71,53 +76,58 @@ func (o ObjectType) String() string { func objectTypeSingularToPluralMap() map[ObjectType]PluralObjectType { return map[ObjectType]PluralObjectType{ - ObjectTypeAccount: PluralObjectTypeAccounts, - ObjectTypeManagedAccount: PluralObjectTypeManagedAccounts, - ObjectTypeUser: PluralObjectTypeUsers, - ObjectTypeDatabaseRole: PluralObjectTypeDatabaseRoles, - ObjectTypeRole: PluralObjectTypeRoles, - ObjectTypeIntegration: PluralObjectTypeIntegrations, - ObjectTypeNetworkPolicy: PluralObjectTypeNetworkPolicies, - ObjectTypePasswordPolicy: PluralObjectTypePasswordPolicies, - ObjectTypeSessionPolicy: PluralObjectTypeSessionPolicies, - ObjectTypeReplicationGroup: PluralObjectTypeReplicationGroups, - ObjectTypeFailoverGroup: PluralObjectTypeFailoverGroups, - ObjectTypeConnection: PluralObjectTypeConnections, - ObjectTypeParameter: PluralObjectTypeParameters, - ObjectTypeWarehouse: PluralObjectTypeWarehouses, - ObjectTypeResourceMonitor: PluralObjectTypeResourceMonitors, - ObjectTypeDatabase: PluralObjectTypeDatabases, - ObjectTypeSchema: PluralObjectTypeSchemas, - ObjectTypeShare: PluralObjectTypeShares, - ObjectTypeTable: PluralObjectTypeTables, - ObjectTypeDynamicTable: PluralObjectTypeDynamicTables, - ObjectTypeExternalTable: PluralObjectTypeExternalTables, - ObjectTypeEventTable: PluralObjectTypeEventTables, - ObjectTypeView: PluralObjectTypeViews, - ObjectTypeMaterializedView: PluralObjectTypeMaterializedViews, - ObjectTypeSequence: PluralObjectTypeSequences, - ObjectTypeFunction: PluralObjectTypeFunctions, - ObjectTypeExternalFunction: PluralObjectTypeExternalFunctions, - ObjectTypeProcedure: PluralObjectTypeProcedures, - ObjectTypeStream: PluralObjectTypeStreams, - ObjectTypeTask: PluralObjectTypeTasks, - ObjectTypeMaskingPolicy: PluralObjectTypeMaskingPolicies, - ObjectTypeRowAccessPolicy: PluralObjectTypeRowAccessPolicies, - ObjectTypeTag: PluralObjectTypeTags, - ObjectTypeSecret: PluralObjectTypeSecrets, - ObjectTypeStage: PluralObjectTypeStages, - ObjectTypeFileFormat: PluralObjectTypeFileFormats, - ObjectTypePipe: PluralObjectTypePipes, - ObjectTypeAlert: PluralObjectTypeAlerts, - ObjectTypeApplication: PluralObjectTypeApplications, - ObjectTypeApplicationPackage: PluralObjectTypeApplicationPackages, - ObjectTypeApplicationRole: PluralObjectTypeApplicationRoles, - ObjectTypeStreamlit: PluralObjectTypeStreamlits, - ObjectTypeIcebergTable: PluralObjectTypeIcebergTables, - ObjectTypeExternalVolume: PluralObjectTypeExternalVolumes, - ObjectTypeNetworkRule: PluralObjectTypeNetworkRules, - ObjectTypePackagesPolicy: PluralObjectTypePackagesPolicies, - ObjectTypeComputePool: PluralObjectTypeComputePool, + ObjectTypeAccount: PluralObjectTypeAccounts, + ObjectTypeManagedAccount: PluralObjectTypeManagedAccounts, + ObjectTypeUser: PluralObjectTypeUsers, + ObjectTypeDatabaseRole: PluralObjectTypeDatabaseRoles, + ObjectTypeRole: PluralObjectTypeRoles, + ObjectTypeIntegration: PluralObjectTypeIntegrations, + ObjectTypeNetworkPolicy: PluralObjectTypeNetworkPolicies, + ObjectTypePasswordPolicy: PluralObjectTypePasswordPolicies, + ObjectTypeSessionPolicy: PluralObjectTypeSessionPolicies, + ObjectTypeReplicationGroup: PluralObjectTypeReplicationGroups, + ObjectTypeFailoverGroup: PluralObjectTypeFailoverGroups, + ObjectTypeConnection: PluralObjectTypeConnections, + ObjectTypeParameter: PluralObjectTypeParameters, + ObjectTypeWarehouse: PluralObjectTypeWarehouses, + ObjectTypeResourceMonitor: PluralObjectTypeResourceMonitors, + ObjectTypeDatabase: PluralObjectTypeDatabases, + ObjectTypeSchema: PluralObjectTypeSchemas, + ObjectTypeShare: PluralObjectTypeShares, + ObjectTypeTable: PluralObjectTypeTables, + ObjectTypeDynamicTable: PluralObjectTypeDynamicTables, + ObjectTypeExternalTable: PluralObjectTypeExternalTables, + ObjectTypeEventTable: PluralObjectTypeEventTables, + ObjectTypeView: PluralObjectTypeViews, + ObjectTypeMaterializedView: PluralObjectTypeMaterializedViews, + ObjectTypeSequence: PluralObjectTypeSequences, + ObjectTypeFunction: PluralObjectTypeFunctions, + ObjectTypeExternalFunction: PluralObjectTypeExternalFunctions, + ObjectTypeProcedure: PluralObjectTypeProcedures, + ObjectTypeStream: PluralObjectTypeStreams, + ObjectTypeTask: PluralObjectTypeTasks, + ObjectTypeMaskingPolicy: PluralObjectTypeMaskingPolicies, + ObjectTypeRowAccessPolicy: PluralObjectTypeRowAccessPolicies, + ObjectTypeTag: PluralObjectTypeTags, + ObjectTypeSecret: PluralObjectTypeSecrets, + ObjectTypeStage: PluralObjectTypeStages, + ObjectTypeFileFormat: PluralObjectTypeFileFormats, + ObjectTypePipe: PluralObjectTypePipes, + ObjectTypeAlert: PluralObjectTypeAlerts, + ObjectTypeApplication: PluralObjectTypeApplications, + ObjectTypeApplicationPackage: PluralObjectTypeApplicationPackages, + ObjectTypeApplicationRole: PluralObjectTypeApplicationRoles, + ObjectTypeStreamlit: PluralObjectTypeStreamlits, + ObjectTypeIcebergTable: PluralObjectTypeIcebergTables, + ObjectTypeExternalVolume: PluralObjectTypeExternalVolumes, + ObjectTypeNetworkRule: PluralObjectTypeNetworkRules, + ObjectTypePackagesPolicy: PluralObjectTypePackagesPolicies, + ObjectTypeComputePool: PluralObjectTypeComputePool, + ObjectTypeAggregationPolicy: PluralObjectTypeAggregationPolicies, + ObjectTypeAuthenticationPolicy: PluralObjectTypeAuthenticationPolicies, + ObjectTypeHybridTable: PluralObjectTypeHybridTables, + ObjectTypeImageRepository: PluralObjectTypeImageRepositories, + ObjectTypeProjectionPolicy: PluralObjectTypeProjectionPolicies, } } @@ -166,53 +176,58 @@ func (o ObjectType) GetObjectIdentifier(fullyQualifiedName string) ObjectIdentif type PluralObjectType string const ( - PluralObjectTypeAccounts PluralObjectType = "ACCOUNTS" - PluralObjectTypeManagedAccounts PluralObjectType = "MANAGED ACCOUNTS" - PluralObjectTypeUsers PluralObjectType = "USERS" - PluralObjectTypeDatabaseRoles PluralObjectType = "DATABASE ROLES" - PluralObjectTypeRoles PluralObjectType = "ROLES" - PluralObjectTypeIntegrations PluralObjectType = "INTEGRATIONS" - PluralObjectTypeNetworkPolicies PluralObjectType = "NETWORK POLICIES" - PluralObjectTypePasswordPolicies PluralObjectType = "PASSWORD POLICIES" - PluralObjectTypeSessionPolicies PluralObjectType = "SESSION POLICIES" - PluralObjectTypeReplicationGroups PluralObjectType = "REPLICATION GROUPS" - PluralObjectTypeFailoverGroups PluralObjectType = "FAILOVER GROUPS" - PluralObjectTypeConnections PluralObjectType = "CONNECTIONS" - PluralObjectTypeParameters PluralObjectType = "PARAMETERS" - PluralObjectTypeWarehouses PluralObjectType = "WAREHOUSES" - PluralObjectTypeResourceMonitors PluralObjectType = "RESOURCE MONITORS" - PluralObjectTypeDatabases PluralObjectType = "DATABASES" - PluralObjectTypeSchemas PluralObjectType = "SCHEMAS" - PluralObjectTypeShares PluralObjectType = "SHARES" - PluralObjectTypeTables PluralObjectType = "TABLES" - PluralObjectTypeDynamicTables PluralObjectType = "DYNAMIC TABLES" - PluralObjectTypeExternalTables PluralObjectType = "EXTERNAL TABLES" - PluralObjectTypeEventTables PluralObjectType = "EVENT TABLES" - PluralObjectTypeViews PluralObjectType = "VIEWS" - PluralObjectTypeMaterializedViews PluralObjectType = "MATERIALIZED VIEWS" - PluralObjectTypeSequences PluralObjectType = "SEQUENCES" - PluralObjectTypeFunctions PluralObjectType = "FUNCTIONS" - PluralObjectTypeExternalFunctions PluralObjectType = "EXTERNAL FUNCTIONS" - PluralObjectTypeProcedures PluralObjectType = "PROCEDURES" - PluralObjectTypeStreams PluralObjectType = "STREAMS" - PluralObjectTypeTasks PluralObjectType = "TASKS" - PluralObjectTypeMaskingPolicies PluralObjectType = "MASKING POLICIES" - PluralObjectTypeRowAccessPolicies PluralObjectType = "ROW ACCESS POLICIES" - PluralObjectTypeTags PluralObjectType = "TAGS" - PluralObjectTypeSecrets PluralObjectType = "SECRETS" - PluralObjectTypeStages PluralObjectType = "STAGES" - PluralObjectTypeFileFormats PluralObjectType = "FILE FORMATS" - PluralObjectTypePipes PluralObjectType = "PIPES" - PluralObjectTypeAlerts PluralObjectType = "ALERTS" - PluralObjectTypeApplications PluralObjectType = "APPLICATIONS" - PluralObjectTypeApplicationPackages PluralObjectType = "APPLICATION PACKAGES" - PluralObjectTypeApplicationRoles PluralObjectType = "APPLICATION ROLES" - PluralObjectTypeStreamlits PluralObjectType = "STREAMLITS" - PluralObjectTypeIcebergTables PluralObjectType = "ICEBERG TABLES" - PluralObjectTypeExternalVolumes PluralObjectType = "EXTERNAL VOLUMES" - PluralObjectTypeNetworkRules PluralObjectType = "NETWORK RULES" - PluralObjectTypePackagesPolicies PluralObjectType = "PACKAGES POLICIES" - PluralObjectTypeComputePool PluralObjectType = "COMPUTE POOLS" + PluralObjectTypeAccounts PluralObjectType = "ACCOUNTS" + PluralObjectTypeManagedAccounts PluralObjectType = "MANAGED ACCOUNTS" + PluralObjectTypeUsers PluralObjectType = "USERS" + PluralObjectTypeDatabaseRoles PluralObjectType = "DATABASE ROLES" + PluralObjectTypeRoles PluralObjectType = "ROLES" + PluralObjectTypeIntegrations PluralObjectType = "INTEGRATIONS" + PluralObjectTypeNetworkPolicies PluralObjectType = "NETWORK POLICIES" + PluralObjectTypePasswordPolicies PluralObjectType = "PASSWORD POLICIES" + PluralObjectTypeSessionPolicies PluralObjectType = "SESSION POLICIES" + PluralObjectTypeReplicationGroups PluralObjectType = "REPLICATION GROUPS" + PluralObjectTypeFailoverGroups PluralObjectType = "FAILOVER GROUPS" + PluralObjectTypeConnections PluralObjectType = "CONNECTIONS" + PluralObjectTypeParameters PluralObjectType = "PARAMETERS" + PluralObjectTypeWarehouses PluralObjectType = "WAREHOUSES" + PluralObjectTypeResourceMonitors PluralObjectType = "RESOURCE MONITORS" + PluralObjectTypeDatabases PluralObjectType = "DATABASES" + PluralObjectTypeSchemas PluralObjectType = "SCHEMAS" + PluralObjectTypeShares PluralObjectType = "SHARES" + PluralObjectTypeTables PluralObjectType = "TABLES" + PluralObjectTypeDynamicTables PluralObjectType = "DYNAMIC TABLES" + PluralObjectTypeExternalTables PluralObjectType = "EXTERNAL TABLES" + PluralObjectTypeEventTables PluralObjectType = "EVENT TABLES" + PluralObjectTypeViews PluralObjectType = "VIEWS" + PluralObjectTypeMaterializedViews PluralObjectType = "MATERIALIZED VIEWS" + PluralObjectTypeSequences PluralObjectType = "SEQUENCES" + PluralObjectTypeFunctions PluralObjectType = "FUNCTIONS" + PluralObjectTypeExternalFunctions PluralObjectType = "EXTERNAL FUNCTIONS" + PluralObjectTypeProcedures PluralObjectType = "PROCEDURES" + PluralObjectTypeStreams PluralObjectType = "STREAMS" + PluralObjectTypeTasks PluralObjectType = "TASKS" + PluralObjectTypeMaskingPolicies PluralObjectType = "MASKING POLICIES" + PluralObjectTypeRowAccessPolicies PluralObjectType = "ROW ACCESS POLICIES" + PluralObjectTypeTags PluralObjectType = "TAGS" + PluralObjectTypeSecrets PluralObjectType = "SECRETS" + PluralObjectTypeStages PluralObjectType = "STAGES" + PluralObjectTypeFileFormats PluralObjectType = "FILE FORMATS" + PluralObjectTypePipes PluralObjectType = "PIPES" + PluralObjectTypeAlerts PluralObjectType = "ALERTS" + PluralObjectTypeApplications PluralObjectType = "APPLICATIONS" + PluralObjectTypeApplicationPackages PluralObjectType = "APPLICATION PACKAGES" + PluralObjectTypeApplicationRoles PluralObjectType = "APPLICATION ROLES" + PluralObjectTypeStreamlits PluralObjectType = "STREAMLITS" + PluralObjectTypeIcebergTables PluralObjectType = "ICEBERG TABLES" + PluralObjectTypeExternalVolumes PluralObjectType = "EXTERNAL VOLUMES" + PluralObjectTypeNetworkRules PluralObjectType = "NETWORK RULES" + PluralObjectTypePackagesPolicies PluralObjectType = "PACKAGES POLICIES" + PluralObjectTypeComputePool PluralObjectType = "COMPUTE POOLS" + PluralObjectTypeAggregationPolicies PluralObjectType = "AGGREGATION POLICIES" + PluralObjectTypeAuthenticationPolicies PluralObjectType = "AUTHENTICATION POLICIES" + PluralObjectTypeHybridTables PluralObjectType = "HYBRID TABLES" + PluralObjectTypeImageRepositories PluralObjectType = "IMAGE REPOSITORIES" + PluralObjectTypeProjectionPolicies PluralObjectType = "PROJECTION POLICIES" ) func (p PluralObjectType) String() string { diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index 7f3033b8f3..f03257904f 100644 --- a/pkg/sdk/testint/helpers_test.go +++ b/pkg/sdk/testint/helpers_test.go @@ -620,7 +620,7 @@ func createFailoverGroup(t *testing.T, client *sdk.Client) (*sdk.FailoverGroup, func createFailoverGroupWithOptions(t *testing.T, client *sdk.Client, objectTypes []sdk.PluralObjectType, allowedAccounts []sdk.AccountIdentifier, opts *sdk.CreateFailoverGroupOptions) (*sdk.FailoverGroup, func()) { t.Helper() - id := sdk.RandomAccountObjectIdentifier() + id := sdk.RandomAlphanumericAccountObjectIdentifier() ctx := context.Background() err := client.FailoverGroups.Create(ctx, id, objectTypes, allowedAccounts, opts) require.NoError(t, err) diff --git a/pkg/sdk/testint/stages_gen_integration_test.go b/pkg/sdk/testint/stages_gen_integration_test.go index 1ff1327a69..08726549f4 100644 --- a/pkg/sdk/testint/stages_gen_integration_test.go +++ b/pkg/sdk/testint/stages_gen_integration_test.go @@ -346,6 +346,11 @@ func TestInt_Stages(t *testing.T) { stage, err := client.Stages.ShowByID(ctx, id) require.NoError(t, err) assertStage(t, stage, id, "EXTERNAL", "Updated comment", "AWS", awsBucketUrl, s3StorageIntegration.Name) + + props, err := client.Stages.Describe(ctx, id) + + require.NoError(t, err) + require.NotNil(t, props) }) t.Run("AlterExternalGCSStage", func(t *testing.T) { diff --git a/templates/resources/grant_privileges_to_account_role.md.tmpl b/templates/resources/grant_privileges_to_account_role.md.tmpl index d5168f46c6..f591bf021c 100644 --- a/templates/resources/grant_privileges_to_account_role.md.tmpl +++ b/templates/resources/grant_privileges_to_account_role.md.tmpl @@ -31,7 +31,7 @@ description: |- ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for schema object it is `""."".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/templates/resources/grant_privileges_to_database_role.md.tmpl b/templates/resources/grant_privileges_to_database_role.md.tmpl index 471eb2f001..1f31f6ac18 100644 --- a/templates/resources/grant_privileges_to_database_role.md.tmpl +++ b/templates/resources/grant_privileges_to_database_role.md.tmpl @@ -29,7 +29,7 @@ description: |- ## Import -~> **Note** All the ..._name parts should be fully qualified names, e.g. for database object it is `"".""` +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for database object it is `"".""` ~> **Note** To import all_privileges write ALL or ALL PRIVILEGES in place of `` Import is supported using the following syntax: diff --git a/tmp/grant_ownership.md.tmpl b/tmp/grant_ownership.md.tmpl new file mode 100644 index 0000000000..3de22b6887 --- /dev/null +++ b/tmp/grant_ownership.md.tmpl @@ -0,0 +1,51 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +{{/* TODO: Cannot mark the documentation page as draft, so remove this after the resource is available */}} +!> **Warning** We're in a process of implementing this resource, so it's not available yet. + +~> **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. +~> **Note** For more details about granting ownership, please visit [`GRANT OWNERSHIP` Snowflake documentation page](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership). + +{{/* TODO: Add note on how on_future works - could be also added to other grant resources with on_future option */}} +{{/* TODO: Add warnings If on_all option will be available - during delete user could transfer ownership of not intended objects due to it's nature */}} + +# {{.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 (where every part is quoted), e.g. for schema object it is `""."".""` + +Import is supported using the following syntax: + +`terraform import "||||"` + +where: +- role_type - string - type of granted role (either ToAccountRole or ToDatabaseRole) +- role_name - string - fully qualified identifier for either account role or database role (depending on the role_type) +- outbound_privileges_behavior - string - behavior specified for existing roles (can be either COPY or REVOKE) +- grant_type - enum +- grant_data - data dependent on grant_type + +It has varying number of parts, depending on grant_type. All the possible types are: + +{{ index (split (codefile "" .ImportFile) "```") 1 | trimspace }} diff --git a/tmp/snowflake_grant_ownership/import.sh b/tmp/snowflake_grant_ownership/import.sh new file mode 100644 index 0000000000..794f5b79e2 --- /dev/null +++ b/tmp/snowflake_grant_ownership/import.sh @@ -0,0 +1,41 @@ +### OnObject +`terraform import "|||OnObject||"` + +### OnAll (contains inner types: InDatabase | InSchema) + +#### InDatabase +`terraform import "|||OnAll||InDatabase|"` + +#### InSchema +`terraform import "|||OnAll||InSchema|"` + +### OnFuture (contains inner types: InDatabase | InSchema) + +#### InDatabase +`terraform import "|||OnFuture||InDatabase|"` + +#### InSchema +`terraform import "|||OnFuture||InSchema|"` + +### Import examples + +#### OnObject on Schema ToAccountRole +`terraform import "ToAccountRole|\"account_role\"|COPY|OnObject|SCHEMA|\"database_name\".\"schema_name\""` + +#### OnObject on Schema ToDatabaseRole +`terraform import "ToDatabaseRole|\"database_name\".\"database_role_name\"|COPY|OnObject|SCHEMA|\"database_name\".\"schema_name\""` + +#### OnObject on Table +`terraform import "ToAccountRole|\"account_role\"|COPY|OnObject|TABLE|\"database_name\".\"schema_name\".\"table_name\""` + +#### OnAll InDatabase +`terraform import "ToAccountRole|\"account_role\"|REVOKE|OnAll|TABLES|InDatabase|\"database_name\""` + +#### OnAll InSchema +`terraform import "ToAccountRole|\"account_role\"||OnAll|TABLES|InSchema|\"database_name\".\"schema_name\""` + +#### OnFuture InDatabase +`terraform import "ToAccountRole|\"account_role\"||OnFuture|TABLES|InDatabase|\"database_name\""` + +#### OnFuture InSchema +`terraform import "ToAccountRole|\"account_role\"|COPY|OnFuture|TABLES|InSchema|\"database_name\".\"schema_name\""` diff --git a/tmp/snowflake_grant_ownership/resource.tf b/tmp/snowflake_grant_ownership/resource.tf new file mode 100644 index 0000000000..e6c4f1cf51 --- /dev/null +++ b/tmp/snowflake_grant_ownership/resource.tf @@ -0,0 +1,151 @@ +################################## +### on object to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + outbound_privileges = "COPY" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} + +################################## +### on object to database role +################################## + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_database_role" "test" { + name = "test_database_role" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database_role.test.database}\".\"${snowflake_database_role.test.name}\"" + outbound_privileges = "REVOKE" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} + +################################## +### on all tables in database to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + all { + plural_object_type = "TABLES" + in_database = snowflake_database.test.name + } + } +} + +################################## +### on all tables in schema to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + all { + plural_object_type = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} + +################################## +### on future tables in database to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + plural_object_type = "TABLES" + in_database = snowflake_database.test.name + } + } +} + +################################## +### on future tables in schema to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + plural_object_type = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} +