| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package azure
- import (
- "fmt"
- "sort"
- "strings"
- "time"
- "yunion.io/x/pkg/errors"
- )
- type SMonthBill struct {
- Month string `json:"month"`
- StartDate string `json:"start_date"`
- EndDate string `json:"end_date"`
- Currency string `json:"currency"`
- Total float64 `json:"total"`
- Metric string `json:"metric"`
- Granularity string `json:"granularity"`
- Subscriptions []SMonthBillServiceFee `json:"subscriptions"`
- }
- type SMonthBillServiceFee struct {
- SubscriptionId string `json:"subscription_id"`
- Amount float64 `json:"amount"`
- Unit string `json:"unit"`
- }
- type SCostQueryResult struct {
- Properties struct {
- Columns []struct {
- Name string `json:"name"`
- Type string `json:"type"`
- } `json:"columns"`
- Rows [][]interface{} `json:"rows"`
- } `json:"properties"`
- }
- func getMonthDateRange(month string) (string, string, error) {
- monthStart, err := time.Parse("2006-01", month)
- if err != nil {
- return "", "", errors.Wrapf(err, "invalid month %q, expected YYYY-MM", month)
- }
- monthEnd := monthStart.AddDate(0, 1, -1)
- return monthStart.Format("2006-01-02"), monthEnd.Format("2006-01-02"), nil
- }
- func (self *SAzureClient) GetMonthBill(month string) (*SMonthBill, error) {
- startDate, endDate, err := getMonthDateRange(month)
- if err != nil {
- return nil, err
- }
- billingAccounts, err := self.GetBillingAccounts()
- if err != nil {
- return nil, errors.Wrap(err, "GetBillingAccounts")
- }
- if len(billingAccounts) == 0 {
- return nil, errors.Errorf("no available billing accounts")
- }
- ret := &SMonthBill{
- Month: month,
- StartDate: startDate,
- EndDate: endDate,
- Metric: "PreTaxCost",
- Granularity: "monthly",
- Subscriptions: make([]SMonthBillServiceFee, 0),
- }
- feeBySubscriptionId := map[string]float64{}
- for i := range billingAccounts {
- accountPath := strings.TrimSpace(billingAccounts[i].Id)
- if len(accountPath) == 0 && len(billingAccounts[i].Name) > 0 {
- accountPath = fmt.Sprintf("/providers/Microsoft.Billing/billingAccounts/%s", billingAccounts[i].Name)
- }
- if len(accountPath) == 0 {
- continue
- }
- resource := fmt.Sprintf("%s/providers/Microsoft.CostManagement/query", strings.TrimSuffix(accountPath, "/"))
- resp, err := self.post_v2(resource, "2025-03-01", map[string]interface{}{
- "type": "ActualCost",
- "timeframe": "Custom",
- "timePeriod": map[string]string{
- "from": fmt.Sprintf("%s", startDate),
- "to": fmt.Sprintf("%s", endDate),
- },
- "dataset": map[string]interface{}{
- "granularity": "None",
- "aggregation": map[string]interface{}{
- "totalCost": map[string]string{
- "name": "PreTaxCost",
- "function": "Sum",
- },
- },
- "grouping": []map[string]string{
- {
- "type": "Dimension",
- "name": "SubscriptionId",
- },
- },
- },
- })
- if err != nil {
- return nil, errors.Wrapf(err, "post_v2 cost query for billing account %s", billingAccounts[i].Name)
- }
- queryRet := SCostQueryResult{}
- if err := resp.Unmarshal(&queryRet); err != nil {
- return nil, errors.Wrapf(err, "resp.Unmarshal cost query for billing account %s", billingAccounts[i].Name)
- }
- subscriptionIdx, costIdx, currencyIdx := -1, -1, -1
- for c := range queryRet.Properties.Columns {
- name := strings.ToLower(queryRet.Properties.Columns[c].Name)
- switch name {
- case "subscriptionid", "subscriptionname":
- subscriptionIdx = c
- case "totalcost", "pretaxcost":
- costIdx = c
- case "currency":
- currencyIdx = c
- }
- }
- if subscriptionIdx < 0 || costIdx < 0 {
- continue
- }
- for j := range queryRet.Properties.Rows {
- row := queryRet.Properties.Rows[j]
- if len(row) <= costIdx || len(row) <= subscriptionIdx {
- continue
- }
- subscription := strings.TrimSpace(fmt.Sprintf("%v", row[subscriptionIdx]))
- if len(subscription) == 0 {
- subscription = "Unknown"
- }
- cost, ok := row[costIdx].(float64)
- if !ok {
- continue
- }
- feeBySubscriptionId[subscription] += cost
- ret.Total += cost
- if len(ret.Currency) == 0 {
- if currencyIdx >= 0 && len(row) > currencyIdx {
- ret.Currency = strings.TrimSpace(fmt.Sprintf("%v", row[currencyIdx]))
- }
- }
- }
- }
- for subscriptionId, amount := range feeBySubscriptionId {
- ret.Subscriptions = append(ret.Subscriptions, SMonthBillServiceFee{
- SubscriptionId: subscriptionId,
- Amount: amount,
- Unit: ret.Currency,
- })
- }
- sort.Slice(ret.Subscriptions, func(i, j int) bool {
- return ret.Subscriptions[i].Amount > ret.Subscriptions[j].Amount
- })
- return ret, nil
- }
|