Skip to content

Commit

Permalink
fix: tag association name convention (#1294)
Browse files Browse the repository at this point in the history
* fix tag association name convention

* resolve conflict

* resolve conflict

* resolve conflict

* update tag association

* add object-id block

* add object-id block

* add object-id block

* add object-id block

* add object-id block

* add object-id block

* add object-id block
  • Loading branch information
sfc-gh-swinkler authored Oct 21, 2022
1 parent de5dc85 commit 472f712
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 43 deletions.
51 changes: 46 additions & 5 deletions docs/resources/tag_association.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,39 @@ resource "snowflake_tag" "tag" {
allowed_values = ["finance", "engineering"]
}
resource "snowflake_tag_association" "association" {
object_name = snowflake_database.database.name
resource "snowflake_tag_association" "db_association" {
object_identifier {
name = snowflake_database.database.name
}
object_type = "DATABASE"
tag_id = snowflake_tag.tag.id
tag_value = "finance"
skip_validation = true
}
resource "snowflake_table" "test" {
database = snowflake_database.test.name
schema = snowflake_schema.test.name
name = "TABLE_NAME"
comment = "Terraform example table"
column {
name = "column1"
type = "VARIANT"
}
column {
name = "column2"
type = "VARCHAR(16)"
}
}
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"
}
```

Expand All @@ -43,20 +70,34 @@ resource "snowflake_tag_association" "association" {

### Required

- `object_name` (String) Specifies the object identifier for the tag association.
- `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 to. ex: 'ACCOUNT', 'COLUMN', 'DATABASE', etc. For more information: https://docs.snowflake.com/en/user-guide/object-tagging.html#supported-objects
- `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')

### Optional

- `skip_validation` (Boolean, Deprecated) If true, skips validation of the tag association.
- `object_name` (String, Deprecated) Specifies the object identifier for the tag association.
- `skip_validation` (Boolean) If true, skips validation of the tag association.
- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))

### Read-Only

- `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 Down
33 changes: 30 additions & 3 deletions examples/resources/snowflake_tag_association/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,37 @@ resource "snowflake_tag" "tag" {
allowed_values = ["finance", "engineering"]
}

resource "snowflake_tag_association" "association" {
object_name = snowflake_database.database.name
resource "snowflake_tag_association" "db_association" {
object_identifier {
name = snowflake_database.database.name
}
object_type = "DATABASE"
tag_id = snowflake_tag.tag.id
tag_value = "finance"
skip_validation = true
}

resource "snowflake_table" "test" {
database = snowflake_database.test.name
schema = snowflake_schema.test.name
name = "TABLE_NAME"
comment = "Terraform example table"
column {
name = "column1"
type = "VARIANT"
}
column {
name = "column2"
type = "VARCHAR(16)"
}
}

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"
}
78 changes: 61 additions & 17 deletions pkg/resources/tag_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,39 @@ import (
var tagAssociationSchema = map[string]*schema.Schema{
"object_name": {
Type: schema.TypeString,
Required: true,
Optional: true,
Description: "Specifies the object identifier for the tag association.",
Deprecated: "Use `object_identifier` instead",
ForceNew: true,
},
"object_identifier": &schema.Schema{
Type: schema.TypeList,
Required: true,
MinItems: 1,
Description: "Specifies the object identifier for the tag association.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Name of the object to associate the tag with.",
},
"database": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "Name of the database that the object was created in.",
},
"schema": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: "Name of the schema that the object was created in.",
},
},
},
},
"object_type": {
Type: schema.TypeString,
Required: true,
Expand Down Expand Up @@ -49,8 +78,7 @@ var tagAssociationSchema = map[string]*schema.Schema{
Type: schema.TypeBool,
Optional: true,
Description: "If true, skips validation of the tag association.",
Deprecated: "Tag associations are now always validated without latency using the SYSTEM$GET_TAG function.",
Default: false,
Default: true,
},
}

Expand All @@ -76,15 +104,16 @@ func TagAssociation() *schema.Resource {
func CreateTagAssociation(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)
tagID := d.Get("tag_id").(string)
objectName := d.Get("object_name").(string)
objectType := d.Get("object_type").(string)
tagValue := d.Get("tag_value").(string)
builder := snowflake.TagAssociation(tagID).WithObjectName(objectName).WithObjectType(objectType).WithTagValue(tagValue)
objectIdentifier := d.Get("object_identifier").([]interface{})[0].(map[string]interface{})
fullyQualifierObjectIdentifier := tagAssociationFullyQualifiedIdentifier(objectIdentifier)
builder := snowflake.TagAssociation(tagID).WithObjectIdentifier(fullyQualifierObjectIdentifier).WithObjectType(objectType).WithTagValue(tagValue)

q := builder.Create()
err := snowflake.Exec(db, q)
if err != nil {
return errors.Wrapf(err, "error associating tag to object: [%v] with command: [%v], tag_id [%v]", objectName, q, tagID)
return errors.Wrapf(err, "error associating tag to object: [%v] with command: [%v], tag_id [%v]", objectIdentifier, q, tagID)
}

_, err = snowflake.ListTagAssociations(builder, db)
Expand All @@ -111,10 +140,10 @@ func ReadTagAssociation(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)

tagID := d.Get("tag_id").(string)
objectName := d.Get("object_name").(string)
objectType := d.Get("object_type").(string)

q := snowflake.TagAssociation(tagID).WithObjectName(objectName).WithObjectType(objectType).Show()
objectIdentifier := d.Get("object_identifier").([]interface{})[0].(map[string]interface{})
fullyQualifierObjectIdentifier := tagAssociationFullyQualifiedIdentifier(objectIdentifier)
q := snowflake.TagAssociation(tagID).WithObjectIdentifier(fullyQualifierObjectIdentifier).WithObjectType(objectType).Show()
row := snowflake.QueryRow(db, q)

ta, err := snowflake.ScanTagAssociation(row)
Expand All @@ -141,10 +170,10 @@ func UpdateTagAssociation(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)

tagID := d.Get("tag_id").(string)
objectName := d.Get("object_name").(string)
objectType := d.Get("object_type").(string)

builder := snowflake.TagAssociation(tagID).WithObjectName(objectName).WithObjectType(objectType)
objectIdentifier := d.Get("object_identifier").([]interface{})[0].(map[string]interface{})
fullyQualifierObjectIdentifier := tagAssociationFullyQualifiedIdentifier(objectIdentifier)
builder := snowflake.TagAssociation(tagID).WithObjectIdentifier(fullyQualifierObjectIdentifier).WithObjectType(objectType)

if d.HasChange("skip_validation") {
old, new := d.GetChange("skip_validation")
Expand All @@ -161,7 +190,7 @@ func UpdateTagAssociation(d *schema.ResourceData, meta interface{}) error {
}
err := snowflake.Exec(db, q)
if err != nil {
return errors.Wrapf(err, "error updating tag association value for object [%v]", objectName)
return errors.Wrapf(err, "error updating tag association value for object [%v]", objectIdentifier)
}
}

Expand All @@ -173,18 +202,33 @@ func DeleteTagAssociation(d *schema.ResourceData, meta interface{}) error {
db := meta.(*sql.DB)

tagID := d.Get("tag_id").(string)
objectName := d.Get("object_name").(string)
objectType := d.Get("object_type").(string)

q := snowflake.TagAssociation(tagID).WithObjectName(objectName).WithObjectType(objectType).Drop()
objectIdentifier := d.Get("object_identifier").([]interface{})[0].(map[string]interface{})
fullyQualifierObjectIdentifier := tagAssociationFullyQualifiedIdentifier(objectIdentifier)
q := snowflake.TagAssociation(tagID).WithObjectIdentifier(fullyQualifierObjectIdentifier).WithObjectType(objectType).Drop()

err := snowflake.Exec(db, q)
if err != nil {
log.Printf("[DEBUG] error is %v", err.Error())
return errors.Wrapf(err, "error deleting tag association for object [%v]", objectName)
return errors.Wrapf(err, "error deleting tag association for object [%v]", objectIdentifier)
}

d.SetId("")

return nil
}

func tagAssociationFullyQualifiedIdentifier(objectIdentifier map[string]interface{}) string {
objectName := objectIdentifier["name"].(string)
objectSchema := ""
obs := objectIdentifier["schema"]
if obs != nil {
objectSchema = obs.(string)
}
objectDatabase := ""
obd := objectIdentifier["database"]
if obd != nil {
objectDatabase = obd.(string)
}
return snowflakeValidation.FormatFullyQualifiedObjectID(objectDatabase, objectSchema, objectName)
}
9 changes: 4 additions & 5 deletions pkg/resources/tag_association_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

func TestAcc_TagAssociation(t *testing.T) {
accName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
accName := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))

resource.ParallelTest(t, resource.TestCase{
Providers: providers(),
Expand All @@ -19,11 +19,9 @@ func TestAcc_TagAssociation(t *testing.T) {
{
Config: tagAssociationConfig(accName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_tag_association.test", "object_name", accName),
resource.TestCheckResourceAttr("snowflake_tag_association.test", "object_type", "DATABASE"),
resource.TestCheckResourceAttr("snowflake_tag_association.test", "tag_id", fmt.Sprintf("%s|%s|%s", accName, accName, accName)),
resource.TestCheckResourceAttr("snowflake_tag_association.test", "tag_value", "finance"),
resource.TestCheckResourceAttr("snowflake_tag_association.test", "skip_validation", "true"),
),
},
},
Expand Down Expand Up @@ -52,11 +50,12 @@ resource "snowflake_tag" "test" {
}
resource "snowflake_tag_association" "test" {
object_name = snowflake_database.test.name
object_identifier {
name = snowflake_database.test.name
}
object_type = "DATABASE"
tag_id = snowflake_tag.test.id
tag_value = "finance"
skip_validation = true
}
`, n)
}
26 changes: 13 additions & 13 deletions pkg/snowflake/tag_association.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,21 @@ import (

// TagAssociationBuilder abstracts the creation of SQL queries for a Snowflake tag.
type TagAssociationBuilder struct {
databaseName string
objectName string
objectType string
schemaName string
tagName string
tagValue string
databaseName string
objectIdentifier string
objectType string
schemaName string
tagName string
tagValue string
}

type tagAssociation struct {
TagValue sql.NullString `db:"TAG_VALUE"`
}

// WithObjectId adds the name of the schema to the TagAssociationBuilder.
func (tb *TagAssociationBuilder) WithObjectName(objectName string) *TagAssociationBuilder {
tb.objectName = objectName
// WithObjectIdentifier adds the name of the schema to the TagAssociationBuilder.
func (tb *TagAssociationBuilder) WithObjectIdentifier(objectIdentifier string) *TagAssociationBuilder {
tb.objectIdentifier = objectIdentifier
return tb
}

Expand Down Expand Up @@ -76,17 +76,17 @@ func TagAssociation(tagID string) *TagAssociationBuilder {

// Create returns the SQL query that will set the tag on an object.
func (tb *TagAssociationBuilder) Create() string {
return fmt.Sprintf(`ALTER %v "%v" SET TAG "%v"."%v"."%v" = '%v'`, tb.objectType, tb.objectName, tb.databaseName, tb.schemaName, tb.tagName, EscapeString(tb.tagValue))
return fmt.Sprintf(`ALTER %v %v SET TAG "%v"."%v"."%v" = '%v'`, tb.objectType, tb.objectIdentifier, tb.databaseName, tb.schemaName, tb.tagName, EscapeString(tb.tagValue))
}

// Drop returns the SQL query that will remove a tag from an object.
func (tb *TagAssociationBuilder) Drop() string {
return fmt.Sprintf(`ALTER %v "%v" UNSET TAG "%v"."%v"."%v"`, tb.objectType, tb.objectName, tb.databaseName, tb.schemaName, tb.tagName)
return fmt.Sprintf(`ALTER %v %v UNSET TAG "%v"."%v"."%v"`, tb.objectType, tb.objectIdentifier, tb.databaseName, tb.schemaName, tb.tagName)
}

// Show returns the SQL query that will show the current tag value on an object.
func (tb *TagAssociationBuilder) Show() string {
return fmt.Sprintf(`SELECT SYSTEM$GET_TAG('"%v"."%v"."%v"', '%v', '%v') TAG_VALUE WHERE TAG_VALUE IS NOT NULL`, tb.databaseName, tb.schemaName, tb.tagName, tb.objectName, tb.objectType)
return fmt.Sprintf(`SELECT SYSTEM$GET_TAG('"%v"."%v"."%v"', '%v', '%v') TAG_VALUE WHERE TAG_VALUE IS NOT NULL`, tb.databaseName, tb.schemaName, tb.tagName, tb.objectIdentifier, tb.objectType)
}

func ScanTagAssociation(row *sqlx.Row) (*tagAssociation, error) {
Expand All @@ -96,7 +96,7 @@ func ScanTagAssociation(row *sqlx.Row) (*tagAssociation, error) {
}

func ListTagAssociations(tb *TagAssociationBuilder, db *sql.DB) ([]tagAssociation, error) {
stmt := fmt.Sprintf(`SELECT SYSTEM$GET_TAG('"%v"."%v"."%v"', '%v', '%v') TAG_VALUE WHERE TAG_VALUE IS NOT NULL`, tb.databaseName, tb.schemaName, tb.tagName, tb.objectName, tb.objectType)
stmt := fmt.Sprintf(`SELECT SYSTEM$GET_TAG('"%v"."%v"."%v"', '%v', '%v') TAG_VALUE WHERE TAG_VALUE IS NOT NULL`, tb.databaseName, tb.schemaName, tb.tagName, tb.objectIdentifier, tb.objectType)
rows, err := db.Query(stmt)
if err != nil {
return nil, err
Expand Down
3 changes: 3 additions & 0 deletions pkg/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,8 @@ func ParseFullyQualifiedObjectID(s string) (dbName, schemaName, objectName strin
} else if strings.Contains(parsedString, ".") {
parts = strings.Split(parsedString, ".")
}
for len(parts) < 3 {
parts = append(parts, "")
}
return parts[0], parts[1], parts[2]
}

0 comments on commit 472f712

Please sign in to comment.