From 32755f36653aebf46da5ef51fe4320667986db3a Mon Sep 17 00:00:00 2001 From: Matt Bolt Date: Mon, 22 Jan 2024 11:46:52 -0500 Subject: [PATCH 1/3] Add parsing for CloudCostProperty to opencost-core Signed-off-by: Matt Bolt --- core/pkg/opencost/cloudcostprops.go | 61 ++++++++++++++++++++++++++++ pkg/cloudcost/queryservice_helper.go | 32 ++++----------- 2 files changed, 68 insertions(+), 25 deletions(-) diff --git a/core/pkg/opencost/cloudcostprops.go b/core/pkg/opencost/cloudcostprops.go index 09a95f1e5..4cef693b7 100644 --- a/core/pkg/opencost/cloudcostprops.go +++ b/core/pkg/opencost/cloudcostprops.go @@ -1,11 +1,29 @@ package opencost import ( + "fmt" "strings" "github.com/opencost/opencost/core/pkg/log" + "github.com/opencost/opencost/core/pkg/util/promutil" ) +type CloudCostProperty string + +// IsLabel returns true if the allocation property has a label prefix +func (apt *CloudCostProperty) IsLabel() bool { + return strings.HasPrefix(string(*apt), "label:") +} + +// GetLabel returns the label string associated with the label property if it exists. +// Otherwise, empty string is returned. +func (apt *CloudCostProperty) GetLabel() string { + if apt.IsLabel() { + return strings.TrimSpace(strings.TrimPrefix(string(*apt), "label:")) + } + return "" +} + const ( CloudCostInvoiceEntityIDProp string = "invoiceEntityID" CloudCostAccountIDProp string = "accountID" @@ -16,6 +34,49 @@ const ( CloudCostLabelProp string = "label" ) +func ParseCloudProperties(props []string) ([]CloudCostProperty, error) { + properties := []CloudCostProperty{} + added := make(map[CloudCostProperty]struct{}) + + for _, prop := range props { + property, err := ParseCloudCostProperty(prop) + if err != nil { + return nil, fmt.Errorf("Failed to parse property: %w", err) + } + + if _, ok := added[property]; !ok { + added[property] = struct{}{} + properties = append(properties, property) + } + } + + return properties, nil +} + +func ParseCloudCostProperty(text string) (CloudCostProperty, error) { + switch strings.TrimSpace(strings.ToLower(text)) { + case "invoiceEntityID": + return CloudCostProperty(CloudCostInvoiceEntityIDProp), nil + case "accountID": + return CloudCostProperty(CloudCostAccountIDProp), nil + case "provider": + return CloudCostProperty(CloudCostProviderProp), nil + case "providerID": + return CloudCostProperty(CloudCostProviderIDProp), nil + case "category": + return CloudCostProperty(CloudCostCategoryProp), nil + case "service": + return CloudCostProperty(CloudCostServiceProp), nil + } + + if strings.HasPrefix(text, "label:") { + label := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:"))) + return CloudCostProperty(fmt.Sprintf("label:%s", label)), nil + } + + return "", fmt.Errorf("invalid cloud cost property: %s", text) +} + const ( // CloudCostClusterManagementCategory describes CloudCost representing Hosted Kubernetes Fees CloudCostClusterManagementCategory string = "Cluster Management" diff --git a/pkg/cloudcost/queryservice_helper.go b/pkg/cloudcost/queryservice_helper.go index c9e3b225e..b298d2f5a 100644 --- a/pkg/cloudcost/queryservice_helper.go +++ b/pkg/cloudcost/queryservice_helper.go @@ -4,13 +4,11 @@ import ( "encoding/csv" "fmt" "net/http" - "strings" "github.com/opencost/opencost/core/pkg/filter" "github.com/opencost/opencost/core/pkg/filter/cloudcost" "github.com/opencost/opencost/core/pkg/opencost" "github.com/opencost/opencost/core/pkg/util/httputil" - "github.com/opencost/opencost/core/pkg/util/promutil" ) func ParseCloudCostRequest(qp httputil.QueryParams) (*QueryRequest, error) { @@ -31,11 +29,11 @@ func ParseCloudCostRequest(qp httputil.QueryParams) (*QueryRequest, error) { aggregateByRaw := qp.GetList("aggregate", ",") var aggregateBy []string for _, aggBy := range aggregateByRaw { - prop, err := ParseCloudCostProperty(aggBy) + prop, err := opencost.ParseCloudCostProperty(aggBy) if err != nil { return nil, fmt.Errorf("error parsing aggregate by %v", err) } - aggregateBy = append(aggregateBy, prop) + aggregateBy = append(aggregateBy, string(prop)) } // if we're aggregating by nothing (aka `item` on the frontend) then aggregate by all @@ -66,28 +64,12 @@ func ParseCloudCostRequest(qp httputil.QueryParams) (*QueryRequest, error) { return opts, nil } -func ParseCloudCostProperty(text string) (string, error) { - switch strings.TrimSpace(strings.ToLower(text)) { - case strings.ToLower(opencost.CloudCostInvoiceEntityIDProp): - return opencost.CloudCostInvoiceEntityIDProp, nil - case strings.ToLower(opencost.CloudCostAccountIDProp): - return opencost.CloudCostAccountIDProp, nil - case strings.ToLower(opencost.CloudCostProviderProp): - return opencost.CloudCostProviderProp, nil - case strings.ToLower(opencost.CloudCostProviderIDProp): - return opencost.CloudCostProviderIDProp, nil - case strings.ToLower(opencost.CloudCostCategoryProp): - return opencost.CloudCostCategoryProp, nil - case strings.ToLower(opencost.CloudCostServiceProp): - return opencost.CloudCostServiceProp, nil +func convertProps[T ~string](values []T) []string { + var result []string + for _, value := range values { + result = append(result, string(value)) } - - if strings.HasPrefix(text, "label:") { - label := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:"))) - return fmt.Sprintf("label:%s", label), nil - } - - return "", fmt.Errorf("invalid cloud cost property: %s", text) + return result } func parseCloudCostViewRequest(qp httputil.QueryParams) (*ViewQueryRequest, error) { From fbe8373c2c44ee97cb45e246460dfcdc3f8c6ca0 Mon Sep 17 00:00:00 2001 From: Matt Bolt Date: Mon, 22 Jan 2024 12:12:20 -0500 Subject: [PATCH 2/3] fix parsing to use lcase Signed-off-by: Matt Bolt --- core/pkg/opencost/cloudcostprops.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/pkg/opencost/cloudcostprops.go b/core/pkg/opencost/cloudcostprops.go index 4cef693b7..19adada8d 100644 --- a/core/pkg/opencost/cloudcostprops.go +++ b/core/pkg/opencost/cloudcostprops.go @@ -55,13 +55,13 @@ func ParseCloudProperties(props []string) ([]CloudCostProperty, error) { func ParseCloudCostProperty(text string) (CloudCostProperty, error) { switch strings.TrimSpace(strings.ToLower(text)) { - case "invoiceEntityID": + case "invoiceentityid": return CloudCostProperty(CloudCostInvoiceEntityIDProp), nil - case "accountID": + case "accountid": return CloudCostProperty(CloudCostAccountIDProp), nil case "provider": return CloudCostProperty(CloudCostProviderProp), nil - case "providerID": + case "providerid": return CloudCostProperty(CloudCostProviderIDProp), nil case "category": return CloudCostProperty(CloudCostCategoryProp), nil From 8dddbef3ed048710d93a7a564ba12d1abb8fe5eb Mon Sep 17 00:00:00 2001 From: Matt Bolt Date: Mon, 22 Jan 2024 12:42:09 -0500 Subject: [PATCH 3/3] Remove unused function Signed-off-by: Matt Bolt --- pkg/cloudcost/queryservice_helper.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/cloudcost/queryservice_helper.go b/pkg/cloudcost/queryservice_helper.go index b298d2f5a..0066d887c 100644 --- a/pkg/cloudcost/queryservice_helper.go +++ b/pkg/cloudcost/queryservice_helper.go @@ -64,14 +64,6 @@ func ParseCloudCostRequest(qp httputil.QueryParams) (*QueryRequest, error) { return opts, nil } -func convertProps[T ~string](values []T) []string { - var result []string - for _, value := range values { - result = append(result, string(value)) - } - return result -} - func parseCloudCostViewRequest(qp httputil.QueryParams) (*ViewQueryRequest, error) { qr, err := ParseCloudCostRequest(qp) if err != nil {