boto3でec2をdescribeして値を取り出す

どうもLambda(Python)初心者の若松です。

唐突ですが、みなさんはLambda使っていますか？

サーバが無くてもコードが動く。
魅力的ですよねぇ。

やっぱり時代はServerlessだと。
インフラでも言語の一つくらい使えるようになれと。

そんな流れに逆らわないゆとり世代ど真ん中の私は、とうとうLambdaに踏み込んだわけです。

前置きが長くなりましたが、インフラ屋が四苦八苦しながら戯言言ってんなぁぐらいの気持ちで生暖かく見ていただければと思います。

# やりたいこと
特定のIPを持っているインスタンスのインスタンスIDを取得したい。

# 環境
AWS Lambda
Python 3.6

# 第一形態
## コード
とりあえずインスタンス情報を全部取り出してみようかと思い、以下のコードを書きました。

```py
import boto3

def lambda_handler(event, context):

    ec2 = boto3.client('ec2')
    
    responce = ec2.describe_instances()

    return responce
```

## 結果
returnで以下のエラーが返って失敗

```json
{
  "errorMessage": "An error occurred during JSON serialization of response",
  "errorType": "datetime.datetime(2017, 5, 11, 5, 15, 59, tzinfo=tzlocal()) is not JSON serializable"
}
```

結果そのままではJSONとして扱えないみたいです。

## 調査
boto3のリファレンスを呼んでみると、どうやら describe_instances の返り値は dict(辞書)型 というもののようです。
とりあえずprintで出力したほうがよさげなので、コードを変更することにしました。

# 第二形態
## コード
とりあえず出力をプリント文に変更

```py
import boto3

def lambda_handler(event, context):

    ec2 = boto3.client('ec2')
    
    responce = ec2.describe_instances()

    print(responce)

    return
```

## 結果
ログに以下が出力されました。
AWSCLIの結果でよく見るあれですね。

```json
{
    "Reservations": [
        {
            "OwnerId": "xxxxxxxxxxxx", 
            "ReservationId": "r-xxxxxxxxxxxxxxxxx", 
            "Groups": [], 
            "Instances": [
                {
                    "Monitoring": {
                        "State": "disabled"
                    }, 
                    "PublicDnsName": "ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com", 
                    "Platform": "xxxxxxx", 
                    "State": {
                        "Code": 80, 
                        "Name": "stopped"
                    }, 
                    "EbsOptimized": false, 
                    "LaunchTime": "xxxx-xx-xxxxx:xx:xx.xxxx", 
                    "PublicIpAddress": "xxx.xxx.xxx.xxx", 
                    "PrivateIpAddress": "xxx.xxx.xxx.xxx", 
                    "ProductCodes": [], 
                    "VpcId": "vpc-xxxxxxx", 
                    "StateTransitionReason": "", 
                    "InstanceId": "i-xxxxxxxxxxxxxxxxx", 
                    "EnaSupport": true, 
                    "ImageId": "ami-xxxxxxxx", 
以下略
```

## 調査
次はIPで絞りたいと思います。
AWSCLIでいうところの`--filter`はboto3にもあるらしいのでそれを使うことにしました。

# 第三形態
## コード
特定のIPを持っているインスタンスに絞るため、Filtersを追加

```py
import boto3

def lambda_handler(event, context):

    ec2 = boto3.client('ec2')
    
    responce = ec2.describe_instances(
        Filters=[{'Name':'network-interface.addresses.private-ip-address','Values':["xxx.xxx.xxx.xxx"]}]
    )

    print(responce)

    return
```

## 結果
指定したIPアドレスを持つインスタンスに絞れた。

## 調査
次は返り値をインスタンスIDに絞ります。
イメージとしてはAWSCLIでいうところの`--query`だが、boto3にはないようです。
ここでJSONに変換するだのなんだのと、ネットの情報に踊らされてかなり苦戦しました。
結果的には、dict型はオブジェクトの後に ["hoge"]["fuga"] と書くことで値を取り出せるらしいことがわかりました。

# 最終形態
## コード

```py
import boto3

def lambda_handler(event, context):

    ec2 = boto3.client('ec2')
    
    responce = ec2.describe_instances(
        Filters=[{'Name':'network-interface.addresses.private-ip-address','Values':["xxx.xxx.xxx.xxx"]}]
    )["Reservations"][0]["Instances"][0]["InstanceId"]

    print(responce)

    return
```

## 結果
めでたくインスタンスIDの取得に成功しました。
リストある際には`[0]`と明示的に0を書く必要があるというところに気をつけたいところです。
※AWSCLIの`--query`では`[]`と省略して書けるため

# まとめ
いかがでしたでしょうか。
やってる内容は非常に初歩的な内容ですが、いくつかハマった箇所があったので備忘録的にまとめました。

意外と簡単に動かせるので、是非Tryしてみてください。
Lambda怖くない。