本記事は 夏休みクラウド自由研究2025 8/22付の記事です。 |
皆さまこんにちは。お元気ですか?
最近、暑すぎて頭が働かなくなっちゃいますよね。
でも、頭が働かなくても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が出始めの頃、コード生成をさせるにも途中で出力が止まり、「続きを書いて」を繰り返して指示して出力されたコードを何度も修正してやっと動くようになる、というのが遠い昔に感じます。
これで夏休みの宿題は終わりです。皆様くれぐれも暑さには気を付けてお過ごしください。





