diff --git a/Makefile b/Makefile index a77f869..43969d4 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -VERSION=0.9.12 +VERSION=0.9.13 SOURCE?=./... VINYLDNS_REPO=github.com/vinyldns/vinyldns -VINYLDNS_VERSION=0.9.3 +VINYLDNS_VERSION=0.9.5 all: check-fmt test build integration stop-api validate-version install diff --git a/vinyldns/endpoints.go b/vinyldns/endpoints.go index 0935d03..925210d 100644 --- a/vinyldns/endpoints.go +++ b/vinyldns/endpoints.go @@ -55,6 +55,13 @@ func recordSetsListEP(c *Client, zoneID string, f ListFilter) string { return concatStrs("", recordSetsEP(c, zoneID), query) } +func recordSetsGlobalListEP(c *Client, f GlobalListFilter) string { + query := buildGlobalListQuery(f) + base := concatStrs("", c.Host, "/recordsets") + + return concatStrs("", base, query) +} + func recordSetEP(c *Client, zoneID, recordSetID string) string { return concatStrs("", recordSetsEP(c, zoneID), "/", recordSetID) } @@ -125,3 +132,38 @@ func buildQuery(f ListFilter, nameFilterName string) string { return query + strings.Join(params, "&") } + +func buildGlobalListQuery(f GlobalListFilter) string { + params := []string{} + query := "?" + + if f.RecordNameFilter != "" { + params = append(params, fmt.Sprintf("%s=%s", "recordNameFilter", f.RecordNameFilter)) + } + + if f.RecordTypeFilter != "" { + params = append(params, fmt.Sprintf("%s=%s", "recordTypeFilter", f.RecordTypeFilter)) + } + + if f.RecordOwnerGroupFilter != "" { + params = append(params, fmt.Sprintf("%s=%s", "recordOwnerGroupFilter", f.RecordOwnerGroupFilter)) + } + + if f.NameSort != "" { + params = append(params, fmt.Sprintf("%s=%s", "nameSort", f.NameSort)) + } + + if f.StartFrom != "" { + params = append(params, fmt.Sprintf("startFrom=%s", f.StartFrom)) + } + + if f.MaxItems != 0 { + params = append(params, fmt.Sprintf("maxItems=%d", f.MaxItems)) + } + + if len(params) == 0 { + query = "" + } + + return query + strings.Join(params, "&") +} diff --git a/vinyldns/endpoints_test.go b/vinyldns/endpoints_test.go index c30d063..85465c8 100644 --- a/vinyldns/endpoints_test.go +++ b/vinyldns/endpoints_test.go @@ -167,6 +167,54 @@ func TestRecordSetsListEP(t *testing.T) { } } +func TestRecordSetsGlobalListEP(t *testing.T) { + rs := recordSetsGlobalListEP(c, GlobalListFilter{}) + expected := "http://host.com/recordsets" + msg := "recordSetsGlobalListEP should return the right endpoint" + + if rs != expected { + fmt.Printf("Expected: %s", expected) + fmt.Printf("Actual: %s", rs) + t.Error(msg) + } + + rs = recordSetsGlobalListEP(c, GlobalListFilter{ + StartFrom: "nextplease", + }) + expected = "http://host.com/recordsets?startFrom=nextplease" + + if rs != expected { + fmt.Printf("Expected: %s", expected) + fmt.Printf("Actual: %s", rs) + t.Error(msg) + } + + rs = recordSetsGlobalListEP(c, GlobalListFilter{ + StartFrom: "nextplease", + MaxItems: 99, + }) + expected = "http://host.com/recordsets?startFrom=nextplease&maxItems=99" + + if rs != expected { + fmt.Printf("Expected: %s", expected) + fmt.Printf("Actual: %s", rs) + t.Error(msg) + } + + rs = recordSetsGlobalListEP(c, GlobalListFilter{ + RecordNameFilter: "foo", + StartFrom: "nextplease", + MaxItems: 99, + }) + expected = "http://host.com/recordsets?recordNameFilter=foo&startFrom=nextplease&maxItems=99" + + if rs != expected { + fmt.Printf("Expected: %s", expected) + fmt.Printf("Actual: %s", rs) + t.Error(msg) + } +} + func TestRecordSetEP(t *testing.T) { rs := recordSetEP(c, "123", "456") expected := "http://host.com/zones/123/recordsets/456" @@ -354,3 +402,28 @@ func TestBuildQueryWithNoQuery(t *testing.T) { t.Error("buildQuery should return the right string") } } + +func TestBuildGlobalListQuery(t *testing.T) { + query := buildGlobalListQuery(GlobalListFilter{ + MaxItems: 1, + RecordNameFilter: "foo", + }) + expected := "?recordNameFilter=foo&maxItems=1" + + if query != expected { + fmt.Printf("Expected: %s", expected) + fmt.Printf("Actual: %s", query) + t.Error("buildGlobalListQuery should return the right string") + } +} + +func TestBuildGlobalListQueryWithNoQuery(t *testing.T) { + query := buildGlobalListQuery(GlobalListFilter{}) + expected := "" + + if query != expected { + fmt.Printf("Expected: %s", expected) + fmt.Printf("Actual: %s", query) + t.Error("buildGlobalListQuery should return the right string") + } +} diff --git a/vinyldns/integration_test.go b/vinyldns/integration_test.go index f75830a..251839b 100644 --- a/vinyldns/integration_test.go +++ b/vinyldns/integration_test.go @@ -397,6 +397,40 @@ func TestRecordSetsListAllIntegrationFilterForNonexistentName(t *testing.T) { } } +func TestRecordSetsGlobalListAllIntegrationFilterForExistentName(t *testing.T) { + c := client() + rName := "foo" + + records, err := c.RecordSetsGlobalListAll(GlobalListFilter{ + RecordNameFilter: "*" + rName + "*", + }) + if err != nil { + t.Error(err) + } + + if len(records) < 1 { + t.Error(fmt.Sprintf("Expected RecordSetsGlobalListAll for records named '%s' to yield results", rName)) + } + + if records[0].Name != rName { + t.Error(fmt.Sprintf("Expected RecordSetsGlobalListAll for records named '%s' to return the matching record", rName)) + } +} + +func TestRecordSetsGlobalListAllIntegrationFilterForNonexistentName(t *testing.T) { + c := client() + records, err := c.RecordSetsGlobalListAll(GlobalListFilter{ + RecordNameFilter: "thisdoesnotexist", + }) + if err != nil { + t.Error(err) + } + + if len(records) > 0 { + t.Error("Expected RecordSetsListAll for records named 'thisdoesnotexist' to yield no results") + } +} + func TestRecordSetDeleteIntegration(t *testing.T) { c := client() zs, err := c.ZonesListAll(ListFilter{}) diff --git a/vinyldns/recordsets.go b/vinyldns/recordsets.go index e25cb7d..7a419ea 100644 --- a/vinyldns/recordsets.go +++ b/vinyldns/recordsets.go @@ -91,8 +91,9 @@ func (c *Client) RecordSets(id string) ([]RecordSet, error) { return recordSets, nil } -// RecordSetsListAll retrieves the complete list of record sets with the ListFilter criteria passed. -// Handles paging through results on the user's behalf. +// RecordSetsListAll retrieves the complete list of record sets from +// the specified zone with the ListFilter criteria passed. +// It handles paging through results on the user's behalf. func (c *Client) RecordSetsListAll(zoneID string, filter ListFilter) ([]RecordSet, error) { if filter.MaxItems > 100 { return nil, fmt.Errorf("MaxItems must be between 1 and 100") @@ -115,6 +116,31 @@ func (c *Client) RecordSetsListAll(zoneID string, filter ListFilter) ([]RecordSe } } +// RecordSetsGlobalListAll retrieves the complete list of record sets with the +// GlobalListFilter criteria passed, across all zones. +// It handles paging through results on the user's behalf. +func (c *Client) RecordSetsGlobalListAll(filter GlobalListFilter) ([]RecordSet, error) { + if filter.MaxItems > 100 { + return nil, fmt.Errorf("MaxItems must be between 1 and 100") + } + + rss := []RecordSet{} + + for { + resp, err := c.recordSetsGlobalList(filter) + if err != nil { + return nil, err + } + + rss = append(rss, resp.RecordSets...) + filter.StartFrom = resp.NextID + + if len(filter.StartFrom) == 0 { + return rss, nil + } + } +} + // RecordSet retrieves the record matching the Zone ID and RecordSet ID it's passed. func (c *Client) RecordSet(zoneID, recordSetID string) (RecordSet, error) { rs := &RecordSetResponse{} diff --git a/vinyldns/recordsets_helpers.go b/vinyldns/recordsets_helpers.go index 75f4e45..2b12173 100644 --- a/vinyldns/recordsets_helpers.go +++ b/vinyldns/recordsets_helpers.go @@ -12,7 +12,8 @@ limitations under the License. package vinyldns -// recordSetsList retrieves the list of record sets with the List criteria passed. +// recordSetsList retrieves the list of record sets with the List criteria passed, +// for the specified zone. func (c *Client) recordSetsList(zoneID string, filter ListFilter) (*RecordSetsResponse, error) { recordSets := &RecordSetsResponse{} err := resourceRequest(c, recordSetsListEP(c, zoneID, filter), "GET", nil, recordSets) @@ -22,3 +23,15 @@ func (c *Client) recordSetsList(zoneID string, filter ListFilter) (*RecordSetsRe return recordSets, nil } + +// recordSetsGlobalList retrieves the list of record sets with the List criteria passed, +// across all zones. +func (c *Client) recordSetsGlobalList(filter GlobalListFilter) (*RecordSetsResponse, error) { + recordSets := &RecordSetsResponse{} + err := resourceRequest(c, recordSetsGlobalListEP(c, filter), "GET", nil, recordSets) + if err != nil { + return recordSets, err + } + + return recordSets, nil +} diff --git a/vinyldns/recordsets_test.go b/vinyldns/recordsets_test.go index 2a8c979..6571656 100644 --- a/vinyldns/recordsets_test.go +++ b/vinyldns/recordsets_test.go @@ -133,6 +133,90 @@ func TestRecordSetsListAllWhenNoneExist(t *testing.T) { } } +func TestRecordSetsGlobalListAll(t *testing.T) { + recordSetsListJSON1, err := readFile("test-fixtures/recordsets/recordsets-list-json-1.json") + if err != nil { + t.Error(err) + } + recordSetsListJSON2, err := readFile("test-fixtures/recordsets/recordsets-list-json-2.json") + if err != nil { + t.Error(err) + } + server, client := testTools([]testToolsConfig{ + { + endpoint: "http://host.com/recordsets?maxItems=1", + code: 200, + body: recordSetsListJSON1, + }, + { + endpoint: "http://host.com/recordsets?startFrom=2&maxItems=1", + code: 200, + body: recordSetsListJSON2, + }, + }) + + defer server.Close() + + if _, err := client.RecordSetsGlobalListAll(GlobalListFilter{ + MaxItems: 200, + }); err == nil { + t.Error("Expected error -- MaxItems must be between 1 and 100") + } + + records, err := client.RecordSetsGlobalListAll(GlobalListFilter{ + MaxItems: 1, + }) + if err != nil { + t.Error(err) + } + + if len(records) != 2 { + t.Error("Expected 2 records; got ", len(records)) + } + + if records[0].ID != "1" { + t.Error("Expected RecordSet.ID to be 1") + } + + if records[1].ID != "2" { + t.Error("Expected RecordSet.ID to be 2") + } +} + +func TestRecordSetsGlobalListAllWhenNoneExist(t *testing.T) { + recordSetsListNoneJSON, err := readFile("test-fixtures/recordsets/recordsets-list-none.json") + if err != nil { + t.Error(err) + } + server, client := testTools([]testToolsConfig{ + { + endpoint: "http://host.com/recordsets", + code: 200, + body: recordSetsListNoneJSON, + }, + }) + + defer server.Close() + + records, err := client.RecordSetsGlobalListAll(GlobalListFilter{}) + if err != nil { + t.Error(err) + } + + if len(records) != 0 { + t.Error("Expected 0 records; got ", len(records)) + } + + j, err := json.Marshal(records) + if err != nil { + t.Error(err) + } + + if string(j) != "[]" { + t.Error("Expected string-converted marshaled JSON to be '[]'; got ", string(j)) + } +} + func TestRecordSetCollector(t *testing.T) { recordSetsJSON, err := readFile("test-fixtures/recordsets/recordsets.json") if err != nil { diff --git a/vinyldns/resources.go b/vinyldns/resources.go index 9559fc6..a3b6d3b 100644 --- a/vinyldns/resources.go +++ b/vinyldns/resources.go @@ -43,9 +43,32 @@ func (d Error) Error() string { } // ListFilter represents the list query parameters that may be passed to -// VinylDNS API endpoints such as /zones and /recordsets +// VinylDNS API endpoints such as /zones and /zones/${zone_id}/recordsets type ListFilter struct { NameFilter string StartFrom string MaxItems int } + +// NameSort specifies the name sort order for record sets returned by the global list record set response. +// Valid values are ASC (ascending; default) and DESC (descending). +type NameSort string + +const ( + // ASC represents an ascending NameSort + ASC NameSort = "ASC" + + // DESC represents a descending NameSort + DESC NameSort = "DESC" +) + +// GlobalListFilter represents the list query parameters that may be passed to +// VinylDNS API endpoints such as /recordsets +type GlobalListFilter struct { + RecordNameFilter string + RecordTypeFilter string + RecordOwnerGroupFilter string + NameSort NameSort + StartFrom string + MaxItems int +} diff --git a/vinyldns/version.go b/vinyldns/version.go index b7044c4..378a14f 100644 --- a/vinyldns/version.go +++ b/vinyldns/version.go @@ -13,4 +13,4 @@ limitations under the License. package vinyldns // Version stores the go-vinyldns semantic version -var Version = "0.9.12" +var Version = "0.9.13"