AWS Lambda + Eventbridge でインスタンス名からインスタンスIDを取得してタグを作成・削除する

lambdaの練習がてら作成した関数のメモです。
出来る限りコメントを入れて初心者でもある程度の処理を把握できるようにしていますので、
何かと応用に使えるかと思います。

作成したのは対象インスタンスのタグを変更する関数となります。


##  lambda用IAMロール

"ec2:Start*" と "ec2:Stop*" は前回作成した自動起動/停止用に付与したものです。
今回は関係ないので必要ありませんが、IAMロールを使い回せるのでこのままにしてます。
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:Start*",
                "ec2:Stop*",
                "ec2:DescribeInstances",
                "ec2:DescribeTags",
                "ec2:CreateTags",
                "ec2:DeleteTags"
            ],
            "Resource": "*"
        }
    ]
}
```

##  lambda関数

Eventbridgeから送信される内容で
"tag_attach_detach": "Attach" だった場合はEventbridgeで指定した対象インスタンスのタグを作成
"tag_attach_detach": "Attach" 以外の場合はEventbridgeで指定した対象インスタンスのタグを削除
といった関数になります。
```
import boto3

# インスタンス名からインスタンスIDを取得する関数
def get_instance_id_by_name(instance_name):
    ec2_client = boto3.client('ec2')
    response = ec2_client.describe_instances(
        Filters=[
            {'Name': 'tag:Name', 'Values': [instance_name]}
        ]
    )
    if response['Reservations']:
        return response['Reservations'][0]['Instances'][0]['InstanceId']
    else:
        return None

def lambda_handler(event, context):
    # イベントから "tag_attach_detach" の値を取得して flag に代入
    flag = event['tag_attach_detach']
    # インスタンス名、キー、値も取得して変数に代入
    ec2_names = event['ec2_names']
    tag_key = event['tag_key']
    tag_value = event['tag_value']

    # EC2サービスのクライアントオブジェクトを作成、EC2サービスのAPI操作を行う事ができる
    client = boto3.client('ec2')

    # もし flag が "Attach" の場合、ec2_names 内のリストを name に代入して繰り返し処理を実行
    if flag == "Attach":
        for name in ec2_names:
            # インスタンス名からインスタンスIDを取得
            instance_id = get_instance_id_by_name(name)
            # instance_id が有効な時処理を実行
            if instance_id:
                # client.create_tags メソッドを呼び出し、Resources パラメータに指定されたIDのリストを対象にタグを付与
                response = client.create_tags(
                    Resources=[instance_id],
                    Tags=[{'Key': tag_key, 'Value': tag_value}]
                )
                print(f"Tag '{tag_key}:{tag_value}' attached to instance '{name}'")
            else:
                print(f"Instance with name '{name}' not found.")

    # もし flag が "Attach" 以外の場合、ec2_names 内のリストを name に代入して繰り返し処理を実行
    else:
        for name in ec2_names:
            # インスタンス名からインスタンスIDを取得
            instance_id = get_instance_id_by_name(name)
            # instance_id が有効な時処理を実行
            if instance_id:
                # client.delete_tags メソッドを呼び出し、Resources パラメータに指定されたIDのリストを対象にタグの削除
                response = client.delete_tags(
                    Resources=[instance_id],
                    Tags=[{'Key': tag_key, 'Value': tag_value}]
                )
                print(f"Tag '{tag_key}:{tag_value}' deleted to instance '{name}'")
            else:
                print(f"Instance with name '{name}' not found.")
```

##  Eventbridgeから送信するイベントJSON

関数のテスト時も以下を使用してます。

タグを作成する用のEventbridge
```
{
  "tag_attach_detach": "Attach",
  "ec2_names": [
    "test-web01",
    "test-web02"
  ],
  "tag_key": "state",
  "tag_value": "run"
}
```

タグを削除する用のEventbridge
※ "Attach" 以外ならなんでもOKです。
```
{
  "tag_attach_detach": "Detach",
  "ec2_names": [
    "test-web01",
    "test-web02"
  ],
  "tag_key": "state",
  "tag_value": "run"
}
```

## オマケ lambdaで指定した特定のタグの値が "true" であれば "false" に、"false" であれば "true" に変更するlambda関数

```
import boto3

def lambda_handler(event, context):
    tag_key = "tag-switch-test"
    tag_value = "true"
    
    # EC2サービスのクライアントオブジェクトを作成、EC2サービスのAPI操作を行う事ができる
    ec2_client = boto3.client('ec2')
    
    # インスタンスの情報を取得
    response = ec2_client.describe_instances()
    # 取得した情報の中からインスタンスのリストを取得
    instances = response['Reservations']
    
    for reservation in instances:
        for instance in reservation['Instances']:
            # 'Tags'キーが存在するか確認
            if 'Tags' in instance:
                # インスタンスのタグを取得して tags に代入
                tags = instance['Tags']
                for tag in tags:
                    # もしタグのキーが "tag-switch-test" であれば処理実行
                    if tag['Key'] == tag_key:
                        # タグの値が "true" の場合は "false" に、"true" 以外の場合は "true" に変更する
                        if tag['Value'] == "true":
                            new_value = "false"
                        else:
                            new_value = "true"
                        
                        # 対象タグの値を更新
                        ec2_client.create_tags(
                            Resources=[instance['InstanceId']],
                            Tags=[{'Key': tag_key, 'Value': new_value}]
                        )
                        
                        print(f"Tag '{tag_key}' updated for instance '{instance['InstanceId']}' to '{new_value}'")
                        break
            else:
                print(f"No tags found for instance '{instance['InstanceId']}'")

```

この関数の EventBridge は起動だけさせればいいのでイベントJSONは以下のように設定
```
{
  "detail-type": "Tag switch test",
  "source": "aws.ec2",
  "detail": {}
}
``` 

##  オマケ2 対象インスタンスのタグの値を Eventbridge で指定されたインスタンス名、タグのキー、値に変更する

lambda
```
import boto3

# インスタンス名からインスタンスIDを取得する関数
def get_instance_id_by_name(instance_name):
    ec2_client = boto3.client('ec2')
    response = ec2_client.describe_instances(
        Filters=[
            {'Name': 'tag:Name', 'Values': [instance_name]}
        ]
    )
    if response['Reservations']:
        return response['Reservations'][0]['Instances'][0]['InstanceId']
    else:
        return None

def lambda_handler(event, context):
    # Eventbridgeから送信されたイベントからインスタンス名、タグのキー、タグの値を取得
    instance_names = event['instance_names']
    tag_key = event['tag_key']
    tag_value = event['tag_value']

    # EC2サービスのクライアントオブジェクトを作成、EC2サービスのAPI操作を行う事ができる
    ec2_client = boto3.client('ec2')

    for instance_name in instance_names:
        # "get_instance_id_by_name" 関数を使用してインスタンス名からインスタンスIDを取得
        instance_id = get_instance_id_by_name(instance_name)
        if instance_id:
            # 対象インスタンスのタグの値を、送信されたイベントで指定されたタグの値に変更する
            response = ec2_client.create_tags(
                Resources=[instance_id],
                Tags=[{'Key': tag_key, 'Value': tag_value}]
            )
            print(f"Tag '{tag_key}:{tag_value}' attached to instance '{instance_name}'")
        else:
            print(f"Instance with name '{instance_name}' not found.")

```

Eventbridge イベントJSON
```
{
  "instance_names": [
    "対象インスタンス名",
    "対象インスタンス名",
    "対象インスタンス名"
  ],
  "tag_key": "指定したいタグのキー",
  "tag_value": "指定したいタグの値"
}
```

## オマケ3 Eventbridgeから受け取ったタグキー、リストを元にインスタンスのタグを変更　

1. Eventbridgeから受け取ったタグキーが設定されているインスタンスをリスト
2. Eventbridgeから受け取ったリストに含まれているインスタンスの特定タグキーの値を false → true に変更
3. Eventbridgeから受け取ったリストに含まれていないインスタンスの特定タグキーの値を true → false に変更
```
import boto3

def lambda_handler(event, context):
    # EventBridge から instance_names と tag_key 受け取る
    instance_names = event.get('instance_names')
    tag_key = event.get('tag_key')

    # EC2サービスのクライアントオブジェクトを作成、EC2サービスのAPI操作を行う
    ec2_client = boto3.client('ec2')

    # フィルターをかけて tag-key と一致するタグがついているインスタンスの情報を取得
    response = ec2_client.describe_instances(
        Filters=[
            {'Name': 'tag-key', 'Values': [tag_key]}
        ]        
    )
                    
    # 入れ子構造から情報を取得していく
    for reservation in response['Reservations']:
        for instance in reservation['Instances']:
            instance_id = instance['InstanceId']
            # タグが存在していれば動作
            if 'Tags' in instance:
                # タグ情報取得して instance_tag に代入
                for instance_tag in instance['Tags']:
                    # Name タグを検索し、タグの値が instance_names に含まれているインスタンス名と一致している時
                    if instance_tag['Key'] == 'Name' and instance_tag['Value'] in instance_names:
                        # タグ情報取得して tag に代入
                        for tag in instance['Tags']:
                            # 対象の中で tag_key と一致しているタグを検索、一致しているタグが存在していて、値が false であるインスタンスを対象にタグ変更
                            if tag['Key'] == tag_key and tag['Value'] == 'false':
                                tag['Value'] = 'true'
                            else:
                                print(f"Tag to not update '{tag['Key']}'")
                        
                        # 変更されたタグ情報に更新
                        ec2_client.create_tags(
                            Resources=[instance['InstanceId']],
                            Tags=instance['Tags']
                        )
                        
                        print(f"Tag '{tag_key}' updated for instance '{instance['InstanceId']}' to true")
                        
                        # タグが更新された時ループ終了
                        break
                    
                    # Name タグを検索し、タグの値が instance_names に含まれているインスタンス名と一致していない時
                    elif instance_tag['Key'] == 'Name' and instance_tag['Value'] not in instance_names:
                        # タグ情報取得して tag に代入
                        for tag in instance['Tags']:
                            # 対象の中で tag_key と一致しているタグのキーを検索、一致しているタグが存在していて、値が true であるインスタンスを対象にタグ変更
                            if tag['Key'] == tag_key and tag['Value'] == 'true':
                                tag['Value'] = 'false'
                            else:
                                print(f"Tag to not update '{tag['Key']}'")
                                
                        # 変更されたタグ情報に更新
                        ec2_client.create_tags(
                            Resources=[instance['InstanceId']],
                            Tags=instance['Tags']
                        )
                        
                        print(f"Tag '{tag_key}' updated for instance '{instance['InstanceId']}' to false")
                        
                        # タグが更新された時ループ終了
                        break
                    
    print(f"Tag Update Complete for tag '{tag_key}'.")
    
    

# EventBridge から instance_names と tag_key 受け取る

# 対象インスタンスを instance_names に含まれているインスタンスのみに絞る
# tag_key とタグのキーが一致している値を変更 　false → true 
# タグ情報更新

# 対象インスタンスを instance_names に含まれていないインスタンスのみに絞る
# tag_key とタグのキーが一致している値を変更 　true → false 
# タグ情報更新
```

イベントJSON
```
{
  "instance_names": [
    "対象インスタンス名",
    "対象インスタンス名",
    "対象インスタンス名"
  ],
  "tag_key": "指定したいタグのキー"
}
```

参考記事

https://dev.classmethod.jp/articles/create-delete-tag-with-lambda/

https://tech.kurojica.com/archives/51402/