From d783e831f8a4ddfd459a77df98b05e0018c0ea00 Mon Sep 17 00:00:00 2001 From: Joseph Anthony Pasquale Holsten Date: Thu, 15 Dec 2016 08:28:34 -0800 Subject: [PATCH] ultradns providers and improvements (#9788) * vendor: update github.com/Ensighten/udnssdk to v1.2.1 * ultradns_tcpool: add * ultradns.baseurl: set default * ultradns.record: cleanup test * ultradns_record: extract common, cleanup * ultradns: extract common * ultradns_dirpool: add * ultradns_dirpool: fix rdata.ip_info.ips to be idempotent * ultradns_tcpool: add doc * ultradns_dirpool: fix rdata.geo_codes.codes to be idempotent * ultradns_dirpool: add doc * ultradns: cleanup testing * ultradns_record: rename resource * ultradns: log username from config, not client udnssdk.Client is being refactored to use x/oauth2, so don't assume we can access Username from it * ultradns_probe_ping: add * ultradns_probe_http: add * doc: add ultradns_probe_ping * doc: add ultradns_probe_http * ultradns_record: remove duplication from error messages * doc: cleanup typos in ultradns * ultradns_probe_ping: add test for pool-level probe * Clean documentation * ultradns: pull makeSetFromStrings() up to common.go * ultradns_dirpool: log hashIPInfoIPs Log the key and generated hashcode used to index ip_info.ips into a set. * ultradns: simplify hashLimits() Limits blocks only have the "name" attribute as their primary key, so hashLimits() needn't use a buffer to concatenate. Also changes log level to a more approriate DEBUG. * ultradns_tcpool: convert rdata to schema.Set RData blocks have the "host" attribute as their primary key, so it is used by hashRdatas() to create the hashcode. Tests are updated to use the new hashcode indexes instead of natural numbers. * ultradns_probe_http: convert agents to schema.Set Also pull the makeSetFromStrings() helper up to common.go * ultradns: pull hashRdatas() up to common * ultradns_dirpool: convert rdata to schema.Set Fixes TF-66 * ultradns_dirpool.conflict_resolve: fix default from response UltraDNS REST API User Guide claims that "Directional Pool Profile Fields" have a "conflictResolve" field which "If not specified, defaults to GEO." https://portal.ultradns.com/static/docs/REST-API_User_Guide.pdf But UltraDNS does not actually return a conflictResolve attribute when it has been updated to "GEO". We could fix it in udnssdk, but that would require either: * hide the response by coercing "" to "GEO" for everyone * use a pointer to allow checking for nil (requires all users to change if they fix this) An ideal solution would be to have the UltraDNS API respond with this attribute for every dirpool's rdata. So at the risk of foolish consistency in the sdk, we're going to solve it where it's visible to the user: by checking and overriding the parsing. I'm sorry. * ultradns_record: convert rdata to set UltraDNS does not store the ordering of rdata elements, so we need a way to identify if changes have been made even it the order changes. A perfect job for schema.Set. * ultradns_record: parse double-encoded answers for TXT records * ultradns: simplify hashLimits() Limits blocks only have the "name" attribute as their primary key, so hashLimits() needn't use a buffer to concatenate. * ultradns_dirpool.description: validate * ultradns_dirpool.rdata: doc need for set * ultradns_dirpool.conflict_resolve: validate --- builtin/providers/ultradns/common.go | 198 ++++++ builtin/providers/ultradns/common_test.go | 67 ++ builtin/providers/ultradns/config.go | 2 +- builtin/providers/ultradns/provider.go | 12 +- .../ultradns/resource_ultradns_dirpool.go | 627 ++++++++++++++++++ .../resource_ultradns_dirpool_test.go | 192 ++++++ .../ultradns/resource_ultradns_probe_http.go | 316 +++++++++ .../resource_ultradns_probe_http_test.go | 260 ++++++++ .../ultradns/resource_ultradns_probe_ping.go | 218 ++++++ .../resource_ultradns_probe_ping_test.go | 219 ++++++ .../ultradns/resource_ultradns_record.go | 82 ++- .../ultradns/resource_ultradns_record_test.go | 176 ++--- .../ultradns/resource_ultradns_tcpool.go | 331 +++++++++ .../ultradns/resource_ultradns_tcpool_test.go | 156 +++++ .../github.com/Ensighten/udnssdk/CHANGELOG.md | 47 ++ .../Ensighten/udnssdk/CONTRIBUTING.md | 112 ++++ vendor/github.com/Ensighten/udnssdk/LICENSE | 20 + .../github.com/Ensighten/udnssdk/account.go | 7 +- vendor/github.com/Ensighten/udnssdk/alert.go | 16 +- vendor/github.com/Ensighten/udnssdk/common.go | 8 +- .../Ensighten/udnssdk/directional_pool.go | 33 +- vendor/github.com/Ensighten/udnssdk/event.go | 13 +- .../Ensighten/udnssdk/notification.go | 15 +- .../passwordcredentials.go | 73 ++ vendor/github.com/Ensighten/udnssdk/probe.go | 151 +++-- vendor/github.com/Ensighten/udnssdk/readme.md | 158 ++--- vendor/github.com/Ensighten/udnssdk/rrset.go | 274 ++++---- vendor/github.com/Ensighten/udnssdk/task.go | 13 +- .../Ensighten/udnssdk/token_source.go | 26 + .../github.com/Ensighten/udnssdk/udnssdk.go | 177 ++--- vendor/vendor.json | 4 +- .../ultradns/r/dirpool.html.markdown | 74 +++ .../ultradns/r/probe_http.html.markdown | 98 +++ .../ultradns/r/probe_ping.html.markdown | 67 ++ .../providers/ultradns/r/record.html.markdown | 4 +- .../providers/ultradns/r/tcpool.html.markdown | 60 ++ website/source/layouts/ultradns.erb | 28 +- 37 files changed, 3719 insertions(+), 615 deletions(-) create mode 100644 builtin/providers/ultradns/common.go create mode 100644 builtin/providers/ultradns/common_test.go create mode 100644 builtin/providers/ultradns/resource_ultradns_dirpool.go create mode 100644 builtin/providers/ultradns/resource_ultradns_dirpool_test.go create mode 100644 builtin/providers/ultradns/resource_ultradns_probe_http.go create mode 100644 builtin/providers/ultradns/resource_ultradns_probe_http_test.go create mode 100644 builtin/providers/ultradns/resource_ultradns_probe_ping.go create mode 100644 builtin/providers/ultradns/resource_ultradns_probe_ping_test.go create mode 100644 builtin/providers/ultradns/resource_ultradns_tcpool.go create mode 100644 builtin/providers/ultradns/resource_ultradns_tcpool_test.go create mode 100644 vendor/github.com/Ensighten/udnssdk/CHANGELOG.md create mode 100644 vendor/github.com/Ensighten/udnssdk/CONTRIBUTING.md create mode 100644 vendor/github.com/Ensighten/udnssdk/LICENSE create mode 100644 vendor/github.com/Ensighten/udnssdk/passwordcredentials/passwordcredentials.go create mode 100644 vendor/github.com/Ensighten/udnssdk/token_source.go create mode 100644 website/source/docs/providers/ultradns/r/dirpool.html.markdown create mode 100644 website/source/docs/providers/ultradns/r/probe_http.html.markdown create mode 100644 website/source/docs/providers/ultradns/r/probe_ping.html.markdown create mode 100644 website/source/docs/providers/ultradns/r/tcpool.html.markdown diff --git a/builtin/providers/ultradns/common.go b/builtin/providers/ultradns/common.go new file mode 100644 index 000000000..1512e7fb0 --- /dev/null +++ b/builtin/providers/ultradns/common.go @@ -0,0 +1,198 @@ +package ultradns + +import ( + "fmt" + "log" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" +) + +// Conversion helper functions +type rRSetResource struct { + OwnerName string + RRType string + RData []string + TTL int + Profile udnssdk.RawProfile + Zone string +} + +// profileAttrSchemaMap is a map from each ultradns_tcpool attribute name onto its respective ProfileSchema URI +var profileAttrSchemaMap = map[string]udnssdk.ProfileSchema{ + "dirpool_profile": udnssdk.DirPoolSchema, + "rdpool_profile": udnssdk.RDPoolSchema, + "sbpool_profile": udnssdk.SBPoolSchema, + "tcpool_profile": udnssdk.TCPoolSchema, +} + +func (r rRSetResource) RRSetKey() udnssdk.RRSetKey { + return udnssdk.RRSetKey{ + Zone: r.Zone, + Type: r.RRType, + Name: r.OwnerName, + } +} + +func (r rRSetResource) RRSet() udnssdk.RRSet { + return udnssdk.RRSet{ + OwnerName: r.OwnerName, + RRType: r.RRType, + RData: r.RData, + TTL: r.TTL, + Profile: r.Profile, + } +} + +func (r rRSetResource) ID() string { + return fmt.Sprintf("%s.%s", r.OwnerName, r.Zone) +} + +func unzipRdataHosts(configured []interface{}) []string { + hs := make([]string, 0, len(configured)) + for _, rRaw := range configured { + data := rRaw.(map[string]interface{}) + h := data["host"].(string) + hs = append(hs, h) + } + return hs +} + +func schemaPingProbe() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "packets": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 3, + }, + "packet_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 56, + }, + "limit": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Set: hashLimits, + Elem: resourceProbeLimits(), + }, + }, + } +} + +func resourceProbeLimits() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "warning": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "critical": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + "fail": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + }, + } +} + +type probeResource struct { + Name string + Zone string + ID string + + Agents []string + Interval string + PoolRecord string + Threshold int + Type udnssdk.ProbeType + + Details *udnssdk.ProbeDetailsDTO +} + +func (p probeResource) RRSetKey() udnssdk.RRSetKey { + return p.Key().RRSetKey() +} + +func (p probeResource) ProbeInfoDTO() udnssdk.ProbeInfoDTO { + return udnssdk.ProbeInfoDTO{ + ID: p.ID, + PoolRecord: p.PoolRecord, + ProbeType: p.Type, + Interval: p.Interval, + Agents: p.Agents, + Threshold: p.Threshold, + Details: p.Details, + } +} + +func (p probeResource) Key() udnssdk.ProbeKey { + return udnssdk.ProbeKey{ + Zone: p.Zone, + Name: p.Name, + ID: p.ID, + } +} + +func mapFromLimit(name string, l udnssdk.ProbeDetailsLimitDTO) map[string]interface{} { + return map[string]interface{}{ + "name": name, + "warning": l.Warning, + "critical": l.Critical, + "fail": l.Fail, + } +} + +// hashLimits generates a hashcode for a limits block +func hashLimits(v interface{}) int { + m := v.(map[string]interface{}) + h := hashcode.String(m["name"].(string)) + log.Printf("[INFO] hashLimits(): %v -> %v", m["name"].(string), h) + return h +} + +// makeSetFromLimits encodes an array of Limits into a +// *schema.Set in the appropriate structure for the schema +func makeSetFromLimits(ls map[string]udnssdk.ProbeDetailsLimitDTO) *schema.Set { + s := &schema.Set{F: hashLimits} + for name, l := range ls { + s.Add(mapFromLimit(name, l)) + } + return s +} + +func makeProbeDetailsLimit(configured interface{}) *udnssdk.ProbeDetailsLimitDTO { + l := configured.(map[string]interface{}) + return &udnssdk.ProbeDetailsLimitDTO{ + Warning: l["warning"].(int), + Critical: l["critical"].(int), + Fail: l["fail"].(int), + } +} + +// makeSetFromStrings encodes an []string into a +// *schema.Set in the appropriate structure for the schema +func makeSetFromStrings(ss []string) *schema.Set { + st := &schema.Set{F: schema.HashString} + for _, s := range ss { + st.Add(s) + } + return st +} + +// hashRdata generates a hashcode for an Rdata block +func hashRdatas(v interface{}) int { + m := v.(map[string]interface{}) + h := hashcode.String(m["host"].(string)) + log.Printf("[DEBUG] hashRdatas(): %v -> %v", m["host"].(string), h) + return h +} diff --git a/builtin/providers/ultradns/common_test.go b/builtin/providers/ultradns/common_test.go new file mode 100644 index 000000000..24470e0d3 --- /dev/null +++ b/builtin/providers/ultradns/common_test.go @@ -0,0 +1,67 @@ +package ultradns + +import ( + "fmt" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func testAccTcpoolCheckDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*udnssdk.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ultradns_tcpool" { + continue + } + + k := udnssdk.RRSetKey{ + Zone: rs.Primary.Attributes["zone"], + Name: rs.Primary.Attributes["name"], + Type: rs.Primary.Attributes["type"], + } + + _, err := client.RRSets.Select(k) + if err == nil { + return fmt.Errorf("Record still exists") + } + } + + return nil +} + +func testAccCheckUltradnsRecordExists(n string, record *udnssdk.RRSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Record ID is set") + } + + client := testAccProvider.Meta().(*udnssdk.Client) + k := udnssdk.RRSetKey{ + Zone: rs.Primary.Attributes["zone"], + Name: rs.Primary.Attributes["name"], + Type: rs.Primary.Attributes["type"], + } + + foundRecord, err := client.RRSets.Select(k) + + if err != nil { + return err + } + + if foundRecord[0].OwnerName != rs.Primary.Attributes["hostname"] { + return fmt.Errorf("Record not found: %+v,\n %+v\n", foundRecord, rs.Primary.Attributes) + } + + *record = foundRecord[0] + + return nil + } +} diff --git a/builtin/providers/ultradns/config.go b/builtin/providers/ultradns/config.go index a702f421e..d3b22d618 100644 --- a/builtin/providers/ultradns/config.go +++ b/builtin/providers/ultradns/config.go @@ -22,7 +22,7 @@ func (c *Config) Client() (*udnssdk.Client, error) { return nil, fmt.Errorf("Error setting up client: %s", err) } - log.Printf("[INFO] UltraDNS Client configured for user: %s", client.Username) + log.Printf("[INFO] UltraDNS Client configured for user: %s", c.Username) return client, nil } diff --git a/builtin/providers/ultradns/provider.go b/builtin/providers/ultradns/provider.go index fbe8178bf..e10015ab2 100644 --- a/builtin/providers/ultradns/provider.go +++ b/builtin/providers/ultradns/provider.go @@ -1,6 +1,7 @@ package ultradns import ( + "github.com/Ensighten/udnssdk" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) @@ -24,14 +25,19 @@ func Provider() terraform.ResourceProvider { }, "baseurl": &schema.Schema{ Type: schema.TypeString, - Required: true, + Optional: true, DefaultFunc: schema.EnvDefaultFunc("ULTRADNS_BASEURL", nil), - Description: "UltraDNS Base Url(defaults to testing)", + Default: udnssdk.DefaultLiveBaseURL, + Description: "UltraDNS Base URL", }, }, ResourcesMap: map[string]*schema.Resource{ - "ultradns_record": resourceUltraDNSRecord(), + "ultradns_dirpool": resourceUltradnsDirpool(), + "ultradns_probe_http": resourceUltradnsProbeHTTP(), + "ultradns_probe_ping": resourceUltradnsProbePing(), + "ultradns_record": resourceUltradnsRecord(), + "ultradns_tcpool": resourceUltradnsTcpool(), }, ConfigureFunc: providerConfigure, diff --git a/builtin/providers/ultradns/resource_ultradns_dirpool.go b/builtin/providers/ultradns/resource_ultradns_dirpool.go new file mode 100644 index 000000000..fb8df98a9 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_dirpool.go @@ -0,0 +1,627 @@ +package ultradns + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "strings" + + "github.com/Ensighten/udnssdk" + "github.com/fatih/structs" + "github.com/hashicorp/terraform/helper/hashcode" + "github.com/hashicorp/terraform/helper/schema" + "github.com/mitchellh/mapstructure" +) + +func resourceUltradnsDirpool() *schema.Resource { + return &schema.Resource{ + Create: resourceUltradnsDirpoolCreate, + Read: resourceUltradnsDirpoolRead, + Update: resourceUltradnsDirpoolUpdate, + Delete: resourceUltradnsDirpoolDelete, + + Schema: map[string]*schema.Schema{ + // Required + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "type": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if len(value) > 255 { + errors = append(errors, fmt.Errorf( + "'description' too long, must be less than 255 characters")) + } + return + }, + }, + "rdata": &schema.Schema{ + // UltraDNS API does not respect rdata ordering + Type: schema.TypeSet, + Set: hashRdatas, + Required: true, + // Valid: len(rdataInfo) == len(rdata) + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // Required + "host": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "all_non_configured": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "geo_info": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "is_account_level": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "codes": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + }, + }, + "ip_info": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "is_account_level": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "ips": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Set: hashIPInfoIPs, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"cidr", "address"}, + }, + "end": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"cidr", "address"}, + }, + "cidr": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"start", "end", "address"}, + }, + "address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"start", "end", "cidr"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + // Optional + "ttl": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 3600, + }, + "conflict_resolve": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "GEO", + ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) { + value := v.(string) + if value != "GEO" && value != "IP" { + errors = append(errors, fmt.Errorf( + "only 'GEO', and 'IP' are supported values for 'conflict_resolve'")) + } + return + }, + }, + "no_response": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "all_non_configured": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "geo_info": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "is_account_level": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "codes": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, + }, + }, + }, + }, + "ip_info": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "is_account_level": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "ips": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Set: hashIPInfoIPs, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "start": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"cidr", "address"}, + }, + "end": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"cidr", "address"}, + }, + "cidr": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"start", "end", "address"}, + }, + "address": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // ConflictsWith: []string{"start", "end", "cidr"}, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + // Computed + "hostname": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// CRUD Operations + +func resourceUltradnsDirpoolCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeDirpoolRRSetResource(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_dirpool create: %#v", r) + _, err = client.RRSets.Create(r.RRSetKey(), r.RRSet()) + if err != nil { + // FIXME: remove the json from log + marshalled, _ := json.Marshal(r) + ms := string(marshalled) + return fmt.Errorf("create failed: %#v [[[[ %v ]]]] -> %v", r, ms, err) + } + + d.SetId(r.ID()) + log.Printf("[INFO] ultradns_dirpool.id: %v", d.Id()) + + return resourceUltradnsDirpoolRead(d, meta) +} + +func resourceUltradnsDirpoolRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + rr, err := makeDirpoolRRSetResource(d) + if err != nil { + return err + } + + rrsets, err := client.RRSets.Select(rr.RRSetKey()) + if err != nil { + uderr, ok := err.(*udnssdk.ErrorResponseList) + if ok { + for _, resps := range uderr.Responses { + // 70002 means Records Not Found + if resps.ErrorCode == 70002 { + d.SetId("") + return nil + } + return fmt.Errorf("resource not found: %v", err) + } + } + return fmt.Errorf("resource not found: %v", err) + } + + r := rrsets[0] + + return populateResourceFromDirpool(d, &r) +} + +func resourceUltradnsDirpoolUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeDirpoolRRSetResource(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_dirpool update: %+v", r) + _, err = client.RRSets.Update(r.RRSetKey(), r.RRSet()) + if err != nil { + return fmt.Errorf("resource update failed: %v", err) + } + + return resourceUltradnsDirpoolRead(d, meta) +} + +func resourceUltradnsDirpoolDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeDirpoolRRSetResource(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_dirpool delete: %+v", r) + _, err = client.RRSets.Delete(r.RRSetKey()) + if err != nil { + return fmt.Errorf("resource delete failed: %v", err) + } + + return nil +} + +// Resource Helpers + +// makeDirpoolRRSetResource converts ResourceData into an rRSetResource +// ready for use in any CRUD operation +func makeDirpoolRRSetResource(d *schema.ResourceData) (rRSetResource, error) { + rDataRaw := d.Get("rdata").(*schema.Set).List() + res := rRSetResource{ + RRType: d.Get("type").(string), + Zone: d.Get("zone").(string), + OwnerName: d.Get("name").(string), + TTL: d.Get("ttl").(int), + RData: unzipRdataHosts(rDataRaw), + } + + profile := udnssdk.DirPoolProfile{ + Context: udnssdk.DirPoolSchema, + Description: d.Get("description").(string), + ConflictResolve: d.Get("conflict_resolve").(string), + } + + ri, err := makeDirpoolRdataInfos(rDataRaw) + if err != nil { + return res, err + } + profile.RDataInfo = ri + + noResponseRaw := d.Get("no_response").([]interface{}) + if len(noResponseRaw) >= 1 { + if len(noResponseRaw) > 1 { + return res, fmt.Errorf("no_response: only 0 or 1 blocks alowed, got: %#v", len(noResponseRaw)) + } + nr, err := makeDirpoolRdataInfo(noResponseRaw[0]) + if err != nil { + return res, err + } + profile.NoResponse = nr + } + + res.Profile = profile.RawProfile() + + return res, nil +} + +// populateResourceFromDirpool takes an RRSet and populates the ResourceData +func populateResourceFromDirpool(d *schema.ResourceData, r *udnssdk.RRSet) error { + // TODO: fix from tcpool to dirpool + zone := d.Get("zone") + // ttl + d.Set("ttl", r.TTL) + // hostname + if r.OwnerName == "" { + d.Set("hostname", zone) + } else { + if strings.HasSuffix(r.OwnerName, ".") { + d.Set("hostname", r.OwnerName) + } else { + d.Set("hostname", fmt.Sprintf("%s.%s", r.OwnerName, zone)) + } + } + + // And now... the Profile! + if r.Profile == nil { + return fmt.Errorf("RRSet.profile missing: invalid DirPool schema in: %#v", r) + } + p, err := r.Profile.DirPoolProfile() + if err != nil { + return fmt.Errorf("RRSet.profile could not be unmarshalled: %v\n", err) + } + + // Set simple values + d.Set("description", p.Description) + + // Ensure default looks like "GEO", even when nothing is returned + if p.ConflictResolve == "" { + d.Set("conflict_resolve", "GEO") + } else { + d.Set("conflict_resolve", p.ConflictResolve) + } + + rd := makeSetFromDirpoolRdata(r.RData, p.RDataInfo) + err = d.Set("rdata", rd) + if err != nil { + return fmt.Errorf("rdata set failed: %v, from %#v", err, rd) + } + return nil +} + +// makeDirpoolRdataInfos converts []map[string]interface{} from rdata +// blocks into []DPRDataInfo +func makeDirpoolRdataInfos(configured []interface{}) ([]udnssdk.DPRDataInfo, error) { + res := make([]udnssdk.DPRDataInfo, 0, len(configured)) + for _, r := range configured { + ri, err := makeDirpoolRdataInfo(r) + if err != nil { + return res, err + } + res = append(res, ri) + } + return res, nil +} + +// makeDirpoolRdataInfo converts a map[string]interface{} from +// an rdata or no_response block into an DPRDataInfo +func makeDirpoolRdataInfo(configured interface{}) (udnssdk.DPRDataInfo, error) { + data := configured.(map[string]interface{}) + res := udnssdk.DPRDataInfo{ + AllNonConfigured: data["all_non_configured"].(bool), + } + // IPInfo + ipInfo := data["ip_info"].([]interface{}) + if len(ipInfo) >= 1 { + if len(ipInfo) > 1 { + return res, fmt.Errorf("ip_info: only 0 or 1 blocks alowed, got: %#v", len(ipInfo)) + } + ii, err := makeIPInfo(ipInfo[0]) + if err != nil { + return res, fmt.Errorf("%v ip_info: %#v", err, ii) + } + res.IPInfo = &ii + } + // GeoInfo + geoInfo := data["geo_info"].([]interface{}) + if len(geoInfo) >= 1 { + if len(geoInfo) > 1 { + return res, fmt.Errorf("geo_info: only 0 or 1 blocks alowed, got: %#v", len(geoInfo)) + } + gi, err := makeGeoInfo(geoInfo[0]) + if err != nil { + return res, fmt.Errorf("%v geo_info: %#v GeoInfo: %#v", err, geoInfo[0], gi) + } + res.GeoInfo = &gi + } + return res, nil +} + +// makeGeoInfo converts a map[string]interface{} from an geo_info block +// into an GeoInfo +func makeGeoInfo(configured interface{}) (udnssdk.GeoInfo, error) { + var res udnssdk.GeoInfo + c := configured.(map[string]interface{}) + err := mapDecode(c, &res) + if err != nil { + return res, err + } + + rawCodes := c["codes"].(*schema.Set).List() + res.Codes = make([]string, 0, len(rawCodes)) + for _, i := range rawCodes { + res.Codes = append(res.Codes, i.(string)) + } + return res, err +} + +// makeIPInfo converts a map[string]interface{} from an ip_info block +// into an IPInfo +func makeIPInfo(configured interface{}) (udnssdk.IPInfo, error) { + var res udnssdk.IPInfo + c := configured.(map[string]interface{}) + err := mapDecode(c, &res) + if err != nil { + return res, err + } + + rawIps := c["ips"].(*schema.Set).List() + res.Ips = make([]udnssdk.IPAddrDTO, 0, len(rawIps)) + for _, rawIa := range rawIps { + var i udnssdk.IPAddrDTO + err = mapDecode(rawIa, &i) + if err != nil { + return res, err + } + res.Ips = append(res.Ips, i) + } + return res, nil +} + +// collate and zip RData and RDataInfo into []map[string]interface{} +func zipDirpoolRData(rds []string, rdis []udnssdk.DPRDataInfo) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(rds)) + for i, rdi := range rdis { + r := map[string]interface{}{ + "host": rds[i], + "all_non_configured": rdi.AllNonConfigured, + "ip_info": mapFromIPInfos(rdi.IPInfo), + "geo_info": mapFromGeoInfos(rdi.GeoInfo), + } + result = append(result, r) + } + return result +} + +// makeSetFromDirpoolRdata encodes an array of Rdata into a +// *schema.Set in the appropriate structure for the schema +func makeSetFromDirpoolRdata(rds []string, rdis []udnssdk.DPRDataInfo) *schema.Set { + s := &schema.Set{F: hashRdatas} + rs := zipDirpoolRData(rds, rdis) + for _, r := range rs { + s.Add(r) + } + return s +} + +// mapFromIPInfos encodes 0 or 1 IPInfos into a []map[string]interface{} +// in the appropriate structure for the schema +func mapFromIPInfos(rdi *udnssdk.IPInfo) []map[string]interface{} { + res := make([]map[string]interface{}, 0, 1) + if rdi != nil { + m := map[string]interface{}{ + "name": rdi.Name, + "is_account_level": rdi.IsAccountLevel, + "ips": makeSetFromIPAddrDTOs(rdi.Ips), + } + res = append(res, m) + } + return res +} + +// makeSetFromIPAddrDTOs encodes an array of IPAddrDTO into a +// *schema.Set in the appropriate structure for the schema +func makeSetFromIPAddrDTOs(ias []udnssdk.IPAddrDTO) *schema.Set { + s := &schema.Set{F: hashIPInfoIPs} + for _, ia := range ias { + s.Add(mapEncode(ia)) + } + return s +} + +// mapFromGeoInfos encodes 0 or 1 GeoInfos into a []map[string]interface{} +// in the appropriate structure for the schema +func mapFromGeoInfos(gi *udnssdk.GeoInfo) []map[string]interface{} { + res := make([]map[string]interface{}, 0, 1) + if gi != nil { + m := mapEncode(gi) + m["codes"] = makeSetFromStrings(gi.Codes) + res = append(res, m) + } + return res +} + +// hashIPInfoIPs generates a hashcode for an ip_info.ips block +func hashIPInfoIPs(v interface{}) int { + var buf bytes.Buffer + m := v.(map[string]interface{}) + buf.WriteString(fmt.Sprintf("%s-", m["start"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["end"].(string))) + buf.WriteString(fmt.Sprintf("%s-", m["cidr"].(string))) + buf.WriteString(fmt.Sprintf("%s", m["address"].(string))) + + h := hashcode.String(buf.String()) + log.Printf("[DEBUG] hashIPInfoIPs(): %v -> %v", buf.String(), h) + return h +} + +// Map <-> Struct transcoding +// Ideally, we sould be able to handle almost all the type conversion +// in this resource using the following helpers. Unfortunately, some +// issues remain: +// - schema.Set values cannot be naively assigned, and must be +// manually converted +// - ip_info and geo_info come in as []map[string]interface{}, but are +// in DPRDataInfo as singluar. + +// mapDecode takes a map[string]interface{} and uses reflection to +// convert it into the given Go native structure. val must be a pointer +// to a struct. This is identical to mapstructure.Decode, but uses the +// `terraform:` tag instead of `mapstructure:` +func mapDecode(m interface{}, rawVal interface{}) error { + config := &mapstructure.DecoderConfig{ + Metadata: nil, + TagName: "terraform", + Result: rawVal, + WeaklyTypedInput: true, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(m) +} + +func mapEncode(rawVal interface{}) map[string]interface{} { + s := structs.New(rawVal) + s.TagName = "terraform" + return s.Map() +} diff --git a/builtin/providers/ultradns/resource_ultradns_dirpool_test.go b/builtin/providers/ultradns/resource_ultradns_dirpool_test.go new file mode 100644 index 000000000..f92c82fcd --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_dirpool_test.go @@ -0,0 +1,192 @@ +package ultradns + +import ( + "fmt" + "testing" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccUltradnsDirpool(t *testing.T) { + var record udnssdk.RRSet + domain := "ultradns.phinze.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccDirpoolCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testCfgDirpoolMinimal, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_dirpool.it", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_dirpool.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "name", "test-dirpool-minimal"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "type", "A"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "ttl", "300"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "description", "Minimal directional pool"), + // hashRdatas(): 10.1.0.1 -> 463398947 + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.463398947.host", "10.1.0.1"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.463398947.all_non_configured", "true"), + // Generated + resource.TestCheckResourceAttr("ultradns_dirpool.it", "id", "test-dirpool-minimal.ultradns.phinze.com"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "hostname", "test-dirpool-minimal.ultradns.phinze.com."), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testCfgDirpoolMaximal, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_dirpool.it", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_dirpool.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "name", "test-dirpool-maximal"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "type", "A"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "ttl", "300"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "description", "Description of pool"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "conflict_resolve", "GEO"), + + // hashRdatas(): 10.1.1.1 -> 442270228 + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.442270228.host", "10.1.1.1"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.442270228.all_non_configured", "true"), + // hashRdatas(): 10.1.1.2 -> 2203440046 + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.2203440046.host", "10.1.1.2"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.2203440046.geo_info.0.name", "North America"), + // hashRdatas(): 10.1.1.3 -> 4099072824 + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.4099072824.host", "10.1.1.3"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "rdata.4099072824.ip_info.0.name", "some Ips"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "no_response.0.geo_info.0.name", "nrGeo"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "no_response.0.ip_info.0.name", "nrIP"), + // Generated + resource.TestCheckResourceAttr("ultradns_dirpool.it", "id", "test-dirpool-maximal.ultradns.phinze.com"), + resource.TestCheckResourceAttr("ultradns_dirpool.it", "hostname", "test-dirpool-maximal.ultradns.phinze.com."), + ), + }, + }, + }) +} + +func testAccDirpoolCheckDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*udnssdk.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ultradns_dirpool" { + continue + } + + k := udnssdk.RRSetKey{ + Zone: rs.Primary.Attributes["zone"], + Name: rs.Primary.Attributes["name"], + Type: rs.Primary.Attributes["type"], + } + + _, err := client.RRSets.Select(k) + + if err == nil { + return fmt.Errorf("Record still exists") + } + } + + return nil +} + +const testCfgDirpoolMinimal = ` +resource "ultradns_dirpool" "it" { + zone = "%s" + name = "test-dirpool-minimal" + type = "A" + ttl = 300 + description = "Minimal directional pool" + + rdata { + host = "10.1.0.1" + all_non_configured = true + } +} +` + +const testCfgDirpoolMaximal = ` +resource "ultradns_dirpool" "it" { + zone = "%s" + name = "test-dirpool-maximal" + type = "A" + ttl = 300 + description = "Description of pool" + + conflict_resolve = "GEO" + + rdata { + host = "10.1.1.1" + all_non_configured = true + } + + rdata { + host = "10.1.1.2" + + geo_info { + name = "North America" + + codes = [ + "US-OK", + "US-DC", + "US-MA", + ] + } + } + + rdata { + host = "10.1.1.3" + + ip_info { + name = "some Ips" + + ips { + start = "200.20.0.1" + end = "200.20.0.10" + } + + ips { + cidr = "20.20.20.0/24" + } + + ips { + address = "50.60.70.80" + } + } + } + +# rdata { +# host = "10.1.1.4" +# +# geo_info { +# name = "accountGeoGroup" +# is_account_level = true +# } +# +# ip_info { +# name = "accountIPGroup" +# is_account_level = true +# } +# } + + no_response { + geo_info { + name = "nrGeo" + + codes = [ + "Z4", + ] + } + + ip_info { + name = "nrIP" + + ips { + address = "197.231.41.3" + } + } + } +} +` diff --git a/builtin/providers/ultradns/resource_ultradns_probe_http.go b/builtin/providers/ultradns/resource_ultradns_probe_http.go new file mode 100644 index 000000000..4149ee6fb --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_probe_http.go @@ -0,0 +1,316 @@ +package ultradns + +import ( + "fmt" + "log" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceUltradnsProbeHTTP() *schema.Resource { + return &schema.Resource{ + Create: resourceUltradnsProbeHTTPCreate, + Read: resourceUltradnsProbeHTTPRead, + Update: resourceUltradnsProbeHTTPUpdate, + Delete: resourceUltradnsProbeHTTPDelete, + + Schema: map[string]*schema.Schema{ + // Key + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "pool_record": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + // Required + "agents": &schema.Schema{ + Type: schema.TypeSet, + Set: schema.HashString, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "threshold": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + // Optional + "interval": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "FIVE_MINUTES", + }, + "http_probe": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: schemaHTTPProbe(), + }, + // Computed + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func schemaHTTPProbe() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "transaction": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "method": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "url": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + "transmitted_data": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + }, + "follow_redirects": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "limit": &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Set: hashLimits, + Elem: resourceProbeLimits(), + }, + }, + }, + }, + "total_limits": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "warning": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "critical": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + "fail": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + }, + }, + }, + }, + }, + } +} + +func resourceUltradnsProbeHTTPCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeHTTPProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_http configuration: %v", err) + } + + log.Printf("[INFO] ultradns_probe_http create: %#v, detail: %#v", r, r.Details.Detail) + resp, err := client.Probes.Create(r.Key().RRSetKey(), r.ProbeInfoDTO()) + if err != nil { + return fmt.Errorf("create failed: %v", err) + } + + uri := resp.Header.Get("Location") + d.Set("uri", uri) + d.SetId(uri) + log.Printf("[INFO] ultradns_probe_http.id: %v", d.Id()) + + return resourceUltradnsProbeHTTPRead(d, meta) +} + +func resourceUltradnsProbeHTTPRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeHTTPProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_http configuration: %v", err) + } + + log.Printf("[DEBUG] ultradns_probe_http read: %#v", r) + probe, _, err := client.Probes.Find(r.Key()) + log.Printf("[DEBUG] ultradns_probe_http response: %#v", probe) + + if err != nil { + uderr, ok := err.(*udnssdk.ErrorResponseList) + if ok { + for _, r := range uderr.Responses { + // 70002 means Probes Not Found + if r.ErrorCode == 70002 { + d.SetId("") + return nil + } + return fmt.Errorf("not found: %s", err) + } + } + return fmt.Errorf("not found: %s", err) + } + + return populateResourceDataFromHTTPProbe(probe, d) +} + +func resourceUltradnsProbeHTTPUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeHTTPProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_http configuration: %v", err) + } + + log.Printf("[INFO] ultradns_probe_http update: %+v", r) + _, err = client.Probes.Update(r.Key(), r.ProbeInfoDTO()) + if err != nil { + return fmt.Errorf("update failed: %s", err) + } + + return resourceUltradnsProbeHTTPRead(d, meta) +} + +func resourceUltradnsProbeHTTPDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makeHTTPProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_http configuration: %s", err) + } + + log.Printf("[INFO] ultradns_probe_http delete: %+v", r) + _, err = client.Probes.Delete(r.Key()) + if err != nil { + return fmt.Errorf("delete failed: %s", err) + } + + return nil +} + +// Resource Helpers + +func makeHTTPProbeResource(d *schema.ResourceData) (probeResource, error) { + p := probeResource{} + p.Zone = d.Get("zone").(string) + p.Name = d.Get("name").(string) + p.ID = d.Id() + p.Interval = d.Get("interval").(string) + p.PoolRecord = d.Get("pool_record").(string) + p.Threshold = d.Get("threshold").(int) + for _, a := range d.Get("agents").(*schema.Set).List() { + p.Agents = append(p.Agents, a.(string)) + } + + p.Type = udnssdk.HTTPProbeType + hps := d.Get("http_probe").([]interface{}) + if len(hps) >= 1 { + if len(hps) > 1 { + return p, fmt.Errorf("http_probe: only 0 or 1 blocks alowed, got: %#v", len(hps)) + } + p.Details = makeHTTPProbeDetails(hps[0]) + } + + return p, nil +} + +func makeHTTPProbeDetails(configured interface{}) *udnssdk.ProbeDetailsDTO { + data := configured.(map[string]interface{}) + // Convert limits from flattened set format to mapping. + d := udnssdk.HTTPProbeDetailsDTO{} + + ts := []udnssdk.Transaction{} + for _, rt := range data["transaction"].([]interface{}) { + mt := rt.(map[string]interface{}) + ls := make(map[string]udnssdk.ProbeDetailsLimitDTO) + for _, limit := range mt["limit"].(*schema.Set).List() { + l := limit.(map[string]interface{}) + name := l["name"].(string) + ls[name] = *makeProbeDetailsLimit(l) + } + t := udnssdk.Transaction{ + Method: mt["method"].(string), + URL: mt["url"].(string), + TransmittedData: mt["transmitted_data"].(string), + FollowRedirects: mt["follow_redirects"].(bool), + Limits: ls, + } + ts = append(ts, t) + } + d.Transactions = ts + rawLims := data["total_limits"].([]interface{}) + if len(rawLims) >= 1 { + // TODO: validate 0 or 1 total_limits + // if len(rawLims) > 1 { + // return nil, fmt.Errorf("total_limits: only 0 or 1 blocks alowed, got: %#v", len(rawLims)) + // } + d.TotalLimits = makeProbeDetailsLimit(rawLims[0]) + } + res := udnssdk.ProbeDetailsDTO{ + Detail: d, + } + return &res +} + +func populateResourceDataFromHTTPProbe(p udnssdk.ProbeInfoDTO, d *schema.ResourceData) error { + d.SetId(p.ID) + d.Set("pool_record", p.PoolRecord) + d.Set("interval", p.Interval) + d.Set("agents", makeSetFromStrings(p.Agents)) + d.Set("threshold", p.Threshold) + + hp := map[string]interface{}{} + hd, err := p.Details.HTTPProbeDetails() + if err != nil { + return fmt.Errorf("ProbeInfo.details could not be unmarshalled: %v, Details: %#v", err, p.Details) + } + ts := make([]map[string]interface{}, 0, len(hd.Transactions)) + for _, rt := range hd.Transactions { + t := map[string]interface{}{ + "method": rt.Method, + "url": rt.URL, + "transmitted_data": rt.TransmittedData, + "follow_redirects": rt.FollowRedirects, + "limit": makeSetFromLimits(rt.Limits), + } + ts = append(ts, t) + } + hp["transaction"] = ts + + tls := []map[string]interface{}{} + rawtl := hd.TotalLimits + if rawtl != nil { + tl := map[string]interface{}{ + "warning": rawtl.Warning, + "critical": rawtl.Critical, + "fail": rawtl.Fail, + } + tls = append(tls, tl) + } + hp["total_limits"] = tls + + err = d.Set("http_probe", []map[string]interface{}{hp}) + if err != nil { + return fmt.Errorf("http_probe set failed: %v, from %#v", err, hp) + } + return nil +} diff --git a/builtin/providers/ultradns/resource_ultradns_probe_http_test.go b/builtin/providers/ultradns/resource_ultradns_probe_http_test.go new file mode 100644 index 000000000..375da86fc --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_probe_http_test.go @@ -0,0 +1,260 @@ +package ultradns + +import ( + "fmt" + "testing" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccUltradnsProbeHTTP(t *testing.T) { + var record udnssdk.RRSet + domain := "ultradns.phinze.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccTcpoolCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testCfgProbeHTTPMinimal, domain, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_tcpool.test-probe-http-minimal", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_probe_http.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "name", "test-probe-http-minimal"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "pool_record", "10.2.0.1"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "agents.4091180299", "DALLAS"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "agents.2144410488", "AMSTERDAM"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "interval", "ONE_MINUTE"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "threshold", "1"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.method", "GET"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.url", "http://localhost/index"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.#", "2"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.name", "connect"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.warning", "20"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.critical", "20"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.fail", "20"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.name", "run"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.warning", "60"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.critical", "60"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.fail", "60"), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testCfgProbeHTTPMaximal, domain, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_tcpool.test-probe-http-maximal", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_probe_http.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "name", "test-probe-http-maximal"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "pool_record", "10.2.1.1"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "agents.4091180299", "DALLAS"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "agents.2144410488", "AMSTERDAM"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "interval", "ONE_MINUTE"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "threshold", "1"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.method", "POST"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.url", "http://localhost/index"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.#", "4"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.name", "run"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.warning", "1"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.critical", "2"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1349952704.fail", "3"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.2720402232.name", "avgConnect"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.2720402232.warning", "4"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.2720402232.critical", "5"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.2720402232.fail", "6"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.896769211.name", "avgRun"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.896769211.warning", "7"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.896769211.critical", "8"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.896769211.fail", "9"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.name", "connect"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.warning", "10"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.critical", "11"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.transaction.0.limit.1959786783.fail", "12"), + + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.total_limits.0.warning", "13"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.total_limits.0.critical", "14"), + resource.TestCheckResourceAttr("ultradns_probe_http.it", "http_probe.0.total_limits.0.fail", "15"), + ), + }, + }, + }) +} + +const testCfgProbeHTTPMinimal = ` +resource "ultradns_tcpool" "test-probe-http-minimal" { + zone = "%s" + name = "test-probe-http-minimal" + + ttl = 30 + description = "traffic controller pool with probes" + + run_probes = true + act_on_probes = true + max_to_lb = 2 + + rdata { + host = "10.2.0.1" + + state = "NORMAL" + run_probes = true + priority = 1 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + rdata { + host = "10.2.0.2" + + state = "NORMAL" + run_probes = true + priority = 2 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + backup_record_rdata = "10.2.0.3" +} + +resource "ultradns_probe_http" "it" { + zone = "%s" + name = "test-probe-http-minimal" + + pool_record = "10.2.0.1" + + agents = ["DALLAS", "AMSTERDAM"] + + interval = "ONE_MINUTE" + threshold = 1 + + http_probe { + transaction { + method = "GET" + url = "http://localhost/index" + + limit { + name = "run" + warning = 60 + critical = 60 + fail = 60 + } + + limit { + name = "connect" + warning = 20 + critical = 20 + fail = 20 + } + } + } + + depends_on = ["ultradns_tcpool.test-probe-http-minimal"] +} +` + +const testCfgProbeHTTPMaximal = ` +resource "ultradns_tcpool" "test-probe-http-maximal" { + zone = "%s" + name = "test-probe-http-maximal" + + ttl = 30 + description = "traffic controller pool with probes" + + run_probes = true + act_on_probes = true + max_to_lb = 2 + + rdata { + host = "10.2.1.1" + + state = "NORMAL" + run_probes = true + priority = 1 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + rdata { + host = "10.2.1.2" + + state = "NORMAL" + run_probes = true + priority = 2 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + backup_record_rdata = "10.2.1.3" +} + +resource "ultradns_probe_http" "it" { + zone = "%s" + name = "test-probe-http-maximal" + + pool_record = "10.2.1.1" + + agents = ["DALLAS", "AMSTERDAM"] + + interval = "ONE_MINUTE" + threshold = 1 + + http_probe { + transaction { + method = "POST" + url = "http://localhost/index" + transmitted_data = "{}" + follow_redirects = true + + limit { + name = "run" + + warning = 1 + critical = 2 + fail = 3 + } + limit { + name = "avgConnect" + + warning = 4 + critical = 5 + fail = 6 + } + limit { + name = "avgRun" + + warning = 7 + critical = 8 + fail = 9 + } + limit { + name = "connect" + + warning = 10 + critical = 11 + fail = 12 + } + } + + total_limits { + warning = 13 + critical = 14 + fail = 15 + } + } + + depends_on = ["ultradns_tcpool.test-probe-http-maximal"] +} +` diff --git a/builtin/providers/ultradns/resource_ultradns_probe_ping.go b/builtin/providers/ultradns/resource_ultradns_probe_ping.go new file mode 100644 index 000000000..44445d5b2 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_probe_ping.go @@ -0,0 +1,218 @@ +package ultradns + +import ( + "fmt" + "log" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceUltradnsProbePing() *schema.Resource { + return &schema.Resource{ + Create: resourceUltradnsProbePingCreate, + Read: resourceUltradnsProbePingRead, + Update: resourceUltradnsProbePingUpdate, + Delete: resourceUltradnsProbePingDelete, + + Schema: map[string]*schema.Schema{ + // Key + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "pool_record": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + // Required + "agents": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "threshold": &schema.Schema{ + Type: schema.TypeInt, + Required: true, + }, + // Optional + "interval": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "FIVE_MINUTES", + }, + "ping_probe": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: schemaPingProbe(), + }, + // Computed + "id": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceUltradnsProbePingCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makePingProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_ping configuration: %v", err) + } + + log.Printf("[INFO] ultradns_probe_ping create: %#v, detail: %#v", r, r.Details.Detail) + resp, err := client.Probes.Create(r.Key().RRSetKey(), r.ProbeInfoDTO()) + if err != nil { + return fmt.Errorf("create failed: %v", err) + } + + uri := resp.Header.Get("Location") + d.Set("uri", uri) + d.SetId(uri) + log.Printf("[INFO] ultradns_probe_ping.id: %v", d.Id()) + + return resourceUltradnsProbePingRead(d, meta) +} + +func resourceUltradnsProbePingRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makePingProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_ping configuration: %v", err) + } + + log.Printf("[DEBUG] ultradns_probe_ping read: %#v", r) + probe, _, err := client.Probes.Find(r.Key()) + log.Printf("[DEBUG] ultradns_probe_ping response: %#v", probe) + + if err != nil { + uderr, ok := err.(*udnssdk.ErrorResponseList) + if ok { + for _, r := range uderr.Responses { + // 70002 means Probes Not Found + if r.ErrorCode == 70002 { + d.SetId("") + return nil + } + return fmt.Errorf("not found: %s", err) + } + } + return fmt.Errorf("not found: %s", err) + } + + return populateResourceDataFromPingProbe(probe, d) +} + +func resourceUltradnsProbePingUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makePingProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_ping configuration: %v", err) + } + + log.Printf("[INFO] ultradns_probe_ping update: %+v", r) + _, err = client.Probes.Update(r.Key(), r.ProbeInfoDTO()) + if err != nil { + return fmt.Errorf("update failed: %s", err) + } + + return resourceUltradnsProbePingRead(d, meta) +} + +func resourceUltradnsProbePingDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := makePingProbeResource(d) + if err != nil { + return fmt.Errorf("Could not load ultradns_probe_ping configuration: %s", err) + } + + log.Printf("[INFO] ultradns_probe_ping delete: %+v", r) + _, err = client.Probes.Delete(r.Key()) + if err != nil { + return fmt.Errorf("delete failed: %s", err) + } + + return nil +} + +// Resource Helpers + +func makePingProbeResource(d *schema.ResourceData) (probeResource, error) { + p := probeResource{} + p.Zone = d.Get("zone").(string) + p.Name = d.Get("name").(string) + p.ID = d.Id() + p.Interval = d.Get("interval").(string) + p.PoolRecord = d.Get("pool_record").(string) + p.Threshold = d.Get("threshold").(int) + for _, a := range d.Get("agents").([]interface{}) { + p.Agents = append(p.Agents, a.(string)) + } + + p.Type = udnssdk.PingProbeType + pps := d.Get("ping_probe").([]interface{}) + if len(pps) >= 1 { + if len(pps) > 1 { + return p, fmt.Errorf("ping_probe: only 0 or 1 blocks alowed, got: %#v", len(pps)) + } + p.Details = makePingProbeDetails(pps[0]) + } + + return p, nil +} + +func makePingProbeDetails(configured interface{}) *udnssdk.ProbeDetailsDTO { + data := configured.(map[string]interface{}) + // Convert limits from flattened set format to mapping. + ls := make(map[string]udnssdk.ProbeDetailsLimitDTO) + for _, limit := range data["limit"].(*schema.Set).List() { + l := limit.(map[string]interface{}) + name := l["name"].(string) + ls[name] = *makeProbeDetailsLimit(l) + } + res := udnssdk.ProbeDetailsDTO{ + Detail: udnssdk.PingProbeDetailsDTO{ + Limits: ls, + PacketSize: data["packet_size"].(int), + Packets: data["packets"].(int), + }, + } + return &res +} + +func populateResourceDataFromPingProbe(p udnssdk.ProbeInfoDTO, d *schema.ResourceData) error { + d.SetId(p.ID) + d.Set("pool_record", p.PoolRecord) + d.Set("interval", p.Interval) + d.Set("agents", p.Agents) + d.Set("threshold", p.Threshold) + + pd, err := p.Details.PingProbeDetails() + if err != nil { + return fmt.Errorf("ProbeInfo.details could not be unmarshalled: %v, Details: %#v", err, p.Details) + } + pp := map[string]interface{}{ + "packets": pd.Packets, + "packet_size": pd.PacketSize, + "limit": makeSetFromLimits(pd.Limits), + } + + err = d.Set("ping_probe", []map[string]interface{}{pp}) + if err != nil { + return fmt.Errorf("ping_probe set failed: %v, from %#v", err, pp) + } + return nil +} diff --git a/builtin/providers/ultradns/resource_ultradns_probe_ping_test.go b/builtin/providers/ultradns/resource_ultradns_probe_ping_test.go new file mode 100644 index 000000000..6414e2d71 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_probe_ping_test.go @@ -0,0 +1,219 @@ +package ultradns + +import ( + "fmt" + "testing" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccUltradnsProbePing(t *testing.T) { + var record udnssdk.RRSet + domain := "ultradns.phinze.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccTcpoolCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testCfgProbePingRecord, domain, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_tcpool.test-probe-ping-record", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "name", "test-probe-ping-record"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "pool_record", "10.3.0.1"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "agents.0", "DALLAS"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "agents.1", "AMSTERDAM"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "interval", "ONE_MINUTE"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "threshold", "1"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.packets", "15"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.packet_size", "56"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.#", "2"), + + // hashLimits(): lossPercent -> 3375621462 + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.name", "lossPercent"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.warning", "1"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.critical", "2"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.fail", "3"), + + // hashLimits(): total -> 3257917790 + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.name", "total"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.warning", "2"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.critical", "3"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.fail", "4"), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testCfgProbePingPool, domain, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_tcpool.test-probe-ping-pool", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "name", "test-probe-ping-pool"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "agents.0", "DALLAS"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "agents.1", "AMSTERDAM"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "interval", "ONE_MINUTE"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "threshold", "1"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.packets", "15"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.packet_size", "56"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.#", "2"), + + // hashLimits(): lossPercent -> 3375621462 + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.name", "lossPercent"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.warning", "1"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.critical", "2"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3375621462.fail", "3"), + + // hashLimits(): total -> 3257917790 + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.name", "total"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.warning", "2"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.critical", "3"), + resource.TestCheckResourceAttr("ultradns_probe_ping.it", "ping_probe.0.limit.3257917790.fail", "4"), + ), + }, + }, + }) +} + +const testCfgProbePingRecord = ` +resource "ultradns_tcpool" "test-probe-ping-record" { + zone = "%s" + name = "test-probe-ping-record" + + ttl = 30 + description = "traffic controller pool with probes" + + run_probes = true + act_on_probes = true + max_to_lb = 2 + + rdata { + host = "10.3.0.1" + + state = "NORMAL" + run_probes = true + priority = 1 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + rdata { + host = "10.3.0.2" + + state = "NORMAL" + run_probes = true + priority = 2 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + backup_record_rdata = "10.3.0.3" +} + +resource "ultradns_probe_ping" "it" { + zone = "%s" + name = "test-probe-ping-record" + + pool_record = "10.3.0.1" + + agents = ["DALLAS", "AMSTERDAM"] + + interval = "ONE_MINUTE" + threshold = 1 + + ping_probe { + packets = 15 + packet_size = 56 + + limit { + name = "lossPercent" + warning = 1 + critical = 2 + fail = 3 + } + + limit { + name = "total" + warning = 2 + critical = 3 + fail = 4 + } + } + + depends_on = ["ultradns_tcpool.test-probe-ping-record"] +} +` + +const testCfgProbePingPool = ` +resource "ultradns_tcpool" "test-probe-ping-pool" { + zone = "%s" + name = "test-probe-ping-pool" + + ttl = 30 + description = "traffic controller pool with probes" + + run_probes = true + act_on_probes = true + max_to_lb = 2 + + rdata { + host = "10.3.0.1" + + state = "NORMAL" + run_probes = true + priority = 1 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + rdata { + host = "10.3.0.2" + + state = "NORMAL" + run_probes = true + priority = 2 + failover_delay = 0 + threshold = 1 + weight = 2 + } + + backup_record_rdata = "10.3.0.3" +} + +resource "ultradns_probe_ping" "it" { + zone = "%s" + name = "test-probe-ping-pool" + + agents = ["DALLAS", "AMSTERDAM"] + + interval = "ONE_MINUTE" + threshold = 1 + + ping_probe { + packets = 15 + packet_size = 56 + + limit { + name = "lossPercent" + warning = 1 + critical = 2 + fail = 3 + } + + limit { + name = "total" + warning = 2 + critical = 3 + fail = 4 + } + } + + depends_on = ["ultradns_tcpool.test-probe-ping-pool"] +} +` diff --git a/builtin/providers/ultradns/resource_ultradns_record.go b/builtin/providers/ultradns/resource_ultradns_record.go index 6dff2e127..4d4181c3a 100644 --- a/builtin/providers/ultradns/resource_ultradns_record.go +++ b/builtin/providers/ultradns/resource_ultradns_record.go @@ -1,6 +1,7 @@ package ultradns import ( + "encoding/json" "fmt" "log" "strconv" @@ -10,18 +11,11 @@ import ( "github.com/hashicorp/terraform/helper/schema" ) -type rRSetResource struct { - OwnerName string - RRType string - RData []string - TTL int - Profile *udnssdk.StringProfile - Zone string -} - func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) { r := rRSetResource{} + // TODO: return error if required attributes aren't ok + if attr, ok := d.GetOk("name"); ok { r.OwnerName = attr.(string) } @@ -35,7 +29,7 @@ func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) { } if attr, ok := d.GetOk("rdata"); ok { - rdata := attr.([]interface{}) + rdata := attr.(*schema.Set).List() r.RData = make([]string, len(rdata)) for i, j := range rdata { r.RData[i] = j.(string) @@ -49,33 +43,30 @@ func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) { return r, nil } -func (r rRSetResource) RRSetKey() udnssdk.RRSetKey { - return udnssdk.RRSetKey{ - Zone: r.Zone, - Type: r.RRType, - Name: r.OwnerName, - } -} - -func (r rRSetResource) RRSet() udnssdk.RRSet { - return udnssdk.RRSet{ - OwnerName: r.OwnerName, - RRType: r.RRType, - RData: r.RData, - TTL: r.TTL, - } -} - -func (r rRSetResource) ID() string { - return fmt.Sprintf("%s.%s", r.OwnerName, r.Zone) -} - func populateResourceDataFromRRSet(r udnssdk.RRSet, d *schema.ResourceData) error { zone := d.Get("zone") + typ := d.Get("type") // ttl d.Set("ttl", r.TTL) // rdata - err := d.Set("rdata", r.RData) + rdata := r.RData + + // UltraDNS API returns answers double-encoded like JSON, so we must decode. This is their bug. + if typ == "TXT" { + rdata = make([]string, len(r.RData)) + for i := range r.RData { + var s string + err := json.Unmarshal([]byte(r.RData[i]), &s) + if err != nil { + log.Printf("[INFO] TXT answer parse error: %+v", err) + s = r.RData[i] + } + rdata[i] = s + + } + } + + err := d.Set("rdata", makeSetFromStrings(rdata)) if err != nil { return fmt.Errorf("ultradns_record.rdata set failed: %#v", err) } @@ -92,7 +83,7 @@ func populateResourceDataFromRRSet(r udnssdk.RRSet, d *schema.ResourceData) erro return nil } -func resourceUltraDNSRecord() *schema.Resource { +func resourceUltradnsRecord() *schema.Resource { return &schema.Resource{ Create: resourceUltraDNSRecordCreate, Read: resourceUltraDNSRecordRead, @@ -117,7 +108,8 @@ func resourceUltraDNSRecord() *schema.Resource { ForceNew: true, }, "rdata": &schema.Schema{ - Type: schema.TypeList, + Type: schema.TypeSet, + Set: schema.HashString, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, }, @@ -136,6 +128,8 @@ func resourceUltraDNSRecord() *schema.Resource { } } +// CRUD Operations + func resourceUltraDNSRecordCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*udnssdk.Client) @@ -144,14 +138,14 @@ func resourceUltraDNSRecordCreate(d *schema.ResourceData, meta interface{}) erro return err } - log.Printf("[INFO] ultradns_record create: %#v", r.RRSet()) + log.Printf("[INFO] ultradns_record create: %+v", r) _, err = client.RRSets.Create(r.RRSetKey(), r.RRSet()) if err != nil { - return fmt.Errorf("Failed to create UltraDNS RRSet: %s", err) + return fmt.Errorf("create failed: %v", err) } d.SetId(r.ID()) - log.Printf("[INFO] ultradns_record.id: %s", d.Id()) + log.Printf("[INFO] ultradns_record.id: %v", d.Id()) return resourceUltraDNSRecordRead(d, meta) } @@ -174,10 +168,10 @@ func resourceUltraDNSRecordRead(d *schema.ResourceData, meta interface{}) error d.SetId("") return nil } - return fmt.Errorf("ultradns_record not found: %s", err) + return fmt.Errorf("not found: %v", err) } } - return fmt.Errorf("ultradns_record not found: %s", err) + return fmt.Errorf("not found: %v", err) } rec := rrsets[0] return populateResourceDataFromRRSet(rec, d) @@ -191,10 +185,10 @@ func resourceUltraDNSRecordUpdate(d *schema.ResourceData, meta interface{}) erro return err } - log.Printf("[INFO] ultradns_record update: %#v", r.RRSet()) + log.Printf("[INFO] ultradns_record update: %+v", r) _, err = client.RRSets.Update(r.RRSetKey(), r.RRSet()) if err != nil { - return fmt.Errorf("ultradns_record update failed: %s", err) + return fmt.Errorf("update failed: %v", err) } return resourceUltraDNSRecordRead(d, meta) @@ -208,11 +202,13 @@ func resourceUltraDNSRecordDelete(d *schema.ResourceData, meta interface{}) erro return err } - log.Printf("[INFO] ultradns_record delete: %#v", r.RRSet()) + log.Printf("[INFO] ultradns_record delete: %+v", r) _, err = client.RRSets.Delete(r.RRSetKey()) if err != nil { - return fmt.Errorf("ultradns_record delete failed: %s", err) + return fmt.Errorf("delete failed: %v", err) } return nil } + +// Conversion helper functions diff --git a/builtin/providers/ultradns/resource_ultradns_record_test.go b/builtin/providers/ultradns/resource_ultradns_record_test.go index 159775025..56073b298 100644 --- a/builtin/providers/ultradns/resource_ultradns_record_test.go +++ b/builtin/providers/ultradns/resource_ultradns_record_test.go @@ -2,7 +2,6 @@ package ultradns import ( "fmt" - "os" "testing" "github.com/Ensighten/udnssdk" @@ -10,72 +9,74 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func TestAccUltraDNSRecord_Basic(t *testing.T) { +func TestAccUltradnsRecord(t *testing.T) { var record udnssdk.RRSet - domain := os.Getenv("ULTRADNS_DOMAIN") + // domain := os.Getenv("ULTRADNS_DOMAIN") + domain := "ultradns.phinze.com" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckUltraDNSRecordDestroy, + CheckDestroy: testAccRecordCheckDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigBasic, domain), + Config: fmt.Sprintf(testCfgRecordMinimal, domain), Check: resource.ComposeTestCheckFunc( - testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), - testAccCheckUltraDNSRecordAttributes(&record), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "name", "terraform"), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "zone", domain), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "rdata.0", "192.168.0.10"), + testAccCheckUltradnsRecordExists("ultradns_record.it", &record), + resource.TestCheckResourceAttr("ultradns_record.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_record.it", "name", "test-record"), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3994963683", "10.5.0.1"), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testCfgRecordMinimal, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_record.it", &record), + resource.TestCheckResourceAttr("ultradns_record.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_record.it", "name", "test-record"), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3994963683", "10.5.0.1"), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testCfgRecordUpdated, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_record.it", &record), + resource.TestCheckResourceAttr("ultradns_record.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_record.it", "name", "test-record"), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.1998004057", "10.5.0.2"), ), }, }, }) } -func TestAccUltraDNSRecord_Updated(t *testing.T) { +func TestAccUltradnsRecordTXT(t *testing.T) { var record udnssdk.RRSet - domain := os.Getenv("ULTRADNS_DOMAIN") + // domain := os.Getenv("ULTRADNS_DOMAIN") + domain := "ultradns.phinze.com" resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, - CheckDestroy: testAccCheckUltraDNSRecordDestroy, + CheckDestroy: testAccRecordCheckDestroy, Steps: []resource.TestStep{ resource.TestStep{ - Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigBasic, domain), + Config: fmt.Sprintf(testCfgRecordTXTMinimal, domain), Check: resource.ComposeTestCheckFunc( - testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), - testAccCheckUltraDNSRecordAttributes(&record), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "name", "terraform"), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "zone", domain), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "rdata.0", "192.168.0.10"), - ), - }, - resource.TestStep{ - Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigNewValue, domain), - Check: resource.ComposeTestCheckFunc( - testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), - testAccCheckUltraDNSRecordAttributesUpdated(&record), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "name", "terraform"), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "zone", domain), - resource.TestCheckResourceAttr( - "ultradns_record.foobar", "rdata.0", "192.168.0.11"), + testAccCheckUltradnsRecordExists("ultradns_record.it", &record), + resource.TestCheckResourceAttr("ultradns_record.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_record.it", "name", "test-record-txt"), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.1447448707", "simple answer"), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3337444205", "backslash answer \\"), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3135730072", "quote answer \""), + resource.TestCheckResourceAttr("ultradns_record.it", "rdata.126343430", "complex answer \\ \""), ), }, }, }) } -func testAccCheckUltraDNSRecordDestroy(s *terraform.State) error { +func testAccRecordCheckDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*udnssdk.Client) for _, rs := range s.RootModule().Resources { @@ -99,79 +100,40 @@ func testAccCheckUltraDNSRecordDestroy(s *terraform.State) error { return nil } -func testAccCheckUltraDNSRecordAttributes(record *udnssdk.RRSet) resource.TestCheckFunc { - return func(s *terraform.State) error { +const testCfgRecordMinimal = ` +resource "ultradns_record" "it" { + zone = "%s" + name = "test-record" - if record.RData[0] != "192.168.0.10" { - return fmt.Errorf("Bad content: %v", record.RData) - } - - return nil - } + rdata = ["10.5.0.1"] + type = "A" + ttl = 3600 } +` -func testAccCheckUltraDNSRecordAttributesUpdated(record *udnssdk.RRSet) resource.TestCheckFunc { - return func(s *terraform.State) error { +const testCfgRecordUpdated = ` +resource "ultradns_record" "it" { + zone = "%s" + name = "test-record" - if record.RData[0] != "192.168.0.11" { - return fmt.Errorf("Bad content: %v", record.RData) - } - - return nil - } + rdata = ["10.5.0.2"] + type = "A" + ttl = 3600 } +` -func testAccCheckUltraDNSRecordExists(n string, record *udnssdk.RRSet) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] +const testCfgRecordTXTMinimal = ` +resource "ultradns_record" "it" { + zone = "%s" + name = "test-record-txt" - if !ok { - return fmt.Errorf("Not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No Record ID is set") - } - - client := testAccProvider.Meta().(*udnssdk.Client) - k := udnssdk.RRSetKey{ - Zone: rs.Primary.Attributes["zone"], - Name: rs.Primary.Attributes["name"], - Type: rs.Primary.Attributes["type"], - } - - foundRecord, err := client.RRSets.Select(k) - - if err != nil { - return err - } - - if foundRecord[0].OwnerName != rs.Primary.Attributes["hostname"] { - return fmt.Errorf("Record not found: %+v,\n %+v\n", foundRecord, rs.Primary.Attributes) - } - - *record = foundRecord[0] - - return nil - } + rdata = [ + "simple answer", + "backslash answer \\", + "quote answer \"", + "complex answer \\ \"", + ] + type = "TXT" + ttl = 3600 } - -const testAccCheckUltraDNSRecordConfigBasic = ` -resource "ultradns_record" "foobar" { - zone = "%s" - - name = "terraform" - rdata = [ "192.168.0.10" ] - type = "A" - ttl = 3600 -}` - -const testAccCheckUltraDNSRecordConfigNewValue = ` -resource "ultradns_record" "foobar" { - zone = "%s" - - name = "terraform" - rdata = [ "192.168.0.11" ] - type = "A" - ttl = 3600 -}` +` diff --git a/builtin/providers/ultradns/resource_ultradns_tcpool.go b/builtin/providers/ultradns/resource_ultradns_tcpool.go new file mode 100644 index 000000000..a497a65b6 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_tcpool.go @@ -0,0 +1,331 @@ +package ultradns + +import ( + "fmt" + "log" + "strings" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceUltradnsTcpool() *schema.Resource { + return &schema.Resource{ + Create: resourceUltradnsTcpoolCreate, + Read: resourceUltradnsTcpoolRead, + Update: resourceUltradnsTcpoolUpdate, + Delete: resourceUltradnsTcpoolDelete, + + Schema: map[string]*schema.Schema{ + // Required + "zone": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Required: true, + // 0-255 char + }, + "rdata": &schema.Schema{ + Type: schema.TypeSet, + Set: hashRdatas, + Required: true, + // Valid: len(rdataInfo) == len(rdata) + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // Required + "host": &schema.Schema{ + Type: schema.TypeString, + Required: true, + }, + // Optional + "failover_delay": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 0, + // Valid: 0-30 + // Units: Minutes + }, + "priority": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + "run_probes": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "state": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "NORMAL", + }, + "threshold": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 1, + }, + "weight": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 2, + // Valid: i%2 == 0 && 2 <= i <= 100 + }, + }, + }, + }, + // Optional + "ttl": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 3600, + }, + "run_probes": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "act_on_probes": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "max_to_lb": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + // Valid: 0 <= i <= len(rdata) + }, + "backup_record_rdata": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + // Valid: IPv4 address or CNAME + }, + "backup_record_failover_delay": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + // Valid: 0-30 + // Units: Minutes + }, + // Computed + "hostname": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +// CRUD Operations + +func resourceUltradnsTcpoolCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResourceFromTcpool(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_tcpool create: %#v", r) + _, err = client.RRSets.Create(r.RRSetKey(), r.RRSet()) + if err != nil { + return fmt.Errorf("create failed: %#v -> %v", r, err) + } + + d.SetId(r.ID()) + log.Printf("[INFO] ultradns_tcpool.id: %v", d.Id()) + + return resourceUltradnsTcpoolRead(d, meta) +} + +func resourceUltradnsTcpoolRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + rr, err := newRRSetResourceFromTcpool(d) + if err != nil { + return err + } + + rrsets, err := client.RRSets.Select(rr.RRSetKey()) + if err != nil { + uderr, ok := err.(*udnssdk.ErrorResponseList) + if ok { + for _, resps := range uderr.Responses { + // 70002 means Records Not Found + if resps.ErrorCode == 70002 { + d.SetId("") + return nil + } + return fmt.Errorf("resource not found: %v", err) + } + } + return fmt.Errorf("resource not found: %v", err) + } + + r := rrsets[0] + + zone := d.Get("zone") + // ttl + d.Set("ttl", r.TTL) + // hostname + if r.OwnerName == "" { + d.Set("hostname", zone) + } else { + if strings.HasSuffix(r.OwnerName, ".") { + d.Set("hostname", r.OwnerName) + } else { + d.Set("hostname", fmt.Sprintf("%s.%s", r.OwnerName, zone)) + } + } + + // And now... the Profile! + if r.Profile == nil { + return fmt.Errorf("RRSet.profile missing: invalid TCPool schema in: %#v", r) + } + p, err := r.Profile.TCPoolProfile() + if err != nil { + return fmt.Errorf("RRSet.profile could not be unmarshalled: %v\n", err) + } + + // Set simple values + d.Set("description", p.Description) + d.Set("run_probes", p.RunProbes) + d.Set("act_on_probes", p.ActOnProbes) + d.Set("max_to_lb", p.MaxToLB) + if p.BackupRecord != nil { + d.Set("backup_record_rdata", p.BackupRecord.RData) + d.Set("backup_record_failover_delay", p.BackupRecord.FailoverDelay) + } + + // TODO: rigorously test this to see if we can remove the error handling + err = d.Set("rdata", makeSetFromRdata(r.RData, p.RDataInfo)) + if err != nil { + return fmt.Errorf("rdata set failed: %#v", err) + } + return nil +} + +func resourceUltradnsTcpoolUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResourceFromTcpool(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_tcpool update: %+v", r) + _, err = client.RRSets.Update(r.RRSetKey(), r.RRSet()) + if err != nil { + return fmt.Errorf("resource update failed: %v", err) + } + + return resourceUltradnsTcpoolRead(d, meta) +} + +func resourceUltradnsTcpoolDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResourceFromTcpool(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_tcpool delete: %+v", r) + _, err = client.RRSets.Delete(r.RRSetKey()) + if err != nil { + return fmt.Errorf("resource delete failed: %v", err) + } + + return nil +} + +// Resource Helpers + +func newRRSetResourceFromTcpool(d *schema.ResourceData) (rRSetResource, error) { + rDataRaw := d.Get("rdata").(*schema.Set).List() + r := rRSetResource{ + // "The only valid rrtype value for SiteBacker or Traffic Controller pools is A" + // per https://portal.ultradns.com/static/docs/REST-API_User_Guide.pdf + RRType: "A", + Zone: d.Get("zone").(string), + OwnerName: d.Get("name").(string), + TTL: d.Get("ttl").(int), + RData: unzipRdataHosts(rDataRaw), + } + + profile := udnssdk.TCPoolProfile{ + Context: udnssdk.TCPoolSchema, + ActOnProbes: d.Get("act_on_probes").(bool), + Description: d.Get("description").(string), + MaxToLB: d.Get("max_to_lb").(int), + RunProbes: d.Get("run_probes").(bool), + RDataInfo: unzipRdataInfos(rDataRaw), + } + + // Only send BackupRecord if present + br := d.Get("backup_record_rdata").(string) + if br != "" { + profile.BackupRecord = &udnssdk.BackupRecord{ + RData: d.Get("backup_record_rdata").(string), + FailoverDelay: d.Get("backup_record_failover_delay").(int), + } + } + + rp := profile.RawProfile() + r.Profile = rp + + return r, nil +} + +func unzipRdataInfos(configured []interface{}) []udnssdk.SBRDataInfo { + rdataInfos := make([]udnssdk.SBRDataInfo, 0, len(configured)) + for _, rRaw := range configured { + data := rRaw.(map[string]interface{}) + r := udnssdk.SBRDataInfo{ + FailoverDelay: data["failover_delay"].(int), + Priority: data["priority"].(int), + RunProbes: data["run_probes"].(bool), + State: data["state"].(string), + Threshold: data["threshold"].(int), + Weight: data["weight"].(int), + } + rdataInfos = append(rdataInfos, r) + } + return rdataInfos +} + +// collate and zip RData and RDataInfo into []map[string]interface{} +func zipRData(rds []string, rdis []udnssdk.SBRDataInfo) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(rds)) + for i, rdi := range rdis { + r := map[string]interface{}{ + "host": rds[i], + "failover_delay": rdi.FailoverDelay, + "priority": rdi.Priority, + "run_probes": rdi.RunProbes, + "state": rdi.State, + "threshold": rdi.Threshold, + "weight": rdi.Weight, + } + result = append(result, r) + } + return result +} + +// makeSetFromRdatas encodes an array of Rdata into a +// *schema.Set in the appropriate structure for the schema +func makeSetFromRdata(rds []string, rdis []udnssdk.SBRDataInfo) *schema.Set { + s := &schema.Set{F: hashRdatas} + rs := zipRData(rds, rdis) + for _, r := range rs { + s.Add(r) + } + return s +} diff --git a/builtin/providers/ultradns/resource_ultradns_tcpool_test.go b/builtin/providers/ultradns/resource_ultradns_tcpool_test.go new file mode 100644 index 000000000..72c7e2a42 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_tcpool_test.go @@ -0,0 +1,156 @@ +package ultradns + +import ( + "fmt" + "testing" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccUltradnsTcpool(t *testing.T) { + var record udnssdk.RRSet + domain := "ultradns.phinze.com" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccTcpoolCheckDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testCfgTcpoolMinimal, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_tcpool.it", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_tcpool.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "name", "test-tcpool-minimal"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "ttl", "300"), + + // hashRdatas(): 10.6.0.1 -> 2847814707 + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.host", "10.6.0.1"), + // Defaults + resource.TestCheckResourceAttr("ultradns_tcpool.it", "act_on_probes", "true"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "description", "Minimal TC Pool"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "max_to_lb", "0"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "run_probes", "true"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.failover_delay", "0"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.priority", "1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.run_probes", "true"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.state", "NORMAL"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.threshold", "1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2847814707.weight", "2"), + // Generated + resource.TestCheckResourceAttr("ultradns_tcpool.it", "id", "test-tcpool-minimal.ultradns.phinze.com"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "hostname", "test-tcpool-minimal.ultradns.phinze.com."), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testCfgTcpoolMaximal, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltradnsRecordExists("ultradns_tcpool.it", &record), + // Specified + resource.TestCheckResourceAttr("ultradns_tcpool.it", "zone", domain), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "name", "test-tcpool-maximal"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "ttl", "300"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "description", "traffic controller pool with all settings tuned"), + + resource.TestCheckResourceAttr("ultradns_tcpool.it", "act_on_probes", "false"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "max_to_lb", "2"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "run_probes", "false"), + + // hashRdatas(): 10.6.1.1 -> 2826722820 + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.host", "10.6.1.1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.failover_delay", "30"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.priority", "1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.run_probes", "true"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.state", "ACTIVE"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.threshold", "1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.2826722820.weight", "2"), + + // hashRdatas(): 10.6.1.2 -> 829755326 + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.host", "10.6.1.2"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.failover_delay", "30"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.priority", "2"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.run_probes", "true"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.state", "INACTIVE"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.threshold", "1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.829755326.weight", "4"), + + // hashRdatas(): 10.6.1.3 -> 1181892392 + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.host", "10.6.1.3"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.failover_delay", "30"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.priority", "3"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.run_probes", "false"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.state", "NORMAL"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.threshold", "1"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "rdata.1181892392.weight", "8"), + // Generated + resource.TestCheckResourceAttr("ultradns_tcpool.it", "id", "test-tcpool-maximal.ultradns.phinze.com"), + resource.TestCheckResourceAttr("ultradns_tcpool.it", "hostname", "test-tcpool-maximal.ultradns.phinze.com."), + ), + }, + }, + }) +} + +const testCfgTcpoolMinimal = ` +resource "ultradns_tcpool" "it" { + zone = "%s" + name = "test-tcpool-minimal" + ttl = 300 + description = "Minimal TC Pool" + + rdata { + host = "10.6.0.1" + } +} +` + +const testCfgTcpoolMaximal = ` +resource "ultradns_tcpool" "it" { + zone = "%s" + name = "test-tcpool-maximal" + ttl = 300 + description = "traffic controller pool with all settings tuned" + + act_on_probes = false + max_to_lb = 2 + run_probes = false + + rdata { + host = "10.6.1.1" + + failover_delay = 30 + priority = 1 + run_probes = true + state = "ACTIVE" + threshold = 1 + weight = 2 + } + + rdata { + host = "10.6.1.2" + + failover_delay = 30 + priority = 2 + run_probes = true + state = "INACTIVE" + threshold = 1 + weight = 4 + } + + rdata { + host = "10.6.1.3" + + failover_delay = 30 + priority = 3 + run_probes = false + state = "NORMAL" + threshold = 1 + weight = 8 + } + + backup_record_rdata = "10.6.1.4" + backup_record_failover_delay = 30 +} +` diff --git a/vendor/github.com/Ensighten/udnssdk/CHANGELOG.md b/vendor/github.com/Ensighten/udnssdk/CHANGELOG.md new file mode 100644 index 000000000..83afee81e --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/CHANGELOG.md @@ -0,0 +1,47 @@ +# Change Log +All notable changes to this project will be documented in this file. +This project adheres to [Semantic Versioning](http://semver.org/). + +## [Unreleased] + +## [1.2.1] - 2016-06-13 +### Fixed +* `omitempty` tags fixed for `ProbeInfoDTO.PoolRecord` & `ProbeInfoDTO.ID` +* Check `*http.Response` values for nil before access + +## [1.2.0] - 2016-06-09 +### Added +* Add probe detail serialization helpers + +### Changed +* Flatten udnssdk.Response to mere http.Response +* Extract self-contained passwordcredentials oauth2 TokenSource +* Change ProbeTypes to constants + +## [1.1.1] - 2016-05-27 +### Fixed +* remove terraform tag for `GeoInfo.Codes` + +## [1.1.0] - 2016-05-27 +### Added +* Add terraform tags to structs to support mapstructure + +### Fixed +* `omitempty` tags fixed for `DirPoolProfile.NoResponse`, `DPRDataInfo.GeoInfo`, `DPRDataInfo.IPInfo`, `IPInfo.Ips` & `GeoInfo.Codes` +* ProbeAlertDataDTO equivalence for times with different locations + +### Changed +* Convert RawProfile to use mapstructure and structs instead of round-tripping through json +* CHANGELOG.md: fix link to v1.0.0 commit history + +## [1.0.0] - 2016-05-11 +### Added +* Support for API endpoints for `RRSets`, `Accounts`, `DirectionalPools`, Traffic Controller Pool `Probes`, `Events`, `Notifications` & `Alerts` +* `Client` wraps common API access including OAuth, deferred tasks and retries + +[Unreleased]: https://github.com/Ensighten/udnssdk/compare/v1.2.1...HEAD +[1.2.1]: https://github.com/Ensighten/udnssdk/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/Ensighten/udnssdk/compare/v1.1.1...v1.2.0 +[1.1.1]: https://github.com/Ensighten/udnssdk/compare/v1.1.0...v1.1.1 +[1.1.0]: https://github.com/Ensighten/udnssdk/compare/v1.0.0...v1.1.0 +[1.0.0]: https://github.com/Ensighten/udnssdk/compare/v0.0.0...v1.0.0 diff --git a/vendor/github.com/Ensighten/udnssdk/CONTRIBUTING.md b/vendor/github.com/Ensighten/udnssdk/CONTRIBUTING.md new file mode 100644 index 000000000..50702d75f --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/CONTRIBUTING.md @@ -0,0 +1,112 @@ +# Contributing + +Want to contribute? Up-to-date pointers should be at: + + +Got an idea? Something smell wrong? Cause you pain? Or lost seconds of +your life you'll never get back? + +All contributions are welcome: ideas, patches, documentation, bug +reports, complaints, and even something you drew up on a napkin. + +Programming is not a required skill. Whatever you've seen about open +source and maintainers or community members saying "send patches or die": +you will not see that here. + +It is more important to me that you are able to contribute. If you +haven't got time to do anything else, just email me and I'll try to +help: . + +I promise to help guide this project with these principles: + +- Community: If a newbie has a bad time, it's a bug. +- Software: Make it work, then make it right, then make it fast. +- Technology: If it doesn't do a thing today, we can make it do + it tomorrow. + +Here are some ways you can be part of the community: + +## Something not working? Found a Bug? + +Find something that doesn't feel quite right? Here are 5 steps to +getting it fixed! + +### Check your version + +To make sure you're not wasting your time, you should be using the +latest version before you file your bug. First of all, you should +download the latest revision to be sure you are up to date. If you've +done this and you still experience the bug, go ahead to the next step. + +### Search our [issues] + +Now that you have the latest version and still think you've found a bug, +search through issues first to see if anyone else has already filed it. +This step is very important! If you find that someone has filed your bug +already, please go to the next step anyway, but instead of filing a new +bug, comment on the one you've found. If you can't find your bug in +issues, go to the next step. + +### Create a Github account https://github.com/join + +You will need to create a Github account to be able to report bugs (and +to comment on them). If you have registered, proceed to the next step. + +### File the bug! + +Now you are ready to file a bug. The [Writing a Good Bug Report] +document gives some tips about the most useful information to include in +bug reports. The better your bug report, the higher the chance that your +bug will be addressed (and possibly fixed) quickly! + +### What happens next? + +Once your bug is filed, you will receive email when it is updated at +each stage in the bug life cycle. After the bug is considered fixed, you +may be asked to download the latest revision and confirm that the fix +works for you. + +## Submitting patches + +1. [Fork the repository.] +2. [Create a topic branch.] +3. Add specs for your unimplemented feature or bug fix. +4. Run `script/test`. If your specs pass, return to step 3. +5. Implement your feature or bug fix. +6. Run `script/test`. If your specs fail, return to step 5. +7. Add, commit (say *why* the changes were made, we can look at the + diff to see *how* they were made.), and push your changes. For + documentation-only fixes, please add `[ci skip]` to your commit + message to avoid needless CI builds. +8. [Submit a patch.] + +## Setting up a local dev environment + +For those of you who do want to contribute with code, we've tried to +make it easy to get started. You can install all dependencies and tools +with: + + script/bootstrap + +Good luck! + +## Style guide + +There are great style guides out there, we don't need to reinvent the +wheel. Here are ones we like: + +- `go`: https://code.google.com/p/go-wiki/wiki/CodeReviewComments +- `sh`: http://google.github.io/styleguide/shell.xml +- `ruby`: https://github.com/bbatsov/ruby-style-guide +- `python`: https://www.python.org/dev/peps/pep-0008/ + +For some things, the best we've got is a decent formatting tool: + +- `markdown`: `pandoc --to=markdown --reference-links --atx-headers --columns 72` +- `json`: `jq .` + + [issues]: https://github.com/Ensighten/udnssdk/issues + [Writing a Good Bug Report]: http://www.webkit.org/quality/bugwriting.html + [Fork the repository.]: https://help.github.com/articles/fork-a-repo + [Create a topic branch.]: http://learn.github.com/p/branching.html + [Submit a patch.]: https://help.github.com/articles/using-pull-requests diff --git a/vendor/github.com/Ensighten/udnssdk/LICENSE b/vendor/github.com/Ensighten/udnssdk/LICENSE new file mode 100644 index 000000000..cffb54f9f --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015, 2016 Ensighten + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/github.com/Ensighten/udnssdk/account.go b/vendor/github.com/Ensighten/udnssdk/account.go index 9d22bfdaa..9476c4d77 100644 --- a/vendor/github.com/Ensighten/udnssdk/account.go +++ b/vendor/github.com/Ensighten/udnssdk/account.go @@ -2,6 +2,7 @@ package udnssdk import ( "fmt" + "net/http" ) // AccountsService provides access to account resources @@ -43,7 +44,7 @@ func AccountsURI() string { } // Select requests all Accounts of user -func (s *AccountsService) Select() ([]Account, *Response, error) { +func (s *AccountsService) Select() ([]Account, *http.Response, error) { var ald AccountListDTO res, err := s.client.get(AccountsURI(), &ald) @@ -55,13 +56,13 @@ func (s *AccountsService) Select() ([]Account, *Response, error) { } // Find requests an Account by AccountKey -func (s *AccountsService) Find(k AccountKey) (Account, *Response, error) { +func (s *AccountsService) Find(k AccountKey) (Account, *http.Response, error) { var t Account res, err := s.client.get(k.URI(), &t) return t, res, err } // Delete requests deletion of an Account by AccountKey -func (s *AccountsService) Delete(k AccountKey) (*Response, error) { +func (s *AccountsService) Delete(k AccountKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/alert.go b/vendor/github.com/Ensighten/udnssdk/alert.go index 6a2220410..c092cf6f3 100644 --- a/vendor/github.com/Ensighten/udnssdk/alert.go +++ b/vendor/github.com/Ensighten/udnssdk/alert.go @@ -2,6 +2,7 @@ package udnssdk import ( "log" + "net/http" "time" ) @@ -21,6 +22,17 @@ type ProbeAlertDataDTO struct { Status string `json:"status"` } +// Equal compares to another ProbeAlertDataDTO, but uses time.Equals to compare semantic equvalance of AlertDate +func (a ProbeAlertDataDTO) Equal(b ProbeAlertDataDTO) bool { + return a.PoolRecord == b.PoolRecord && + a.ProbeType == b.ProbeType && + a.ProbeStatus == b.ProbeStatus && + a.AlertDate.Equal(b.AlertDate) && + a.FailoverOccured == b.FailoverOccured && + a.OwnerName == b.OwnerName && + a.Status == b.Status +} + // ProbeAlertDataListDTO wraps the response for an index of probe alerts type ProbeAlertDataListDTO struct { Alerts []ProbeAlertDataDTO `json:"alerts"` @@ -42,7 +54,7 @@ func (s *AlertsService) Select(k RRSetKey) ([]ProbeAlertDataDTO, error) { for { reqAlerts, ri, res, err := s.SelectWithOffset(k, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -65,7 +77,7 @@ func (s *AlertsService) Select(k RRSetKey) ([]ProbeAlertDataDTO, error) { } // SelectWithOffset returns the probe alerts with a RRSetKey, accepting an offset -func (s *AlertsService) SelectWithOffset(k RRSetKey, offset int) ([]ProbeAlertDataDTO, ResultInfo, *Response, error) { +func (s *AlertsService) SelectWithOffset(k RRSetKey, offset int) ([]ProbeAlertDataDTO, ResultInfo, *http.Response, error) { var ald ProbeAlertDataListDTO uri := k.AlertsQueryURI(offset) diff --git a/vendor/github.com/Ensighten/udnssdk/common.go b/vendor/github.com/Ensighten/udnssdk/common.go index 86e8ec10e..5de5f9ac3 100644 --- a/vendor/github.com/Ensighten/udnssdk/common.go +++ b/vendor/github.com/Ensighten/udnssdk/common.go @@ -1,7 +1,9 @@ package udnssdk +import "net/http" + // GetResultByURI just requests a URI -func (c *Client) GetResultByURI(uri string) (*Response, error) { +func (c *Client) GetResultByURI(uri string) (*http.Response, error) { req, err := c.NewRequest("GET", uri, nil) if err != nil { return nil, err @@ -9,7 +11,7 @@ func (c *Client) GetResultByURI(uri string) (*Response, error) { res, err := c.HTTPClient.Do(req) if err != nil { - return &Response{Response: res}, err + return res, err } - return &Response{Response: res}, err + return res, err } diff --git a/vendor/github.com/Ensighten/udnssdk/directional_pool.go b/vendor/github.com/Ensighten/udnssdk/directional_pool.go index e19af49fd..85569ee8c 100644 --- a/vendor/github.com/Ensighten/udnssdk/directional_pool.go +++ b/vendor/github.com/Ensighten/udnssdk/directional_pool.go @@ -3,6 +3,7 @@ package udnssdk import ( "fmt" "log" + "net/http" "time" ) @@ -28,10 +29,10 @@ type AccountLevelGeoDirectionalGroupDTO struct { // IPAddrDTO wraps an IP address range or CIDR block type IPAddrDTO struct { - Start string `json:"start,omitempty"` - End string `json:"end,omitempty"` - CIDR string `json:"cidr,omitempty"` - Address string `json:"address,omitempty"` + Start string `json:"start,omitempty" terraform:"start"` + End string `json:"end,omitempty" terraform:"end"` + CIDR string `json:"cidr,omitempty" terraform:"cidr"` + Address string `json:"address,omitempty" terraform:"address"` } // AccountLevelIPDirectionalGroupDTO wraps an account-level, IP directional-group response @@ -146,7 +147,7 @@ func (s *GeoDirectionalPoolsService) Select(k GeoDirectionalPoolKey, query strin for { reqDtos, ri, res, err := s.SelectWithOffset(k, query, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -169,7 +170,7 @@ func (s *GeoDirectionalPoolsService) Select(k GeoDirectionalPoolKey, query strin } // SelectWithOffset requests list of geo directional-pools, by query & account, and an offset, returning the directional-group, the list-metadata, the actual response, or an error -func (s *GeoDirectionalPoolsService) SelectWithOffset(k GeoDirectionalPoolKey, query string, offset int) ([]AccountLevelGeoDirectionalGroupDTO, ResultInfo, *Response, error) { +func (s *GeoDirectionalPoolsService) SelectWithOffset(k GeoDirectionalPoolKey, query string, offset int) ([]AccountLevelGeoDirectionalGroupDTO, ResultInfo, *http.Response, error) { var tld AccountLevelGeoDirectionalGroupListDTO res, err := s.client.get(k.QueryURI(query, offset), &tld) @@ -182,24 +183,24 @@ func (s *GeoDirectionalPoolsService) SelectWithOffset(k GeoDirectionalPoolKey, q } // Find requests a geo directional-pool by name & account -func (s *GeoDirectionalPoolsService) Find(k GeoDirectionalPoolKey) (AccountLevelGeoDirectionalGroupDTO, *Response, error) { +func (s *GeoDirectionalPoolsService) Find(k GeoDirectionalPoolKey) (AccountLevelGeoDirectionalGroupDTO, *http.Response, error) { var t AccountLevelGeoDirectionalGroupDTO res, err := s.client.get(k.URI(), &t) return t, res, err } // Create requests creation of a DirectionalPool by DirectionalPoolKey given a directional-pool -func (s *GeoDirectionalPoolsService) Create(k GeoDirectionalPoolKey, val interface{}) (*Response, error) { +func (s *GeoDirectionalPoolsService) Create(k GeoDirectionalPoolKey, val interface{}) (*http.Response, error) { return s.client.post(k.URI(), val, nil) } // Update requests update of a DirectionalPool by DirectionalPoolKey given a directional-pool -func (s *GeoDirectionalPoolsService) Update(k GeoDirectionalPoolKey, val interface{}) (*Response, error) { +func (s *GeoDirectionalPoolsService) Update(k GeoDirectionalPoolKey, val interface{}) (*http.Response, error) { return s.client.put(k.URI(), val, nil) } // Delete requests deletion of a DirectionalPool -func (s *GeoDirectionalPoolsService) Delete(k GeoDirectionalPoolKey) (*Response, error) { +func (s *GeoDirectionalPoolsService) Delete(k GeoDirectionalPoolKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } @@ -247,7 +248,7 @@ func (s *IPDirectionalPoolsService) Select(k IPDirectionalPoolKey, query string) for { reqIPGroups, ri, res, err := s.SelectWithOffset(k, query, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -270,7 +271,7 @@ func (s *IPDirectionalPoolsService) Select(k IPDirectionalPoolKey, query string) } // SelectWithOffset requests all IP directional-pools, by query & account, and an offset, returning the list of IP groups, list metadata & the actual response, or an error -func (s *IPDirectionalPoolsService) SelectWithOffset(k IPDirectionalPoolKey, query string, offset int) ([]AccountLevelIPDirectionalGroupDTO, ResultInfo, *Response, error) { +func (s *IPDirectionalPoolsService) SelectWithOffset(k IPDirectionalPoolKey, query string, offset int) ([]AccountLevelIPDirectionalGroupDTO, ResultInfo, *http.Response, error) { var tld AccountLevelIPDirectionalGroupListDTO res, err := s.client.get(k.QueryURI(query, offset), &tld) @@ -284,23 +285,23 @@ func (s *IPDirectionalPoolsService) SelectWithOffset(k IPDirectionalPoolKey, que } // Find requests a directional-pool by name & account -func (s *IPDirectionalPoolsService) Find(k IPDirectionalPoolKey) (AccountLevelIPDirectionalGroupDTO, *Response, error) { +func (s *IPDirectionalPoolsService) Find(k IPDirectionalPoolKey) (AccountLevelIPDirectionalGroupDTO, *http.Response, error) { var t AccountLevelIPDirectionalGroupDTO res, err := s.client.get(k.URI(), &t) return t, res, err } // Create requests creation of a DirectionalPool by DirectionalPoolKey given a directional-pool -func (s *IPDirectionalPoolsService) Create(k IPDirectionalPoolKey, val interface{}) (*Response, error) { +func (s *IPDirectionalPoolsService) Create(k IPDirectionalPoolKey, val interface{}) (*http.Response, error) { return s.client.post(k.URI(), val, nil) } // Update requests update of a DirectionalPool by DirectionalPoolKey given a directional-pool -func (s *IPDirectionalPoolsService) Update(k IPDirectionalPoolKey, val interface{}) (*Response, error) { +func (s *IPDirectionalPoolsService) Update(k IPDirectionalPoolKey, val interface{}) (*http.Response, error) { return s.client.put(k.URI(), val, nil) } // Delete deletes an directional-pool -func (s *IPDirectionalPoolsService) Delete(k IPDirectionalPoolKey) (*Response, error) { +func (s *IPDirectionalPoolsService) Delete(k IPDirectionalPoolKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/event.go b/vendor/github.com/Ensighten/udnssdk/event.go index c5137ddad..74f88018c 100644 --- a/vendor/github.com/Ensighten/udnssdk/event.go +++ b/vendor/github.com/Ensighten/udnssdk/event.go @@ -3,6 +3,7 @@ package udnssdk import ( "fmt" "log" + "net/http" "time" ) @@ -65,7 +66,7 @@ func (s *EventsService) Select(r RRSetKey, query string) ([]EventInfoDTO, error) for { reqEvents, ri, res, err := s.SelectWithOffset(r, query, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -88,7 +89,7 @@ func (s *EventsService) Select(r RRSetKey, query string) ([]EventInfoDTO, error) } // SelectWithOffset requests list of events by RRSetKey, query and offset, also returning list metadata, the actual response, or an error -func (s *EventsService) SelectWithOffset(r RRSetKey, query string, offset int) ([]EventInfoDTO, ResultInfo, *Response, error) { +func (s *EventsService) SelectWithOffset(r RRSetKey, query string, offset int) ([]EventInfoDTO, ResultInfo, *http.Response, error) { var tld EventInfoListDTO uri := r.EventsQueryURI(query, offset) @@ -102,23 +103,23 @@ func (s *EventsService) SelectWithOffset(r RRSetKey, query string, offset int) ( } // Find requests an event by name, type, zone & guid, also returning the actual response, or an error -func (s *EventsService) Find(e EventKey) (EventInfoDTO, *Response, error) { +func (s *EventsService) Find(e EventKey) (EventInfoDTO, *http.Response, error) { var t EventInfoDTO res, err := s.client.get(e.URI(), &t) return t, res, err } // Create requests creation of an event by RRSetKey, with provided event-info, returning actual response or an error -func (s *EventsService) Create(r RRSetKey, ev EventInfoDTO) (*Response, error) { +func (s *EventsService) Create(r RRSetKey, ev EventInfoDTO) (*http.Response, error) { return s.client.post(r.EventsURI(), ev, nil) } // Update requests update of an event by EventKey, withprovided event-info, returning the actual response or an error -func (s *EventsService) Update(e EventKey, ev EventInfoDTO) (*Response, error) { +func (s *EventsService) Update(e EventKey, ev EventInfoDTO) (*http.Response, error) { return s.client.put(e.URI(), ev, nil) } // Delete requests deletion of an event by EventKey, returning the actual response or an error -func (s *EventsService) Delete(e EventKey) (*Response, error) { +func (s *EventsService) Delete(e EventKey) (*http.Response, error) { return s.client.delete(e.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/notification.go b/vendor/github.com/Ensighten/udnssdk/notification.go index 09dbcc450..484f15c03 100644 --- a/vendor/github.com/Ensighten/udnssdk/notification.go +++ b/vendor/github.com/Ensighten/udnssdk/notification.go @@ -3,6 +3,7 @@ package udnssdk import ( "fmt" "log" + "net/http" "time" ) @@ -60,7 +61,7 @@ func (k NotificationKey) URI() string { } // Select requests all notifications by RRSetKey and optional query, using pagination and error handling -func (s *NotificationsService) Select(k RRSetKey, query string) ([]NotificationDTO, *Response, error) { +func (s *NotificationsService) Select(k RRSetKey, query string) ([]NotificationDTO, *http.Response, error) { // TODO: Sane Configuration for timeouts / retries maxerrs := 5 waittime := 5 * time.Second @@ -73,7 +74,7 @@ func (s *NotificationsService) Select(k RRSetKey, query string) ([]NotificationD for { reqNotifications, ri, res, err := s.SelectWithOffset(k, query, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -96,7 +97,7 @@ func (s *NotificationsService) Select(k RRSetKey, query string) ([]NotificationD } // SelectWithOffset requests list of notifications by RRSetKey, query and offset, also returning list metadata, the actual response, or an error -func (s *NotificationsService) SelectWithOffset(k RRSetKey, query string, offset int) ([]NotificationDTO, ResultInfo, *Response, error) { +func (s *NotificationsService) SelectWithOffset(k RRSetKey, query string, offset int) ([]NotificationDTO, ResultInfo, *http.Response, error) { var tld NotificationListDTO uri := k.NotificationsQueryURI(query, offset) @@ -111,23 +112,23 @@ func (s *NotificationsService) SelectWithOffset(k RRSetKey, query string, offset } // Find requests a notification by NotificationKey,returning the actual response, or an error -func (s *NotificationsService) Find(k NotificationKey) (NotificationDTO, *Response, error) { +func (s *NotificationsService) Find(k NotificationKey) (NotificationDTO, *http.Response, error) { var t NotificationDTO res, err := s.client.get(k.URI(), &t) return t, res, err } // Create requests creation of an event by RRSetKey, with provided NotificationInfoDTO, returning actual response or an error -func (s *NotificationsService) Create(k NotificationKey, n NotificationDTO) (*Response, error) { +func (s *NotificationsService) Create(k NotificationKey, n NotificationDTO) (*http.Response, error) { return s.client.post(k.URI(), n, nil) } // Update requests update of an event by NotificationKey, with provided NotificationInfoDTO, returning the actual response or an error -func (s *NotificationsService) Update(k NotificationKey, n NotificationDTO) (*Response, error) { +func (s *NotificationsService) Update(k NotificationKey, n NotificationDTO) (*http.Response, error) { return s.client.put(k.URI(), n, nil) } // Delete requests deletion of an event by NotificationKey, returning the actual response or an error -func (s *NotificationsService) Delete(k NotificationKey) (*Response, error) { +func (s *NotificationsService) Delete(k NotificationKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/passwordcredentials/passwordcredentials.go b/vendor/github.com/Ensighten/udnssdk/passwordcredentials/passwordcredentials.go new file mode 100644 index 000000000..96e470f7e --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/passwordcredentials/passwordcredentials.go @@ -0,0 +1,73 @@ +package passwordcredentials + +import ( + "net/http" + + "golang.org/x/net/context" + "golang.org/x/oauth2" +) + +type Config struct { + // ClientID is the application's ID. + ClientID string + + // ClientSecret is the application's secret. + ClientSecret string + + // Resource owner username + Username string + + // Resource owner password + Password string + + // Endpoint contains the resource server's token endpoint + // URLs. These are constants specific to each server and are + // often available via site-specific packages, such as + // google.Endpoint or github.Endpoint. + Endpoint oauth2.Endpoint + + // Scope specifies optional requested permissions. + Scopes []string +} + +func (c *Config) Client(ctx context.Context) *http.Client { + return oauth2.NewClient(ctx, c.TokenSource(ctx)) +} + +// TokenSource returns a TokenSource that returns t until t expires, +// automatically refreshing it as necessary using the provided context and the +// client ID and client secret. +// +// Most users will use Config.Client instead. +// +// Client returns an HTTP client using the provided token. +// The token will auto-refresh as necessary. The underlying +// HTTP transport will be obtained using the provided context. +// The returned client and its Transport should not be modified. +func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { + source := &tokenSource{ + ctx: ctx, + conf: c, + } + return oauth2.ReuseTokenSource(nil, source) +} + +type tokenSource struct { + ctx context.Context + conf *Config +} + +// Token refreshes the token by using a new client credentials request. +// tokens received this way do not include a refresh token +// Token returns a token or an error. +// Token must be safe for concurrent use by multiple goroutines. +// The returned Token must not be modified. +func (c *tokenSource) Token() (*oauth2.Token, error) { + config := oauth2.Config{ + ClientID: c.conf.ClientID, + ClientSecret: c.conf.ClientSecret, + Endpoint: c.conf.Endpoint, + Scopes: c.conf.Scopes, + } + return config.PasswordCredentialsToken(c.ctx, c.conf.Username, c.conf.Password) +} diff --git a/vendor/github.com/Ensighten/udnssdk/probe.go b/vendor/github.com/Ensighten/udnssdk/probe.go index 3723d606a..eff1460dd 100644 --- a/vendor/github.com/Ensighten/udnssdk/probe.go +++ b/vendor/github.com/Ensighten/udnssdk/probe.go @@ -3,14 +3,26 @@ package udnssdk import ( "encoding/json" "fmt" - "strings" + "net/http" +) + +type ProbeType string + +const ( + DNSProbeType ProbeType = "DNS" + FTPProbeType ProbeType = "FTP" + HTTPProbeType ProbeType = "HTTP" + PingProbeType ProbeType = "PING" + SMTPProbeType ProbeType = "SMTP" + SMTPSENDProbeType ProbeType = "SMTP_SEND" + TCPProbeType ProbeType = "TCP" ) // ProbeInfoDTO wraps a probe response type ProbeInfoDTO struct { - ID string `json:"id"` - PoolRecord string `json:"poolRecord"` - ProbeType string `json:"type"` + ID string `json:"id,omitempty"` + PoolRecord string `json:"poolRecord,omitempty"` + ProbeType ProbeType `json:"type"` Interval string `json:"interval"` Agents []string `json:"agents"` Threshold int `json:"threshold"` @@ -28,7 +40,7 @@ type ProbeDetailsLimitDTO struct { type ProbeDetailsDTO struct { data []byte Detail interface{} `json:"detail,omitempty"` - typ string + typ ProbeType } // GetData returns the data because I'm working around something. @@ -40,54 +52,81 @@ func (s *ProbeDetailsDTO) GetData() []byte { // an appropriate datatype. These are helper structures and functions for testing // and direct API use. In the Terraform implementation, we will use Terraforms own // warped schema structure to handle the marshalling and unmarshalling. -func (s *ProbeDetailsDTO) Populate(typ string) (err error) { - // TODO: actually document - switch strings.ToUpper(typ) { - case "HTTP": - var pp HTTPProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp +func (s *ProbeDetailsDTO) Populate(t ProbeType) (err error) { + s.typ = t + d, err := s.GetDetailsObject(t) + if err != nil { return err - case "PING": - var pp PingProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp - return err - case "FTP": - var pp FTPProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp - return err - case "TCP": - var pp TCPProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp - return err - case "SMTP": - var pp SMTPProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp - return err - case "SMTP_SEND": - var pp SMTPSENDProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp - return err - case "DNS": - var pp DNSProbeDetailsDTO - err = json.Unmarshal(s.data, &pp) - s.typ = typ - s.Detail = pp - return err - default: - return fmt.Errorf("ERROR - ProbeDetailsDTO.Populate(\"%s\") - Fall through!\n", typ) } + s.Detail = d + return nil +} + +// Populate does magical things with json unmarshalling to unroll the Probe into +// an appropriate datatype. These are helper structures and functions for testing +// and direct API use. In the Terraform implementation, we will use Terraforms own +// warped schema structure to handle the marshalling and unmarshalling. +func (s *ProbeDetailsDTO) GetDetailsObject(t ProbeType) (interface{}, error) { + switch t { + case DNSProbeType: + return s.DNSProbeDetails() + case FTPProbeType: + return s.FTPProbeDetails() + case HTTPProbeType: + return s.HTTPProbeDetails() + case PingProbeType: + return s.PingProbeDetails() + case SMTPProbeType: + return s.SMTPProbeDetails() + case SMTPSENDProbeType: + return s.SMTPSENDProbeDetails() + case TCPProbeType: + return s.TCPProbeDetails() + default: + return nil, fmt.Errorf("Invalid ProbeType: %#v", t) + } +} + +func (s *ProbeDetailsDTO) DNSProbeDetails() (DNSProbeDetailsDTO, error) { + var d DNSProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err +} + +func (s *ProbeDetailsDTO) FTPProbeDetails() (FTPProbeDetailsDTO, error) { + var d FTPProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err +} + +func (s *ProbeDetailsDTO) HTTPProbeDetails() (HTTPProbeDetailsDTO, error) { + var d HTTPProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err +} + +func (s *ProbeDetailsDTO) PingProbeDetails() (PingProbeDetailsDTO, error) { + var d PingProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err +} + +func (s *ProbeDetailsDTO) SMTPProbeDetails() (SMTPProbeDetailsDTO, error) { + var d SMTPProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err +} + +func (s *ProbeDetailsDTO) SMTPSENDProbeDetails() (SMTPSENDProbeDetailsDTO, error) { + var d SMTPSENDProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err +} + +func (s *ProbeDetailsDTO) TCPProbeDetails() (TCPProbeDetailsDTO, error) { + var d TCPProbeDetailsDTO + err := json.Unmarshal(s.data, &d) + return d, err } // UnmarshalJSON does what it says on the tin @@ -213,7 +252,7 @@ func (k ProbeKey) URI() string { } // Select returns all probes by a RRSetKey, with an optional query -func (s *ProbesService) Select(k RRSetKey, query string) ([]ProbeInfoDTO, *Response, error) { +func (s *ProbesService) Select(k RRSetKey, query string) ([]ProbeInfoDTO, *http.Response, error) { var pld ProbeListDTO // This API does not support pagination. @@ -230,23 +269,23 @@ func (s *ProbesService) Select(k RRSetKey, query string) ([]ProbeInfoDTO, *Respo } // Find returns a probe from a ProbeKey -func (s *ProbesService) Find(k ProbeKey) (ProbeInfoDTO, *Response, error) { +func (s *ProbesService) Find(k ProbeKey) (ProbeInfoDTO, *http.Response, error) { var t ProbeInfoDTO res, err := s.client.get(k.URI(), &t) return t, res, err } // Create creates a probe with a RRSetKey using the ProbeInfoDTO dp -func (s *ProbesService) Create(k RRSetKey, dp ProbeInfoDTO) (*Response, error) { +func (s *ProbesService) Create(k RRSetKey, dp ProbeInfoDTO) (*http.Response, error) { return s.client.post(k.ProbesURI(), dp, nil) } // Update updates a probe given a ProbeKey with the ProbeInfoDTO dp -func (s *ProbesService) Update(k ProbeKey, dp ProbeInfoDTO) (*Response, error) { +func (s *ProbesService) Update(k ProbeKey, dp ProbeInfoDTO) (*http.Response, error) { return s.client.put(k.URI(), dp, nil) } // Delete deletes a probe by its ProbeKey -func (s *ProbesService) Delete(k ProbeKey) (*Response, error) { +func (s *ProbesService) Delete(k ProbeKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/readme.md b/vendor/github.com/Ensighten/udnssdk/readme.md index 2b1143006..6536fe13c 100644 --- a/vendor/github.com/Ensighten/udnssdk/readme.md +++ b/vendor/github.com/Ensighten/udnssdk/readme.md @@ -1,103 +1,81 @@ -# udnssdk - A ultradns SDK for GoLang -## about +# udnssdk - An UltraDNS SDK for Go -This is a golang 'SDK' for UltraDNS that I copapasta'd from weppos/dnsimple. -It seemed like an ideal donor since the use case is terraform. +This is a golang SDK for the UltraDNS REST API. It's not feature complete, and currently is only known to be used for Terraform's `ultradns` provider. -## How It works: - client := udnssdk.NewClient("username","password",udnssdk.DefaultTestBaseURL) +Full API docs are available at [godoc](https://godoc.org/github.com/Ensighten/udnssdk) -There is DefaultTestBaseURL and DefaultLiveBaseURL. -When you call NewClient, it performs the 'oauth2' authorization step. -The refresh token is saved, but not implemented. It should ideally be an error -condition triggering a reauth and retry. But since Terraform is the use case, this won't be an issue. +## Example -### RRSet Declaration +```go +package main - type RRSet struct { - OwnerName string `json:"ownerName"` - RRType string `json:"rrtype"` - TTL int `json:"ttl"` - RData []string `json:"rdata"` - } +import ( + "fmt" + "log" -###GetRRSets(DomainName, RecordName(leave blank for all), RecordType[A|CNAME|ANY]) - rrsets, resp, err := client.Zones.GetRRSets("domain.com","","ANY") - rrsets, resp, err := client.Zones.GetRRSets("domain.com","www","ANY") - rrsets, resp, err := client.Zones.GetRRSets("domain.com","","MX") - rrsets, resp, err := client.Zones.GetRRSets("domain.com","www","SRV") + "github.com/Ensighten/udnssdk" +) +func main() { + client := udnssdk.NewClient("username", "password", udnssdk.DefaultTestBaseURL) + if client == nil { + log.Fatalf("Failed to create client") + } + fmt.Printf("---- Query RRSets\n") + rrsetkey := RRSetKey{ + Zone: "domain.com", + Type: "ANY", + Name: "", + } + rrsets, err := client.RRSets.Select(rrsetkey) + if err != nil { + log.Fatalf(err) + } + fmt.Printf("%+v\n", rrsets) + fmt.Printf("---- Create RRSet\n") + rrsetkey = RRSetKey{ + Zone: "domain.com", + Type: "A", + Name: "test", + } + rrset := udnssdk.RRSet{ + OwnerName: r.Name, + RRType: r.Type, + TTL: 300, + RData: []string{"127.0.0.1"}, + } + resp, err := client.RRSets.Create(rrsetkey, rrset) + if err != nil { + log.Fatalf(err) + } + fmt.Printf("Response: %+v\n", resp) -###CreateRRSet(DomainName, RRSet) - rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.1"}} - resp2,err2 := client.Zones.CreateRRSet("ensighten.com",*rr1) + fmt.Printf("---- Update RRSet\n") + rrset = udnssdk.RRSet{ + OwnerName: r.Name, + RRType: r.Type, + TTL: 300, + RData: []string{"127.0.0.2"}, + } + resp, err := client.RRSets.Update(rrsetkey, rrset) + if err != nil { + log.Fatalf(err) + } + fmt.Printf("Response: %+v\n", resp) -###UpdateRRSet(DomainName, RRSet) -UpdateRRSet requires you to specify the complete RRSet for the update. This implementation does not support PATCHing. + fmt.Printf("---- Delete RRSet\n") + resp, err := client.RRSets.Delete(rrsetkey) + if err != nil { + log.Fatalf(err) + } + fmt.Printf("Response: %+v\n", resp) +} +``` - rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"192.168.1.1"}} - resp2,err2 := client.Zones.CreateRRSet("domain.com",*rr1) +## Thanks -###DeleteRRSet(DomainName, RRSet) -Delete RRSet only uses the ownerName and RRType values from the RRSet object. - - rr3 := &udnssdk.RRSet{OwnerName: "test", RRType: "A"} // This is permissible. - resp3,err3 := client.RRSets.DeleteRRSet("domain.com",*rr3) - -## Example Program - - package main - // udnssdk - a golang sdk for the ultradns REST service. - // based on weppos/dnsimple - - import ( - "fmt" - "udnssdk" - ) - - - func main() { - client := udnssdk.NewClient("username","password",udnssdk.DefaultTestBaseURL) - if client == nil { - fmt.Printf("Fail") - } else { - fmt.Printf("Win\n") - rrsets, resp, err := client.RRSets.GetRRSets("domain.com","test","ANY") - fmt.Printf("%+v\n",rrsets) - fmt.Printf("%+v\n",resp) - fmt.Printf("%+v\n",err) - fmt.Printf("------------------------\n") - fmt.Printf("---- Create RRSet\n") - rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.1}} - resp2,err2 := client.RRSets.CreateRRSet("domain.com",*rr1) - fmt.Printf("Resp2: %+v\n", resp2) - fmt.Printf("Err2: %+v\n", err2) - fmt.Printf("------------------------\n") - fmt.Printf("------------------------\n") - fmt.Printf("---- Update RRSet\n") - fmt.Printf("------------------------\n") - rr2 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.2"}} - resp3, err3 := client.RRSets.UpdateRRSet("domain.com",*rr2) - fmt.Printf("Resp3: %+v\n", resp3) - fmt.Printf("Err3: %+v\n", err3) - fmt.Printf("------------------------\n") - - fmt.Printf("------------------------\n") - fmt.Printf("---- Delete RRSet\n") - fmt.Printf("------------------------\n") - resp4,err4 := client.RRSets.DeleteRRSet("domain.com",*rr2) - fmt.Printf("Resp4: %+v\n", resp4) - fmt.Printf("Err4: %+v\n", err4) - fmt.Printf("------------------------\n") - - } - } - - -#thanks -* [weppo's dnsimple go sdk @ github](https://github.com/weppos/go-dnsimple) -* [pearkes dnsimple sdk (this one is used by terraform) @ github](https://github.com/pearkes/dnsimple) -* [terraform](http://terraform.io) -* [UltraDNS's various SDK's](https://github.com/ultradns) +* Originally started as a modified version of [weppos/go-dnsimple](https://github.com/weppos/go-dnsimple) +* Designed to add UltraDNS support to [terraform](http://terraform.io) +* And for other languages, be sure to check out [UltraDNS's various SDKs](https://github.com/ultradns) diff --git a/vendor/github.com/Ensighten/udnssdk/rrset.go b/vendor/github.com/Ensighten/udnssdk/rrset.go index a50c0ac58..2f78d2dd7 100644 --- a/vendor/github.com/Ensighten/udnssdk/rrset.go +++ b/vendor/github.com/Ensighten/udnssdk/rrset.go @@ -1,10 +1,13 @@ package udnssdk import ( - "encoding/json" "fmt" "log" + "net/http" "time" + + "github.com/fatih/structs" + "github.com/mitchellh/mapstructure" ) // RRSetsService provides access to RRSet resources @@ -14,16 +17,6 @@ type RRSetsService struct { // Here is the big 'Profile' mess that should get refactored to a more managable place -// StringProfile wraps a Profile string -type StringProfile struct { - Profile string `json:"profile,omitempty"` -} - -// Metaprofile is a helper struct for extracting a Context from a StringProfile -type Metaprofile struct { - Context ProfileSchema `json:"@context"` -} - // ProfileSchema are the schema URIs for RRSet Profiles type ProfileSchema string @@ -38,34 +31,150 @@ const ( TCPoolSchema = "http://schemas.ultradns.com/TCPool.jsonschema" ) +// RawProfile represents the naive interface to an RRSet Profile +type RawProfile map[string]interface{} + +// Context extracts the schema context from a RawProfile +func (rp RawProfile) Context() ProfileSchema { + return ProfileSchema(rp["@context"].(string)) +} + +// GetProfileObject extracts the full Profile by its schema type +func (rp RawProfile) GetProfileObject() (interface{}, error) { + c := rp.Context() + switch c { + case DirPoolSchema: + return rp.DirPoolProfile() + case RDPoolSchema: + return rp.RDPoolProfile() + case SBPoolSchema: + return rp.SBPoolProfile() + case TCPoolSchema: + return rp.TCPoolProfile() + default: + return nil, fmt.Errorf("Fallthrough on GetProfileObject type %s\n", c) + } +} + +// decode takes a RawProfile and uses reflection to convert it into the +// given Go native structure. val must be a pointer to a struct. +// This is identical to mapstructure.Decode, but uses the `json:` tag instead of `mapstructure:` +func decodeProfile(m interface{}, rawVal interface{}) error { + config := &mapstructure.DecoderConfig{ + Metadata: nil, + TagName: "json", + Result: rawVal, + ErrorUnused: true, + WeaklyTypedInput: true, + } + + decoder, err := mapstructure.NewDecoder(config) + if err != nil { + return err + } + + return decoder.Decode(m) +} + +// DirPoolProfile extracts the full Profile as a DirPoolProfile or returns an error +func (rp RawProfile) DirPoolProfile() (DirPoolProfile, error) { + var result DirPoolProfile + c := rp.Context() + if c != DirPoolSchema { + return result, fmt.Errorf("RDPoolProfile has incorrect JSON-LD @context %s\n", c) + } + err := decodeProfile(rp, &result) + return result, err +} + +// RDPoolProfile extracts the full Profile as a RDPoolProfile or returns an error +func (rp RawProfile) RDPoolProfile() (RDPoolProfile, error) { + var result RDPoolProfile + c := rp.Context() + if c != RDPoolSchema { + return result, fmt.Errorf("RDPoolProfile has incorrect JSON-LD @context %s\n", c) + } + err := decodeProfile(rp, &result) + return result, err +} + +// SBPoolProfile extracts the full Profile as a SBPoolProfile or returns an error +func (rp RawProfile) SBPoolProfile() (SBPoolProfile, error) { + var result SBPoolProfile + c := rp.Context() + if c != SBPoolSchema { + return result, fmt.Errorf("SBPoolProfile has incorrect JSON-LD @context %s\n", c) + } + err := decodeProfile(rp, &result) + return result, err +} + +// TCPoolProfile extracts the full Profile as a TCPoolProfile or returns an error +func (rp RawProfile) TCPoolProfile() (TCPoolProfile, error) { + var result TCPoolProfile + c := rp.Context() + if c != TCPoolSchema { + return result, fmt.Errorf("TCPoolProfile has incorrect JSON-LD @context %s\n", c) + } + err := decodeProfile(rp, &result) + return result, err +} + +// encodeProfile takes a struct and converts to a RawProfile +func encodeProfile(rawVal interface{}) RawProfile { + s := structs.New(rawVal) + s.TagName = "json" + return s.Map() +} + +// RawProfile converts to a naive RawProfile +func (p DirPoolProfile) RawProfile() RawProfile { + return encodeProfile(p) +} + +// RawProfile converts to a naive RawProfile +func (p RDPoolProfile) RawProfile() RawProfile { + return encodeProfile(p) +} + +// RawProfile converts to a naive RawProfile +func (p SBPoolProfile) RawProfile() RawProfile { + return encodeProfile(p) +} + +// RawProfile converts to a naive RawProfile +func (p TCPoolProfile) RawProfile() RawProfile { + return encodeProfile(p) +} + // DirPoolProfile wraps a Profile for a Directional Pool type DirPoolProfile struct { Context ProfileSchema `json:"@context"` Description string `json:"description"` ConflictResolve string `json:"conflictResolve,omitempty"` RDataInfo []DPRDataInfo `json:"rdataInfo"` - NoResponse DPRDataInfo `json:"noResponse"` + NoResponse DPRDataInfo `json:"noResponse,omitempty"` } // DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response type DPRDataInfo struct { - AllNonConfigured bool `json:"allNonConfigured,omitempty"` - IPInfo IPInfo `json:"ipInfo,omitempty"` - GeoInfo GeoInfo `json:"geoInfo,omitempty"` + AllNonConfigured bool `json:"allNonConfigured,omitempty" terraform:"all_non_configured"` + IPInfo *IPInfo `json:"ipInfo,omitempty" terraform:"ip_info"` + GeoInfo *GeoInfo `json:"geoInfo,omitempty" terraform:"geo_info"` } // IPInfo wraps the ipInfo object of a DPRDataInfo type IPInfo struct { - Name string `json:"name"` - IsAccountLevel bool `json:"isAccountLevel,omitempty"` - Ips []IPAddrDTO `json:"ips"` + Name string `json:"name" terraform:"name"` + IsAccountLevel bool `json:"isAccountLevel,omitempty" terraform:"is_account_level"` + Ips []IPAddrDTO `json:"ips,omitempty" terraform:"-"` } // GeoInfo wraps the geoInfo object of a DPRDataInfo type GeoInfo struct { - Name string `json:"name"` - IsAccountLevel bool `json:"isAccountLevel,omitempty"` - Codes []string `json:"codes"` + Name string `json:"name" terraform:"name"` + IsAccountLevel bool `json:"isAccountLevel,omitempty" terraform:"is_account_level"` + Codes []string `json:"codes,omitempty" terraform:"-"` } // RDPoolProfile wraps a Profile for a Resource Distribution pool @@ -79,8 +188,8 @@ type RDPoolProfile struct { type SBPoolProfile struct { Context ProfileSchema `json:"@context"` Description string `json:"description"` - RunProbes bool `json:"runProbes,omitempty"` - ActOnProbes bool `json:"actOnProbes,omitempty"` + RunProbes bool `json:"runProbes"` + ActOnProbes bool `json:"actOnProbes"` Order string `json:"order,omitempty"` MaxActive int `json:"maxActive,omitempty"` MaxServed int `json:"maxServed,omitempty"` @@ -91,7 +200,7 @@ type SBPoolProfile struct { // SBRDataInfo wraps the rdataInfo object of a SBPoolProfile type SBRDataInfo struct { State string `json:"state"` - RunProbes bool `json:"runProbes,omitempty"` + RunProbes bool `json:"runProbes"` Priority int `json:"priority"` FailoverDelay int `json:"failoverDelay,omitempty"` Threshold int `json:"threshold"` @@ -100,7 +209,7 @@ type SBRDataInfo struct { // BackupRecord wraps the backupRecord objects of an SBPoolProfile response type BackupRecord struct { - RData string `json:"rdata"` + RData string `json:"rdata,omitempty"` FailoverDelay int `json:"failoverDelay,omitempty"` } @@ -108,109 +217,20 @@ type BackupRecord struct { type TCPoolProfile struct { Context ProfileSchema `json:"@context"` Description string `json:"description"` - RunProbes bool `json:"runProbes,omitempty"` - ActOnProbes bool `json:"actOnProbes,omitempty"` + RunProbes bool `json:"runProbes"` + ActOnProbes bool `json:"actOnProbes"` MaxToLB int `json:"maxToLB,omitempty"` RDataInfo []SBRDataInfo `json:"rdataInfo"` - BackupRecord BackupRecord `json:"backupRecord"` -} - -// UnmarshalJSON does what it says on the tin -func (sp *StringProfile) UnmarshalJSON(b []byte) (err error) { - sp.Profile = string(b) - return nil -} - -// MarshalJSON does what it says on the tin -func (sp *StringProfile) MarshalJSON() ([]byte, error) { - if sp.Profile != "" { - return []byte(sp.Profile), nil - } - return json.Marshal(nil) -} - -// Metaprofile converts a StringProfile to a Metaprofile to extract the context -func (sp *StringProfile) Metaprofile() (Metaprofile, error) { - var mp Metaprofile - if sp.Profile == "" { - return mp, fmt.Errorf("Empty Profile cannot be converted to a Metaprofile") - } - err := json.Unmarshal([]byte(sp.Profile), &mp) - if err != nil { - return mp, fmt.Errorf("Error getting profile type: %+v\n", err) - } - return mp, nil -} - -// Context extracts the schema context from a StringProfile -func (sp *StringProfile) Context() ProfileSchema { - mp, err := sp.Metaprofile() - if err != nil { - log.Printf("[ERROR] %+s\n", err) - return "" - } - return mp.Context -} - -// GoString returns the StringProfile's Profile. -func (sp *StringProfile) GoString() string { - return sp.Profile -} - -// String returns the StringProfile's Profile. -func (sp *StringProfile) String() string { - return sp.Profile -} - -// GetProfileObject extracts the full Profile by its schema type -func (sp *StringProfile) GetProfileObject() interface{} { - c := sp.Context() - switch c { - case DirPoolSchema: - var dpp DirPoolProfile - err := json.Unmarshal([]byte(sp.Profile), &dpp) - if err != nil { - log.Printf("Could not Unmarshal the DirPoolProfile.\n") - return nil - } - return dpp - case RDPoolSchema: - var rdp RDPoolProfile - err := json.Unmarshal([]byte(sp.Profile), &rdp) - if err != nil { - log.Printf("Could not Unmarshal the RDPoolProfile.\n") - return nil - } - return rdp - case SBPoolSchema: - var sbp SBPoolProfile - err := json.Unmarshal([]byte(sp.Profile), &sbp) - if err != nil { - log.Printf("Could not Unmarshal the SBPoolProfile.\n") - return nil - } - return sbp - case TCPoolSchema: - var tcp TCPoolProfile - err := json.Unmarshal([]byte(sp.Profile), &tcp) - if err != nil { - log.Printf("Could not Unmarshal the TCPoolProfile.\n") - return nil - } - return tcp - default: - log.Printf("ERROR - Fall through on GetProfileObject - %s.\n", c) - return fmt.Errorf("Fallthrough on GetProfileObject type %s\n", c) - } + BackupRecord *BackupRecord `json:"backupRecord,omitempty"` } // RRSet wraps an RRSet resource type RRSet struct { - OwnerName string `json:"ownerName"` - RRType string `json:"rrtype"` - TTL int `json:"ttl"` - RData []string `json:"rdata"` - Profile *StringProfile `json:"profile,omitempty"` + OwnerName string `json:"ownerName"` + RRType string `json:"rrtype"` + TTL int `json:"ttl"` + RData []string `json:"rdata"` + Profile RawProfile `json:"profile,omitempty"` } // RRSetListDTO wraps a list of RRSet resources @@ -323,7 +343,7 @@ func (s *RRSetsService) Select(k RRSetKey) ([]RRSet, error) { for { reqRrsets, ri, res, err := s.SelectWithOffset(k, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -346,7 +366,7 @@ func (s *RRSetsService) Select(k RRSetKey) ([]RRSet, error) { } // SelectWithOffset requests zone rrsets by RRSetKey & optional offset -func (s *RRSetsService) SelectWithOffset(k RRSetKey, offset int) ([]RRSet, ResultInfo, *Response, error) { +func (s *RRSetsService) SelectWithOffset(k RRSetKey, offset int) ([]RRSet, ResultInfo, *http.Response, error) { var rrsld RRSetListDTO uri := k.QueryURI(offset) @@ -360,18 +380,18 @@ func (s *RRSetsService) SelectWithOffset(k RRSetKey, offset int) ([]RRSet, Resul } // Create creates an rrset with val -func (s *RRSetsService) Create(k RRSetKey, rrset RRSet) (*Response, error) { +func (s *RRSetsService) Create(k RRSetKey, rrset RRSet) (*http.Response, error) { var ignored interface{} return s.client.post(k.URI(), rrset, &ignored) } // Update updates a RRSet with the provided val -func (s *RRSetsService) Update(k RRSetKey, val RRSet) (*Response, error) { +func (s *RRSetsService) Update(k RRSetKey, val RRSet) (*http.Response, error) { var ignored interface{} return s.client.put(k.URI(), val, &ignored) } // Delete deletes an RRSet -func (s *RRSetsService) Delete(k RRSetKey) (*Response, error) { +func (s *RRSetsService) Delete(k RRSetKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/task.go b/vendor/github.com/Ensighten/udnssdk/task.go index 6fb10baf2..d08c64f47 100644 --- a/vendor/github.com/Ensighten/udnssdk/task.go +++ b/vendor/github.com/Ensighten/udnssdk/task.go @@ -3,6 +3,7 @@ package udnssdk import ( "fmt" "log" + "net/http" "time" ) @@ -65,7 +66,7 @@ func (s *TasksService) Select(query string) ([]Task, error) { for { reqDtos, ri, res, err := s.SelectWithOffset(query, offset) if err != nil { - if res.StatusCode >= 500 { + if res != nil && res.StatusCode >= 500 { errcnt = errcnt + 1 if errcnt < maxerrs { time.Sleep(waittime) @@ -88,7 +89,7 @@ func (s *TasksService) Select(query string) ([]Task, error) { } // SelectWithOffset request tasks by query & offset, list them also returning list metadata, the actual response, or an error -func (s *TasksService) SelectWithOffset(query string, offset int) ([]Task, ResultInfo, *Response, error) { +func (s *TasksService) SelectWithOffset(query string, offset int) ([]Task, ResultInfo, *http.Response, error) { var tld TaskListDTO uri := TasksQueryURI(query, offset) @@ -102,23 +103,23 @@ func (s *TasksService) SelectWithOffset(query string, offset int) ([]Task, Resul } // Find Get the status of a task. -func (s *TasksService) Find(t TaskID) (Task, *Response, error) { +func (s *TasksService) Find(t TaskID) (Task, *http.Response, error) { var tv Task res, err := s.client.get(t.URI(), &tv) return tv, res, err } // FindResult requests -func (s *TasksService) FindResult(t TaskID) (*Response, error) { +func (s *TasksService) FindResult(t TaskID) (*http.Response, error) { return s.client.GetResultByURI(t.ResultURI()) } // FindResultByTask requests a task by the provided task's result uri -func (s *TasksService) FindResultByTask(t Task) (*Response, error) { +func (s *TasksService) FindResultByTask(t Task) (*http.Response, error) { return s.client.GetResultByURI(t.ResultURI) } // Delete requests deletions -func (s *TasksService) Delete(t TaskID) (*Response, error) { +func (s *TasksService) Delete(t TaskID) (*http.Response, error) { return s.client.delete(t.URI(), nil) } diff --git a/vendor/github.com/Ensighten/udnssdk/token_source.go b/vendor/github.com/Ensighten/udnssdk/token_source.go new file mode 100644 index 000000000..2d6d2605e --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/token_source.go @@ -0,0 +1,26 @@ +package udnssdk + +import ( + "fmt" + + "github.com/Ensighten/udnssdk/passwordcredentials" + "golang.org/x/oauth2" +) + +func NewConfig(username, password, BaseURL string) *passwordcredentials.Config { + c := passwordcredentials.Config{} + c.Username = username + c.Password = password + c.Endpoint = Endpoint(BaseURL) + return &c +} + +func Endpoint(BaseURL string) oauth2.Endpoint { + return oauth2.Endpoint{ + TokenURL: TokenURL(BaseURL), + } +} + +func TokenURL(BaseURL string) string { + return fmt.Sprintf("%s/%s/authorization/token", BaseURL, apiVersion) +} diff --git a/vendor/github.com/Ensighten/udnssdk/udnssdk.go b/vendor/github.com/Ensighten/udnssdk/udnssdk.go index b9a145679..7e104e207 100644 --- a/vendor/github.com/Ensighten/udnssdk/udnssdk.go +++ b/vendor/github.com/Ensighten/udnssdk/udnssdk.go @@ -11,8 +11,11 @@ import ( "io/ioutil" "log" "net/http" - "net/url" "time" + + "golang.org/x/oauth2" + + "github.com/Ensighten/udnssdk/passwordcredentials" ) const ( @@ -46,17 +49,10 @@ type ResultInfo struct { type Client struct { // This is our client structure. HTTPClient *http.Client + Config *passwordcredentials.Config - // UltraDNS makes a call to an authorization API using your username and - // password, returning an 'Access Token' and a 'Refresh Token'. - // Our use case does not require the refresh token, but we should implement - // for completeness. - AccessToken string - RefreshToken string - Username string - Password string - BaseURL string - UserAgent string + BaseURL string + UserAgent string // Accounts API Accounts *AccountsService @@ -78,18 +74,14 @@ type Client struct { // NewClient returns a new ultradns API client. func NewClient(username, password, BaseURL string) (*Client, error) { - accesstoken, refreshtoken, err := GetAuthTokens(username, password, BaseURL) - if err != nil { - return nil, err - } + ctx := oauth2.NoContext + conf := NewConfig(username, password, BaseURL) + c := &Client{ - AccessToken: accesstoken, - RefreshToken: refreshtoken, - Username: username, - Password: password, - HTTPClient: &http.Client{}, - BaseURL: BaseURL, - UserAgent: userAgent, + HTTPClient: conf.Client(ctx), + BaseURL: BaseURL, + UserAgent: userAgent, + Config: conf, } c.Accounts = &AccountsService{client: c} c.Alerts = &AlertsService{client: c} @@ -103,15 +95,11 @@ func NewClient(username, password, BaseURL string) (*Client, error) { } // newStubClient returns a new ultradns API client. -func newStubClient(username, password, BaseURL, accesstoken, refreshtoken string) (*Client, error) { +func newStubClient(username, password, BaseURL, clientID, clientSecret string) (*Client, error) { c := &Client{ - AccessToken: accesstoken, - RefreshToken: refreshtoken, - Username: username, - Password: password, - HTTPClient: &http.Client{}, - BaseURL: BaseURL, - UserAgent: userAgent, + HTTPClient: &http.Client{}, + BaseURL: BaseURL, + UserAgent: userAgent, } c.Accounts = &AccountsService{client: c} c.Alerts = &AlertsService{client: c} @@ -124,51 +112,6 @@ func newStubClient(username, password, BaseURL, accesstoken, refreshtoken string return c, nil } -// NewAuthRequest creates an Authorization request to get an access and refresh token. -// -// { -// "tokenType":"Bearer", -// "refreshToken":"48472efcdce044c8850ee6a395c74a7872932c7112", -// "accessToken":"b91d037c75934fc89a9f43fe4a", -// "expiresIn":"3600", -// "expires_in":"3600" -// } - -// AuthResponse wraps the response to an auth request -type AuthResponse struct { - TokenType string `json:"tokenType"` - AccessToken string `json:"accessToken"` - RefreshToken string `json:"refreshToken"` - ExpiresIn string `json:"expiresIn"` -} - -// GetAuthTokens requests by username, password & base URL, returns the access-token & refresh-token, or a possible error -func GetAuthTokens(username, password, BaseURL string) (string, string, error) { - res, err := http.PostForm(fmt.Sprintf("%s/%s/authorization/token", BaseURL, apiVersion), url.Values{"grant_type": {"password"}, "username": {username}, "password": {password}}) - - if err != nil { - return "", "", err - } - - //response := &Response{Response: res} - defer res.Body.Close() - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", "", err - } - err = CheckAuthResponse(res, body) - if err != nil { - return "", "", err - } - - var authr AuthResponse - err = json.Unmarshal(body, &authr) - if err != nil { - return string(body), "JSON Decode Error", err - } - return authr.AccessToken, authr.RefreshToken, err -} - // NewRequest creates an API request. // The path is expected to be a relative path and will be resolved // according to the BaseURL of the Client. Paths should always be specified without a preceding slash. @@ -191,25 +134,23 @@ func (c *Client) NewRequest(method, path string, payload interface{}) (*http.Req req.Header.Set("Content-Type", "application/json") req.Header.Add("Accept", "application/json") req.Header.Add("User-Agent", c.UserAgent) - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken)) - req.Header.Add("Token", fmt.Sprintf("Bearer %s", c.AccessToken)) return req, nil } -func (c *Client) get(path string, v interface{}) (*Response, error) { +func (c *Client) get(path string, v interface{}) (*http.Response, error) { return c.Do("GET", path, nil, v) } -func (c *Client) post(path string, payload, v interface{}) (*Response, error) { +func (c *Client) post(path string, payload, v interface{}) (*http.Response, error) { return c.Do("POST", path, payload, v) } -func (c *Client) put(path string, payload, v interface{}) (*Response, error) { +func (c *Client) put(path string, payload, v interface{}) (*http.Response, error) { return c.Do("PUT", path, payload, v) } -func (c *Client) delete(path string, payload interface{}) (*Response, error) { +func (c *Client) delete(path string, payload interface{}) (*http.Response, error) { return c.Do("DELETE", path, payload, nil) } @@ -218,25 +159,23 @@ func (c *Client) delete(path string, payload interface{}) (*Response, error) { // or returned as an error if an API error has occurred. // If v implements the io.Writer interface, the raw response body will be written to v, // without attempting to decode it. -func (c *Client) Do(method, path string, payload, v interface{}) (*Response, error) { +func (c *Client) Do(method, path string, payload, v interface{}) (*http.Response, error) { + hc := c.HTTPClient req, err := c.NewRequest(method, path, payload) if err != nil { return nil, err } log.Printf("[DEBUG] HTTP Request: %+v\n", req) - res, err := c.HTTPClient.Do(req) - log.Printf("[DEBUG] HTTP Response: %+v\n", res) + r, err := hc.Do(req) + log.Printf("[DEBUG] HTTP Response: %+v\n", r) if err != nil { return nil, err } - defer res.Body.Close() - origresponse := &Response{Response: res} + defer r.Body.Close() - var nres *http.Response - nres = res - if res.StatusCode == 202 { + if r.StatusCode == 202 { // This is a deferred task. - tid := TaskID(res.Header.Get("X-Task-Id")) + tid := TaskID(r.Header.Get("X-Task-Id")) log.Printf("[DEBUG] Received Async Task %+v.. will retry...\n", tid) // TODO: Sane Configuration for timeouts / retries timeout := 5 @@ -244,51 +183,45 @@ func (c *Client) Do(method, path string, payload, v interface{}) (*Response, err i := 0 breakmeout := false for i < timeout || breakmeout { - myt, statusres, err := c.Tasks.Find(tid) + t, _, err := c.Tasks.Find(tid) if err != nil { - return origresponse, err + return nil, err } - log.Printf("[DEBUG] Task ID: %+v Retry: %d Status Code: %s\n", tid, i, myt.TaskStatusCode) - switch myt.TaskStatusCode { + log.Printf("[DEBUG] Task ID: %+v Retry: %d Status Code: %s\n", tid, i, t.TaskStatusCode) + switch t.TaskStatusCode { case "COMPLETE": // Yay - tres, err := c.Tasks.FindResultByTask(myt) + resp, err := c.Tasks.FindResultByTask(t) if err != nil { - return origresponse, err + return nil, err } - nres = tres.Response + r = resp breakmeout = true case "PENDING", "IN_PROCESS": i = i + 1 time.Sleep(waittime) continue case "ERROR": - return statusres, err - + return nil, err } } } - response := &Response{Response: nres} - err = CheckResponse(nres) + err = CheckResponse(r) if err != nil { - return response, err + return r, err } if v != nil { if w, ok := v.(io.Writer); ok { - io.Copy(w, res.Body) + io.Copy(w, r.Body) } else { - err = json.NewDecoder(res.Body).Decode(v) + err = json.NewDecoder(r.Body).Decode(v) + // err = json.Unmarshal(r.Body, v) } } - return response, err -} - -// A Response represents an API response. -type Response struct { - *http.Response + return r, err } // ErrorResponse represents an error caused by an API request. @@ -321,30 +254,6 @@ func (r ErrorResponseList) Error() string { r.Response.StatusCode, r.Responses[0].ErrorCode, r.Responses[0].ErrorMessage) } -// CheckAuthResponse checks the API response for errors, and returns them if so -func CheckAuthResponse(r *http.Response, body []byte) error { - if code := r.StatusCode; 200 <= code && code <= 299 { - return nil - } - - // Attempt marshaling to ErrorResponse - var er ErrorResponse - err := json.Unmarshal(body, &er) - if err == nil { - er.Response = r - return er - } - - // Attempt marshaling to ErrorResponseList - var ers []ErrorResponse - err = json.Unmarshal(body, &ers) - if err == nil { - return &ErrorResponseList{Response: r, Responses: ers} - } - - return fmt.Errorf("Response had non-successful status: %d, but could not extract error from body: %+v", r.StatusCode, body) -} - // CheckResponse checks the API response for errors, and returns them if present. // A response is considered an error if the status code is different than 2xx. Specific requests // may have additional requirements, but this is sufficient in most of the cases. diff --git a/vendor/vendor.json b/vendor/vendor.json index 947aa3204..fea560645 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -312,8 +312,10 @@ "revisionTime": "2016-12-01T15:35:21Z" }, { + "checksumSHA1": "nomT+8bvze/Qmc0tK0r0mwgHV6M=", "path": "github.com/Ensighten/udnssdk", - "revision": "0290933f5e8afd933f2823fce32bf2847e6ea603" + "revision": "9f1218928b30c6dec7f2c184c47286abc325deb9", + "revisionTime": "2016-06-13T20:05:45Z" }, { "checksumSHA1": "B+GonfgNwOAJMEe0WUHQGATRtMA=", diff --git a/website/source/docs/providers/ultradns/r/dirpool.html.markdown b/website/source/docs/providers/ultradns/r/dirpool.html.markdown new file mode 100644 index 000000000..4faf3c2ab --- /dev/null +++ b/website/source/docs/providers/ultradns/r/dirpool.html.markdown @@ -0,0 +1,74 @@ +--- +layout: "ultradns" +page_title: "UltraDNS: ultradns_dirpool" +sidebar_current: "docs-ultradns-resource-dirpool" +description: |- + Provides an UltraDNS Directional Controller pool resource. +--- + +# ultradns\_dirpool + +Provides an UltraDNS Directional Controller pool resource. + +## Example Usage +``` +# Create a Directional Controller pool +resource "ultradns_dirpool" "pool" { + zone = "${var.ultradns_domain}" + name = "terraform-dirpool" + ttl = 300 + description = "Minimal DirPool" + + rdata { + host = "192.168.0.10" + } +} +``` + +## Argument Reference + +See [related part of UltraDNS Docs](https://restapi.ultradns.com/v1/docs#post-rrset) for details about valid values. + +The following arguments are supported: + +* `zone` - (Required) The domain to add the record to +* `name` - (Required) The name of the record +- `type` - (Required) The Record Type of the record +* `description` - (Required) Description of the Traffic Controller pool. Valid values are strings less than 256 characters. +* `rdata` - (Required) a list of Record Data blocks, one for each member in the pool. Record Data documented below. +* `ttl` - (Optional) The TTL of the record. Default: `3600`. +* `conflict_resolve` - (Optional) String. Valid: `"GEO"` or `"IP"`. Default: `"GEO"`. +* `no_response` - (Optional) a single Record Data block, without any `host` attribute. Record Data documented below. + +Record Data blocks support the following: + +* `host` - (Required in `rdata`, absent in `no_response`) IPv4 address or CNAME for the pool member. +- `all_non_configured` - (Optional) Boolean. Default: `false`. +- `geo_info` - (Optional) a single Geo Info block. Geo Info documented below. +- `ip_info` - (Optional) a single IP Info block. IP Info documented below. + + +Geo Info blocks support the following: + +- `name` - (Optional) String. +- `is_account_level` - (Optional) Boolean. Default: `false`. +- `codes` - (Optional) Set of geo code strings. Shorthand codes are expanded. + +IP Info blocks support the following: + +- `name` - (Optional) String. +- `is_account_level` - (Optional) Boolean. Default: `false`. +- `ips` - (Optional) Set of IP blocks. IP Info documented below. + +IP blocks support the following: +- `start` - (Optional) String. IP Address. Must be paired with `end`. Conflicts with `cidr` or `address`. +- `end` - (Optional) String. IP Address. Must be paired with `start`. +- `cidr` - (Optional) String. +- `address` - (Optional) String. IP Address. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The record ID +* `hostname` - The FQDN of the record diff --git a/website/source/docs/providers/ultradns/r/probe_http.html.markdown b/website/source/docs/providers/ultradns/r/probe_http.html.markdown new file mode 100644 index 000000000..e73b41592 --- /dev/null +++ b/website/source/docs/providers/ultradns/r/probe_http.html.markdown @@ -0,0 +1,98 @@ +--- +layout: "ultradns" +page_title: "UltraDNS: ultradns_probe_http" +sidebar_current: "docs-ultradns-resource-probe-http" +description: |- + Provides an UltraDNS HTTP probe +--- + +# ultradns\_probe\_http + +Provides an UltraDNS HTTP probe + +## Example Usage +``` +resource "ultradns_probe_http" "probe" { + zone = "${ultradns_tcpool.pool.zone}" + name = "${ultradns_tcpool.pool.name}" + pool_record = "10.2.1.1" + + agents = ["DALLAS", "AMSTERDAM"] + + interval = "ONE_MINUTE" + threshold = 1 + + http_probe { + transaction { + method = "POST" + url = "http://localhost/index" + transmitted_data = "{}" + follow_redirects = true + + limit { + name = "run" + + warning = 1 + critical = 2 + fail = 3 + } + limit { + name = "avgConnect" + + warning = 4 + critical = 5 + fail = 6 + } + limit { + name = "avgRun" + + warning = 7 + critical = 8 + fail = 9 + } + limit { + name = "connect" + + warning = 10 + critical = 11 + fail = 12 + } + } + + total_limits { + warning = 13 + critical = 14 + fail = 15 + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `zone` - (Required) The domain of the pool to probe. +* `name` - (Required) The name of the pool to probe. +- `pool_record` - (optional) IP address or domain. If provided, a record-level probe is created, otherwise a pool-level probe is created. +- `agents` - (Required) List of locations that will be used for probing. One or more values must be specified. Valid values are `"NEW_YORK"`, `"PALO_ALTO"`, `"DALLAS"` & `"AMSTERDAM"`. +- `threshold` - (Required) Number of agents that must agree for a probe state to be changed. +- `http_probe` - (Required) an HTTP Probe block. +- `interval` - (Optional) Length of time between probes in minutes. Valid values are `"HALF_MINUTE"`, `"ONE_MINUTE"`, `"TWO_MINUTES"`, `"FIVE_MINUTES"`, `"TEN_MINUTES"` & `"FIFTEEN_MINUTE"`. Default: `"FIVE_MINUTES"`. + +HTTP Probe block +- `transaction` - (Optional) One or more Transaction blocks. +- `total_limits` - (Optional) A Limit block, but with no `name` attribute. + +Transaction block +- `method` - (Required) HTTP method. Valid values are`"GET"`, `"POST"`. +- `url` - (Required) URL to probe. +- `transmitted_data` - (Optional) Data to send to URL. +- `follow_redirects` - (Optional) Whether to follow redirects. +- `limit` - (Required) One or more Limit blocks. Only one limit block may exist for each name. + +Limit block +- `name` - (Required) Kind of limit. Valid values are `"lossPercent"`, `"total"`, `"average"`, `"run"` & `"avgRun"`. +- `warning` - (Optional) Amount to trigger a warning. +- `critical` - (Optional) Amount to trigger a critical. +- `fail` - (Optional) Amount to trigger a failure. diff --git a/website/source/docs/providers/ultradns/r/probe_ping.html.markdown b/website/source/docs/providers/ultradns/r/probe_ping.html.markdown new file mode 100644 index 000000000..0c0b3a79a --- /dev/null +++ b/website/source/docs/providers/ultradns/r/probe_ping.html.markdown @@ -0,0 +1,67 @@ +--- +layout: "ultradns" +page_title: "UltraDNS: ultradns_probe_ping" +sidebar_current: "docs-ultradns-resource-probe-ping" +description: |- + Provides an UltraDNS Ping Probe +--- + +# ultradns\_probe\_ping + +Provides an UltraDNS ping probe + +## Example Usage +``` +resource "ultradns_probe_ping" "probe" { + zone = "${ultradns_tcpool.pool.zone}" + name = "${ultradns_tcpool.pool.name}" + pool_record = "10.3.0.1" + + agents = ["DALLAS", "AMSTERDAM"] + + interval = "ONE_MINUTE" + threshold = 1 + + ping_probe { + packets = 15 + packet_size = 56 + + limit { + name = "lossPercent" + warning = 1 + critical = 2 + fail = 3 + } + + limit { + name = "total" + warning = 2 + critical = 3 + fail = 4 + } + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `zone` - (Required) The domain of the pool to probe. +* `name` - (Required) The name of the pool to probe. +- `pool_record` - (optional) IP address or domain. If provided, a record-level probe is created, otherwise a pool-level probe is created. +- `agents` - (Required) List of locations that will be used for probing. One or more values must be specified. Valid values are `"NEW_YORK"`, `"PALO_ALTO"`, `"DALLAS"` & `"AMSTERDAM"`. +- `threshold` - (Required) Number of agents that must agree for a probe state to be changed. +- `ping_probe` - (Required) a Ping Probe block. +- `interval` - (Optional) Length of time between probes in minutes. Valid values are `"HALF_MINUTE"`, `"ONE_MINUTE"`, `"TWO_MINUTES"`, `"FIVE_MINUTES"`, `"TEN_MINUTES"` & `"FIFTEEN_MINUTE"`. Default: `"FIVE_MINUTES"`. + +Ping Probe block +- `packets` - (Optional) Number of ICMP packets to send. Default `3`. +- `packet_size` - (Optional) Size of packets in bytes. Default `56`. +- `limit` - (Required) One or more Limit blocks. Only one limit block may exist for each name. + +Limit block +- `name` - (Required) Kind of limit. Valid values are `"lossPercent"`, `"total"`, `"average"`, `"run"` & `"avgRun"`. +- `warning` - (Optional) Amount to trigger a warning. +- `critical` - (Optional) Amount to trigger a critical. +- `fail` - (Optional) Amount to trigger a failure. diff --git a/website/source/docs/providers/ultradns/r/record.html.markdown b/website/source/docs/providers/ultradns/r/record.html.markdown index 67dd90c3d..87615f19f 100644 --- a/website/source/docs/providers/ultradns/r/record.html.markdown +++ b/website/source/docs/providers/ultradns/r/record.html.markdown @@ -3,12 +3,12 @@ layout: "ultradns" page_title: "UltraDNS: ultradns_record" sidebar_current: "docs-ultradns-resource-record" description: |- - Provides a UltraDNS record resource. + Provides an UltraDNS record resource. --- # ultradns\_record -Provides a UltraDNS record resource. +Provides an UltraDNS record resource. ## Example Usage diff --git a/website/source/docs/providers/ultradns/r/tcpool.html.markdown b/website/source/docs/providers/ultradns/r/tcpool.html.markdown new file mode 100644 index 000000000..a550f15e2 --- /dev/null +++ b/website/source/docs/providers/ultradns/r/tcpool.html.markdown @@ -0,0 +1,60 @@ +--- +layout: "ultradns" +page_title: "UltraDNS: ultradns_tcpool" +sidebar_current: "docs-ultradns-resource-tcpool" +description: |- + Provides an UltraDNS Traffic Controller pool resource. +--- + +# ultradns\_tcpool + +Provides an UltraDNS Traffic Controller pool resource. + +## Example Usage +``` +# Create a Traffic Controller pool +resource "ultradns_tcpool" "pool" { + zone = "${var.ultradns_domain}" + name = "terraform-tcpool" + ttl = 300 + description = "Minimal TC Pool" + + rdata { + host = "192.168.0.10" + } +} +``` + +## Argument Reference + +See [related part of UltraDNS Docs](https://restapi.ultradns.com/v1/docs#post-rrset) for details about valid values. + +The following arguments are supported: + +* `zone` - (Required) The domain to add the record to +* `name` - (Required) The name of the record +* `rdata` - (Required) a list of rdata blocks, one for each member in the pool. Record Data documented below. +* `description` - (Required) Description of the Traffic Controller pool. Valid values are strings less than 256 characters. +* `ttl` - (Optional) The TTL of the record. Default: `3600`. +* `run_probes` - (Optional) Boolean to run probes for this pool. Default: `true`. +* `act_on_probes` - (Optional) Boolean to enable and disable pool records when probes are run. Default: `true`. +* `max_to_lb` - (Optional) Determines the number of records to balance between. Valid values are integers `0` - `len(rdata)`. Default: `0`. +* `backup_record_rdata` - (Optional) IPv4 address or CNAME for the backup record. Default: `nil`. +* `backup_record_failover_delay` - (Optional) Time in minutes that Traffic Controller waits after detecting that the pool record has failed before activating primary records. Valid values are integers `0` - `30`. Default: `0`. + +Record Data blocks support the following: + +* `host` - (Required) IPv4 address or CNAME for the pool member. +* `failover_delay` - (Optional) Time in minutes that Traffic Controller waits after detecting that the pool record has failed before activating secondary records. `0` will activate the secondary records immediately. Integer. Range: `0` - `30`. Default: `0`. +* `priority` - (Optional) Indicates the serving preference for this pool record. Valid values are integers `1` or greater. Default: `1`. +* `run_probes` - (Optional) Whether probes are run for this pool record. Boolean. Default: `true`. +* `state` - (Optional) Current state of the pool record. String. Must be one of `"NORMAL"`, `"ACTIVE"`, or `"INACTIVE"`. Default: `"NORMAL"`. +* `threshold` - (Optional) How many probes must agree before the record state is changed. Valid values are integers `1` - `len(probes)`. Default: `1`. +* `weight` - (Optional) Traffic load to send to each server in the Traffic Controller pool. Valid values are integers `2` - `100`. Default: `2` + +## Attributes Reference + +The following attributes are exported: + +* `id` - The record ID +* `hostname` - The FQDN of the record diff --git a/website/source/layouts/ultradns.erb b/website/source/layouts/ultradns.erb index 6df4afa0f..b30dd7496 100644 --- a/website/source/layouts/ultradns.erb +++ b/website/source/layouts/ultradns.erb @@ -3,19 +3,31 @@