business.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package aws
  15. import (
  16. "context"
  17. "sort"
  18. "strconv"
  19. "time"
  20. aws2 "github.com/aws/aws-sdk-go-v2/aws"
  21. "github.com/aws/aws-sdk-go-v2/service/costexplorer"
  22. cetypes "github.com/aws/aws-sdk-go-v2/service/costexplorer/types"
  23. api "yunion.io/x/cloudmux/pkg/apis/compute"
  24. "yunion.io/x/pkg/errors"
  25. )
  26. type SMonthBill struct {
  27. Month string `json:"month"`
  28. StartDate string `json:"start_date"`
  29. EndDate string `json:"end_date"`
  30. Currency string `json:"currency"`
  31. Total float64 `json:"total"`
  32. Metric string `json:"metric"`
  33. Granularity string `json:"granularity"`
  34. Services []SMonthBillServiceFee `json:"services"`
  35. }
  36. type SMonthBillServiceFee struct {
  37. Service string `json:"service"`
  38. Amount string `json:"amount"`
  39. Unit string `json:"unit"`
  40. }
  41. func getMonthDateRange(month string) (string, string, error) {
  42. monthStart, err := time.Parse("2006-01", month)
  43. if err != nil {
  44. return "", "", errors.Wrapf(err, "invalid month %q, expected YYYY-MM", month)
  45. }
  46. monthEnd := monthStart.AddDate(0, 1, 0)
  47. return monthStart.Format("2006-01-02"), monthEnd.Format("2006-01-02"), nil
  48. }
  49. func (self *SAwsClient) getCostExplorerRegion() string {
  50. if self.GetAccessEnv() == api.CLOUD_ACCESS_ENV_AWS_CHINA {
  51. return "cn-northwest-1"
  52. }
  53. return "us-east-1"
  54. }
  55. func (self *SAwsClient) GetMonthBill(month string) (*SMonthBill, error) {
  56. startDate, endDate, err := getMonthDateRange(month)
  57. if err != nil {
  58. return nil, err
  59. }
  60. cfg, err := self.getConfig(context.Background(), self.getCostExplorerRegion(), true)
  61. if err != nil {
  62. return nil, errors.Wrap(err, "getConfig")
  63. }
  64. ceCli := costexplorer.NewFromConfig(cfg)
  65. resp, err := ceCli.GetCostAndUsage(context.Background(), &costexplorer.GetCostAndUsageInput{
  66. TimePeriod: &cetypes.DateInterval{
  67. Start: aws2.String(startDate),
  68. End: aws2.String(endDate),
  69. },
  70. Granularity: cetypes.GranularityMonthly,
  71. Metrics: []string{"UnblendedCost"},
  72. GroupBy: []cetypes.GroupDefinition{
  73. {
  74. Type: cetypes.GroupDefinitionTypeDimension,
  75. Key: aws2.String("SERVICE"),
  76. },
  77. },
  78. })
  79. if err != nil {
  80. return nil, errors.Wrap(err, "GetCostAndUsage")
  81. }
  82. ret := &SMonthBill{
  83. Month: month,
  84. StartDate: startDate,
  85. EndDate: endDate,
  86. Metric: "UnblendedCost",
  87. Granularity: string(cetypes.GranularityMonthly),
  88. Services: make([]SMonthBillServiceFee, 0),
  89. }
  90. var total float64
  91. for _, byTime := range resp.ResultsByTime {
  92. for _, group := range byTime.Groups {
  93. if len(group.Keys) == 0 {
  94. continue
  95. }
  96. metric, ok := group.Metrics["UnblendedCost"]
  97. if !ok {
  98. continue
  99. }
  100. if len(ret.Currency) == 0 {
  101. ret.Currency = aws2.ToString(metric.Unit)
  102. }
  103. amount := aws2.ToString(metric.Amount)
  104. if len(amount) > 0 {
  105. if val, parseErr := strconv.ParseFloat(amount, 64); parseErr == nil {
  106. total += val
  107. }
  108. }
  109. ret.Services = append(ret.Services, SMonthBillServiceFee{
  110. Service: group.Keys[0],
  111. Amount: amount,
  112. Unit: aws2.ToString(metric.Unit),
  113. })
  114. }
  115. }
  116. ret.Total = total
  117. sort.Slice(ret.Services, func(i, j int) bool {
  118. iVal, iErr := strconv.ParseFloat(ret.Services[i].Amount, 64)
  119. jVal, jErr := strconv.ParseFloat(ret.Services[j].Amount, 64)
  120. if iErr != nil || jErr != nil {
  121. return ret.Services[i].Amount > ret.Services[j].Amount
  122. }
  123. return iVal > jVal
  124. })
  125. return ret, nil
  126. }