Using Kiro specs to build IaC out of a shell script
In this post, I am continuing my Kiro experiments to produce better specs outcomes. I am doing so using my demo app repository (https://github.com/aws-containers/votingapp). This is the same application I have used in my previous blog post Using Q CLI to validate the implementation of Kiros specs.
For background, when I built this application I was in a bit of a rush and I did not have time to build a proper IaC setup for the pre-requsites of its deployment (including the DynamoDB table and its initialization). Particularly the initialization of the table with starting values for the counts of vote was challenging as it required custom resources (e.g. AWS Lambda functions) in a CFN template. Eventually -and embarrassingly- I resorted to authoring a quick shell script that would call the AWS CLI to do most of this setup work for the prerequisites. You can look at the script as it exists today here.
Four years in, as lazy as I am, that script is still there (hello tech debt!) and it's one of the main reasons why this demo can't really be used more broadly. A few months ago, I (briefly) tried to vibe code a solution out of this situation but I just gave up as I was not getting where I wanted to go (i.e. a single CFN template that does everything the script and its surroundings files do). I will admit that I could use more conviction there so it's not just vibe coding limitations, it was me being lazy to keep it on track towards my goal.
I have decided to attack the problem using Kiro and the specs workflow. But, in the spirit of increasing the odds of making the generation of IaC more successful, I wanted to introduce (or force) specific steps in my implementation plan that would guarantee higher odds of a successful IaC template deployment.
This is the prompt that I used to trigger the specs creation:
Create a Cloudformation template that implements all the requirements for running the application. Look into the /preparation folder to learn what this application requires and turn those files and the script into a single CFN template that does all that in a single stack. You should iteratively test that the deployment works properly and the application can start, connect to dynamodb. The validation should be that you can query the
getvotes
endpoint and verify that it works. You have access to a local AWS profile named "default" that gives you access to an account where you can deploy the stack. Do not validate the CFN template simply with local tests and mocks. Make sure you actually deploy it to the AWS account and you test the application against that backend.
The following is a graphical representation of the workflow I have forced:
In the Appendix A you can find the specs Kiro produced. Below you can find the CloudFormation template Kiro generated at the end of the specs workflow:
1AWSTemplateFormatVersion: '2010-09-09'
2Description: 'CloudFormation template for Voting App infrastructure - Creates DynamoDB table, IAM roles, and policies for App Runner deployment'
3
4# ============================================================================
5# VOTING APP INFRASTRUCTURE TEMPLATE
6# ============================================================================
7#
8# This CloudFormation template creates all necessary AWS infrastructure for
9# the Voting App, a simple REST API service for collecting restaurant votes.
10#
11# WHAT THIS TEMPLATE CREATES:
12# - DynamoDB table with PAY_PER_REQUEST billing for restaurant vote storage
13# - IAM role with least-privilege access for App Runner service
14# - Custom IAM policy for DynamoDB table access
15# - Lambda function for seeding initial restaurant data
16# - CloudFormation custom resource for automated data initialization
17#
18# DEPLOYMENT INSTRUCTIONS:
19# 1. Deploy this template using AWS CLI or Console:
20# aws cloudformation create-stack --stack-name votingapp-infrastructure \
21# --template-body file://cloudformation-template.yaml \
22# --capabilities CAPABILITY_NAMED_IAM
23#
24# 2. Wait for stack creation to complete:
25# aws cloudformation wait stack-create-complete --stack-name votingapp-infrastructure
26#
27# 3. Get stack outputs for App Runner configuration:
28# aws cloudformation describe-stacks --stack-name votingapp-infrastructure \
29# --query 'Stacks[0].Outputs'
30#
31# REQUIRED PERMISSIONS:
32# The deploying user/role must have permissions to:
33# - Create/manage DynamoDB tables
34# - Create/manage IAM roles and policies
35# - Create/manage Lambda functions
36# - Create/manage CloudFormation stacks
37#
38# CUSTOMIZATION:
39# - Modify Parameters section to change default values
40# - Update Environment parameter for different deployment stages
41# - Adjust DynamoDB table configuration as needed
42#
43# CLEANUP:
44# To remove all resources:
45# aws cloudformation delete-stack --stack-name votingapp-infrastructure
46#
47# ============================================================================
48
49Metadata:
50 AWS::CloudFormation::Interface:
51 ParameterGroups:
52 - Label:
53 default: "Application Configuration"
54 Parameters:
55 - TableName
56 - RoleName
57 - Environment
58 ParameterLabels:
59 TableName:
60 default: "DynamoDB Table Name"
61 RoleName:
62 default: "IAM Role Name"
63 Environment:
64 default: "Environment Tag"
65
66Parameters:
67 TableName:
68 Type: String
69 Default: 'votingapp-restaurants'
70 Description: 'Name of the DynamoDB table for storing restaurant votes'
71 AllowedPattern: '[a-zA-Z0-9_.-]+'
72 ConstraintDescription: 'Table name must contain only alphanumeric characters, hyphens, underscores, and periods'
73
74 RoleName:
75 Type: String
76 Default: 'votingapp-role'
77 Description: 'Name of the IAM role for App Runner service'
78 AllowedPattern: '[a-zA-Z0-9_+=,.@-]+'
79 ConstraintDescription: 'Role name must contain only alphanumeric characters and valid IAM role name characters'
80
81 Environment:
82 Type: String
83 Default: 'dev'
84 Description: 'Environment tag for resource identification and management'
85 AllowedValues:
86 - dev
87 - staging
88 - prod
89 ConstraintDescription: 'Environment must be dev, staging, or prod'
90
91Resources:
92 # DynamoDB Table for storing restaurant votes
93 DynamoDBTable:
94 Type: AWS::DynamoDB::Table
95 Properties:
96 TableName: !Ref TableName
97 AttributeDefinitions:
98 - AttributeName: name
99 AttributeType: S
100 KeySchema:
101 - AttributeName: name
102 KeyType: HASH
103 BillingMode: PAY_PER_REQUEST
104 Tags:
105 - Key: Environment
106 Value: !Ref Environment
107 - Key: Application
108 Value: 'votingapp'
109 - Key: ManagedBy
110 Value: 'CloudFormation'
111 PointInTimeRecoverySpecification:
112 PointInTimeRecoveryEnabled: true
113
114 # IAM Role for App Runner service
115 AppRunnerInstanceRole:
116 Type: AWS::IAM::Role
117 Properties:
118 RoleName: !Ref RoleName
119 AssumeRolePolicyDocument:
120 Version: '2012-10-17'
121 Statement:
122 - Effect: Allow
123 Principal:
124 Service: tasks.apprunner.amazonaws.com
125 Action: sts:AssumeRole
126 ManagedPolicyArns:
127 - 'arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess'
128 Tags:
129 - Key: Environment
130 Value: !Ref Environment
131 - Key: Application
132 Value: 'votingapp'
133 - Key: ManagedBy
134 Value: 'CloudFormation'
135
136 # Custom DynamoDB Policy with least-privilege access
137 DynamoDBAccessPolicy:
138 Type: AWS::IAM::Policy
139 Properties:
140 PolicyName: 'votingapp-ddb-policy'
141 PolicyDocument:
142 Version: '2012-10-17'
143 Statement:
144 - Sid: 'DynamoDBTableAccess'
145 Effect: Allow
146 Action:
147 - 'dynamodb:GetItem'
148 - 'dynamodb:PutItem'
149 - 'dynamodb:UpdateItem'
150 - 'dynamodb:DeleteItem'
151 - 'dynamodb:Query'
152 - 'dynamodb:Scan'
153 - 'dynamodb:BatchGetItem'
154 - 'dynamodb:BatchWriteItem'
155 Resource: !GetAtt DynamoDBTable.Arn
156 Roles:
157 - !Ref AppRunnerInstanceRole
158 DependsOn:
159 - AppRunnerInstanceRole
160 - DynamoDBTable
161
162 # Lambda execution role for data seeding function
163 LambdaExecutionRole:
164 Type: AWS::IAM::Role
165 Properties:
166 RoleName: !Sub '${RoleName}-lambda-execution'
167 AssumeRolePolicyDocument:
168 Version: '2012-10-17'
169 Statement:
170 - Effect: Allow
171 Principal:
172 Service: lambda.amazonaws.com
173 Action: sts:AssumeRole
174 ManagedPolicyArns:
175 - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
176 Tags:
177 - Key: Environment
178 Value: !Ref Environment
179 - Key: Application
180 Value: 'votingapp'
181 - Key: ManagedBy
182 Value: 'CloudFormation'
183
184 # Lambda policy for DynamoDB write access during data seeding
185 LambdaDynamoDBPolicy:
186 Type: AWS::IAM::Policy
187 Properties:
188 PolicyName: 'lambda-ddb-seeding-policy'
189 PolicyDocument:
190 Version: '2012-10-17'
191 Statement:
192 - Sid: 'DynamoDBWriteAccess'
193 Effect: Allow
194 Action:
195 - 'dynamodb:PutItem'
196 - 'dynamodb:GetItem'
197 - 'dynamodb:BatchWriteItem'
198 - 'dynamodb:DescribeTable'
199 Resource: !GetAtt DynamoDBTable.Arn
200 Roles:
201 - !Ref LambdaExecutionRole
202 DependsOn:
203 - LambdaExecutionRole
204 - DynamoDBTable
205
206 # Lambda function for seeding DynamoDB with initial restaurant data
207 DataSeedingFunction:
208 Type: AWS::Lambda::Function
209 Properties:
210 FunctionName: !Sub '${AWS::StackName}-data-seeding'
211 Runtime: python3.9
212 Handler: index.handler
213 Role: !GetAtt LambdaExecutionRole.Arn
214 Timeout: 60
215 MemorySize: 128
216 Description: 'Seeds DynamoDB table with initial restaurant data for voting app'
217 Code:
218 ZipFile: |
219 import json
220 import boto3
221 import logging
222 import cfnresponse
223 from botocore.exceptions import ClientError
224
225 # Configure logging
226 logger = logging.getLogger()
227 logger.setLevel(logging.INFO)
228
229 def handler(event, context):
230 """
231 Lambda function to seed DynamoDB table with initial restaurant data.
232 Handles CloudFormation custom resource lifecycle events.
233 """
234 logger.info(f"Received event: {json.dumps(event, default=str)}")
235
236 try:
237 # Extract parameters from CloudFormation event
238 request_type = event['RequestType']
239 resource_properties = event.get('ResourceProperties', {})
240 table_name = resource_properties.get('TableName')
241
242 if not table_name:
243 raise ValueError("TableName is required in ResourceProperties")
244
245 logger.info(f"Request type: {request_type}, Table name: {table_name}")
246
247 if request_type == 'Create':
248 seed_data(table_name)
249 cfnresponse.send(event, context, cfnresponse.SUCCESS, {
250 'Message': 'Successfully seeded DynamoDB table with restaurant data',
251 'TableName': table_name,
252 'RestaurantsSeeded': 4
253 })
254 elif request_type == 'Update':
255 # For updates, check if table name changed and handle accordingly
256 old_properties = event.get('OldResourceProperties', {})
257 old_table_name = old_properties.get('TableName')
258
259 if old_table_name != table_name:
260 logger.info(f"Table name changed from {old_table_name} to {table_name}, seeding new table")
261 seed_data(table_name)
262 message = f'Table name updated and new table seeded: {table_name}'
263 else:
264 logger.info("Update request - no table name change, no action needed")
265 message = 'Update completed - no data seeding required'
266
267 cfnresponse.send(event, context, cfnresponse.SUCCESS, {
268 'Message': message,
269 'TableName': table_name
270 })
271 elif request_type == 'Delete':
272 # For deletes, we don't need to clean up data (table will be deleted by CloudFormation)
273 # But we should validate the operation completed successfully
274 logger.info("Delete request - no data cleanup needed (table will be deleted by CloudFormation)")
275 cfnresponse.send(event, context, cfnresponse.SUCCESS, {
276 'Message': 'Delete completed - table cleanup handled by CloudFormation',
277 'TableName': table_name
278 })
279 else:
280 logger.error(f"Unknown request type: {request_type}")
281 cfnresponse.send(event, context, cfnresponse.FAILED, {
282 'Message': f'Unknown request type: {request_type}'
283 })
284
285 except Exception as e:
286 logger.error(f"Error processing request: {str(e)}")
287 cfnresponse.send(event, context, cfnresponse.FAILED, {
288 'Message': f'Error: {str(e)}'
289 })
290
291 def seed_data(table_name):
292 """
293 Seeds the DynamoDB table with initial restaurant data.
294 Implements idempotent operations to handle retries safely.
295 """
296 try:
297 dynamodb = boto3.resource('dynamodb')
298 table = dynamodb.Table(table_name)
299
300 # Wait for table to be active before seeding
301 logger.info(f"Waiting for table {table_name} to be active...")
302 table.wait_until_exists()
303
304 # Verify table is in ACTIVE state
305 table_status = table.table_status
306 if table_status != 'ACTIVE':
307 raise Exception(f"Table {table_name} is not in ACTIVE state (current: {table_status})")
308
309 # Initial restaurant data - matches the original preparation script
310 restaurants = [
311 {'name': 'ihop', 'restaurantcount': 0},
312 {'name': 'outback', 'restaurantcount': 0},
313 {'name': 'bucadibeppo', 'restaurantcount': 0},
314 {'name': 'chipotle', 'restaurantcount': 0}
315 ]
316
317 logger.info(f"Seeding table {table_name} with {len(restaurants)} restaurants")
318
319 # Track seeding results
320 seeded_count = 0
321 skipped_count = 0
322
323 # Use batch write for efficiency, but handle individual failures
324 with table.batch_writer() as batch:
325 for restaurant in restaurants:
326 try:
327 # Check if item already exists (idempotent operation)
328 response = table.get_item(Key={'name': restaurant['name']})
329
330 if 'Item' not in response:
331 # Item doesn't exist, create it
332 batch.put_item(Item=restaurant)
333 logger.info(f"Added restaurant: {restaurant['name']}")
334 seeded_count += 1
335 else:
336 # Item exists, log but don't overwrite (idempotent behavior)
337 existing_count = response['Item'].get('restaurantcount', 0)
338 logger.info(f"Restaurant {restaurant['name']} already exists with count {existing_count}, skipping")
339 skipped_count += 1
340
341 except ClientError as e:
342 logger.error(f"Error processing restaurant {restaurant['name']}: {str(e)}")
343 raise
344
345 logger.info(f"Data seeding completed: {seeded_count} restaurants added, {skipped_count} already existed")
346
347 # Verify seeding was successful by checking all restaurants exist
348 verify_seeded_data(table, restaurants)
349
350 except ClientError as e:
351 error_code = e.response['Error']['Code']
352 error_message = e.response['Error']['Message']
353 logger.error(f"DynamoDB error ({error_code}): {error_message}")
354 raise Exception(f"Failed to seed data: {error_code} - {error_message}")
355 except Exception as e:
356 logger.error(f"Unexpected error during data seeding: {str(e)}")
357 raise
358
359 def verify_seeded_data(table, expected_restaurants):
360 """
361 Verifies that all expected restaurants were seeded successfully.
362 """
363 logger.info("Verifying seeded data...")
364
365 for restaurant in expected_restaurants:
366 try:
367 response = table.get_item(Key={'name': restaurant['name']})
368 if 'Item' not in response:
369 raise Exception(f"Verification failed: Restaurant {restaurant['name']} not found in table")
370
371 item = response['Item']
372 if 'restaurantcount' not in item:
373 raise Exception(f"Verification failed: Restaurant {restaurant['name']} missing restaurantcount attribute")
374
375 logger.info(f"Verified restaurant {restaurant['name']} exists with count {item['restaurantcount']}")
376
377 except ClientError as e:
378 logger.error(f"Error verifying restaurant {restaurant['name']}: {str(e)}")
379 raise Exception(f"Verification failed for {restaurant['name']}: {str(e)}")
380
381 logger.info("All restaurants verified successfully")
382 Tags:
383 - Key: Environment
384 Value: !Ref Environment
385 - Key: Application
386 Value: 'votingapp'
387 - Key: ManagedBy
388 Value: 'CloudFormation'
389 DependsOn:
390 - LambdaExecutionRole
391 - LambdaDynamoDBPolicy
392
393 # Lambda permission to allow CloudFormation to invoke the function
394 LambdaInvokePermission:
395 Type: AWS::Lambda::Permission
396 Properties:
397 FunctionName: !Ref DataSeedingFunction
398 Action: lambda:InvokeFunction
399 Principal: cloudformation.amazonaws.com
400 SourceAccount: !Ref 'AWS::AccountId'
401 DependsOn:
402 - DataSeedingFunction
403
404 # Custom resource to trigger data seeding during stack operations
405 DataInitializationResource:
406 Type: AWS::CloudFormation::CustomResource
407 Properties:
408 ServiceToken: !GetAtt DataSeedingFunction.Arn
409 TableName: !Ref DynamoDBTable
410 # Adding a version property to force updates when needed
411 Version: '1.0'
412 DependsOn:
413 - DataSeedingFunction
414 - DynamoDBTable
415 - LambdaInvokePermission
416
417Outputs:
418 # Core outputs required for App Runner service configuration
419 # These values should be used when configuring the App Runner service
420
421 DynamoDBTableName:
422 Description: 'Name of the DynamoDB table for restaurant votes - use this for DDB_TABLE_NAME environment variable'
423 Value: !Ref DynamoDBTable
424 Export:
425 Name: !Sub '${AWS::StackName}-DynamoDBTableName'
426
427 IAMRoleArn:
428 Description: 'ARN of the IAM role for App Runner instance configuration - use this for App Runner instance role'
429 Value: !GetAtt AppRunnerInstanceRole.Arn
430 Export:
431 Name: !Sub '${AWS::StackName}-IAMRoleArn'
432
433 AWSRegion:
434 Description: 'AWS Region where resources are deployed - use this for DDB_AWS_REGION environment variable'
435 Value: !Ref 'AWS::Region'
436 Export:
437 Name: !Sub '${AWS::StackName}-AWSRegion'
438
439 # Additional outputs for monitoring and management
440
441 DynamoDBTableArn:
442 Description: 'ARN of the DynamoDB table for resource identification and monitoring'
443 Value: !GetAtt DynamoDBTable.Arn
444 Export:
445 Name: !Sub '${AWS::StackName}-DynamoDBTableArn'
446
447 IAMRoleName:
448 Description: 'Name of the IAM role for reference and management'
449 Value: !Ref AppRunnerInstanceRole
450 Export:
451 Name: !Sub '${AWS::StackName}-IAMRoleName'
452
453 StackEnvironment:
454 Description: 'Environment tag value for this stack deployment'
455 Value: !Ref Environment
456 Export:
457 Name: !Sub '${AWS::StackName}-Environment'
458
459 # Configuration summary for easy reference
460
461 AppRunnerConfiguration:
462 Description: 'Summary of key configuration values for App Runner deployment'
463 Value: !Sub |
464 DynamoDB Table: ${DynamoDBTable}
465 IAM Role ARN: ${AppRunnerInstanceRole.Arn}
466 AWS Region: ${AWS::Region}
467 Environment: ${Environment}
468 Export:
469 Name: !Sub '${AWS::StackName}-AppRunnerConfig'
470
471# ============================================================================
472# USAGE NOTES AND TROUBLESHOOTING
473# ============================================================================
474#
475# APP RUNNER CONFIGURATION:
476# After deploying this stack, use the outputs to configure your App Runner service:
477# - DynamoDBTableName -> Set as DDB_TABLE_NAME environment variable
478# - IAMRoleArn -> Use as App Runner instance role ARN
479# - AWSRegion -> Set as DDB_AWS_REGION environment variable
480#
481# INITIAL DATA:
482# The template automatically seeds the DynamoDB table with four restaurants:
483# - ihop (vote count: 0)
484# - outback (vote count: 0)
485# - bucadibeppo (vote count: 0)
486# - chipotle (vote count: 0)
487#
488# MONITORING:
489# All resources are tagged with:
490# - Environment: dev/staging/prod (configurable)
491# - Application: votingapp
492# - ManagedBy: CloudFormation
493#
494# TROUBLESHOOTING:
495# - If stack creation fails, check CloudFormation events for detailed error messages
496# - Ensure your AWS credentials have sufficient permissions
497# - Verify that resource names don't conflict with existing resources
498# - Check AWS service limits if resource creation fails
499#
500# COST OPTIMIZATION:
501# - DynamoDB uses PAY_PER_REQUEST billing (no fixed costs)
502# - Lambda function only runs during stack operations
503# - IAM roles and policies have no direct costs
504#
505# SECURITY CONSIDERATIONS:
506# - IAM policies follow least-privilege principle
507# - DynamoDB access is restricted to specific table ARN
508# - Lambda function has minimal required permissions
509# - Point-in-time recovery is enabled for DynamoDB table
510#
511# ============================================================================
The process went (mostly) smoothly, but here below I am going to make a few observations in no particular order.
This output is nothing a real developer would define as complex. Yet, if I think about 4 years ago, when I built this application, it could have taken me the better part of a full day (if not more!) to build anything like this (especially building and testing the Lambda function for the CloudFormation custom resource). Arguably, this is something that, with proper vibe coding, it could have taken me a lot less time (likely still a few hours). The good thing about specs is that it only took me a few minutes of attention span to get this work done.
You may have noticed I said a few minutes of ... attention span. That is because it didn't take Kiro a few minutes to build this. Probably the end-to-end workflow took about roughly 2-3 hours (elapsed time). This is because I had to keep tabs on it. This was mostly for running the tasks in sequence and trusting the terminal commands the agent needed to run (there is work we need to do to optimize the commands trust engine, it can become a bit frustrating). But the good thing is that I did not need many brain cycles to go through all this. Yes, it took 2-3 hours but that was time I spent doing (for the most part) something else and only checking in from time to time to make sure the flow was not blocked.
The third observation is that I lied :) . I spent more than 2-3 hours. I went through this entire workflow twice. The reason for that is that I did not want Kiro to only validate empirically the correctness of the CFN template. As I said, I wanted Kiro to actually deploy the template and track any errors along the way. My first attempt was with the same prompt but I did not include the last line (Do not validate the CFN template simply with local tests and mocks. Make sure you actually deploy it to the AWS account and you test the application against that backend.
). Without that line, the first result was that it created the template through a specs workflow, but never attempted to actually deploy it. When I tried to deploy it, it failed. That is when I decided to start from scratch (as a learning exercise) and added that new sentence at the bottom of the prompt. The effect of that line appears to be that it added Requirement 3 (As a quality assurance engineer, I want the infrastructure to be validated through actual deployment and testing, so that I can ensure the application works correctly with the created resources
) and eventually Task 7 through Task 10 (check out Appendix A to inspect these tasks in the implementation document).
The last observation, for good or bad, is that this workflow left a lot behind. In addition to creating the CloudFormation template (the goal) in the root of my repository, it created a folder called scripts
with a bunch of ... scripts (and reports, logs, etc). It did this as part of the process of building, testing, and verifying the work it was doing. Below is a snapshot of that folder's contents at the end of the workflow (during the workflow, many more scripts were generated and eventually deleted dynamically):
1scripts/
2├── README.md
3├── api-validation-test.sh
4├── cleanup-report.json
5├── cleanup.log
6├── cleanup.sh
7├── comprehensive-e2e-test.sh
8├── concurrent-test.sh
9├── deploy.log
10├── deploy.sh
11├── e2e-test-report.md
12├── e2e-test.log
13├── test-scripts.sh
14├── validate.log
15└── validate.sh
I don't have strong opinions about this. I am wondering if someone would look at this positively (it's great to track everything it has done and potentially re-use some of these scripts) or negatively (what do I do with all this? Should I keep them? Should I check them in? Or should I just delete entirely what seems to be a temporary folder to get to the final goal?).
Massimo.
Appendix A. Kiro specifications files
1# Requirements Document
2
3## Introduction
4
5This feature involves creating a comprehensive CloudFormation template that automates the entire AWS infrastructure setup for the voting application. The template will replace the manual preparation scripts and provide a single-stack deployment solution that includes DynamoDB table creation, IAM roles and policies, initial data seeding, and App Runner service configuration. The solution must be validated through actual deployment and testing to ensure the application can successfully connect to DynamoDB and serve API requests.
6
7## Requirements
8
9### Requirement 1
10
11**User Story:** As a DevOps engineer, I want a single CloudFormation template that creates all required AWS infrastructure, so that I can deploy the voting app without running manual scripts.
12
13#### Acceptance Criteria
14
151. WHEN the CloudFormation template is deployed THEN the system SHALL create a DynamoDB table named "votingapp-restaurants" with the correct schema
162. WHEN the DynamoDB table is created THEN the system SHALL populate it with initial data for all four restaurants (ihop, outback, bucadibeppo, chipotle) with zero vote counts
173. WHEN the template is deployed THEN the system SHALL create an IAM role with appropriate trust policy for App Runner services
184. WHEN the IAM role is created THEN the system SHALL attach a custom policy granting DynamoDB access to the specific table
195. WHEN the IAM role is created THEN the system SHALL attach the AWS managed AWSXRayDaemonWriteAccess policy for tracing
20
21### Requirement 2
22
23**User Story:** As a developer, I want the CloudFormation template to output all necessary values for App Runner configuration, so that I can easily deploy the application service.
24
25#### Acceptance Criteria
26
271. WHEN the CloudFormation stack is deployed THEN the system SHALL output the DynamoDB table name
282. WHEN the CloudFormation stack is deployed THEN the system SHALL output the IAM role ARN for App Runner instance configuration
293. WHEN the CloudFormation stack is deployed THEN the system SHALL output the AWS region for environment variable configuration
304. IF the stack deployment fails THEN the system SHALL provide clear error messages indicating the failure reason
31
32### Requirement 3
33
34**User Story:** As a quality assurance engineer, I want the infrastructure to be validated through actual deployment and testing, so that I can ensure the application works correctly with the created resources.
35
36#### Acceptance Criteria
37
381. WHEN the CloudFormation template is deployed to AWS THEN the system SHALL successfully create all resources without errors
392. WHEN the infrastructure is created THEN the system SHALL allow the application to connect to DynamoDB using the created IAM role
403. WHEN the application is running THEN the system SHALL respond successfully to GET requests on the /api/getvotes endpoint
414. WHEN the /api/getvotes endpoint is called THEN the system SHALL return valid JSON with all four restaurants and their vote counts
425. WHEN any restaurant voting endpoint is called THEN the system SHALL successfully increment the vote count in DynamoDB
43
44### Requirement 4
45
46**User Story:** As a system administrator, I want the CloudFormation template to follow AWS best practices for security and resource management, so that the infrastructure is secure and maintainable.
47
48#### Acceptance Criteria
49
501. WHEN creating IAM policies THEN the system SHALL use least-privilege access principles
512. WHEN creating the DynamoDB table THEN the system SHALL use PAY_PER_REQUEST billing mode for cost optimization
523. WHEN creating resources THEN the system SHALL use appropriate resource naming conventions with consistent prefixes
534. WHEN the stack is deleted THEN the system SHALL cleanly remove all created resources without leaving orphaned components
545. IF resource creation fails THEN the system SHALL rollback successfully without leaving partial deployments
55
56### Requirement 5
57
58**User Story:** As a deployment engineer, I want the template to be parameterized and configurable, so that I can customize the deployment for different environments.
59
60#### Acceptance Criteria
61
621. WHEN deploying the template THEN the system SHALL accept parameters for table name with a sensible default
632. WHEN deploying the template THEN the system SHALL accept parameters for IAM role name with a sensible default
643. WHEN parameters are provided THEN the system SHALL use those values instead of defaults
654. WHEN no parameters are provided THEN the system SHALL use default values that match the original preparation script behavior
665. WHEN invalid parameters are provided THEN the system SHALL validate and reject the deployment with clear error messages
1# Design Document
2
3## Overview# Design Document
4
5## Overview
6
7The CloudFormation template will be a comprehensive infrastructure-as-code solution that replaces the manual preparation scripts. It will create all necessary AWS resources in a single stack deployment, including DynamoDB table with initial data, IAM roles and policies, and provide outputs for App Runner configuration. The design emphasizes automation, validation, and adherence to AWS best practices.
8
9## Architecture
10
11### High-Level Architecture
12```
13CloudFormation Stack
14├── DynamoDB Table (votingapp-restaurants)
15│ ├── Hash Key: name (String)
16│ ├── Billing Mode: PAY_PER_REQUEST
17│ └── Initial Items: 4 restaurants with 0 votes
18├── IAM Role (votingapp-role)
19│ ├── Trust Policy: App Runner service principal
20│ ├── Custom DynamoDB Policy: Table-specific access
21│ └── AWS Managed Policy: X-Ray write access
22└── Stack Outputs
23 ├── DynamoDB Table Name
24 ├── IAM Role ARN
25 └── AWS Region
26```
27
28### Resource Dependencies
291. DynamoDB Table (independent resource)
302. IAM Role (independent resource)
313. IAM Policy (depends on DynamoDB table for ARN reference)
324. Policy Attachments (depend on role and policies)
335. Custom Resource for data seeding (depends on DynamoDB table)
34
35## Components and Interfaces
36
37### CloudFormation Template Structure
38
39#### Parameters Section
40- `TableName`: String parameter with default "votingapp-restaurants"
41- `RoleName`: String parameter with default "votingapp-role"
42- `Environment`: String parameter for resource tagging (default: "dev")
43
44#### Resources Section
45
46**DynamoDB Table Resource**
47```yaml
48Type: AWS::DynamoDB::Table
49Properties:
50 TableName: !Ref TableName
51 AttributeDefinitions:
52 - AttributeName: name
53 AttributeType: S
54 KeySchema:
55 - AttributeName: name
56 KeyType: HASH
57 BillingMode: PAY_PER_REQUEST
58 Tags:
59 - Key: Environment
60 Value: !Ref Environment
61```
62
63**IAM Role Resource**
64```yaml
65Type: AWS::IAM::Role
66Properties:
67 RoleName: !Ref RoleName
68 AssumeRolePolicyDocument:
69 Version: '2012-10-17'
70 Statement:
71 - Effect: Allow
72 Principal:
73 Service: tasks.apprunner.amazonaws.com
74 Action: sts:AssumeRole
75```
76
77**Custom DynamoDB Policy**
78```yaml
79Type: AWS::IAM::Policy
80Properties:
81 PolicyName: votingapp-ddb-policy
82 PolicyDocument:
83 Version: '2012-10-17'
84 Statement:
85 - Effect: Allow
86 Action: dynamodb:*
87 Resource: !GetAtt DynamoDBTable.Arn
88 Roles:
89 - !Ref IAMRole
90```
91
92**Lambda Function for Data Seeding**
93```yaml
94Type: AWS::Lambda::Function
95Properties:
96 Runtime: python3.9
97 Handler: index.handler
98 Code:
99 ZipFile: |
100 # Python code to seed initial restaurant data
101 Role: !GetAtt LambdaExecutionRole.Arn
102```
103
104**Custom Resource for Data Initialization**
105```yaml
106Type: AWS::CloudFormation::CustomResource
107Properties:
108 ServiceToken: !GetAtt DataSeedingFunction.Arn
109 TableName: !Ref DynamoDBTable
110```
111
112#### Outputs Section
113- `DynamoDBTableName`: Table name for App Runner environment variables
114- `IAMRoleArn`: Role ARN for App Runner instance configuration
115- `AWSRegion`: Current region for application configuration
116
117### Data Seeding Strategy
118
119The template will include a Lambda function that acts as a CloudFormation custom resource to seed the DynamoDB table with initial data. This approach ensures:
120- Data is created only once during stack creation
121- Data is properly cleaned up during stack deletion
122- Idempotent operations that can handle retries
123
124### Validation and Testing Strategy
125
126#### Deployment Validation
1271. CloudFormation template syntax validation using AWS CLI
1282. Actual deployment to AWS account using default profile
1293. Resource creation verification through AWS console/CLI
1304. Stack outputs validation
131
132#### Application Testing
1331. Deploy the CloudFormation stack
1342. Configure App Runner service using stack outputs
1353. Deploy the voting application
1364. Test API endpoints:
137 - GET /api/getvotes (should return initial data)
138 - POST to voting endpoints (should increment counts)
139 - Verify DynamoDB updates
140
141## Data Models
142
143### DynamoDB Table Schema
144```
145Table: votingapp-restaurants
146Primary Key: name (String)
147Attributes:
148 - name: String (Hash Key) - Restaurant identifier
149 - restaurantcount: Number - Vote count for the restaurant
150```
151
152### Initial Data Set
153```json
154[
155 {"name": "ihop", "restaurantcount": 0},
156 {"name": "outback", "restaurantcount": 0},
157 {"name": "bucadibeppo", "restaurantcount": 0},
158 {"name": "chipotle", "restaurantcount": 0}
159]
160```
161
162### IAM Policy Structure
163```json
164{
165 "Version": "2012-10-17",
166 "Statement": [
167 {
168 "Effect": "Allow",
169 "Action": "dynamodb:*",
170 "Resource": "arn:aws:dynamodb:region:account:table/table-name"
171 }
172 ]
173}
174```
175
176## Error Handling
177
178### CloudFormation Deployment Errors
179- Template validation errors: Provide clear syntax error messages
180- Resource creation failures: Implement proper rollback mechanisms
181- Permission errors: Clear error messages about required AWS permissions
182- Resource limit errors: Guidance on AWS service limits
183
184### Application Runtime Errors
185- DynamoDB connection failures: Verify IAM role permissions
186- Missing environment variables: Validate App Runner configuration
187- API endpoint errors: Check application logs and DynamoDB access
188
189### Custom Resource Error Handling
190- Lambda function failures during data seeding
191- Retry mechanisms for transient DynamoDB errors
192- Proper cleanup during stack deletion failures
193
194## Testing Strategy
195
196### Unit Testing
197- CloudFormation template validation using cfn-lint
198- IAM policy validation using policy simulator
199- Lambda function code testing with mock DynamoDB calls
200
201### Integration Testing
2021. **Infrastructure Testing**
203 - Deploy CloudFormation stack to AWS
204 - Verify all resources are created correctly
205 - Test IAM role permissions with AWS CLI
206 - Validate DynamoDB table structure and initial data
207
2082. **Application Testing**
209 - Deploy App Runner service using stack outputs
210 - Test application startup and DynamoDB connectivity
211 - Validate all API endpoints return expected responses
212 - Test vote increment functionality
213
2143. **End-to-End Testing**
215 - Complete deployment workflow from CloudFormation to running application
216 - API testing with curl/Postman
217 - Load testing with multiple concurrent requests
218 - Stack deletion and cleanup verification
219
220### Validation Criteria
221- CloudFormation stack deploys successfully without errors
222- All AWS resources are created with correct configurations
223- Application can connect to DynamoDB using created IAM role
224- /api/getvotes endpoint returns valid JSON with all restaurants
225- Vote increment operations successfully update DynamoDB
226- Stack deletion removes all resources cleanly
1# Implementation Plan
2
3- [x] 1. Create CloudFormation template structure and basic resources
4 - Create cloudformation-template.yaml file with basic structure
5 - Define template parameters for table name, role name, and environment
6 - Implement DynamoDB table resource with correct schema and billing mode
7 - Add basic metadata and description to template
8 - _Requirements: 1.1, 1.2, 4.2, 5.1, 5.2, 5.4_
9
10- [x] 2. Implement IAM role and policy resources
11 - Create IAM role resource with App Runner trust policy
12 - Implement custom DynamoDB policy with least-privilege access
13 - Add AWS managed policy attachment for X-Ray access
14 - Configure proper resource dependencies between role and policies
15 - _Requirements: 1.3, 1.4, 1.5, 4.1_
16
17- [x] 3. Create Lambda function for DynamoDB data seeding
18 - Write Python Lambda function code for initializing restaurant data
19 - Create Lambda execution role with DynamoDB write permissions
20 - Implement error handling and idempotent operations in Lambda
21 - Add proper logging for debugging data seeding operations
22 - _Requirements: 1.2, 4.4_
23
24- [x] 4. Implement CloudFormation custom resource for data initialization
25 - Create custom resource that triggers Lambda function during stack operations
26 - Configure custom resource to pass table name to Lambda function
27 - Implement proper cleanup logic for stack deletion scenarios
28 - Add dependency management between custom resource and DynamoDB table
29 - _Requirements: 1.2, 4.4_
30
31- [x] 5. Add CloudFormation outputs and finalize template
32 - Implement stack outputs for DynamoDB table name, IAM role ARN, and region
33 - Add resource tags for environment identification and management
34 - Validate template syntax and resource references
35 - Add template documentation and usage comments
36 - _Requirements: 2.1, 2.2, 2.3, 4.3_
37
38- [x] 6. Create deployment and testing scripts
39 - Write deployment script that uses AWS CLI to deploy CloudFormation stack
40 - Create validation script to verify all resources are created correctly
41 - Implement cleanup script for stack deletion and resource verification
42 - Add error handling and logging to all deployment scripts
43 - _Requirements: 3.1, 4.4, 5.5_
44
45- [x] 7. Deploy and validate CloudFormation stack in AWS
46 - Execute deployment script to create CloudFormation stack in AWS account
47 - Verify DynamoDB table creation and initial data population
48 - Validate IAM role creation and policy attachments
49 - Test stack outputs and confirm all values are correct
50 - _Requirements: 3.1, 3.2, 2.4_
51
52- [x] 8. Test application connectivity with created infrastructure
53 - Launch the voting application locally
54 - Verify application can connect to DynamoDB using the local AWS profile
55 - Test application startup and basic functionality
56 - _Requirements: 3.2, 3.3_
57
58- [x] 9. Validate API endpoints and DynamoDB operations
59 - Test GET /api/getvotes endpoint returns correct initial data structure
60 - Verify all four restaurants are present with zero vote counts
61 - Test vote increment functionality on all restaurant endpoints
62 - Confirm DynamoDB updates are persisted correctly after vote operations
63 - _Requirements: 3.3, 3.4, 3.5_
64
65- [x] 10. Perform comprehensive end-to-end testing
66 - Execute complete deployment workflow from CloudFormation to running app
67 - Test multiple concurrent API requests to verify system stability
68 - Validate stack deletion removes all resources without orphaned components
69 - Document any issues found and verify all requirements are met
70 - _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5, 4.4_