pax_global_header00006660000000000000000000000064136451561160014522gustar00rootroot0000000000000052 comment=c6356edd1b4bd5ced89c179ab7a451f88289452b udnssdk-1.3.5/000077500000000000000000000000001364515611600132035ustar00rootroot00000000000000udnssdk-1.3.5/.travis.yml000066400000000000000000000000541364515611600153130ustar00rootroot00000000000000language: go go: - 1.14 script: script/test udnssdk-1.3.5/CHANGELOG.md000066400000000000000000000053571364515611600150260ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] ## [1.3.5] - Added 'availableToServe' to BackupRecord DTO - Added 'status' to TCPool profile DTO - Improved the testing steps used latest `go 1.14` and `go mod` ## [1.3.4] - 2018-01-15 ### Changed - Update all references to udnssdk from Ensighten to terra-farm ## [1.3.3] - 2017-12-19 ### Changed - Added 'availableToServe' to SBPool and TCPool profile DTOs. ## [1.3.2] - 2017-03-03 ### Changed - CheckResponse: improve fallthrough error to include full Response Status and properly format Body - Client.NewRequest: split query to avoid encoding "?" as "%3F" into path ## [1.3.1] - 2017-03-03 ### Changed - Client.NewRequest: shallow-copy BaseURL to avoid retaining modifications ## [1.3.0] - 2017-02-28 ### Added - cmd/udns: add rrset query tool - DPRDataInfo.Type: add field to support API change ## [1.2.1] - 2016-06-13 ### Fixed * `omitempty` tags fixed for `ProbeInfoDTO.PoolRecord` & `ProbeInfoDTO.ID` * Check `*http.Response` values for nil before access ## [1.2.0] - 2016-06-09 ### Added * Add probe detail serialization helpers ### Changed * Flatten udnssdk.Response to mere http.Response * Extract self-contained passwordcredentials oauth2 TokenSource * Change ProbeTypes to constants ## [1.1.1] - 2016-05-27 ### Fixed * remove terraform tag for `GeoInfo.Codes` ## [1.1.0] - 2016-05-27 ### Added * Add terraform tags to structs to support mapstructure ### Fixed * `omitempty` tags fixed for `DirPoolProfile.NoResponse`, `DPRDataInfo.GeoInfo`, `DPRDataInfo.IPInfo`, `IPInfo.Ips` & `GeoInfo.Codes` * ProbeAlertDataDTO equivalence for times with different locations ### Changed * Convert RawProfile to use mapstructure and structs instead of round-tripping through json * CHANGELOG.md: fix link to v1.0.0 commit history ## [1.0.0] - 2016-05-11 ### Added * Support for API endpoints for `RRSets`, `Accounts`, `DirectionalPools`, Traffic Controller Pool `Probes`, `Events`, `Notifications` & `Alerts` * `Client` wraps common API access including OAuth, deferred tasks and retries [Unreleased]: https://github.com/Ensighten/udnssdk/compare/v1.3.2...HEAD [1.3.2]: https://github.com/Ensighten/udnssdk/compare/v1.3.1...v1.3.2 [1.3.1]: https://github.com/Ensighten/udnssdk/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/Ensighten/udnssdk/compare/v1.2.1...v1.3.0 [1.2.1]: https://github.com/Ensighten/udnssdk/compare/v1.2.0...v1.2.1 [1.2.0]: https://github.com/Ensighten/udnssdk/compare/v1.1.1...v1.2.0 [1.1.1]: https://github.com/Ensighten/udnssdk/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/Ensighten/udnssdk/compare/v1.0.0...v1.1.0 [1.0.0]: https://github.com/Ensighten/udnssdk/compare/v0.0.0...v1.0.0 udnssdk-1.3.5/CONTRIBUTING.md000066400000000000000000000102171364515611600154350ustar00rootroot00000000000000# Contributing Want to contribute? Up-to-date pointers should be at: Got an idea? Something smell wrong? Cause you pain? Or lost seconds of your life you'll never get back? All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin. Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die": you will not see that here. It is more important to me that you are able to contribute. If you haven't got time to do anything else, just email me and I'll try to help: . I promise to help guide this project with these principles: - Community: If a newbie has a bad time, it's a bug. - Software: Make it work, then make it right, then make it fast. - Technology: If it doesn't do a thing today, we can make it do it tomorrow. Here are some ways you can be part of the community: ## Something not working? Found a Bug? Find something that doesn't feel quite right? Here are 5 steps to getting it fixed! ### Check your version To make sure you're not wasting your time, you should be using the latest version before you file your bug. First of all, you should download the latest revision to be sure you are up to date. If you've done this and you still experience the bug, go ahead to the next step. ### Search our [issues] Now that you have the latest version and still think you've found a bug, search through issues first to see if anyone else has already filed it. This step is very important! If you find that someone has filed your bug already, please go to the next step anyway, but instead of filing a new bug, comment on the one you've found. If you can't find your bug in issues, go to the next step. ### Create a Github account https://github.com/join You will need to create a Github account to be able to report bugs (and to comment on them). If you have registered, proceed to the next step. ### File the bug! Now you are ready to file a bug. The [Writing a Good Bug Report] document gives some tips about the most useful information to include in bug reports. The better your bug report, the higher the chance that your bug will be addressed (and possibly fixed) quickly! ### What happens next? Once your bug is filed, you will receive email when it is updated at each stage in the bug life cycle. After the bug is considered fixed, you may be asked to download the latest revision and confirm that the fix works for you. ## Submitting patches 1. [Fork the repository.] 2. [Create a topic branch.] 3. Add specs for your unimplemented feature or bug fix. 4. Run `script/test`. If your specs pass, return to step 3. 5. Implement your feature or bug fix. 6. Run `script/test`. If your specs fail, return to step 5. 7. Add, commit (say *why* the changes were made, we can look at the diff to see *how* they were made.), and push your changes. For documentation-only fixes, please add `[ci skip]` to your commit message to avoid needless CI builds. 8. [Submit a patch.] ## Setting up a local dev environment For those of you who do want to contribute with code, we've tried to make it easy to get started. You can install all dependencies and tools with: script/bootstrap Good luck! ## Style guide There are great style guides out there, we don't need to reinvent the wheel. Here are ones we like: - `go`: https://code.google.com/p/go-wiki/wiki/CodeReviewComments - `sh`: http://google.github.io/styleguide/shell.xml - `ruby`: https://github.com/bbatsov/ruby-style-guide - `python`: https://www.python.org/dev/peps/pep-0008/ For some things, the best we've got is a decent formatting tool: - `markdown`: `pandoc --to=markdown --reference-links --atx-headers --columns 72` - `json`: `jq .` [issues]: https://github.com/terra-farm/udnssdk/issues [Writing a Good Bug Report]: http://www.webkit.org/quality/bugwriting.html [Fork the repository.]: https://help.github.com/articles/fork-a-repo [Create a topic branch.]: http://learn.github.com/p/branching.html [Submit a patch.]: https://help.github.com/articles/using-pull-requests udnssdk-1.3.5/LICENSE000066400000000000000000000020721364515611600142110ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015, 2016 Ensighten Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. udnssdk-1.3.5/README.md000066400000000000000000000034771364515611600144750ustar00rootroot00000000000000# udnssdk - An UltraDNS SDK for Go This is a golang SDK for the UltraDNS REST API. It's not feature complete, and currently is only known to be used for Terraform's `ultradns` provider. Full API docs are available at [godoc](https://godoc.org/github.com/terra-farm/udnssdk) ## Example ```go package main import ( "fmt" "log" "github.com/terra-farm/udnssdk" ) func main() { client := udnssdk.NewClient("username", "password", udnssdk.DefaultTestBaseURL) if client == nil { log.Fatalf("Failed to create client") } fmt.Printf("---- Query RRSets\n") rrsetkey := RRSetKey{ Zone: "domain.com", Type: "ANY", Name: "", } rrsets, err := client.RRSets.Select(rrsetkey) if err != nil { log.Fatalf(err) } fmt.Printf("%+v\n", rrsets) fmt.Printf("---- Create RRSet\n") rrsetkey = RRSetKey{ Zone: "domain.com", Type: "A", Name: "test", } rrset := udnssdk.RRSet{ OwnerName: r.Name, RRType: r.Type, TTL: 300, RData: []string{"127.0.0.1"}, } resp, err := client.RRSets.Create(rrsetkey, rrset) if err != nil { log.Fatalf(err) } fmt.Printf("Response: %+v\n", resp) fmt.Printf("---- Update RRSet\n") rrset = udnssdk.RRSet{ OwnerName: r.Name, RRType: r.Type, TTL: 300, RData: []string{"127.0.0.2"}, } resp, err := client.RRSets.Update(rrsetkey, rrset) if err != nil { log.Fatalf(err) } fmt.Printf("Response: %+v\n", resp) fmt.Printf("---- Delete RRSet\n") resp, err := client.RRSets.Delete(rrsetkey) if err != nil { log.Fatalf(err) } fmt.Printf("Response: %+v\n", resp) } ``` ## Thanks * Originally started as a modified version of [weppos/go-dnsimple](https://github.com/weppos/go-dnsimple) * Designed to add UltraDNS support to [terraform](http://terraform.io) * And for other languages, be sure to check out [UltraDNS's various SDKs](https://github.com/ultradns) udnssdk-1.3.5/account.go000066400000000000000000000033151364515611600151700ustar00rootroot00000000000000package udnssdk import ( "fmt" "net/http" ) // 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, *http.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, *http.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) (*http.Response, error) { return s.client.delete(k.URI(), nil) } udnssdk-1.3.5/account_test.go000066400000000000000000000054231364515611600162310ustar00rootroot00000000000000package udnssdk import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" ) func Test_Accounts_Select_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableAccountTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accounts, resp, err := testClient.Accounts.Select() if err != nil { if resp != nil && resp.StatusCode == 404 { t.SkipNow() } t.Fatal(err) } t.Logf("Accounts: %+v \n", accounts) t.Logf("Response: %+v\n", resp) } func Test_Accounts_Select(t *testing.T) { want := []Account{ Account{ AccountName: "terraform", AccountHolderUserName: "terraform", OwnerUserName: "terraform", NumberOfUsers: 1, NumberOfGroups: 1, AccountType: "ORGANIZATION", }, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := AccountListDTO{ Accounts: want, Resultinfo: ResultInfo{ TotalCount: len(want), Offset: 0, ReturnedCount: len(want), }, } mess, _ := json.Marshal(resp) fmt.Fprintln(w, string(mess)) })) defer ts.Close() testClient, _ := newStubClient(testUsername, testPassword, ts.URL, "", "") accounts, _, err := testClient.Accounts.Select() if err != nil { t.Fatal(err) } if len(accounts) != len(want) { t.Errorf("len(accounts): %+v, want: %+v", len(accounts), len(want)) } for i, a := range accounts { w := want[i] if a != w { t.Errorf("accounts[%d]: %+v, want: %+v", i, a, w) } } } func Test_Accounts_Find(t *testing.T) { want := Account{ AccountName: "terraform", AccountHolderUserName: "terraform", OwnerUserName: "terraform", NumberOfUsers: 1, NumberOfGroups: 1, AccountType: "ORGANIZATION", } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := want mess, _ := json.Marshal(resp) fmt.Fprintln(w, string(mess)) })) defer ts.Close() testClient, _ := newStubClient(testUsername, testPassword, ts.URL, "", "") a, _, err := testClient.Accounts.Find("terraform") if err != nil { t.Fatal(err) } if a != want { t.Errorf("account: %+v, want: %+v", a, want) } } func Test_AccountsURI(t *testing.T) { uri := AccountsURI() want := "accounts" if uri != want { t.Errorf("AccountsURI: %+v, want: %+v", uri, want) } } func Test_Accounts_URI_empty(t *testing.T) { a := AccountKey("") want := "accounts" uri := a.URI() if uri != want { t.Errorf("AccountKey.URI: %+v, want: %+v", uri, want) } } func Test_Accounts_URI_nonempty(t *testing.T) { a := AccountKey("foo") want := "accounts/foo" uri := a.URI() if uri != want { t.Errorf("AccountKey.URI: %+v, want: %+v", uri, want) } } udnssdk-1.3.5/alert.go000066400000000000000000000046111364515611600146430ustar00rootroot00000000000000package udnssdk import ( "log" "net/http" "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"` } // Equal compares to another ProbeAlertDataDTO, but uses time.Equals to compare semantic equvalance of AlertDate func (a ProbeAlertDataDTO) Equal(b ProbeAlertDataDTO) bool { return a.PoolRecord == b.PoolRecord && a.ProbeType == b.ProbeType && a.ProbeStatus == b.ProbeStatus && a.AlertDate.Equal(b.AlertDate) && a.FailoverOccured == b.FailoverOccured && a.OwnerName == b.OwnerName && a.Status == b.Status } // ProbeAlertDataListDTO wraps the response for an index of probe alerts 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 != nil && 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, *http.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 } udnssdk-1.3.5/alert_test.go000066400000000000000000000033641364515611600157060ustar00rootroot00000000000000package udnssdk import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "time" ) func Test_GetProbeAlerts(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableProbeTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testProbeDomain, Type: testProbeType, Name: testProbeName, } alerts, err := testClient.Alerts.Select(r) if err != nil { t.Fatal(err) } t.Logf("Probe Alerts: %+v \n", alerts) } func Test_Alerts_Select(t *testing.T) { want := []ProbeAlertDataDTO{ ProbeAlertDataDTO{ PoolRecord: "1.2.3.4", ProbeType: "DNS", ProbeStatus: "Failed", AlertDate: time.Now(), FailoverOccured: true, OwnerName: "foo.basedomain.example", Status: "Active", }, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := ProbeAlertDataListDTO{ Alerts: want, Queryinfo: QueryInfo{ Q: "", Sort: "", Reverse: false, Limit: 0, }, Resultinfo: ResultInfo{ TotalCount: len(want), Offset: 0, ReturnedCount: len(want), }, } mess, _ := json.Marshal(resp) fmt.Fprintln(w, string(mess)) })) defer ts.Close() testClient, _ := newStubClient(testUsername, testPassword, ts.URL, "", "") r := RRSetKey{ Zone: "basedomain.example", Type: "A", Name: "foo", } alerts, err := testClient.Alerts.Select(r) if err != nil { t.Fatal(err) } if len(alerts) != len(want) { t.Errorf("len(alerts): %+v, want: %+v", len(alerts), len(want)) } for i, a := range alerts { w := want[i] if !a.Equal(w) { t.Errorf("alerts[%d]: %#v, want: %#v", i, a, w) } } } udnssdk-1.3.5/cmd/000077500000000000000000000000001364515611600137465ustar00rootroot00000000000000udnssdk-1.3.5/cmd/udns/000077500000000000000000000000001364515611600147175ustar00rootroot00000000000000udnssdk-1.3.5/cmd/udns/main.go000066400000000000000000000037331364515611600162000ustar00rootroot00000000000000package main import ( "encoding/json" "flag" "fmt" "log" "os" "github.com/hashicorp/logutils" "github.com/terra-farm/udnssdk" ) var username string var password string var baseURL string var logLevel string var zone string var domain string var typ string func init() { flag.StringVar(&username, "username", os.Getenv("ULTRADNS_USERNAME"), "ultradns username") flag.StringVar(&password, "password", os.Getenv("ULTRADNS_PASSWORD"), "ultradns password") flag.StringVar(&baseURL, "base-url", udnssdk.DefaultLiveBaseURL, "ultradns base url") flag.StringVar(&logLevel, "log-level", "WARN", "log level: DEBUG, WARN, ERROR. default: WARN") flag.StringVar(&zone, "zone", "", "dns zone") flag.StringVar(&domain, "domain", "", "dns domain") flag.StringVar(&typ, "type", "A", "dns type") } func main() { flag.Parse() if username == "" { fmt.Println("no username provided. Set with parameter -username=samdoe or environment variable ULTRADNS_USERNAME=samdoe") os.Exit(1) } if password == "" { fmt.Println("no password provided. Set with parameter -password=s3cr3t or environment variable ULTRADNS_PASSWORD=s3cr3t") os.Exit(1) } if zone == "" { fmt.Println("no zone provided. Set with parameter -zone=example.com.") os.Exit(1) } if domain == "" { fmt.Println("no domain provided. Set with parameter -domain=foo") os.Exit(1) } filter := &logutils.LevelFilter{ Levels: []logutils.LogLevel{"DEBUG", "WARN", "ERROR"}, MinLevel: logutils.LogLevel(logLevel), Writer: os.Stderr, } log.SetOutput(filter) client, err := udnssdk.NewClient(username, password, baseURL) if err != nil { log.Fatalf("Error setting up client: %s", err) } k := udnssdk.RRSetKey{ Zone: zone, Type: typ, Name: domain, } rrs, err := client.RRSets.Select(k) if err != nil { log.Fatalf("Error requesting records: %s", err) } data, err := json.MarshalIndent(rrs, "", " ") if err != nil { log.Fatalf("Error marshaling response: %s", err) } fmt.Printf("%v\n", string(data)) } udnssdk-1.3.5/common.go000066400000000000000000000004751364515611600150300ustar00rootroot00000000000000package udnssdk import "net/http" // GetResultByURI just requests a URI func (c *Client) GetResultByURI(uri string) (*http.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 res, err } return res, err } udnssdk-1.3.5/directional_pool.go000066400000000000000000000242751364515611600170720ustar00rootroot00000000000000package udnssdk import ( "fmt" "log" "net/http" "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" terraform:"start"` End string `json:"end,omitempty" terraform:"end"` CIDR string `json:"cidr,omitempty" terraform:"cidr"` Address string `json:"address,omitempty" terraform:"address"` } // 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 != nil && 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, *http.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, *http.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{}) (*http.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{}) (*http.Response, error) { return s.client.put(k.URI(), val, nil) } // Delete requests deletion of a DirectionalPool func (s *GeoDirectionalPoolsService) Delete(k GeoDirectionalPoolKey) (*http.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 != nil && 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, *http.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, *http.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{}) (*http.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{}) (*http.Response, error) { return s.client.put(k.URI(), val, nil) } // Delete deletes an directional-pool func (s *IPDirectionalPoolsService) Delete(k IPDirectionalPoolKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } udnssdk-1.3.5/directional_pools_test.go000066400000000000000000000236251364515611600203120ustar00rootroot00000000000000package udnssdk import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "reflect" "testing" ) func Test_GeoDirectionalPoolKey_URI(t *testing.T) { want := "accounts/udnssdk/dirgroups/geo/unicorn" p := GeoDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } uri := p.URI() if uri != want { t.Errorf("URI: %+v, want: %+v", uri, want) } } func Test_GeoDirectionalPoolKey_QueryURI(t *testing.T) { want := "accounts/udnssdk/dirgroups/geo/unicorn?sort=NAME&query=rainbow&offset=1" p := GeoDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } uri := p.QueryURI("rainbow", 1) if uri != want { t.Errorf("QueryURI: %+v, want: %+v", uri, want) } } func Test_GeoDirectionalPoolKey_DirectionalPoolKey(t *testing.T) { want := DirectionalPoolKey{ Account: AccountKey("udnssdk"), Type: "geo", Name: "unicorn", } p := GeoDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } dp := p.DirectionalPoolKey() if dp != want { t.Errorf("DirectionalPoolKey: %+v, want: %+v", dp, want) } } func Test_GeoDirectionalPoolsService_Select_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName p := GeoDirectionalPoolKey{Account: AccountKey(accountName)} dpools, err := testClient.DirectionalPools.Geos().Select(p, "") if err != nil { t.Fatal(err) } t.Logf("Geo Pools: %v \n", dpools) } func Test_GeoDirectionalPoolsService_Select_Query_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName p := GeoDirectionalPoolKey{Account: AccountKey(accountName)} dpools, err := testClient.DirectionalPools.Geos().Select(p, testQuery) if err != nil { t.Fatal(err) } t.Logf("Geo Pools: %v \n", dpools) } func Test_GeoDirectionalPoolsService_Select(t *testing.T) { want := []AccountLevelGeoDirectionalGroupDTO{ AccountLevelGeoDirectionalGroupDTO{ Name: "unicorn", Description: "unicorn: a service of rainbows", Codes: []string{"US", "CA"}, }, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := AccountLevelGeoDirectionalGroupListDTO{ AccountName: "udnssdk", GeoGroups: want, Queryinfo: QueryInfo{ Q: "unicorn", Sort: "", Reverse: false, Limit: 0, }, Resultinfo: ResultInfo{ TotalCount: len(want), Offset: 0, ReturnedCount: len(want), }, } mess, _ := json.Marshal(resp) fmt.Fprintln(w, string(mess)) })) defer ts.Close() c, _ := newStubClient(testUsername, testPassword, ts.URL, "", "") p := GeoDirectionalPoolKey{Account: AccountKey("udnssdk")} ps, err := c.DirectionalPools.Geos().Select(p, "unicorn") if err != nil { t.Fatal(err) } if !reflect.DeepEqual(ps, want) { t.Errorf("Geos: %+v, want: %+v", ps, want) } } func Test_GeoDirectionalPoolsService_Find_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName p := GeoDirectionalPoolKey{ Account: AccountKey(accountName), Name: testIPDPool.Name, } dp, resp, err := testClient.DirectionalPools.Geos().Find(p) t.Logf("Test Get IP DPool Group (%s, %s)\n", testIPDPool.Name, testIPDPool) t.Logf("Response: %+v\n", resp) t.Logf("DPool: %+v\n", dp) if err != nil { t.Logf("GetDirectionalPoolIP Error: %+v\n", err) if resp != nil && resp.StatusCode == 404 { return } t.Fatal(err) } dp2, er := json.Marshal(dp) t.Logf("DPool Marshalled back: %s - %+v\n", string(dp2), er) } func Test_GeoDirectionalPoolsService_Find(t *testing.T) { want := AccountLevelGeoDirectionalGroupDTO{ Name: "unicorn", Description: "unicorn: a service of rainbows", Codes: []string{"US", "CA"}, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := want mess, _ := json.Marshal(resp) fmt.Fprintln(w, string(mess)) })) defer ts.Close() c, _ := newStubClient(testUsername, testPassword, ts.URL, "", "") p := GeoDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } a, _, err := c.DirectionalPools.Geos().Find(p) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(a, want) { t.Errorf("IPs: %+v, want: %+v", a, want) } } func Test_GeoDirectionalPoolsService_Create(t *testing.T) { t.SkipNow() } func Test_GeoDirectionalPoolsService_Update(t *testing.T) { t.SkipNow() } func Test_GeoDirectionalPoolService_Delete_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName p := GeoDirectionalPoolKey{ Account: AccountKey(accountName), Name: testIPDPool.Name, } t.Logf("Delete(%+v)\n", p) resp, err := testClient.DirectionalPools.Geos().Delete(p) if err != nil { t.Logf("DeleteDirectionalPoolIP Error: %+v\n", err) if resp != nil && resp.StatusCode == 404 { return } t.Fatal(err) } t.Logf("Response: %+v\n", resp) } func Test_GeoDirectionalPoolsService_Delete(t *testing.T) { t.SkipNow() } func Test_IPDirectionalPoolKey_URI(t *testing.T) { want := "accounts/udnssdk/dirgroups/ip/unicorn" p := IPDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } uri := p.URI() if uri != want { t.Errorf("URI: %+v, want: %+v", uri, want) } } func Test_IPDirectionalPoolKey_QueryURI(t *testing.T) { want := "accounts/udnssdk/dirgroups/ip/unicorn?sort=NAME&query=rainbow&offset=1" p := IPDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } uri := p.QueryURI("rainbow", 1) if uri != want { t.Errorf("QueryURI: %+v, want: %+v", uri, want) } } func Test_IPDirectionalPoolKey_DirectionalPoolKey(t *testing.T) { want := DirectionalPoolKey{ Account: AccountKey("udnssdk"), Type: "ip", Name: "unicorn", } p := IPDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } dp := p.DirectionalPoolKey() if dp != want { t.Errorf("DirectionalPoolKey: %+v, want: %+v", dp, want) } } func Test_IPDirectionalPoolService_Select_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if !enableChanges { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName p := IPDirectionalPoolKey{Account: AccountKey(accountName)} dpools, err := testClient.DirectionalPools.IPs().Select(p, "") if err != nil { t.Fatal(err) } t.Logf("IP Pools: %v \n", dpools) } func Test_IPDirectionalPoolService_Select_Query_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if !enableChanges { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName p := IPDirectionalPoolKey{Account: AccountKey(accountName)} dpools, err := testClient.DirectionalPools.IPs().Select(p, testQuery) t.Logf("IP Pools: %v \n", dpools) if err != nil { t.Fatal(err) } } func Test_IPDirectionalPoolsService_Select(t *testing.T) { t.SkipNow() } func Test_IPDirectionalPoolsService_Find(t *testing.T) { want := AccountLevelIPDirectionalGroupDTO{ Name: "unicorn", Description: "unicorn: a service of rainbows", IPs: []IPAddrDTO{ IPAddrDTO{Address: "1.2.3.4"}, }, } ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := want mess, _ := json.Marshal(resp) fmt.Fprintln(w, string(mess)) })) defer ts.Close() c, _ := newStubClient(testUsername, testPassword, ts.URL, "", "") p := IPDirectionalPoolKey{ Account: AccountKey("udnssdk"), Name: "unicorn", } a, _, err := c.DirectionalPools.IPs().Find(p) if err != nil { t.Fatal(err) } if !reflect.DeepEqual(a, want) { t.Errorf("IPs: %+v, want: %+v", a, want) } } func Test_IPDirectionalPoolsService_Create_Live(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableDirectionalPoolTests { t.SkipNow() } if !enableChanges { t.SkipNow() } if testAccounts == nil { t.Logf("No Accounts Present, skipping...") t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } accountName := testAccounts[0].AccountName t.Logf("Creating %s with %+v\n", testIPDPool.Name, testIPDPool) p := IPDirectionalPoolKey{ Account: AccountKey(accountName), Name: testIPDPool.Name, } resp, err := testClient.DirectionalPools.IPs().Create(p, testIPDPool) if err != nil { t.Fatal(err) } t.Logf("Response: %+v\n", resp) } func Test_IPDirectionalPoolsService_Create(t *testing.T) { t.SkipNow() } func Test_IPDirectionalPoolsService_Update(t *testing.T) { t.SkipNow() } func Test_IPDirectionalPoolsService_Delete(t *testing.T) { t.SkipNow() } udnssdk-1.3.5/event.go000066400000000000000000000065641364515611600146660ustar00rootroot00000000000000package udnssdk import ( "fmt" "log" "net/http" "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 != nil && 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, *http.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, *http.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) (*http.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) (*http.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) (*http.Response, error) { return s.client.delete(e.URI(), nil) } udnssdk-1.3.5/event_test.go000066400000000000000000000007421364515611600157150ustar00rootroot00000000000000package udnssdk import ( "testing" ) func Test_ListEvents(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableProbeTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testProbeDomain, Type: testProbeType, Name: testProbeName, } events, err := testClient.Events.Select(r, "") t.Logf("Events: %+v \n", events) if err != nil { t.Fatal(err) } } udnssdk-1.3.5/notification.go000066400000000000000000000076321364515611600162300ustar00rootroot00000000000000package udnssdk import ( "fmt" "log" "net/http" "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, *http.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 != nil && 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, *http.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, *http.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) (*http.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) (*http.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) (*http.Response, error) { return s.client.delete(k.URI(), nil) } udnssdk-1.3.5/notification_test.go000066400000000000000000000012771364515611600172660ustar00rootroot00000000000000package udnssdk import ( "testing" ) func Test_ListNotifications(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableProbeTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testProbeDomain, Type: testProbeType, Name: testProbeName, } events, resp, err := testClient.Notifications.Select(r, "") if err != nil { if resp != nil && resp.StatusCode == 404 { t.Logf("ERROR - %+v", err) t.SkipNow() } t.Fatal(err) } t.Logf("Notifications: %+v \n", events) t.Logf("Response: %+v\n", resp) } // TODO: Write a full Notification test suite. We do use these. udnssdk-1.3.5/password/000077500000000000000000000000001364515611600150455ustar00rootroot00000000000000udnssdk-1.3.5/password/password.go000066400000000000000000000042511364515611600172400ustar00rootroot00000000000000// Package password implements the OAuth2.0 "password credentials" token flow. // See https://tools.ietf.org/html/rfc6749#section-4.3 package password import ( "net/http" "golang.org/x/net/context" "golang.org/x/oauth2" ) // Config describes a Resource Owner Password Credentials OAuth2 flow, with the // client application information, resource owner credentials and the server's // endpoint URLs. type Config struct { // ClientID is the application's ID. ClientID string // ClientSecret is the application's secret. ClientSecret string // Resource owner username Username string // Resource owner password Password string // Endpoint contains the resource server's token endpoint // URLs. These are constants specific to each server and are // often available via site-specific packages, such as // google.Endpoint or github.Endpoint. Endpoint oauth2.Endpoint // Scope specifies optional requested permissions. Scopes []string } // Client returns an HTTP client using the provided token. // The token will auto-refresh as necessary. The underlying // HTTP transport will be obtained using the provided context. // The returned client and its Transport should not be modified. func (c *Config) Client(ctx context.Context) *http.Client { return oauth2.NewClient(ctx, c.TokenSource(ctx)) } // TokenSource returns a TokenSource that returns t until t expires, // automatically refreshing it as necessary using the provided context and the // client ID and client secret. // // Most users will use Config.Client instead. func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource { source := &tokenSource{ ctx: ctx, conf: c, } return oauth2.ReuseTokenSource(nil, source) } type tokenSource struct { ctx context.Context conf *Config } // Token refreshes the token by using a new password credentials request. // tokens received this way do not include a refresh token func (c *tokenSource) Token() (*oauth2.Token, error) { config := oauth2.Config{ ClientID: c.conf.ClientID, ClientSecret: c.conf.ClientSecret, Endpoint: c.conf.Endpoint, Scopes: c.conf.Scopes, } return config.PasswordCredentialsToken(c.ctx, c.conf.Username, c.conf.Password) } udnssdk-1.3.5/probe.go000066400000000000000000000223151364515611600146440ustar00rootroot00000000000000package udnssdk import ( "encoding/json" "fmt" "net/http" ) // ProbeType wraps the possible types of a ProbeInfoDTO type ProbeType string // Here lie all the possible ProbeType values const ( DNSProbeType ProbeType = "DNS" FTPProbeType ProbeType = "FTP" HTTPProbeType ProbeType = "HTTP" PingProbeType ProbeType = "PING" SMTPProbeType ProbeType = "SMTP" SMTPSENDProbeType ProbeType = "SMTP_SEND" TCPProbeType ProbeType = "TCP" ) // ProbeInfoDTO wraps a probe response type ProbeInfoDTO struct { ID string `json:"id,omitempty"` PoolRecord string `json:"poolRecord,omitempty"` ProbeType ProbeType `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 ProbeType } // 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(t ProbeType) (err error) { s.typ = t d, err := s.GetDetailsObject(t) if err != nil { return err } s.Detail = d return nil } // GetDetailsObject extracts the appropriate details object from a ProbeDetailsDTO with the given ProbeType func (s *ProbeDetailsDTO) GetDetailsObject(t ProbeType) (interface{}, error) { switch t { case DNSProbeType: return s.DNSProbeDetails() case FTPProbeType: return s.FTPProbeDetails() case HTTPProbeType: return s.HTTPProbeDetails() case PingProbeType: return s.PingProbeDetails() case SMTPProbeType: return s.SMTPProbeDetails() case SMTPSENDProbeType: return s.SMTPSENDProbeDetails() case TCPProbeType: return s.TCPProbeDetails() default: return nil, fmt.Errorf("Invalid ProbeType: %#v", t) } } // DNSProbeDetails returns the ProbeDetailsDTO data deserialized as a DNSProbeDetailsDTO func (s *ProbeDetailsDTO) DNSProbeDetails() (DNSProbeDetailsDTO, error) { var d DNSProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // FTPProbeDetails returns the ProbeDetailsDTO data deserialized as a FTPProbeDetailsDTO func (s *ProbeDetailsDTO) FTPProbeDetails() (FTPProbeDetailsDTO, error) { var d FTPProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // HTTPProbeDetails returns the ProbeDetailsDTO data deserialized as a HTTPProbeDetailsDTO func (s *ProbeDetailsDTO) HTTPProbeDetails() (HTTPProbeDetailsDTO, error) { var d HTTPProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // PingProbeDetails returns the ProbeDetailsDTO data deserialized as a PingProbeDetailsDTO func (s *ProbeDetailsDTO) PingProbeDetails() (PingProbeDetailsDTO, error) { var d PingProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // SMTPProbeDetails returns the ProbeDetailsDTO data deserialized as a SMTPProbeDetailsDTO func (s *ProbeDetailsDTO) SMTPProbeDetails() (SMTPProbeDetailsDTO, error) { var d SMTPProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // SMTPSENDProbeDetails returns the ProbeDetailsDTO data deserialized as a SMTPSENDProbeDetailsDTO func (s *ProbeDetailsDTO) SMTPSENDProbeDetails() (SMTPSENDProbeDetailsDTO, error) { var d SMTPSENDProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // TCPProbeDetails returns the ProbeDetailsDTO data deserialized as a TCPProbeDetails func (s *ProbeDetailsDTO) TCPProbeDetails() (TCPProbeDetailsDTO, error) { var d TCPProbeDetailsDTO err := json.Unmarshal(s.data, &d) return d, err } // UnmarshalJSON does what it says on the tin 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, *http.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, *http.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) (*http.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) (*http.Response, error) { return s.client.put(k.URI(), dp, nil) } // Delete deletes a probe by its ProbeKey func (s *ProbesService) Delete(k ProbeKey) (*http.Response, error) { return s.client.delete(k.URI(), nil) } udnssdk-1.3.5/probe_test.go000066400000000000000000000017571364515611600157120ustar00rootroot00000000000000package udnssdk import ( "testing" ) func Test_ProbesSelectProbes(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableProbeTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testProbeDomain, Type: testProbeType, Name: testProbeName, } probes, resp, err := testClient.Probes.Select(r, "") if err != nil { if resp != nil && resp.StatusCode == 404 { t.Logf("ERROR - %+v", err) t.SkipNow() } t.Fatal(err) } t.Logf("Probes: %+v \n", probes) t.Logf("Response: %+v\n", resp) for i, e := range probes { t.Logf("DEBUG - Probe %d Data - %s\n", i, e.Details.data) err = e.Details.Populate(e.ProbeType) if err != nil { t.Fatal(err) } t.Logf("DEBUG - Populated Probe: %+v\n", e) } } /* TODO: A full probe test suite. I'm not really even sure I understand how this * works well enough to write one yet. What is the correct order of operations? */ udnssdk-1.3.5/rrset.go000066400000000000000000000300351364515611600146720ustar00rootroot00000000000000package 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", 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("incorrect JSON-LD @context for DirPoolProfile %s", 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("incorrect JSON-LD @context for RDPoolProfile %s", 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("incorrect JSON-LD @context for SBPoolProfile %s", 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("incorrect JSON-LD @context for TCPoolProfile %s", 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"` Type string `json:"type,omitempty" terraform:"type"` // not mentioned in REST API doc } // 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"` AvailableToServe bool `json:"availableToServe,omitempty"` } // BackupRecord wraps the backupRecord objects of an SBPoolProfile response type BackupRecord struct { RData string `json:"rdata,omitempty"` FailoverDelay int `json:"failoverDelay,omitempty"` AvailableToServe bool `json:"availableToServe,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"` Status string `json:"status,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) } udnssdk-1.3.5/rrset_test.go000066400000000000000000000220341364515611600157310ustar00rootroot00000000000000package udnssdk import ( "encoding/json" "os" "reflect" "strings" "testing" ) func Test_RRSets_SelectPre(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if testClient == nil { t.Fatalf("TestClient Not Defined?\n") } if !enableRRSetTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "ANY", Name: testHostname, } t.Logf("Select(%v)", r) rrsets, err := testClient.RRSets.Select(r) if err != nil { t.Fatal(err) } t.Logf("RRSets: %+v\n", rrsets) } func Test_RRSets_Select(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "ANY", Name: "", } t.Logf("Select(%v)", r) rrsets, err := testClient.RRSets.Select(r) if err != nil { t.Fatal(err) } t.Logf("RRSets: %+v\n", rrsets) t.Logf("Checking for profiles...\n") for _, rr := range rrsets { if rr.Profile != nil { typ := rr.Profile.Context() if typ == "" { t.Fatalf("Could not get type for profile %+v\n", rr.Profile) } t.Logf("Found Profile %s for %s\n", rr.Profile.Context(), rr.OwnerName) st, er := json.Marshal(rr.Profile) t.Logf("Marshal the profile to JSON: %s / %+v", string(st), er) p, _ := rr.Profile.GetProfileObject() t.Logf("Check the Magic Profile: %+v\n", p) } } } func Test_RRSets_Create(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } if !enableChanges { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "A", Name: testHostname, } val := RRSet{ OwnerName: r.Name, RRType: r.Type, TTL: 300, RData: []string{testIP1}, } p := RDPoolProfile{ Context: RDPoolSchema, Order: "ROUND_ROBIN", Description: "T. migratorius", } val.Profile = p.RawProfile() t.Logf("Create(%v, %v)", r, val) resp, err := testClient.RRSets.Create(r, val) if err != nil { t.Fatal(err) } t.Logf("Response: %+v\n", resp) } // Another Get Test if it matchs the Ip in IP1 func Test_RRSets_SelectMid1(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "ANY", Name: testHostname, } t.Logf("Select(%v)", r) rrsets, err := testClient.RRSets.Select(r) if err != nil { t.Fatal(err) } t.Logf("RRSets: %+v\n", rrsets) // Do the test v IP1 here actual := rrsets[0].RData[0] expected := testIP1 if actual != expected { t.Fatalf("actual RData[0]\"%s\" != expected \"%s\"", actual, expected) } } func Test_RRSets_Update(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } if !enableChanges { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "A", Name: testHostname, } val := RRSet{ OwnerName: r.Name, RRType: r.Type, TTL: 300, RData: []string{testIP2}, } p := RDPoolProfile{ Context: RDPoolSchema, Order: "RANDOM", Description: "T. migratorius", } val.Profile = p.RawProfile() t.Logf("Update(%v, %v)", r, val) resp, err := testClient.RRSets.Update(r, val) if err != nil { t.Fatal(err) } t.Logf("Response: %+v\n", resp) } // Another Get Test if it matches the Ip in IP2 func Test_RRSets_SelectMid(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "ANY", Name: testHostname, } t.Logf("Select(%v)", r) rrsets, err := testClient.RRSets.Select(r) if err != nil { t.Fatal(err) } t.Logf("RRSets: %+v\n", rrsets) // Do the test v IP2 here if rrsets[0].RData[0] != testIP2 { t.Fatalf("RData[0]\"%s\" != testIP2\"%s\"", rrsets[0].RData[0], testIP2) } p, _ := rrsets[0].Profile.GetProfileObject() t.Logf("Check the Magic Profile: %+v\n", p) } func Test_RRSet_Delete(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } if !enableChanges { t.SkipNow() } if testHostname == "" || testHostname[0] == '*' || testHostname[0] == '@' || testHostname == "www" || testHostname[0] == '.' { t.Fatalf("Invalid testHostname defined: %v", testHostname) os.Exit(1) } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "ANY", Name: testHostname, } t.Logf("Select(%v)", r) rrsets, err := testClient.RRSets.Select(r) if err != nil { t.Fatal(err) } t.Logf("RRSets: %+v\n", rrsets) for _, e := range rrsets { r := RRSetKey{ Zone: testDomain, Type: e.RRType, Name: e.OwnerName, } if strings.Index(r.Type, " ") != -1 { t.Logf("Stripping whitespace rom Type: %v\n", r.Type) r.Type = strings.Fields(r.Type)[0] } t.Logf("Delete(%v)", r) resp, err := testClient.RRSets.Delete(r) t.Logf("Response: %+v\n", resp) if err != nil { t.Fatal(err) } } } func Test_RRSet_SelectPost(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } if !enableRRSetTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } r := RRSetKey{ Zone: testDomain, Type: "ANY", Name: testHostname, } t.Logf("Select(%v)", r) rrsets, err := testClient.RRSets.Select(r) if err != nil { t.Fatal(err) } t.Logf("RRSets: %+v\n", rrsets) } func Test_DirPoolProfile_RawProfile(t *testing.T) { p := DirPoolProfile{} expected := RawProfile{ "@context": ProfileSchema(""), "description": "", // "rdataInfo": []DPRDataInfo(nil), "rdataInfo": []interface{}{}, } actual := p.RawProfile() if !reflect.DeepEqual(actual, expected) { for k := range expected { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } for k := range actual { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } t.Fatalf("want %#v got %#v", expected, actual) } } func Test_RDPoolProfile_RawProfile(t *testing.T) { p := RDPoolProfile{} expected := RawProfile{ "@context": ProfileSchema(""), "description": "", "order": "", } actual := p.RawProfile() if !reflect.DeepEqual(actual, expected) { for k := range expected { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } for k := range actual { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } t.Fatalf("want %#v got %#v", expected, actual) } } func Test_SBPoolProfile_RawProfile(t *testing.T) { p := SBPoolProfile{} expected := RawProfile{ "@context": ProfileSchema(""), "description": "", "actOnProbes": false, // "rdataInfo": []SBRDataInfo(nil), "rdataInfo": []interface{}{}, "runProbes": false, // "backupRecords": []BackupRecord(nil), "backupRecords": []interface{}{}, } actual := p.RawProfile() if !reflect.DeepEqual(actual, expected) { for k := range expected { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } for k := range actual { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } t.Fatalf("want %#v got %#v", expected, actual) } } func Test_RawProfile_TCPoolProfile(t *testing.T) { input := RawProfile{ "@context": TCPoolSchema, "description": "", "actOnProbes": false, "rdataInfo": []SBRDataInfo{}, "runProbes": false, } expected := TCPoolProfile{ Context: TCPoolSchema, Description: "", ActOnProbes: false, RunProbes: false, RDataInfo: []SBRDataInfo{}, } actual, err := input.TCPoolProfile() if err != nil { t.Fatal(err) } if !reflect.DeepEqual(actual, expected) { t.Fatalf("want %#v got %#v", expected, actual) } } func Test_TCPoolProfile_RawProfile(t *testing.T) { input := TCPoolProfile{} expected := RawProfile{ "@context": ProfileSchema(""), "description": "", "actOnProbes": false, // "rdataInfo": []SBRDataInfo(nil), "rdataInfo": []interface{}{}, "runProbes": false, } actual := input.RawProfile() if !reflect.DeepEqual(actual, expected) { for k := range expected { if !reflect.DeepEqual(actual[k], expected[k]) { t.Fatalf("RawProfile[%v] want %#v got %#v", k, expected[k], actual[k]) } } t.Fatalf("want %#v got %#v", expected, actual) } } udnssdk-1.3.5/script/000077500000000000000000000000001364515611600145075ustar00rootroot00000000000000udnssdk-1.3.5/script/bootstrap000077500000000000000000000000651364515611600164530ustar00rootroot00000000000000#!/bin/sh set -e go mod init github.com go mod tidy udnssdk-1.3.5/script/test000077500000000000000000000001051364515611600154100ustar00rootroot00000000000000#!/bin/sh set -e script/bootstrap go fmt go vet ./... go test ./... udnssdk-1.3.5/task.go000066400000000000000000000060571364515611600145040ustar00rootroot00000000000000package udnssdk import ( "fmt" "log" "net/http" "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 != nil && 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, *http.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, *http.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) (*http.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) (*http.Response, error) { return s.client.GetResultByURI(t.ResultURI) } // Delete requests deletions func (s *TasksService) Delete(t TaskID) (*http.Response, error) { return s.client.delete(t.URI(), nil) } udnssdk-1.3.5/tasks_test.go000066400000000000000000000005271364515611600157220ustar00rootroot00000000000000package udnssdk import ( "testing" ) func Test_ListTasks(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } tasks, err := testClient.Tasks.Select("") t.Logf("Tasks: %+v \n", tasks) if err != nil { t.Fatal(err) } } udnssdk-1.3.5/token_source.go000066400000000000000000000012761364515611600162400ustar00rootroot00000000000000package udnssdk import ( "fmt" "golang.org/x/oauth2" oauthPassword "github.com/terra-farm/udnssdk/password" ) // NewConfig creates a new *password.config for UltraDNS OAuth2 func NewConfig(username, password, BaseURL string) *oauthPassword.Config { c := oauthPassword.Config{} c.Username = username c.Password = password c.Endpoint = Endpoint(BaseURL) return &c } // Endpoint returns an oauth2.Endpoint for UltraDNS func Endpoint(BaseURL string) oauth2.Endpoint { return oauth2.Endpoint{ TokenURL: TokenURL(BaseURL), } } // TokenURL returns an OAuth2 TokenURL for UltraDNS func TokenURL(BaseURL string) string { return fmt.Sprintf("%s/%s/authorization/token", BaseURL, apiVersion) } udnssdk-1.3.5/udnssdk.go000066400000000000000000000202751364515611600152130ustar00rootroot00000000000000package 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" "strings" "time" "golang.org/x/oauth2" oauthPassword "github.com/terra-farm/udnssdk/password" ) 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 *oauthPassword.Config BaseURL *url.URL 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) u, err := url.Parse(baseURL) if err != nil { return nil, err } c := &Client{ HTTPClient: conf.Client(ctx), BaseURL: u, 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) { u, err := url.Parse(baseURL) if err != nil { return nil, err } c := &Client{ HTTPClient: &http.Client{}, BaseURL: u, 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, pathquery string, payload interface{}) (*http.Request, error) { url := *c.BaseURL pq := strings.SplitN(pathquery, "?", 2) url.Path = url.Path + fmt.Sprintf("%s/%s", apiVersion, pq[0]) if len(pq) == 2 { url.RawQuery = pq[1] } 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.String(), 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: %#v, but could not extract any errors from Body: %#v", r.Status, string(body)) } udnssdk-1.3.5/udnssdk_test.go000066400000000000000000000142301364515611600162440ustar00rootroot00000000000000package udnssdk import ( "fmt" "io/ioutil" "log" "math/rand" "net/http" "os" "strings" "testing" "time" ) var ( testUsername = os.Getenv("ULTRADNS_USERNAME") testPassword = os.Getenv("ULTRADNS_PASSWORD") testDomain = os.Getenv("ULTRADNS_DOMAIN") testHostname = os.Getenv("ULTRADNS_TEST_HOSTNAME") testIP1 = os.Getenv("ULTRADNS_TEST_IP1") testIP2 = os.Getenv("ULTRADNS_TEST_IP2") testBaseURL = os.Getenv("ULTRADNS_BASEURL") testQuery = os.Getenv("ULTRADNS_TEST_QUERY") testProbeType = os.Getenv("ULTRADNS_TEST_PROBE_TYPE") testProbeName = os.Getenv("ULTRADNS_TEST_PROBE_NAME") testProbeDomain = os.Getenv("ULTRADNS_TEST_PROBE_DOMAIN") testIPDPoolName = "testipdpool" testIPDPoolAddress = "127.0.0.1" testIPDPoolDescr = "A Test IP Directional Pool Group" testIPAddrDTO = IPAddrDTO{Address: "127.0.0.1"} testIPDPool = AccountLevelIPDirectionalGroupDTO{Name: "testippool", Description: "An IP Test Pool", IPs: []IPAddrDTO{IPAddrDTO{Address: "127.0.0.1"}}} testGeoDPool = AccountLevelGeoDirectionalGroupDTO{Name: "testgeopool", Description: "A test geo pool", Codes: []string{"US, UK"}} testGeoDPoolName = "testgeodpool" testGeoDPoolDescr = "A Test Geo Directional Pool Group" testGeoDPoolCodes = []string{"US", "UK"} envenableAccountTests = os.Getenv("ULTRADNS_ENABLE_ACCOUNT_TESTS") envenableRRSetTests = os.Getenv("ULTRADNS_ENABLE_RRSET_TESTS") envenableProbeTests = os.Getenv("ULTRADNS_ENABLE_PROBE_TESTS") envenableChanges = os.Getenv("ULTRADNS_ENABLE_CHANGES") envenableDirectionalPoolTests = os.Getenv("ULTRADNS_ENABLE_DPOOL_TESTS") envEnableIntegrationTests = os.Getenv("ULTRADNS_ENABLE_INTEGRATION_TESTS") enableAccountTests = true enableRRSetTests = true enableProbeTests = true enableChanges = true enableDirectionalPoolTests = false enableIntegrationTests = false testProfile = `{"@context": "http://schemas.ultradns.com/RDPool.jsonschema", "order": "ROUND_ROBIN","description": "T. migratorius"}` testProfile2 = `{"@context": "http://schemas.ultradns.com/RDPool.jsonschema", "order": "RANDOM","description": "T. migratorius"}` testClient *Client testAccounts []Account ) func TestMain(m *testing.M) { rand.Seed(time.Now().UnixNano()) if envEnableIntegrationTests == "false" || envEnableIntegrationTests == "0" { enableIntegrationTests = false } else if envEnableIntegrationTests == "true" || envEnableIntegrationTests == "1" { enableIntegrationTests = true } if enableIntegrationTests { if testUsername == "" { log.Printf("Please configure ULTRADNS_USERNAME.\n") os.Exit(1) } if testPassword == "" { log.Printf("Please configure ULTRADNS_PASSWORD.\n") os.Exit(1) } if testDomain == "" { log.Printf("Please configure ULTRADNS_DOMAIN.\n") os.Exit(1) } if testHostname == "" { log.Printf("Please configure ULTRADNS_TEST_HOSTNAME.\n") os.Exit(1) } } if testBaseURL == "" { testBaseURL = DefaultTestBaseURL } if testIP1 == "" { testIP1 = "54.86.13.225" } if testIP2 == "" { testIP2 = fmt.Sprintf("54.86.13.%d", (rand.Intn(254) + 1)) } if testQuery == "" { testQuery = "nexus" } if testProbeName == "" || testProbeType == "" { testProbeName = "nexus2" testProbeType = "A" } if testProbeDomain == "" { testProbeDomain = testDomain } if envenableAccountTests == "false" || envenableAccountTests == "0" { enableAccountTests = false } else if envenableAccountTests == "true" || envenableAccountTests == "1" { enableAccountTests = true } if envenableRRSetTests == "false" || envenableRRSetTests == "0" { enableRRSetTests = false } else if envenableRRSetTests == "true" || envenableRRSetTests == "1" { enableRRSetTests = true } // TODO: I need a better way of handling this. /* if envenableFUDGETests == "false" || envenableFUDGETests == "0" { enableFUDGETests = false } else if envenableFUDGETests == "true" || envenableFUDGETests == "1" { enableFUDGETests = true } */ if envenableProbeTests == "false" || envenableProbeTests == "0" { enableProbeTests = false } else if envenableProbeTests == "true" || envenableProbeTests == "1" { enableProbeTests = true } if envenableChanges == "false" || envenableChanges == "0" { enableChanges = false } else if envenableChanges == "true" || envenableChanges == "1" { enableChanges = true } if envenableDirectionalPoolTests == "false" || envenableDirectionalPoolTests == "0" { enableDirectionalPoolTests = false } else if envenableDirectionalPoolTests == "true" || envenableDirectionalPoolTests == "1" { enableDirectionalPoolTests = true } testAccounts = nil os.Exit(m.Run()) } func Test_CreateClient(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } log.Printf("Creating Client...\n") var err error testClient, err = NewClient(testUsername, testPassword, testBaseURL) if testClient == nil || err != nil { t.Fail() log.Fatalf("Could not create client - %+v\n", err) os.Exit(1) } t.Logf("Client created successfully.\n") } func Test_Do(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } testClient, err := NewClient(testUsername, testPassword, testBaseURL) if err != nil { t.Fatal(err) } _, err = testClient.Do("GET", "zones", nil, nil) if err != nil { t.Fatal(err) } } func Test_Do_PreservesBaseURL(t *testing.T) { if !enableIntegrationTests { t.SkipNow() } testClient, _ := NewClient(testUsername, testPassword, testBaseURL) if testClient.BaseURL.String() != testBaseURL { t.Fatalf("BaseURL = %v; want: %v", testClient.BaseURL.String(), testBaseURL) } testClient.Do("GET", "zones", nil, nil) if testClient.BaseURL.String() != testBaseURL { t.Fatalf("BaseURL = %v; want: %v", testClient.BaseURL.String(), testBaseURL) } } func Test_CheckResponse_StatusCode4xx(t *testing.T) { h := &http.Response{ Body: ioutil.NopCloser(strings.NewReader("")), StatusCode: 400, Status: "400 Bad Request", } err := CheckResponse(h) if err.Error() != "Response had non-successful Status: \"400 Bad Request\", but could not extract any errors from Body: \"\"" { t.Fatal(err) } }