diff --git a/docs/resources/masking_policy.md b/docs/resources/masking_policy.md index 513083aaf3..0806f24147 100644 --- a/docs/resources/masking_policy.md +++ b/docs/resources/masking_policy.md @@ -5,6 +5,8 @@ description: |- Resource used to manage masking policies. For more information, check masking policies documentation https://docs.snowflake.com/en/sql-reference/sql/create-masking-policy. --- +!> **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. + # 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). @@ -74,7 +76,6 @@ EOF comment = "example masking policy" } ``` - -> **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). diff --git a/docs/resources/tag_association.md b/docs/resources/tag_association.md index ea03d48bdd..77acc40091 100644 --- a/docs/resources/tag_association.md +++ b/docs/resources/tag_association.md @@ -84,7 +84,7 @@ 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_type` (String) Specifies the type of object to add a tag. Allowed object types: [ACCOUNT APPLICATION APPLICATION PACKAGE DATABASE INTEGRATION NETWORK POLICY ROLE SHARE USER WAREHOUSE DATABASE ROLE SCHEMA ALERT EXTERNAL FUNCTION EXTERNAL TABLE GIT REPOSITORY ICEBERG TABLE MATERIALIZED VIEW PIPE MASKING POLICY PASSWORD POLICY ROW ACCESS POLICY SESSION POLICY PROCEDURE STAGE STREAM TABLE TASK VIEW COLUMN EVENT TABLE]. +- `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_value` (String) Specifies the value of the tag, (e.g. 'finance' or 'engineering') diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go b/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go index a5aae66a99..395452fddc 100644 --- a/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/gen/sdk_object_def.go @@ -72,6 +72,11 @@ var allStructs = []SdkObjectDef{ ObjectType: sdk.ObjectTypeStream, ObjectStruct: sdk.Stream{}, }, + { + IdType: "sdk.SchemaObjectIdentifier", + ObjectType: sdk.ObjectTypeTag, + ObjectStruct: sdk.Tag{}, + }, } func GetSdkObjectDetails() []genhelpers.SdkObjectDetails { diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/tag_snowflake_ext.go b/pkg/acceptance/bettertestspoc/assert/objectassert/tag_snowflake_ext.go new file mode 100644 index 0000000000..bfb9e14529 --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/tag_snowflake_ext.go @@ -0,0 +1,29 @@ +package objectassert + +import ( + "errors" + "fmt" + "slices" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +func (s *TagAssert) HasAllowedValues(expected ...string) *TagAssert { + s.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if len(o.AllowedValues) != len(expected) { + return fmt.Errorf("expected allowed values length: %v; got: %v", len(expected), len(o.AllowedValues)) + } + var errs []error + for _, wantElem := range expected { + if !slices.ContainsFunc(o.AllowedValues, func(gotElem string) bool { + return wantElem == gotElem + }) { + errs = append(errs, fmt.Errorf("expected value: %s, to be in the value list: %v", wantElem, o.AllowedValues)) + } + } + return errors.Join(errs...) + }) + return s +} diff --git a/pkg/acceptance/bettertestspoc/assert/objectassert/tag_snowflake_gen.go b/pkg/acceptance/bettertestspoc/assert/objectassert/tag_snowflake_gen.go new file mode 100644 index 0000000000..27db2b280e --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/objectassert/tag_snowflake_gen.go @@ -0,0 +1,109 @@ +// Code generated by assertions generator; DO NOT EDIT. + +package objectassert + +import ( + "fmt" + "testing" + "time" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +type TagAssert struct { + *assert.SnowflakeObjectAssert[sdk.Tag, sdk.SchemaObjectIdentifier] +} + +func Tag(t *testing.T, id sdk.SchemaObjectIdentifier) *TagAssert { + t.Helper() + return &TagAssert{ + assert.NewSnowflakeObjectAssertWithProvider(sdk.ObjectTypeTag, id, acc.TestClient().Tag.Show), + } +} + +func TagFromObject(t *testing.T, tag *sdk.Tag) *TagAssert { + t.Helper() + return &TagAssert{ + assert.NewSnowflakeObjectAssertWithObject(sdk.ObjectTypeTag, tag.ID(), tag), + } +} + +func (t *TagAssert) HasCreatedOn(expected time.Time) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.CreatedOn != expected { + return fmt.Errorf("expected created on: %v; got: %v", expected, o.CreatedOn) + } + return nil + }) + return t +} + +func (t *TagAssert) HasName(expected string) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.Name != expected { + return fmt.Errorf("expected name: %v; got: %v", expected, o.Name) + } + return nil + }) + return t +} + +func (t *TagAssert) HasDatabaseName(expected string) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.DatabaseName != expected { + return fmt.Errorf("expected database name: %v; got: %v", expected, o.DatabaseName) + } + return nil + }) + return t +} + +func (t *TagAssert) HasSchemaName(expected string) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.SchemaName != expected { + return fmt.Errorf("expected schema name: %v; got: %v", expected, o.SchemaName) + } + return nil + }) + return t +} + +func (t *TagAssert) HasOwner(expected string) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.Owner != expected { + return fmt.Errorf("expected owner: %v; got: %v", expected, o.Owner) + } + return nil + }) + return t +} + +func (t *TagAssert) HasComment(expected string) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.Comment != expected { + return fmt.Errorf("expected comment: %v; got: %v", expected, o.Comment) + } + return nil + }) + return t +} + +func (t *TagAssert) HasOwnerRoleType(expected string) *TagAssert { + t.AddAssertion(func(t *testing.T, o *sdk.Tag) error { + t.Helper() + if o.OwnerRoleType != expected { + return fmt.Errorf("expected owner role type: %v; got: %v", expected, o.OwnerRoleType) + } + return nil + }) + return t +} diff --git a/pkg/acceptance/bettertestspoc/assert/resourceshowoutputassert/tag_show_output_gen.go b/pkg/acceptance/bettertestspoc/assert/resourceshowoutputassert/tag_show_output_gen.go new file mode 100644 index 0000000000..486e08488c --- /dev/null +++ b/pkg/acceptance/bettertestspoc/assert/resourceshowoutputassert/tag_show_output_gen.go @@ -0,0 +1,82 @@ +// Code generated by assertions generator; DO NOT EDIT. + +package resourceshowoutputassert + +import ( + "testing" + "time" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +// to ensure sdk package is used +var _ = sdk.Object{} + +type TagShowOutputAssert struct { + *assert.ResourceAssert +} + +func TagShowOutput(t *testing.T, name string) *TagShowOutputAssert { + t.Helper() + + tt := TagShowOutputAssert{ + ResourceAssert: assert.NewResourceAssert(name, "show_output"), + } + tt.AddAssertion(assert.ValueSet("show_output.#", "1")) + return &tt +} + +func ImportedTagShowOutput(t *testing.T, id string) *TagShowOutputAssert { + t.Helper() + + tt := TagShowOutputAssert{ + ResourceAssert: assert.NewImportedResourceAssert(id, "show_output"), + } + tt.AddAssertion(assert.ValueSet("show_output.#", "1")) + return &tt +} + +//////////////////////////// +// Attribute value checks // +//////////////////////////// + +func (t *TagShowOutputAssert) HasCreatedOn(expected time.Time) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("created_on", expected.String())) + return t +} + +func (t *TagShowOutputAssert) HasName(expected string) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("name", expected)) + return t +} + +func (t *TagShowOutputAssert) HasDatabaseName(expected string) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("database_name", expected)) + return t +} + +func (t *TagShowOutputAssert) HasSchemaName(expected string) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("schema_name", expected)) + return t +} + +func (t *TagShowOutputAssert) HasOwner(expected string) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("owner", expected)) + return t +} + +func (t *TagShowOutputAssert) HasComment(expected string) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("comment", expected)) + return t +} + +// func (t *TagShowOutputAssert) HasAllowedValues(expected []string) *TagShowOutputAssert { +// t.AddAssertion(assert.ResourceShowOutputValueSet("allowed_values", expected)) +// return t +// } + +func (t *TagShowOutputAssert) HasOwnerRoleType(expected string) *TagShowOutputAssert { + t.AddAssertion(assert.ResourceShowOutputValueSet("owner_role_type", expected)) + return t +} diff --git a/pkg/acceptance/helpers/tag_client.go b/pkg/acceptance/helpers/tag_client.go index 8ee7d7aa3d..ee2f01d46a 100644 --- a/pkg/acceptance/helpers/tag_client.go +++ b/pkg/acceptance/helpers/tag_client.go @@ -29,19 +29,27 @@ func (c *TagClient) CreateTag(t *testing.T) (*sdk.Tag, func()) { return c.CreateTagInSchema(t, c.ids.SchemaId()) } +func (c *TagClient) CreateTagWithIdentifier(t *testing.T, id sdk.SchemaObjectIdentifier) (*sdk.Tag, func()) { + t.Helper() + return c.CreateWithRequest(t, sdk.NewCreateTagRequest(id)) +} + func (c *TagClient) CreateTagInSchema(t *testing.T, schemaId sdk.DatabaseObjectIdentifier) (*sdk.Tag, func()) { t.Helper() - ctx := context.Background() + return c.CreateWithRequest(t, sdk.NewCreateTagRequest(c.ids.RandomSchemaObjectIdentifierInSchema(schemaId))) +} - id := c.ids.RandomSchemaObjectIdentifierInSchema(schemaId) +func (c *TagClient) CreateWithRequest(t *testing.T, req *sdk.CreateTagRequest) (*sdk.Tag, func()) { + t.Helper() + ctx := context.Background() - err := c.client().Create(ctx, sdk.NewCreateTagRequest(id)) + err := c.client().Create(ctx, req) require.NoError(t, err) - tag, err := c.client().ShowByID(ctx, id) + tag, err := c.client().ShowByID(ctx, req.GetName()) require.NoError(t, err) - return tag, c.DropTagFunc(t, id) + return tag, c.DropTagFunc(t, req.GetName()) } func (c *TagClient) DropTagFunc(t *testing.T, id sdk.SchemaObjectIdentifier) func() { @@ -53,3 +61,10 @@ func (c *TagClient) DropTagFunc(t *testing.T, id sdk.SchemaObjectIdentifier) fun require.NoError(t, err) } } + +func (c *TagClient) Show(t *testing.T, id sdk.SchemaObjectIdentifier) (*sdk.Tag, error) { + t.Helper() + ctx := context.Background() + + return c.client().ShowByID(ctx, id) +} diff --git a/pkg/resources/tag.go b/pkg/resources/tag.go index 37e4018b9d..08f50c8116 100644 --- a/pkg/resources/tag.go +++ b/pkg/resources/tag.go @@ -47,6 +47,7 @@ var tagSchema = map[string]*schema.Schema{ FullyQualifiedNameAttributeName: schemas.FullyQualifiedNameSchema, } +// TODO(SNOW-1348114, SNOW-1348110, SNOW-1348355, SNOW-1348353): remove after rework of external table, materialized view, stage and table var tagReferenceSchema = &schema.Schema{ Type: schema.TypeList, Optional: true, diff --git a/pkg/sdk/object_types.go b/pkg/sdk/object_types.go index a5b17fa9c9..6c65d694fe 100644 --- a/pkg/sdk/object_types.go +++ b/pkg/sdk/object_types.go @@ -25,6 +25,7 @@ const ( ObjectTypeNetworkPolicy ObjectType = "NETWORK POLICY" ObjectTypePasswordPolicy ObjectType = "PASSWORD POLICY" ObjectTypeSessionPolicy ObjectType = "SESSION POLICY" + ObjectTypePrivacyPolicy ObjectType = "PRIVACY POLICY" ObjectTypeReplicationGroup ObjectType = "REPLICATION GROUP" ObjectTypeFailoverGroup ObjectType = "FAILOVER GROUP" ObjectTypeConnection ObjectType = "CONNECTION" @@ -56,6 +57,8 @@ const ( ObjectTypeFileFormat ObjectType = "FILE FORMAT" ObjectTypePipe ObjectType = "PIPE" ObjectTypeAlert ObjectType = "ALERT" + ObjectTypeBudget ObjectType = "SNOWFLAKE.CORE.BUDGET" + ObjectTypeClassification ObjectType = "SNOWFLAKE.ML.CLASSIFICATION" ObjectTypeApplication ObjectType = "APPLICATION" ObjectTypeApplicationPackage ObjectType = "APPLICATION PACKAGE" ObjectTypeApplicationRole ObjectType = "APPLICATION ROLE" @@ -98,6 +101,7 @@ func objectTypeSingularToPluralMap() map[ObjectType]PluralObjectType { ObjectTypeNetworkPolicy: PluralObjectTypeNetworkPolicies, ObjectTypePasswordPolicy: PluralObjectTypePasswordPolicies, ObjectTypeSessionPolicy: PluralObjectTypeSessionPolicies, + ObjectTypePrivacyPolicy: PluralObjectTypePrivacyPolicies, ObjectTypeReplicationGroup: PluralObjectTypeReplicationGroups, ObjectTypeFailoverGroup: PluralObjectTypeFailoverGroups, ObjectTypeConnection: PluralObjectTypeConnections, @@ -129,6 +133,8 @@ func objectTypeSingularToPluralMap() map[ObjectType]PluralObjectType { ObjectTypeFileFormat: PluralObjectTypeFileFormats, ObjectTypePipe: PluralObjectTypePipes, ObjectTypeAlert: PluralObjectTypeAlerts, + ObjectTypeBudget: PluralObjectTypeBudgets, + ObjectTypeClassification: PluralObjectTypeClassifications, ObjectTypeApplication: PluralObjectTypeApplications, ObjectTypeApplicationPackage: PluralObjectTypeApplicationPackages, ObjectTypeApplicationRole: PluralObjectTypeApplicationRoles, @@ -206,6 +212,7 @@ const ( PluralObjectTypeNetworkPolicies PluralObjectType = "NETWORK POLICIES" PluralObjectTypePasswordPolicies PluralObjectType = "PASSWORD POLICIES" PluralObjectTypeSessionPolicies PluralObjectType = "SESSION POLICIES" + PluralObjectTypePrivacyPolicies PluralObjectType = "PRIVACY POLICIES" PluralObjectTypeReplicationGroups PluralObjectType = "REPLICATION GROUPS" PluralObjectTypeFailoverGroups PluralObjectType = "FAILOVER GROUPS" PluralObjectTypeConnections PluralObjectType = "CONNECTIONS" @@ -237,6 +244,8 @@ const ( PluralObjectTypeFileFormats PluralObjectType = "FILE FORMATS" PluralObjectTypePipes PluralObjectType = "PIPES" PluralObjectTypeAlerts PluralObjectType = "ALERTS" + PluralObjectTypeBudgets PluralObjectType = "SNOWFLAKE.CORE.BUDGET" + PluralObjectTypeClassifications PluralObjectType = "SNOWFLAKE.ML.CLASSIFICATION" PluralObjectTypeApplications PluralObjectType = "APPLICATIONS" PluralObjectTypeApplicationPackages PluralObjectType = "APPLICATION PACKAGES" PluralObjectTypeApplicationRoles PluralObjectType = "APPLICATION ROLES" diff --git a/pkg/sdk/system_functions.go b/pkg/sdk/system_functions.go index 0ff406ab1a..4e179ee9ba 100644 --- a/pkg/sdk/system_functions.go +++ b/pkg/sdk/system_functions.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "slices" "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" @@ -26,6 +27,8 @@ type systemFunctions struct { } func (c *systemFunctions) GetTag(ctx context.Context, tagID ObjectIdentifier, objectID ObjectIdentifier, objectType ObjectType) (string, error) { + objectType = normalizeGetTagObjectType(objectType) + s := &struct { Tag string `db:"TAG"` }{} @@ -37,6 +40,20 @@ func (c *systemFunctions) GetTag(ctx context.Context, tagID ObjectIdentifier, ob return s.Tag, nil } +// normalize object types for some values because of errors like below +// SQL compilation error: Invalid value VIEW for argument OBJECT_TYPE. Please use object type TABLE for all kinds of table-like objects. +// TODO [SNOW-1022645]: discuss how we handle situation like this in the SDK +func normalizeGetTagObjectType(objectType ObjectType) ObjectType { + if slices.Contains([]ObjectType{ObjectTypeView, ObjectTypeMaterializedView, ObjectTypeExternalTable}, objectType) { + return ObjectTypeTable + } + + if slices.Contains([]ObjectType{ObjectTypeExternalFunction}, objectType) { + return ObjectTypeFunction + } + return objectType +} + type PipeExecutionState string const ( diff --git a/pkg/sdk/tag_association_validations.go b/pkg/sdk/tag_association_validations.go index f4a2d1e33f..b7f5c03b03 100644 --- a/pkg/sdk/tag_association_validations.go +++ b/pkg/sdk/tag_association_validations.go @@ -3,21 +3,33 @@ package sdk var ( // based on https://docs.snowflake.com/en/user-guide/object-tagging.html#supported-objects TagAssociationAllowedObjectTypes = []ObjectType{ + // organization level ObjectTypeAccount, + + // account level ObjectTypeApplication, ObjectTypeApplicationPackage, ObjectTypeDatabase, + ObjectTypeFailoverGroup, ObjectTypeIntegration, ObjectTypeNetworkPolicy, + ObjectTypeReplicationGroup, ObjectTypeRole, ObjectTypeShare, ObjectTypeUser, ObjectTypeWarehouse, + + // database level ObjectTypeDatabaseRole, ObjectTypeSchema, + + // schema level ObjectTypeAlert, + ObjectTypeBudget, + ObjectTypeClassification, ObjectTypeExternalFunction, ObjectTypeExternalTable, + ObjectTypeFunction, ObjectTypeGitRepository, ObjectTypeIcebergTable, ObjectTypeMaterializedView, @@ -26,12 +38,15 @@ var ( ObjectTypePasswordPolicy, ObjectTypeRowAccessPolicy, ObjectTypeSessionPolicy, + ObjectTypePrivacyPolicy, ObjectTypeProcedure, ObjectTypeStage, ObjectTypeStream, ObjectTypeTable, ObjectTypeTask, ObjectTypeView, + + // table or column level ObjectTypeColumn, ObjectTypeEventTable, } diff --git a/pkg/sdk/tags.go b/pkg/sdk/tags.go index 78f90e755a..8e0c87fc29 100644 --- a/pkg/sdk/tags.go +++ b/pkg/sdk/tags.go @@ -1,10 +1,8 @@ -//lint:file-ignore U1000 Ignore all unused code, it's generated package sdk import ( "context" "database/sql" - "strings" "time" ) @@ -56,10 +54,10 @@ type AllowedValue struct { // showTagOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-tags type showTagOptions struct { - show bool `ddl:"static" sql:"SHOW"` - tag bool `ddl:"static" sql:"TAGS"` - Like *Like `ddl:"keyword" sql:"LIKE"` - In *In `ddl:"keyword" sql:"IN"` + show bool `ddl:"static" sql:"SHOW"` + tag bool `ddl:"static" sql:"TAGS"` + Like *Like `ddl:"keyword" sql:"LIKE"` + In *ExtendedIn `ddl:"keyword" sql:"IN"` } type Tag struct { @@ -99,15 +97,7 @@ func (tr tagRow) convert() *Tag { OwnerRoleType: tr.OwnerRoleType, } if tr.AllowedValues.Valid { - // remove brackets - if s := strings.Trim(tr.AllowedValues.String, "[]"); s != "" { - items := strings.Split(s, ",") - values := make([]string, len(items)) - for i, item := range items { - values[i] = strings.Trim(item, `"`) // remove quotes - } - t.AllowedValues = values - } + t.AllowedValues = ParseCommaSeparatedStringArray(tr.AllowedValues.String, true) } return t } @@ -150,9 +140,10 @@ type TagRename struct { // alterTagOptions is based on https://docs.snowflake.com/en/sql-reference/sql/alter-tag type alterTagOptions struct { - alter bool `ddl:"static" sql:"ALTER"` - tag string `ddl:"static" sql:"TAG"` - name SchemaObjectIdentifier `ddl:"identifier"` + alter bool `ddl:"static" sql:"ALTER"` + tag string `ddl:"static" sql:"TAG"` + ifExists *bool `ddl:"keyword" sql:"IF EXISTS"` + name SchemaObjectIdentifier `ddl:"identifier"` // One of Add *TagAdd `ddl:"keyword" sql:"ADD"` diff --git a/pkg/sdk/tags_dto.go b/pkg/sdk/tags_dto.go index 25b3069915..f79a6589a2 100644 --- a/pkg/sdk/tags_dto.go +++ b/pkg/sdk/tags_dto.go @@ -26,8 +26,8 @@ type UnsetTagRequest struct { } type CreateTagRequest struct { - orReplace bool - ifNotExists bool + orReplace *bool + ifNotExists *bool name SchemaObjectIdentifier // required @@ -36,8 +36,13 @@ type CreateTagRequest struct { allowedValues *AllowedValues } +func (r *CreateTagRequest) GetName() SchemaObjectIdentifier { + return r.name +} + type AlterTagRequest struct { - name SchemaObjectIdentifier // required + ifExists *bool + name SchemaObjectIdentifier // required // One of add *TagAdd @@ -61,7 +66,7 @@ type TagUnsetRequest struct { type ShowTagRequest struct { like *Like - in *In + in *ExtendedIn } type DropTagRequest struct { diff --git a/pkg/sdk/tags_dto_builders.go b/pkg/sdk/tags_dto_builders.go index 4a123f9957..b9aaa38e7a 100644 --- a/pkg/sdk/tags_dto_builders.go +++ b/pkg/sdk/tags_dto_builders.go @@ -31,12 +31,12 @@ func NewCreateTagRequest(name SchemaObjectIdentifier) *CreateTagRequest { } func (s *CreateTagRequest) WithOrReplace(orReplace bool) *CreateTagRequest { - s.orReplace = orReplace + s.orReplace = &orReplace return s } func (s *CreateTagRequest) WithIfExists(ifExists bool) *CreateTagRequest { - s.ifNotExists = ifExists + s.ifNotExists = &ifExists return s } @@ -70,6 +70,11 @@ func NewAlterTagRequest(name SchemaObjectIdentifier) *AlterTagRequest { return &s } +func (s *AlterTagRequest) WithIfExists(ifExists bool) *AlterTagRequest { + s.ifExists = &ifExists + return s +} + func (s *AlterTagRequest) WithAdd(values []string) *AlterTagRequest { if len(values) > 0 { s.add = &TagAdd{createAllowedValues(values)} @@ -178,7 +183,7 @@ func (s *ShowTagRequest) WithLike(pattern string) *ShowTagRequest { return s } -func (s *ShowTagRequest) WithIn(in *In) *ShowTagRequest { +func (s *ShowTagRequest) WithIn(in *ExtendedIn) *ShowTagRequest { s.in = in return s } diff --git a/pkg/sdk/tags_impl.go b/pkg/sdk/tags_impl.go index e5b63af758..529dc851eb 100644 --- a/pkg/sdk/tags_impl.go +++ b/pkg/sdk/tags_impl.go @@ -33,8 +33,10 @@ func (v *tags) Show(ctx context.Context, request *ShowTagRequest) ([]Tag, error) } func (v *tags) ShowByID(ctx context.Context, id SchemaObjectIdentifier) (*Tag, error) { - request := NewShowTagRequest().WithIn(&In{ - Schema: id.SchemaId(), + request := NewShowTagRequest().WithIn(&ExtendedIn{ + In: In{ + Schema: id.SchemaId(), + }, }).WithLike(id.Name()) tags, err := v.Show(ctx, request) @@ -55,19 +57,21 @@ func (v *tags) Undrop(ctx context.Context, request *UndropTagRequest) error { } func (v *tags) Set(ctx context.Context, request *SetTagRequest) error { + // TODO [SNOW-1022645]: use query from resource sdk - similarly to https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/0e88e082282adf35f605c323569908a99bd406f9/pkg/acceptance/check_destroy.go#L67 opts := request.toOpts() return validateAndExec(v.client, ctx, opts) } func (v *tags) Unset(ctx context.Context, request *UnsetTagRequest) error { + // TODO [SNOW-1022645]: use query from resource sdk - similarly to https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/0e88e082282adf35f605c323569908a99bd406f9/pkg/acceptance/check_destroy.go#L67 opts := request.toOpts() return validateAndExec(v.client, ctx, opts) } func (s *CreateTagRequest) toOpts() *createTagOptions { return &createTagOptions{ - OrReplace: Bool(s.orReplace), - IfNotExists: Bool(s.ifNotExists), + OrReplace: s.orReplace, + IfNotExists: s.ifNotExists, name: s.name, Comment: s.comment, AllowedValues: s.allowedValues, @@ -76,12 +80,13 @@ func (s *CreateTagRequest) toOpts() *createTagOptions { func (s *AlterTagRequest) toOpts() *alterTagOptions { return &alterTagOptions{ - name: s.name, - Add: s.add, - Drop: s.drop, - Set: s.set, - Unset: s.unset, - Rename: s.rename, + name: s.name, + ifExists: s.ifExists, + Add: s.add, + Drop: s.drop, + Set: s.set, + Unset: s.unset, + Rename: s.rename, } } @@ -111,6 +116,7 @@ func (s *SetTagRequest) toOpts() *setTagOptions { objectName: s.objectName, SetTags: s.SetTags, } + // TODO [SNOW-1022645]: discuss how we handle situation like this in the SDK if o.objectType == ObjectTypeColumn { id, ok := o.objectName.(TableColumnIdentifier) if ok { @@ -128,6 +134,7 @@ func (s *UnsetTagRequest) toOpts() *unsetTagOptions { objectName: s.objectName, UnsetTags: s.UnsetTags, } + // TODO [SNOW-1022645]: discuss how we handle situation like this in the SDK if o.objectType == ObjectTypeColumn { id, ok := o.objectName.(TableColumnIdentifier) if ok { diff --git a/pkg/sdk/tags_test.go b/pkg/sdk/tags_test.go index a72d636dc7..5c3e01717a 100644 --- a/pkg/sdk/tags_test.go +++ b/pkg/sdk/tags_test.go @@ -15,7 +15,6 @@ func TestTagCreate(t *testing.T) { t.Run("create with all optional", func(t *testing.T) { opts := defaultOpts() opts.IfNotExists = Bool(true) - opts.OrReplace = Bool(false) opts.Comment = String("comment") opts.AllowedValues = &AllowedValues{ Values: []AllowedValue{ @@ -104,7 +103,7 @@ func TestTagUndrop(t *testing.T) { } } t.Run("validation: nil options", func(t *testing.T) { - var opts *dropTagOptions = nil + var opts *undropTagOptions = nil assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) }) @@ -138,7 +137,7 @@ func TestTagShow(t *testing.T) { t.Run("validation: empty in", func(t *testing.T) { opts := defaultOpts() - opts.In = &In{} + opts.In = &ExtendedIn{} assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("showTagOptions.In", "Account", "Database", "Schema")) }) @@ -155,8 +154,10 @@ func TestTagShow(t *testing.T) { t.Run("show with in", func(t *testing.T) { opts := defaultOpts() - opts.In = &In{ - Account: Bool(true), + opts.In = &ExtendedIn{ + In: In{ + Account: Bool(true), + }, } assertOptsValidAndSQLEquals(t, opts, `SHOW TAGS IN ACCOUNT`) }) @@ -194,10 +195,11 @@ func TestTagAlter(t *testing.T) { } } - t.Run("alter with rename to", func(t *testing.T) { + t.Run("alter with rename to and if exists", func(t *testing.T) { opts := defaultOpts() opts.Rename = &TagRename{Name: randomSchemaObjectIdentifierInSchema(id.SchemaId())} - assertOptsValidAndSQLEquals(t, opts, `ALTER TAG %s RENAME TO %s`, id.FullyQualifiedName(), opts.Rename.Name.FullyQualifiedName()) + opts.ifExists = Pointer(true) + assertOptsValidAndSQLEquals(t, opts, `ALTER TAG IF EXISTS %s RENAME TO %s`, id.FullyQualifiedName(), opts.Rename.Name.FullyQualifiedName()) }) t.Run("alter with add", func(t *testing.T) { @@ -252,7 +254,7 @@ func TestTagAlter(t *testing.T) { }) t.Run("validation: nil options", func(t *testing.T) { - opts := (*createTagOptions)(nil) + opts := (*alterTagOptions)(nil) assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) }) @@ -278,6 +280,31 @@ func TestTagAlter(t *testing.T) { assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("alterTagOptions", "Add", "Drop", "Set", "Unset", "Rename")) }) + t.Run("validation: multiple fields in set", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &TagSet{ + Comment: String("comment"), + MaskingPolicies: &TagSetMaskingPolicies{}, + } + assertOptsInvalidJoinedErrors(t, opts, errOneOf("TagSet", "MaskingPolicies", "Comment")) + }) + + t.Run("validation: empty masking policies in set", func(t *testing.T) { + opts := defaultOpts() + opts.Set = &TagSet{ + MaskingPolicies: &TagSetMaskingPolicies{}, + } + assertOptsInvalidJoinedErrors(t, opts, errIntValue("TagSet.MaskingPolicies", "MaskingPolicies", IntErrGreater, 0)) + }) + + t.Run("validation: empty masking policies in unset", func(t *testing.T) { + opts := defaultOpts() + opts.Unset = &TagUnset{ + MaskingPolicies: &TagUnsetMaskingPolicies{}, + } + assertOptsInvalidJoinedErrors(t, opts, errIntValue("TagUnset.MaskingPolicies", "MaskingPolicies", IntErrGreater, 0)) + }) + t.Run("validation: invalid new name", func(t *testing.T) { opts := defaultOpts() opts.Rename = &TagRename{ @@ -302,7 +329,7 @@ func TestTagAlter(t *testing.T) { assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("TagUnset", "MaskingPolicies", "AllowedValues", "Comment")) }) - t.Run("validation: allowed values count", func(t *testing.T) { + t.Run("validation: add allowed values count", func(t *testing.T) { opts := defaultOpts() opts.Add = &TagAdd{ AllowedValues: &AllowedValues{ @@ -311,6 +338,16 @@ func TestTagAlter(t *testing.T) { } assertOptsInvalidJoinedErrors(t, opts, errIntBetween("AllowedValues", "Values", 1, 300)) }) + + t.Run("validation: drop allowed values count", func(t *testing.T) { + opts := defaultOpts() + opts.Drop = &TagDrop{ + AllowedValues: &AllowedValues{ + Values: []AllowedValue{}, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errIntBetween("AllowedValues", "Values", 1, 300)) + }) } func TestTagSet(t *testing.T) { diff --git a/pkg/sdk/tags_validations.go b/pkg/sdk/tags_validations.go index c5b136ae04..cd0b55cd0a 100644 --- a/pkg/sdk/tags_validations.go +++ b/pkg/sdk/tags_validations.go @@ -23,7 +23,7 @@ func (opts *createTagOptions) validate() error { if !ValidObjectIdentifier(opts.name) { errs = append(errs, ErrInvalidObjectIdentifier) } - if everyValueSet(opts.OrReplace, opts.IfNotExists) && *opts.OrReplace && *opts.IfNotExists { + if everyValueSet(opts.OrReplace, opts.IfNotExists) { errs = append(errs, errOneOf("createTagOptions", "OrReplace", "IfNotExists")) } if valueSet(opts.AllowedValues) { diff --git a/pkg/sdk/testint/application_packages_integration_test.go b/pkg/sdk/testint/application_packages_integration_test.go index ec4a005304..196d299974 100644 --- a/pkg/sdk/testint/application_packages_integration_test.go +++ b/pkg/sdk/testint/application_packages_integration_test.go @@ -159,6 +159,10 @@ func TestInt_ApplicationPackages(t *testing.T) { require.NoError(t, err) assertApplicationPackage(t, id) + value, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), id, sdk.ObjectTypeApplicationPackage) + require.NoError(t, err) + assert.Equal(t, "v1", value) + unsetTags := []sdk.ObjectIdentifier{ tagTest.ID(), } diff --git a/pkg/sdk/testint/applications_integration_test.go b/pkg/sdk/testint/applications_integration_test.go index 8a1b68283b..2ee81c8da6 100644 --- a/pkg/sdk/testint/applications_integration_test.go +++ b/pkg/sdk/testint/applications_integration_test.go @@ -251,10 +251,9 @@ func TestInt_Applications(t *testing.T) { require.NoError(t, err) assertApplication(t, id, applicationPackage.Name, version, patch, "") - // TODO: 391801 (0A000): SQL compilation error: Object tagging not supported for object type APPLICATION. - // tv, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), id, sdk.ObjectTypeApplication) - // require.NoError(t, err) - // assert.Equal(t, "v1", tv) + // TODO(SNOW-1746420): adjust after this is fixed on Snowflake side + _, err = client.SystemFunctions.GetTag(ctx, tagTest.ID(), id, sdk.ObjectTypeApplication) + require.ErrorContains(t, err, "391801 (0A000): SQL compilation error: Object tagging not supported for object type APPLICATION") unsetTags := []sdk.ObjectIdentifier{ tagTest.ID(), diff --git a/pkg/sdk/testint/databases_integration_test.go b/pkg/sdk/testint/databases_integration_test.go index 38b7d6c229..d6b45916b7 100644 --- a/pkg/sdk/testint/databases_integration_test.go +++ b/pkg/sdk/testint/databases_integration_test.go @@ -340,6 +340,9 @@ func TestInt_DatabasesAlter(t *testing.T) { secondaryClient := testSecondaryClient(t) ctx := testContext(t) + tagTest, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) + assertDatabaseParameterEquals := func(t *testing.T, params []*sdk.Parameter, parameterName sdk.AccountParameter, expected string) { t.Helper() assert.Equal(t, expected, helpers.FindParameter(t, params, parameterName).Value) @@ -582,6 +585,38 @@ func TestInt_DatabasesAlter(t *testing.T) { assert.Equal(t, "", database.Comment) }) + t.Run(fmt.Sprintf("Database: %s - setting and unsetting tags", testCase.DatabaseType), func(t *testing.T) { + if testCase.DatabaseType == "Replica" { + t.Skipf("Skipping database test because secondary databases cannot be modified") + } + databaseTest, databaseTestCleanup := testCase.CreateFn(t) + t.Cleanup(databaseTestCleanup) + + err := client.Databases.Alter(ctx, databaseTest.ID(), &sdk.AlterDatabaseOptions{ + SetTag: []sdk.TagAssociation{ + { + Name: tagTest.ID(), + Value: "v1", + }, + }, + }) + require.NoError(t, err) + + value, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), databaseTest.ID(), sdk.ObjectTypeDatabase) + require.NoError(t, err) + assert.Equal(t, "v1", value) + + err = client.Databases.Alter(ctx, databaseTest.ID(), &sdk.AlterDatabaseOptions{ + UnsetTag: []sdk.ObjectIdentifier{ + tagTest.ID(), + }, + }) + require.NoError(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tagTest.ID(), databaseTest.ID(), sdk.ObjectTypeDatabase) + require.Error(t, err) + }) + t.Run(fmt.Sprintf("Database: %s - swap with another database", testCase.DatabaseType), func(t *testing.T) { databaseTest, databaseTestCleanup := testCase.CreateFn(t) t.Cleanup(databaseTestCleanup) diff --git a/pkg/sdk/testint/event_tables_integration_test.go b/pkg/sdk/testint/event_tables_integration_test.go index f8771a2e6f..806095fe4b 100644 --- a/pkg/sdk/testint/event_tables_integration_test.go +++ b/pkg/sdk/testint/event_tables_integration_test.go @@ -160,6 +160,10 @@ func TestInt_EventTables(t *testing.T) { err := client.EventTables.Alter(ctx, sdk.NewAlterEventTableRequest(id).WithSetTags(set)) require.NoError(t, err) + value, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), id, sdk.ObjectTypeEventTable) + require.NoError(t, err) + assert.Equal(t, "v1", value) + unset := []sdk.ObjectIdentifier{tagTest.ID()} err = client.EventTables.Alter(ctx, sdk.NewAlterEventTableRequest(id).WithUnsetTags(unset)) require.NoError(t, err) diff --git a/pkg/sdk/testint/external_functions_integration_test.go b/pkg/sdk/testint/external_functions_integration_test.go index 3d23fd6d6d..a9858c5742 100644 --- a/pkg/sdk/testint/external_functions_integration_test.go +++ b/pkg/sdk/testint/external_functions_integration_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -194,6 +195,33 @@ func TestInt_ExternalFunctions(t *testing.T) { assertExternalFunction(t, externalFunction.ID(), true) }) + t.Run("alter external function: set and unset tags", func(t *testing.T) { + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) + + externalFunction := createExternalFunction(t) + + id := externalFunction.ID() + setTags := []sdk.TagAssociation{ + { + Name: tag.ID(), + Value: "v1", + }, + } + err := client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithSetTags(setTags)) + require.NoError(t, err) + + value, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeExternalFunction) + require.NoError(t, err) + assert.Equal(t, "v1", value) + + unsetTags := []sdk.ObjectIdentifier{ + tag.ID(), + } + err = client.Functions.Alter(ctx, sdk.NewAlterFunctionRequest(id).WithUnsetTags(unsetTags)) + require.NoError(t, err) + }) + t.Run("show external function: with like", func(t *testing.T) { e1 := createExternalFunction(t) e2 := createExternalFunction(t) diff --git a/pkg/sdk/testint/external_tables_integration_test.go b/pkg/sdk/testint/external_tables_integration_test.go index 802ee41980..83832274d9 100644 --- a/pkg/sdk/testint/external_tables_integration_test.go +++ b/pkg/sdk/testint/external_tables_integration_test.go @@ -239,50 +239,49 @@ func TestInt_ExternalTables(t *testing.T) { require.NoError(t, err) }) - // TODO: (SNOW-919981) Uncomment when the problem with alter external table set / unset tags is solved - // t.Run("Alter: set tags", func(t *testing.T) { - // externalTableID := randomAccountObjectIdentifier(t) - // err := client.ExternalTables.Create(ctx, minimalCreateExternalTableReq(externalTableID)) - // require.NoError(t, err) - // - // tagValue := "tag-value" - // err = client.ExternalTables.Alter( - // ctx, - // NewAlterExternalTableRequest(externalTableID). - // WithIfExists(Bool(true)). - // WithSetTag([]*TagAssociationRequest{NewTagAssociationRequest(tag.ID(), tagValue)})) - // require.NoError(t, err) - // - // tv, err := client.SystemFunctions.GetTag(ctx, tag.ID(), externalTableID, ObjectTypeExternalTable) - // require.NoError(t, err) - // assert.Equal(t, tagValue, tv) - // }) - // - // t.Run("Alter: unset tags", func(t *testing.T) { - // externalTableID := randomAccountObjectIdentifier(t) - // err := client.ExternalTables.Create( - // ctx, - // minimalCreateExternalTableReq(externalTableID). - // WithTag([]*TagAssociationRequest{NewTagAssociationRequest(tag.ID(), "tag-value")}), - // ) - // require.NoError(t, err) - // tv, err := client.SystemFunctions.GetTag(ctx, tag.ID(), externalTableID, ObjectTypeExternalTable) - // require.NoError(t, err) - // assert.Equal(t, "tag-value", tv) - // - // err = client.ExternalTables.Alter( - // ctx, - // NewAlterExternalTableRequest(externalTableID). - // WithIfExists(Bool(true)). - // WithUnsetTag([]ObjectIdentifier{ - // NewAccountObjectIdentifier(tag.ID().Name()), - // }), - // ) - // require.NoError(t, err) - // - // _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), externalTableID, ObjectTypeExternalTable) - // require.Error(t, err) - // }) + t.Run("Alter: set tags", func(t *testing.T) { + externalTableID := testClientHelper().Ids.RandomSchemaObjectIdentifier() + err := client.ExternalTables.Create(ctx, minimalCreateExternalTableReq(externalTableID)) + require.NoError(t, err) + + tagValue := "tag-value" + err = client.Tables.Alter( + ctx, + sdk.NewAlterTableRequest(externalTableID). + WithIfExists(sdk.Bool(true)). + WithSetTags([]sdk.TagAssociationRequest{*sdk.NewTagAssociationRequest(tag.ID(), tagValue)})) + require.NoError(t, err) + + tv, err := client.SystemFunctions.GetTag(ctx, tag.ID(), externalTableID, sdk.ObjectTypeExternalTable) + require.NoError(t, err) + assert.Equal(t, tagValue, tv) + }) + + t.Run("Alter: unset tags", func(t *testing.T) { + externalTableID := testClientHelper().Ids.RandomSchemaObjectIdentifier() + err := client.ExternalTables.Create( + ctx, + minimalCreateExternalTableReq(externalTableID). + WithTag([]*sdk.TagAssociationRequest{sdk.NewTagAssociationRequest(tag.ID(), "tag-value")}), + ) + require.NoError(t, err) + tv, err := client.SystemFunctions.GetTag(ctx, tag.ID(), externalTableID, sdk.ObjectTypeExternalTable) + require.NoError(t, err) + assert.Equal(t, "tag-value", tv) + + err = client.Tables.Alter( + ctx, + sdk.NewAlterTableRequest(externalTableID). + WithIfExists(sdk.Bool(true)). + WithUnsetTags([]sdk.ObjectIdentifier{ + tag.ID(), + }), + ) + require.NoError(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), externalTableID, sdk.ObjectTypeExternalTable) + require.Error(t, err) + }) t.Run("Alter: add partitions", func(t *testing.T) { externalTableID := testClientHelper().Ids.RandomSchemaObjectIdentifier() diff --git a/pkg/sdk/testint/functions_integration_test.go b/pkg/sdk/testint/functions_integration_test.go index cef7e7cf21..31e96153ca 100644 --- a/pkg/sdk/testint/functions_integration_test.go +++ b/pkg/sdk/testint/functions_integration_test.go @@ -376,6 +376,10 @@ func TestInt_OtherFunctions(t *testing.T) { require.NoError(t, err) assertFunction(t, id, false, true) + value, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), id, sdk.ObjectTypeFunction) + require.NoError(t, err) + assert.Equal(t, "v1", value) + unsetTags := []sdk.ObjectIdentifier{ tagTest.ID(), } diff --git a/pkg/sdk/testint/materialized_views_gen_integration_test.go b/pkg/sdk/testint/materialized_views_gen_integration_test.go index f0759ba400..08c625ad3a 100644 --- a/pkg/sdk/testint/materialized_views_gen_integration_test.go +++ b/pkg/sdk/testint/materialized_views_gen_integration_test.go @@ -326,7 +326,7 @@ func TestInt_MaterializedViews(t *testing.T) { err := client.Views.Alter(ctx, alterRequestSetTags) require.NoError(t, err) - returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeTable) + returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeMaterializedView) require.NoError(t, err) assert.Equal(t, tagValue, returnedTagValue) @@ -339,7 +339,7 @@ func TestInt_MaterializedViews(t *testing.T) { err = client.Views.Alter(ctx, alterRequestUnsetTags) require.NoError(t, err) - _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeTable) + _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeMaterializedView) require.Error(t, err) }) diff --git a/pkg/sdk/testint/procedures_integration_test.go b/pkg/sdk/testint/procedures_integration_test.go index ad5bab732e..687d80a2db 100644 --- a/pkg/sdk/testint/procedures_integration_test.go +++ b/pkg/sdk/testint/procedures_integration_test.go @@ -485,6 +485,10 @@ func TestInt_OtherProcedureFunctions(t *testing.T) { require.NoError(t, err) assertProcedure(t, id, true) + value, err := client.SystemFunctions.GetTag(ctx, tagTest.ID(), id, sdk.ObjectTypeProcedure) + require.NoError(t, err) + assert.Equal(t, "v1", value) + unsetTags := []sdk.ObjectIdentifier{ tagTest.ID(), } diff --git a/pkg/sdk/testint/roles_integration_test.go b/pkg/sdk/testint/roles_integration_test.go index c0e10e4933..a27422745f 100644 --- a/pkg/sdk/testint/roles_integration_test.go +++ b/pkg/sdk/testint/roles_integration_test.go @@ -119,6 +119,9 @@ func TestInt_Roles(t *testing.T) { addedTag, err := client.SystemFunctions.GetTag(ctx, tag.ID(), role.ID(), sdk.ObjectTypeRole) require.NoError(t, err) assert.Equal(t, tagValue, addedTag) + + err = client.Roles.Alter(ctx, sdk.NewAlterRoleRequest(role.ID()).WithUnsetTags([]sdk.ObjectIdentifier{tag.ID()})) + require.NoError(t, err) }) t.Run("alter unset tags", func(t *testing.T) { diff --git a/pkg/sdk/testint/schemas_integration_test.go b/pkg/sdk/testint/schemas_integration_test.go index 0031f8a353..3b59001862 100644 --- a/pkg/sdk/testint/schemas_integration_test.go +++ b/pkg/sdk/testint/schemas_integration_test.go @@ -446,22 +446,14 @@ func TestInt_Schemas(t *testing.T) { }) t.Run("alter: set tags", func(t *testing.T) { - schemaID := testClientHelper().Ids.RandomDatabaseObjectIdentifier() - err := client.Schemas.Create(ctx, schemaID, nil) - require.NoError(t, err) - t.Cleanup(func() { - err := client.Schemas.Drop(ctx, schemaID, nil) - require.NoError(t, err) - }) - - s, err := client.Schemas.ShowByID(ctx, schemaID) - require.NoError(t, err) + schema, cleanupSchema := testClientHelper().Schema.CreateSchema(t) + t.Cleanup(cleanupSchema) - tag, cleanupTag := testClientHelper().Tag.CreateTagInSchema(t, s.ID()) + tag, cleanupTag := testClientHelper().Tag.CreateTagInSchema(t, schema.ID()) t.Cleanup(cleanupTag) tagValue := "tag-value" - err = client.Schemas.Alter(ctx, schemaID, &sdk.AlterSchemaOptions{ + err := client.Schemas.Alter(ctx, schema.ID(), &sdk.AlterSchemaOptions{ SetTag: []sdk.TagAssociation{ { Name: tag.ID(), @@ -471,7 +463,7 @@ func TestInt_Schemas(t *testing.T) { }) require.NoError(t, err) - tv, err := client.SystemFunctions.GetTag(ctx, tag.ID(), s.ID(), sdk.ObjectTypeSchema) + tv, err := client.SystemFunctions.GetTag(ctx, tag.ID(), schema.ID(), sdk.ObjectTypeSchema) require.NoError(t, err) assert.Equal(t, tagValue, tv) }) diff --git a/pkg/sdk/testint/tables_integration_test.go b/pkg/sdk/testint/tables_integration_test.go index 9071c4fcc4..7ea659be23 100644 --- a/pkg/sdk/testint/tables_integration_test.go +++ b/pkg/sdk/testint/tables_integration_test.go @@ -592,6 +592,62 @@ func TestInt_Table(t *testing.T) { require.Error(t, err) }) + t.Run("alter table: set and unset tags on columns", func(t *testing.T) { + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + columns := []sdk.TableColumnRequest{ + *sdk.NewTableColumnRequest("COLUMN_1", sdk.DataTypeVARCHAR), + *sdk.NewTableColumnRequest("COLUMN_2", sdk.DataTypeVARCHAR), + } + + err := client.Tables.Create(ctx, sdk.NewCreateTableRequest(id, columns)) + require.NoError(t, err) + t.Cleanup(cleanupTableProvider(id)) + + columnTags := []sdk.TagAssociation{ + { + Name: tag1.ID(), + Value: "v1", + }, + { + Name: tag2.ID(), + Value: "v2", + }, + } + + alterRequestSetTags := sdk.NewAlterTableRequest(id).WithColumnAction(sdk.NewTableColumnActionRequest(). + WithSetTags(sdk.NewTableColumnAlterSetTagsActionRequest("COLUMN_1", columnTags))) + err = client.Tables.Alter(ctx, alterRequestSetTags) + require.NoError(t, err) + + columnId := sdk.NewTableColumnIdentifier(id.DatabaseName(), id.SchemaName(), id.Name(), "COLUMN_1") + + returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag1.ID(), columnId, sdk.ObjectTypeColumn) + require.NoError(t, err) + + assert.Equal(t, "v1", returnedTagValue) + + returnedTagValue, err = client.SystemFunctions.GetTag(ctx, tag2.ID(), columnId, sdk.ObjectTypeColumn) + require.NoError(t, err) + + assert.Equal(t, "v2", returnedTagValue) + + unsetTags := []sdk.ObjectIdentifier{ + tag1.ID(), + tag2.ID(), + } + alterRequestUnsetTags := sdk.NewAlterTableRequest(id).WithColumnAction(sdk.NewTableColumnActionRequest(). + WithUnsetTags(sdk.NewTableColumnAlterUnsetTagsActionRequest("COLUMN_1", unsetTags))) + + err = client.Tables.Alter(ctx, alterRequestUnsetTags) + require.NoError(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tag1.ID(), id, sdk.ObjectTypeColumn) + require.Error(t, err) + + _, err = client.SystemFunctions.GetTag(ctx, tag2.ID(), id, sdk.ObjectTypeColumn) + require.Error(t, err) + }) + t.Run("alter table: drop columns", func(t *testing.T) { id := testClientHelper().Ids.RandomSchemaObjectIdentifier() columns := []sdk.TableColumnRequest{ diff --git a/pkg/sdk/testint/tags_integration_test.go b/pkg/sdk/testint/tags_integration_test.go index dd8af002c3..d1fc3ba772 100644 --- a/pkg/sdk/testint/tags_integration_test.go +++ b/pkg/sdk/testint/tags_integration_test.go @@ -2,11 +2,13 @@ package testint import ( "context" - "errors" "testing" + assertions "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert/objectassert" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeroles" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -16,70 +18,45 @@ func TestInt_Tags(t *testing.T) { client := testClient(t) ctx := context.Background() - schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) - t.Cleanup(schemaCleanup) - - assertTagHandle := func(t *testing.T, tag *sdk.Tag, expectedName string, expectedComment string, expectedAllowedValues []string) { + assertTagHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier, expectedComment string, expectedAllowedValues []string) { t.Helper() - assert.NotEmpty(t, tag.CreatedOn) - assert.Equal(t, expectedName, tag.Name) - assert.Equal(t, "ACCOUNTADMIN", tag.Owner) - assert.Equal(t, expectedComment, tag.Comment) - assert.Equal(t, expectedAllowedValues, tag.AllowedValues) - assert.Equal(t, "ROLE", tag.OwnerRoleType) - } - cleanupTagHandle := func(id sdk.SchemaObjectIdentifier) func() { - return func() { - err := client.Tags.Drop(ctx, sdk.NewDropTagRequest(id)) - if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { - return - } - require.NoError(t, err) - } - } - createTagHandle := func(t *testing.T) *sdk.Tag { - t.Helper() - - id := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) - err := client.Tags.Create(ctx, sdk.NewCreateTagRequest(id)) - require.NoError(t, err) - t.Cleanup(cleanupTagHandle(id)) - - tag, err := client.Tags.ShowByID(ctx, id) - require.NoError(t, err) - return tag + assertions.AssertThatObject(t, objectassert.Tag(t, id). + HasName(id.Name()). + HasDatabaseName(id.DatabaseName()). + HasSchemaName(id.SchemaName()). + HasOwner(snowflakeroles.Accountadmin.Name()). + HasComment(expectedComment). + HasAllowedValues(expectedAllowedValues...). + HasOwnerRoleType("ROLE"), + ) } t.Run("create tag: comment", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() comment := random.Comment() request := sdk.NewCreateTagRequest(id).WithComment(&comment) err := client.Tags.Create(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupTagHandle(id)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, id)) - tag, err := client.Tags.ShowByID(ctx, id) - require.NoError(t, err) - assertTagHandle(t, tag, id.Name(), comment, nil) + assertTagHandle(t, id, comment, nil) }) t.Run("create tag: allowed values", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() values := []string{"value1", "value2"} request := sdk.NewCreateTagRequest(id).WithAllowedValues(values) err := client.Tags.Create(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupTagHandle(id)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, id)) - tag, err := client.Tags.ShowByID(ctx, id) - require.NoError(t, err) - assertTagHandle(t, tag, id.Name(), "", values) + assertTagHandle(t, id, "", values) }) t.Run("create tag: comment and allowed values", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() comment := random.Comment() values := []string{"value1", "value2"} @@ -89,26 +66,24 @@ func TestInt_Tags(t *testing.T) { WithAllowedValues(values) err := client.Tags.Create(ctx, request) require.NoError(t, err) - t.Cleanup(cleanupTagHandle(id)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, id)) - tag, err := client.Tags.ShowByID(ctx, id) - require.NoError(t, err) - assertTagHandle(t, tag, id.Name(), comment, values) + assertTagHandle(t, id, comment, values) }) t.Run("create tag: no optionals", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() + err := client.Tags.Create(ctx, sdk.NewCreateTagRequest(id)) require.NoError(t, err) - t.Cleanup(cleanupTagHandle(id)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, id)) - tag, err := client.Tags.ShowByID(ctx, id) - require.NoError(t, err) - assertTagHandle(t, tag, id.Name(), "", nil) + assertTagHandle(t, id, "", nil) }) t.Run("drop tag: existing", func(t *testing.T) { - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() err := client.Tags.Drop(ctx, sdk.NewDropTagRequest(id)) require.NoError(t, err) @@ -118,14 +93,15 @@ func TestInt_Tags(t *testing.T) { }) t.Run("drop tag: non-existing", func(t *testing.T) { - id := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) + id := testClientHelper().Ids.RandomSchemaObjectIdentifier() err := client.Tags.Drop(ctx, sdk.NewDropTagRequest(id)) assert.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) }) t.Run("undrop tag: existing", func(t *testing.T) { - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() err := client.Tags.Drop(ctx, sdk.NewDropTagRequest(id)) require.NoError(t, err) @@ -139,13 +115,14 @@ func TestInt_Tags(t *testing.T) { }) t.Run("alter tag: set and unset comment", func(t *testing.T) { - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() // alter tag with set comment comment := random.Comment() set := sdk.NewTagSetRequest().WithComment(comment) - err := client.Tags.Alter(ctx, sdk.NewAlterTagRequest(id).WithSet(set)) + err := client.Tags.Alter(ctx, sdk.NewAlterTagRequest(id).WithIfExists(true).WithSet(set)) require.NoError(t, err) tag, err = client.Tags.ShowByID(ctx, id) @@ -166,7 +143,8 @@ func TestInt_Tags(t *testing.T) { policyTest, policyCleanup := testClientHelper().MaskingPolicy.CreateMaskingPolicy(t) t.Cleanup(policyCleanup) - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() policies := []sdk.SchemaObjectIdentifier{policyTest.ID()} @@ -180,7 +158,8 @@ func TestInt_Tags(t *testing.T) { }) t.Run("alter tag: add and drop allowed values", func(t *testing.T) { - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() values := []string{"value1", "value2"} @@ -200,30 +179,30 @@ func TestInt_Tags(t *testing.T) { }) t.Run("alter tag: rename", func(t *testing.T) { - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() - nid := testClientHelper().Ids.RandomSchemaObjectIdentifierInSchema(schema.ID()) + nid := testClientHelper().Ids.RandomSchemaObjectIdentifier() err := client.Tags.Alter(ctx, sdk.NewAlterTagRequest(id).WithRename(nid)) if err != nil { - t.Cleanup(cleanupTagHandle(id)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, id)) } else { - t.Cleanup(cleanupTagHandle(nid)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, nid)) } require.NoError(t, err) _, err = client.Tags.ShowByID(ctx, id) assert.ErrorIs(t, err, collections.ErrObjectNotFound) - tag, err = client.Tags.ShowByID(ctx, nid) - require.NoError(t, err) - assertTagHandle(t, tag, nid.Name(), "", nil) + assertTagHandle(t, nid, "", nil) }) t.Run("alter tag: unset allowed values", func(t *testing.T) { - tag := createTagHandle(t) + tag, tagCleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tagCleanup) id := tag.ID() - t.Cleanup(cleanupTagHandle(id)) + t.Cleanup(testClientHelper().Tag.DropTagFunc(t, id)) values := []string{"value1", "value2"} err := client.Tags.Alter(ctx, sdk.NewAlterTagRequest(id).WithAdd(values)) @@ -243,26 +222,30 @@ func TestInt_Tags(t *testing.T) { }) t.Run("show tag: without like", func(t *testing.T) { - t1 := createTagHandle(t) - t2 := createTagHandle(t) + tag1, tag1Cleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tag1Cleanup) + tag2, tag2Cleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tag2Cleanup) tags, err := client.Tags.Show(ctx, sdk.NewShowTagRequest()) require.NoError(t, err) assert.Equal(t, 2, len(tags)) - assert.Contains(t, tags, *t1) - assert.Contains(t, tags, *t2) + assert.Contains(t, tags, *tag1) + assert.Contains(t, tags, *tag2) }) t.Run("show tag: with like", func(t *testing.T) { - t1 := createTagHandle(t) - t2 := createTagHandle(t) + tag1, tag1Cleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tag1Cleanup) + tag2, tag2Cleanup := testClientHelper().Tag.CreateTag(t) + t.Cleanup(tag2Cleanup) - tags, err := client.Tags.Show(ctx, sdk.NewShowTagRequest().WithLike(t1.Name)) + tags, err := client.Tags.Show(ctx, sdk.NewShowTagRequest().WithLike(tag1.Name)) require.NoError(t, err) assert.Equal(t, 1, len(tags)) - assert.Contains(t, tags, *t1) - assert.NotContains(t, tags, *t2) + assert.Contains(t, tags, *tag1) + assert.NotContains(t, tags, *tag2) }) t.Run("show tag: no matches", func(t *testing.T) { @@ -276,23 +259,6 @@ func TestInt_TagsShowByID(t *testing.T) { client := testClient(t) ctx := testContext(t) - cleanupTagHandle := func(id sdk.SchemaObjectIdentifier) func() { - return func() { - err := client.Tags.Drop(ctx, sdk.NewDropTagRequest(id)) - if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { - return - } - require.NoError(t, err) - } - } - createTagHandle := func(t *testing.T, id sdk.SchemaObjectIdentifier) { - t.Helper() - - err := client.Tags.Create(ctx, sdk.NewCreateTagRequest(id)) - require.NoError(t, err) - t.Cleanup(cleanupTagHandle(id)) - } - t.Run("show by id - same name in different schemas", func(t *testing.T) { schema, schemaCleanup := testClientHelper().Schema.CreateSchema(t) t.Cleanup(schemaCleanup) @@ -300,8 +266,11 @@ func TestInt_TagsShowByID(t *testing.T) { id1 := testClientHelper().Ids.RandomSchemaObjectIdentifier() id2 := testClientHelper().Ids.NewSchemaObjectIdentifierInSchema(id1.Name(), schema.ID()) - createTagHandle(t, id1) - createTagHandle(t, id2) + _, tag1Cleanup := testClientHelper().Tag.CreateTagWithIdentifier(t, id1) + t.Cleanup(tag1Cleanup) + + _, tag2Cleanup := testClientHelper().Tag.CreateTagWithIdentifier(t, id2) + t.Cleanup(tag2Cleanup) e1, err := client.Tags.ShowByID(ctx, id1) require.NoError(t, err) diff --git a/pkg/sdk/testint/views_gen_integration_test.go b/pkg/sdk/testint/views_gen_integration_test.go index 45a5fdb790..e96b2666a6 100644 --- a/pkg/sdk/testint/views_gen_integration_test.go +++ b/pkg/sdk/testint/views_gen_integration_test.go @@ -372,9 +372,7 @@ func TestInt_Views(t *testing.T) { err := client.Views.Alter(ctx, alterRequestSetTags) require.NoError(t, err) - // setting object type to view results in: - // SQL compilation error: Invalid value VIEW for argument OBJECT_TYPE. Please use object type TABLE for all kinds of table-like objects. - returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeTable) + returnedTagValue, err := client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeView) require.NoError(t, err) assert.Equal(t, tagValue, returnedTagValue) @@ -387,7 +385,7 @@ func TestInt_Views(t *testing.T) { err = client.Views.Alter(ctx, alterRequestUnsetTags) require.NoError(t, err) - _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeTable) + _, err = client.SystemFunctions.GetTag(ctx, tag.ID(), id, sdk.ObjectTypeView) require.Error(t, err) }) diff --git a/templates/resources/masking_policy.md.tmpl b/templates/resources/masking_policy.md.tmpl new file mode 100644 index 0000000000..e09e58f03f --- /dev/null +++ b/templates/resources/masking_policy.md.tmpl @@ -0,0 +1,35 @@ +--- +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 }} +--- + +!> **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. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +-> **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). + + +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }}