diff --git a/docs/data-sources/waf_rules.md b/docs/data-sources/waf_rules.md index 44f5270d8..33f87fe35 100644 --- a/docs/data-sources/waf_rules.md +++ b/docs/data-sources/waf_rules.md @@ -156,6 +156,7 @@ The `rules` block supports: - **exclude_modsec_rule_ids** (List of Number) A list of modsecurity rules IDs to be excluded from the data set. - **id** (String) The ID of this resource. +- **modsec_rule_ids** (List of Number) A list of modsecurity rules IDs to be used as filters for the data set. - **publishers** (List of String) A list of publishers to be used as filters for the data set. - **tags** (List of String) A list of tags to be used as filters for the data set. diff --git a/fastly/data_source_waf_rules.go b/fastly/data_source_waf_rules.go index 2b3dd7dae..9a20c616c 100644 --- a/fastly/data_source_waf_rules.go +++ b/fastly/data_source_waf_rules.go @@ -30,6 +30,12 @@ func dataSourceFastlyWAFRules() *schema.Resource { Description: "A list of tags to be used as filters for the data set.", Elem: &schema.Schema{Type: schema.TypeString}, }, + "modsec_rule_ids": { + Type: schema.TypeList, + Optional: true, + Description: "A list of modsecurity rules IDs to be used as filters for the data set.", + Elem: &schema.Schema{Type: schema.TypeInt}, + }, "exclude_modsec_rule_ids": { Type: schema.TypeList, Optional: true, @@ -85,6 +91,13 @@ func dataSourceFastlyWAFRulesRead(_ context.Context, d *schema.ResourceData, met } } + if v, ok := d.GetOk("modsec_rule_ids"); ok { + l := v.([]interface{}) + for i := range l { + input.FilterModSecIDs = append(input.FilterModSecIDs, l[i].(int)) + } + } + if v, ok := d.GetOk("exclude_modsec_rule_ids"); ok { l := v.([]interface{}) for i := range l { diff --git a/fastly/data_source_waf_rules_test.go b/fastly/data_source_waf_rules_test.go index 16cdb7835..f6621df83 100644 --- a/fastly/data_source_waf_rules_test.go +++ b/fastly/data_source_waf_rules_test.go @@ -124,6 +124,25 @@ func TestAccFastlyWAFRulesPublisherFilter(t *testing.T) { }) } +func TestAccFastlyWAFRulesModSecIDsFilter(t *testing.T) { + + wafrulesHCL := ` + modsec_rule_ids = [1010060, 1010070] + ` + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: testAccProviders, + CheckDestroy: testAccCheckServiceV1Destroy, + Steps: []resource.TestStep{ + { + Config: testAccFastlyWAFRules(wafrulesHCL), + Check: resource.TestCheckResourceAttr("data.fastly_waf_rules.r1", "rules.#", "2"), + }, + }, + }) +} + func TestAccFastlyWAFRulesExcludeFilter(t *testing.T) { wafrulesHCL := ` diff --git a/go.mod b/go.mod index 36d4705da..caf71e353 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.16 require ( github.com/bflad/tfproviderlint v0.27.1 - github.com/fastly/go-fastly/v5 v5.1.3 + github.com/fastly/go-fastly/v5 v5.2.0 github.com/google/go-cmp v0.5.6 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/terraform-plugin-docs v0.5.0 diff --git a/go.sum b/go.sum index 8aedaa808..ad060da6f 100644 --- a/go.sum +++ b/go.sum @@ -103,8 +103,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fastly/go-fastly/v5 v5.1.3 h1:idgaGxLqPn9m+pZu7Fo8uNB7UJXqwtEoKHmhr3VtqlI= -github.com/fastly/go-fastly/v5 v5.1.3/go.mod h1:4WKmXkCyvjq6+XlE9zrCBsFyoQngQGQjVkpr0LjAXoY= +github.com/fastly/go-fastly/v5 v5.2.0 h1:jWULYVCcER3x4a2gX4KTyg/+ejBlBcmVOXg4mTyn/Y4= +github.com/fastly/go-fastly/v5 v5.2.0/go.mod h1:zlDZcVcOigWpNixpCvV0LdatzXtgpg5ISOETF5CoKWo= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= @@ -341,6 +341,8 @@ github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce h1:RPclfga2SEJmgMmz2k github.com/nsf/jsondiff v0.0.0-20200515183724-f29ed568f4ce/go.mod h1:uFMI8w+ref4v2r9jz+c9i1IfIttS/OkmLfrk1jne5hs= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/peterhellberg/link v1.1.0 h1:s2+RH8EGuI/mI4QwrWGSYQCRz7uNgip9BaM04HKu5kc= +github.com/peterhellberg/link v1.1.0/go.mod h1:gtSlOT4jmkY8P47hbTc8PTgiDDWpdPbFYl75keYyBB8= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -694,7 +696,6 @@ gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qS gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/fastly/go-fastly/v5/fastly/acl_entry.go b/vendor/github.com/fastly/go-fastly/v5/fastly/acl_entry.go index 43c540a38..7ddbd9661 100644 --- a/vendor/github.com/fastly/go-fastly/v5/fastly/acl_entry.go +++ b/vendor/github.com/fastly/go-fastly/v5/fastly/acl_entry.go @@ -2,8 +2,12 @@ package fastly import ( "fmt" + "net/url" "sort" + "strconv" "time" + + "github.com/peterhellberg/link" ) type ACLEntry struct { @@ -34,6 +38,10 @@ func (s entriesById) Less(i, j int) bool { type ListACLEntriesInput struct { ServiceID string ACLID string + Direction string + PerPage int + Page int + Sort string } // ListACLEntries return a list of entries for an ACL @@ -63,6 +71,113 @@ func (c *Client) ListACLEntries(i *ListACLEntriesInput) ([]*ACLEntry, error) { return es, nil } +type ListAclEntriesPaginator struct { + consumed bool + CurrentPage int + NextPage int + LastPage int + client *Client + options *ListACLEntriesInput +} + +// HasNext returns a boolean indicating whether more pages are available +func (p *ListAclEntriesPaginator) HasNext() bool { + return !p.consumed || p.Remaining() != 0 +} + +// Remaining returns the remaining page count +func (p *ListAclEntriesPaginator) Remaining() int { + if p.LastPage == 0 { + return 0 + } + return p.LastPage - p.CurrentPage +} + +// GetNext retrieves data in the next page +func (p *ListAclEntriesPaginator) GetNext() ([]*ACLEntry, error) { + return p.client.listACLEntriesWithPage(p.options, p) +} + +// NewListACLEntriesPaginator returns a new ListAclEntriesPaginator +func (c *Client) NewListACLEntriesPaginator(i *ListACLEntriesInput) *ListAclEntriesPaginator { + return &ListAclEntriesPaginator{ + client: c, + options: i, + } +} + +// listACLEntriesWithPage return a list of entries for an ACL of a given page +func (c *Client) listACLEntriesWithPage(i *ListACLEntriesInput, p *ListAclEntriesPaginator) ([]*ACLEntry, error) { + + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + + if i.ACLID == "" { + return nil, ErrMissingACLID + } + + var perPage int + const maxPerPage = 100 + if i.PerPage <= 0 { + perPage = maxPerPage + } else { + perPage = i.PerPage + } + + if i.Page <= 0 && p.CurrentPage == 0 { + p.CurrentPage = 1 + } else { + p.CurrentPage = p.CurrentPage + 1 + } + + path := fmt.Sprintf("/service/%s/acl/%s/entries", i.ServiceID, i.ACLID) + requestOptions := &RequestOptions{ + Params: map[string]string{ + "per_page": strconv.Itoa(perPage), + "page": strconv.Itoa(p.CurrentPage), + }, + } + + if i.Direction != "" { + requestOptions.Params["direction"] = i.Direction + } + if i.Sort != "" { + requestOptions.Params["sort"] = i.Sort + } + + resp, err := c.Get(path, requestOptions) + if err != nil { + return nil, err + } + + for _, l := range link.ParseResponse(resp) { + // indicates the Link response header contained the next page instruction + if l.Rel == "next" { + u, _ := url.Parse(l.URI) + query := u.Query() + p.NextPage, _ = strconv.Atoi(query["page"][0]) + } + // indicates the Link response header contained the last page instruction + if l.Rel == "last" { + u, _ := url.Parse(l.URI) + query := u.Query() + p.LastPage, _ = strconv.Atoi(query["page"][0]) + } + } + + p.consumed = true + + var es []*ACLEntry + if err := decodeBodyMap(resp.Body, &es); err != nil { + return nil, err + } + + sort.Stable(entriesById(es)) + + return es, nil +} + // GetACLEntryInput is the input parameter to GetACLEntry function. type GetACLEntryInput struct { ServiceID string diff --git a/vendor/github.com/fastly/go-fastly/v5/fastly/client.go b/vendor/github.com/fastly/go-fastly/v5/fastly/client.go index a00d8c8df..a3830b92e 100644 --- a/vendor/github.com/fastly/go-fastly/v5/fastly/client.go +++ b/vendor/github.com/fastly/go-fastly/v5/fastly/client.go @@ -46,7 +46,7 @@ const DefaultRealtimeStatsEndpoint = "https://rt.fastly.com" var ProjectURL = "github.com/fastly/go-fastly" // ProjectVersion is the version of this library. -var ProjectVersion = "5.1.2" +var ProjectVersion = "5.2.0" // UserAgent is the user agent for this particular client. var UserAgent = fmt.Sprintf("FastlyGo/%s (+%s; %s)", diff --git a/vendor/github.com/fastly/go-fastly/v5/fastly/dictionary_item.go b/vendor/github.com/fastly/go-fastly/v5/fastly/dictionary_item.go index c520c9e88..f8a3e5cf7 100644 --- a/vendor/github.com/fastly/go-fastly/v5/fastly/dictionary_item.go +++ b/vendor/github.com/fastly/go-fastly/v5/fastly/dictionary_item.go @@ -4,7 +4,10 @@ import ( "fmt" "net/url" "sort" + "strconv" "time" + + "github.com/peterhellberg/link" ) // DictionaryItem represents a dictionary item response from the Fastly API. @@ -36,10 +39,13 @@ type ListDictionaryItemsInput struct { // DictionaryID is the ID of the dictionary to retrieve items for (required). DictionaryID string + Direction string + PerPage int + Page int + Sort string } -// ListDictionaryItems returns the list of dictionary items for the -// configuration version. +// ListDictionaryItems returns a list of items for a dictionary func (c *Client) ListDictionaryItems(i *ListDictionaryItemsInput) ([]*DictionaryItem, error) { if i.ServiceID == "" { return nil, ErrMissingServiceID @@ -63,6 +69,111 @@ func (c *Client) ListDictionaryItems(i *ListDictionaryItemsInput) ([]*Dictionary return bs, nil } +type ListDictionaryItemsPaginator struct { + consumed bool + CurrentPage int + NextPage int + LastPage int + client *Client + options *ListDictionaryItemsInput +} + +// HasNext returns a boolean indicating whether more pages are available +func (p *ListDictionaryItemsPaginator) HasNext() bool { + return !p.consumed || p.Remaining() != 0 +} + +// Remaining returns the remaining page count +func (p *ListDictionaryItemsPaginator) Remaining() int { + if p.LastPage == 0 { + return 0 + } + return p.LastPage - p.CurrentPage +} + +// GetNext retrieves data in the next page +func (p *ListDictionaryItemsPaginator) GetNext() ([]*DictionaryItem, error) { + return p.client.listDictionaryItemsWithPage(p.options, p) +} + +// NewListDictionaryItemsPaginator returns a new ListDictionaryItemsPaginator +func (c *Client) NewListDictionaryItemsPaginator(i *ListDictionaryItemsInput) *ListDictionaryItemsPaginator { + return &ListDictionaryItemsPaginator{ + client: c, + options: i, + } +} + +// listDictionaryItemsWithPage returns a list of items for a dictionary of a given page +func (c *Client) listDictionaryItemsWithPage(i *ListDictionaryItemsInput, p *ListDictionaryItemsPaginator) ([]*DictionaryItem, error) { + if i.ServiceID == "" { + return nil, ErrMissingServiceID + } + + if i.DictionaryID == "" { + return nil, ErrMissingDictionaryID + } + + var perPage int + const maxPerPage = 100 + if i.PerPage <= 0 { + perPage = maxPerPage + } else { + perPage = i.PerPage + } + + if i.Page <= 0 && p.CurrentPage == 0 { + p.CurrentPage = 1 + } else { + p.CurrentPage = p.CurrentPage + 1 + } + + path := fmt.Sprintf("/service/%s/dictionary/%s/items", i.ServiceID, i.DictionaryID) + requestOptions := &RequestOptions{ + Params: map[string]string{ + "per_page": strconv.Itoa(perPage), + "page": strconv.Itoa(p.CurrentPage), + }, + } + + if i.Direction != "" { + requestOptions.Params["direction"] = i.Direction + } + if i.Sort != "" { + requestOptions.Params["sort"] = i.Sort + } + + resp, err := c.Get(path, requestOptions) + if err != nil { + return nil, err + } + + for _, l := range link.ParseResponse(resp) { + // indicates the Link response header contained the next page instruction + if l.Rel == "next" { + u, _ := url.Parse(l.URI) + query := u.Query() + p.NextPage, _ = strconv.Atoi(query["page"][0]) + } + // indicates the Link response header contained the last page instruction + if l.Rel == "last" { + u, _ := url.Parse(l.URI) + query := u.Query() + p.LastPage, _ = strconv.Atoi(query["page"][0]) + } + } + + p.consumed = true + + var bs []*DictionaryItem + if err := decodeBodyMap(resp.Body, &bs); err != nil { + return nil, err + } + sort.Stable(dictionaryItemsByKey(bs)) + + return bs, nil +} + // CreateDictionaryItemInput is used as input to the CreateDictionaryItem function. type CreateDictionaryItemInput struct { // ServiceID is the ID of the service (required). diff --git a/vendor/github.com/fastly/go-fastly/v5/fastly/waf_rules.go b/vendor/github.com/fastly/go-fastly/v5/fastly/waf_rules.go index ca475b16d..f3f46db54 100644 --- a/vendor/github.com/fastly/go-fastly/v5/fastly/waf_rules.go +++ b/vendor/github.com/fastly/go-fastly/v5/fastly/waf_rules.go @@ -49,7 +49,10 @@ type ListWAFRulesInput struct { FilterTagNames []string // Limit the returned rules to a set by publishers. FilterPublishers []string + // Limit the returned rules to a set by modsecurity rule IDs. + FilterModSecIDs []int // Excludes individual rules by modsecurity rule IDs. + // TODO: fix typo ExcludeMocSecIDs -> ExcludeModSecIDs ExcludeMocSecIDs []int // Limit the number of returned rules. PageSize int @@ -65,6 +68,7 @@ func (i *ListWAFRulesInput) formatFilters() map[string]string { pairings := map[string]interface{}{ "filter[waf_tags][name][in]": i.FilterTagNames, "filter[publisher][in]": i.FilterPublishers, + "filter[modsec_rule_id][in]": i.FilterModSecIDs, "filter[modsec_rule_id][not]": i.ExcludeMocSecIDs, "page[size]": i.PageSize, "page[number]": i.PageNumber, @@ -142,6 +146,8 @@ type ListAllWAFRulesInput struct { FilterTagNames []string // Limit the returned rules to a set by publishers. FilterPublishers []string + // Limit the returned rules to a set by modsecurity rule IDs. + FilterModSecIDs []int // Excludes individual rules by modsecurity rule IDs. ExcludeMocSecIDs []int // Include relationships. Optional, comma-separated values. Permitted values: waf_tags and waf_rule_revisions. @@ -158,6 +164,7 @@ func (c *Client) ListAllWAFRules(i *ListAllWAFRulesInput) (*WAFRuleResponse, err r, err := c.ListWAFRules(&ListWAFRulesInput{ FilterTagNames: i.FilterTagNames, FilterPublishers: i.FilterPublishers, + FilterModSecIDs: i.FilterModSecIDs, ExcludeMocSecIDs: i.ExcludeMocSecIDs, Include: i.Include, PageNumber: currentPage, diff --git a/vendor/github.com/peterhellberg/link/.travis.yml b/vendor/github.com/peterhellberg/link/.travis.yml new file mode 100644 index 000000000..350f9d423 --- /dev/null +++ b/vendor/github.com/peterhellberg/link/.travis.yml @@ -0,0 +1,7 @@ +language: go + +dist: bionic + +go: + - "1.13.4" + - "1.12.13" diff --git a/vendor/github.com/peterhellberg/link/LICENSE b/vendor/github.com/peterhellberg/link/LICENSE new file mode 100644 index 000000000..7975bec62 --- /dev/null +++ b/vendor/github.com/peterhellberg/link/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2015-2019 Peter Hellberg https://c7.se + +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. + diff --git a/vendor/github.com/peterhellberg/link/README.md b/vendor/github.com/peterhellberg/link/README.md new file mode 100644 index 000000000..c2972328f --- /dev/null +++ b/vendor/github.com/peterhellberg/link/README.md @@ -0,0 +1,82 @@ +# link + +[![Build Status](https://travis-ci.org/peterhellberg/link.svg?branch=master)](https://travis-ci.org/peterhellberg/link) +[![Go Report Card](https://goreportcard.com/badge/github.com/peterhellberg/link)](https://goreportcard.com/report/github.com/peterhellberg/link) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/peterhellberg/link) +[![License MIT](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/peterhellberg/link#license-mit) + +Parses **Link** headers used for pagination, as defined in [RFC 5988](https://tools.ietf.org/html/rfc5988). + +This package was originally based on , but **Parse** takes a `string` instead of `*http.Request` in this version. +It also has the convenience functions **ParseHeader**, **ParseRequest** and **ParseResponse**. + +## Installation + + go get -u github.com/peterhellberg/link + +## Exported functions + + - [Parse(s string) Group](https://godoc.org/github.com/peterhellberg/link#Parse) + - [ParseHeader(h http.Header) Group](https://godoc.org/github.com/peterhellberg/link#ParseHeader) + - [ParseRequest(req \*http.Request) Group](https://godoc.org/github.com/peterhellberg/link#ParseRequest) + - [ParseResponse(resp \*http.Response) Group](https://godoc.org/github.com/peterhellberg/link#ParseResponse) + +## Usage + +```go +package main + +import ( + "fmt" + "net/http" + + "github.com/peterhellberg/link" +) + +func main() { + for _, l := range link.Parse(`; rel="next"; foo="bar"`) { + fmt.Printf("URI: %q, Rel: %q, Extra: %+v\n", l.URI, l.Rel, l.Extra) + // URI: "https://example.com/?page=2", Rel: "next", Extra: map[foo:bar] + } + + if resp, err := http.Get("https://api.github.com/search/code?q=Println+user:golang"); err == nil { + for _, l := range link.ParseResponse(resp) { + fmt.Printf("URI: %q, Rel: %q, Extra: %+v\n", l.URI, l.Rel, l.Extra) + // URI: "https://api.github.com/search/code?q=Println+user%3Agolang&page=2", Rel: "next", Extra: map[] + // URI: "https://api.github.com/search/code?q=Println+user%3Agolang&page=34", Rel: "last", Extra: map[] + } + } +} +``` + +## Not supported + + - Extended notation ([RFC 5987](https://tools.ietf.org/html/rfc5987)) + +## Alternatives to this package + + - [github.com/tent/http-link-go](https://github.com/tent/http-link-go) + - [github.com/swhite24/link](https://github.com/swhite24/link) + +## License (MIT) + +Copyright (c) 2015-2019 [Peter Hellberg](https://c7.se) + +> 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. diff --git a/vendor/github.com/peterhellberg/link/doc.go b/vendor/github.com/peterhellberg/link/doc.go new file mode 100644 index 000000000..f3a5f172e --- /dev/null +++ b/vendor/github.com/peterhellberg/link/doc.go @@ -0,0 +1,40 @@ +/* + +Package link parses Link headers used for pagination, as defined in RFC 5988 + +Installation + +Just go get the package: + + go get -u github.com/peterhellberg/link + +Usage + +A small usage example + + package main + + import ( + "fmt" + "net/http" + + "github.com/peterhellberg/link" + ) + + func main() { + for _, l := range link.Parse(`; rel="next"; foo="bar"`) { + fmt.Printf("URI: %q, Rel: %q, Extra: %+v\n", l.URI, l.Rel, l.Extra) + // URI: "https://example.com/?page=2", Rel: "next", Extra: map[foo:bar] + } + + if resp, err := http.Get("https://api.github.com/search/code?q=Println+user:golang"); err == nil { + for _, l := range link.ParseResponse(resp) { + fmt.Printf("URI: %q, Rel: %q, Extra: %+v\n", l.URI, l.Rel, l.Extra) + // URI: "https://api.github.com/search/code?q=Println+user%3Agolang&page=2", Rel: "next", Extra: map[] + // URI: "https://api.github.com/search/code?q=Println+user%3Agolang&page=34", Rel: "last", Extra: map[] + } + } + } + +*/ +package link diff --git a/vendor/github.com/peterhellberg/link/link.go b/vendor/github.com/peterhellberg/link/link.go new file mode 100644 index 000000000..8c2f02525 --- /dev/null +++ b/vendor/github.com/peterhellberg/link/link.go @@ -0,0 +1,111 @@ +package link + +import ( + "net/http" + "regexp" + "strings" +) + +var ( + commaRegexp = regexp.MustCompile(`,\s{0,}`) + valueCommaRegexp = regexp.MustCompile(`([^"]),`) + equalRegexp = regexp.MustCompile(` *= *`) + keyRegexp = regexp.MustCompile(`[a-z*]+`) + linkRegexp = regexp.MustCompile(`\A<(.+)>;(.+)\z`) + semiRegexp = regexp.MustCompile(`; +`) + valRegexp = regexp.MustCompile(`"+([^"]+)"+`) +) + +// Group returned by Parse, contains multiple links indexed by "rel" +type Group map[string]*Link + +// Link contains a Link item with URI, Rel, and other non-URI components in Extra. +type Link struct { + URI string + Rel string + Extra map[string]string +} + +// String returns the URI +func (l *Link) String() string { + return l.URI +} + +// ParseRequest parses the provided *http.Request into a Group +func ParseRequest(req *http.Request) Group { + if req == nil { + return nil + } + + return ParseHeader(req.Header) +} + +// ParseResponse parses the provided *http.Response into a Group +func ParseResponse(resp *http.Response) Group { + if resp == nil { + return nil + } + + return ParseHeader(resp.Header) +} + +// ParseHeader retrieves the Link header from the provided http.Header and parses it into a Group +func ParseHeader(h http.Header) Group { + if headers, found := h["Link"]; found { + return Parse(strings.Join(headers, ", ")) + } + + return nil +} + +// Parse parses the provided string into a Group +func Parse(s string) Group { + if s == "" { + return nil + } + + s = valueCommaRegexp.ReplaceAllString(s, "$1") + + group := Group{} + + for _, l := range commaRegexp.Split(s, -1) { + linkMatches := linkRegexp.FindAllStringSubmatch(l, -1) + + if len(linkMatches) == 0 { + return nil + } + + pieces := linkMatches[0] + + link := &Link{URI: pieces[1], Extra: map[string]string{}} + + for _, extra := range semiRegexp.Split(pieces[2], -1) { + vals := equalRegexp.Split(extra, -1) + + key := keyRegexp.FindString(vals[0]) + val := valRegexp.FindStringSubmatch(vals[1])[1] + + if key == "rel" { + vals := strings.Split(val, " ") + rels := []string{vals[0]} + + if len(vals) > 1 { + for _, v := range vals[1:] { + if !strings.HasPrefix(v, "http") { + rels = append(rels, v) + } + } + } + + rel := strings.Join(rels, " ") + + link.Rel = rel + group[rel] = link + } else { + link.Extra[key] = val + } + } + } + + return group +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 96968382a..11aeed6bd 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -234,7 +234,7 @@ github.com/bgentry/go-netrc/netrc github.com/bgentry/speakeasy # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew -# github.com/fastly/go-fastly/v5 v5.1.3 +# github.com/fastly/go-fastly/v5 v5.2.0 ## explicit github.com/fastly/go-fastly/v5/fastly # github.com/fatih/color v1.7.0 @@ -387,6 +387,8 @@ github.com/mitchellh/mapstructure github.com/mitchellh/reflectwalk # github.com/oklog/run v1.0.0 github.com/oklog/run +# github.com/peterhellberg/link v1.1.0 +github.com/peterhellberg/link # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib # github.com/posener/complete v1.2.1