terraform/vendor/github.com/Ensighten/udnssdk/rrset.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)
}