Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Grant ownership follow up #2628

Merged
merged 17 commits into from
Apr 3, 2024
64 changes: 64 additions & 0 deletions docs/resources/grant_ownership.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
page_title: "snowflake_grant_ownership Resource - terraform-provider-snowflake"
subcategory: ""
description: |-

---

# snowflake_grant_ownership (Resource)
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved





<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `on` (Block List, Min: 1, Max: 1) Configures which object(s) should transfer their ownership to the specified role. (see [below for nested schema](#nestedblock--on))

### Optional

- `account_role_name` (String) The fully qualified name of the account role to which privileges will be granted.
- `database_role_name` (String) The fully qualified name of the database role to which privileges will be granted.
- `outbound_privileges` (String) 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).

### Read-Only

- `id` (String) The ID of this resource.

<a id="nestedblock--on"></a>
### Nested Schema for `on`

Optional:

- `all` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on--all))
- `future` (Block List, Max: 1) Configures the privilege to be granted on all objects in either a database or schema. (see [below for nested schema](#nestedblock--on--future))
- `object_name` (String) Specifies the identifier for the object on which you are transferring ownership.
- `object_type` (String) Specifies the type of object on which you are transferring ownership. Available values are: AGGREGATION POLICY | ALERT | AUTHENTICATION POLICY | COMPUTE POOL | DATABASE | DATABASE ROLE | DYNAMIC TABLE | EVENT TABLE | EXTERNAL TABLE | EXTERNAL VOLUME | FAILOVER GROUP | FILE FORMAT | FUNCTION | HYBRID TABLE | ICEBERG TABLE | IMAGE REPOSITORY | INTEGRATION | MATERIALIZED VIEW | NETWORK POLICY | NETWORK RULE | PACKAGES POLICY | PIPE | PROCEDURE | MASKING POLICY | PASSWORD POLICY | PROJECTION POLICY | REPLICATION GROUP | ROLE | ROW ACCESS POLICY | SCHEMA | SESSION POLICY | SECRET | SEQUENCE | STAGE | STREAM | TABLE | TAG | TASK | USER | VIEW | WAREHOUSE

<a id="nestedblock--on--all"></a>
### Nested Schema for `on.all`

Required:

- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters).

Optional:

- `in_database` (String) The fully qualified name of the database.
- `in_schema` (String) The fully qualified name of the schema.


<a id="nestedblock--on--future"></a>
### Nested Schema for `on.future`

Required:

- `object_type_plural` (String) Specifies the type of object in plural form on which you are transferring ownership. Available values are: AGGREGATION POLICIES | ALERTS | AUTHENTICATION POLICIES | COMPUTE POOLS | DATABASES | DATABASE ROLES | DYNAMIC TABLES | EVENT TABLES | EXTERNAL TABLES | EXTERNAL VOLUMES | FAILOVER GROUPS | FILE FORMATS | FUNCTIONS | HYBRID TABLES | ICEBERG TABLES | IMAGE REPOSITORIES | INTEGRATIONS | MATERIALIZED VIEWS | NETWORK POLICIES | NETWORK RULES | PACKAGES POLICIES | PIPES | PROCEDURES | MASKING POLICIES | PASSWORD POLICIES | PROJECTION POLICIES | REPLICATION GROUPS | ROLES | ROW ACCESS POLICIES | SCHEMAS | SESSION POLICIES | SECRETS | SEQUENCES | STAGES | STREAMS | TABLES | TAGS | TASKS | USERS | VIEWS | WAREHOUSES. For more information head over to [Snowflake documentation](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#required-parameters).

Optional:

- `in_database` (String) The fully qualified name of the database.
- `in_schema` (String) The fully qualified name of the schema.
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_function": resources.Function(),
"snowflake_grant_account_role": resources.GrantAccountRole(),
"snowflake_grant_database_role": resources.GrantDatabaseRole(),
"snowflake_grant_ownership": resources.GrantOwnership(),
"snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(),
"snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(),
"snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(),
Expand Down
8 changes: 5 additions & 3 deletions pkg/resources/grant_ownership.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,11 +383,13 @@ func ReadGrantOwnership(ctx context.Context, d *schema.ResourceData, meta any) d
}

if !ownershipFound {
d.SetId("")

return diag.Diagnostics{
diag.Diagnostic{
Severity: diag.Error,
Summary: "Couldn't find OWNERSHIP privilege on target object",
Detail: fmt.Sprintf("Id: %s", d.Id()),
Severity: diag.Warning,
Summary: "Couldn't find OWNERSHIP privilege on the target object. Marking resource as removed.",
Detail: fmt.Sprintf("Id: %s", id.String()),
},
}
}
Expand Down
277 changes: 277 additions & 0 deletions pkg/resources/grant_ownership_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"strings"
"testing"

"github.com/stretchr/testify/assert"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"
"github.com/hashicorp/terraform-plugin-testing/terraform"

Expand Down Expand Up @@ -577,6 +579,281 @@ func TestAcc_GrantOwnership_InvalidConfiguration_MultipleTargets(t *testing.T) {
})
}

func TestAcc_GrantOwnership_MoveOwnershipOutsideTerraform(t *testing.T) {
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()

otherAccountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))

configVariables := config.Variables{
"account_role_name": config.StringVariable(accountRoleName),
"other_account_role_name": config.StringVariable(otherAccountRoleName),
"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/MoveResourceOwnershipOutsideTerraform"),
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)),
),
},
{
PreConfig: func() {
moveResourceOwnershipToAccountRole(t, sdk.ObjectTypeDatabase, sdk.NewAccountObjectIdentifier(databaseName), sdk.NewAccountObjectIdentifier(otherAccountRoleName))
},
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/MoveResourceOwnershipOutsideTerraform"),
ConfigVariables: configVariables,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName),
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
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)),
),
},
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/MoveResourceOwnershipOutsideTerraform"),
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
ConfigVariables: configVariables,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc_GrantOwnership_ForceOwnershipTransferOnCreate(t *testing.T) {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
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{
{
PreConfig: func() {
t.Cleanup(createAccountRole(t, accountRoleName))
t.Cleanup(createDatabaseWithRoleAsOwner(t, accountRoleName, databaseName))
},
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/ForceOwnershipTransferOnCreate"),
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)),
),
},
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/ForceOwnershipTransferOnCreate"),
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
ConfigVariables: configVariables,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc_GrantOwnership_OnPipe(t *testing.T) {
accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
stageName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
pipeName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName()
pipeFullyQualifiedName := sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, pipeName).FullyQualifiedName()

configVariables := config.Variables{
"account_role_name": config.StringVariable(accountRoleName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
"stage": config.StringVariable(stageName),
"table": config.StringVariable(tableName),
"pipe": config.StringVariable(pipeName),
}
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/OnPipe"),
ConfigVariables: configVariables,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName),
resource.TestCheckResourceAttr(resourceName, "on.0.object_type", sdk.ObjectTypePipe.String()),
resource.TestCheckResourceAttr(resourceName, "on.0.object_name", pipeFullyQualifiedName),
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|PIPE|%s", accountRoleFullyQualifiedName, pipeFullyQualifiedName)),
checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{
On: &sdk.ShowGrantsOn{
Object: &sdk.Object{
ObjectType: sdk.ObjectTypePipe,
Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(pipeFullyQualifiedName),
},
},
// TODO(): Fix this identifier
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
}, sdk.ObjectTypePipe, accountRoleName, fmt.Sprintf("%s\".\"%s\".%s", acc.TestDatabaseName, acc.TestSchemaName, pipeName)),
),
},
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnPipe"),
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
ConfigVariables: configVariables,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc_GrantOwnership_OnAllPipes(t *testing.T) {
accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
stageName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
tableName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
pipeName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
secondPipeName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName()
schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName).FullyQualifiedName()

configVariables := config.Variables{
"account_role_name": config.StringVariable(accountRoleName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
"stage": config.StringVariable(stageName),
"table": config.StringVariable(tableName),
"pipe": config.StringVariable(pipeName),
"second_pipe": config.StringVariable(secondPipeName),
}
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/OnAllPipes"),
ConfigVariables: configVariables,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName),
resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnAll|PIPES|InSchema|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)),
checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{
To: &sdk.ShowGrantsTo{
Role: sdk.NewAccountObjectIdentifier(accountRoleName),
},
// TODO: Fix this identifier
}, sdk.ObjectTypePipe, accountRoleName, fmt.Sprintf("%s\".\"%s\".%s", acc.TestDatabaseName, acc.TestSchemaName, pipeName), fmt.Sprintf("%s\".\"%s\".%s", acc.TestDatabaseName, acc.TestSchemaName, secondPipeName)),
),
},
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAllPipes"),
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
ConfigVariables: configVariables,
ResourceName: resourceName,
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func createDatabaseWithRoleAsOwner(t *testing.T, roleName string, databaseName string) func() {
t.Helper()
client, err := sdk.NewDefaultClient()
assert.NoError(t, err)

ctx := context.Background()
databaseId := sdk.NewAccountObjectIdentifier(databaseName)
assert.NoError(t, client.Databases.Create(ctx, databaseId, &sdk.CreateDatabaseOptions{}))

err = client.Grants.GrantOwnership(
ctx,
sdk.OwnershipGrantOn{
Object: &sdk.Object{
ObjectType: sdk.ObjectTypeDatabase,
Name: databaseId,
},
},
sdk.OwnershipGrantTo{
AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(roleName)),
},
new(sdk.GrantOwnershipOptions),
)
assert.NoError(t, err)

return func() {
assert.NoError(t, client.Databases.Drop(ctx, databaseId, &sdk.DropDatabaseOptions{}))
}
}

func createAccountRole(t *testing.T, name string) func() {
sfc-gh-asawicki marked this conversation as resolved.
Show resolved Hide resolved
t.Helper()
client, err := sdk.NewDefaultClient()
assert.NoError(t, err)

ctx := context.Background()
roleId := sdk.NewAccountObjectIdentifier(name)
assert.NoError(t, client.Roles.Create(ctx, sdk.NewCreateRoleRequest(roleId)))

return func() {
assert.NoError(t, client.Roles.Drop(ctx, sdk.NewDropRoleRequest(roleId)))
}
}

func moveResourceOwnershipToAccountRole(t *testing.T, objectType sdk.ObjectType, objectName sdk.ObjectIdentifier, accountRoleName sdk.AccountObjectIdentifier) {
t.Helper()

client, err := sdk.NewDefaultClient()
assert.NoError(t, err)

ctx := context.Background()
err = client.Grants.GrantOwnership(
ctx,
sdk.OwnershipGrantOn{
Object: &sdk.Object{
ObjectType: objectType,
Name: objectName,
},
},
sdk.OwnershipGrantTo{
AccountRoleName: &accountRoleName,
},
new(sdk.GrantOwnershipOptions),
)
assert.NoError(t, err)
}

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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
resource "snowflake_grant_ownership" "test" {
account_role_name = var.account_role_name
on {
object_type = "DATABASE"
object_name = var.database_name
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
variable "account_role_name" {
type = string
}

variable "database_name" {
type = string
}
Loading
Loading