Skip to content

Commit

Permalink
rework tag association
Browse files Browse the repository at this point in the history
  • Loading branch information
sfc-gh-jmichalak committed Nov 20, 2024
1 parent 6f06e25 commit 624a07d
Show file tree
Hide file tree
Showing 43 changed files with 865 additions and 272 deletions.
40 changes: 40 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,46 @@ across different versions.
> [!TIP]
> We highly recommend upgrading the versions one by one instead of bulk upgrades.
## v0.98.0 ➞ v0.99.0

### snowflake_tag_association resource changes
#### *(behavior change)* new id format
In order to provide more functionality for tagging objects, we have changed the resource id from `"TAG_DATABASE"."TAG_SCHEMA"."TAG_NAME"` to `"TAG_DATABASE"."TAG_SCHEMA"."TAG_NAME"|TAG_VALUE|OBJECT_TYPE`. This allows to group tags associations per tag ID, tag value and object type like the following:
```
```

The state is migrated automatically. There is no need to adjust configuration files, unless you use resource id like `snowflake_tag_association.example.id`.


#### *(behavior change)* changed fields
Behavior of some fields was changed:
- `object_identifier` was renamed to `object_identifiers` and it is now a set of fully qualified names. Change your configurations from
```
resource "snowflake_tag_association" "table_association" {
object_identifier {
name = snowflake_table.test.name
database = snowflake_database.test.name
schema = snowflake_schema.test.name
}
object_type = "TABLE"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}
```
to
```
resource "snowflake_tag_association" "table_association" {
object_identifiers = [snowflake_table.test.fully_qualified_name]
object_type = "TABLE"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}
```
- `tag_id` has now suppressed identifier quoting to prevent issues with Terraform showing permament differences, like [this one](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2982)
- `object_type` and `tag_id` are now marked as ForceNew

The state is migrated automatically. Please adjust your configuration files.

## v0.97.0 ➞ v0.98.0

### *(new feature)* snowflake_connections datasource
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/authentication_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ description: |-
Resource used to manage authentication policy objects. For more information, check authentication policy documentation https://docs.snowflake.com/en/sql-reference/sql/create-authentication-policy.
---

-> **Note** According to Snowflake [docs](https://docs.snowflake.com/en/sql-reference/sql/drop-authentication-policy#usage-notes), an authentication policy cannot be dropped successfully if it is currently assigned to another object. Currently, the provider does not unassign such objects automatically. Before dropping the resource, list the assigned objects with `SELECT * from table(information_schema.policy_references(policy_name=>'<string>'));` and unassign them manually with `ALTER ...` or with updated Terraform configuration, if possible.

# snowflake_authentication_policy (Resource)

Resource used to manage authentication policy objects. For more information, check [authentication policy documentation](https://docs.snowflake.com/en/sql-reference/sql/create-authentication-policy).
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/masking_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: |-

!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0950--v0960) to use it.

-> **Note** According to Snowflake [docs](https://docs.snowflake.com/en/sql-reference/sql/drop-masking-policy#usage-notes), a masking policy cannot be dropped successfully if it is currently assigned to another object. Currently, the provider does not unassign such objects automatically. Before dropping the resource, list the assigned objects with `SELECT * from table(information_schema.policy_references(policy_name=>'<string>'));` and unassign them manually with `ALTER ...` or with updated Terraform configuration, if possible.

# snowflake_masking_policy (Resource)

Resource used to manage masking policies. For more information, check [masking policies documentation](https://docs.snowflake.com/en/sql-reference/sql/create-masking-policy).
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/network_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: |-

!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it.

-> **Note** According to Snowflake [docs](https://docs.snowflake.com/en/sql-reference/sql/drop-network-policy#usage-notes), a network policy cannot be dropped successfully if it is currently assigned to another object. Currently, the provider does not unassign such objects automatically. Before dropping the resource, list the assigned objects with `SELECT * from table(information_schema.policy_references(policy_name=>'<string>'));` and unassign them manually with `ALTER ...` or with updated Terraform configuration, if possible.

# snowflake_network_policy (Resource)

Resource used to control network traffic. For more information, check an [official guide](https://docs.snowflake.com/en/user-guide/network-policies) on controlling network traffic with network policies.
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/password_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ description: |-
A password policy specifies the requirements that must be met to create and reset a password to authenticate to Snowflake.
---

-> **Note** According to Snowflake [docs](https://docs.snowflake.com/en/sql-reference/sql/drop-password-policy#usage-notes), a password policy cannot be dropped successfully if it is currently assigned to another object. Currently, the provider does not unassign such objects automatically. Before dropping the resource, list the assigned objects with `SELECT * from table(information_schema.policy_references(policy_name=>'<string>'));` and unassign them manually with `ALTER ...` or with updated Terraform configuration, if possible.

# snowflake_password_policy (Resource)

A password policy specifies the requirements that must be met to create and reset a password to authenticate to Snowflake.
Expand Down
2 changes: 2 additions & 0 deletions docs/resources/row_access_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ description: |-

!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0950--v0960) to use it.

-> **Note** According to Snowflake [docs](https://docs.snowflake.com/en/sql-reference/sql/drop-row-access-policy#usage-notes), a row access policy cannot be dropped successfully if it is currently assigned to another object. Currently, the provider does not unassign such objects automatically. Before dropping the resource, list the assigned objects with `SELECT * from table(information_schema.policy_references(policy_name=>'<string>'));` and unassign them manually with `ALTER ...` or with updated Terraform configuration, if possible.

# snowflake_row_access_policy (Resource)

Resource used to manage row access policy objects. For more information, check [row access policy documentation](https://docs.snowflake.com/en/sql-reference/sql/create-row-access-policy).
Expand Down
70 changes: 28 additions & 42 deletions docs/resources/tag_association.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
page_title: "snowflake_tag_association Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Resource used to manage tag associations. For more information, check object tagging documentation https://docs.snowflake.com/en/user-guide/object-tagging.
---

# snowflake_tag_association (Resource)
!> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0980--v0990) to use it.

# snowflake_tag_association (Resource)

Resource used to manage tag associations. For more information, check [object tagging documentation](https://docs.snowflake.com/en/user-guide/object-tagging).

## Example Usage

Expand All @@ -29,12 +31,10 @@ resource "snowflake_tag" "test" {
}
resource "snowflake_tag_association" "db_association" {
object_identifier {
name = snowflake_database.test.name
}
object_type = "DATABASE"
tag_id = snowflake_tag.test.id
tag_value = "finance"
object_identifiers = [snowflake_database.test.fully_qualified_name]
object_type = "DATABASE"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "finance"
}
resource "snowflake_table" "test" {
Expand All @@ -53,28 +53,26 @@ resource "snowflake_table" "test" {
}
resource "snowflake_tag_association" "table_association" {
object_identifier {
name = snowflake_table.test.name
database = snowflake_database.test.name
schema = snowflake_schema.test.name
}
object_type = "TABLE"
tag_id = snowflake_tag.test.id
tag_value = "engineering"
object_identifiers = [snowflake_table.test.fully_qualified_name]
object_type = "TABLE"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}
resource "snowflake_tag_association" "column_association" {
object_identifier {
name = "${snowflake_table.test.name}.column_name"
database = snowflake_database.test.name
schema = snowflake_schema.test.name
}
object_type = "COLUMN"
tag_id = snowflake_tag.test.id
tag_value = "engineering"
object_identifiers = [snowflake_database.test.fully_qualified_name]
object_type = "COLUMN"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}
```
resource "snowflake_tag_association" "account_association" {
object_identifiers = ["\"ORGANIZATION_NAME\".\"ACCOUNT_NAME\""]
object_type = "ACCOUNT"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}
```
-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/guides/identifiers#new-computed-fully-qualified-name-field-in-resources).
<!-- TODO(SNOW-1634854): include an example showing both methods-->

Expand All @@ -83,9 +81,9 @@ resource "snowflake_tag_association" "column_association" {

### Required

- `object_identifier` (Block List, Min: 1) Specifies the object identifier for the tag association. (see [below for nested schema](#nestedblock--object_identifier))
- `object_identifiers` (Set of String) Specifies the object identifiers for the tag association.
- `object_type` (String) Specifies the type of object to add a tag. Allowed object types: [ACCOUNT APPLICATION APPLICATION PACKAGE DATABASE FAILOVER GROUP INTEGRATION NETWORK POLICY REPLICATION GROUP ROLE SHARE USER WAREHOUSE DATABASE ROLE SCHEMA ALERT SNOWFLAKE.CORE.BUDGET SNOWFLAKE.ML.CLASSIFICATION EXTERNAL FUNCTION EXTERNAL TABLE FUNCTION GIT REPOSITORY ICEBERG TABLE MATERIALIZED VIEW PIPE MASKING POLICY PASSWORD POLICY ROW ACCESS POLICY SESSION POLICY PRIVACY POLICY PROCEDURE STAGE STREAM TABLE TASK VIEW COLUMN EVENT TABLE].
- `tag_id` (String) Specifies the identifier for the tag. Note: format must follow: "databaseName"."schemaName"."tagName" or "databaseName.schemaName.tagName" or "databaseName|schemaName.tagName" (snowflake_tag.tag.id)
- `tag_id` (String) Specifies the identifier for the tag.
- `tag_value` (String) Specifies the value of the tag, (e.g. 'finance' or 'engineering')

### Optional
Expand All @@ -98,19 +96,6 @@ resource "snowflake_tag_association" "column_association" {

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

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

Required:

- `name` (String) Name of the object to associate the tag with.

Optional:

- `database` (String) Name of the database that the object was created in.
- `schema` (String) Name of the schema that the object was created in.


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

Expand All @@ -120,9 +105,10 @@ Optional:

## Import

~> **Note** Due to technical limitations of Terraform SDK, `object_identifiers` are not set during import state. Please run `terraform refresh` after importing to get this field populated.

Import is supported using the following syntax:

```shell
# format is dbName.schemaName.tagName or dbName.schemaName.tagName
terraform import snowflake_tag_association.example 'dbName.schemaName.tagName'
terraform import snowflake_tag_association.example '"TAG_DATABASE"."TAG_SCHEMA"."TAG_NAME"|TAG_VALUE|OBJECT_TYPE'
```
3 changes: 1 addition & 2 deletions examples/resources/snowflake_tag_association/import.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
# format is dbName.schemaName.tagName or dbName.schemaName.tagName
terraform import snowflake_tag_association.example 'dbName.schemaName.tagName'
terraform import snowflake_tag_association.example '"TAG_DATABASE"."TAG_SCHEMA"."TAG_NAME"|TAG_VALUE|OBJECT_TYPE'
41 changes: 19 additions & 22 deletions examples/resources/snowflake_tag_association/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ resource "snowflake_tag" "test" {
}

resource "snowflake_tag_association" "db_association" {
object_identifier {
name = snowflake_database.test.name
}
object_type = "DATABASE"
tag_id = snowflake_tag.test.id
tag_value = "finance"
object_identifiers = [snowflake_database.test.fully_qualified_name]
object_type = "DATABASE"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "finance"
}

resource "snowflake_table" "test" {
Expand All @@ -39,23 +37,22 @@ resource "snowflake_table" "test" {
}

resource "snowflake_tag_association" "table_association" {
object_identifier {
name = snowflake_table.test.name
database = snowflake_database.test.name
schema = snowflake_schema.test.name
}
object_type = "TABLE"
tag_id = snowflake_tag.test.id
tag_value = "engineering"
object_identifiers = [snowflake_table.test.fully_qualified_name]
object_type = "TABLE"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}

resource "snowflake_tag_association" "column_association" {
object_identifier {
name = "${snowflake_table.test.name}.column_name"
database = snowflake_database.test.name
schema = snowflake_schema.test.name
}
object_type = "COLUMN"
tag_id = snowflake_tag.test.id
tag_value = "engineering"
object_identifiers = [snowflake_database.test.fully_qualified_name]
object_type = "COLUMN"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}

resource "snowflake_tag_association" "account_association" {
object_identifiers = ["\"ORGANIZATION_NAME\".\"ACCOUNT_NAME\""]
object_type = "ACCOUNT"
tag_id = snowflake_tag.test.fully_qualified_name
tag_value = "engineering"
}
50 changes: 50 additions & 0 deletions pkg/acceptance/check_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"testing"

Expand Down Expand Up @@ -490,6 +491,55 @@ func CheckUserAuthenticationPolicyAttachmentDestroy(t *testing.T) func(*terrafor
}
}

// CheckTagValueEmpty is a custom check that should be later incorporated into generic CheckDestroy
func CheckTagValueEmpty(t *testing.T) func(*terraform.State) error {
t.Helper()

return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "snowflake_tag_association" {
continue
}
objectType := sdk.ObjectType(rs.Primary.Attributes["object_type"])
tagId, err := sdk.ParseSchemaObjectIdentifier(rs.Primary.Attributes["tag_id"])
if err != nil {
return err
}
idLen, err := strconv.Atoi(rs.Primary.Attributes["object_identifiers.#"])
if err != nil {
return err
}
for i := 0; i < idLen; i++ {
idRaw := rs.Primary.Attributes[fmt.Sprintf("object_identifiers.%d", i)]
var id sdk.ObjectIdentifier
if objectType == sdk.ObjectTypeAccount {
id, err = sdk.ParseAccountIdentifier(idRaw)
if err != nil {
return fmt.Errorf("invalid account id: %w", err)
}
} else {
id, err = sdk.ParseObjectIdentifierString(idRaw)
if err != nil {
return fmt.Errorf("invalid object id: %w", err)
}
}
tag, err := TestClient().Tag.GetTag(t, tagId, id, objectType)
if err != nil {
if strings.Contains(err.Error(), "does not exist or not authorized") {
// Note: this can happen if the object has been deleted as well; in this case, ignore the error
continue
}
return err
}
if tag != nil {
return fmt.Errorf("tag %s for object %s expected to be empty, got %s", tagId.FullyQualifiedName(), id.FullyQualifiedName(), *tag)
}
}
}
return nil
}
}

func TestAccCheckGrantApplicationRoleDestroy(s *terraform.State) error {
client := TestAccProvider.Meta().(*provider.Context).Client
for _, rs := range s.RootModule().Resources {
Expand Down
20 changes: 20 additions & 0 deletions pkg/acceptance/helpers/context_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,26 @@ func (c *ContextClient) CurrentAccount(t *testing.T) string {
return currentAccount
}

func (c *ContextClient) CurrentAccountName(t *testing.T) string {
t.Helper()
ctx := context.Background()

accountName, err := c.client().CurrentAccountName(ctx)
require.NoError(t, err)

return accountName
}

func (c *ContextClient) CurrentOrganizationName(t *testing.T) string {
t.Helper()
ctx := context.Background()

orgName, err := c.client().CurrentOrganizationName(ctx)
require.NoError(t, err)

return orgName
}

func (c *ContextClient) CurrentRole(t *testing.T) sdk.AccountObjectIdentifier {
t.Helper()
ctx := context.Background()
Expand Down
8 changes: 8 additions & 0 deletions pkg/acceptance/helpers/tag_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ func (c *TagClient) Unset(t *testing.T, objectType sdk.ObjectType, id sdk.Object
require.NoError(t, err)
}

func (c *TagClient) GetTag(t *testing.T, tagId sdk.SchemaObjectIdentifier, objectId sdk.ObjectIdentifier, objectType sdk.ObjectType) (*string, error) {
t.Helper()
ctx := context.Background()
client := c.context.client.SystemFunctions

return client.GetTag(ctx, tagId, objectId, objectType)
}

func (c *TagClient) DropTagFunc(t *testing.T, id sdk.SchemaObjectIdentifier) func() {
t.Helper()
ctx := context.Background()
Expand Down
Loading

0 comments on commit 624a07d

Please sign in to comment.