cloudcost.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. package cloudcost
  2. import (
  3. "reflect"
  4. "strings"
  5. "github.com/opencost/opencost/pkg/filter"
  6. "github.com/opencost/opencost/pkg/kubecost"
  7. "github.com/opencost/opencost/pkg/log"
  8. "github.com/opencost/opencost/pkg/util/mapper"
  9. )
  10. type CloudCostFilter struct {
  11. AccountIDs []string `json:"accountIDs,omitempty"`
  12. Categories []string `json:"categories,omitempty"`
  13. InvoiceEntityIDs []string `json:"invoiceEntityIDs,omitempty"`
  14. Labels []string `json:"labels,omitempty"`
  15. Providers []string `json:"providers,omitempty"`
  16. ProviderIDs []string `json:"providerIDs,omitempty"`
  17. Services []string `json:"services,omitempty"`
  18. }
  19. func (g *CloudCostFilter) Equals(that CloudCostFilter) bool {
  20. return reflect.DeepEqual(g.AccountIDs, that.AccountIDs) &&
  21. reflect.DeepEqual(g.Categories, that.Categories) &&
  22. reflect.DeepEqual(g.InvoiceEntityIDs, that.InvoiceEntityIDs) &&
  23. reflect.DeepEqual(g.Labels, that.Labels) &&
  24. reflect.DeepEqual(g.Providers, that.Providers) &&
  25. reflect.DeepEqual(g.ProviderIDs, that.ProviderIDs) &&
  26. reflect.DeepEqual(g.Services, that.Services)
  27. }
  28. func parseWildcardEnd(rawFilterValue string) (string, bool) {
  29. return strings.TrimSuffix(rawFilterValue, "*"), strings.HasSuffix(rawFilterValue, "*")
  30. }
  31. func CloudCostFilterFromParams(pmr mapper.PrimitiveMapReader) filter.Filter[*kubecost.CloudCost] {
  32. ccFilter := convertFilterQueryParams(pmr)
  33. return ParseCloudCostFilter(ccFilter)
  34. }
  35. func convertFilterQueryParams(pmr mapper.PrimitiveMapReader) CloudCostFilter {
  36. return CloudCostFilter{
  37. AccountIDs: pmr.GetList("filterAccountIDs", ","),
  38. Categories: pmr.GetList("filterCategories", ","),
  39. InvoiceEntityIDs: pmr.GetList("filterInvoiceEntityIDs", ","),
  40. Labels: pmr.GetList("filterLabels", ","),
  41. Providers: pmr.GetList("filterProviders", ","),
  42. ProviderIDs: pmr.GetList("filterProviderIDs", ","),
  43. Services: pmr.GetList("filterServices", ","),
  44. }
  45. }
  46. func ParseCloudCostFilter(filters CloudCostFilter) filter.Filter[*kubecost.CloudCost] {
  47. result := filter.And[*kubecost.CloudCost]{
  48. Filters: []filter.Filter[*kubecost.CloudCost]{},
  49. }
  50. if len(filters.InvoiceEntityIDs) > 0 {
  51. result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.InvoiceEntityIDs, kubecost.CloudCostInvoiceEntityIDProp))
  52. }
  53. if len(filters.AccountIDs) > 0 {
  54. result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.AccountIDs, kubecost.CloudCostAccountIDProp))
  55. }
  56. if len(filters.Providers) > 0 {
  57. result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Providers, kubecost.CloudCostProviderProp))
  58. }
  59. if len(filters.ProviderIDs) > 0 {
  60. result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.ProviderIDs, kubecost.CloudCostProviderIDProp))
  61. }
  62. if len(filters.Services) > 0 {
  63. result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Services, kubecost.CloudCostServiceProp))
  64. }
  65. if len(filters.Categories) > 0 {
  66. result.Filters = append(result.Filters, filterV1SingleValueFromList(filters.Categories, kubecost.CloudCostCategoryProp))
  67. }
  68. if len(filters.Labels) > 0 {
  69. result.Filters = append(result.Filters, filterV1DoubleValueFromList(filters.Labels, kubecost.CloudCostLabelProp))
  70. }
  71. if len(result.Filters) == 0 {
  72. return nil
  73. }
  74. return result
  75. }
  76. func filterV1SingleValueFromList(rawFilterValues []string, field string) filter.Filter[*kubecost.CloudCost] {
  77. result := filter.Or[*kubecost.CloudCost]{
  78. Filters: []filter.Filter[*kubecost.CloudCost]{},
  79. }
  80. for _, filterValue := range rawFilterValues {
  81. filterValue = strings.TrimSpace(filterValue)
  82. filterValue, wildcard := parseWildcardEnd(filterValue)
  83. subFilter := filter.StringProperty[*kubecost.CloudCost]{
  84. Field: field,
  85. Op: filter.StringEquals,
  86. Value: filterValue,
  87. }
  88. if wildcard {
  89. subFilter.Op = filter.StringStartsWith
  90. }
  91. result.Filters = append(result.Filters, subFilter)
  92. }
  93. return result
  94. }
  95. // filterV1DoubleValueFromList creates an OR of key:value equality filters for
  96. // colon-split filter values.
  97. //
  98. // The v1 query language (e.g. "filterLabels=app:foo,l2:bar") uses OR within
  99. // a field (e.g. label[app] = foo OR label[l2] = bar)
  100. func filterV1DoubleValueFromList(rawFilterValuesUnsplit []string, filterField string) filter.Filter[*kubecost.CloudCost] {
  101. result := filter.Or[*kubecost.CloudCost]{
  102. Filters: []filter.Filter[*kubecost.CloudCost]{},
  103. }
  104. for _, unsplit := range rawFilterValuesUnsplit {
  105. if unsplit != "" {
  106. split := strings.Split(unsplit, ":")
  107. if len(split) != 2 {
  108. log.Warnf("illegal key/value filter (ignoring): %s", unsplit)
  109. continue
  110. }
  111. labelName := strings.TrimSpace(split[0])
  112. val := strings.TrimSpace(split[1])
  113. val, wildcard := parseWildcardEnd(val)
  114. subFilter := filter.StringMapProperty[*kubecost.CloudCost]{
  115. Field: filterField,
  116. // All v1 filters are equality comparisons
  117. Op: filter.StringMapEquals,
  118. Key: labelName,
  119. Value: val,
  120. }
  121. if wildcard {
  122. subFilter.Op = filter.StringMapStartsWith
  123. }
  124. result.Filters = append(result.Filters, subFilter)
  125. }
  126. }
  127. return result
  128. }