🐡

毎日AWS利用料金をLineに送信する仕組みづくり

2022/02/22に公開

扱う技術:
--SAM
--Lambda
--LineAPI
--AWSCostExplo

なにをするのか
AWSの利用料金を毎朝8時にラインにて通知できるようにする。
(利用料金を毎日確認したいが毎日コンソールを開くわけではないので、利用料金を簡単に確認できるようにしたいというのが理由)
リポジトリ
全体構成

prerequizite
AWS CLI
AWS SAM
|-- have already installed

SAMで基本設定。

今回は ランタイムはpythonで、管理方法はzip形式とする。

sam init
Which template source would you like to use?
choice: 1 - AWS Quick Start Templates

Choose an AWS Quick Start application template
Template: 1 -  Hello World Example

Which runtime would you like to use?
Runtime: 9 - python3.9

What package type would you like to use?
Package type: 1 - Zip

Project name [sam-app]:hoge

これで基本的な設定ファイルが生成される。このテンプレートをそのままデプロイするとApiGateWayをトリガーとするLambdaが一つ生成される。

これをいじって上の構成のSAMの枠内にあるeventCloudWatchをトリガーにしたLambdaにする。

使用するリソースについての設定はtemplate.yaml内に記述されている。
このtemplate.yamlファイルの記述方法について詳しくはこちらの記事を参考に。

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

Globals:
  Function:
    Timeout: 30

Parameters:
  LineToken:
    Type: String
    Default: xxx
  MyLineId:
    Type: String
    Default: xxx

Globals:
  Function:
    Handler: app.lambda_handler
    Runtime: python3.9
    Environment:
      Variables:
        TZ: Asia/Tokyo
        REGION: ap-northeast-1
        LINE_TOKEN: !Ref LineToken
        MY_LINE_ID: !Ref MyLineId
Resources:
  InitialResponseFunction:
    Type: AWS::Serverless::Function
    Properties:
      Role: !GetAtt BillingNotiferIamRole.Arn
      Timeout: 300
      CodeUri: initial_handler/
      Events:
        CronAt8oclock:
          Type: Schedule
          Properties:
            Schedule: 'cron(0 12 * * ? *)'
            Name: InformBillingTrigger
            Description: test schedule
            Enabled: false
  BillingNotiferIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        -
          PolicyName: "billig_notifer_lambda"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              -
                Effect: "Allow"
                Action: "logs:*"
                Resource: "*"

こんな感じで設定する。
ここで気をつけるのは環境変数のxxxの部分は自分で埋めること。

あとはそのまま使えば大丈夫である

基本は下のgithubレポジトリを見て貰えばいいが、
実際のコード

少し解説すると

この関数で、Cost Exprorerにアクセスしてデータを取ってきていて。
TimePeriodでいつからいつまでのコストをとってくるのかを定義する。
今回はその月はじめ1日からその日までのコストを取得したいから
その日の月+01日を開始日、関数実行時の日終了日に設定している。
Granularityではどの幅でデータを取得するかを定義していて、今回はMontlyにしているのでその月毎にデータをフェッチできる。ここをDailyにすると開始日から終了日までのコストが1日ごとに取得できる。詳しくはこのドキュメント参照

def get_montlycost(head_of_month, today):
    billing_client = boto3.client('ce')
    response = billing_client.get_cost_and_usage(
        TimePeriod={
            'Start': head_of_month,
            'End': today },
        Granularity='MONTHLY',
        Metrics=[ 'UnblendedCost',],
        GroupBy= [
            { 'Type':'DIMENSION', 'Key':'SERVICE' }
        ],
    )
    cost_with_services = response["ResultsByTime"][0]["Groups"]
    return cost_with_services

いい感じにその日までのコストと、サービスが取得できたので、それをLine APIを使用して送っていく。

Line APIを使用したデータの送信

今回使用するのはユーザーにデータを送るだけの処理なのでMessageing APIを使用する。


こんな感じのBotが作れたら下にスクロールしてトークンを控えておく。
またBasicSettingにあるYour user ID も控えておこう。これがLINE_TOKENとMY_LINE_IDになる。
LineAPIではBotを使用してメッセージをユーザーに送ることができて、送ることができるメッセージのタイプは
参考資料

テキストメッセージ
スタンプメッセージ
画像メッセージ
動画メッセージ
音声メッセージ
位置情報メッセージ
イメージマップメッセージ
テンプレートメッセージ
Flex Message
である。それで今回使うのはFlex messageのほう。テンプレートメッセージはユーザーとのインタラクションが生じるときに用いられる。

一方的に送る場合はFlex Messageの方を使う。
このシュミレータを使うことで色々テストできる。

flex messageを送るときには
typeをflexに
altTextに何か文字列を入れる必要があり、
contentsのなかにシミュレーターでつくったものをそのまま突っ込めばいい。

{
    "type": "flex",
    "altText": "alternative text",
    "contents": {
        "type": "carousel",
        "contents": []
    }
}

今回はコンポーネントがAWSのその月に使ったサービスによって変動するため、contentsの量が可変的なので、そのままシミュレータで作ったものを突っ込むのではなく、分解して
Blocks/frame.json

{
    "type": "flex",
    "altText": "alternative text",
    "contents": {
        "type": "carousel",
        "contents": []
    }
}

このcontents: []の中にBlock/component.json(幾つになるかわからない)をpushしていくようなコードを書いた。
Block/component.json

{
    "type": "bubble",
    "size": "nano",
    "header": {
        "type": "box",
        "layout": "vertical",
        "contents": [
            {
                "type": "text",
                "text": "0.079 USD",
                "color": "#ffffff",
                "align": "start",
                "size": "md",
                "gravity": "center"
            },
            {
                "type": "text",
                "text": "20%",
                "color": "#ffffff",
                "align": "start",
                "size": "xs",
                "gravity": "center",
                "margin": "lg"
            },
            {
                "type": "box",
                "layout": "vertical",
                "contents": [
                    {
                        "type": "box",
                        "layout": "vertical",
                        "contents": [
                            {
                                "type": "filler"
                            }
                        ],
                        "width": "20%",
                        "backgroundColor": "#0D8186",
                        "height": "6px"
                    }
                ],
                "backgroundColor": "#9FD8E36E",
                "height": "6px",
                "margin": "sm"
            }
        ],
        "backgroundColor": "#27ACB2",
        "paddingTop": "19px",
        "paddingAll": "12px",
        "paddingBottom": "16px"
    },
    "body": {
        "type": "box",
        "layout": "vertical",
        "contents": [
            {
                "type": "box",
                "layout": "horizontal",
                "contents": [
                    {
                        "type": "text",
                        "text": "Amazon Relational Database Service",
                        "color": "#8C8C8C",
                        "size": "sm",
                        "wrap": true
                    }
                ],
                "flex": 1
            }
        ],
        "spacing": "md",
        "paddingAll": "12px"
    },
    "styles": {
        "footer": {
            "separator": false
        }
    }
}

Flex Message詳細

この送る形ができたらhttps postリクエストにより、実際に送ってみる。

そうするとこんな感じのメッセージを受け取ることができた。
おしまい。
送信時にはこれを参照すると良い。

Discussion