398 lines
12 KiB
Go
398 lines
12 KiB
Go
package udnssdk
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/fatih/structs"
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// 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
|
|
|
|
// 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"
|
|
)
|
|
|
|
// RawProfile represents the naive interface to an RRSet Profile
|
|
type RawProfile map[string]interface{}
|
|
|
|
// Context extracts the schema context from a RawProfile
|
|
func (rp RawProfile) Context() ProfileSchema {
|
|
return ProfileSchema(rp["@context"].(string))
|
|
}
|
|
|
|
// GetProfileObject extracts the full Profile by its schema type
|
|
func (rp RawProfile) GetProfileObject() (interface{}, error) {
|
|
c := rp.Context()
|
|
switch c {
|
|
case DirPoolSchema:
|
|
return rp.DirPoolProfile()
|
|
case RDPoolSchema:
|
|
return rp.RDPoolProfile()
|
|
case SBPoolSchema:
|
|
return rp.SBPoolProfile()
|
|
case TCPoolSchema:
|
|
return rp.TCPoolProfile()
|
|
default:
|
|
return nil, fmt.Errorf("Fallthrough on GetProfileObject type %s\n", c)
|
|
}
|
|
}
|
|
|
|
// decode takes a RawProfile and uses reflection to convert it into the
|
|
// given Go native structure. val must be a pointer to a struct.
|
|
// This is identical to mapstructure.Decode, but uses the `json:` tag instead of `mapstructure:`
|
|
func decodeProfile(m interface{}, rawVal interface{}) error {
|
|
config := &mapstructure.DecoderConfig{
|
|
Metadata: nil,
|
|
TagName: "json",
|
|
Result: rawVal,
|
|
ErrorUnused: true,
|
|
WeaklyTypedInput: true,
|
|
}
|
|
|
|
decoder, err := mapstructure.NewDecoder(config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return decoder.Decode(m)
|
|
}
|
|
|
|
// DirPoolProfile extracts the full Profile as a DirPoolProfile or returns an error
|
|
func (rp RawProfile) DirPoolProfile() (DirPoolProfile, error) {
|
|
var result DirPoolProfile
|
|
c := rp.Context()
|
|
if c != DirPoolSchema {
|
|
return result, fmt.Errorf("RDPoolProfile has incorrect JSON-LD @context %s\n", c)
|
|
}
|
|
err := decodeProfile(rp, &result)
|
|
return result, err
|
|
}
|
|
|
|
// RDPoolProfile extracts the full Profile as a RDPoolProfile or returns an error
|
|
func (rp RawProfile) RDPoolProfile() (RDPoolProfile, error) {
|
|
var result RDPoolProfile
|
|
c := rp.Context()
|
|
if c != RDPoolSchema {
|
|
return result, fmt.Errorf("RDPoolProfile has incorrect JSON-LD @context %s\n", c)
|
|
}
|
|
err := decodeProfile(rp, &result)
|
|
return result, err
|
|
}
|
|
|
|
// SBPoolProfile extracts the full Profile as a SBPoolProfile or returns an error
|
|
func (rp RawProfile) SBPoolProfile() (SBPoolProfile, error) {
|
|
var result SBPoolProfile
|
|
c := rp.Context()
|
|
if c != SBPoolSchema {
|
|
return result, fmt.Errorf("SBPoolProfile has incorrect JSON-LD @context %s\n", c)
|
|
}
|
|
err := decodeProfile(rp, &result)
|
|
return result, err
|
|
}
|
|
|
|
// TCPoolProfile extracts the full Profile as a TCPoolProfile or returns an error
|
|
func (rp RawProfile) TCPoolProfile() (TCPoolProfile, error) {
|
|
var result TCPoolProfile
|
|
c := rp.Context()
|
|
if c != TCPoolSchema {
|
|
return result, fmt.Errorf("TCPoolProfile has incorrect JSON-LD @context %s\n", c)
|
|
}
|
|
err := decodeProfile(rp, &result)
|
|
return result, err
|
|
}
|
|
|
|
// encodeProfile takes a struct and converts to a RawProfile
|
|
func encodeProfile(rawVal interface{}) RawProfile {
|
|
s := structs.New(rawVal)
|
|
s.TagName = "json"
|
|
return s.Map()
|
|
}
|
|
|
|
// RawProfile converts to a naive RawProfile
|
|
func (p DirPoolProfile) RawProfile() RawProfile {
|
|
return encodeProfile(p)
|
|
}
|
|
|
|
// RawProfile converts to a naive RawProfile
|
|
func (p RDPoolProfile) RawProfile() RawProfile {
|
|
return encodeProfile(p)
|
|
}
|
|
|
|
// RawProfile converts to a naive RawProfile
|
|
func (p SBPoolProfile) RawProfile() RawProfile {
|
|
return encodeProfile(p)
|
|
}
|
|
|
|
// RawProfile converts to a naive RawProfile
|
|
func (p TCPoolProfile) RawProfile() RawProfile {
|
|
return encodeProfile(p)
|
|
}
|
|
|
|
// DirPoolProfile wraps a Profile for a Directional Pool
|
|
type DirPoolProfile struct {
|
|
Context ProfileSchema `json:"@context"`
|
|
Description string `json:"description"`
|
|
ConflictResolve string `json:"conflictResolve,omitempty"`
|
|
RDataInfo []DPRDataInfo `json:"rdataInfo"`
|
|
NoResponse DPRDataInfo `json:"noResponse,omitempty"`
|
|
}
|
|
|
|
// DPRDataInfo wraps the rdataInfo object of a DirPoolProfile response
|
|
type DPRDataInfo struct {
|
|
AllNonConfigured bool `json:"allNonConfigured,omitempty" terraform:"all_non_configured"`
|
|
IPInfo *IPInfo `json:"ipInfo,omitempty" terraform:"ip_info"`
|
|
GeoInfo *GeoInfo `json:"geoInfo,omitempty" terraform:"geo_info"`
|
|
}
|
|
|
|
// IPInfo wraps the ipInfo object of a DPRDataInfo
|
|
type IPInfo struct {
|
|
Name string `json:"name" terraform:"name"`
|
|
IsAccountLevel bool `json:"isAccountLevel,omitempty" terraform:"is_account_level"`
|
|
Ips []IPAddrDTO `json:"ips,omitempty" terraform:"-"`
|
|
}
|
|
|
|
// GeoInfo wraps the geoInfo object of a DPRDataInfo
|
|
type GeoInfo struct {
|
|
Name string `json:"name" terraform:"name"`
|
|
IsAccountLevel bool `json:"isAccountLevel,omitempty" terraform:"is_account_level"`
|
|
Codes []string `json:"codes,omitempty" terraform:"-"`
|
|
}
|
|
|
|
// RDPoolProfile wraps a Profile for a Resource Distribution pool
|
|
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"`
|
|
ActOnProbes bool `json:"actOnProbes"`
|
|
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"`
|
|
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,omitempty"`
|
|
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"`
|
|
ActOnProbes bool `json:"actOnProbes"`
|
|
MaxToLB int `json:"maxToLB,omitempty"`
|
|
RDataInfo []SBRDataInfo `json:"rdataInfo"`
|
|
BackupRecord *BackupRecord `json:"backupRecord,omitempty"`
|
|
}
|
|
|
|
// RRSet wraps an RRSet resource
|
|
type RRSet struct {
|
|
OwnerName string `json:"ownerName"`
|
|
RRType string `json:"rrtype"`
|
|
TTL int `json:"ttl"`
|
|
RData []string `json:"rdata"`
|
|
Profile RawProfile `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 != nil && 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, *http.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) (*http.Response, error) {
|
|
var ignored interface{}
|
|
return s.client.post(k.URI(), rrset, &ignored)
|
|
}
|
|
|
|
// Update updates a RRSet with the provided val
|
|
func (s *RRSetsService) Update(k RRSetKey, val RRSet) (*http.Response, error) {
|
|
var ignored interface{}
|
|
return s.client.put(k.URI(), val, &ignored)
|
|
}
|
|
|
|
// Delete deletes an RRSet
|
|
func (s *RRSetsService) Delete(k RRSetKey) (*http.Response, error) {
|
|
return s.client.delete(k.URI(), nil)
|
|
}
|