Elastic Beanstalkのインスタンスやボリュームのタグをデプロイ時に更新

#なぜ
 * .ebextensionsで環境プロパティからインスタンスやボリュームにカスタムタグを追加している。管理ルールが変わりタグ更新が必要になったため、何気なく更新したらデプロイに失敗した。目的の環境プロパティをタグ以外の処で参照していた。
 * シンプルに.ebextensionsを修正する何のことでもないことだが、保守担当に説明すると承認が必要のこと。これは時間がかかりそうだ。


#遠回り
改善策でサーバ構成もプログラムも遠回りしてはいけない。根本原因をつぶすのがベストだ。しかし、
承認とリリースまで1~2ヵ月はかかりそうなことと、以前からLambdaのイベントトリガーを試してみたかったことがあり今回は遠回りをする。

![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/249425/90b47277-865f-e9e2-67a6-5066270e43f4.png)


## Lambda
 * LambdaのRoleは事前登録しておく

```python:Python3.8
import json
import boto3

def lambda_handler(event, context):
    evt = str(event)
    print(evt) #json parseできない文字列が飛んでくるため

    # eb-app, new-tag, old-tag //環境変数の利用をお勧め
    apps = [
        ["app1", "newapp1", "oldapp1"],
        ["app2", "newapp2", "oldapp2"],
        ["app3", "newapp3", "oldapp3"],
        ...
        ...
    ]
    newTag = ''
    oldTag = ''
    client = boto3.resource('ec2', region_name='ap-northeast-1')

    if evt.find('EbUpdateEnvironmentCron') > 0:
        #from cloudwatch cron EBインスタンスはすべてチェックしたい時
        #EbUpdateEnvironmentCron: イベント名
        
        for app in apps:
            newTag = app[1]
            oldTag = app[2]

            update_ec2tag(client, oldTag, newTag)
            
    else:
        #from cloudtail event
        #更新されたEB環境に絞る        

        for app in apps:
            if evt.find(app[0]) > -1:
                newTag = app[1]
                oldTag = app[2]
                break
            
        if len(newTag) > 1:
            update_ec2tag(client, oldTag, newTag)
        else:
            print('target not ')

    return {
        'statusCode': 200,
        'body': json.dumps('OK')
    }

 
#ec2タグ更新
def update_ec2tag(client, oldTag, newTag):
    print(str(newTag))

    newTags = [{'Key': 'Project', 'Value': newTag}]
    
    instances = client.instances.filter(
        Filters = [{'Name': 'tag:Project', 'Values': [oldTag]}]
    )

    for instance in instances:
        hasTag = 0
        for tag in instance.tags:
            if tag['Key'] == 'Project': #調整対象タグ
                hasTag += 1
            elif tag['Key'] == 'elasticbeanstalk:environment-id': #Elastic Beanstalkのインスタンのみにしたい
                hasTag += 1

        if hasTag == 2:
            client.create_tags(
                Resources=[instance.instance_id],
                Tags=newTags
            )

            #print(instance.instance_id)
            update_voltag(instance, newTags)

#volumeタグ更新
def update_voltag(instance, ptags):
    instId = instance.instance_id
    print(instId)
    
    for vol in instance.volumes.all():
        print('Updating tags for {}'.format(vol.id))
        vol.create_tags(Tags=ptags)
```

## CloudWatch Events Rule
 * 名前: EbUpdateEnvironment -> Lambdaのトリガーに追加
 * イベントパターン
 
```json
{
  "source": [
    "aws.elasticbeanstalk"
  ],
  "detail-type": [
    "AWS API Call via CloudTrail"
  ],
  "detail": {
    "eventSource": [
      "elasticbeanstalk.amazonaws.com"
    ],
    "eventName": [
      "UpdateEnvironment"
    ]
  }
}
```

#おまけ
イベントトリガーをSQSにすると

```python
def lambda_handler(event, context):
    for record in event['Records']:
        pval = record["body"] #ここにElastic Beanstalkのアプリ名
        print(str(pval))

```

イベント発行
例え、zabbixで環境更新を検知したとしてアクションで設定
Elastic Beanstalkのデプロイ -> zabbixでアイテムで更新検知＆アクション（SQSキュー生成） -> Lambda実行
zabbixアイテム例：https://qiita.com/mkawanee/items/66fb507ab9bd53638f58

```bash
/usr/bin/aws sqs send-message --queue-url https://sqs.ap-northeast-1.amazonaws.com/12345678/infra-modify-tag \
--region ap-northeast-1 --profile awsX --message-body {EVENT.NAME}
```

# 参照
 * https://docs.aws.amazon.com/cli/latest/reference/ec2/create-tags.html

 * https://gist.github.com/mlapida/931c03cce1e9e43f147b

#追記
CloudWatch EventでEBの環境名を拾って判断しているが、APIからはEnvironmentIdを使うため、環境名が入らない。EnvironmentNameをパラメータに追加すると、「'errorCode': 'InvalidParameterValueException'」になる。
とにかくログに環境名があればよいのでDescriptionに入れてみたらOK！

```C#:C#
var req = new UpdateEnvironmentRequest
{
    EnvironmentId = "e-xxxxxx",
    Description = "env-name-xxx",
    OptionSettings = {options}
};
```