providers/redhat/api/client.go (102 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package api import ( "context" "encoding/json" "fmt" "net/http" "net/url" "strconv" "sync" "time" "github.com/facebookincubator/flog" "github.com/facebookincubator/nvdtools/providers/lib/client" "github.com/facebookincubator/nvdtools/providers/lib/runner" "github.com/facebookincubator/nvdtools/providers/redhat/schema" ) const ( perPage = 50 ) // Client struct type Client struct { client.Client baseURL string } // NewClient creates an object which is used to query the RedHat API func NewClient(c client.Client, baseURL string) *Client { return &Client{ Client: c, baseURL: baseURL, } } // FetchAllCVEs will fetch all vulnerabilities func (c *Client) FetchAllCVEs(ctx context.Context, since int64) (<-chan runner.Convertible, error) { output := make(chan runner.Convertible) wg := sync.WaitGroup{} for page := range c.fetchAllPages(ctx, since) { for _, cveItem := range *page { wg.Add(1) go func(cveid string) { defer wg.Done() flog.Infof("\tfetching cve %s", cveid) cve, err := c.FetchCVE(ctx, cveid) if err != nil { flog.Errorf("error while fetching cve %s: %v", cveid, err) return } output <- cve }(cveItem.CVE) } } go func() { wg.Wait() close(output) }() return output, nil } // FetchCVE retrieves a single CVE. func (c *Client) FetchCVE(ctx context.Context, cveid string) (*schema.CVE, error) { resp, err := c.queryPath(ctx, fmt.Sprintf("/cve/%s.json", cveid)) if err != nil { return nil, fmt.Errorf("failed to fetch from feed: %v", err) } defer resp.Body.Close() var cve schema.CVE if err := json.NewDecoder(resp.Body).Decode(&cve); err != nil { return nil, fmt.Errorf("failed to decode cve response into a cve: %v", err) } return &cve, nil } func (c *Client) fetchAllPages(ctx context.Context, since int64) <-chan *schema.CVEList { output := make(chan *schema.CVEList) go func() { defer close(output) for page := 1; ; page++ { flog.Infof("fetching page %d", page) if list, err := c.fetchListPage(ctx, since, page); err == nil { output <- list if len(*list) < perPage { break } } else { flog.Errorf("can't fetch page %d: %v", page, err) break } } }() return output } func (c *Client) fetchListPage(ctx context.Context, since int64, page int) (*schema.CVEList, error) { params := url.Values{} params.Add("per_page", strconv.Itoa(perPage)) params.Add("page", strconv.Itoa(page)) params.Add("after", time.Unix(since, 0).Format("2006-01-02")) // YYYY-MM-DD resp, err := c.queryPath(ctx, "/cve.json?"+params.Encode()) if err != nil { return nil, fmt.Errorf("failed to fetch cve list: %v", err) } defer resp.Body.Close() var cveList schema.CVEList if err := json.NewDecoder(resp.Body).Decode(&cveList); err != nil { return nil, fmt.Errorf("failed to decode response into a list of cves: %v", err) } return &cveList, nil } func (c *Client) queryPath(ctx context.Context, path string) (*http.Response, error) { return client.Get(ctx, c, c.baseURL+path, http.Header{}) }