- Python CLI tool for rolling updates of CL-AppPipe-* and CL-SvcPipe-* stacks - Async update engine with configurable concurrency (asyncio.Semaphore) - Exponential backoff retry for API throttling - Dry-run mode for safe preview - IAM permission pre-validation - Comprehensive test suite (80 tests: 11 property-based + 69 unit) - Full spec documentation (requirements, design, tasks)
9.5 KiB
Implementation Plan: One-Click CloudFormation Stack Updater
Overview
Implement a Python CLI tool that discovers and updates all CL-AppPipe-* CloudFormation stacks in an audit account. The implementation follows the pipeline architecture: Permission Validation → Stack Discovery → Stack Update Engine → Report Generator. Each task builds incrementally, wiring components together at the end.
Tasks
-
1. Set up project structure, configuration constants, and data models
- Create the package directory structure:
cfn_updater/with__init__.py,cli.py,permissions.py,discovery.py,updater.py,report.py,config.py - Create
tests/directory with__init__.py,conftest.py - Implement
config.pywith all configuration constants (TEMPLATE_URL,DEFAULT_PREFIX,DEFAULT_CONCURRENCY,MAX_RETRIES,BASE_RETRY_DELAY,NON_UPDATABLE_STATUSES) - Implement data model dataclasses:
DiscoveredStack,StackUpdateResult,UpdateRunReport - Add
pyproject.tomlorrequirements.txtwith dependencies:boto3,pytest,hypothesis,botocore - Requirements: 1.1, 2.3, 2.4, 3.1, 5.2
- Create the package directory structure:
-
2. Implement Permission Validator
-
2.1 Implement
validate_permissions()inpermissions.py- Check
cloudformation:ListStacks,cloudformation:DescribeStacks, andcloudformation:UpdateStackpermissions via dry-run API calls - Return a list of missing permission names; empty list means all OK
- Requirements: 7.1, 7.2
- Check
-
2.2 Write property test for permission validation (Property 11)
- Property 11: Permission validation correctness
- For any subset of required permissions marked as missing, the validator returns exactly those missing permissions
- When any permissions are missing, no UpdateStack calls are made
- Validates: Requirements 7.1, 7.2
-
2.3 Write unit tests for permission validation
- Test all permissions present (happy path)
- Test single missing permission
- Test all permissions missing
- Test boto3 error handling during validation
- Requirements: 7.1, 7.2
-
-
3. Implement Stack Discovery
-
3.1 Implement
discover_stacks()indiscovery.py- List all CloudFormation stacks using paginated
list_stacksAPI calls - Filter stacks by
Stack_Name_Prefix(CL-AppPipe-) - Mark each stack as updatable or not based on
NON_UPDATABLE_STATUSES - Return list of
DiscoveredStackobjects - Requirements: 1.1, 1.2, 1.3, 4.2
- List all CloudFormation stacks using paginated
-
3.2 Write property test for discovery prefix filtering (Property 1)
- Property 1: Discovery returns exactly prefix-matched stacks with correct count
- For any list of stack names, discovery returns exactly those starting with the prefix
- Reported count equals the length of the filtered list
- Validates: Requirements 1.1, 1.3
-
3.3 Write property test for non-updatable stack classification (Property 6)
- Property 6: Non-updatable stacks are skipped
- For any stack with a status in
NON_UPDATABLE_STATUSES,updatableisFalse - For any stack with a status not in
NON_UPDATABLE_STATUSES,updatableisTrue - Validates: Requirements 4.2
-
3.4 Write unit tests for stack discovery
- Test empty stack list (Req 1.4)
- Test pagination across multiple pages
- Test mixed updatable and non-updatable stacks
- Requirements: 1.1, 1.2, 1.3, 1.4, 4.2
-
-
4. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
-
5. Implement Stack Update Engine
-
5.1 Implement
update_stack()coroutine inupdater.py- Fetch current stack parameters via
describe_stacks - Call
UpdateStackwithUsePreviousValue=Truefor all existing parameters and the configuredTEMPLATE_URL - Handle "No updates are to be performed" response as
no-update-needed - Implement exponential backoff retry for throttling errors (
Throttling,RequestLimitExceeded) - Record duration and return
StackUpdateResult - Requirements: 3.1, 3.2, 3.4, 4.1, 4.3, 4.4
- Fetch current stack parameters via
-
5.2 Implement
update_all_stacks()coroutine inupdater.py- Use
asyncio.Semaphoreto bound concurrent updates toConcurrency_Limit - Skip non-updatable stacks (record as
skipped) - Collect results for all stacks, including failures
- Requirements: 2.2, 2.3, 3.3, 4.1, 4.2
- Use
-
5.3 Write property test for parameter preservation (Property 4)
- Property 4: Update call preserves existing parameters and uses correct template URL
- For any stack with any set of parameters, UpdateStack includes all parameter keys with
UsePreviousValue=True TemplateURLequals the configuredTEMPLATE_URL- Validates: Requirements 3.1, 3.2
-
5.4 Write property test for "no updates" handling (Property 5)
- Property 5: "No updates" response maps to no-update-needed status
- For any stack returning "No updates are to be performed", result status is
no-update-needed - Validates: Requirements 3.4
-
5.5 Write property test for all updatable stacks attempted (Property 2)
- Property 2: All updatable stacks are attempted
- For any set of discovered stacks, exactly one result per updatable stack; no drops, no duplicates
- Validates: Requirements 2.2
-
5.6 Write property test for concurrency limit invariant (Property 3)
- Property 3: Concurrency limit invariant
- For any positive concurrency limit and stack list, concurrent in-progress updates never exceed the limit
- Validates: Requirements 2.3, 3.3
-
5.7 Write property test for fault isolation (Property 7)
- Property 7: Fault isolation — failures do not block remaining stacks
- For any N updatable stacks where K fail, results are produced for all N stacks
- Validates: Requirements 4.1, 4.4
-
5.8 Write property test for exponential backoff (Property 8)
- Property 8: Throttling triggers exponential backoff retries
- For any stack receiving throttling errors, retries up to
MAX_RETRIEStimes - Delay between attempt i and i+1 is at least
BASE_RETRY_DELAY * 2^iseconds - Validates: Requirements 4.3
-
5.9 Write unit tests for stack updater
- Test successful update flow
- Test "no updates" response handling
- Test throttling with retry and eventual success
- Test throttling with retry exhaustion
- Test non-updatable stack skipping
- Test concurrent updates with Stubber
- Requirements: 3.1, 3.2, 3.4, 4.1, 4.2, 4.3, 4.4
-
-
6. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
-
7. Implement Report Generator
-
7.1 Implement
generate_report()andformat_report()inreport.py- Aggregate
StackUpdateResultlist intoUpdateRunReportwith correct counts - Format report as human-readable console output with start/end times, per-stack results, and summary totals
- Requirements: 5.1, 5.2, 5.4
- Aggregate
-
7.2 Write property test for report aggregation (Property 9)
- Property 9: Report aggregation and exit code correctness
- For any list of
StackUpdateResult, report counts match actual counts per status - Exit code is non-zero if and only if
failed > 0 - Validates: Requirements 5.2, 5.3
-
7.3 Write unit tests for report generator
- Test all-success scenario
- Test mixed results scenario
- Test empty results list
- Test format output contains expected fields
- Requirements: 5.1, 5.2, 5.3, 5.4
-
-
8. Implement Dry-Run Mode
-
8.1 Add dry-run logic to the pipeline
- When
--dry-runis set, discover stacks and output name + status for each, but make zero UpdateStack calls - Requirements: 6.1, 6.2, 6.3
- When
-
8.2 Write property test for dry-run (Property 10)
- Property 10: Dry-run performs no updates and lists all discovered stacks
- For any set of discovered stacks with dry-run enabled, zero UpdateStack calls are made
- Output contains name and status of every discovered stack
- Validates: Requirements 6.2, 6.3
-
8.3 Write unit tests for dry-run mode
- Test dry-run outputs all stack names and statuses
- Test dry-run makes no API update calls
- Requirements: 6.1, 6.2, 6.3
-
-
9. Implement CLI Entry Point and Wire Components Together
-
9.1 Implement
main()incli.pywith argument parsing- Parse
--prefix,--concurrency,--dry-run,--regionflags usingargparse - Orchestrate the full pipeline: validate permissions → discover stacks → update (or dry-run) → generate report
- Return exit code 0 (success/no stacks/dry-run), 1 (any failure), or 2 (permission failure)
- Add
if __name__ == "__main__"block - Requirements: 1.3, 1.4, 2.1, 2.2, 2.3, 2.4, 5.1, 5.2, 5.3, 5.4, 6.1, 7.1, 7.2
- Parse
-
9.2 Write unit tests for CLI argument parsing and pipeline orchestration
- Test default argument values
- Test custom argument values
- Test invalid arguments produce non-zero exit
- Test end-to-end pipeline with mocked components
- Requirements: 2.1, 2.3, 2.4
-
-
10. Final checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
Notes
- Tasks marked with
*are optional and can be skipped for faster MVP - Each task references specific requirements for traceability
- Property tests use
hypothesiswith a minimum of 100 iterations per property - AWS API calls are mocked using
botocore.stub.Stubberin all tests - Checkpoints ensure incremental validation between major phases