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 @@