CodeStarを使ったAWS lambdaの料金通知Botのデプロイ

AWS Codestarがリリースされて少し経ちましたが、まだ東京リージョンには来ないですね。待ち遠しい。

Codestar自体は知っていたのですがAWSSummitTokyoで思った以上に便利そうな印象を受けたことと、LambdaのServerlessApplicationModel(SAM)を使ってみたくなったため、社内向けのbotのデプロイ作業をこれで置き換えてみました。

※2017/7/11現在、東京リージョンにはまだ来ていませんのでバージニアリージョンで試しています

## これからやること

請求情報をSkypeへ毎日通知するBotを作成して、CodeStarでデプロイする。


## 前提

* LambdaFunctionのコード自体の説明はしません。
* 他のChatツールへの通知もLambdaのコードを多少書きかえればすぐできると思います。


## 手順

### 1. 請求情報を取得できるように設定を行う

右上のアカウントのプルダウンから「請求ダッシュボード」を選択。
左側のメニューの「設定」で、「請求アラートを受け取る」にチェックを入れる。

![billingreport.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/07f46599-07dc-a58d-0870-6505a5c9ef12.jpeg)


### 2. Codestarでプロジェクトを作成

a. マネジメントコンソールからCodeStarを開く
b. ポップアップでロールを作るか？という質問が出るので「yes」を選択。
　　裏で「aws-codestar-service-role」ロールが作られています。
　 （初回やったときにこれが出なくて権限エラーが出てはまりました・・）
c. 「create new project」
d. pythonの新規プロジェクトを選択
![codestar.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/1409d450-5680-6f23-47f0-3b90dcce5d67.jpeg)

e. プロジェクト名を「billing-bot」にして「create project」
![codestar2.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/e1af4b0b-162c-7853-948b-388f6cbe6623.jpeg)

f. 開発ツールを選択する画面はSkipして完了。
![codestar3.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/13c6e11c-4c12-f8f7-814e-957b6cc2a471.jpeg)

なんかかっこいい画面が出てきましたね。

### 3. CodeCommitからひな形となるファイルをclone

CodeCommitのgitリポジトリにはsshで接続するものとします。

a. マネジメントコンソールのIAMを開く。
b. ユーザ一覧から利用しているユーザを選択する。
c. 認証情報欄で[SSH公開キーのアップロード]を押して、公開鍵を張り付ける。
d. SSHキーIDが表示されるのでメモっておく。
e. SSHキーIDが覚えにくいのでCodeCommitの場合はこのsshキーを使うということをconfigに書いておく。 \<SSH_KEY_ID>は自分のSSHキーIDに置き換えること。

```
$ cat ~/.ssh/config
Host git-codecommit.*.amazonaws.com
User <SSH_KEY_ID>
IdentityFile ~/.ssh/id_rsa
```

f. git cloneしてパスフレーズを入力するとcloneが実行される。
　（cloneするURLはCodeCommitのページに行けば参照可能です。

```
$ git clone ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/<REPOSITORY_NAME>
Enter passphrase for key '*****/.ssh/id_rsa':
```

httpsでの接続の場合は下記手順 c の下のあたりに認証情報(ユーザ名、パスワード)を生成する手順がありましたので、それでできると思います

初期ファイルはこのようになっていました

```
$ cd <REPOSITORY_NAME>
$ find -type f | grep -v .git
./buildspec.yml
./index.py
./README.md
./template.yml
```

READMEに説明が書かれています（英語ですが）

* buildspec.yml - コードをパッケージングする方法を記載したCodeBuild用設定ファイル
* index.py - Lambdaの実行コード
* template.yml - LambdaとAPIGatewayにデプロイするためのSAMの設定ファイル

非常にわかりにくかったのですが、buildspec.ymlの中の

```
aws cloudformation package --template template.yml --s3-bucket $S3_BUCKET --output-template template-export.json
```

にてtemplate.ymlをtemplate-export.jsonに変換して出力されます。
そして、ServerlessApplicationModelでデプロイする際はtemplate-export.jsonを使用しているようです。

template.ymlではGetEvent/PostEventなどからも想像できるようにApiGatewayの設定も行われています。

### 4. 実行コードの作成

lambdaの実行コードを書いていきます。作成対象は以下の★がついている3つのファイルです。
index.pyはいらないので消しておいてもOKです

```
$ find  -type f | grep -v .git
./buildspec.yml
./README.md
./requirements.txt　　　★
./src/billing_bot.py　　★
./src/skype_adapter.py　★
./template.yml
```

```requirement.txt
requests
pytz
```

```src/billing_bot.py
# -*- coding: utf-8 -*-

from skype_adapter import SkypeAdapter
from datetime import datetime, timedelta

import boto3
import pytz
import os

def post_current_charges():

	startDate = datetime.today() - timedelta(days = 1)
	endDate = datetime.today()

	session = boto3.Session()
	client = session.client('cloudwatch')	
	response = client.get_metric_statistics (
		MetricName = 'EstimatedCharges',
		Namespace  = 'AWS/Billing',
		Period	 = 86400,
		StartTime  = startDate,
		EndTime	= endDate,
		Statistics = ['Maximum'],
		Dimensions = [
			{
				'Name': 'Currency',
				'Value': 'USD'
			}
		]
	)
	
	maximum = response['Datapoints'][0]['Maximum']
	date = response['Datapoints'][0]['Timestamp'].strftime('%Y/%m/%d')

	roomid = os.environ['ROOM_ID']
	message = date + "時点でのAWS利用料は" + str(maximum) + "ドルです" 
	print message

	SkypeAdapter().postConversation(roomid, message)

def lambda_handler(event, context):
	"""Lambda使う場合のエントリポイント"""
	post_current_charges()


if __name__ == "__main__":
	"""コマンド実行のエントリポイント"""
	post_current_charges()
```

```src/skype_adapter.py
# -*- coding: utf-8 -*-

import requests
import json
import os

class SkypeAdapter:
	""" Skypeに投稿するためのAdapterClass """
	
	def postConversation(self, roomid, message):
		""" Skypeへメッセージを投稿する """
		token = self.__auth()
		self.__post( token, roomid, message )

	def __auth(self):
		""" MicrosoftBotFrameworkのOAuthClient認証を行いaccess_tokenを取得する """
		
		headers = { 'Content-Type' : 'application/x-www-form-urlencoded' }
		data = {
			'grant_type' : 'client_credentials',
			'client_id' : os.environ['CLIENT_ID'],
			'client_secret' : os.environ['CLIENT_SECRET'],
			'scope' : 'https://graph.microsoft.com/.default'
		}
		 
		access_token_response = requests.post( 'https://login.microsoftonline.com/common/oauth2/v2.0/token', headers=headers, data=data )

		if access_token_response.status_code != 200 :
			print access_token_response.headers
			print access_token_response.text
			raise StandardError('Skype OAuth Failed')
		 
		tokens = json.loads(access_token_response.text)
		return tokens['access_token']
	
	def __post(self, token, roomid, message):
		""" MicrosoftBotFrameworkのチャット投稿用RESTAPIを叩く """
		
		headers = { 
			'Authorization' : 'Bearer ' + token,
			'Content-Type' : 'application/json'
		}
		
		data = {
			'type' : 'message/text',
			'text' : message
		}
		
		url = 'https://api.skype.net/v3/conversations/' + roomid + '/activities/'
		
		response = requests.post( url, headers=headers, json=data)
		
		if response.status_code != 201 :
			print response.status_code
			print response.headers
			print response.text
			raise StandardError('Skype Post Failed')
		
		return
```

### 5. buildspec.yml

CodeBuildでの処理内容を定義するbuildspec.ymlを編集していきます。
installフェーズを追加して、pipを使って関連ライブラリの取得を行います。

```yaml
version: 0.1

phases:
  install:
    commands:
      - pip install -r requirements.txt -t ./src
  build:
    commands:
      - aws cloudformation package --template template.yml --s3-bucket $S3_BUCKET --output-template template-export.json
artifacts:
  type: zip
  files:
    - template-export.json
```

ただ、これをCodeCommitにpushしてBuildを実行しても落ちました・・

CodeBuildを詳しく見てみると、「ビルド環境」というもので実行環境のイメージを選択する部分があり、なぜか **nodejs** のイメージが選択されている！！！

![codebuild2.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/7813eb35-aacd-4b78-5981-ca21e20f1b8d.jpeg)

怒りを抑えて、python用のイメージに変えておきましょう。

![codebuild3.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/bfea5e25-b4c9-0e21-a2ef-2c0ef8594be9.jpeg)


### 6. template.ymlの設定

最後はLambdaをデプロイするためのtemplate.ymlです。
環境変数の部分は自分の環境にあったものを指定してください。アカウント取得方法は[過去記事](http://qiita.com/nyasba/items/268524b2349cfe54600d)を参考に。

```yaml
AWSTemplateFormatVersion: 2010-09-09
Transform:
- AWS::Serverless-2016-10-31
- AWS::CodeStar
Description: AWS利用料を通知するbot

Parameters:
  ProjectId:
    Type: String
    Description: CodeStar projectId used to associate new resources to team members

Resources:
  billingbot:
    Type: AWS::Serverless::Function
    Properties:
      Handler: billing_bot.lambda_handler
      Runtime: python2.7
      CodeUri: src
      Description: AWS利用料を通知するbot
      MemorySize: 128
      Timeout: 60
      Role: arn:aws:iam::860559588436:role/CodeStarWorker-billing-bot-Lambda
      Events:
        Schedule1:
          Type: Schedule
          Properties:
            Schedule: cron(0 1 * * ? *)
      Environment:
        Variables:
          CLIENT_SECRET: ********
          ROOM_ID: '19:*******@thread.skype'
          CLIENT_ID: *******
```

### 7. IAMロールに足りていない権限を付与

今回の用途に応じた権限をIAMのロールの設定画面から付与します。

* CloudFormationのロール：スケジュール実行用トリガ作成用にCloudWatchEventFullAccessが必要
* Lambdaのロール：請求情報を参照するためにCloudWatchReadOnlyAccessが必要

これらがないとビルドが当然ビルドは落ちます。
ここもはまりポイントでした。落ちてからじゃないとロールが作られていることにすら気づかなかったです。

ここも怒りを抑えてポチポチ設定。

### 8. CodeCommitにコードをpush

最後にこれらのコードをpushするとCodePipelineが動き出します。あとは、じっと待ちましょう。

### 9. テスト

Lambdaの画面を開いて、テスト！（パラメータは適当でいいです）

Skypeに通知が届けばテスト成功です！
![bot.JPG](https://qiita-image-store.s3.amazonaws.com/0/47589/85bd9255-c6f8-f21f-44f4-49a22aaaedc8.jpeg)

あとは毎日朝10時に自動投稿してくれます。

## 感想

### なんか遅い

* コードをCodeCommitにプッシュしてからのPipelineの起動に少し間が空く
* CodeCommitがInProgress状態になる(単にpushされただけなのに何が動いているのか不明)

逆にCodeBuildはDockerベースなのに思ったより早い。

### 完成度がまだ微妙

* CodeCommitはプルリクができなかったり
* CodeBuild用のDockerイメージがなぜかnodeJSで作られていたり
* 裏で勝手に作られているIAMロールに対して権限をつけることがわかりにくかったり
* （Cloudformationの機能ですが）失敗したときのデバッグ・リトライがつらかったり

と微妙なところも多いですが、トータルで見れば今後に期待が持てるサービスですね。