Skip to content

Commit 07f268d

Browse files
committed
Add integrated change preview to deploy command
Implement changeset-based deployment with rich change preview functionality: - Add ExecuteChangeSet to AWS operations layer with comprehensive testing - Enhance ChangeSetManager with CreateChangeSetForDeployment method for deployment scenarios - Rewrite deploy command to use unified diff engine for consistent change previews - Show detailed template, parameter, tag, and resource changes before deployment - Use CloudFormation ChangeSets for both preview generation and deployment execution - Maintain separate flows for new stack creation vs existing stack updates - Update all documentation including README, command help, examples, and architecture docs Benefits: - Accurate previews using same changeset that gets deployed - Rich diff output consistent with dedicated diff command - Enhanced user experience with change visibility before deployment - CloudFormation best practices using native ChangeSet API - Comprehensive test coverage with no breaking changes
1 parent 7cc0f36 commit 07f268d

File tree

17 files changed

+622
-65
lines changed

17 files changed

+622
-65
lines changed

AGENTS.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Project Overview
44

5-
Stackaroo is a command-line tool for managing AWS CloudFormation stacks as code, written in Go. It provides declarative configuration, environment management, template validation, and dependency management for CloudFormation deployments.
5+
Stackaroo is a command-line tool for managing AWS CloudFormation stacks as code, written in Go. It provides declarative configuration, environment management, change preview, template validation, and dependency management for CloudFormation deployments.
66

77
**Key Technologies:**
88
- Go 1.24
@@ -71,7 +71,7 @@ cmd/ - CLI commands and subcommands
7171
internal/ - Internal packages (not importable by other projects)
7272
aws/ - AWS service interactions
7373
config/ - Configuration handling
74-
deploy/ - Deployment logic
74+
deploy/ - Deployment logic with integrated change preview
7575
resolve/ - Dependency resolution
7676
examples/ - Usage examples
7777
docs/ - Documentation

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Stackaroo simplifies CloudFormation stack management by providing:
88

99
- **Declarative Configuration**: Define your stacks and parameters in YAML files
1010
- **Environment Management**: Deploy the same templates across multiple environments
11+
- **Change Preview**: See exactly what changes will be made before deployment
1112
- **Template Validation**: Validate CloudFormation templates before deployment
1213
- **Stack Lifecycle**: Deploy, update, delete, and monitor stack status
1314
- **Parameter Management**: Organize parameters by environment and stack
@@ -25,6 +26,15 @@ Stackaroo simplifies CloudFormation stack management by providing:
2526
- Automatic deployment ordering
2627
- Parallel deployment where possible
2728

29+
### Change Preview
30+
31+
- **Comprehensive Change Analysis**: Shows template, parameter, tag, and resource changes
32+
- **CloudFormation ChangeSet Integration**: Uses AWS ChangeSet API for accurate previews
33+
- **Rich Diff Output**: Detailed comparison of current vs proposed infrastructure
34+
- **Resource Impact Assessment**: Identifies which resources will be created, modified, or deleted
35+
- **Replacement Warnings**: Highlights resources that require replacement during updates
36+
- **Consistent Formatting**: Same preview format as the dedicated `diff` command
37+
2838
### Template Validation
2939

3040
- Local CloudFormation template validation
@@ -33,6 +43,7 @@ Stackaroo simplifies CloudFormation stack management by providing:
3343

3444
### Real-time Event Streaming
3545

46+
- **Change Preview Before Deployment**: See exactly what will change before applying
3647
- Live CloudFormation events during deployment operations
3748
- See resource creation, updates, and completion status in real-time
3849
- Smart detection of create vs update operations

cmd/deploy.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,25 @@ var (
2525
var deployCmd = &cobra.Command{
2626
Use: "deploy",
2727
Short: "Deploy CloudFormation stacks",
28+
Long: `Deploy CloudFormation stacks with integrated change preview.
29+
30+
This command automatically shows you exactly what changes will be made before
31+
applying them to your infrastructure. For existing stacks, it uses AWS CloudFormation
32+
ChangeSets to provide accurate previews including:
33+
34+
• Template changes (resources added, modified, or removed)
35+
• Parameter changes (current vs new values)
36+
• Tag changes (added, modified, or removed tags)
37+
• Resource-level impact analysis with replacement warnings
38+
39+
For new stacks, the command proceeds directly with stack creation.
40+
41+
Examples:
42+
stackaroo deploy vpc --environment dev
43+
stackaroo deploy app --environment prod
44+
45+
The preview shows the same detailed diff information as 'stackaroo diff' but
46+
automatically proceeds with deployment after displaying the changes.`,
2847
Args: cobra.ExactArgs(1),
2948
RunE: func(cmd *cobra.Command, args []string) error {
3049
stackName := args[0]

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ var rootCmd = &cobra.Command{
2020
• Declarative configuration in YAML files
2121
• Environment-specific parameter management
2222
• Stack dependency resolution
23+
• Change preview before deployment
2324
• Template validation and deployment
2425
• Rich terminal output with progress indicators
2526

docs/architecture/diff.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,30 @@ sequenceDiagram
285285
- `ResolvedStack.Environment` - Track deployment context
286286
- Dependency resolution for complete stack information
287287

288+
### 4. Deployment Integration (`internal/deploy`)
289+
290+
**Integrated Preview:**
291+
- `NewDiffer(cfClient)` - Create differ with existing CloudFormation operations
292+
- `DiffStack(ctx, resolvedStack, options)` - Generate change preview during deployment
293+
- Consistent formatting between `stackaroo diff` and `stackaroo deploy` commands
294+
- Automatic change preview before deployment execution for existing stacks
295+
- Same changeset-based approach for both standalone diff and integrated deployment preview
296+
297+
**Deployment Flow Integration:**
298+
```mermaid
299+
sequenceDiagram
300+
participant Deploy as Deploy Command
301+
participant Differ as Diff Engine
302+
participant AWS as AWS CloudFormation
303+
304+
Deploy->>Differ: DiffStack(resolved, options)
305+
Differ->>AWS: Generate changeset & preview
306+
AWS->>Differ: Change details
307+
Differ->>Deploy: Formatted preview
308+
Deploy->>User: Display changes
309+
Deploy->>AWS: Execute deployment
310+
```
311+
288312
## Error Handling Strategy
289313

290314
```mermaid

examples/simple-vpc/README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This example demonstrates basic Stackaroo usage with a simple VPC deployment acr
99
- **Cross-account deployment** - Production uses a separate AWS account
1010
- **Parameter inheritance** - Global defaults with environment-specific overrides
1111
- **Tag management** - Consistent tagging across environments
12+
- **Change preview** - See exactly what infrastructure changes before deployment
1213

1314
## Prerequisites
1415

@@ -50,12 +51,12 @@ The `stackaroo.yaml` file defines:
5051
cd examples/simple-vpc
5152
```
5253

53-
3. **Deploy to development**:
54+
3. **Deploy to development** (shows preview before applying changes):
5455
```bash
5556
../../stackaroo deploy vpc --environment dev
5657
```
5758

58-
4. **Deploy to staging**:
59+
4. **Deploy to staging** (shows preview before applying changes):
5960
```bash
6061
../../stackaroo deploy vpc --environment staging
6162
```
@@ -65,6 +66,36 @@ The `stackaroo.yaml` file defines:
6566
../../stackaroo deploy vpc --environment prod
6667
```
6768

69+
## Preview Output
70+
71+
When you run the deploy commands, Stackaroo will show you exactly what changes will be made:
72+
73+
```
74+
=== Calculating changes for stack vpc ===
75+
Changes to be applied to stack vpc:
76+
77+
Status: CHANGES DETECTED (for updates) or Creating new stack: vpc (for new deployments)
78+
79+
Template Changes:
80+
-----------------
81+
✓ Template has been modified (if updating)
82+
Resource changes:
83+
+ 6 resources to be added (for new stacks)
84+
85+
AWS CloudFormation Preview:
86+
---------------------------
87+
Resource Changes:
88+
+ VPC (AWS::EC2::VPC)
89+
+ InternetGateway (AWS::EC2::InternetGateway)
90+
+ PublicSubnet (AWS::EC2::Subnet)
91+
+ PrivateSubnet (AWS::EC2::Subnet)
92+
+ PublicRouteTable (AWS::EC2::RouteTable)
93+
+ PrivateRouteTable (AWS::EC2::RouteTable)
94+
95+
=== Deploying stack vpc ===
96+
[Live deployment events appear here...]
97+
```
98+
6899
## What Gets Deployed
69100

70101
Each deployment creates:
@@ -108,6 +139,7 @@ This example demonstrates:
108139
3. **Cross-account deployment** - Managing resources across AWS accounts
109140
4. **Infrastructure consistency** - Identical setup with environment-appropriate sizing
110141
5. **Tag management** - Consistent tagging strategy across environments
142+
6. **Change preview** - How Stackaroo shows you exactly what will change before deployment
111143

112144
## Next Steps
113145

internal/aws/client_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@ func (m *MockCloudFormationClient) CreateChangeSet(ctx context.Context, params *
103103
return nil, nil
104104
}
105105

106+
func (m *MockCloudFormationClient) ExecuteChangeSet(ctx context.Context, params *cloudformation.ExecuteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ExecuteChangeSetOutput, error) {
107+
return nil, nil
108+
}
109+
106110
func (m *MockCloudFormationClient) DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error) {
107111
return nil, nil
108112
}

internal/aws/cloudformation.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,11 @@ func (cf *DefaultCloudFormationOperations) CreateChangeSet(ctx context.Context,
436436
return cf.client.CreateChangeSet(ctx, params, optFns...)
437437
}
438438

439+
// ExecuteChangeSet executes a CloudFormation changeset
440+
func (cf *DefaultCloudFormationOperations) ExecuteChangeSet(ctx context.Context, params *cloudformation.ExecuteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ExecuteChangeSetOutput, error) {
441+
return cf.client.ExecuteChangeSet(ctx, params, optFns...)
442+
}
443+
439444
// DeleteChangeSet deletes a CloudFormation changeset
440445
func (cf *DefaultCloudFormationOperations) DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error) {
441446
return cf.client.DeleteChangeSet(ctx, params, optFns...)

internal/aws/cloudformation_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func (m *MockCloudFormationClientForDeployTest) CreateChangeSet(ctx context.Cont
7272
return args.Get(0).(*cloudformation.CreateChangeSetOutput), args.Error(1)
7373
}
7474

75+
func (m *MockCloudFormationClientForDeployTest) ExecuteChangeSet(ctx context.Context, params *cloudformation.ExecuteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ExecuteChangeSetOutput, error) {
76+
args := m.Called(ctx, params)
77+
return args.Get(0).(*cloudformation.ExecuteChangeSetOutput), args.Error(1)
78+
}
79+
7580
func (m *MockCloudFormationClientForDeployTest) DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error) {
7681
args := m.Called(ctx, params)
7782
return args.Get(0).(*cloudformation.DeleteChangeSetOutput), args.Error(1)
@@ -490,3 +495,48 @@ func TestDeployStackWithCallback_Success(t *testing.T) {
490495
assert.Equal(t, "callback-stack", capturedEvents[0].StackName)
491496
mockClient.AssertExpectations(t)
492497
}
498+
499+
func TestDefaultCloudFormationOperations_ExecuteChangeSet_Success(t *testing.T) {
500+
mockClient := &MockCloudFormationClientForDeployTest{}
501+
cfOps := NewCloudFormationOperationsWithClient(mockClient)
502+
ctx := context.Background()
503+
504+
changeSetID := "arn:aws:cloudformation:us-east-1:123456789012:changeSet/test-changeset/test-stack"
505+
506+
executeInput := &cloudformation.ExecuteChangeSetInput{
507+
ChangeSetName: aws.String(changeSetID),
508+
}
509+
510+
expectedOutput := &cloudformation.ExecuteChangeSetOutput{}
511+
512+
mockClient.On("ExecuteChangeSet", ctx, executeInput).Return(expectedOutput, nil)
513+
514+
output, err := cfOps.ExecuteChangeSet(ctx, executeInput)
515+
516+
require.NoError(t, err)
517+
assert.Equal(t, expectedOutput, output)
518+
mockClient.AssertExpectations(t)
519+
}
520+
521+
func TestDefaultCloudFormationOperations_ExecuteChangeSet_Error(t *testing.T) {
522+
mockClient := &MockCloudFormationClientForDeployTest{}
523+
cfOps := NewCloudFormationOperationsWithClient(mockClient)
524+
ctx := context.Background()
525+
526+
changeSetID := "arn:aws:cloudformation:us-east-1:123456789012:changeSet/test-changeset/test-stack"
527+
528+
executeInput := &cloudformation.ExecuteChangeSetInput{
529+
ChangeSetName: aws.String(changeSetID),
530+
}
531+
532+
expectedError := errors.New("changeset execution failed")
533+
534+
mockClient.On("ExecuteChangeSet", ctx, executeInput).Return((*cloudformation.ExecuteChangeSetOutput)(nil), expectedError)
535+
536+
output, err := cfOps.ExecuteChangeSet(ctx, executeInput)
537+
538+
assert.Nil(t, output)
539+
assert.Error(t, err)
540+
assert.Equal(t, expectedError, err)
541+
mockClient.AssertExpectations(t)
542+
}

internal/aws/interfaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type CloudFormationClient interface {
2121
ValidateTemplate(ctx context.Context, params *cloudformation.ValidateTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ValidateTemplateOutput, error)
2222
GetTemplate(ctx context.Context, params *cloudformation.GetTemplateInput, optFns ...func(*cloudformation.Options)) (*cloudformation.GetTemplateOutput, error)
2323
CreateChangeSet(ctx context.Context, params *cloudformation.CreateChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.CreateChangeSetOutput, error)
24+
ExecuteChangeSet(ctx context.Context, params *cloudformation.ExecuteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ExecuteChangeSetOutput, error)
2425
DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error)
2526
DescribeChangeSet(ctx context.Context, params *cloudformation.DescribeChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeChangeSetOutput, error)
2627
DescribeStackEvents(ctx context.Context, params *cloudformation.DescribeStackEventsInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeStackEventsOutput, error)
@@ -53,6 +54,7 @@ type CloudFormationOperations interface {
5354
GetTemplate(ctx context.Context, stackName string) (string, error)
5455
DescribeStack(ctx context.Context, stackName string) (*StackInfo, error)
5556
CreateChangeSet(ctx context.Context, params *cloudformation.CreateChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.CreateChangeSetOutput, error)
57+
ExecuteChangeSet(ctx context.Context, params *cloudformation.ExecuteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.ExecuteChangeSetOutput, error)
5658
DeleteChangeSet(ctx context.Context, params *cloudformation.DeleteChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DeleteChangeSetOutput, error)
5759
DescribeChangeSet(ctx context.Context, params *cloudformation.DescribeChangeSetInput, optFns ...func(*cloudformation.Options)) (*cloudformation.DescribeChangeSetOutput, error)
5860
DescribeStackEvents(ctx context.Context, stackName string) ([]StackEvent, error)

0 commit comments

Comments
 (0)