![]() |
皆さまこんにちは。お元気ですか?
最近、暑すぎて頭が働かなくなっちゃいますよね。
でも、頭が働かなくてもCloudFormationテンプレートを作成したいことって、往々にしてありますよね。
そういうわけで、Amazon Q Developer CLIを使って頭を使わずにスタックを作成してみようというのが、今回の自由研究のテーマです。
Amazon Q Developer CLIが画像入力をサポート
2025年5月のアップデートにより、Amazon Q Developer CLIに「画像サポート機能」が実装され、画像ファイルをターミナルから直接入力できるようになりました。JPEG、PNG、WEBP、GIFの形式をサポートしており、また、1回のリクエストで最大10枚の画像をアップロード可能です。この機能を活用することで、視覚的なアイデアを文書やコードといった成果物に落としこむことが可能になります。
以下にご紹介する公式ブログでは、アーキテクチャ図からのIaC作成、ER図からのDBスキーマ作成、手描き図からの設計書作成、スクリーンショットからUIのモックアップ作成という4つのユースケースが紹介されています。
実際にやってみた
そこで今回は、手描きのアーキテクチャ図からYAML形式のCloudFormationテンプレートを出力させ、想定した振る舞いをするかを確かめてみます。なお、以降は読みやすさのため(&親しみを込めて)Amazon Q Developer CLIのことはQdevくんと呼ぶこととします。
準備:手書きのアーキテクチャ図
まずはQdevくんに読み込ませるサービス構成の絵を描きます。今回はこんなのを書いてみました(日本語を正しく読み込むか分からなかったので保険で英語にしました)。処理の流れはざっくり以下の通りです。
- S3にファイルをアップロード→イベント通知がEventBridgeへ渡される
- ファイルがCSV形式ならSNSを介してメールで通知。また、常にLambdaで処理してメタデータ情報をDynamoDBに格納する。
(あくまで構成図通りに一連のサービスを組み立てられるかのテストを目的としているため、上の構成が有用/望ましい構成であるかは一旦置いておきます。)
Amazon Q Devにテンプレートを生成させる
準備ができたら「test-diagram.jpeg」として保存し、後はQdevくんにお任せしましょう。
🤖 You are chatting with claude-4-sonnet > test-diagram.jpegを実現するためのCloudFormationテンプレートを作成して。図に載っていない詳細についてはAWSの ベストプラクティスに従うこととします。それでもなお確認が必要であれば、ユーザに質問してください > まず、test-diagram.jpegファイルを確認させていただきます。 🛠️ Using tool: fs_read (trusted) ⋮ ● Reading images: ■■■■/qdev/test-diagram.jpeg ✓ Successfully read image ⋮ ● Completed in 0.0s > 図を確認しました。この「Uploaded data process flow」を実現するCloudFormationテンプレートを作成します。 フローは以下のようになっています: 1. ユーザーがS3にCSVファイルをアップロード 2. S3イベントがEventBridgeをトリガー 3. EventBridgeがSNSに通知 4. SNSからEmailで通知 5. EventBridgeが常にLambda関数もトリガー 6. Lambda関数がメタデータをDynamoDBに保存 CloudFormationテンプレートを作成します: (略) ⋮ ↳ Purpose: データ処理フローのCloudFormationテンプレートを作成 Allow this action? Use 't' to trust (always allow) this tool for the session. [y/n/t]: > y Creating: ■■■■/qdev/data-process-flow.yaml ⋮ ● Completed in 0.2s > CloudFormationテンプレートを作成しました。このテンプレートは図に示されたデータ処理フローを実現します。 ## 主な機能 1. S3バケット: CSVファイルのアップロード先 2. EventBridge: S3イベントをルーティング 3. SNS: CSVファイルアップロード時のEmail通知 4. Lambda関数: 全ファイルのメタデータをDynamoDBに保存 5. DynamoDB: ファイルメタデータの永続化 ## AWSベストプラクティスの適用 • S3バケットの暗号化とバージョニング有効化 • パブリックアクセスブロック設定 • IAMロールの最小権限原則 • DynamoDBのポイントインタイム復旧有効化 • Lambda関数のタイムアウト設定 • リソース名の一意性確保 ## デプロイ方法 bash aws cloudformation create-stack \ --stack-name data-process-flow \ --template-body file://data-process-flow.yaml \ --parameters ParameterKey=NotificationEmail,ParameterValue=your-email@example.com \ --capabilities CAPABILITY_IAM テンプレートをデプロイする前に、NotificationEmailパラメータに実際のメールアドレスを指定してください。 デプロイ後、SNSサブスクリプションの確認メールが送信されます。 何か追加の設定や修正が必要でしたらお知らせください。
作成されたdata-process-flow.yaml
AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket}/*' # Lambda function for processing metadata MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print(f"Received event: {json.dumps(event)}") try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print(f"Successfully stored metadata for {object_key}") return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print(f"Error processing metadata: {str(e)}") raise e # EventBridge Rule for S3 CSV uploads to SNS S3ToSNSRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-sns' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !Ref DataProcessNotificationTopic Id: SNSTarget RoleArn: !GetAtt EventBridgeToSNSRole.Arn # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus'
ほとんど追加の説明を渡さずに依頼を投げたので、テンプレートを作成する前に何回か確認の往復があることを予想していたのですが、意外とあっさり作成してくれました。
- PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*'
(よく見るとLambdaの実行ロールの中でs3:GetObjectMetadataという存在しないIAMアクションを許可してしまっているようですが、その辺はご愛嬌としておきましょう。)
生成されたテンプレートからスタックを起動してみる
テンプレートができたので次はこれをCloudFormationにアップロードします。
通知先のメールアドレス部分がパラメータになっているので、自分のメールアドレスを入力します。あとは「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックを入れること以外はデフォルトの設定のまま作成に進み、しばらく待ちます。
……スタックの作成を待っていたら、途中でロールバックしてしまいました。見ると、次のようなエラーが出ています。
🤖 You are chatting with claude-4-sonnet
> 作ってもらったdata-process-flow.yamlを展開したら次のようなエラーが出てCREATE_FAILEDになりました。
Resource handler returned message: "Resource q-image-stack-20250822-data-upload-111111111111/*
must be in ARN format or "*".(Service: Iam, Status Code: 400, Request ID: ****-****-****-****-************)
(SDK Attempt Count: 1) (RequestToken: ****-****-****-****-************, HandlerErrorCode: InvalidRequest)
適切にテンプレートファイルを修正してくれる?
> エラーメッセージを見ると、IAMポリシーでリソースARNの形式が正しくないことが原因です。S3バケットのリソース指定で /* を使う場合は、
完全なARN形式にする必要があります。
まず現在のテンプレートファイルを確認して、適切に修正しましょう。
> 問題を特定しました。LambdaExecutionRoleのS3Accessポリシーで、リソースARNの形式が正しくありません。!Sub '${DataUploadBucket}/*'
は正しいARN形式ではありません。S3バケットのARNは arn:aws:s3:::bucket-name/* の形式である必要があります。
テンプレートを修正します:
⋮
● Completed in 0.0s
> 修正が完了しました。主な変更点は以下の通りです:
1. S3リソースARNの修正: !Sub '${DataUploadBucket}/*' を !Sub '${DataUploadBucket.Arn}/*' に変更しました。
これにより、正しいS3バケットのARN形式(arn:aws:s3:::bucket-name/*)が生成されます。
2. ListBucket権限の追加: S3バケット自体への s3:ListBucket 権限も追加しました。これはLambda関数がバケット内の
オブジェクトにアクセスする際に必要になる場合があります。
これで、IAMポリシーのリソースARNが正しい形式になり、CloudFormationスタックのデプロイが成功するはずです。
修正されたテンプレートを使って再度デプロイしてみてください。
修正されたdata-process-flow.yaml
AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' - Effect: Allow Action: - s3:ListBucket Resource: !GetAtt DataUploadBucket.Arn # Lambda function for processing metadata MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print(f"Received event: {json.dumps(event)}") try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print(f"Successfully stored metadata for {object_key}") return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print(f"Error processing metadata: {str(e)}") raise e # EventBridge Rule for S3 CSV uploads to SNS S3ToSNSRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-sns' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !Ref DataProcessNotificationTopic Id: SNSTarget RoleArn: !GetAtt EventBridgeToSNSRole.Arn # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus'
再度アップロードして今度は無事、デプロイまで完了することができました。SNSから登録したアドレスにメールが来るので、承認をしておきます。
テスト:データの作成もAmazon Qに任せよう
それでは、ちゃんと動くかテストしてみましょう。ダミーデータが必要になりますが、むろん、これもQdevくんに作ってもらいます。
> data-process-flow.yamlをテストしたい。testフォルダを作って、S3にアップロードするファイル(CSV, HTML, TXT)を作って > data-process-flow.yamlファイルをテストするために、testフォルダを作成してテスト用のファイルを準備しましょう。 (略) > CloudFormationテンプレートの内容を確認しました。このテンプレートはS3にファイルがアップロードされると、EventBridge経由 でLambda関数とSNS(CSVファイルの場合)に通知する仕組みです。 testフォルダを作成して、CSV、HTML、TXTファイルのサンプルを作成しましょう。 (略) > 完璧です!testフォルダに以下のテストファイルを作成しました:
作成されたバケットにこれらのファイルをアップロードすると、程なくして以下のようなメールが2通届きました。確かに、CSVファイルのそれぞれについてアップロード通知を送ってきてくれています。

おまけ
せっかくなのでもう少し遊んでみましょう。
機能の追加
ただ、上のメール通知はJSON形式のメッセージがそのままメールの本文に書かれているだけで、正直可読性は低いです。さらにおねだりをしてフォーマットされたメールが飛ぶようにしてもらいましょう。
> data-process-flow.yamlを更新して、メール送信される通知の本文の可読性を上げてくれる? > data-process-flow.yamlを更新して、SNS通知の本文をより読みやすくするために、Lambda関数を追加してメール通知をフォーマットしましょう。 (略) > 完璧です!data-process-flow.yamlを更新して、メール通知の可読性を大幅に向上させました。
機能を追加したdata-process-flow.yaml
AWSTemplateFormatVersion: '2010-09-09' Description: 'Uploaded data process flow infrastructure' Parameters: NotificationEmail: Type: String Description: Email address for notifications AllowedPattern: ^[^\s@]+@[^\s@]+\.[^\s@]+$ ConstraintDescription: Must be a valid email address Resources: # S3 Bucket for data uploads DataUploadBucket: Type: AWS::S3::Bucket Properties: BucketName: !Sub '${AWS::StackName}-data-upload-${AWS::AccountId}' NotificationConfiguration: EventBridgeConfiguration: EventBridgeEnabled: true PublicAccessBlockConfiguration: BlockPublicAcls: true BlockPublicPolicy: true IgnorePublicAcls: true RestrictPublicBuckets: true BucketEncryption: ServerSideEncryptionConfiguration: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 VersioningConfiguration: Status: Enabled # EventBridge Custom Bus DataProcessEventBus: Type: AWS::Events::EventBus Properties: Name: !Sub '${AWS::StackName}-data-process-bus' # SNS Topic for notifications DataProcessNotificationTopic: Type: AWS::SNS::Topic Properties: TopicName: !Sub '${AWS::StackName}-data-process-notifications' DisplayName: Data Process Notifications # SNS Subscription for email notifications EmailSubscription: Type: AWS::SNS::Subscription Properties: Protocol: email TopicArn: !Ref DataProcessNotificationTopic Endpoint: !Ref NotificationEmail # DynamoDB Table for metadata storage MetadataTable: Type: AWS::DynamoDB::Table Properties: TableName: !Sub '${AWS::StackName}-file-metadata' BillingMode: PAY_PER_REQUEST AttributeDefinitions: - AttributeName: fileKey AttributeType: S - AttributeName: uploadTimestamp AttributeType: S KeySchema: - AttributeName: fileKey KeyType: HASH - AttributeName: uploadTimestamp KeyType: RANGE StreamSpecification: StreamViewType: NEW_AND_OLD_IMAGES PointInTimeRecoverySpecification: PointInTimeRecoveryEnabled: true # IAM Role for Lambda function LambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: DynamoDBAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - dynamodb:PutItem - dynamodb:GetItem - dynamodb:UpdateItem Resource: !GetAtt MetadataTable.Arn - PolicyName: S3Access PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectMetadata Resource: !Sub '${DataUploadBucket.Arn}/*' - Effect: Allow Action: - s3:ListBucket Resource: !GetAtt DataUploadBucket.Arn - PolicyName: SNSAccess PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda function for formatting notifications NotificationFormatterFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-notification-formatter' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: SNS_TOPIC_ARN: !Ref DataProcessNotificationTopic Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse sns = boto3.client('sns') s3 = boto3.client('s3') def lambda_handler(event, context): print("Received event: {}".format(json.dumps(event))) try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Format file size file_size = response['ContentLength'] if file_size < 1024: size_str = "{} bytes".format(file_size) elif file_size < 1024 * 1024: size_str = "{:.1f} KB".format(file_size / 1024) else: size_str = "{:.1f} MB".format(file_size / (1024 * 1024)) # Format timestamp upload_time = response['LastModified'].strftime('%Y-%m-%d %H:%M:%S (UTC)') # Create formatted message subject = "CSV File Uploaded - {}".format(object_key) message_parts = [ "CSV File Upload Notification", "", "File Information", "================", "File Name - {}".format(object_key), "Bucket - {}".format(bucket_name), "File Size - {}".format(size_str), "Content Type - {}".format(response.get('ContentType', 'unknown')), "Upload Time - {}".format(upload_time), "", "Processing Status", "=================", "File upload completed", "Metadata processing in progress", "Data analysis preparation", "", "Next Steps", "==========", "1. File content validation", "2. Data quality check", "3. Processing result notification", "", "This is an automated message from the Data Processing System v2.1" ] message = "\n".join(message_parts) # Send formatted notification sns.publish( TopicArn=os.environ['SNS_TOPIC_ARN'], Subject=subject, Message=message ) print("Successfully sent formatted notification for {}".format(object_key)) return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Notification sent successfully', 'fileKey': object_key }) } except Exception as e: print("Error sending notification: {}".format(str(e))) raise e MetadataProcessorFunction: Type: AWS::Lambda::Function Properties: FunctionName: !Sub '${AWS::StackName}-metadata-processor' Runtime: python3.11 Handler: index.lambda_handler Role: !GetAtt LambdaExecutionRole.Arn Timeout: 60 Environment: Variables: METADATA_TABLE: !Ref MetadataTable Code: ZipFile: | import json import boto3 import os from datetime import datetime import urllib.parse dynamodb = boto3.resource('dynamodb') s3 = boto3.client('s3') table = dynamodb.Table(os.environ['METADATA_TABLE']) def lambda_handler(event, context): print("Received event: {}".format(json.dumps(event))) try: # Parse the EventBridge event detail = event['detail'] bucket_name = detail['bucket']['name'] object_key = urllib.parse.unquote_plus(detail['object']['key']) # Get object metadata from S3 response = s3.head_object(Bucket=bucket_name, Key=object_key) # Prepare metadata for DynamoDB metadata = { 'fileKey': object_key, 'uploadTimestamp': datetime.utcnow().isoformat(), 'bucketName': bucket_name, 'fileSize': response['ContentLength'], 'contentType': response.get('ContentType', 'unknown'), 'lastModified': response['LastModified'].isoformat(), 'etag': response['ETag'].strip('"'), 'processedAt': datetime.utcnow().isoformat() } # Store metadata in DynamoDB table.put_item(Item=metadata) print("Successfully stored metadata for {}".format(object_key)) return { 'statusCode': 200, 'body': json.dumps({ 'message': 'Metadata processed successfully', 'fileKey': object_key }) } except Exception as e: print("Error processing metadata: {}".format(str(e))) raise e # EventBridge Rule for S3 CSV uploads to notification formatter S3ToNotificationRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-csv-to-notification' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket object: key: - suffix: .csv State: ENABLED Targets: - Arn: !GetAtt NotificationFormatterFunction.Arn Id: NotificationFormatterTarget # EventBridge Rule for S3 uploads to Lambda (always) S3ToLambdaRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-to-lambda' EventBusName: !Ref DataProcessEventBus EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt MetadataProcessorFunction.Arn Id: LambdaTarget # IAM Role for EventBridge to SNS EventBridgeToSNSRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: SNSPublishPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - sns:Publish Resource: !Ref DataProcessNotificationTopic # Lambda permission for EventBridge (Notification Formatter) NotificationFormatterInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref NotificationFormatterFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToNotificationRule.Arn # Lambda permission for EventBridge (Metadata Processor) LambdaInvokePermission: Type: AWS::Lambda::Permission Properties: FunctionName: !Ref MetadataProcessorFunction Action: lambda:InvokeFunction Principal: events.amazonaws.com SourceArn: !GetAtt S3ToLambdaRule.Arn # EventBridge Rule to route S3 events to custom bus S3EventRule: Type: AWS::Events::Rule Properties: Name: !Sub '${AWS::StackName}-s3-event-router' EventPattern: source: - aws.s3 detail-type: - Object Created detail: bucket: name: - !Ref DataUploadBucket State: ENABLED Targets: - Arn: !GetAtt DataProcessEventBus.Arn Id: CustomBusTarget RoleArn: !GetAtt EventBridgeRouterRole.Arn # IAM Role for EventBridge routing EventBridgeRouterRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: events.amazonaws.com Action: sts:AssumeRole Policies: - PolicyName: EventBridgePolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - events:PutEvents Resource: !GetAtt DataProcessEventBus.Arn Outputs: S3BucketName: Description: Name of the S3 bucket for data uploads Value: !Ref DataUploadBucket Export: Name: !Sub '${AWS::StackName}-S3Bucket' SNSTopicArn: Description: ARN of the SNS topic for notifications Value: !Ref DataProcessNotificationTopic Export: Name: !Sub '${AWS::StackName}-SNSTopic' DynamoDBTableName: Description: Name of the DynamoDB table for metadata Value: !Ref MetadataTable Export: Name: !Sub '${AWS::StackName}-MetadataTable' LambdaFunctionName: Description: Name of the Lambda function for metadata processing Value: !Ref MetadataProcessorFunction Export: Name: !Sub '${AWS::StackName}-LambdaFunction' NotificationFormatterFunctionName: Description: Name of the Lambda function for notification formatting Value: !Ref NotificationFormatterFunction Export: Name: !Sub '${AWS::StackName}-NotificationFormatterFunction' EventBusName: Description: Name of the custom EventBridge bus Value: !Ref DataProcessEventBus Export: Name: !Sub '${AWS::StackName}-EventBus'
フォーマット用のLambda関数を追加してくれました。
更新されたテンプレートでスタックを更新し、再度CSVファイルをアップロードすると今度は以下のように読みやすい形で通知が送られてきました。
CSV File Upload Notification
File Information
================
File Name – sample_data-2.csv
Bucket – q-image-data-flow-data-upload-111111111111
File Size – 610 bytes
Content Type – text/csv
Upload Time – 2025-08-16 06:03:20 (UTC)
Processing Status
=================
File upload completed
Metadata processing in progress
Data analysis preparation
アーキテクチャ図を清書してもらう
機能の追加によってLambdaが追加されたので、作成された添付ファイルをもとに改めて構成図を作成させてみます。
実際の指示は履歴が消えてしまったのですが、プロンプトを作成するにあたっては下記記事を参考に最新版のアイコンを参照するなどを指示に含めました。
作成された構成図は以下の通りです。かなり自分のイメージ通りに出力してくれた印象です。
おわりに
いかがでしたか?そこそこ複雑なテンプレートでもちょっとした対話で簡単に作成することができ、今日の生成AIの威力を身にしみて感じる自由研究となりました。
ChatGPTが出始めの頃、コード生成をさせるにも途中で出力が止まり、「続きを書いて」を繰り返して指示して出力されたコードを何度も修正してやっと動くようになる、というのが遠い昔に感じます。
これで夏休みの宿題は終わりです。皆様くれぐれも暑さには気を付けてお過ごしください。