Skip to content

Commit

Permalink
refactor(errors): add skipped resource reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
aliscott committed Oct 7, 2020
1 parent a1d889f commit 7f5f527
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 27 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/briandowns/spinner v1.11.1
github.com/fatih/color v1.9.0
github.com/google/go-cmp v0.5.2
github.com/google/uuid v1.1.2
github.com/joho/godotenv v1.3.0
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/kelseyhightower/envconfig v1.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
Expand Down
8 changes: 4 additions & 4 deletions pkg/output/output.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import (
)

func skippedResourcesMessage(resources []*schema.Resource, showDetails bool) string {
unsupportedTypeCount, _, unsupportedCount := schema.CountSkippedResources(resources)
if unsupportedCount == 0 {
typeCount, total := schema.CountSkippedResources(resources)
if total == 0 {
return ""
}
message := fmt.Sprintf("%d out of %d resources couldn't be estimated as Infracost doesn't support them yet (https://www.infracost.io/docs/supported_resources)", unsupportedCount, len(resources))
message := fmt.Sprintf("%d out of %d resources couldn't be estimated as Infracost doesn't support them yet (https://www.infracost.io/docs/supported_resources)", total, len(resources))
if showDetails {
message += ".\n"
} else {
message += ", re-run with --show-skipped to see the list.\n"
}
message += "We're continually adding new resources, please email hello@infracost.io if you'd like us to prioritize your list."
if showDetails {
for rType, count := range unsupportedTypeCount {
for rType, count := range typeCount {
message += fmt.Sprintf("\n%d x %s", count, rType)
}
}
Expand Down
12 changes: 7 additions & 5 deletions pkg/prices/prices.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package prices

import (
"fmt"

"github.com/infracost/infracost/pkg/config"
"github.com/infracost/infracost/pkg/schema"

"github.com/shopspring/decimal"
Expand All @@ -12,17 +9,22 @@ import (
)

func PopulatePrices(resources []*schema.Resource) error {
q := NewGraphQLQueryRunner(fmt.Sprintf("%s/graphql", config.Config.PricingAPIEndpoint))
q := NewGraphQLQueryRunner()

hasSkipped := false
for _, r := range resources {
if r.IsSkipped {
continue
hasSkipped = true
}
if err := GetPrices(r, q); err != nil {
return err
}
}

if hasSkipped {
q.ReportMissingPrices(resources)
}

return nil
}

Expand Down
71 changes: 63 additions & 8 deletions pkg/prices/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io/ioutil"
"net/http"

"github.com/google/uuid"
"github.com/infracost/infracost/pkg/config"
"github.com/infracost/infracost/pkg/schema"
"github.com/pkg/errors"
Expand Down Expand Up @@ -40,6 +41,12 @@ type queryResult struct {
Result gjson.Result
}

type skippedResourcesJSON struct {
SkippedTypeCounts map[string]int `json:"skippedTypeCounts"`
SkippedTotal int `json:"skippedTotal"`
Total int `json:"total"`
}

type GraphQLQuery struct {
Query string `json:"query"`
Variables map[string]interface{} `json:"variables"`
Expand All @@ -50,16 +57,22 @@ type QueryRunner interface {
}

type GraphQLQueryRunner struct {
endpoint string
baseURL string
graphQLEndpoint string
traceId string
}

func NewGraphQLQueryRunner(endpoint string) *GraphQLQueryRunner {
func NewGraphQLQueryRunner() *GraphQLQueryRunner {
baseURL := config.Config.PricingAPIEndpoint
return &GraphQLQueryRunner{
endpoint: endpoint,
baseURL: baseURL,
graphQLEndpoint: fmt.Sprintf("%s/graphql", baseURL),
}
}

func (q *GraphQLQueryRunner) RunQueries(r *schema.Resource) ([]queryResult, error) {
q.traceId = uuid.New().String()

keys, queries := q.batchQueries(r)

if len(queries) == 0 {
Expand Down Expand Up @@ -109,14 +122,12 @@ func (q *GraphQLQueryRunner) getQueryResults(queries []GraphQLQuery) ([]gjson.Re
return results, errors.Wrap(err, "Error generating request for pricing API")
}

req, err := http.NewRequest("POST", q.endpoint, bytes.NewBuffer([]byte(queriesBody)))
req, err := http.NewRequest("POST", q.graphQLEndpoint, bytes.NewBuffer([]byte(queriesBody)))
if err != nil {
return results, errors.Wrap(err, "Error generating request for pricing API")
}

req.Header.Set("content-type", "application/json")
req.Header.Set("User-Agent", config.GetUserAgent())
req.Header.Set("X-Api-Key", config.Config.ApiKey)
q.addHeaders(req)

client := http.Client{}
resp, err := client.Do(req)
Expand All @@ -138,14 +149,58 @@ func (q *GraphQLQueryRunner) getQueryResults(queries []GraphQLQuery) ([]gjson.Re
if r.Error == "Invalid API key" {
return results, InvalidAPIKeyError
}
return results, &PricingAPIError{err, "Received error from pricing API"}
return results, &PricingAPIError{errors.New(r.Error), "Received error from pricing API"}
}

results = append(results, gjson.ParseBytes(body).Array()...)

return results, nil
}

func (q *GraphQLQueryRunner) ReportMissingPrices(resources []*schema.Resource) {
if q.baseURL != config.Config.DefaultPricingAPIEndpoint {
// skip for non-default pricing API endpoints
return
}

url := fmt.Sprintf("%s/report", q.baseURL)

skippedTypeCounts, skippedTotal := schema.CountSkippedResources(resources)
j := skippedResourcesJSON{skippedTypeCounts, skippedTotal, len(resources)}
body, err := json.Marshal(j)
if err != nil {
log.Debugf("Unable to generate missing prices request: %v", err)
return
}

req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(body)))
if err != nil {
log.Debugf("Unable to generate missing prices request: %v", err)
return
}

q.addHeaders(req)

client := http.Client{}
resp, err := client.Do(req)
if err != nil {
log.Debugf("Unable to send missing prices request: %v", err)
return
}
defer resp.Body.Close()

if resp.StatusCode != 200 {
log.Debugf("Unexpected response sending missing prices request: %d", resp.StatusCode)
}
}

func (q *GraphQLQueryRunner) addHeaders(req *http.Request) {
req.Header.Set("content-type", "application/json")
req.Header.Set("User-Agent", config.GetUserAgent())
req.Header.Set("X-Api-Key", config.Config.ApiKey)
req.Header.Set("X-Trace-Id", q.traceId)
}

// Batch all the queries for this resource so we can use one GraphQL call
// Use queryKeys to keep track of which query maps to which sub-resource and price component
func (q *GraphQLQueryRunner) batchQueries(r *schema.Resource) ([]queryKey, []GraphQLQuery) {
Expand Down
18 changes: 8 additions & 10 deletions pkg/schema/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,17 @@ func SortResources(resources []*Resource) {
}
}

func CountSkippedResources(resources []*Resource) (map[string]int, int, int) {
skippedCount := 0
unsupportedCount := 0
unsupportedTypeCount := make(map[string]int)
func CountSkippedResources(resources []*Resource) (map[string]int, int) {
total := 0
typeCounts := make(map[string]int)
for _, r := range resources {
if r.IsSkipped {
skippedCount++
unsupportedCount++
if _, ok := unsupportedTypeCount[r.ResourceType]; !ok {
unsupportedTypeCount[r.ResourceType] = 0
total++
if _, ok := typeCounts[r.ResourceType]; !ok {
typeCounts[r.ResourceType] = 0
}
unsupportedTypeCount[r.ResourceType]++
typeCounts[r.ResourceType]++
}
}
return unsupportedTypeCount, skippedCount, unsupportedCount
return typeCounts, total
}

0 comments on commit 7f5f527

Please sign in to comment.