ultradns providers and improvements (#9788)

* vendor: update github.com/Ensighten/udnssdk to v1.2.1

* ultradns_tcpool: add

* ultradns.baseurl: set default

* ultradns.record: cleanup test

* ultradns_record: extract common, cleanup

* ultradns: extract common

* ultradns_dirpool: add

* ultradns_dirpool: fix rdata.ip_info.ips to be idempotent

* ultradns_tcpool: add doc

* ultradns_dirpool: fix rdata.geo_codes.codes to be idempotent

* ultradns_dirpool: add doc

* ultradns: cleanup testing

* ultradns_record: rename resource

* ultradns: log username from config, not client

udnssdk.Client is being refactored to use x/oauth2, so don't assume we
can access Username from it

* ultradns_probe_ping: add

* ultradns_probe_http: add

* doc: add ultradns_probe_ping

* doc: add ultradns_probe_http

* ultradns_record: remove duplication from error messages

* doc: cleanup typos in ultradns

* ultradns_probe_ping: add test for pool-level probe

* Clean documentation

* ultradns: pull makeSetFromStrings() up to common.go

* ultradns_dirpool: log hashIPInfoIPs

Log the key and generated hashcode used to index ip_info.ips into a set.

* ultradns: simplify hashLimits()

Limits blocks only have the "name" attribute as their primary key, so
hashLimits() needn't use a buffer to concatenate.

Also changes log level to a more approriate DEBUG.

* ultradns_tcpool: convert rdata to schema.Set

RData blocks have the "host" attribute as their primary key, so it is
used by hashRdatas() to create the hashcode.

Tests are updated to use the new hashcode indexes instead of natural
numbers.

* ultradns_probe_http: convert agents to schema.Set

Also pull the makeSetFromStrings() helper up to common.go

* ultradns: pull hashRdatas() up to common

* ultradns_dirpool: convert rdata to schema.Set

Fixes TF-66

* ultradns_dirpool.conflict_resolve: fix default from response

UltraDNS REST API User Guide claims that "Directional Pool
Profile Fields" have a "conflictResolve" field which "If not
specified, defaults to GEO."
https://portal.ultradns.com/static/docs/REST-API_User_Guide.pdf

But UltraDNS does not actually return a conflictResolve
attribute when it has been updated to "GEO".

We could fix it in udnssdk, but that would require either:
* hide the response by coercing "" to "GEO" for everyone
* use a pointer to allow checking for nil (requires all
users to change if they fix this)

An ideal solution would be to have the UltraDNS API respond
with this attribute for every dirpool's rdata.

So at the risk of foolish consistency in the sdk, we're
going to solve it where it's visible to the user:
by checking and overriding the parsing. I'm sorry.

* ultradns_record: convert rdata to set

UltraDNS does not store the ordering of rdata elements, so we need a way
to identify if changes have been made even it the order changes.
A perfect job for schema.Set.

* ultradns_record: parse double-encoded answers for TXT records

* ultradns: simplify hashLimits()

Limits blocks only have the "name" attribute as their primary key, so
hashLimits() needn't use a buffer to concatenate.

* ultradns_dirpool.description: validate

* ultradns_dirpool.rdata: doc need for set

* ultradns_dirpool.conflict_resolve: validate
This commit is contained in:
Joseph Anthony Pasquale Holsten 2016-12-15 08:28:34 -08:00 committed by Paul Stack
parent b215e7e9e6
commit d783e831f8
37 changed files with 3719 additions and 615 deletions

View File

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

View File

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

View File

@ -22,7 +22,7 @@ func (c *Config) Client() (*udnssdk.Client, error) {
return nil, fmt.Errorf("Error setting up client: %s", err) 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 return client, nil
} }

View File

@ -1,6 +1,7 @@
package ultradns package ultradns
import ( import (
"github.com/Ensighten/udnssdk"
"github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
@ -24,14 +25,19 @@ func Provider() terraform.ResourceProvider {
}, },
"baseurl": &schema.Schema{ "baseurl": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Required: true, Optional: true,
DefaultFunc: schema.EnvDefaultFunc("ULTRADNS_BASEURL", nil), 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{ 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, ConfigureFunc: providerConfigure,

View File

@ -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()
}

View File

@ -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"
}
}
}
}
`

View File

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

View File

@ -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"]
}
`

View File

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

View File

@ -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"]
}
`

View File

@ -1,6 +1,7 @@
package ultradns package ultradns
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"strconv" "strconv"
@ -10,18 +11,11 @@ import (
"github.com/hashicorp/terraform/helper/schema" "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) { func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) {
r := rRSetResource{} r := rRSetResource{}
// TODO: return error if required attributes aren't ok
if attr, ok := d.GetOk("name"); ok { if attr, ok := d.GetOk("name"); ok {
r.OwnerName = attr.(string) r.OwnerName = attr.(string)
} }
@ -35,7 +29,7 @@ func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) {
} }
if attr, ok := d.GetOk("rdata"); ok { if attr, ok := d.GetOk("rdata"); ok {
rdata := attr.([]interface{}) rdata := attr.(*schema.Set).List()
r.RData = make([]string, len(rdata)) r.RData = make([]string, len(rdata))
for i, j := range rdata { for i, j := range rdata {
r.RData[i] = j.(string) r.RData[i] = j.(string)
@ -49,33 +43,30 @@ func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) {
return r, nil 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 { func populateResourceDataFromRRSet(r udnssdk.RRSet, d *schema.ResourceData) error {
zone := d.Get("zone") zone := d.Get("zone")
typ := d.Get("type")
// ttl // ttl
d.Set("ttl", r.TTL) d.Set("ttl", r.TTL)
// rdata // 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 { if err != nil {
return fmt.Errorf("ultradns_record.rdata set failed: %#v", err) 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 return nil
} }
func resourceUltraDNSRecord() *schema.Resource { func resourceUltradnsRecord() *schema.Resource {
return &schema.Resource{ return &schema.Resource{
Create: resourceUltraDNSRecordCreate, Create: resourceUltraDNSRecordCreate,
Read: resourceUltraDNSRecordRead, Read: resourceUltraDNSRecordRead,
@ -117,7 +108,8 @@ func resourceUltraDNSRecord() *schema.Resource {
ForceNew: true, ForceNew: true,
}, },
"rdata": &schema.Schema{ "rdata": &schema.Schema{
Type: schema.TypeList, Type: schema.TypeSet,
Set: schema.HashString,
Required: true, Required: true,
Elem: &schema.Schema{Type: schema.TypeString}, Elem: &schema.Schema{Type: schema.TypeString},
}, },
@ -136,6 +128,8 @@ func resourceUltraDNSRecord() *schema.Resource {
} }
} }
// CRUD Operations
func resourceUltraDNSRecordCreate(d *schema.ResourceData, meta interface{}) error { func resourceUltraDNSRecordCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*udnssdk.Client) client := meta.(*udnssdk.Client)
@ -144,14 +138,14 @@ func resourceUltraDNSRecordCreate(d *schema.ResourceData, meta interface{}) erro
return err 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()) _, err = client.RRSets.Create(r.RRSetKey(), r.RRSet())
if err != nil { if err != nil {
return fmt.Errorf("Failed to create UltraDNS RRSet: %s", err) return fmt.Errorf("create failed: %v", err)
} }
d.SetId(r.ID()) 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) return resourceUltraDNSRecordRead(d, meta)
} }
@ -174,10 +168,10 @@ func resourceUltraDNSRecordRead(d *schema.ResourceData, meta interface{}) error
d.SetId("") d.SetId("")
return nil 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] rec := rrsets[0]
return populateResourceDataFromRRSet(rec, d) return populateResourceDataFromRRSet(rec, d)
@ -191,10 +185,10 @@ func resourceUltraDNSRecordUpdate(d *schema.ResourceData, meta interface{}) erro
return err 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()) _, err = client.RRSets.Update(r.RRSetKey(), r.RRSet())
if err != nil { if err != nil {
return fmt.Errorf("ultradns_record update failed: %s", err) return fmt.Errorf("update failed: %v", err)
} }
return resourceUltraDNSRecordRead(d, meta) return resourceUltraDNSRecordRead(d, meta)
@ -208,11 +202,13 @@ func resourceUltraDNSRecordDelete(d *schema.ResourceData, meta interface{}) erro
return err 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()) _, err = client.RRSets.Delete(r.RRSetKey())
if err != nil { if err != nil {
return fmt.Errorf("ultradns_record delete failed: %s", err) return fmt.Errorf("delete failed: %v", err)
} }
return nil return nil
} }
// Conversion helper functions

View File

@ -2,7 +2,6 @@ package ultradns
import ( import (
"fmt" "fmt"
"os"
"testing" "testing"
"github.com/Ensighten/udnssdk" "github.com/Ensighten/udnssdk"
@ -10,72 +9,74 @@ import (
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
) )
func TestAccUltraDNSRecord_Basic(t *testing.T) { func TestAccUltradnsRecord(t *testing.T) {
var record udnssdk.RRSet var record udnssdk.RRSet
domain := os.Getenv("ULTRADNS_DOMAIN") // domain := os.Getenv("ULTRADNS_DOMAIN")
domain := "ultradns.phinze.com"
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckUltraDNSRecordDestroy, CheckDestroy: testAccRecordCheckDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigBasic, domain), Config: fmt.Sprintf(testCfgRecordMinimal, domain),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), testAccCheckUltradnsRecordExists("ultradns_record.it", &record),
testAccCheckUltraDNSRecordAttributes(&record), resource.TestCheckResourceAttr("ultradns_record.it", "zone", domain),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr("ultradns_record.it", "name", "test-record"),
"ultradns_record.foobar", "name", "terraform"), resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3994963683", "10.5.0.1"),
resource.TestCheckResourceAttr( ),
"ultradns_record.foobar", "zone", domain), },
resource.TestCheckResourceAttr( resource.TestStep{
"ultradns_record.foobar", "rdata.0", "192.168.0.10"), 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 var record udnssdk.RRSet
domain := os.Getenv("ULTRADNS_DOMAIN") // domain := os.Getenv("ULTRADNS_DOMAIN")
domain := "ultradns.phinze.com"
resource.Test(t, resource.TestCase{ resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) }, PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders, Providers: testAccProviders,
CheckDestroy: testAccCheckUltraDNSRecordDestroy, CheckDestroy: testAccRecordCheckDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
resource.TestStep{ resource.TestStep{
Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigBasic, domain), Config: fmt.Sprintf(testCfgRecordTXTMinimal, domain),
Check: resource.ComposeTestCheckFunc( Check: resource.ComposeTestCheckFunc(
testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), testAccCheckUltradnsRecordExists("ultradns_record.it", &record),
testAccCheckUltraDNSRecordAttributes(&record), resource.TestCheckResourceAttr("ultradns_record.it", "zone", domain),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr("ultradns_record.it", "name", "test-record-txt"),
"ultradns_record.foobar", "name", "terraform"), resource.TestCheckResourceAttr("ultradns_record.it", "rdata.1447448707", "simple answer"),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3337444205", "backslash answer \\"),
"ultradns_record.foobar", "zone", domain), resource.TestCheckResourceAttr("ultradns_record.it", "rdata.3135730072", "quote answer \""),
resource.TestCheckResourceAttr( resource.TestCheckResourceAttr("ultradns_record.it", "rdata.126343430", "complex answer \\ \""),
"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"),
), ),
}, },
}, },
}) })
} }
func testAccCheckUltraDNSRecordDestroy(s *terraform.State) error { func testAccRecordCheckDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*udnssdk.Client) client := testAccProvider.Meta().(*udnssdk.Client)
for _, rs := range s.RootModule().Resources { for _, rs := range s.RootModule().Resources {
@ -99,79 +100,40 @@ func testAccCheckUltraDNSRecordDestroy(s *terraform.State) error {
return nil return nil
} }
func testAccCheckUltraDNSRecordAttributes(record *udnssdk.RRSet) resource.TestCheckFunc { const testCfgRecordMinimal = `
return func(s *terraform.State) error { resource "ultradns_record" "it" {
zone = "%s"
name = "test-record"
if record.RData[0] != "192.168.0.10" { rdata = ["10.5.0.1"]
return fmt.Errorf("Bad content: %v", record.RData) type = "A"
} ttl = 3600
return nil
}
} }
`
func testAccCheckUltraDNSRecordAttributesUpdated(record *udnssdk.RRSet) resource.TestCheckFunc { const testCfgRecordUpdated = `
return func(s *terraform.State) error { resource "ultradns_record" "it" {
zone = "%s"
name = "test-record"
if record.RData[0] != "192.168.0.11" { rdata = ["10.5.0.2"]
return fmt.Errorf("Bad content: %v", record.RData) type = "A"
} ttl = 3600
return nil
}
} }
`
func testAccCheckUltraDNSRecordExists(n string, record *udnssdk.RRSet) resource.TestCheckFunc { const testCfgRecordTXTMinimal = `
return func(s *terraform.State) error { resource "ultradns_record" "it" {
rs, ok := s.RootModule().Resources[n] zone = "%s"
name = "test-record-txt"
if !ok { rdata = [
return fmt.Errorf("Not found: %s", n) "simple answer",
} "backslash answer \\",
"quote answer \"",
if rs.Primary.ID == "" { "complex answer \\ \"",
return fmt.Errorf("No Record ID is set") ]
} type = "TXT"
ttl = 3600
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
}
} }
`
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
}`

View File

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

View File

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

47
vendor/github.com/Ensighten/udnssdk/CHANGELOG.md generated vendored Normal file
View File

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

112
vendor/github.com/Ensighten/udnssdk/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,112 @@
# Contributing
Want to contribute? Up-to-date pointers should be at:
<http://contributing.appspot.com/udnssdk>
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: <joseph@josephholsten.com>.
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

20
vendor/github.com/Ensighten/udnssdk/LICENSE generated vendored Normal file
View File

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

View File

@ -2,6 +2,7 @@ package udnssdk
import ( import (
"fmt" "fmt"
"net/http"
) )
// AccountsService provides access to account resources // AccountsService provides access to account resources
@ -43,7 +44,7 @@ func AccountsURI() string {
} }
// Select requests all Accounts of user // Select requests all Accounts of user
func (s *AccountsService) Select() ([]Account, *Response, error) { func (s *AccountsService) Select() ([]Account, *http.Response, error) {
var ald AccountListDTO var ald AccountListDTO
res, err := s.client.get(AccountsURI(), &ald) res, err := s.client.get(AccountsURI(), &ald)
@ -55,13 +56,13 @@ func (s *AccountsService) Select() ([]Account, *Response, error) {
} }
// Find requests an Account by AccountKey // 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 var t Account
res, err := s.client.get(k.URI(), &t) res, err := s.client.get(k.URI(), &t)
return t, res, err return t, res, err
} }
// Delete requests deletion of an Account by AccountKey // 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) return s.client.delete(k.URI(), nil)
} }

View File

@ -2,6 +2,7 @@ package udnssdk
import ( import (
"log" "log"
"net/http"
"time" "time"
) )
@ -21,6 +22,17 @@ type ProbeAlertDataDTO struct {
Status string `json:"status"` 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 // ProbeAlertDataListDTO wraps the response for an index of probe alerts
type ProbeAlertDataListDTO struct { type ProbeAlertDataListDTO struct {
Alerts []ProbeAlertDataDTO `json:"alerts"` Alerts []ProbeAlertDataDTO `json:"alerts"`
@ -42,7 +54,7 @@ func (s *AlertsService) Select(k RRSetKey) ([]ProbeAlertDataDTO, error) {
for { for {
reqAlerts, ri, res, err := s.SelectWithOffset(k, offset) reqAlerts, ri, res, err := s.SelectWithOffset(k, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) 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 // 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 var ald ProbeAlertDataListDTO
uri := k.AlertsQueryURI(offset) uri := k.AlertsQueryURI(offset)

View File

@ -1,7 +1,9 @@
package udnssdk package udnssdk
import "net/http"
// GetResultByURI just requests a URI // 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) req, err := c.NewRequest("GET", uri, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@ -9,7 +11,7 @@ func (c *Client) GetResultByURI(uri string) (*Response, error) {
res, err := c.HTTPClient.Do(req) res, err := c.HTTPClient.Do(req)
if err != nil { if err != nil {
return &Response{Response: res}, err return res, err
} }
return &Response{Response: res}, err return res, err
} }

View File

@ -3,6 +3,7 @@ package udnssdk
import ( import (
"fmt" "fmt"
"log" "log"
"net/http"
"time" "time"
) )
@ -28,10 +29,10 @@ type AccountLevelGeoDirectionalGroupDTO struct {
// IPAddrDTO wraps an IP address range or CIDR block // IPAddrDTO wraps an IP address range or CIDR block
type IPAddrDTO struct { type IPAddrDTO struct {
Start string `json:"start,omitempty"` Start string `json:"start,omitempty" terraform:"start"`
End string `json:"end,omitempty"` End string `json:"end,omitempty" terraform:"end"`
CIDR string `json:"cidr,omitempty"` CIDR string `json:"cidr,omitempty" terraform:"cidr"`
Address string `json:"address,omitempty"` Address string `json:"address,omitempty" terraform:"address"`
} }
// AccountLevelIPDirectionalGroupDTO wraps an account-level, IP directional-group response // AccountLevelIPDirectionalGroupDTO wraps an account-level, IP directional-group response
@ -146,7 +147,7 @@ func (s *GeoDirectionalPoolsService) Select(k GeoDirectionalPoolKey, query strin
for { for {
reqDtos, ri, res, err := s.SelectWithOffset(k, query, offset) reqDtos, ri, res, err := s.SelectWithOffset(k, query, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) 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 // 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 var tld AccountLevelGeoDirectionalGroupListDTO
res, err := s.client.get(k.QueryURI(query, offset), &tld) 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 // 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 var t AccountLevelGeoDirectionalGroupDTO
res, err := s.client.get(k.URI(), &t) res, err := s.client.get(k.URI(), &t)
return t, res, err return t, res, err
} }
// Create requests creation of a DirectionalPool by DirectionalPoolKey given a directional-pool // 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) return s.client.post(k.URI(), val, nil)
} }
// Update requests update of a DirectionalPool by DirectionalPoolKey given a directional-pool // 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) return s.client.put(k.URI(), val, nil)
} }
// Delete requests deletion of a DirectionalPool // 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) return s.client.delete(k.URI(), nil)
} }
@ -247,7 +248,7 @@ func (s *IPDirectionalPoolsService) Select(k IPDirectionalPoolKey, query string)
for { for {
reqIPGroups, ri, res, err := s.SelectWithOffset(k, query, offset) reqIPGroups, ri, res, err := s.SelectWithOffset(k, query, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) 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 // 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 var tld AccountLevelIPDirectionalGroupListDTO
res, err := s.client.get(k.QueryURI(query, offset), &tld) 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 // 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 var t AccountLevelIPDirectionalGroupDTO
res, err := s.client.get(k.URI(), &t) res, err := s.client.get(k.URI(), &t)
return t, res, err return t, res, err
} }
// Create requests creation of a DirectionalPool by DirectionalPoolKey given a directional-pool // 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) return s.client.post(k.URI(), val, nil)
} }
// Update requests update of a DirectionalPool by DirectionalPoolKey given a directional-pool // 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) return s.client.put(k.URI(), val, nil)
} }
// Delete deletes an directional-pool // 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) return s.client.delete(k.URI(), nil)
} }

View File

@ -3,6 +3,7 @@ package udnssdk
import ( import (
"fmt" "fmt"
"log" "log"
"net/http"
"time" "time"
) )
@ -65,7 +66,7 @@ func (s *EventsService) Select(r RRSetKey, query string) ([]EventInfoDTO, error)
for { for {
reqEvents, ri, res, err := s.SelectWithOffset(r, query, offset) reqEvents, ri, res, err := s.SelectWithOffset(r, query, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) 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 // 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 var tld EventInfoListDTO
uri := r.EventsQueryURI(query, offset) 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 // 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 var t EventInfoDTO
res, err := s.client.get(e.URI(), &t) res, err := s.client.get(e.URI(), &t)
return t, res, err return t, res, err
} }
// Create requests creation of an event by RRSetKey, with provided event-info, returning actual response or an error // 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) 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 // 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) return s.client.put(e.URI(), ev, nil)
} }
// Delete requests deletion of an event by EventKey, returning the actual response or an error // 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) return s.client.delete(e.URI(), nil)
} }

View File

@ -3,6 +3,7 @@ package udnssdk
import ( import (
"fmt" "fmt"
"log" "log"
"net/http"
"time" "time"
) )
@ -60,7 +61,7 @@ func (k NotificationKey) URI() string {
} }
// Select requests all notifications by RRSetKey and optional query, using pagination and error handling // 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 // TODO: Sane Configuration for timeouts / retries
maxerrs := 5 maxerrs := 5
waittime := 5 * time.Second waittime := 5 * time.Second
@ -73,7 +74,7 @@ func (s *NotificationsService) Select(k RRSetKey, query string) ([]NotificationD
for { for {
reqNotifications, ri, res, err := s.SelectWithOffset(k, query, offset) reqNotifications, ri, res, err := s.SelectWithOffset(k, query, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) 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 // 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 var tld NotificationListDTO
uri := k.NotificationsQueryURI(query, offset) 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 // 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 var t NotificationDTO
res, err := s.client.get(k.URI(), &t) res, err := s.client.get(k.URI(), &t)
return t, res, err return t, res, err
} }
// Create requests creation of an event by RRSetKey, with provided NotificationInfoDTO, returning actual response or an error // 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) 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 // 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) return s.client.put(k.URI(), n, nil)
} }
// Delete requests deletion of an event by NotificationKey, returning the actual response or an error // 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) return s.client.delete(k.URI(), nil)
} }

View File

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

View File

@ -3,14 +3,26 @@ package udnssdk
import ( import (
"encoding/json" "encoding/json"
"fmt" "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 // ProbeInfoDTO wraps a probe response
type ProbeInfoDTO struct { type ProbeInfoDTO struct {
ID string `json:"id"` ID string `json:"id,omitempty"`
PoolRecord string `json:"poolRecord"` PoolRecord string `json:"poolRecord,omitempty"`
ProbeType string `json:"type"` ProbeType ProbeType `json:"type"`
Interval string `json:"interval"` Interval string `json:"interval"`
Agents []string `json:"agents"` Agents []string `json:"agents"`
Threshold int `json:"threshold"` Threshold int `json:"threshold"`
@ -28,7 +40,7 @@ type ProbeDetailsLimitDTO struct {
type ProbeDetailsDTO struct { type ProbeDetailsDTO struct {
data []byte data []byte
Detail interface{} `json:"detail,omitempty"` Detail interface{} `json:"detail,omitempty"`
typ string typ ProbeType
} }
// GetData returns the data because I'm working around something. // 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 // an appropriate datatype. These are helper structures and functions for testing
// and direct API use. In the Terraform implementation, we will use Terraforms own // and direct API use. In the Terraform implementation, we will use Terraforms own
// warped schema structure to handle the marshalling and unmarshalling. // warped schema structure to handle the marshalling and unmarshalling.
func (s *ProbeDetailsDTO) Populate(typ string) (err error) { func (s *ProbeDetailsDTO) Populate(t ProbeType) (err error) {
// TODO: actually document s.typ = t
switch strings.ToUpper(typ) { d, err := s.GetDetailsObject(t)
case "HTTP": if err != nil {
var pp HTTPProbeDetailsDTO
err = json.Unmarshal(s.data, &pp)
s.typ = typ
s.Detail = pp
return err 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 // 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 // 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 var pld ProbeListDTO
// This API does not support pagination. // 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 // 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 var t ProbeInfoDTO
res, err := s.client.get(k.URI(), &t) res, err := s.client.get(k.URI(), &t)
return t, res, err return t, res, err
} }
// Create creates a probe with a RRSetKey using the ProbeInfoDTO dp // 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) return s.client.post(k.ProbesURI(), dp, nil)
} }
// Update updates a probe given a ProbeKey with the ProbeInfoDTO dp // 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) return s.client.put(k.URI(), dp, nil)
} }
// Delete deletes a probe by its ProbeKey // 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) return s.client.delete(k.URI(), nil)
} }

View File

@ -1,103 +1,81 @@
# udnssdk - A ultradns SDK for GoLang # udnssdk - An UltraDNS SDK for Go
## about
This is a golang 'SDK' for UltraDNS that I copapasta'd from weppos/dnsimple. 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.
It seemed like an ideal donor since the use case is terraform.
## How It works: Full API docs are available at [godoc](https://godoc.org/github.com/Ensighten/udnssdk)
client := udnssdk.NewClient("username","password",udnssdk.DefaultTestBaseURL)
There is DefaultTestBaseURL and DefaultLiveBaseURL. ## Example
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.
### RRSet Declaration ```go
package main
type RRSet struct { import (
OwnerName string `json:"ownerName"` "fmt"
RRType string `json:"rrtype"` "log"
TTL int `json:"ttl"`
RData []string `json:"rdata"`
}
###GetRRSets(DomainName, RecordName(leave blank for all), RecordType[A|CNAME|ANY]) "github.com/Ensighten/udnssdk"
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")
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) fmt.Printf("---- Update RRSet\n")
rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.1"}} rrset = udnssdk.RRSet{
resp2,err2 := client.Zones.CreateRRSet("ensighten.com",*rr1) 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) fmt.Printf("---- Delete RRSet\n")
UpdateRRSet requires you to specify the complete RRSet for the update. This implementation does not support PATCHing. 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"}} ## Thanks
resp2,err2 := client.Zones.CreateRRSet("domain.com",*rr1)
###DeleteRRSet(DomainName, RRSet) * Originally started as a modified version of [weppos/go-dnsimple](https://github.com/weppos/go-dnsimple)
Delete RRSet only uses the ownerName and RRType values from the RRSet object. * 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)
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)

View File

@ -1,10 +1,13 @@
package udnssdk package udnssdk
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"net/http"
"time" "time"
"github.com/fatih/structs"
"github.com/mitchellh/mapstructure"
) )
// RRSetsService provides access to RRSet resources // 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 // 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 // ProfileSchema are the schema URIs for RRSet Profiles
type ProfileSchema string type ProfileSchema string
@ -38,34 +31,150 @@ const (
TCPoolSchema = "http://schemas.ultradns.com/TCPool.jsonschema" 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 // DirPoolProfile wraps a Profile for a Directional Pool
type DirPoolProfile struct { type DirPoolProfile struct {
Context ProfileSchema `json:"@context"` Context ProfileSchema `json:"@context"`
Description string `json:"description"` Description string `json:"description"`
ConflictResolve string `json:"conflictResolve,omitempty"` ConflictResolve string `json:"conflictResolve,omitempty"`
RDataInfo []DPRDataInfo `json:"rdataInfo"` RDataInfo []DPRDataInfo `json:"rdataInfo"`
NoResponse DPRDataInfo `json:"noResponse"` NoResponse DPRDataInfo `json:"noResponse,omitempty"`
} }
// DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response // DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response
type DPRDataInfo struct { type DPRDataInfo struct {
AllNonConfigured bool `json:"allNonConfigured,omitempty"` AllNonConfigured bool `json:"allNonConfigured,omitempty" terraform:"all_non_configured"`
IPInfo IPInfo `json:"ipInfo,omitempty"` IPInfo *IPInfo `json:"ipInfo,omitempty" terraform:"ip_info"`
GeoInfo GeoInfo `json:"geoInfo,omitempty"` GeoInfo *GeoInfo `json:"geoInfo,omitempty" terraform:"geo_info"`
} }
// IPInfo wraps the ipInfo object of a DPRDataInfo // IPInfo wraps the ipInfo object of a DPRDataInfo
type IPInfo struct { type IPInfo struct {
Name string `json:"name"` Name string `json:"name" terraform:"name"`
IsAccountLevel bool `json:"isAccountLevel,omitempty"` IsAccountLevel bool `json:"isAccountLevel,omitempty" terraform:"is_account_level"`
Ips []IPAddrDTO `json:"ips"` Ips []IPAddrDTO `json:"ips,omitempty" terraform:"-"`
} }
// GeoInfo wraps the geoInfo object of a DPRDataInfo // GeoInfo wraps the geoInfo object of a DPRDataInfo
type GeoInfo struct { type GeoInfo struct {
Name string `json:"name"` Name string `json:"name" terraform:"name"`
IsAccountLevel bool `json:"isAccountLevel,omitempty"` IsAccountLevel bool `json:"isAccountLevel,omitempty" terraform:"is_account_level"`
Codes []string `json:"codes"` Codes []string `json:"codes,omitempty" terraform:"-"`
} }
// RDPoolProfile wraps a Profile for a Resource Distribution pool // RDPoolProfile wraps a Profile for a Resource Distribution pool
@ -79,8 +188,8 @@ type RDPoolProfile struct {
type SBPoolProfile struct { type SBPoolProfile struct {
Context ProfileSchema `json:"@context"` Context ProfileSchema `json:"@context"`
Description string `json:"description"` Description string `json:"description"`
RunProbes bool `json:"runProbes,omitempty"` RunProbes bool `json:"runProbes"`
ActOnProbes bool `json:"actOnProbes,omitempty"` ActOnProbes bool `json:"actOnProbes"`
Order string `json:"order,omitempty"` Order string `json:"order,omitempty"`
MaxActive int `json:"maxActive,omitempty"` MaxActive int `json:"maxActive,omitempty"`
MaxServed int `json:"maxServed,omitempty"` MaxServed int `json:"maxServed,omitempty"`
@ -91,7 +200,7 @@ type SBPoolProfile struct {
// SBRDataInfo wraps the rdataInfo object of a SBPoolProfile // SBRDataInfo wraps the rdataInfo object of a SBPoolProfile
type SBRDataInfo struct { type SBRDataInfo struct {
State string `json:"state"` State string `json:"state"`
RunProbes bool `json:"runProbes,omitempty"` RunProbes bool `json:"runProbes"`
Priority int `json:"priority"` Priority int `json:"priority"`
FailoverDelay int `json:"failoverDelay,omitempty"` FailoverDelay int `json:"failoverDelay,omitempty"`
Threshold int `json:"threshold"` Threshold int `json:"threshold"`
@ -100,7 +209,7 @@ type SBRDataInfo struct {
// BackupRecord wraps the backupRecord objects of an SBPoolProfile response // BackupRecord wraps the backupRecord objects of an SBPoolProfile response
type BackupRecord struct { type BackupRecord struct {
RData string `json:"rdata"` RData string `json:"rdata,omitempty"`
FailoverDelay int `json:"failoverDelay,omitempty"` FailoverDelay int `json:"failoverDelay,omitempty"`
} }
@ -108,109 +217,20 @@ type BackupRecord struct {
type TCPoolProfile struct { type TCPoolProfile struct {
Context ProfileSchema `json:"@context"` Context ProfileSchema `json:"@context"`
Description string `json:"description"` Description string `json:"description"`
RunProbes bool `json:"runProbes,omitempty"` RunProbes bool `json:"runProbes"`
ActOnProbes bool `json:"actOnProbes,omitempty"` ActOnProbes bool `json:"actOnProbes"`
MaxToLB int `json:"maxToLB,omitempty"` MaxToLB int `json:"maxToLB,omitempty"`
RDataInfo []SBRDataInfo `json:"rdataInfo"` RDataInfo []SBRDataInfo `json:"rdataInfo"`
BackupRecord BackupRecord `json:"backupRecord"` BackupRecord *BackupRecord `json:"backupRecord,omitempty"`
}
// 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)
}
} }
// RRSet wraps an RRSet resource // RRSet wraps an RRSet resource
type RRSet struct { type RRSet struct {
OwnerName string `json:"ownerName"` OwnerName string `json:"ownerName"`
RRType string `json:"rrtype"` RRType string `json:"rrtype"`
TTL int `json:"ttl"` TTL int `json:"ttl"`
RData []string `json:"rdata"` RData []string `json:"rdata"`
Profile *StringProfile `json:"profile,omitempty"` Profile RawProfile `json:"profile,omitempty"`
} }
// RRSetListDTO wraps a list of RRSet resources // RRSetListDTO wraps a list of RRSet resources
@ -323,7 +343,7 @@ func (s *RRSetsService) Select(k RRSetKey) ([]RRSet, error) {
for { for {
reqRrsets, ri, res, err := s.SelectWithOffset(k, offset) reqRrsets, ri, res, err := s.SelectWithOffset(k, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) time.Sleep(waittime)
@ -346,7 +366,7 @@ func (s *RRSetsService) Select(k RRSetKey) ([]RRSet, error) {
} }
// SelectWithOffset requests zone rrsets by RRSetKey & optional offset // 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 var rrsld RRSetListDTO
uri := k.QueryURI(offset) uri := k.QueryURI(offset)
@ -360,18 +380,18 @@ func (s *RRSetsService) SelectWithOffset(k RRSetKey, offset int) ([]RRSet, Resul
} }
// Create creates an rrset with val // 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{} var ignored interface{}
return s.client.post(k.URI(), rrset, &ignored) return s.client.post(k.URI(), rrset, &ignored)
} }
// Update updates a RRSet with the provided val // 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{} var ignored interface{}
return s.client.put(k.URI(), val, &ignored) return s.client.put(k.URI(), val, &ignored)
} }
// Delete deletes an RRSet // 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) return s.client.delete(k.URI(), nil)
} }

View File

@ -3,6 +3,7 @@ package udnssdk
import ( import (
"fmt" "fmt"
"log" "log"
"net/http"
"time" "time"
) )
@ -65,7 +66,7 @@ func (s *TasksService) Select(query string) ([]Task, error) {
for { for {
reqDtos, ri, res, err := s.SelectWithOffset(query, offset) reqDtos, ri, res, err := s.SelectWithOffset(query, offset)
if err != nil { if err != nil {
if res.StatusCode >= 500 { if res != nil && res.StatusCode >= 500 {
errcnt = errcnt + 1 errcnt = errcnt + 1
if errcnt < maxerrs { if errcnt < maxerrs {
time.Sleep(waittime) 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 // 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 var tld TaskListDTO
uri := TasksQueryURI(query, offset) 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. // 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 var tv Task
res, err := s.client.get(t.URI(), &tv) res, err := s.client.get(t.URI(), &tv)
return tv, res, err return tv, res, err
} }
// FindResult requests // 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()) return s.client.GetResultByURI(t.ResultURI())
} }
// FindResultByTask requests a task by the provided task's result uri // 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) return s.client.GetResultByURI(t.ResultURI)
} }
// Delete requests deletions // 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) return s.client.delete(t.URI(), nil)
} }

26
vendor/github.com/Ensighten/udnssdk/token_source.go generated vendored Normal file
View File

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

View File

@ -11,8 +11,11 @@ import (
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"net/url"
"time" "time"
"golang.org/x/oauth2"
"github.com/Ensighten/udnssdk/passwordcredentials"
) )
const ( const (
@ -46,17 +49,10 @@ type ResultInfo struct {
type Client struct { type Client struct {
// This is our client structure. // This is our client structure.
HTTPClient *http.Client HTTPClient *http.Client
Config *passwordcredentials.Config
// UltraDNS makes a call to an authorization API using your username and BaseURL string
// password, returning an 'Access Token' and a 'Refresh Token'. UserAgent string
// 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
// Accounts API // Accounts API
Accounts *AccountsService Accounts *AccountsService
@ -78,18 +74,14 @@ type Client struct {
// NewClient returns a new ultradns API client. // NewClient returns a new ultradns API client.
func NewClient(username, password, BaseURL string) (*Client, error) { func NewClient(username, password, BaseURL string) (*Client, error) {
accesstoken, refreshtoken, err := GetAuthTokens(username, password, BaseURL) ctx := oauth2.NoContext
if err != nil { conf := NewConfig(username, password, BaseURL)
return nil, err
}
c := &Client{ c := &Client{
AccessToken: accesstoken, HTTPClient: conf.Client(ctx),
RefreshToken: refreshtoken, BaseURL: BaseURL,
Username: username, UserAgent: userAgent,
Password: password, Config: conf,
HTTPClient: &http.Client{},
BaseURL: BaseURL,
UserAgent: userAgent,
} }
c.Accounts = &AccountsService{client: c} c.Accounts = &AccountsService{client: c}
c.Alerts = &AlertsService{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. // 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{ c := &Client{
AccessToken: accesstoken, HTTPClient: &http.Client{},
RefreshToken: refreshtoken, BaseURL: BaseURL,
Username: username, UserAgent: userAgent,
Password: password,
HTTPClient: &http.Client{},
BaseURL: BaseURL,
UserAgent: userAgent,
} }
c.Accounts = &AccountsService{client: c} c.Accounts = &AccountsService{client: c}
c.Alerts = &AlertsService{client: c} c.Alerts = &AlertsService{client: c}
@ -124,51 +112,6 @@ func newStubClient(username, password, BaseURL, accesstoken, refreshtoken string
return c, nil 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. // NewRequest creates an API request.
// The path is expected to be a relative path and will be resolved // 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. // 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.Set("Content-Type", "application/json")
req.Header.Add("Accept", "application/json") req.Header.Add("Accept", "application/json")
req.Header.Add("User-Agent", c.UserAgent) 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 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) 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) 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) 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) 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. // 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, // If v implements the io.Writer interface, the raw response body will be written to v,
// without attempting to decode it. // 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) req, err := c.NewRequest(method, path, payload)
if err != nil { if err != nil {
return nil, err return nil, err
} }
log.Printf("[DEBUG] HTTP Request: %+v\n", req) log.Printf("[DEBUG] HTTP Request: %+v\n", req)
res, err := c.HTTPClient.Do(req) r, err := hc.Do(req)
log.Printf("[DEBUG] HTTP Response: %+v\n", res) log.Printf("[DEBUG] HTTP Response: %+v\n", r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer res.Body.Close() defer r.Body.Close()
origresponse := &Response{Response: res}
var nres *http.Response if r.StatusCode == 202 {
nres = res
if res.StatusCode == 202 {
// This is a deferred task. // 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) log.Printf("[DEBUG] Received Async Task %+v.. will retry...\n", tid)
// TODO: Sane Configuration for timeouts / retries // TODO: Sane Configuration for timeouts / retries
timeout := 5 timeout := 5
@ -244,51 +183,45 @@ func (c *Client) Do(method, path string, payload, v interface{}) (*Response, err
i := 0 i := 0
breakmeout := false breakmeout := false
for i < timeout || breakmeout { for i < timeout || breakmeout {
myt, statusres, err := c.Tasks.Find(tid) t, _, err := c.Tasks.Find(tid)
if err != nil { 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) log.Printf("[DEBUG] Task ID: %+v Retry: %d Status Code: %s\n", tid, i, t.TaskStatusCode)
switch myt.TaskStatusCode { switch t.TaskStatusCode {
case "COMPLETE": case "COMPLETE":
// Yay // Yay
tres, err := c.Tasks.FindResultByTask(myt) resp, err := c.Tasks.FindResultByTask(t)
if err != nil { if err != nil {
return origresponse, err return nil, err
} }
nres = tres.Response r = resp
breakmeout = true breakmeout = true
case "PENDING", "IN_PROCESS": case "PENDING", "IN_PROCESS":
i = i + 1 i = i + 1
time.Sleep(waittime) time.Sleep(waittime)
continue continue
case "ERROR": case "ERROR":
return statusres, err return nil, err
} }
} }
} }
response := &Response{Response: nres}
err = CheckResponse(nres) err = CheckResponse(r)
if err != nil { if err != nil {
return response, err return r, err
} }
if v != nil { if v != nil {
if w, ok := v.(io.Writer); ok { if w, ok := v.(io.Writer); ok {
io.Copy(w, res.Body) io.Copy(w, r.Body)
} else { } else {
err = json.NewDecoder(res.Body).Decode(v) err = json.NewDecoder(r.Body).Decode(v)
// err = json.Unmarshal(r.Body, v)
} }
} }
return response, err return r, err
}
// A Response represents an API response.
type Response struct {
*http.Response
} }
// ErrorResponse represents an error caused by an API request. // 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) 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. // 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 // 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. // may have additional requirements, but this is sufficient in most of the cases.

4
vendor/vendor.json vendored
View File

@ -312,8 +312,10 @@
"revisionTime": "2016-12-01T15:35:21Z" "revisionTime": "2016-12-01T15:35:21Z"
}, },
{ {
"checksumSHA1": "nomT+8bvze/Qmc0tK0r0mwgHV6M=",
"path": "github.com/Ensighten/udnssdk", "path": "github.com/Ensighten/udnssdk",
"revision": "0290933f5e8afd933f2823fce32bf2847e6ea603" "revision": "9f1218928b30c6dec7f2c184c47286abc325deb9",
"revisionTime": "2016-06-13T20:05:45Z"
}, },
{ {
"checksumSHA1": "B+GonfgNwOAJMEe0WUHQGATRtMA=", "checksumSHA1": "B+GonfgNwOAJMEe0WUHQGATRtMA=",

View File

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

View File

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

View File

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

View File

@ -3,12 +3,12 @@ layout: "ultradns"
page_title: "UltraDNS: ultradns_record" page_title: "UltraDNS: ultradns_record"
sidebar_current: "docs-ultradns-resource-record" sidebar_current: "docs-ultradns-resource-record"
description: |- description: |-
Provides a UltraDNS record resource. Provides an UltraDNS record resource.
--- ---
# ultradns\_record # ultradns\_record
Provides a UltraDNS record resource. Provides an UltraDNS record resource.
## Example Usage ## Example Usage

View File

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

View File

@ -3,19 +3,31 @@
<div class="docs-sidebar hidden-print affix-top" role="complementary"> <div class="docs-sidebar hidden-print affix-top" role="complementary">
<ul class="nav docs-sidenav"> <ul class="nav docs-sidenav">
<li<%= sidebar_current("docs-home") %>> <li<%= sidebar_current("docs-home") %>>
<a href="/docs/providers/index.html">&laquo; Documentation Home</a> <a href="/docs/providers/index.html">&laquo; Documentation Home</a>
</li> </li>
<li<%= sidebar_current("docs-ultradns-index") %>> <li<%= sidebar_current("docs-ultradns-index") %>>
<a href="/docs/providers/ultradns/index.html">UltraDNS Provider</a> <a href="/docs/providers/ultradns/index.html">UltraDNS Provider</a>
</li> </li>
<li<%= sidebar_current(/^docs-ultradns-resource/) %>> <li<%= sidebar_current(/^docs-ultradns-resource/) %>>
<a href="#">Resources</a> <a href="#">Resources</a>
<ul class="nav nav-visible"> <ul class="nav nav-visible">
<li<%= sidebar_current("docs-ultradns-resource-record") %>> <li<%= sidebar_current("docs-ultradns-resource-dirpool") %>>
<a href="/docs/providers/ultradns/r/record.html">ultradns_record</a> <a href="/docs/providers/ultradns/r/dirpool.html">ultradns_dirpool</a>
</li> </li>
<li<%= sidebar_current("docs-ultradns-resource-probe-http") %>>
<a href="/docs/providers/ultradns/r/probe_http.html">ultradns_probe_http</a>
</li>
<li<%= sidebar_current("docs-ultradns-resource-probe-ping") %>>
<a href="/docs/providers/ultradns/r/probe_ping.html">ultradns_probe_ping</a>
</li>
<li<%= sidebar_current("docs-ultradns-resource-record") %>>
<a href="/docs/providers/ultradns/r/record.html">ultradns_record</a>
</li>
<li<%= sidebar_current("docs-ultradns-resource-tcpool") %>>
<a href="/docs/providers/ultradns/r/tcpool.html">ultradns_tcpool</a>
</li>
</ul> </ul>
</li> </li>
</ul> </ul>