@friggframework/devtools 2.0.0-next.47 → 2.0.0-next.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/frigg-cli/README.md +1290 -0
- package/frigg-cli/__tests__/unit/commands/build.test.js +279 -0
- package/frigg-cli/__tests__/unit/commands/db-setup.test.js +548 -0
- package/frigg-cli/__tests__/unit/commands/deploy.test.js +320 -0
- package/frigg-cli/__tests__/unit/commands/doctor.test.js +309 -0
- package/frigg-cli/__tests__/unit/commands/install.test.js +400 -0
- package/frigg-cli/__tests__/unit/commands/ui.test.js +346 -0
- package/frigg-cli/__tests__/unit/dependencies.test.js +74 -0
- package/frigg-cli/__tests__/unit/utils/database-validator.test.js +366 -0
- package/frigg-cli/__tests__/unit/utils/error-messages.test.js +304 -0
- package/frigg-cli/__tests__/unit/version-detection.test.js +171 -0
- package/frigg-cli/__tests__/utils/mock-factory.js +270 -0
- package/frigg-cli/__tests__/utils/prisma-mock.js +194 -0
- package/frigg-cli/__tests__/utils/test-fixtures.js +463 -0
- package/frigg-cli/__tests__/utils/test-setup.js +287 -0
- package/frigg-cli/build-command/index.js +66 -0
- package/frigg-cli/db-setup-command/index.js +193 -0
- package/frigg-cli/deploy-command/SPEC-DEPLOY-DRY-RUN.md +981 -0
- package/frigg-cli/deploy-command/index.js +302 -0
- package/frigg-cli/doctor-command/index.js +335 -0
- package/frigg-cli/generate-command/__tests__/generate-command.test.js +301 -0
- package/frigg-cli/generate-command/azure-generator.js +43 -0
- package/frigg-cli/generate-command/gcp-generator.js +47 -0
- package/frigg-cli/generate-command/index.js +332 -0
- package/frigg-cli/generate-command/terraform-generator.js +555 -0
- package/frigg-cli/generate-iam-command.js +118 -0
- package/frigg-cli/index.js +173 -0
- package/frigg-cli/index.test.js +158 -0
- package/frigg-cli/init-command/backend-first-handler.js +756 -0
- package/frigg-cli/init-command/index.js +93 -0
- package/frigg-cli/init-command/template-handler.js +143 -0
- package/frigg-cli/install-command/backend-js.js +33 -0
- package/frigg-cli/install-command/commit-changes.js +16 -0
- package/frigg-cli/install-command/environment-variables.js +127 -0
- package/frigg-cli/install-command/environment-variables.test.js +136 -0
- package/frigg-cli/install-command/index.js +54 -0
- package/frigg-cli/install-command/install-package.js +13 -0
- package/frigg-cli/install-command/integration-file.js +30 -0
- package/frigg-cli/install-command/logger.js +12 -0
- package/frigg-cli/install-command/template.js +90 -0
- package/frigg-cli/install-command/validate-package.js +75 -0
- package/frigg-cli/jest.config.js +124 -0
- package/frigg-cli/package.json +63 -0
- package/frigg-cli/repair-command/index.js +564 -0
- package/frigg-cli/start-command/index.js +149 -0
- package/frigg-cli/start-command/start-command.test.js +297 -0
- package/frigg-cli/test/init-command.test.js +180 -0
- package/frigg-cli/test/npm-registry.test.js +319 -0
- package/frigg-cli/ui-command/index.js +154 -0
- package/frigg-cli/utils/app-resolver.js +319 -0
- package/frigg-cli/utils/backend-path.js +25 -0
- package/frigg-cli/utils/database-validator.js +154 -0
- package/frigg-cli/utils/error-messages.js +257 -0
- package/frigg-cli/utils/npm-registry.js +167 -0
- package/frigg-cli/utils/process-manager.js +199 -0
- package/frigg-cli/utils/repo-detection.js +405 -0
- package/infrastructure/create-frigg-infrastructure.js +125 -12
- package/infrastructure/docs/PRE-DEPLOYMENT-HEALTH-CHECK-SPEC.md +1317 -0
- package/infrastructure/domains/shared/resource-discovery.enhanced.test.js +306 -0
- package/infrastructure/domains/shared/resource-discovery.js +31 -2
- package/infrastructure/domains/shared/utilities/base-definition-factory.js +1 -1
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.js +109 -5
- package/infrastructure/domains/shared/utilities/prisma-layer-manager.test.js +310 -4
- package/infrastructure/domains/shared/validation/plugin-validator.js +187 -0
- package/infrastructure/domains/shared/validation/plugin-validator.test.js +323 -0
- package/infrastructure/infrastructure-composer.js +22 -0
- package/layers/prisma/.build-complete +3 -0
- package/package.json +18 -7
- package/management-ui/package-lock.json +0 -16517
|
@@ -0,0 +1,981 @@
|
|
|
1
|
+
# Specification: Deploy Dry-Run Mode
|
|
2
|
+
|
|
3
|
+
**Version**: 1.0.0
|
|
4
|
+
**Status**: Draft
|
|
5
|
+
**Created**: 2025-10-28
|
|
6
|
+
**Author**: Claude Code
|
|
7
|
+
**Related**: [SPEC-CLEANUP-COMMAND.md](../../devtools/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md)
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
Add a `--dry-run` flag to the `frigg deploy` command to preview deployment changes before executing them. This allows users to validate infrastructure changes, verify CloudFormation template generation, check AWS resource discovery results, and review estimated costs without actually deploying.
|
|
12
|
+
|
|
13
|
+
## Business Context
|
|
14
|
+
|
|
15
|
+
### Problem Statement
|
|
16
|
+
|
|
17
|
+
Currently, `frigg deploy` immediately executes deployments without giving users a chance to:
|
|
18
|
+
- Preview what CloudFormation changes will be made
|
|
19
|
+
- Validate generated serverless.yml configuration
|
|
20
|
+
- Review AWS resource discovery results (VPC, subnets, KMS keys)
|
|
21
|
+
- Check for potential issues before deployment
|
|
22
|
+
- Estimate costs of infrastructure changes
|
|
23
|
+
- Verify environment variable configuration
|
|
24
|
+
|
|
25
|
+
This leads to:
|
|
26
|
+
- Unexpected resource creation/modification
|
|
27
|
+
- Wasted time rolling back incorrect deployments
|
|
28
|
+
- Difficulty troubleshooting deployment failures
|
|
29
|
+
- Anxiety about deploying to production
|
|
30
|
+
- Lack of visibility into what the framework is doing
|
|
31
|
+
|
|
32
|
+
### User Story
|
|
33
|
+
|
|
34
|
+
> As a developer deploying a Frigg application
|
|
35
|
+
> I want to preview deployment changes before executing them
|
|
36
|
+
> So that I can validate the infrastructure configuration and avoid costly mistakes
|
|
37
|
+
> While maintaining confidence in my deployments
|
|
38
|
+
|
|
39
|
+
### Real-World Scenarios
|
|
40
|
+
|
|
41
|
+
**Scenario 1: First Production Deployment**
|
|
42
|
+
```bash
|
|
43
|
+
# Developer wants to see what will be created
|
|
44
|
+
frigg deploy --stage prod --dry-run
|
|
45
|
+
|
|
46
|
+
# Output shows:
|
|
47
|
+
# - VPC configuration discovered
|
|
48
|
+
# - 12 Lambda functions to be created
|
|
49
|
+
# - API Gateway endpoints
|
|
50
|
+
# - Estimated monthly cost: $145
|
|
51
|
+
# - Change set preview with resource actions
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Scenario 2: VPC Migration**
|
|
55
|
+
```bash
|
|
56
|
+
# Changing VPC configuration after AWS resource discovery
|
|
57
|
+
frigg deploy --stage dev --dry-run
|
|
58
|
+
|
|
59
|
+
# Output shows:
|
|
60
|
+
# - VPC change detected (vpc-old → vpc-new)
|
|
61
|
+
# - All Lambda functions will be updated (recreation required)
|
|
62
|
+
# - Potential downtime: 3-5 minutes
|
|
63
|
+
# - Dependencies: 17 Lambda functions, 2 security groups
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Scenario 3: Environment Variable Check**
|
|
67
|
+
```bash
|
|
68
|
+
# Verify environment variables before deployment
|
|
69
|
+
frigg deploy --stage prod --dry-run
|
|
70
|
+
|
|
71
|
+
# Output shows:
|
|
72
|
+
# - ✓ All 8 required environment variables present
|
|
73
|
+
# - ⚠️ 2 optional variables missing: SENTRY_DSN, NEW_RELIC_KEY
|
|
74
|
+
# - Template preview generated successfully
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Requirements
|
|
78
|
+
|
|
79
|
+
### Functional Requirements
|
|
80
|
+
|
|
81
|
+
#### FR-1: Command Interface
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# Dry-run mode (preview without deploying)
|
|
85
|
+
frigg deploy --dry-run
|
|
86
|
+
frigg deploy --stage prod --dry-run
|
|
87
|
+
|
|
88
|
+
# All existing flags work with dry-run
|
|
89
|
+
frigg deploy --dry-run --verbose
|
|
90
|
+
frigg deploy --dry-run --skip-env-validation
|
|
91
|
+
frigg deploy --dry-run --skip-doctor
|
|
92
|
+
|
|
93
|
+
# JSON output for CI/CD pipelines
|
|
94
|
+
frigg deploy --dry-run --output json
|
|
95
|
+
|
|
96
|
+
# Compare with specific CloudFormation stack
|
|
97
|
+
frigg deploy --dry-run --compare-stack my-app-prod
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### FR-2: Dry-Run Execution Flow
|
|
101
|
+
|
|
102
|
+
**Phase 1: Pre-Flight Checks**
|
|
103
|
+
1. Load app definition from `index.js`
|
|
104
|
+
2. Validate app definition structure
|
|
105
|
+
3. Check for required files (package.json, serverless.yml if exists)
|
|
106
|
+
4. Extract environment variable configuration
|
|
107
|
+
|
|
108
|
+
**Phase 2: Environment Validation**
|
|
109
|
+
1. Check for app-defined environment variables
|
|
110
|
+
2. Report missing optional variables (warnings only)
|
|
111
|
+
3. Check AWS credentials availability
|
|
112
|
+
4. Verify AWS account access
|
|
113
|
+
|
|
114
|
+
**Phase 3: AWS Resource Discovery** (if applicable)
|
|
115
|
+
1. Run AWS resource discovery if VPC/KMS/SSM enabled
|
|
116
|
+
2. Display discovered resources:
|
|
117
|
+
- VPC ID and CIDR block
|
|
118
|
+
- Private subnet IDs
|
|
119
|
+
- Security group IDs
|
|
120
|
+
- KMS key ARN
|
|
121
|
+
- Route table IDs
|
|
122
|
+
3. Show any discovery warnings or fallbacks
|
|
123
|
+
|
|
124
|
+
**Phase 4: Template Generation**
|
|
125
|
+
1. Generate serverless.yml from app definition
|
|
126
|
+
2. Apply AWS discovery results to template
|
|
127
|
+
3. Resolve all environment variables
|
|
128
|
+
4. Display generated template summary:
|
|
129
|
+
- Service name and stage
|
|
130
|
+
- Provider configuration (region, runtime, etc.)
|
|
131
|
+
- Function count and names
|
|
132
|
+
- API Gateway endpoints
|
|
133
|
+
- Custom resources
|
|
134
|
+
- Environment variables (with values masked)
|
|
135
|
+
|
|
136
|
+
**Phase 5: CloudFormation Change Set Preview**
|
|
137
|
+
1. Create a CloudFormation change set (if stack exists)
|
|
138
|
+
2. Display planned changes:
|
|
139
|
+
- Resources to be added (green)
|
|
140
|
+
- Resources to be modified (yellow)
|
|
141
|
+
- Resources to be removed (red)
|
|
142
|
+
- Resource replacements (requires recreation)
|
|
143
|
+
3. Show change set details:
|
|
144
|
+
- Logical ID
|
|
145
|
+
- Physical ID (if exists)
|
|
146
|
+
- Resource type
|
|
147
|
+
- Action (Add, Modify, Remove, Replace)
|
|
148
|
+
- Replacement reason (if applicable)
|
|
149
|
+
4. Estimate deployment impact:
|
|
150
|
+
- Estimated downtime
|
|
151
|
+
- Functions requiring cold start
|
|
152
|
+
- Breaking changes detected
|
|
153
|
+
|
|
154
|
+
**Phase 6: Cost Estimation** (optional, future enhancement)
|
|
155
|
+
1. Estimate monthly infrastructure costs
|
|
156
|
+
2. Show cost breakdown by resource type
|
|
157
|
+
3. Compare with current stack costs (if exists)
|
|
158
|
+
|
|
159
|
+
**Phase 7: Summary Report**
|
|
160
|
+
1. Display comprehensive summary:
|
|
161
|
+
- Stack name and region
|
|
162
|
+
- Change summary (X added, Y modified, Z removed)
|
|
163
|
+
- Warnings and recommendations
|
|
164
|
+
- Next steps to execute deployment
|
|
165
|
+
2. Exit with status code:
|
|
166
|
+
- `0` = Dry-run successful, ready to deploy
|
|
167
|
+
- `1` = Validation errors, cannot deploy
|
|
168
|
+
- `2` = Warnings present, review before deploying
|
|
169
|
+
|
|
170
|
+
#### FR-3: Output Formats
|
|
171
|
+
|
|
172
|
+
**Console Output (Default)**
|
|
173
|
+
```
|
|
174
|
+
🔍 Frigg Deploy Dry-Run
|
|
175
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
176
|
+
|
|
177
|
+
📋 App Configuration
|
|
178
|
+
Service: my-integration
|
|
179
|
+
Stage: production
|
|
180
|
+
Region: us-east-1
|
|
181
|
+
Runtime: nodejs20.x
|
|
182
|
+
|
|
183
|
+
🔧 Environment Variables
|
|
184
|
+
✓ 8 required variables present
|
|
185
|
+
⚠️ 2 optional variables missing:
|
|
186
|
+
- SENTRY_DSN
|
|
187
|
+
- NEW_RELIC_KEY
|
|
188
|
+
|
|
189
|
+
🌐 AWS Resource Discovery
|
|
190
|
+
✓ VPC: vpc-12345678 (10.0.0.0/16)
|
|
191
|
+
✓ Subnets: subnet-11111111, subnet-22222222
|
|
192
|
+
✓ Security Group: sg-12345678
|
|
193
|
+
✓ KMS Key: arn:aws:kms:us-east-1:123456789012:key/...
|
|
194
|
+
|
|
195
|
+
📦 Generated Template Summary
|
|
196
|
+
Functions: 12
|
|
197
|
+
- health (256MB, 30s timeout)
|
|
198
|
+
- user (512MB, 30s timeout)
|
|
199
|
+
- integration-hubspot (1024MB, 60s timeout)
|
|
200
|
+
...
|
|
201
|
+
|
|
202
|
+
API Endpoints: 15
|
|
203
|
+
- GET /health
|
|
204
|
+
- POST /api/integrations
|
|
205
|
+
- GET /api/integrations/{id}
|
|
206
|
+
...
|
|
207
|
+
|
|
208
|
+
Custom Resources: 3
|
|
209
|
+
- MongoDB Connection
|
|
210
|
+
- WebSocket Connection Manager
|
|
211
|
+
- S3 Deployment Bucket
|
|
212
|
+
|
|
213
|
+
🔄 CloudFormation Change Set Preview
|
|
214
|
+
Stack: my-integration-production
|
|
215
|
+
Change Set: frigg-dry-run-20251028-143022
|
|
216
|
+
|
|
217
|
+
Changes:
|
|
218
|
+
✓ Add (5):
|
|
219
|
+
- HealthLambdaFunction (AWS::Lambda::Function)
|
|
220
|
+
- UserLambdaFunction (AWS::Lambda::Function)
|
|
221
|
+
- ApiGatewayRestApi (AWS::ApiGateway::RestApi)
|
|
222
|
+
- DeploymentBucket (AWS::S3::Bucket)
|
|
223
|
+
- LambdaExecutionRole (AWS::IAM::Role)
|
|
224
|
+
|
|
225
|
+
⚠️ Modify (7):
|
|
226
|
+
- IntegrationLambdaFunction (AWS::Lambda::Function)
|
|
227
|
+
• VpcConfig.SubnetIds: [subnet-old1, subnet-old2] → [subnet-11111111, subnet-22222222]
|
|
228
|
+
• Environment.Variables.VPC_ID: vpc-old → vpc-12345678
|
|
229
|
+
- HealthLambdaFunction (AWS::Lambda::Function)
|
|
230
|
+
• VpcConfig.SecurityGroupIds: [sg-old] → [sg-12345678]
|
|
231
|
+
...
|
|
232
|
+
|
|
233
|
+
🔄 Replace (2):
|
|
234
|
+
- DatabaseSecurityGroup (AWS::EC2::SecurityGroup)
|
|
235
|
+
Reason: VpcId property change requires replacement
|
|
236
|
+
- PrivateSubnet1RouteTableAssociation (AWS::EC2::SubnetRouteTableAssociation)
|
|
237
|
+
Reason: SubnetId property change requires replacement
|
|
238
|
+
|
|
239
|
+
⚠️ Remove (0)
|
|
240
|
+
|
|
241
|
+
📊 Deployment Impact
|
|
242
|
+
Estimated Downtime: 2-3 minutes
|
|
243
|
+
Functions Affected: 12/12
|
|
244
|
+
Cold Starts Expected: All functions
|
|
245
|
+
Breaking Changes: None detected
|
|
246
|
+
|
|
247
|
+
💰 Estimated Monthly Cost
|
|
248
|
+
Lambda Compute: ~$23
|
|
249
|
+
API Gateway: ~$18
|
|
250
|
+
CloudWatch Logs: ~$5
|
|
251
|
+
S3 Storage: ~$2
|
|
252
|
+
Total: ~$48/month
|
|
253
|
+
|
|
254
|
+
✅ Dry-Run Summary
|
|
255
|
+
✓ App definition valid
|
|
256
|
+
✓ AWS resources discovered successfully
|
|
257
|
+
✓ Template generated successfully
|
|
258
|
+
✓ Change set created successfully
|
|
259
|
+
⚠️ 2 optional environment variables missing
|
|
260
|
+
⚠️ 2 resources will be replaced (potential downtime)
|
|
261
|
+
|
|
262
|
+
Next Steps:
|
|
263
|
+
To execute this deployment, run:
|
|
264
|
+
frigg deploy --stage production
|
|
265
|
+
|
|
266
|
+
To skip environment validation, run:
|
|
267
|
+
frigg deploy --stage production --skip-env-validation
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
**JSON Output (for CI/CD)**
|
|
271
|
+
```json
|
|
272
|
+
{
|
|
273
|
+
"dryRun": true,
|
|
274
|
+
"timestamp": "2025-10-28T14:30:22Z",
|
|
275
|
+
"app": {
|
|
276
|
+
"service": "my-integration",
|
|
277
|
+
"stage": "production",
|
|
278
|
+
"region": "us-east-1",
|
|
279
|
+
"runtime": "nodejs20.x"
|
|
280
|
+
},
|
|
281
|
+
"environment": {
|
|
282
|
+
"required": {
|
|
283
|
+
"present": ["AWS_REGION", "STAGE", "DB_URI", "ENCRYPTION_KEY", "JWT_SECRET", "API_KEY", "WEBHOOK_SECRET", "LOG_LEVEL"],
|
|
284
|
+
"missing": []
|
|
285
|
+
},
|
|
286
|
+
"optional": {
|
|
287
|
+
"present": [],
|
|
288
|
+
"missing": ["SENTRY_DSN", "NEW_RELIC_KEY"]
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
"discovery": {
|
|
292
|
+
"enabled": true,
|
|
293
|
+
"results": {
|
|
294
|
+
"vpc": {
|
|
295
|
+
"id": "vpc-12345678",
|
|
296
|
+
"cidr": "10.0.0.0/16"
|
|
297
|
+
},
|
|
298
|
+
"subnets": ["subnet-11111111", "subnet-22222222"],
|
|
299
|
+
"securityGroups": ["sg-12345678"],
|
|
300
|
+
"kmsKey": "arn:aws:kms:us-east-1:123456789012:key/..."
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
"template": {
|
|
304
|
+
"service": "my-integration-production",
|
|
305
|
+
"functions": {
|
|
306
|
+
"count": 12,
|
|
307
|
+
"names": ["health", "user", "integration-hubspot"]
|
|
308
|
+
},
|
|
309
|
+
"endpoints": {
|
|
310
|
+
"count": 15,
|
|
311
|
+
"methods": ["GET /health", "POST /api/integrations"]
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
"changeSet": {
|
|
315
|
+
"stackName": "my-integration-production",
|
|
316
|
+
"changeSetId": "arn:aws:cloudformation:...",
|
|
317
|
+
"status": "CREATE_COMPLETE",
|
|
318
|
+
"changes": [
|
|
319
|
+
{
|
|
320
|
+
"action": "Add",
|
|
321
|
+
"logicalId": "HealthLambdaFunction",
|
|
322
|
+
"resourceType": "AWS::Lambda::Function",
|
|
323
|
+
"replacement": null
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"action": "Modify",
|
|
327
|
+
"logicalId": "IntegrationLambdaFunction",
|
|
328
|
+
"resourceType": "AWS::Lambda::Function",
|
|
329
|
+
"replacement": null,
|
|
330
|
+
"details": [
|
|
331
|
+
{
|
|
332
|
+
"target": "Properties",
|
|
333
|
+
"attribute": "VpcConfig.SubnetIds",
|
|
334
|
+
"changeSource": "DirectModification"
|
|
335
|
+
}
|
|
336
|
+
]
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
"summary": {
|
|
340
|
+
"add": 5,
|
|
341
|
+
"modify": 7,
|
|
342
|
+
"remove": 0,
|
|
343
|
+
"replace": 2
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
"impact": {
|
|
347
|
+
"downtime": "2-3 minutes",
|
|
348
|
+
"functionsAffected": 12,
|
|
349
|
+
"coldStarts": true,
|
|
350
|
+
"breakingChanges": false
|
|
351
|
+
},
|
|
352
|
+
"cost": {
|
|
353
|
+
"lambda": 23,
|
|
354
|
+
"apiGateway": 18,
|
|
355
|
+
"cloudWatch": 5,
|
|
356
|
+
"s3": 2,
|
|
357
|
+
"total": 48,
|
|
358
|
+
"currency": "USD",
|
|
359
|
+
"period": "monthly"
|
|
360
|
+
},
|
|
361
|
+
"validation": {
|
|
362
|
+
"success": true,
|
|
363
|
+
"errors": [],
|
|
364
|
+
"warnings": [
|
|
365
|
+
"2 optional environment variables missing",
|
|
366
|
+
"2 resources will be replaced"
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
"exitCode": 2
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
#### FR-4: Change Set Management
|
|
374
|
+
|
|
375
|
+
**Change Set Creation**:
|
|
376
|
+
```javascript
|
|
377
|
+
// Create a temporary change set for preview
|
|
378
|
+
const changeSetName = `frigg-dry-run-${Date.now()}`;
|
|
379
|
+
const changeSet = await cloudFormation.createChangeSet({
|
|
380
|
+
StackName: stackName,
|
|
381
|
+
ChangeSetName: changeSetName,
|
|
382
|
+
TemplateBody: generatedTemplate,
|
|
383
|
+
ChangeSetType: stackExists ? 'UPDATE' : 'CREATE',
|
|
384
|
+
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
|
|
385
|
+
Parameters: parameters,
|
|
386
|
+
Tags: tags,
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
// Wait for change set creation
|
|
390
|
+
await cloudFormation.waitFor('changeSetCreateComplete', {
|
|
391
|
+
StackName: stackName,
|
|
392
|
+
ChangeSetName: changeSetName,
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
// Retrieve change set details
|
|
396
|
+
const changeSetDetails = await cloudFormation.describeChangeSet({
|
|
397
|
+
StackName: stackName,
|
|
398
|
+
ChangeSetName: changeSetName,
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
// Clean up change set after preview
|
|
402
|
+
await cloudFormation.deleteChangeSet({
|
|
403
|
+
StackName: stackName,
|
|
404
|
+
ChangeSetName: changeSetName,
|
|
405
|
+
});
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Change Set Analysis**:
|
|
409
|
+
```javascript
|
|
410
|
+
function analyzeChangeSet(changeSet) {
|
|
411
|
+
const summary = {
|
|
412
|
+
add: 0,
|
|
413
|
+
modify: 0,
|
|
414
|
+
remove: 0,
|
|
415
|
+
replace: 0,
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
const criticalChanges = [];
|
|
419
|
+
const warnings = [];
|
|
420
|
+
|
|
421
|
+
for (const change of changeSet.Changes) {
|
|
422
|
+
const { Action, ResourceChange } = change;
|
|
423
|
+
|
|
424
|
+
// Count actions
|
|
425
|
+
if (Action === 'Add') summary.add++;
|
|
426
|
+
if (Action === 'Modify') summary.modify++;
|
|
427
|
+
if (Action === 'Remove') summary.remove++;
|
|
428
|
+
if (ResourceChange?.Replacement === 'True') summary.replace++;
|
|
429
|
+
|
|
430
|
+
// Detect critical changes
|
|
431
|
+
if (ResourceChange?.Replacement === 'True') {
|
|
432
|
+
criticalChanges.push({
|
|
433
|
+
logicalId: ResourceChange.LogicalResourceId,
|
|
434
|
+
resourceType: ResourceChange.ResourceType,
|
|
435
|
+
reason: 'Requires replacement',
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// Detect VPC changes (high impact)
|
|
440
|
+
if (ResourceChange?.ResourceType === 'AWS::Lambda::Function') {
|
|
441
|
+
const vpcChange = ResourceChange.Details?.find(
|
|
442
|
+
(d) => d.Target?.Attribute === 'VpcConfig'
|
|
443
|
+
);
|
|
444
|
+
if (vpcChange) {
|
|
445
|
+
warnings.push({
|
|
446
|
+
logicalId: ResourceChange.LogicalResourceId,
|
|
447
|
+
message: 'VPC configuration change - function will be recreated',
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return { summary, criticalChanges, warnings };
|
|
454
|
+
}
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
#### FR-5: Integration with Existing Flags
|
|
458
|
+
|
|
459
|
+
All existing `frigg deploy` flags work with `--dry-run`:
|
|
460
|
+
|
|
461
|
+
**Environment Validation**:
|
|
462
|
+
```bash
|
|
463
|
+
# Skip environment validation in dry-run
|
|
464
|
+
frigg deploy --dry-run --skip-env-validation
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Health Check Skip**:
|
|
468
|
+
```bash
|
|
469
|
+
# Skip post-deployment health check (not applicable in dry-run)
|
|
470
|
+
frigg deploy --dry-run --skip-doctor
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
**Verbose Output**:
|
|
474
|
+
```bash
|
|
475
|
+
# Show detailed logs during dry-run
|
|
476
|
+
frigg deploy --dry-run --verbose
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
**Force Deployment**:
|
|
480
|
+
```bash
|
|
481
|
+
# Force flag ignored in dry-run (cannot force a preview)
|
|
482
|
+
frigg deploy --dry-run --force
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Non-Functional Requirements
|
|
486
|
+
|
|
487
|
+
#### NFR-1: Performance
|
|
488
|
+
|
|
489
|
+
- Dry-run execution must complete within 30 seconds for small stacks (<20 resources)
|
|
490
|
+
- Dry-run execution must complete within 60 seconds for large stacks (50+ resources)
|
|
491
|
+
- AWS resource discovery should be cached for 5 minutes to speed up repeated dry-runs
|
|
492
|
+
- Change set creation timeout: 5 minutes (CloudFormation standard)
|
|
493
|
+
|
|
494
|
+
#### NFR-2: Safety
|
|
495
|
+
|
|
496
|
+
- **CRITICAL**: Dry-run must NEVER modify any AWS resources
|
|
497
|
+
- Change sets created for preview must be automatically deleted after display
|
|
498
|
+
- No side effects from running dry-run multiple times
|
|
499
|
+
- Failed dry-run must not leave CloudFormation in inconsistent state
|
|
500
|
+
|
|
501
|
+
#### NFR-3: Usability
|
|
502
|
+
|
|
503
|
+
- Output must be clear and actionable
|
|
504
|
+
- Warnings must be visually distinct from errors
|
|
505
|
+
- Critical changes (replacements) must be highlighted
|
|
506
|
+
- Next steps must be provided at end of output
|
|
507
|
+
- JSON output must be parseable by standard JSON tools
|
|
508
|
+
|
|
509
|
+
#### NFR-4: Compatibility
|
|
510
|
+
|
|
511
|
+
- Must work with all existing Frigg app definitions
|
|
512
|
+
- Must support both create and update operations
|
|
513
|
+
- Must work with AWS discovery enabled/disabled
|
|
514
|
+
- Must integrate with existing serverless.yml generation
|
|
515
|
+
|
|
516
|
+
## Implementation Design
|
|
517
|
+
|
|
518
|
+
### Architecture
|
|
519
|
+
|
|
520
|
+
```
|
|
521
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
522
|
+
│ frigg deploy --dry-run │
|
|
523
|
+
└────────────────────┬────────────────────────────────────────┘
|
|
524
|
+
│
|
|
525
|
+
▼
|
|
526
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
527
|
+
│ DryRunOrchestrator │
|
|
528
|
+
│ - Coordinates dry-run workflow │
|
|
529
|
+
│ - Manages phase execution │
|
|
530
|
+
│ - Collects results │
|
|
531
|
+
└────────────┬────────────────────────────────────────────────┘
|
|
532
|
+
│
|
|
533
|
+
├──▶ PreFlightChecker
|
|
534
|
+
│ - Load app definition
|
|
535
|
+
│ - Validate structure
|
|
536
|
+
│ - Check required files
|
|
537
|
+
│
|
|
538
|
+
├──▶ EnvironmentValidator
|
|
539
|
+
│ - Check environment variables
|
|
540
|
+
│ - Validate AWS credentials
|
|
541
|
+
│ - Verify account access
|
|
542
|
+
│
|
|
543
|
+
├──▶ AWSDiscoveryRunner (if enabled)
|
|
544
|
+
│ - Discover VPC resources
|
|
545
|
+
│ - Find KMS keys
|
|
546
|
+
│ - Locate subnets/security groups
|
|
547
|
+
│
|
|
548
|
+
├──▶ TemplateGenerator
|
|
549
|
+
│ - Generate serverless.yml
|
|
550
|
+
│ - Apply discovery results
|
|
551
|
+
│ - Resolve environment variables
|
|
552
|
+
│
|
|
553
|
+
├──▶ ChangeSetCreator
|
|
554
|
+
│ - Create CloudFormation change set
|
|
555
|
+
│ - Wait for completion
|
|
556
|
+
│ - Retrieve change details
|
|
557
|
+
│ - Analyze changes
|
|
558
|
+
│ - Delete change set (cleanup)
|
|
559
|
+
│
|
|
560
|
+
├──▶ CostEstimator (future)
|
|
561
|
+
│ - Estimate resource costs
|
|
562
|
+
│ - Compare with current costs
|
|
563
|
+
│ - Show cost breakdown
|
|
564
|
+
│
|
|
565
|
+
└──▶ DryRunReporter
|
|
566
|
+
- Format output
|
|
567
|
+
- Display summary
|
|
568
|
+
- Provide next steps
|
|
569
|
+
- Return exit code
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
### File Structure
|
|
573
|
+
|
|
574
|
+
```
|
|
575
|
+
packages/frigg-cli/deploy-command/
|
|
576
|
+
├── index.js # Main deploy command (modified)
|
|
577
|
+
├── dry-run/
|
|
578
|
+
│ ├── orchestrator.js # DryRunOrchestrator
|
|
579
|
+
│ ├── pre-flight-checker.js # PreFlightChecker
|
|
580
|
+
│ ├── environment-validator.js # EnvironmentValidator
|
|
581
|
+
│ ├── template-generator.js # TemplateGenerator (uses existing)
|
|
582
|
+
│ ├── change-set-creator.js # ChangeSetCreator
|
|
583
|
+
│ ├── change-set-analyzer.js # ChangeSetAnalyzer
|
|
584
|
+
│ ├── dry-run-reporter.js # DryRunReporter
|
|
585
|
+
│ └── __tests__/
|
|
586
|
+
│ ├── orchestrator.test.js
|
|
587
|
+
│ ├── change-set-creator.test.js
|
|
588
|
+
│ └── dry-run-reporter.test.js
|
|
589
|
+
└── SPEC-DEPLOY-DRY-RUN.md # This specification
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
### Key Classes
|
|
593
|
+
|
|
594
|
+
#### DryRunOrchestrator
|
|
595
|
+
|
|
596
|
+
```javascript
|
|
597
|
+
class DryRunOrchestrator {
|
|
598
|
+
constructor({ appDefinition, options }) {
|
|
599
|
+
this.appDefinition = appDefinition;
|
|
600
|
+
this.options = options;
|
|
601
|
+
this.results = {};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
async execute() {
|
|
605
|
+
// Phase 1: Pre-flight checks
|
|
606
|
+
this.results.preFlight = await this.runPreFlightChecks();
|
|
607
|
+
|
|
608
|
+
// Phase 2: Environment validation
|
|
609
|
+
this.results.environment = await this.validateEnvironment();
|
|
610
|
+
|
|
611
|
+
// Phase 3: AWS discovery (if enabled)
|
|
612
|
+
if (this.shouldRunDiscovery()) {
|
|
613
|
+
this.results.discovery = await this.runDiscovery();
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Phase 4: Template generation
|
|
617
|
+
this.results.template = await this.generateTemplate();
|
|
618
|
+
|
|
619
|
+
// Phase 5: Change set preview
|
|
620
|
+
this.results.changeSet = await this.createChangeSetPreview();
|
|
621
|
+
|
|
622
|
+
// Phase 6: Cost estimation (future)
|
|
623
|
+
// this.results.cost = await this.estimateCosts();
|
|
624
|
+
|
|
625
|
+
// Phase 7: Report
|
|
626
|
+
return this.generateReport();
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
shouldRunDiscovery() {
|
|
630
|
+
return (
|
|
631
|
+
this.appDefinition.vpc?.enable === true ||
|
|
632
|
+
this.appDefinition.encryption?.useDefaultKMSForFieldLevelEncryption === true ||
|
|
633
|
+
this.appDefinition.ssm?.enable === true
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
#### ChangeSetCreator
|
|
640
|
+
|
|
641
|
+
```javascript
|
|
642
|
+
class ChangeSetCreator {
|
|
643
|
+
constructor({ cloudFormation, stackName, region }) {
|
|
644
|
+
this.cloudFormation = cloudFormation;
|
|
645
|
+
this.stackName = stackName;
|
|
646
|
+
this.region = region;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
async createPreview(template, parameters, tags) {
|
|
650
|
+
// Check if stack exists
|
|
651
|
+
const stackExists = await this.checkStackExists();
|
|
652
|
+
|
|
653
|
+
// Create change set
|
|
654
|
+
const changeSetName = `frigg-dry-run-${Date.now()}`;
|
|
655
|
+
const changeSet = await this.cloudFormation.createChangeSet({
|
|
656
|
+
StackName: this.stackName,
|
|
657
|
+
ChangeSetName: changeSetName,
|
|
658
|
+
TemplateBody: JSON.stringify(template),
|
|
659
|
+
ChangeSetType: stackExists ? 'UPDATE' : 'CREATE',
|
|
660
|
+
Capabilities: ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
|
|
661
|
+
Parameters: parameters,
|
|
662
|
+
Tags: tags,
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Wait for change set creation
|
|
666
|
+
await this.waitForChangeSet(changeSetName);
|
|
667
|
+
|
|
668
|
+
// Get change set details
|
|
669
|
+
const details = await this.getChangeSetDetails(changeSetName);
|
|
670
|
+
|
|
671
|
+
// Clean up
|
|
672
|
+
await this.deleteChangeSet(changeSetName);
|
|
673
|
+
|
|
674
|
+
return details;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async checkStackExists() {
|
|
678
|
+
try {
|
|
679
|
+
await this.cloudFormation.describeStacks({
|
|
680
|
+
StackName: this.stackName,
|
|
681
|
+
});
|
|
682
|
+
return true;
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (error.code === 'ValidationError') {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
async waitForChangeSet(changeSetName, maxWaitTime = 300000) {
|
|
692
|
+
const startTime = Date.now();
|
|
693
|
+
|
|
694
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
695
|
+
const { Status, StatusReason } = await this.cloudFormation.describeChangeSet({
|
|
696
|
+
StackName: this.stackName,
|
|
697
|
+
ChangeSetName: changeSetName,
|
|
698
|
+
});
|
|
699
|
+
|
|
700
|
+
if (Status === 'CREATE_COMPLETE') {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
if (Status === 'FAILED') {
|
|
705
|
+
// "No updates are to be performed" is expected for no-op changes
|
|
706
|
+
if (StatusReason?.includes('No updates are to be performed')) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
throw new Error(`Change set creation failed: ${StatusReason}`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
throw new Error('Change set creation timeout');
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
async deleteChangeSet(changeSetName) {
|
|
719
|
+
try {
|
|
720
|
+
await this.cloudFormation.deleteChangeSet({
|
|
721
|
+
StackName: this.stackName,
|
|
722
|
+
ChangeSetName: changeSetName,
|
|
723
|
+
});
|
|
724
|
+
} catch (error) {
|
|
725
|
+
console.warn(`Warning: Could not delete change set ${changeSetName}:`, error.message);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
```
|
|
730
|
+
|
|
731
|
+
### Integration with Existing Code
|
|
732
|
+
|
|
733
|
+
**Modified: `packages/frigg-cli/deploy-command/index.js`**
|
|
734
|
+
|
|
735
|
+
```javascript
|
|
736
|
+
async function deployCommand(options) {
|
|
737
|
+
// Parse command-line options
|
|
738
|
+
const { stage, dryRun, verbose, skipEnvValidation, skipDoctor, output } = options;
|
|
739
|
+
|
|
740
|
+
// Load app definition
|
|
741
|
+
const appDefinition = loadAppDefinition();
|
|
742
|
+
if (!appDefinition) {
|
|
743
|
+
console.error('❌ Could not load app definition from index.js');
|
|
744
|
+
process.exit(1);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// DRY-RUN MODE
|
|
748
|
+
if (dryRun) {
|
|
749
|
+
console.log('🔍 Running deployment dry-run...\n');
|
|
750
|
+
|
|
751
|
+
const orchestrator = new DryRunOrchestrator({
|
|
752
|
+
appDefinition,
|
|
753
|
+
options: { stage, verbose, skipEnvValidation, output },
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
const results = await orchestrator.execute();
|
|
757
|
+
|
|
758
|
+
// Display results
|
|
759
|
+
const reporter = new DryRunReporter({ format: output || 'console' });
|
|
760
|
+
reporter.display(results);
|
|
761
|
+
|
|
762
|
+
// Exit with appropriate code
|
|
763
|
+
process.exit(results.exitCode);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// NORMAL DEPLOYMENT (existing code)
|
|
767
|
+
// ... existing deployment logic ...
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
## Testing Strategy
|
|
772
|
+
|
|
773
|
+
### Unit Tests
|
|
774
|
+
|
|
775
|
+
**Change Set Creator Tests**:
|
|
776
|
+
```javascript
|
|
777
|
+
describe('ChangeSetCreator', () => {
|
|
778
|
+
it('should create change set for new stack', async () => {
|
|
779
|
+
const creator = new ChangeSetCreator({
|
|
780
|
+
cloudFormation: mockCF,
|
|
781
|
+
stackName: 'test-stack',
|
|
782
|
+
region: 'us-east-1',
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
mockCF.describeStacks.mockRejectedValueOnce(
|
|
786
|
+
new Error('Stack does not exist')
|
|
787
|
+
);
|
|
788
|
+
mockCF.createChangeSet.mockResolvedValueOnce({ Id: 'cs-123' });
|
|
789
|
+
mockCF.describeChangeSet.mockResolvedValueOnce({
|
|
790
|
+
Status: 'CREATE_COMPLETE',
|
|
791
|
+
Changes: [],
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
const result = await creator.createPreview(template, [], []);
|
|
795
|
+
|
|
796
|
+
expect(result.Changes).toBeDefined();
|
|
797
|
+
expect(mockCF.createChangeSet).toHaveBeenCalledWith(
|
|
798
|
+
expect.objectContaining({
|
|
799
|
+
ChangeSetType: 'CREATE',
|
|
800
|
+
})
|
|
801
|
+
);
|
|
802
|
+
});
|
|
803
|
+
|
|
804
|
+
it('should clean up change set after preview', async () => {
|
|
805
|
+
const creator = new ChangeSetCreator({
|
|
806
|
+
cloudFormation: mockCF,
|
|
807
|
+
stackName: 'test-stack',
|
|
808
|
+
region: 'us-east-1',
|
|
809
|
+
});
|
|
810
|
+
|
|
811
|
+
await creator.createPreview(template, [], []);
|
|
812
|
+
|
|
813
|
+
expect(mockCF.deleteChangeSet).toHaveBeenCalled();
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
**Dry-Run Orchestrator Tests**:
|
|
819
|
+
```javascript
|
|
820
|
+
describe('DryRunOrchestrator', () => {
|
|
821
|
+
it('should execute all phases in order', async () => {
|
|
822
|
+
const orchestrator = new DryRunOrchestrator({
|
|
823
|
+
appDefinition: mockAppDef,
|
|
824
|
+
options: { stage: 'dev' },
|
|
825
|
+
});
|
|
826
|
+
|
|
827
|
+
const results = await orchestrator.execute();
|
|
828
|
+
|
|
829
|
+
expect(results.preFlight).toBeDefined();
|
|
830
|
+
expect(results.environment).toBeDefined();
|
|
831
|
+
expect(results.template).toBeDefined();
|
|
832
|
+
expect(results.changeSet).toBeDefined();
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
it('should skip discovery when not enabled', async () => {
|
|
836
|
+
const orchestrator = new DryRunOrchestrator({
|
|
837
|
+
appDefinition: { vpc: { enable: false } },
|
|
838
|
+
options: { stage: 'dev' },
|
|
839
|
+
});
|
|
840
|
+
|
|
841
|
+
const results = await orchestrator.execute();
|
|
842
|
+
|
|
843
|
+
expect(results.discovery).toBeUndefined();
|
|
844
|
+
});
|
|
845
|
+
});
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Integration Tests
|
|
849
|
+
|
|
850
|
+
**End-to-End Dry-Run Test**:
|
|
851
|
+
```javascript
|
|
852
|
+
describe('frigg deploy --dry-run', () => {
|
|
853
|
+
it('should preview deployment without modifying resources', async () => {
|
|
854
|
+
// Create test app definition
|
|
855
|
+
const appDef = {
|
|
856
|
+
name: 'test-app',
|
|
857
|
+
provider: 'aws',
|
|
858
|
+
vpc: { enable: true },
|
|
859
|
+
};
|
|
860
|
+
|
|
861
|
+
// Run dry-run
|
|
862
|
+
const result = await runCommand('frigg deploy --dry-run --stage dev');
|
|
863
|
+
|
|
864
|
+
expect(result.exitCode).toBe(0);
|
|
865
|
+
expect(result.stdout).toContain('Dry-Run Summary');
|
|
866
|
+
expect(result.stdout).toContain('Change Set Preview');
|
|
867
|
+
|
|
868
|
+
// Verify no resources were created
|
|
869
|
+
const stacks = await cloudFormation.listStacks();
|
|
870
|
+
expect(stacks).not.toContain('test-app-dev');
|
|
871
|
+
});
|
|
872
|
+
});
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
## Comparison with SPEC-CLEANUP-COMMAND
|
|
876
|
+
|
|
877
|
+
This spec follows similar patterns to [SPEC-CLEANUP-COMMAND.md](../../devtools/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md):
|
|
878
|
+
|
|
879
|
+
### Similarities
|
|
880
|
+
|
|
881
|
+
1. **Dry-Run First**: Both commands default to preview mode
|
|
882
|
+
- `frigg cleanup --orphaned` → dry-run by default
|
|
883
|
+
- `frigg deploy --dry-run` → explicit dry-run flag
|
|
884
|
+
|
|
885
|
+
2. **Safety Features**:
|
|
886
|
+
- Both create preview without modifying resources
|
|
887
|
+
- Both require explicit execution flag (`--execute` for cleanup)
|
|
888
|
+
- Both provide detailed change summaries
|
|
889
|
+
|
|
890
|
+
3. **Output Formats**:
|
|
891
|
+
- Console output (default, human-readable)
|
|
892
|
+
- JSON output (`--output json` for automation)
|
|
893
|
+
|
|
894
|
+
4. **Change Analysis**:
|
|
895
|
+
- Both analyze resource changes before execution
|
|
896
|
+
- Both detect critical changes (replacements)
|
|
897
|
+
- Both provide warnings and recommendations
|
|
898
|
+
|
|
899
|
+
5. **Cleanup After Preview**:
|
|
900
|
+
- Cleanup command: No cleanup needed (just listing)
|
|
901
|
+
- Deploy command: Delete change set after preview
|
|
902
|
+
|
|
903
|
+
### Differences
|
|
904
|
+
|
|
905
|
+
| Feature | Deploy Dry-Run | Cleanup Orphaned |
|
|
906
|
+
|---------|----------------|------------------|
|
|
907
|
+
| **Default Behavior** | Requires `--dry-run` flag | Dry-run by default |
|
|
908
|
+
| **Execution** | Run deploy without flag | Requires `--execute` |
|
|
909
|
+
| **AWS Modification** | Creates/modifies resources | Deletes resources |
|
|
910
|
+
| **Change Set** | Creates temporary change set | N/A (uses describe APIs) |
|
|
911
|
+
| **Cost Impact** | Shows future costs | Shows cost savings |
|
|
912
|
+
| **Dependencies** | Not checked (CloudFormation handles) | Explicitly checked before delete |
|
|
913
|
+
| **Confirmation** | Not needed (explicit deploy) | Required before deletion |
|
|
914
|
+
|
|
915
|
+
## Success Criteria
|
|
916
|
+
|
|
917
|
+
1. **Functional**:
|
|
918
|
+
- ✅ Dry-run completes without errors for valid app definitions
|
|
919
|
+
- ✅ All phases execute in correct order
|
|
920
|
+
- ✅ Change set preview displays accurately
|
|
921
|
+
- ✅ No AWS resources modified during dry-run
|
|
922
|
+
- ✅ Change sets cleaned up after preview
|
|
923
|
+
|
|
924
|
+
2. **Usability**:
|
|
925
|
+
- ✅ Output is clear and actionable
|
|
926
|
+
- ✅ Warnings visually distinct from errors
|
|
927
|
+
- ✅ Next steps provided at end of output
|
|
928
|
+
- ✅ JSON output parseable by standard tools
|
|
929
|
+
|
|
930
|
+
3. **Performance**:
|
|
931
|
+
- ✅ Dry-run completes within 30s for small stacks
|
|
932
|
+
- ✅ Dry-run completes within 60s for large stacks
|
|
933
|
+
|
|
934
|
+
4. **Safety**:
|
|
935
|
+
- ✅ Zero side effects from running dry-run
|
|
936
|
+
- ✅ Failed dry-run doesn't leave inconsistent state
|
|
937
|
+
- ✅ Change sets always cleaned up (even on error)
|
|
938
|
+
|
|
939
|
+
## Future Enhancements
|
|
940
|
+
|
|
941
|
+
### Phase 2: Cost Estimation
|
|
942
|
+
- Integrate with AWS Cost Explorer API
|
|
943
|
+
- Show estimated monthly costs by resource
|
|
944
|
+
- Compare with current stack costs
|
|
945
|
+
- Alert on significant cost increases
|
|
946
|
+
|
|
947
|
+
### Phase 3: Drift Detection
|
|
948
|
+
- Compare deployed stack with app definition
|
|
949
|
+
- Detect manual changes in AWS console
|
|
950
|
+
- Suggest `frigg repair` for drift correction
|
|
951
|
+
|
|
952
|
+
### Phase 4: Multi-Stack Preview
|
|
953
|
+
- Preview changes across multiple stacks
|
|
954
|
+
- Show cross-stack dependencies
|
|
955
|
+
- Validate stack outputs/imports
|
|
956
|
+
|
|
957
|
+
### Phase 5: Interactive Mode
|
|
958
|
+
- Prompt user to proceed with deployment
|
|
959
|
+
- Allow selective deployment of changes
|
|
960
|
+
- Confirm critical changes before execution
|
|
961
|
+
|
|
962
|
+
## References
|
|
963
|
+
|
|
964
|
+
- **Related Spec**: [SPEC-CLEANUP-COMMAND.md](../../devtools/infrastructure/domains/health/docs/SPEC-CLEANUP-COMMAND.md)
|
|
965
|
+
- **CloudFormation Change Sets**: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html
|
|
966
|
+
- **Serverless Framework**: https://www.serverless.com/
|
|
967
|
+
- **AWS SDK v3**: https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/
|
|
968
|
+
|
|
969
|
+
## Approval
|
|
970
|
+
|
|
971
|
+
| Role | Name | Date | Signature |
|
|
972
|
+
|------|------|------|-----------|
|
|
973
|
+
| Product Owner | TBD | | |
|
|
974
|
+
| Tech Lead | TBD | | |
|
|
975
|
+
| Developer | Claude Code | 2025-10-28 | ✓ |
|
|
976
|
+
|
|
977
|
+
## Change Log
|
|
978
|
+
|
|
979
|
+
| Version | Date | Author | Changes |
|
|
980
|
+
|---------|------|--------|---------|
|
|
981
|
+
| 1.0.0 | 2025-10-28 | Claude Code | Initial specification |
|