diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index b0f899d..ff4a07f 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -54,33 +54,3 @@ func (c paginatedResponseClient) Do(req *http.Request) (*http.Response, error) { }).ServeHTTP(rec, req) return rec.Result(), nil } - -// -// -// - -type sequentialResponseClient struct { - responses []string -} - -func newSequentialResponseClient(responses ...string) *sequentialResponseClient { - return &sequentialResponseClient{ - responses: responses, - } -} - -func (c *sequentialResponseClient) Do(req *http.Request) (*http.Response, error) { - var response string - if len(c.responses) <= 1 { - response = c.responses[0] - } else { - response, c.responses = c.responses[0], c.responses[1:] - } - - rec := httptest.NewRecorder() - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, response) - }).ServeHTTP(rec, req) - return rec.Result(), nil -} diff --git a/pkg/api/product_cache.go b/pkg/api/product_cache.go index 73ef221..0229513 100644 --- a/pkg/api/product_cache.go +++ b/pkg/api/product_cache.go @@ -27,12 +27,14 @@ const ( // Products is the slice of available products supported by real-time stats. var Products = []string{ProductDefault, ProductOriginInspector, ProductDomainInspector} -// Product models the response from the Fastly Product Entitlement API. -type Product struct { - HasAccess bool `json:"has_access"` - Product struct { - Name string `json:"id"` - } `json:"product"` +type entitlementsResponse struct { + Customers []struct { + Contracts []struct { + Items []struct { + ProductID *string `json:"product_id,omitempty"` + } `json:"items,omitempty"` + } `json:"contracts"` + } `json:"customers"` } // ProductCache fetches product information from the Fastly Product Entitlement API @@ -59,41 +61,52 @@ func NewProductCache(client HTTPClient, token string, logger log.Logger) *Produc // Refresh requests data from the Fastly API and stores data in the cache. func (p *ProductCache) Refresh(ctx context.Context) error { - for _, product := range Products { - if product == ProductDefault { - continue - } - uri := fmt.Sprintf("https://api.fastly.com/entitled-products/%s", product) - req, err := http.NewRequestWithContext(ctx, "GET", uri, nil) - if err != nil { - return fmt.Errorf("error constructing API product request: %w", err) - } + req, err := http.NewRequestWithContext(ctx, "GET", "https://api.fastly.com/entitlements", nil) + if err != nil { + return fmt.Errorf("error constructing API product request: %w", err) + } - req.Header.Set("Fastly-Key", p.token) - req.Header.Set("Accept", "application/json") - resp, err := p.client.Do(req) - if err != nil { - return fmt.Errorf("error executing API product request: %w", err) - } - defer resp.Body.Close() + req.Header.Set("Fastly-Key", p.token) + req.Header.Set("Accept", "application/json") + resp, err := p.client.Do(req) + if err != nil { + return fmt.Errorf("error executing API product request: %w", err) + } + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return NewError(resp) - } + if resp.StatusCode != http.StatusOK { + return NewError(resp) + } - var response Product + var response entitlementsResponse - if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { - return fmt.Errorf("error decoding API product response: %w", err) - } + if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { + return fmt.Errorf("error decoding API product response: %w", err) + } + + activeProducts := make(map[string]interface{}) - level.Debug(p.logger).Log("product", response.Product.Name, "hasAccess", response.HasAccess) + for _, customer := range response.Customers { + for _, contract := range customer.Contracts { + for _, item := range contract.Items { + if item.ProductID == nil { + continue + } + activeProducts[*item.ProductID] = true + } + } + } + for _, product := range Products { + if product == ProductDefault { + continue + } + _, hasAccess := activeProducts[product] + level.Debug(p.logger).Log("product", product, "hasAccess", hasAccess) p.mtx.Lock() - p.products[response.Product.Name] = response.HasAccess + p.products[product] = hasAccess p.mtx.Unlock() - } return nil diff --git a/pkg/api/product_cache_test.go b/pkg/api/product_cache_test.go index 352ae2c..63cd8f3 100644 --- a/pkg/api/product_cache_test.go +++ b/pkg/api/product_cache_test.go @@ -21,12 +21,18 @@ func TestProductCache(t *testing.T) { }{ { name: "success", - client: newSequentialResponseClient(productsResponseOne, productsResponseTwo), + client: fixedResponseClient{response: productsResponse, code: http.StatusOK}, wantErr: nil, wantProds: map[string]bool{ "origin_inspector": true, }, }, + { + name: "noCustomerSuccess", + client: fixedResponseClient{response: noCustomerResponse, code: http.StatusOK}, + wantErr: nil, + wantProds: map[string]bool{}, + }, { name: "error", client: fixedResponseClient{code: http.StatusUnauthorized}, @@ -55,34 +61,37 @@ func TestProductCache(t *testing.T) { } } -const productsResponseOne = ` +// only products that the customer is entitled to are returned from the /entitlements endpoint +const productsResponse = ` { - "product": { - "id": "origin_inspector", - "object": "product" - }, - "has_access": true, - "access_level": "Origin_Inspector", - "has_permission_to_enable": false, - "has_permission_to_disable": true, - "_links": { - "self": "" - } + "customers": [ + { + "contracts": [ + { + "items": [ + { + "product_id": "origin_inspector" + }, + { + "other_key": "other" + } + ] + }, + { + "other_key_2": [ + ] + } + ] + }, + { + "other key": {} + } + ] } ` -const productsResponseTwo = ` +const noCustomerResponse = ` { - "product": { - "id": "domain_inspector", - "object": "product" - }, - "has_access": false, - "access_level": "Domain_Inspector", - "has_permission_to_enable": false, - "has_permission_to_disable": true, - "_links": { - "self": "" - } + "metadata": {} } `