EventBridge+LambdaによるAMI作成通知を実装するCloudFormationテンプレート作成

EventBridge+LambdaによるAMI作成通知を実装するCloudFormationテンプレートを作成しました。

## 今回参考にさせていただいた記事

[AMIの作成時間が知りたかったのでSNS Topicで通知させてみた](https://dev.classmethod.jp/articles/ami-create-time-sns/)

## CloudFormationテンプレートによる実装概要

CloudFormationテンプレートでスタックを作成すると下記のような構成が出来上がります。
CloudTrailについてはCloudFormationテンプレートに含んでいませんので別途有効化が必要となります。

![概要.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74171/fcff9e85-2cee-a33b-a941-8ebf759737fc.png)

この構成は下記のCloudFormationテンプレートで構築しています。

- AMI作成時のSNS通知用CloudFormationテンプレート(notify-sns-createimage.yml)
    - CloudTrail有効化を前提
    - SNSの通知先指定メールアドレス宛に届く確認メールは別途対応前提

## AMI作成時のSNS通知用CloudFormationテンプレート(notify-sns-createimage.yml)

AMI作成時にCreateImageというAWS API Call が呼び出されたときにEventBridge経由にてSNS通知(Email)するものです。Lambdaでは下記の処理を行います。

- AMI作成時にSNS通知

```ruby:メールフォーマット
件名：
Start CreateImage (対象インスタンスID(対象AMI名))

本文：
Start CreateImage

Region:対象リージョン
InstanceID:対象インスタンスID
AMI Name:対象AMI名
AMI ID:対象AMI ID
```

AMI作成時のSNS通知用CloudFormationテンプレート(notify-sns-createimage.yml)の内容は下記となります。
CloudFormationスタック作成時に入力が必要となるパラメータを説明します。

- EventName ：Event名（初期値：notify-sns-createimage_event）
- FunctionName：Lambda関数名（初期値：notify-sns-createimage）
- RoleName : Lambda関数で利用するIAMロール名（初期値：notify-sns-createimage）
- SNSSubscriptionEmail : SNSのエンドポイント(送信先メールアドレス) (初期値：xxxx@example.com)
    - **必ず受信可能なメールアドレスに変更してください**
- SNSTopicName : SNS Topic名（初期値：notify-sns-createimage_event）

![parameter.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/74171/122db17e-cec9-12c0-faa1-1efc4b9fe542.png)

```ruby:notify-sns-createimage.yml
AWSTemplateFormatVersion: 2010-09-09
Parameters:
  RoleName:
    Type: String
    Default: notify-sns-createimage
  FunctionName:
    Type: String
    Default: notify-sns-createimage
  EventName:
    Type: String
    Default: notify-sns-createimage_event
  SNSTopicName:
    Type: String
    Default: notify-sns-createimage
  SNSSubscriptionEmail:
    Type: String
    Default: xxxx@example.com
Resources:
  LambdaRole:
    Type: 'AWS::IAM::Role'
    Properties:
      RoleName: !Sub '${RoleName}'
      AssumeRolePolicyDocument:
        Statement:
          - Action:
              - 'sts:AssumeRole'
            Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
        Version: 2012-10-17
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/AWSLambdaExecute'
      Path: /
  Lambda:
    Type: 'AWS::Lambda::Function'
    DependsOn: LambdaRole
    Properties:
      Code:
        ZipFile: |
          import os
          import boto3
          
          def lambda_handler(event, context):
              awsRegion = event['detail']['awsRegion']
              name = event['detail']['requestParameters']['name']
              instanceId = event['detail']['requestParameters']['instanceId']
              imageId = event['detail']['responseElements']['imageId']
          
              client = boto3.client('sns')
          
              sns_response = client.publish(
                  TopicArn= os.environ['SNS_TopicName'] ,
                  Message=('Start CreateImage\n\nRegion:'
                  + awsRegion
                  + '\nInstanceID:'
                  + instanceId
                  + '\nAMI Name:'
                  + name
                  + '\nAMI ID:'
                  + imageId
                  + '\n\n'),
                            Subject= 'Start CreateImage  (' + instanceId + '(' + name + '))'
              )
          
              return sns_response
      Description: SNS notification when CreateImage is executed
      FunctionName: !Sub ${FunctionName}
      Handler: index.lambda_handler
      MemorySize: 128
      Role: !GetAtt LambdaRole.Arn
      Runtime: python3.6
      Timeout: 300
      Environment:
        Variables:
          'AWS_ACCOUNT': !Sub ${AWS::AccountId}
          'SNS_TopicName': !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${SNSTopicName}
      Tags:
        - Key: Name
          Value: !Sub ${RoleName}
        - Key: CloudformationArn
          Value: !Ref 'AWS::StackId'
  NotifyMailSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: !Sub ${SNSTopicName}
      TopicName: !Sub ${SNSTopicName}
      Subscription:
        - Endpoint: !Sub ${SNSSubscriptionEmail}
          Protocol: email
  NotifyMailSNSPolicy:
    Type: AWS::SNS::TopicPolicy
    DependsOn: NotifyMailSNSTopic
    Properties:
      PolicyDocument:
        Id: MyTopicPolicy
        Version: '2012-10-17'
        Statement:
        - Sid: NotifyMailSNSPolicy
          Effect: Allow
          Principal:
           AWS: !GetAtt LambdaRole.Arn
          Action:
          - sns:Publish
          Resource: !Ref NotifyMailSNSTopic
      Topics:
        - !Ref NotifyMailSNSTopic
  Rule:
    Type: 'AWS::Events::Rule'
    Properties:
      Description: !Sub ${EventName}
      Name: !Sub ${EventName}
      EventPattern: 
        source: 
          - "aws.ec2"
        detail-type: 
          - "AWS API Call via CloudTrail"
        detail: 
          eventSource: 
            - "ec2.amazonaws.com"
          eventName: 
            - "CreateImage"
      State: "ENABLED"
      Targets:
        - Arn: !GetAtt 
            - Lambda
            - Arn
          Id: lambda
  LambdaEvent:
    Type: 'AWS::Lambda::Permission'
    Properties:
      Action: 'lambda:InvokeFunction'
      FunctionName: !Ref Lambda
      Principal: events.amazonaws.com
      SourceArn: !GetAtt 
        - Rule
        - Arn
```