【Go】AWSのサービス毎の利用料金を取得する

## はじめに
これまで、「[AWSの利用料金をChatworkに通知してくれるバッチをGoで作った](https://qiita.com/maika_kamada/items/17ac04a6b8b3c56c4b18)」や「[Goでslackに通知するバッチを作った【新Webhook】](https://qiita.com/maika_kamada/items/8abb3d570217ffbda388)」ではAWSの合計利用料金を通知してきましたが、サービス毎の明細が欲しかったので、サービス毎の利用料金もいっしょに通知してみることにしました！

## 通知したいこと

+ 昨日の合計利用料金
+ 昨日のサービス毎の利用料金

## 利用料金の取得
### Lambda関数の作成
定期的にSlackやChatworkに通知することを想定して、Lambda関数を作成していきます。

#### IAMポリシー
API「GetCostAndUsage」を実行できる権限を付与します。

```JSON
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "ce:GetCostAndUsage",
            "Resource": "*"
        }
    ]
}
```
このポリシーをアタッチしたIAMロールを作成し、Lambdaに紐づけてください。

#### ソースコード
本コードはサービス毎の利用料金の取得方法にフォーカスしています。合計料金の取得方法は、[こちら](https://qiita.com/maika_kamada/items/17ac04a6b8b3c56c4b18)を参考にしてください。

```Go
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/costexplorer"
	"github.com/aws/aws-sdk-go/service/costexplorer/costexploreriface"
)

// コストの取得
func GetCost(svc costexploreriface.CostExplorerAPI) (result *costexplorer.GetCostAndUsageOutput) {

	// Granularity
	granularity := aws.String("DAILY")

	// Metrics
	metric := "UnblendedCost"
	metrics := []*string{&metric}

	// TimePeriod
	// 現在時刻の取得
	jst, _ := time.LoadLocation("Asia/Tokyo")
	now := time.Now().UTC().In(jst)
	dayBefore := now.AddDate(0, 0, -1)

	nowDate := now.Format("2006-01-02")
	dateBefore := dayBefore.Format("2006-01-02")

	// 昨日から今日まで
	timePeriod := costexplorer.DateInterval{
		Start: aws.String(dateBefore),
		End:   aws.String(nowDate),
	}

	// GroupBy
	group := costexplorer.GroupDefinition{
		Key:  aws.String("SERVICE"),
		Type: aws.String("DIMENSION"),
	}
	groups := []*costexplorer.GroupDefinition{&group}

	// Inputの作成
	input := costexplorer.GetCostAndUsageInput{}
	input.Granularity = granularity
	input.Metrics = metrics
	input.TimePeriod = &timePeriod
	input.GroupBy = groups

	// 処理実行
	result, err := svc.GetCostAndUsage(&input)
	if err != nil {
		log.Println(err.Error())
	}

	return result
}

/**************************
    処理実行
**************************/
func run() error {
	log.Println("コスト取得バッチ 開始")
	log.Println("セッション作成")
	svc := costexplorer.New(session.Must(session.NewSession()))

	log.Println("コスト取得 実行")
	cost := GetCost(svc)
	log.Println("コスト取得 完了")

	fmt.Println(cost)

	log.Println("コスト取得バッチ 完了")
	return nil
}

/**************************
    メイン
**************************/
func main() {
	lambda.Start(run)
}
```

### 実行結果
Lambda関数の実行ログから一部抜粋しています。

```
2020/05/06 03:59:54 コスト取得バッチ 開始
2020/05/06 03:59:54 セッション作成
2020/05/06 03:59:54 コスト取得 実行
2020/05/06 03:59:55 コスト取得 完了
{
  GroupDefinitions: [{
      Key: "SERVICE",
      Type: "DIMENSION"
    }],
  ResultsByTime: [{
      Estimated: true,
      Groups: [
        {
          Keys: ["AWS Cost Explorer"],
          Metrics: {
            UnblendedCost: {
              Amount: "0.05",
              Unit: "USD"
            }
          }
        },
        {
          Keys: ["AWS Lambda"],
          Metrics: {
            UnblendedCost: {
              Amount: "0.0000019028",
              Unit: "USD"
            }
          }
        },
      ],
      TimePeriod: {
        End: "2020-05-06",
        Start: "2020-05-05"
      },
      Total: {

      }
    }]
}
2020/05/06 03:59:55 コスト取得バッチ 完了
```

### 解説

合計利用料金を取得する際に利用したAPI「GetCostAndUsage」のInput要素は以下の3つでした。

+ Granularity
+ Metrics
+ TimePeriod

今回、サービス毎の料金を取得するため、以下の要素を追加します。

+ GroupBy

GroupByは指定した条件に沿ってグループ化することができます。条件を指定するために、**Key**と**Type**を設定します。
Keyは何を基準に判断するのか、Typeはどう分けるのかということを意味します。今回はサービス毎の利用料金を知りたいので下記のようにしました。

```
Key:  aws.String("SERVICE"),
Type: aws.String("DIMENSION"),
```

よって、SERVICE（サービス）のDIMENTION（種類）でグループ化するという意味になります。

その他サービス毎以外にも、タグ毎など様々な条件のグループでコストを取得することができます。
参考：[AWS SDK for Go API Reference](https://docs.aws.amazon.com/sdk-for-go/api/service/costexplorer/#GetCostAndUsageInput)

### 注意点
GroupByの要素を追加することで、Totalの値が取得できなくなります。（[実行結果](#実行結果)参照）
なので、今回のように合計料金とサービス毎の両方のコストを取得したい場合は、2度リクエスト投げる必要があります。

ちなみに、AWSのサポートに問い合わせてみましたが、現状は1度のリクエストで両方のコストが取得できる方法は確認できておりませんとの返事が返ってきました。(2020/03/19時点）

## おわりに
合計料金とサービス毎の料金を1度に取得できないので、2回APIをたたく必要がある、つまり、倍のコストがかかってしまうというところがありますので…利用料金とその明細が知りたい！！ってときには、サービス毎の利用料金を自分で合計してしまいましょう。
### おまけ
ちなみに、自分で合計してみました。

```Go
func SumCost(cost *costexplorer.GetCostAndUsageOutput) (total string){

	sum := 0.0
	for _, data := range cost.ResultsByTime[0].Groups {
		amount, _ := strconv.ParseFloat(*data.Metrics["UnblendedCost"].Amount, 64)
		sum = sum + amount
	}
	total = fmt.Sprintf("%.10f", sum)
	return total
}
```

取得したコストは`String`型なので、`float64`型にしてから足していきます。
これでサービス毎の利用料金を取得するだけで、合計料金も合わせて通知することができそうです！