Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Commit

Permalink
Treat tags as an unordered set
Browse files Browse the repository at this point in the history
Breaking change.  The `tags` attribute will change from string-type to
set-of-string type.  Users will need to make the following syntactic
change to their Terraform configuration files:

    // before:
    resource "pingdom_check" "example" {
      // ...
      tags = "foo,bar"
    }

    // after:
    resource "pingdom_check" "example" {
      // ...
      tags = ["foo", "bar"]  // order no longer relevant
    }

This commit includes a schema migration for existing user state.

---

The Pingdom API returns tag elements in an unpredictable order.
Ordering for this type is not documented in the Pingdom API spec.
Current plugin behaviour often leads to spurious diffs against state.

https://www.pingdom.com/api/2.1/#MethodGet+Check+List

Notionally, tags should be considered to be sets in the Pingdom data
model.  It does not make much practical sense for us to enforce strict
ordering on them.  Any implied ordering is likely an inadvertent
side-effect of JSON's lack of an unordered set type.

This change will hash tag names using the FNV-1a alternate algorithm.
(Tag names are the only labels that users see in the Pingdom UI and API.
Pingdom do not expose tag IDs in their API.)

http://www.isthe.com/chongo/tech/comp/fnv/index.html#FNV-1a

As far as I know, this algorithm is a good fit for set element hash
computation:  it is said to be fast, said to provide reasonably good
dispersion, and is a part of the Go standard library.
  • Loading branch information
saj committed Nov 13, 2019
1 parent 1beece6 commit 98bf51b
Show file tree
Hide file tree
Showing 2 changed files with 214 additions and 6 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ For the HTTP checks, you can set these attributes:

* **requestheaders** - Custom HTTP headers. It should be a hash with pairs, like `{ "header_name" = "header_content" }`

* **tags** - List of tags the check should contain. Should be in the format "tagA,tagB"
* **tags** - Unordered set of Pingdom tags to assign to the check. Should be in the format `["tagA", "tagB"]`

* **probefilters** - Region from which the check should originate. One of NA, EU, APAC, or LATAM. Should be in the format "region:NA"

Expand Down
218 changes: 213 additions & 5 deletions pingdom/resource_pingdom_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package pingdom

import (
"fmt"
"hash/fnv"
"log"
"strconv"
"strings"
Expand Down Expand Up @@ -146,10 +147,12 @@ func resourcePingdomCheck() *schema.Resource {
Optional: true,
ForceNew: false,
},

"tags": {
Type: schema.TypeString,
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Elem: &schema.Schema{Type: schema.TypeString},
},

"probefilters": {
Expand Down Expand Up @@ -184,6 +187,15 @@ func resourcePingdomCheck() *schema.Resource {
ForceNew: false,
},
},

SchemaVersion: 1,
StateUpgraders: []schema.StateUpgrader{
{
Version: 0,
Type: resourcePingdomCheckResourceV0().CoreConfigSchema().ImpliedType(),
Upgrade: resourcePingdomCheckStateUpgradeV0,
},
},
}
}

Expand Down Expand Up @@ -315,7 +327,12 @@ func checkForResource(d *schema.ResourceData) (pingdom.Check, error) {
}
}
if v, ok := d.GetOk("tags"); ok {
checkParams.Tags = v.(string)
interfaceSlice := v.(*schema.Set).List()
tags := make([]string, len(interfaceSlice))
for i := range interfaceSlice {
tags[i] = interfaceSlice[i].(string)
}
checkParams.Tags = strings.Join(tags, ",")
}

if v, ok := d.GetOk("probefilters"); ok {
Expand Down Expand Up @@ -467,11 +484,18 @@ func resourcePingdomCheckRead(d *schema.ResourceData, meta interface{}) error {
d.Set("notifywhenbackup", ck.NotifyWhenBackup)
d.Set("publicreport", inPublicReport)

tags := []string{}
tags := schema.NewSet(
func(tagName interface{}) int {
h := fnv.New32a()
h.Write([]byte(tagName.(string)))
return int(h.Sum32())
},
[]interface{}{},
)
for _, tag := range ck.Tags {
tags = append(tags, tag.Name)
tags.Add(tag.Name)
}
d.Set("tags", strings.Join(tags, ","))
d.Set("tags", tags)

if ck.Status == "paused" {
d.Set("paused", true)
Expand Down Expand Up @@ -580,3 +604,187 @@ func resourcePingdomCheckDelete(d *schema.ResourceData, meta interface{}) error

return nil
}

func resourcePingdomCheckResourceV0() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
ForceNew: false,
},

"host": {
Type: schema.TypeString,
Required: true,
ForceNew: false,
},

"type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"paused": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
},

"responsetime_threshold": {
Type: schema.TypeInt,
Optional: true,
ForceNew: false,
Computed: true,
},

"publicreport": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
},

"resolution": {
Type: schema.TypeInt,
Required: true,
ForceNew: false,
},

"sendnotificationwhendown": {
Type: schema.TypeInt,
Optional: true,
ForceNew: false,
Computed: true,
},

"notifyagainevery": {
Type: schema.TypeInt,
Optional: true,
ForceNew: false,
},

"notifywhenbackup": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
Computed: true,
},

"integrationids": {
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Elem: &schema.Schema{Type: schema.TypeInt},
},

"encryption": {
Type: schema.TypeBool,
Optional: true,
ForceNew: false,
},

"url": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
Default: "/",
},

"port": {
Type: schema.TypeInt,
Optional: true,
ForceNew: false,
Computed: true,
},

"username": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"password": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"shouldcontain": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"shouldnotcontain": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"postdata": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"requestheaders": {
Type: schema.TypeMap,
Optional: true,
ForceNew: false,
},

"tags": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"probefilters": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"userids": {
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Elem: &schema.Schema{Type: schema.TypeInt},
},

"teamids": {
Type: schema.TypeSet,
Optional: true,
ForceNew: false,
Elem: &schema.Schema{Type: schema.TypeInt},
},

"stringtosend": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},

"stringtoexpect": {
Type: schema.TypeString,
Optional: true,
ForceNew: false,
},
},
}
}

func resourcePingdomCheckStateUpgradeV0(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) {
oldTags, ok := rawState["tags"]
if !ok {
return rawState, nil
}
newTags := []string{}
tags := strings.Split(oldTags.(string), ",")
for _, t := range tags {
newTags = append(newTags, t)
}
rawState["tags"] = newTags
return rawState, nil
}

0 comments on commit 98bf51b

Please sign in to comment.