From 1b4991163f25aad51f8ad26f472e892cc5e4bd52 Mon Sep 17 00:00:00 2001 From: Josh Masseo Date: Sat, 4 Jul 2015 03:26:13 -0500 Subject: [PATCH] UltraDNS Provider --- Godeps/Godeps.json | 4 + builtin/bins/provider-ultradns/main.go | 12 + builtin/providers/ultradns/config.go | 28 ++ builtin/providers/ultradns/provider.go | 49 +++ builtin/providers/ultradns/provider_test.go | 43 ++ .../ultradns/resource_ultradns_record.go | 218 ++++++++++ .../ultradns/resource_ultradns_record_test.go | 177 ++++++++ .../github.com/Ensighten/udnssdk/account.go | 67 ++++ vendor/github.com/Ensighten/udnssdk/alert.go | 79 ++++ vendor/github.com/Ensighten/udnssdk/common.go | 15 + .../Ensighten/udnssdk/directional_pool.go | 306 ++++++++++++++ vendor/github.com/Ensighten/udnssdk/event.go | 124 ++++++ .../Ensighten/udnssdk/notification.go | 133 ++++++ vendor/github.com/Ensighten/udnssdk/probe.go | 252 ++++++++++++ vendor/github.com/Ensighten/udnssdk/readme.md | 103 +++++ vendor/github.com/Ensighten/udnssdk/rrset.go | 377 ++++++++++++++++++ vendor/github.com/Ensighten/udnssdk/task.go | 124 ++++++ .../github.com/Ensighten/udnssdk/udnssdk.go | 377 ++++++++++++++++++ website/source/assets/stylesheets/_docs.scss | 1 + .../providers/ultradns/index.html.markdown | 39 ++ .../providers/ultradns/r/record.html.markdown | 48 +++ website/source/layouts/docs.erb | 4 + website/source/layouts/ultradns.erb | 26 ++ 23 files changed, 2606 insertions(+) create mode 100644 builtin/bins/provider-ultradns/main.go create mode 100644 builtin/providers/ultradns/config.go create mode 100644 builtin/providers/ultradns/provider.go create mode 100644 builtin/providers/ultradns/provider_test.go create mode 100644 builtin/providers/ultradns/resource_ultradns_record.go create mode 100644 builtin/providers/ultradns/resource_ultradns_record_test.go create mode 100644 vendor/github.com/Ensighten/udnssdk/account.go create mode 100644 vendor/github.com/Ensighten/udnssdk/alert.go create mode 100644 vendor/github.com/Ensighten/udnssdk/common.go create mode 100644 vendor/github.com/Ensighten/udnssdk/directional_pool.go create mode 100644 vendor/github.com/Ensighten/udnssdk/event.go create mode 100644 vendor/github.com/Ensighten/udnssdk/notification.go create mode 100644 vendor/github.com/Ensighten/udnssdk/probe.go create mode 100644 vendor/github.com/Ensighten/udnssdk/readme.md create mode 100644 vendor/github.com/Ensighten/udnssdk/rrset.go create mode 100644 vendor/github.com/Ensighten/udnssdk/task.go create mode 100644 vendor/github.com/Ensighten/udnssdk/udnssdk.go create mode 100644 website/source/docs/providers/ultradns/index.html.markdown create mode 100644 website/source/docs/providers/ultradns/r/record.html.markdown create mode 100644 website/source/layouts/ultradns.erb diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index afd346bd6..005d0846f 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -154,6 +154,10 @@ "ImportPath": "github.com/DreamItGetIT/statuscake", "Rev": "8cbe86575f00210a6df2c19cb2f59b00cd181de3" }, + { + "ImportPath": "github.com/Ensighten/udnssdk", + "Rev": "0290933f5e8afd933f2823fce32bf2847e6ea603" + }, { "ImportPath": "github.com/apparentlymart/go-cidr/cidr", "Rev": "a3ebdb999b831ecb6ab8a226e31b07b2b9061c47" diff --git a/builtin/bins/provider-ultradns/main.go b/builtin/bins/provider-ultradns/main.go new file mode 100644 index 000000000..7a9d00800 --- /dev/null +++ b/builtin/bins/provider-ultradns/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "github.com/hashicorp/terraform/builtin/providers/ultradns" + "github.com/hashicorp/terraform/plugin" +) + +func main() { + plugin.Serve(&plugin.ServeOpts{ + ProviderFunc: ultradns.Provider, + }) +} diff --git a/builtin/providers/ultradns/config.go b/builtin/providers/ultradns/config.go new file mode 100644 index 000000000..a702f421e --- /dev/null +++ b/builtin/providers/ultradns/config.go @@ -0,0 +1,28 @@ +package ultradns + +import ( + "fmt" + "log" + + "github.com/Ensighten/udnssdk" +) + +// Config collects the connection service-endpoint and credentials +type Config struct { + Username string + Password string + BaseURL string +} + +// Client returns a new client for accessing UltraDNS. +func (c *Config) Client() (*udnssdk.Client, error) { + client, err := udnssdk.NewClient(c.Username, c.Password, c.BaseURL) + + if err != nil { + return nil, fmt.Errorf("Error setting up client: %s", err) + } + + log.Printf("[INFO] UltraDNS Client configured for user: %s", client.Username) + + return client, nil +} diff --git a/builtin/providers/ultradns/provider.go b/builtin/providers/ultradns/provider.go new file mode 100644 index 000000000..fbe8178bf --- /dev/null +++ b/builtin/providers/ultradns/provider.go @@ -0,0 +1,49 @@ +package ultradns + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +// Provider returns a terraform.ResourceProvider. +func Provider() terraform.ResourceProvider { + return &schema.Provider{ + Schema: map[string]*schema.Schema{ + "username": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("ULTRADNS_USERNAME", nil), + Description: "UltraDNS Username.", + }, + + "password": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("ULTRADNS_PASSWORD", nil), + Description: "UltraDNS User Password", + }, + "baseurl": &schema.Schema{ + Type: schema.TypeString, + Required: true, + DefaultFunc: schema.EnvDefaultFunc("ULTRADNS_BASEURL", nil), + Description: "UltraDNS Base Url(defaults to testing)", + }, + }, + + ResourcesMap: map[string]*schema.Resource{ + "ultradns_record": resourceUltraDNSRecord(), + }, + + ConfigureFunc: providerConfigure, + } +} + +func providerConfigure(d *schema.ResourceData) (interface{}, error) { + config := Config{ + Username: d.Get("username").(string), + Password: d.Get("password").(string), + BaseURL: d.Get("baseurl").(string), + } + + return config.Client() +} diff --git a/builtin/providers/ultradns/provider_test.go b/builtin/providers/ultradns/provider_test.go new file mode 100644 index 000000000..8b5f92904 --- /dev/null +++ b/builtin/providers/ultradns/provider_test.go @@ -0,0 +1,43 @@ +package ultradns + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/terraform" +) + +var testAccProviders map[string]terraform.ResourceProvider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider().(*schema.Provider) + testAccProviders = map[string]terraform.ResourceProvider{ + "ultradns": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider().(*schema.Provider).InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ terraform.ResourceProvider = Provider() +} + +func testAccPreCheck(t *testing.T) { + if v := os.Getenv("ULTRADNS_USERNAME"); v == "" { + t.Fatal("ULTRADNS_USERNAME must be set for acceptance tests") + } + + if v := os.Getenv("ULTRADNS_PASSWORD"); v == "" { + t.Fatal("ULTRADNS_PASSWORD must be set for acceptance tests") + } + + if v := os.Getenv("ULTRADNS_DOMAIN"); v == "" { + t.Fatal("ULTRADNS_DOMAIN must be set for acceptance tests. The domain is used to create and destroy record against.") + } +} diff --git a/builtin/providers/ultradns/resource_ultradns_record.go b/builtin/providers/ultradns/resource_ultradns_record.go new file mode 100644 index 000000000..6dff2e127 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_record.go @@ -0,0 +1,218 @@ +package ultradns + +import ( + "fmt" + "log" + "strconv" + "strings" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/schema" +) + +type rRSetResource struct { + OwnerName string + RRType string + RData []string + TTL int + Profile *udnssdk.StringProfile + Zone string +} + +func newRRSetResource(d *schema.ResourceData) (rRSetResource, error) { + r := rRSetResource{} + + if attr, ok := d.GetOk("name"); ok { + r.OwnerName = attr.(string) + } + + if attr, ok := d.GetOk("type"); ok { + r.RRType = attr.(string) + } + + if attr, ok := d.GetOk("zone"); ok { + r.Zone = attr.(string) + } + + if attr, ok := d.GetOk("rdata"); ok { + rdata := attr.([]interface{}) + r.RData = make([]string, len(rdata)) + for i, j := range rdata { + r.RData[i] = j.(string) + } + } + + if attr, ok := d.GetOk("ttl"); ok { + r.TTL, _ = strconv.Atoi(attr.(string)) + } + + return r, nil +} + +func (r rRSetResource) RRSetKey() udnssdk.RRSetKey { + return udnssdk.RRSetKey{ + Zone: r.Zone, + Type: r.RRType, + Name: r.OwnerName, + } +} + +func (r rRSetResource) RRSet() udnssdk.RRSet { + return udnssdk.RRSet{ + OwnerName: r.OwnerName, + RRType: r.RRType, + RData: r.RData, + TTL: r.TTL, + } +} + +func (r rRSetResource) ID() string { + return fmt.Sprintf("%s.%s", r.OwnerName, r.Zone) +} + +func populateResourceDataFromRRSet(r udnssdk.RRSet, d *schema.ResourceData) error { + zone := d.Get("zone") + // ttl + d.Set("ttl", r.TTL) + // rdata + err := d.Set("rdata", r.RData) + if err != nil { + return fmt.Errorf("ultradns_record.rdata set failed: %#v", err) + } + // 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)) + } + } + return nil +} + +func resourceUltraDNSRecord() *schema.Resource { + return &schema.Resource{ + Create: resourceUltraDNSRecordCreate, + Read: resourceUltraDNSRecordRead, + Update: resourceUltraDNSRecordUpdate, + Delete: resourceUltraDNSRecordDelete, + + 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, + }, + "rdata": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + // Optional + "ttl": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Default: "3600", + }, + // Computed + "hostname": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceUltraDNSRecordCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResource(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_record create: %#v", r.RRSet()) + _, err = client.RRSets.Create(r.RRSetKey(), r.RRSet()) + if err != nil { + return fmt.Errorf("Failed to create UltraDNS RRSet: %s", err) + } + + d.SetId(r.ID()) + log.Printf("[INFO] ultradns_record.id: %s", d.Id()) + + return resourceUltraDNSRecordRead(d, meta) +} + +func resourceUltraDNSRecordRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResource(d) + if err != nil { + return err + } + + rrsets, err := client.RRSets.Select(r.RRSetKey()) + if err != nil { + uderr, ok := err.(*udnssdk.ErrorResponseList) + if ok { + for _, r := range uderr.Responses { + // 70002 means Records Not Found + if r.ErrorCode == 70002 { + d.SetId("") + return nil + } + return fmt.Errorf("ultradns_record not found: %s", err) + } + } + return fmt.Errorf("ultradns_record not found: %s", err) + } + rec := rrsets[0] + return populateResourceDataFromRRSet(rec, d) +} + +func resourceUltraDNSRecordUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResource(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_record update: %#v", r.RRSet()) + _, err = client.RRSets.Update(r.RRSetKey(), r.RRSet()) + if err != nil { + return fmt.Errorf("ultradns_record update failed: %s", err) + } + + return resourceUltraDNSRecordRead(d, meta) +} + +func resourceUltraDNSRecordDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*udnssdk.Client) + + r, err := newRRSetResource(d) + if err != nil { + return err + } + + log.Printf("[INFO] ultradns_record delete: %#v", r.RRSet()) + _, err = client.RRSets.Delete(r.RRSetKey()) + if err != nil { + return fmt.Errorf("ultradns_record delete failed: %s", err) + } + + return nil +} diff --git a/builtin/providers/ultradns/resource_ultradns_record_test.go b/builtin/providers/ultradns/resource_ultradns_record_test.go new file mode 100644 index 000000000..159775025 --- /dev/null +++ b/builtin/providers/ultradns/resource_ultradns_record_test.go @@ -0,0 +1,177 @@ +package ultradns + +import ( + "fmt" + "os" + "testing" + + "github.com/Ensighten/udnssdk" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccUltraDNSRecord_Basic(t *testing.T) { + var record udnssdk.RRSet + domain := os.Getenv("ULTRADNS_DOMAIN") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckUltraDNSRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigBasic, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), + testAccCheckUltraDNSRecordAttributes(&record), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "name", "terraform"), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "zone", domain), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "rdata.0", "192.168.0.10"), + ), + }, + }, + }) +} + +func TestAccUltraDNSRecord_Updated(t *testing.T) { + var record udnssdk.RRSet + domain := os.Getenv("ULTRADNS_DOMAIN") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckUltraDNSRecordDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigBasic, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), + testAccCheckUltraDNSRecordAttributes(&record), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "name", "terraform"), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "zone", domain), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "rdata.0", "192.168.0.10"), + ), + }, + resource.TestStep{ + Config: fmt.Sprintf(testAccCheckUltraDNSRecordConfigNewValue, domain), + Check: resource.ComposeTestCheckFunc( + testAccCheckUltraDNSRecordExists("ultradns_record.foobar", &record), + testAccCheckUltraDNSRecordAttributesUpdated(&record), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "name", "terraform"), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "zone", domain), + resource.TestCheckResourceAttr( + "ultradns_record.foobar", "rdata.0", "192.168.0.11"), + ), + }, + }, + }) +} + +func testAccCheckUltraDNSRecordDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*udnssdk.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "ultradns_record" { + 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 testAccCheckUltraDNSRecordAttributes(record *udnssdk.RRSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if record.RData[0] != "192.168.0.10" { + return fmt.Errorf("Bad content: %v", record.RData) + } + + return nil + } +} + +func testAccCheckUltraDNSRecordAttributesUpdated(record *udnssdk.RRSet) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if record.RData[0] != "192.168.0.11" { + return fmt.Errorf("Bad content: %v", record.RData) + } + + 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 + } +} + +const testAccCheckUltraDNSRecordConfigBasic = ` +resource "ultradns_record" "foobar" { + zone = "%s" + + name = "terraform" + rdata = [ "192.168.0.10" ] + type = "A" + ttl = 3600 +}` + +const testAccCheckUltraDNSRecordConfigNewValue = ` +resource "ultradns_record" "foobar" { + zone = "%s" + + name = "terraform" + rdata = [ "192.168.0.11" ] + type = "A" + ttl = 3600 +}` diff --git a/vendor/github.com/Ensighten/udnssdk/account.go b/vendor/github.com/Ensighten/udnssdk/account.go new file mode 100644 index 000000000..9d22bfdaa --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/account.go @@ -0,0 +1,67 @@ +package udnssdk + +import ( + "fmt" +) + +// AccountsService provides access to account resources +type AccountsService struct { + client *Client +} + +// Account represents responses from the service +type Account struct { + AccountName string `json:"accountName"` + AccountHolderUserName string `json:"accountHolderUserName"` + OwnerUserName string `json:"ownerUserName"` + NumberOfUsers int `json:"numberOfUsers"` + NumberOfGroups int `json:"numberOfGroups"` + AccountType string `json:"accountType"` +} + +// AccountListDTO represents a account index response +type AccountListDTO struct { + Accounts []Account `json:"accounts"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// AccountKey represents the string identifier of an Account +type AccountKey string + +// URI generates the URI for an Account +func (k AccountKey) URI() string { + uri := "accounts" + if k != "" { + uri = fmt.Sprintf("accounts/%s", k) + } + return uri +} + +// AccountsURI generates the URI for Accounts collection +func AccountsURI() string { + return "accounts" +} + +// Select requests all Accounts of user +func (s *AccountsService) Select() ([]Account, *Response, error) { + var ald AccountListDTO + res, err := s.client.get(AccountsURI(), &ald) + + accts := []Account{} + for _, t := range ald.Accounts { + accts = append(accts, t) + } + return accts, res, err +} + +// Find requests an Account by AccountKey +func (s *AccountsService) Find(k AccountKey) (Account, *Response, error) { + var t Account + res, err := s.client.get(k.URI(), &t) + return t, res, err +} + +// Delete requests deletion of an Account by AccountKey +func (s *AccountsService) Delete(k AccountKey) (*Response, error) { + return s.client.delete(k.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/alert.go b/vendor/github.com/Ensighten/udnssdk/alert.go new file mode 100644 index 000000000..6a2220410 --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/alert.go @@ -0,0 +1,79 @@ +package udnssdk + +import ( + "log" + "time" +) + +// AlertsService manages Alerts +type AlertsService struct { + client *Client +} + +// ProbeAlertDataDTO wraps a probe alert response +type ProbeAlertDataDTO struct { + PoolRecord string `json:"poolRecord"` + ProbeType string `json:"probeType"` + ProbeStatus string `json:"probeStatus"` + AlertDate time.Time `json:"alertDate"` + FailoverOccured bool `json:"failoverOccured"` + OwnerName string `json:"ownerName"` + Status string `json:"status"` +} + +// ProbeAlertDataListDTO wraps the response for an index of probe alerts +type ProbeAlertDataListDTO struct { + Alerts []ProbeAlertDataDTO `json:"alerts"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// Select returns all probe alerts with a RRSetKey +func (s *AlertsService) Select(k RRSetKey) ([]ProbeAlertDataDTO, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + // init accumulators + as := []ProbeAlertDataDTO{} + offset := 0 + errcnt := 0 + + for { + reqAlerts, ri, res, err := s.SelectWithOffset(k, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return as, err + } + + log.Printf("ResultInfo: %+v\n", ri) + for _, a := range reqAlerts { + as = append(as, a) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return as, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// SelectWithOffset returns the probe alerts with a RRSetKey, accepting an offset +func (s *AlertsService) SelectWithOffset(k RRSetKey, offset int) ([]ProbeAlertDataDTO, ResultInfo, *Response, error) { + var ald ProbeAlertDataListDTO + + uri := k.AlertsQueryURI(offset) + res, err := s.client.get(uri, &ald) + + as := []ProbeAlertDataDTO{} + for _, a := range ald.Alerts { + as = append(as, a) + } + return as, ald.Resultinfo, res, err +} diff --git a/vendor/github.com/Ensighten/udnssdk/common.go b/vendor/github.com/Ensighten/udnssdk/common.go new file mode 100644 index 000000000..86e8ec10e --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/common.go @@ -0,0 +1,15 @@ +package udnssdk + +// GetResultByURI just requests a URI +func (c *Client) GetResultByURI(uri string) (*Response, error) { + req, err := c.NewRequest("GET", uri, nil) + if err != nil { + return nil, err + } + res, err := c.HTTPClient.Do(req) + + if err != nil { + return &Response{Response: res}, err + } + return &Response{Response: res}, err +} diff --git a/vendor/github.com/Ensighten/udnssdk/directional_pool.go b/vendor/github.com/Ensighten/udnssdk/directional_pool.go new file mode 100644 index 000000000..e19af49fd --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/directional_pool.go @@ -0,0 +1,306 @@ +package udnssdk + +import ( + "fmt" + "log" + "time" +) + +// DirectionalPoolsService manages 'account level' 'geo' and 'ip' groups for directional-pools +type DirectionalPoolsService struct { + client *Client +} + +// DirectionalPool wraps an account-level directional-groups response from a index request +type DirectionalPool struct { + DirectionalPoolID string `json:"taskId"` + DirectionalPoolStatusCode string `json:"taskStatusCode"` + Message string `json:"message"` + ResultURI string `json:"resultUri"` +} + +// AccountLevelGeoDirectionalGroupDTO wraps an account-level, geo directonal-group response +type AccountLevelGeoDirectionalGroupDTO struct { + Name string `json:"name"` + Description string `json:"description"` + Codes []string `json:"codes"` +} + +// IPAddrDTO wraps an IP address range or CIDR block +type IPAddrDTO struct { + Start string `json:"start,omitempty"` + End string `json:"end,omitempty"` + CIDR string `json:"cidr,omitempty"` + Address string `json:"address,omitempty"` +} + +// AccountLevelIPDirectionalGroupDTO wraps an account-level, IP directional-group response +type AccountLevelIPDirectionalGroupDTO struct { + Name string `json:"name"` + Description string `json:"description"` + IPs []IPAddrDTO `json:"ips"` +} + +// DirectionalPoolListDTO wraps a list of account-level directional-groups response from a index request +type DirectionalPoolListDTO struct { + DirectionalPools []DirectionalPool `json:"tasks"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// AccountLevelGeoDirectionalGroupListDTO wraps a list of account-level, geo directional-groups response from a index request +type AccountLevelGeoDirectionalGroupListDTO struct { + AccountName string `json:"zoneName"` + GeoGroups []AccountLevelGeoDirectionalGroupDTO `json:"geoGroups"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// AccountLevelIPDirectionalGroupListDTO wraps an account-level, IP directional-group response +type AccountLevelIPDirectionalGroupListDTO struct { + AccountName string `json:"zoneName"` + IPGroups []AccountLevelIPDirectionalGroupDTO `json:"ipGroups"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// Geos allows access to the Geo DirectionalPools API +func (s *DirectionalPoolsService) Geos() *GeoDirectionalPoolsService { + return &GeoDirectionalPoolsService{client: s.client} +} + +// IPs allows access to the IP DirectionalPools API +func (s *DirectionalPoolsService) IPs() *IPDirectionalPoolsService { + return &IPDirectionalPoolsService{client: s.client} +} + +// DirectionalPoolKey collects the identifiers of a DirectionalPool +type DirectionalPoolKey struct { + Account AccountKey + Type string + Name string +} + +// URI generates the URI for directional pools by account, type & slug ID +func (k DirectionalPoolKey) URI() string { + if k.Name == "" { + return fmt.Sprintf("%s/dirgroups/%s", k.Account.URI(), k.Type) + } + return fmt.Sprintf("%s/dirgroups/%s/%s", k.Account.URI(), k.Type, k.Name) +} + +// QueryURI generates the URI for directional pools by account, type, query & offset +func (k DirectionalPoolKey) QueryURI(query string, offset int) string { + uri := k.URI() + + if query != "" { + uri = fmt.Sprintf("%s?sort=NAME&query=%s&offset=%d", uri, query, offset) + } else { + uri = fmt.Sprintf("%s?offset=%d", uri, offset) + } + + return uri +} + +// GeoDirectionalPoolKey collects the identifiers of an DirectionalPool with type Geo +type GeoDirectionalPoolKey struct { + Account AccountKey + Name string +} + +// DirectionalPoolKey generates the DirectionalPoolKey for the GeoDirectionalPoolKey +func (k GeoDirectionalPoolKey) DirectionalPoolKey() DirectionalPoolKey { + return DirectionalPoolKey{ + Account: k.Account, + Type: "geo", + Name: k.Name, + } +} + +// URI generates the URI for a GeoDirectionalPool +func (k GeoDirectionalPoolKey) URI() string { + return k.DirectionalPoolKey().URI() +} + +// QueryURI generates the GeoDirectionalPool URI with query +func (k GeoDirectionalPoolKey) QueryURI(query string, offset int) string { + return k.DirectionalPoolKey().QueryURI(query, offset) +} + +// GeoDirectionalPoolsService manages 'geo' groups for directional-pools +type GeoDirectionalPoolsService struct { + client *Client +} + +// Select requests all geo directional-pools, by query and account, providing pagination and error handling +func (s *GeoDirectionalPoolsService) Select(k GeoDirectionalPoolKey, query string) ([]AccountLevelGeoDirectionalGroupDTO, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + // init accumulators + dtos := []AccountLevelGeoDirectionalGroupDTO{} + errcnt := 0 + offset := 0 + + for { + reqDtos, ri, res, err := s.SelectWithOffset(k, query, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return dtos, err + } + + log.Printf("[DEBUG] ResultInfo: %+v\n", ri) + for _, d := range reqDtos { + dtos = append(dtos, d) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return dtos, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// 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) { + var tld AccountLevelGeoDirectionalGroupListDTO + + res, err := s.client.get(k.QueryURI(query, offset), &tld) + + pis := []AccountLevelGeoDirectionalGroupDTO{} + for _, pi := range tld.GeoGroups { + pis = append(pis, pi) + } + return pis, tld.Resultinfo, res, err +} + +// Find requests a geo directional-pool by name & account +func (s *GeoDirectionalPoolsService) Find(k GeoDirectionalPoolKey) (AccountLevelGeoDirectionalGroupDTO, *Response, error) { + var t AccountLevelGeoDirectionalGroupDTO + res, err := s.client.get(k.URI(), &t) + return t, res, err +} + +// Create requests creation of a DirectionalPool by DirectionalPoolKey given a directional-pool +func (s *GeoDirectionalPoolsService) Create(k GeoDirectionalPoolKey, val interface{}) (*Response, error) { + return s.client.post(k.URI(), val, nil) +} + +// Update requests update of a DirectionalPool by DirectionalPoolKey given a directional-pool +func (s *GeoDirectionalPoolsService) Update(k GeoDirectionalPoolKey, val interface{}) (*Response, error) { + return s.client.put(k.URI(), val, nil) +} + +// Delete requests deletion of a DirectionalPool +func (s *GeoDirectionalPoolsService) Delete(k GeoDirectionalPoolKey) (*Response, error) { + return s.client.delete(k.URI(), nil) +} + +// IPDirectionalPoolKey collects the identifiers of an DirectionalPool with type IP +type IPDirectionalPoolKey struct { + Account AccountKey + Name string +} + +// DirectionalPoolKey generates the DirectionalPoolKey for the IPDirectionalPoolKey +func (k IPDirectionalPoolKey) DirectionalPoolKey() DirectionalPoolKey { + return DirectionalPoolKey{ + Account: k.Account, + Type: "ip", + Name: k.Name, + } +} + +// URI generates the IPDirectionalPool query URI +func (k IPDirectionalPoolKey) URI() string { + return k.DirectionalPoolKey().URI() +} + +// QueryURI generates the IPDirectionalPool URI with query +func (k IPDirectionalPoolKey) QueryURI(query string, offset int) string { + return k.DirectionalPoolKey().QueryURI(query, offset) +} + +// IPDirectionalPoolsService manages 'geo' groups for directional-pools +type IPDirectionalPoolsService struct { + client *Client +} + +// Select requests all IP directional-pools, using pagination and error handling +func (s *IPDirectionalPoolsService) Select(k IPDirectionalPoolKey, query string) ([]AccountLevelIPDirectionalGroupDTO, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + // init accumulators + gs := []AccountLevelIPDirectionalGroupDTO{} + errcnt := 0 + offset := 0 + + for { + reqIPGroups, ri, res, err := s.SelectWithOffset(k, query, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return gs, err + } + + log.Printf("ResultInfo: %+v\n", ri) + for _, g := range reqIPGroups { + gs = append(gs, g) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return gs, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// 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) { + var tld AccountLevelIPDirectionalGroupListDTO + + res, err := s.client.get(k.QueryURI(query, offset), &tld) + + pis := []AccountLevelIPDirectionalGroupDTO{} + for _, pi := range tld.IPGroups { + pis = append(pis, pi) + } + + return pis, tld.Resultinfo, res, err +} + +// Find requests a directional-pool by name & account +func (s *IPDirectionalPoolsService) Find(k IPDirectionalPoolKey) (AccountLevelIPDirectionalGroupDTO, *Response, error) { + var t AccountLevelIPDirectionalGroupDTO + res, err := s.client.get(k.URI(), &t) + return t, res, err +} + +// Create requests creation of a DirectionalPool by DirectionalPoolKey given a directional-pool +func (s *IPDirectionalPoolsService) Create(k IPDirectionalPoolKey, val interface{}) (*Response, error) { + return s.client.post(k.URI(), val, nil) +} + +// Update requests update of a DirectionalPool by DirectionalPoolKey given a directional-pool +func (s *IPDirectionalPoolsService) Update(k IPDirectionalPoolKey, val interface{}) (*Response, error) { + return s.client.put(k.URI(), val, nil) +} + +// Delete deletes an directional-pool +func (s *IPDirectionalPoolsService) Delete(k IPDirectionalPoolKey) (*Response, error) { + return s.client.delete(k.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/event.go b/vendor/github.com/Ensighten/udnssdk/event.go new file mode 100644 index 000000000..c5137ddad --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/event.go @@ -0,0 +1,124 @@ +package udnssdk + +import ( + "fmt" + "log" + "time" +) + +// EventsService manages Events +type EventsService struct { + client *Client +} + +// EventInfoDTO wraps an event's info response +type EventInfoDTO struct { + ID string `json:"id"` + PoolRecord string `json:"poolRecord"` + EventType string `json:"type"` + Start time.Time `json:"start"` + Repeat string `json:"repeat"` + End time.Time `json:"end"` + Notify string `json:"notify"` +} + +// EventInfoListDTO wraps a list of event info and list metadata, from an index request +type EventInfoListDTO struct { + Events []EventInfoDTO `json:"events"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// EventKey collects the identifiers of an Event +type EventKey struct { + Zone string + Type string + Name string + GUID string +} + +// RRSetKey generates the RRSetKey for the EventKey +func (p EventKey) RRSetKey() RRSetKey { + return RRSetKey{ + Zone: p.Zone, + Type: p.Type, + Name: p.Name, + } +} + +// URI generates the URI for a probe +func (p EventKey) URI() string { + return fmt.Sprintf("%s/%s", p.RRSetKey().EventsURI(), p.GUID) +} + +// Select requests all events, using pagination and error handling +func (s *EventsService) Select(r RRSetKey, query string) ([]EventInfoDTO, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + // init accumulators + pis := []EventInfoDTO{} + offset := 0 + errcnt := 0 + + for { + reqEvents, ri, res, err := s.SelectWithOffset(r, query, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return pis, err + } + + log.Printf("ResultInfo: %+v\n", ri) + for _, pi := range reqEvents { + pis = append(pis, pi) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return pis, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// 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) { + var tld EventInfoListDTO + + uri := r.EventsQueryURI(query, offset) + res, err := s.client.get(uri, &tld) + + pis := []EventInfoDTO{} + for _, pi := range tld.Events { + pis = append(pis, pi) + } + return pis, tld.Resultinfo, res, err +} + +// 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) { + var t EventInfoDTO + res, err := s.client.get(e.URI(), &t) + return t, res, err +} + +// Create requests creation of an event by RRSetKey, with provided event-info, returning actual response or an error +func (s *EventsService) Create(r RRSetKey, ev EventInfoDTO) (*Response, error) { + return s.client.post(r.EventsURI(), ev, nil) +} + +// Update requests update of an event by EventKey, withprovided event-info, returning the actual response or an error +func (s *EventsService) Update(e EventKey, ev EventInfoDTO) (*Response, error) { + return s.client.put(e.URI(), ev, nil) +} + +// Delete requests deletion of an event by EventKey, returning the actual response or an error +func (s *EventsService) Delete(e EventKey) (*Response, error) { + return s.client.delete(e.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/notification.go b/vendor/github.com/Ensighten/udnssdk/notification.go new file mode 100644 index 000000000..09dbcc450 --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/notification.go @@ -0,0 +1,133 @@ +package udnssdk + +import ( + "fmt" + "log" + "time" +) + +// NotificationsService manages Probes +type NotificationsService struct { + client *Client +} + +// NotificationDTO manages notifications +type NotificationDTO struct { + Email string `json:"email"` + PoolRecords []NotificationPoolRecord `json:"poolRecords"` +} + +// NotificationPoolRecord does things unknown +type NotificationPoolRecord struct { + PoolRecord string `json:"poolRecord"` + Notification NotificationInfoDTO `json:"notification"` +} + +// NotificationInfoDTO does things unknown +type NotificationInfoDTO struct { + Probe bool `json:"probe"` + Record bool `json:"record"` + Scheduled bool `json:"scheduled"` +} + +// NotificationListDTO does things unknown +type NotificationListDTO struct { + Notifications []NotificationDTO `json:"notifications"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// NotificationKey collects the identifiers of an Notification +type NotificationKey struct { + Zone string + Type string + Name string + Email string +} + +// RRSetKey generates the RRSetKey for the NotificationKey +func (k NotificationKey) RRSetKey() RRSetKey { + return RRSetKey{ + Zone: k.Zone, + Type: k.Type, + Name: k.Name, + } +} + +// URI generates the URI for a probe +func (k NotificationKey) URI() string { + return fmt.Sprintf("%s/%s", k.RRSetKey().NotificationsURI(), k.Email) +} + +// 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) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + // init accumulators + pis := []NotificationDTO{} + errcnt := 0 + offset := 0 + + for { + reqNotifications, ri, res, err := s.SelectWithOffset(k, query, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return pis, res, err + } + + log.Printf("[DEBUG] ResultInfo: %+v\n", ri) + for _, pi := range reqNotifications { + pis = append(pis, pi) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return pis, res, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// 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) { + var tld NotificationListDTO + + uri := k.NotificationsQueryURI(query, offset) + res, err := s.client.get(uri, &tld) + + log.Printf("DEBUG - ResultInfo: %+v\n", tld.Resultinfo) + pis := []NotificationDTO{} + for _, pi := range tld.Notifications { + pis = append(pis, pi) + } + return pis, tld.Resultinfo, res, err +} + +// Find requests a notification by NotificationKey,returning the actual response, or an error +func (s *NotificationsService) Find(k NotificationKey) (NotificationDTO, *Response, error) { + var t NotificationDTO + res, err := s.client.get(k.URI(), &t) + return t, res, err +} + +// Create requests creation of an event by RRSetKey, with provided NotificationInfoDTO, returning actual response or an error +func (s *NotificationsService) Create(k NotificationKey, n NotificationDTO) (*Response, error) { + return s.client.post(k.URI(), n, nil) +} + +// Update requests update of an event by NotificationKey, with provided NotificationInfoDTO, returning the actual response or an error +func (s *NotificationsService) Update(k NotificationKey, n NotificationDTO) (*Response, error) { + return s.client.put(k.URI(), n, nil) +} + +// Delete requests deletion of an event by NotificationKey, returning the actual response or an error +func (s *NotificationsService) Delete(k NotificationKey) (*Response, error) { + return s.client.delete(k.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/probe.go b/vendor/github.com/Ensighten/udnssdk/probe.go new file mode 100644 index 000000000..3723d606a --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/probe.go @@ -0,0 +1,252 @@ +package udnssdk + +import ( + "encoding/json" + "fmt" + "strings" +) + +// ProbeInfoDTO wraps a probe response +type ProbeInfoDTO struct { + ID string `json:"id"` + PoolRecord string `json:"poolRecord"` + ProbeType string `json:"type"` + Interval string `json:"interval"` + Agents []string `json:"agents"` + Threshold int `json:"threshold"` + Details *ProbeDetailsDTO `json:"details"` +} + +// ProbeDetailsLimitDTO wraps a probe +type ProbeDetailsLimitDTO struct { + Warning int `json:"warning"` + Critical int `json:"critical"` + Fail int `json:"fail"` +} + +// ProbeDetailsDTO wraps the details of a probe +type ProbeDetailsDTO struct { + data []byte + Detail interface{} `json:"detail,omitempty"` + typ string +} + +// GetData returns the data because I'm working around something. +func (s *ProbeDetailsDTO) GetData() []byte { + return s.data +} + +// 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) Populate(typ string) (err error) { + // TODO: actually document + switch strings.ToUpper(typ) { + case "HTTP": + var pp HTTPProbeDetailsDTO + err = json.Unmarshal(s.data, &pp) + s.typ = typ + s.Detail = pp + 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) + } +} + +// UnmarshalJSON does what it says on the tin +func (s *ProbeDetailsDTO) UnmarshalJSON(b []byte) (err error) { + s.data = b + return nil +} + +// MarshalJSON does what it says on the tin +func (s *ProbeDetailsDTO) MarshalJSON() ([]byte, error) { + var err error + if s.Detail != nil { + return json.Marshal(s.Detail) + } + if len(s.data) != 0 { + return s.data, err + } + return json.Marshal(nil) +} + +// GoString returns a string representation of the ProbeDetailsDTO internal data +func (s *ProbeDetailsDTO) GoString() string { + return string(s.data) +} +func (s *ProbeDetailsDTO) String() string { + return string(s.data) +} + +// Transaction wraps a transaction response +type Transaction struct { + Method string `json:"method"` + URL string `json:"url"` + TransmittedData string `json:"transmittedData,omitempty"` + FollowRedirects bool `json:"followRedirects,omitempty"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// HTTPProbeDetailsDTO wraps HTTP probe details +type HTTPProbeDetailsDTO struct { + Transactions []Transaction `json:"transactions"` + TotalLimits *ProbeDetailsLimitDTO `json:"totalLimits,omitempty"` +} + +// PingProbeDetailsDTO wraps Ping probe details +type PingProbeDetailsDTO struct { + Packets int `json:"packets,omitempty"` + PacketSize int `json:"packetSize,omitempty"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// FTPProbeDetailsDTO wraps FTP probe details +type FTPProbeDetailsDTO struct { + Port int `json:"port,omitempty"` + PassiveMode bool `json:"passiveMode,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Path string `json:"path"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// TCPProbeDetailsDTO wraps TCP probe details +type TCPProbeDetailsDTO struct { + Port int `json:"port,omitempty"` + ControlIP string `json:"controlIP,omitempty"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// SMTPProbeDetailsDTO wraps SMTP probe details +type SMTPProbeDetailsDTO struct { + Port int `json:"port,omitempty"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// SMTPSENDProbeDetailsDTO wraps SMTP SEND probe details +type SMTPSENDProbeDetailsDTO struct { + Port int `json:"port,omitempty"` + From string `json:"from"` + To string `json:"to"` + Message string `json:"message,omitempty"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// DNSProbeDetailsDTO wraps DNS probe details +type DNSProbeDetailsDTO struct { + Port int `json:"port,omitempty"` + TCPOnly bool `json:"tcpOnly,omitempty"` + RecordType string `json:"type,omitempty"` + OwnerName string `json:"ownerName,omitempty"` + Limits map[string]ProbeDetailsLimitDTO `json:"limits"` +} + +// ProbeListDTO wraps a list of probes +type ProbeListDTO struct { + Probes []ProbeInfoDTO `json:"probes"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// ProbesService manages Probes +type ProbesService struct { + client *Client +} + +// ProbeKey collects the identifiers of a Probe +type ProbeKey struct { + Zone string + Name string + ID string +} + +// RRSetKey generates the RRSetKey for the ProbeKey +func (k ProbeKey) RRSetKey() RRSetKey { + return RRSetKey{ + Zone: k.Zone, + Type: "A", // Only A records have probes + Name: k.Name, + } +} + +// URI generates the URI for a probe +func (k ProbeKey) URI() string { + return fmt.Sprintf("%s/%s", k.RRSetKey().ProbesURI(), k.ID) +} + +// Select returns all probes by a RRSetKey, with an optional query +func (s *ProbesService) Select(k RRSetKey, query string) ([]ProbeInfoDTO, *Response, error) { + var pld ProbeListDTO + + // This API does not support pagination. + uri := k.ProbesQueryURI(query) + res, err := s.client.get(uri, &pld) + + ps := []ProbeInfoDTO{} + if err == nil { + for _, t := range pld.Probes { + ps = append(ps, t) + } + } + return ps, res, err +} + +// Find returns a probe from a ProbeKey +func (s *ProbesService) Find(k ProbeKey) (ProbeInfoDTO, *Response, error) { + var t ProbeInfoDTO + res, err := s.client.get(k.URI(), &t) + return t, res, err +} + +// Create creates a probe with a RRSetKey using the ProbeInfoDTO dp +func (s *ProbesService) Create(k RRSetKey, dp ProbeInfoDTO) (*Response, error) { + return s.client.post(k.ProbesURI(), dp, nil) +} + +// Update updates a probe given a ProbeKey with the ProbeInfoDTO dp +func (s *ProbesService) Update(k ProbeKey, dp ProbeInfoDTO) (*Response, error) { + return s.client.put(k.URI(), dp, nil) +} + +// Delete deletes a probe by its ProbeKey +func (s *ProbesService) Delete(k ProbeKey) (*Response, error) { + return s.client.delete(k.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/readme.md b/vendor/github.com/Ensighten/udnssdk/readme.md new file mode 100644 index 000000000..2b1143006 --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/readme.md @@ -0,0 +1,103 @@ +# udnssdk - A ultradns SDK for GoLang +## about + +This is a golang 'SDK' for UltraDNS that I copapasta'd from weppos/dnsimple. +It seemed like an ideal donor since the use case is terraform. + +## How It works: + client := udnssdk.NewClient("username","password",udnssdk.DefaultTestBaseURL) + +There is DefaultTestBaseURL and DefaultLiveBaseURL. +When you call NewClient, it performs the 'oauth2' authorization step. +The refresh token is saved, but not implemented. It should ideally be an error +condition triggering a reauth and retry. But since Terraform is the use case, this won't be an issue. + +### RRSet Declaration + + type RRSet struct { + OwnerName string `json:"ownerName"` + RRType string `json:"rrtype"` + TTL int `json:"ttl"` + RData []string `json:"rdata"` + } + +###GetRRSets(DomainName, RecordName(leave blank for all), RecordType[A|CNAME|ANY]) + rrsets, resp, err := client.Zones.GetRRSets("domain.com","","ANY") + rrsets, resp, err := client.Zones.GetRRSets("domain.com","www","ANY") + rrsets, resp, err := client.Zones.GetRRSets("domain.com","","MX") + rrsets, resp, err := client.Zones.GetRRSets("domain.com","www","SRV") + + + + +###CreateRRSet(DomainName, RRSet) + rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.1"}} + resp2,err2 := client.Zones.CreateRRSet("ensighten.com",*rr1) + +###UpdateRRSet(DomainName, RRSet) +UpdateRRSet requires you to specify the complete RRSet for the update. This implementation does not support PATCHing. + + rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"192.168.1.1"}} + resp2,err2 := client.Zones.CreateRRSet("domain.com",*rr1) + +###DeleteRRSet(DomainName, RRSet) +Delete RRSet only uses the ownerName and RRType values from the RRSet object. + + rr3 := &udnssdk.RRSet{OwnerName: "test", RRType: "A"} // This is permissible. + resp3,err3 := client.RRSets.DeleteRRSet("domain.com",*rr3) + +## Example Program + + package main + // udnssdk - a golang sdk for the ultradns REST service. + // based on weppos/dnsimple + + import ( + "fmt" + "udnssdk" + ) + + + func main() { + client := udnssdk.NewClient("username","password",udnssdk.DefaultTestBaseURL) + if client == nil { + fmt.Printf("Fail") + } else { + fmt.Printf("Win\n") + rrsets, resp, err := client.RRSets.GetRRSets("domain.com","test","ANY") + fmt.Printf("%+v\n",rrsets) + fmt.Printf("%+v\n",resp) + fmt.Printf("%+v\n",err) + fmt.Printf("------------------------\n") + fmt.Printf("---- Create RRSet\n") + rr1 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.1}} + resp2,err2 := client.RRSets.CreateRRSet("domain.com",*rr1) + fmt.Printf("Resp2: %+v\n", resp2) + fmt.Printf("Err2: %+v\n", err2) + fmt.Printf("------------------------\n") + fmt.Printf("------------------------\n") + fmt.Printf("---- Update RRSet\n") + fmt.Printf("------------------------\n") + rr2 := &udnssdk.RRSet{OwnerName: "test", RRType: "A", TTL: 300, RData: []string{"127.0.0.2"}} + resp3, err3 := client.RRSets.UpdateRRSet("domain.com",*rr2) + fmt.Printf("Resp3: %+v\n", resp3) + fmt.Printf("Err3: %+v\n", err3) + fmt.Printf("------------------------\n") + + fmt.Printf("------------------------\n") + fmt.Printf("---- Delete RRSet\n") + fmt.Printf("------------------------\n") + resp4,err4 := client.RRSets.DeleteRRSet("domain.com",*rr2) + fmt.Printf("Resp4: %+v\n", resp4) + fmt.Printf("Err4: %+v\n", err4) + fmt.Printf("------------------------\n") + + } + } + + +#thanks +* [weppo's dnsimple go sdk @ github](https://github.com/weppos/go-dnsimple) +* [pearkes dnsimple sdk (this one is used by terraform) @ github](https://github.com/pearkes/dnsimple) +* [terraform](http://terraform.io) +* [UltraDNS's various SDK's](https://github.com/ultradns) diff --git a/vendor/github.com/Ensighten/udnssdk/rrset.go b/vendor/github.com/Ensighten/udnssdk/rrset.go new file mode 100644 index 000000000..a50c0ac58 --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/rrset.go @@ -0,0 +1,377 @@ +package udnssdk + +import ( + "encoding/json" + "fmt" + "log" + "time" +) + +// RRSetsService provides access to RRSet resources +type RRSetsService struct { + client *Client +} + +// Here is the big 'Profile' mess that should get refactored to a more managable place + +// StringProfile wraps a Profile string +type StringProfile struct { + Profile string `json:"profile,omitempty"` +} + +// Metaprofile is a helper struct for extracting a Context from a StringProfile +type Metaprofile struct { + Context ProfileSchema `json:"@context"` +} + +// ProfileSchema are the schema URIs for RRSet Profiles +type ProfileSchema string + +const ( + // DirPoolSchema is the schema URI for a Directional pool profile + DirPoolSchema ProfileSchema = "http://schemas.ultradns.com/DirPool.jsonschema" + // RDPoolSchema is the schema URI for a Resource Distribution pool profile + RDPoolSchema = "http://schemas.ultradns.com/RDPool.jsonschema" + // SBPoolSchema is the schema URI for a SiteBacker pool profile + SBPoolSchema = "http://schemas.ultradns.com/SBPool.jsonschema" + // TCPoolSchema is the schema URI for a Traffic Controller pool profile + TCPoolSchema = "http://schemas.ultradns.com/TCPool.jsonschema" +) + +// DirPoolProfile wraps a Profile for a Directional Pool +type DirPoolProfile struct { + Context ProfileSchema `json:"@context"` + Description string `json:"description"` + ConflictResolve string `json:"conflictResolve,omitempty"` + RDataInfo []DPRDataInfo `json:"rdataInfo"` + NoResponse DPRDataInfo `json:"noResponse"` +} + +// DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response +type DPRDataInfo struct { + AllNonConfigured bool `json:"allNonConfigured,omitempty"` + IPInfo IPInfo `json:"ipInfo,omitempty"` + GeoInfo GeoInfo `json:"geoInfo,omitempty"` +} + +// IPInfo wraps the ipInfo object of a DPRDataInfo +type IPInfo struct { + Name string `json:"name"` + IsAccountLevel bool `json:"isAccountLevel,omitempty"` + Ips []IPAddrDTO `json:"ips"` +} + +// GeoInfo wraps the geoInfo object of a DPRDataInfo +type GeoInfo struct { + Name string `json:"name"` + IsAccountLevel bool `json:"isAccountLevel,omitempty"` + Codes []string `json:"codes"` +} + +// RDPoolProfile wraps a Profile for a Resource Distribution pool +type RDPoolProfile struct { + Context ProfileSchema `json:"@context"` + Order string `json:"order"` + Description string `json:"description"` +} + +// SBPoolProfile wraps a Profile for a SiteBacker pool +type SBPoolProfile struct { + Context ProfileSchema `json:"@context"` + Description string `json:"description"` + RunProbes bool `json:"runProbes,omitempty"` + ActOnProbes bool `json:"actOnProbes,omitempty"` + Order string `json:"order,omitempty"` + MaxActive int `json:"maxActive,omitempty"` + MaxServed int `json:"maxServed,omitempty"` + RDataInfo []SBRDataInfo `json:"rdataInfo"` + BackupRecords []BackupRecord `json:"backupRecords"` +} + +// SBRDataInfo wraps the rdataInfo object of a SBPoolProfile +type SBRDataInfo struct { + State string `json:"state"` + RunProbes bool `json:"runProbes,omitempty"` + Priority int `json:"priority"` + FailoverDelay int `json:"failoverDelay,omitempty"` + Threshold int `json:"threshold"` + Weight int `json:"weight"` +} + +// BackupRecord wraps the backupRecord objects of an SBPoolProfile response +type BackupRecord struct { + RData string `json:"rdata"` + FailoverDelay int `json:"failoverDelay,omitempty"` +} + +// TCPoolProfile wraps a Profile for a Traffic Controller pool +type TCPoolProfile struct { + Context ProfileSchema `json:"@context"` + Description string `json:"description"` + RunProbes bool `json:"runProbes,omitempty"` + ActOnProbes bool `json:"actOnProbes,omitempty"` + MaxToLB int `json:"maxToLB,omitempty"` + RDataInfo []SBRDataInfo `json:"rdataInfo"` + BackupRecord BackupRecord `json:"backupRecord"` +} + +// UnmarshalJSON does what it says on the tin +func (sp *StringProfile) UnmarshalJSON(b []byte) (err error) { + sp.Profile = string(b) + return nil +} + +// MarshalJSON does what it says on the tin +func (sp *StringProfile) MarshalJSON() ([]byte, error) { + if sp.Profile != "" { + return []byte(sp.Profile), nil + } + return json.Marshal(nil) +} + +// Metaprofile converts a StringProfile to a Metaprofile to extract the context +func (sp *StringProfile) Metaprofile() (Metaprofile, error) { + var mp Metaprofile + if sp.Profile == "" { + return mp, fmt.Errorf("Empty Profile cannot be converted to a Metaprofile") + } + err := json.Unmarshal([]byte(sp.Profile), &mp) + if err != nil { + return mp, fmt.Errorf("Error getting profile type: %+v\n", err) + } + return mp, nil +} + +// Context extracts the schema context from a StringProfile +func (sp *StringProfile) Context() ProfileSchema { + mp, err := sp.Metaprofile() + if err != nil { + log.Printf("[ERROR] %+s\n", err) + return "" + } + return mp.Context +} + +// GoString returns the StringProfile's Profile. +func (sp *StringProfile) GoString() string { + return sp.Profile +} + +// String returns the StringProfile's Profile. +func (sp *StringProfile) String() string { + return sp.Profile +} + +// GetProfileObject extracts the full Profile by its schema type +func (sp *StringProfile) GetProfileObject() interface{} { + c := sp.Context() + switch c { + case DirPoolSchema: + var dpp DirPoolProfile + err := json.Unmarshal([]byte(sp.Profile), &dpp) + if err != nil { + log.Printf("Could not Unmarshal the DirPoolProfile.\n") + return nil + } + return dpp + case RDPoolSchema: + var rdp RDPoolProfile + err := json.Unmarshal([]byte(sp.Profile), &rdp) + if err != nil { + log.Printf("Could not Unmarshal the RDPoolProfile.\n") + return nil + } + return rdp + case SBPoolSchema: + var sbp SBPoolProfile + err := json.Unmarshal([]byte(sp.Profile), &sbp) + if err != nil { + log.Printf("Could not Unmarshal the SBPoolProfile.\n") + return nil + } + return sbp + case TCPoolSchema: + var tcp TCPoolProfile + err := json.Unmarshal([]byte(sp.Profile), &tcp) + if err != nil { + log.Printf("Could not Unmarshal the TCPoolProfile.\n") + return nil + } + return tcp + default: + log.Printf("ERROR - Fall through on GetProfileObject - %s.\n", c) + return fmt.Errorf("Fallthrough on GetProfileObject type %s\n", c) + } +} + +// RRSet wraps an RRSet resource +type RRSet struct { + OwnerName string `json:"ownerName"` + RRType string `json:"rrtype"` + TTL int `json:"ttl"` + RData []string `json:"rdata"` + Profile *StringProfile `json:"profile,omitempty"` +} + +// RRSetListDTO wraps a list of RRSet resources +type RRSetListDTO struct { + ZoneName string `json:"zoneName"` + Rrsets []RRSet `json:"rrsets"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +// RRSetKey collects the identifiers of a Zone +type RRSetKey struct { + Zone string + Type string + Name string +} + +// URI generates the URI for an RRSet +func (k RRSetKey) URI() string { + uri := fmt.Sprintf("zones/%s/rrsets", k.Zone) + if k.Type != "" { + uri += fmt.Sprintf("/%v", k.Type) + if k.Name != "" { + uri += fmt.Sprintf("/%v", k.Name) + } + } + return uri +} + +// QueryURI generates the query URI for an RRSet and offset +func (k RRSetKey) QueryURI(offset int) string { + // TODO: find a more appropriate place to set "" to "ANY" + if k.Type == "" { + k.Type = "ANY" + } + return fmt.Sprintf("%s?offset=%d", k.URI(), offset) +} + +// AlertsURI generates the URI for an RRSet +func (k RRSetKey) AlertsURI() string { + return fmt.Sprintf("%s/alerts", k.URI()) +} + +// AlertsQueryURI generates the alerts query URI for an RRSet with query +func (k RRSetKey) AlertsQueryURI(offset int) string { + uri := k.AlertsURI() + if offset != 0 { + uri = fmt.Sprintf("%s?offset=%d", uri, offset) + } + return uri +} + +// EventsURI generates the URI for an RRSet +func (k RRSetKey) EventsURI() string { + return fmt.Sprintf("%s/events", k.URI()) +} + +// EventsQueryURI generates the events query URI for an RRSet with query +func (k RRSetKey) EventsQueryURI(query string, offset int) string { + uri := k.EventsURI() + if query != "" { + return fmt.Sprintf("%s?sort=NAME&query=%s&offset=%d", uri, query, offset) + } + if offset != 0 { + return fmt.Sprintf("%s?offset=%d", uri, offset) + } + return uri +} + +// NotificationsURI generates the notifications URI for an RRSet +func (k RRSetKey) NotificationsURI() string { + return fmt.Sprintf("%s/notifications", k.URI()) +} + +// NotificationsQueryURI generates the notifications query URI for an RRSet with query +func (k RRSetKey) NotificationsQueryURI(query string, offset int) string { + uri := k.NotificationsURI() + if query != "" { + uri = fmt.Sprintf("%s?sort=NAME&query=%s&offset=%d", uri, query, offset) + } else { + uri = fmt.Sprintf("%s?offset=%d", uri, offset) + } + return uri +} + +// ProbesURI generates the probes URI for an RRSet +func (k RRSetKey) ProbesURI() string { + return fmt.Sprintf("%s/probes", k.URI()) +} + +// ProbesQueryURI generates the probes query URI for an RRSet with query +func (k RRSetKey) ProbesQueryURI(query string) string { + uri := k.ProbesURI() + if query != "" { + uri = fmt.Sprintf("%s?sort=NAME&query=%s", uri, query) + } + return uri +} + +// Select will list the zone rrsets, paginating through all available results +func (s *RRSetsService) Select(k RRSetKey) ([]RRSet, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + rrsets := []RRSet{} + errcnt := 0 + offset := 0 + + for { + reqRrsets, ri, res, err := s.SelectWithOffset(k, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return rrsets, err + } + + log.Printf("ResultInfo: %+v\n", ri) + for _, rrset := range reqRrsets { + rrsets = append(rrsets, rrset) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return rrsets, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// SelectWithOffset requests zone rrsets by RRSetKey & optional offset +func (s *RRSetsService) SelectWithOffset(k RRSetKey, offset int) ([]RRSet, ResultInfo, *Response, error) { + var rrsld RRSetListDTO + + uri := k.QueryURI(offset) + res, err := s.client.get(uri, &rrsld) + + rrsets := []RRSet{} + for _, rrset := range rrsld.Rrsets { + rrsets = append(rrsets, rrset) + } + return rrsets, rrsld.Resultinfo, res, err +} + +// Create creates an rrset with val +func (s *RRSetsService) Create(k RRSetKey, rrset RRSet) (*Response, error) { + var ignored interface{} + return s.client.post(k.URI(), rrset, &ignored) +} + +// Update updates a RRSet with the provided val +func (s *RRSetsService) Update(k RRSetKey, val RRSet) (*Response, error) { + var ignored interface{} + return s.client.put(k.URI(), val, &ignored) +} + +// Delete deletes an RRSet +func (s *RRSetsService) Delete(k RRSetKey) (*Response, error) { + return s.client.delete(k.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/task.go b/vendor/github.com/Ensighten/udnssdk/task.go new file mode 100644 index 000000000..6fb10baf2 --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/task.go @@ -0,0 +1,124 @@ +package udnssdk + +import ( + "fmt" + "log" + "time" +) + +// TasksService provides access to the tasks resources +type TasksService struct { + client *Client +} + +// Task wraps a task response +type Task struct { + TaskID string `json:"taskId"` + TaskStatusCode string `json:"taskStatusCode"` + Message string `json:"message"` + ResultURI string `json:"resultUri"` +} + +// TaskListDTO wraps a list of Task resources, from an HTTP response +type TaskListDTO struct { + Tasks []Task `json:"tasks"` + Queryinfo QueryInfo `json:"queryInfo"` + Resultinfo ResultInfo `json:"resultInfo"` +} + +type taskWrapper struct { + Task Task `json:"task"` +} + +// TaskID represents the string identifier of a task +type TaskID string + +// ResultURI generates URI for the task result +func (t TaskID) ResultURI() string { + return fmt.Sprintf("%s/result", t.URI()) +} + +// URI generates the URI for a task +func (t TaskID) URI() string { + return fmt.Sprintf("tasks/%s", t) +} + +// TasksQueryURI generates the query URI for the tasks collection given a query and offset +func TasksQueryURI(query string, offset int) string { + if query != "" { + return fmt.Sprintf("tasks?sort=NAME&query=%s&offset=%d", query, offset) + } + return fmt.Sprintf("tasks?offset=%d", offset) +} + +// Select requests all tasks, with pagination +func (s *TasksService) Select(query string) ([]Task, error) { + // TODO: Sane Configuration for timeouts / retries + maxerrs := 5 + waittime := 5 * time.Second + + // init accumulators + dtos := []Task{} + offset := 0 + errcnt := 0 + + for { + reqDtos, ri, res, err := s.SelectWithOffset(query, offset) + if err != nil { + if res.StatusCode >= 500 { + errcnt = errcnt + 1 + if errcnt < maxerrs { + time.Sleep(waittime) + continue + } + } + return dtos, err + } + + log.Printf("[DEBUG] ResultInfo: %+v\n", ri) + for _, d := range reqDtos { + dtos = append(dtos, d) + } + if ri.ReturnedCount+ri.Offset >= ri.TotalCount { + return dtos, nil + } + offset = ri.ReturnedCount + ri.Offset + continue + } +} + +// 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) { + var tld TaskListDTO + + uri := TasksQueryURI(query, offset) + res, err := s.client.get(uri, &tld) + + ts := []Task{} + for _, t := range tld.Tasks { + ts = append(ts, t) + } + return ts, tld.Resultinfo, res, err +} + +// Find Get the status of a task. +func (s *TasksService) Find(t TaskID) (Task, *Response, error) { + var tv Task + res, err := s.client.get(t.URI(), &tv) + return tv, res, err +} + +// FindResult requests +func (s *TasksService) FindResult(t TaskID) (*Response, error) { + return s.client.GetResultByURI(t.ResultURI()) +} + +// FindResultByTask requests a task by the provided task's result uri +func (s *TasksService) FindResultByTask(t Task) (*Response, error) { + return s.client.GetResultByURI(t.ResultURI) +} + +// Delete requests deletions +func (s *TasksService) Delete(t TaskID) (*Response, error) { + return s.client.delete(t.URI(), nil) +} diff --git a/vendor/github.com/Ensighten/udnssdk/udnssdk.go b/vendor/github.com/Ensighten/udnssdk/udnssdk.go new file mode 100644 index 000000000..b9a145679 --- /dev/null +++ b/vendor/github.com/Ensighten/udnssdk/udnssdk.go @@ -0,0 +1,377 @@ +package udnssdk + +// udnssdk - a golang sdk for the ultradns REST service. +// 2015-07-03 - jmasseo@gmail.com + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "log" + "net/http" + "net/url" + "time" +) + +const ( + libraryVersion = "0.1" + // DefaultTestBaseURL returns the URL for UltraDNS's test restapi endpoint + DefaultTestBaseURL = "https://test-restapi.ultradns.com/" + // DefaultLiveBaseURL returns the URL for UltraDNS's production restapi endpoint + DefaultLiveBaseURL = "https://restapi.ultradns.com/" + + userAgent = "udnssdk-go/" + libraryVersion + + apiVersion = "v1" +) + +// QueryInfo wraps a query request +type QueryInfo struct { + Q string `json:"q"` + Sort string `json:"sort"` + Reverse bool `json:"reverse"` + Limit int `json:"limit"` +} + +// ResultInfo wraps the list metadata for an index response +type ResultInfo struct { + TotalCount int `json:"totalCount"` + Offset int `json:"offset"` + ReturnedCount int `json:"returnedCount"` +} + +// Client wraps our general-purpose Service Client +type Client struct { + // This is our client structure. + HTTPClient *http.Client + + // UltraDNS makes a call to an authorization API using your username and + // password, returning an 'Access Token' and a 'Refresh Token'. + // Our use case does not require the refresh token, but we should implement + // for completeness. + AccessToken string + RefreshToken string + Username string + Password string + BaseURL string + UserAgent string + + // Accounts API + Accounts *AccountsService + // Probe Alerts API + Alerts *AlertsService + // Directional Pools API + DirectionalPools *DirectionalPoolsService + // Events API + Events *EventsService + // Notifications API + Notifications *NotificationsService + // Probes API + Probes *ProbesService + // Resource Record Sets API + RRSets *RRSetsService + // Tasks API + Tasks *TasksService +} + +// NewClient returns a new ultradns API client. +func NewClient(username, password, BaseURL string) (*Client, error) { + accesstoken, refreshtoken, err := GetAuthTokens(username, password, BaseURL) + if err != nil { + return nil, err + } + c := &Client{ + AccessToken: accesstoken, + RefreshToken: refreshtoken, + Username: username, + Password: password, + HTTPClient: &http.Client{}, + BaseURL: BaseURL, + UserAgent: userAgent, + } + c.Accounts = &AccountsService{client: c} + c.Alerts = &AlertsService{client: c} + c.DirectionalPools = &DirectionalPoolsService{client: c} + c.Events = &EventsService{client: c} + c.Notifications = &NotificationsService{client: c} + c.Probes = &ProbesService{client: c} + c.RRSets = &RRSetsService{client: c} + c.Tasks = &TasksService{client: c} + return c, nil +} + +// newStubClient returns a new ultradns API client. +func newStubClient(username, password, BaseURL, accesstoken, refreshtoken string) (*Client, error) { + c := &Client{ + AccessToken: accesstoken, + RefreshToken: refreshtoken, + Username: username, + Password: password, + HTTPClient: &http.Client{}, + BaseURL: BaseURL, + UserAgent: userAgent, + } + c.Accounts = &AccountsService{client: c} + c.Alerts = &AlertsService{client: c} + c.DirectionalPools = &DirectionalPoolsService{client: c} + c.Events = &EventsService{client: c} + c.Notifications = &NotificationsService{client: c} + c.Probes = &ProbesService{client: c} + c.RRSets = &RRSetsService{client: c} + c.Tasks = &TasksService{client: c} + return c, nil +} + +// NewAuthRequest creates an Authorization request to get an access and refresh token. +// +// { +// "tokenType":"Bearer", +// "refreshToken":"48472efcdce044c8850ee6a395c74a7872932c7112", +// "accessToken":"b91d037c75934fc89a9f43fe4a", +// "expiresIn":"3600", +// "expires_in":"3600" +// } + +// AuthResponse wraps the response to an auth request +type AuthResponse struct { + TokenType string `json:"tokenType"` + AccessToken string `json:"accessToken"` + RefreshToken string `json:"refreshToken"` + ExpiresIn string `json:"expiresIn"` +} + +// GetAuthTokens requests by username, password & base URL, returns the access-token & refresh-token, or a possible error +func GetAuthTokens(username, password, BaseURL string) (string, string, error) { + res, err := http.PostForm(fmt.Sprintf("%s/%s/authorization/token", BaseURL, apiVersion), url.Values{"grant_type": {"password"}, "username": {username}, "password": {password}}) + + if err != nil { + return "", "", err + } + + //response := &Response{Response: res} + defer res.Body.Close() + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return "", "", err + } + err = CheckAuthResponse(res, body) + if err != nil { + return "", "", err + } + + var authr AuthResponse + err = json.Unmarshal(body, &authr) + if err != nil { + return string(body), "JSON Decode Error", err + } + return authr.AccessToken, authr.RefreshToken, err +} + +// NewRequest creates an API request. +// The path is expected to be a relative path and will be resolved +// according to the BaseURL of the Client. Paths should always be specified without a preceding slash. +func (c *Client) NewRequest(method, path string, payload interface{}) (*http.Request, error) { + url := c.BaseURL + fmt.Sprintf("%s/%s", apiVersion, path) + + body := new(bytes.Buffer) + if payload != nil { + err := json.NewEncoder(body).Encode(payload) + if err != nil { + return nil, err + } + } + + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + req.Header.Add("User-Agent", c.UserAgent) + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.AccessToken)) + req.Header.Add("Token", fmt.Sprintf("Bearer %s", c.AccessToken)) + + return req, nil +} + +func (c *Client) get(path string, v interface{}) (*Response, error) { + return c.Do("GET", path, nil, v) +} + +func (c *Client) post(path string, payload, v interface{}) (*Response, error) { + return c.Do("POST", path, payload, v) +} + +func (c *Client) put(path string, payload, v interface{}) (*Response, error) { + return c.Do("PUT", path, payload, v) +} + +func (c *Client) delete(path string, payload interface{}) (*Response, error) { + return c.Do("DELETE", path, payload, nil) +} + +// Do sends an API request and returns the API response. +// The API response is JSON decoded and stored in the value pointed by v, +// or returned as an error if an API error has occurred. +// If v implements the io.Writer interface, the raw response body will be written to v, +// without attempting to decode it. +func (c *Client) Do(method, path string, payload, v interface{}) (*Response, error) { + req, err := c.NewRequest(method, path, payload) + if err != nil { + return nil, err + } + log.Printf("[DEBUG] HTTP Request: %+v\n", req) + res, err := c.HTTPClient.Do(req) + log.Printf("[DEBUG] HTTP Response: %+v\n", res) + if err != nil { + return nil, err + } + defer res.Body.Close() + origresponse := &Response{Response: res} + + var nres *http.Response + nres = res + if res.StatusCode == 202 { + // This is a deferred task. + tid := TaskID(res.Header.Get("X-Task-Id")) + log.Printf("[DEBUG] Received Async Task %+v.. will retry...\n", tid) + // TODO: Sane Configuration for timeouts / retries + timeout := 5 + waittime := 5 * time.Second + i := 0 + breakmeout := false + for i < timeout || breakmeout { + myt, statusres, err := c.Tasks.Find(tid) + if err != nil { + return origresponse, err + } + log.Printf("[DEBUG] Task ID: %+v Retry: %d Status Code: %s\n", tid, i, myt.TaskStatusCode) + switch myt.TaskStatusCode { + case "COMPLETE": + // Yay + tres, err := c.Tasks.FindResultByTask(myt) + if err != nil { + return origresponse, err + } + nres = tres.Response + breakmeout = true + case "PENDING", "IN_PROCESS": + i = i + 1 + time.Sleep(waittime) + continue + case "ERROR": + return statusres, err + + } + } + } + response := &Response{Response: nres} + + err = CheckResponse(nres) + if err != nil { + return response, err + } + + if v != nil { + if w, ok := v.(io.Writer); ok { + io.Copy(w, res.Body) + } else { + err = json.NewDecoder(res.Body).Decode(v) + } + } + + return response, err +} + +// A Response represents an API response. +type Response struct { + *http.Response +} + +// ErrorResponse represents an error caused by an API request. +// Example: +// {"errorCode":60001,"errorMessage":"invalid_grant:Invalid username & password combination.","error":"invalid_grant","error_description":"60001: invalid_grant:Invalid username & password combination."} +type ErrorResponse struct { + Response *http.Response // HTTP response that caused this error + ErrorCode int `json:"errorCode"` // error code + ErrorMessage string `json:"errorMessage"` // human-readable message + ErrorStr string `json:"error"` + ErrorDescription string `json:"error_description"` +} + +// ErrorResponseList wraps an HTTP response that has a list of errors +type ErrorResponseList struct { + Response *http.Response // HTTP response that caused this error + Responses []ErrorResponse +} + +// Error implements the error interface. +func (r ErrorResponse) Error() string { + return fmt.Sprintf("%v %v: %d %d %v", + r.Response.Request.Method, r.Response.Request.URL, + r.Response.StatusCode, r.ErrorCode, r.ErrorMessage) +} + +func (r ErrorResponseList) Error() string { + return fmt.Sprintf("%v %v: %d %d %v", + r.Response.Request.Method, r.Response.Request.URL, + r.Response.StatusCode, r.Responses[0].ErrorCode, r.Responses[0].ErrorMessage) +} + +// CheckAuthResponse checks the API response for errors, and returns them if so +func CheckAuthResponse(r *http.Response, body []byte) error { + if code := r.StatusCode; 200 <= code && code <= 299 { + return nil + } + + // Attempt marshaling to ErrorResponse + var er ErrorResponse + err := json.Unmarshal(body, &er) + if err == nil { + er.Response = r + return er + } + + // Attempt marshaling to ErrorResponseList + var ers []ErrorResponse + err = json.Unmarshal(body, &ers) + if err == nil { + return &ErrorResponseList{Response: r, Responses: ers} + } + + return fmt.Errorf("Response had non-successful status: %d, but could not extract error from body: %+v", r.StatusCode, body) +} + +// CheckResponse checks the API response for errors, and returns them if present. +// A response is considered an error if the status code is different than 2xx. Specific requests +// may have additional requirements, but this is sufficient in most of the cases. +func CheckResponse(r *http.Response) error { + if code := r.StatusCode; 200 <= code && code <= 299 { + return nil + } + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + return err + } + + // 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) +} diff --git a/website/source/assets/stylesheets/_docs.scss b/website/source/assets/stylesheets/_docs.scss index 83c8f2314..1630ffb47 100755 --- a/website/source/assets/stylesheets/_docs.scss +++ b/website/source/assets/stylesheets/_docs.scss @@ -33,6 +33,7 @@ body.layout-rundeck, body.layout-statuscake, body.layout-template, body.layout-tls, +body.layout-ultradns, body.layout-vcd, body.layout-vsphere, body.layout-docs, diff --git a/website/source/docs/providers/ultradns/index.html.markdown b/website/source/docs/providers/ultradns/index.html.markdown new file mode 100644 index 000000000..18d8bb44d --- /dev/null +++ b/website/source/docs/providers/ultradns/index.html.markdown @@ -0,0 +1,39 @@ +--- +layout: "ultradns" +page_title: "Provider: UltraDNS" +sidebar_current: "docs-ultradns-index" +description: |- + The UltraDNS provider is used to interact with the resources supported by UltraDNS. The provider needs to be configured with the proper credentials before it can be used. +--- + +# UltraDNS Provider + +The UltraDNS provider is used to interact with the +resources supported by UltraDNS. The provider needs to be configured +with the proper credentials before it can be used. + +Use the navigation to the left to read about the available resources. + +## Example Usage + +``` +# Configure the UltraDNS provider +provider "ultradns" { + username = "${var.ultradns_username}" + password = "${var.ultradns_password}" + baseurl = "https://test-restapi.ultradns.com/" +} + +# Create a record +resource "ultradns_record" "www" { + ... +} +``` + +## Argument Reference + +The following arguments are supported: + +* `username` - (Required) The UltraDNS username. It must be provided, but it can also be sourced from the `ULTRADNS_USERNAME` environment variable. +* `password` - (Required) The password associated with the username. It must be provided, but it can also be sourced from the `ULTRADNS_PASSWORD` environment variable. +* `baseurl` - (Required) The base url for the UltraDNS REST API, but it can also be sourced from the `ULTRADNS_BASEURL` environment variable. diff --git a/website/source/docs/providers/ultradns/r/record.html.markdown b/website/source/docs/providers/ultradns/r/record.html.markdown new file mode 100644 index 000000000..67dd90c3d --- /dev/null +++ b/website/source/docs/providers/ultradns/r/record.html.markdown @@ -0,0 +1,48 @@ +--- +layout: "ultradns" +page_title: "UltraDNS: ultradns_record" +sidebar_current: "docs-ultradns-resource-record" +description: |- + Provides a UltraDNS record resource. +--- + +# ultradns\_record + +Provides a UltraDNS record resource. + +## Example Usage + +``` +# Add a record to the domain +resource "ultradns_record" "foobar" { + zone = "${var.ultradns_domain}" + name = "terraform" + rdata = [ "192.168.0.11" ] + type = "A" + ttl = 3600 +} +``` + +## 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) An array containing the values of the record +* `type` - (Required) The type of the record +* `ttl` - (Optional) The TTL of the record + +## Attributes Reference + +The following attributes are exported: + +* `id` - The record ID +* `name` - The name of the record +* `rdata` - An array containing the values of the record +* `type` - The type of the record +* `ttl` - The TTL of the record +* `zone` - The domain of the record +* `hostname` - The FQDN of the record diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index aec52d682..94a8d1ba7 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -241,6 +241,10 @@ TLS + > + UltraDNS + + > VMware vCloud Director diff --git a/website/source/layouts/ultradns.erb b/website/source/layouts/ultradns.erb new file mode 100644 index 000000000..6df4afa0f --- /dev/null +++ b/website/source/layouts/ultradns.erb @@ -0,0 +1,26 @@ +<% wrap_layout :inner do %> + <% content_for :sidebar do %> + + <% end %> + + <%= yield %> + <% end %>