terraform/vendor/github.com/Ensighten/udnssdk/udnssdk.go

287 lines
7.9 KiB
Go

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"
"time"
"golang.org/x/oauth2"
"github.com/Ensighten/udnssdk/passwordcredentials"
)
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
Config *passwordcredentials.Config
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) {
ctx := oauth2.NoContext
conf := NewConfig(username, password, BaseURL)
c := &Client{
HTTPClient: conf.Client(ctx),
BaseURL: BaseURL,
UserAgent: userAgent,
Config: conf,
}
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, clientID, clientSecret string) (*Client, error) {
c := &Client{
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
}
// 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)
return req, nil
}
func (c *Client) get(path string, v interface{}) (*http.Response, error) {
return c.Do("GET", path, nil, v)
}
func (c *Client) post(path string, payload, v interface{}) (*http.Response, error) {
return c.Do("POST", path, payload, v)
}
func (c *Client) put(path string, payload, v interface{}) (*http.Response, error) {
return c.Do("PUT", path, payload, v)
}
func (c *Client) delete(path string, payload interface{}) (*http.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{}) (*http.Response, error) {
hc := c.HTTPClient
req, err := c.NewRequest(method, path, payload)
if err != nil {
return nil, err
}
log.Printf("[DEBUG] HTTP Request: %+v\n", req)
r, err := hc.Do(req)
log.Printf("[DEBUG] HTTP Response: %+v\n", r)
if err != nil {
return nil, err
}
defer r.Body.Close()
if r.StatusCode == 202 {
// This is a deferred task.
tid := TaskID(r.Header.Get("X-Task-Id"))
log.Printf("[DEBUG] Received Async Task %+v.. will retry...\n", tid)
// TODO: Sane Configuration for timeouts / retries
timeout := 5
waittime := 5 * time.Second
i := 0
breakmeout := false
for i < timeout || breakmeout {
t, _, err := c.Tasks.Find(tid)
if err != nil {
return nil, err
}
log.Printf("[DEBUG] Task ID: %+v Retry: %d Status Code: %s\n", tid, i, t.TaskStatusCode)
switch t.TaskStatusCode {
case "COMPLETE":
// Yay
resp, err := c.Tasks.FindResultByTask(t)
if err != nil {
return nil, err
}
r = resp
breakmeout = true
case "PENDING", "IN_PROCESS":
i = i + 1
time.Sleep(waittime)
continue
case "ERROR":
return nil, err
}
}
}
err = CheckResponse(r)
if err != nil {
return r, err
}
if v != nil {
if w, ok := v.(io.Writer); ok {
io.Copy(w, r.Body)
} else {
err = json.NewDecoder(r.Body).Decode(v)
// err = json.Unmarshal(r.Body, v)
}
}
return r, err
}
// 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)
}
// 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)
}