queryservice_helper.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package cloudcost
  2. import (
  3. "encoding/csv"
  4. "fmt"
  5. "net/http"
  6. "github.com/opencost/opencost/core/pkg/filter"
  7. "github.com/opencost/opencost/core/pkg/filter/cloudcost"
  8. "github.com/opencost/opencost/core/pkg/opencost"
  9. "github.com/opencost/opencost/core/pkg/util/httputil"
  10. )
  11. func ParseCloudCostRequest(qp httputil.QueryParams) (*QueryRequest, error) {
  12. windowStr := qp.Get("window", "")
  13. if windowStr == "" {
  14. return nil, fmt.Errorf("missing require window param")
  15. }
  16. window, err := opencost.ParseWindowUTC(windowStr)
  17. if err != nil {
  18. return nil, fmt.Errorf("invalid window parameter: %w", err)
  19. }
  20. if window.IsOpen() {
  21. return nil, fmt.Errorf("invalid window parameter: %s", window.String())
  22. }
  23. aggregateByRaw := qp.GetList("aggregate", ",")
  24. var aggregateBy []string
  25. for _, aggBy := range aggregateByRaw {
  26. prop, err := opencost.ParseCloudCostProperty(aggBy)
  27. if err != nil {
  28. return nil, fmt.Errorf("error parsing aggregate by %v", err)
  29. }
  30. aggregateBy = append(aggregateBy, string(prop))
  31. }
  32. // if we're aggregating by nothing (aka `item` on the frontend) then aggregate by all
  33. if len(aggregateBy) == 0 {
  34. aggregateBy = []string{opencost.CloudCostInvoiceEntityIDProp, opencost.CloudCostAccountIDProp, opencost.CloudCostProviderProp, opencost.CloudCostProviderIDProp, opencost.CloudCostCategoryProp, opencost.CloudCostServiceProp}
  35. }
  36. accumulate := opencost.ParseAccumulate(qp.Get("accumulate", ""))
  37. var filter filter.Filter
  38. filterString := qp.Get("filter", "")
  39. if filterString != "" {
  40. parser := cloudcost.NewCloudCostFilterParser()
  41. filter, err = parser.Parse(filterString)
  42. if err != nil {
  43. return nil, fmt.Errorf("Parsing 'filter' parameter: %s", err)
  44. }
  45. }
  46. opts := &QueryRequest{
  47. Start: *window.Start(),
  48. End: *window.End(),
  49. AggregateBy: aggregateBy,
  50. Accumulate: accumulate,
  51. Filter: filter,
  52. }
  53. return opts, nil
  54. }
  55. func ParseCloudCostAutocompleteRequest(qp httputil.QueryParams) (*CloudCostAutocompleteRequest, error) {
  56. windowStr := qp.Get("window", "")
  57. if windowStr == "" {
  58. return nil, fmt.Errorf("missing require window param")
  59. }
  60. window, err := opencost.ParseWindowUTC(windowStr)
  61. if err != nil {
  62. return nil, fmt.Errorf("invalid window parameter: %w", err)
  63. }
  64. if window.IsOpen() {
  65. return nil, fmt.Errorf("invalid window parameter: %s", window.String())
  66. }
  67. var parsedFilter filter.Filter
  68. filterString := qp.Get("filter", "")
  69. if filterString != "" {
  70. parser := cloudcost.NewCloudCostFilterParser()
  71. parsedFilter, err = parser.Parse(filterString)
  72. if err != nil {
  73. return nil, fmt.Errorf("Parsing 'filter' parameter: %s", err)
  74. }
  75. }
  76. req := &CloudCostAutocompleteRequest{
  77. Search: qp.Get("search", ""),
  78. Field: qp.Get("field", ""),
  79. Limit: qp.GetInt("limit", 0),
  80. Window: window,
  81. Filter: parsedFilter,
  82. }
  83. if req.Field == "" {
  84. return nil, fmt.Errorf("missing required 'field' parameter")
  85. }
  86. return req, nil
  87. }
  88. func ParseCloudCostViewRequest(qp httputil.QueryParams) (*ViewQueryRequest, error) {
  89. qr, err := ParseCloudCostRequest(qp)
  90. if err != nil {
  91. return nil, err
  92. }
  93. // parse cost metric
  94. costMetricName, err := opencost.ParseCostMetricName(qp.Get("costMetric", string(opencost.CostMetricAmortizedNetCost)))
  95. if err != nil {
  96. return nil, fmt.Errorf("error parsing 'costMetric': %w", err)
  97. }
  98. limit := qp.GetInt("limit", 0)
  99. if limit < 0 {
  100. return nil, fmt.Errorf("invalid value for limit %d", limit)
  101. }
  102. offset := qp.GetInt("offset", 0)
  103. if offset < 0 {
  104. return nil, fmt.Errorf("invalid value for offset %d", offset)
  105. }
  106. // parse order
  107. order, err := ParseSortDirection(qp.Get("sortByOrder", "desc"))
  108. if err != nil {
  109. return nil, fmt.Errorf("error parsing 'sortByOrder: %w", err)
  110. }
  111. sortColumn, err := ParseSortField(qp.Get("sortBy", "cost"))
  112. if err != nil {
  113. return nil, fmt.Errorf("error parsing 'sortBy': %w", err)
  114. }
  115. return &ViewQueryRequest{
  116. QueryRequest: *qr,
  117. CostMetricName: costMetricName,
  118. ChartItemsLength: DefaultChartItemsLength,
  119. Limit: limit,
  120. Offset: offset,
  121. SortDirection: order,
  122. SortColumn: sortColumn,
  123. }, nil
  124. }
  125. // CloudCostViewTableRowsToCSV takes the csv writer and writes the ViewTableRows into the writer.
  126. func CloudCostViewTableRowsToCSV(writer *csv.Writer, ctr ViewTableRows, window string) error {
  127. defer writer.Flush()
  128. // Write the column headers
  129. headers := []string{
  130. "Name",
  131. "K8s Utilization",
  132. "Total",
  133. "Window",
  134. }
  135. err := writer.Write(headers)
  136. if err != nil {
  137. return fmt.Errorf("CloudCostViewTableRowsToCSV: failed to convert ViewTableRows to csv with error: %w", err)
  138. }
  139. // Write one row per entry in the ViewTableRows
  140. for _, row := range ctr {
  141. err = writer.Write([]string{
  142. row.Name,
  143. fmt.Sprintf("%.3f", row.KubernetesPercent),
  144. fmt.Sprintf("%.3f", row.Cost),
  145. window,
  146. })
  147. if err != nil {
  148. return fmt.Errorf("CloudCostViewTableRowsToCSV: failed to convert ViewTableRows to csv with error: %w", err)
  149. }
  150. }
  151. return nil
  152. }
  153. func writeCloudCostViewTableRowsAsCSV(w http.ResponseWriter, ctr ViewTableRows, window string) {
  154. writer := csv.NewWriter(w)
  155. err := CloudCostViewTableRowsToCSV(writer, ctr, window)
  156. if err != nil {
  157. protocol.WriteError(w, protocol.InternalServerError(err.Error()))
  158. return
  159. }
  160. }