@friggframework/devtools 2.0.0--canary.474.6a0bba7.0 → 2.0.0--canary.474.ca45ad3.0
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/infrastructure/domains/health/application/use-cases/__tests__/execute-resource-import-use-case.test.js +679 -0
- package/infrastructure/domains/health/application/use-cases/execute-resource-import-use-case.js +221 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-progress-monitor.test.js +971 -0
- package/infrastructure/domains/health/domain/services/__tests__/import-template-generator.test.js +1150 -0
- package/infrastructure/domains/health/domain/services/__tests__/update-progress-monitor.test.js +419 -0
- package/infrastructure/domains/health/domain/services/import-progress-monitor.js +195 -0
- package/infrastructure/domains/health/domain/services/import-template-generator.js +435 -0
- package/infrastructure/domains/health/domain/services/property-mutability-config.js +382 -0
- package/infrastructure/domains/health/domain/services/update-progress-monitor.js +192 -0
- package/package.json +6 -6
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Property Mutability Configuration
|
|
3
|
+
*
|
|
4
|
+
* Defines which CloudFormation resource properties are immutable (require replacement),
|
|
5
|
+
* mutable (can be updated), or conditional (depends on other properties).
|
|
6
|
+
*
|
|
7
|
+
* Based on AWS CloudFormation documentation "Update requires" behavior:
|
|
8
|
+
* - IMMUTABLE: "Replacement" - Cannot be changed without replacing the resource
|
|
9
|
+
* - MUTABLE: "No interruption" or "Some interruptions" - Can be updated in place
|
|
10
|
+
* - CONDITIONAL: Depends on other property values or specific conditions
|
|
11
|
+
*
|
|
12
|
+
* References:
|
|
13
|
+
* - AWS CloudFormation Template Reference: https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/
|
|
14
|
+
* - Each resource type has "Update requires" documentation for each property
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const PropertyMutability = require('../value-objects/property-mutability');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Property mutability configuration by resource type
|
|
21
|
+
*
|
|
22
|
+
* Key: CloudFormation resource type (e.g., 'AWS::Lambda::Function')
|
|
23
|
+
* Value: Object mapping property paths to PropertyMutability instances
|
|
24
|
+
*
|
|
25
|
+
* Property paths match AWS drift detection format (without 'Properties.' prefix):
|
|
26
|
+
* - Simple: 'BucketName'
|
|
27
|
+
* - Nested: 'VpcConfig.SubnetIds'
|
|
28
|
+
*/
|
|
29
|
+
const PROPERTY_MUTABILITY_CONFIG = {
|
|
30
|
+
//
|
|
31
|
+
// AWS::EC2::* Resources
|
|
32
|
+
//
|
|
33
|
+
|
|
34
|
+
'AWS::EC2::VPC': {
|
|
35
|
+
// Immutable properties
|
|
36
|
+
'CidrBlock': PropertyMutability.IMMUTABLE, // Replacement required
|
|
37
|
+
'InstanceTenancy': PropertyMutability.IMMUTABLE, // Replacement required
|
|
38
|
+
|
|
39
|
+
// Mutable properties
|
|
40
|
+
'EnableDnsSupport': PropertyMutability.MUTABLE, // No interruption
|
|
41
|
+
'EnableDnsHostnames': PropertyMutability.MUTABLE, // No interruption
|
|
42
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
'AWS::EC2::Subnet': {
|
|
46
|
+
// Immutable properties
|
|
47
|
+
'VpcId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
48
|
+
'CidrBlock': PropertyMutability.IMMUTABLE, // Replacement required
|
|
49
|
+
'AvailabilityZone': PropertyMutability.IMMUTABLE, // Replacement required
|
|
50
|
+
'AvailabilityZoneId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
51
|
+
'Ipv4IpamPoolId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
52
|
+
'Ipv4NetmaskLength': PropertyMutability.IMMUTABLE, // Replacement required
|
|
53
|
+
'Ipv6IpamPoolId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
54
|
+
'Ipv6Native': PropertyMutability.IMMUTABLE, // Replacement required
|
|
55
|
+
'Ipv6NetmaskLength': PropertyMutability.IMMUTABLE, // Replacement required
|
|
56
|
+
'OutpostArn': PropertyMutability.IMMUTABLE, // Replacement required
|
|
57
|
+
|
|
58
|
+
// Mutable properties
|
|
59
|
+
'AssignIpv6AddressOnCreation': PropertyMutability.MUTABLE, // No interruption
|
|
60
|
+
'EnableDns64': PropertyMutability.MUTABLE, // No interruption
|
|
61
|
+
'EnableLniAtDeviceIndex': PropertyMutability.MUTABLE, // No interruption
|
|
62
|
+
'Ipv6CidrBlock': PropertyMutability.MUTABLE, // Some interruptions
|
|
63
|
+
'MapPublicIpOnLaunch': PropertyMutability.MUTABLE, // No interruption
|
|
64
|
+
'PrivateDnsNameOptionsOnLaunch': PropertyMutability.MUTABLE, // No interruption
|
|
65
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
'AWS::EC2::SecurityGroup': {
|
|
69
|
+
// Immutable properties
|
|
70
|
+
'VpcId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
71
|
+
'GroupName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
72
|
+
|
|
73
|
+
// Mutable properties
|
|
74
|
+
'GroupDescription': PropertyMutability.MUTABLE, // No interruption
|
|
75
|
+
'SecurityGroupIngress': PropertyMutability.MUTABLE, // No interruption
|
|
76
|
+
'SecurityGroupEgress': PropertyMutability.MUTABLE, // No interruption
|
|
77
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
'AWS::EC2::RouteTable': {
|
|
81
|
+
// Immutable properties
|
|
82
|
+
'VpcId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
83
|
+
|
|
84
|
+
// Mutable properties
|
|
85
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
'AWS::EC2::Instance': {
|
|
89
|
+
// Immutable properties
|
|
90
|
+
'ImageId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
91
|
+
'InstanceType': PropertyMutability.IMMUTABLE, // Replacement required
|
|
92
|
+
'KeyName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
93
|
+
'AvailabilityZone': PropertyMutability.IMMUTABLE, // Replacement required
|
|
94
|
+
'PlacementGroupName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
95
|
+
'PrivateIpAddress': PropertyMutability.IMMUTABLE, // Replacement required
|
|
96
|
+
'SubnetId': PropertyMutability.IMMUTABLE, // Replacement required
|
|
97
|
+
|
|
98
|
+
// Mutable properties
|
|
99
|
+
'SecurityGroupIds': PropertyMutability.MUTABLE, // No interruption (VPC instances)
|
|
100
|
+
'SecurityGroups': PropertyMutability.MUTABLE, // No interruption
|
|
101
|
+
'UserData': PropertyMutability.MUTABLE, // Some interruptions
|
|
102
|
+
'IamInstanceProfile': PropertyMutability.MUTABLE, // No interruption
|
|
103
|
+
'Monitoring': PropertyMutability.MUTABLE, // No interruption
|
|
104
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
105
|
+
'DisableApiTermination': PropertyMutability.MUTABLE, // No interruption
|
|
106
|
+
'EbsOptimized': PropertyMutability.MUTABLE, // Some interruptions
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
//
|
|
110
|
+
// AWS::Lambda::* Resources
|
|
111
|
+
//
|
|
112
|
+
|
|
113
|
+
'AWS::Lambda::Function': {
|
|
114
|
+
// Immutable properties
|
|
115
|
+
'FunctionName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
116
|
+
|
|
117
|
+
// Mutable properties
|
|
118
|
+
'Code': PropertyMutability.MUTABLE, // No interruption
|
|
119
|
+
'Runtime': PropertyMutability.MUTABLE, // No interruption
|
|
120
|
+
'Handler': PropertyMutability.MUTABLE, // No interruption
|
|
121
|
+
'Role': PropertyMutability.MUTABLE, // No interruption
|
|
122
|
+
'Description': PropertyMutability.MUTABLE, // No interruption
|
|
123
|
+
'Timeout': PropertyMutability.MUTABLE, // No interruption
|
|
124
|
+
'MemorySize': PropertyMutability.MUTABLE, // No interruption
|
|
125
|
+
'Environment': PropertyMutability.MUTABLE, // No interruption
|
|
126
|
+
'Environment.Variables': PropertyMutability.MUTABLE, // No interruption
|
|
127
|
+
'VpcConfig': PropertyMutability.MUTABLE, // No interruption
|
|
128
|
+
'VpcConfig.SubnetIds': PropertyMutability.MUTABLE, // No interruption
|
|
129
|
+
'VpcConfig.SecurityGroupIds': PropertyMutability.MUTABLE, // No interruption
|
|
130
|
+
'VpcConfig.Ipv6AllowedForDualStack': PropertyMutability.MUTABLE, // No interruption
|
|
131
|
+
'DeadLetterConfig': PropertyMutability.MUTABLE, // No interruption
|
|
132
|
+
'TracingConfig': PropertyMutability.MUTABLE, // No interruption
|
|
133
|
+
'KMSKeyArn': PropertyMutability.MUTABLE, // No interruption
|
|
134
|
+
'Layers': PropertyMutability.MUTABLE, // No interruption
|
|
135
|
+
'ReservedConcurrentExecutions': PropertyMutability.MUTABLE, // No interruption
|
|
136
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
137
|
+
'FileSystemConfigs': PropertyMutability.MUTABLE, // No interruption
|
|
138
|
+
'Architectures': PropertyMutability.MUTABLE, // No interruption
|
|
139
|
+
'EphemeralStorage': PropertyMutability.MUTABLE, // No interruption
|
|
140
|
+
'SnapStart': PropertyMutability.MUTABLE, // No interruption
|
|
141
|
+
'RuntimeManagementConfig': PropertyMutability.MUTABLE, // No interruption
|
|
142
|
+
'LoggingConfig': PropertyMutability.MUTABLE, // No interruption
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
//
|
|
146
|
+
// AWS::RDS::* Resources
|
|
147
|
+
//
|
|
148
|
+
|
|
149
|
+
'AWS::RDS::DBInstance': {
|
|
150
|
+
// Immutable properties
|
|
151
|
+
'DBInstanceIdentifier': PropertyMutability.IMMUTABLE, // Replacement required
|
|
152
|
+
'Engine': PropertyMutability.IMMUTABLE, // Replacement required
|
|
153
|
+
'DBName': PropertyMutability.IMMUTABLE, // Replacement required (for some engines)
|
|
154
|
+
'AvailabilityZone': PropertyMutability.IMMUTABLE, // Replacement required
|
|
155
|
+
|
|
156
|
+
// Mutable properties
|
|
157
|
+
'AllocatedStorage': PropertyMutability.MUTABLE, // No interruption
|
|
158
|
+
'DBInstanceClass': PropertyMutability.MUTABLE, // Some interruptions
|
|
159
|
+
'EngineVersion': PropertyMutability.MUTABLE, // Some interruptions
|
|
160
|
+
'MasterUsername': PropertyMutability.MUTABLE, // No interruption (can't actually change)
|
|
161
|
+
'MasterUserPassword': PropertyMutability.MUTABLE, // No interruption
|
|
162
|
+
'BackupRetentionPeriod': PropertyMutability.MUTABLE, // No interruption
|
|
163
|
+
'DBSecurityGroups': PropertyMutability.MUTABLE, // No interruption
|
|
164
|
+
'VPCSecurityGroups': PropertyMutability.MUTABLE, // No interruption
|
|
165
|
+
'MultiAZ': PropertyMutability.MUTABLE, // No interruption
|
|
166
|
+
'PubliclyAccessible': PropertyMutability.MUTABLE, // No interruption
|
|
167
|
+
'StorageEncrypted': PropertyMutability.MUTABLE, // Some interruptions
|
|
168
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
'AWS::RDS::DBCluster': {
|
|
172
|
+
// Immutable properties
|
|
173
|
+
'DBClusterIdentifier': PropertyMutability.IMMUTABLE, // Replacement required
|
|
174
|
+
'Engine': PropertyMutability.IMMUTABLE, // Replacement required
|
|
175
|
+
'DatabaseName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
176
|
+
|
|
177
|
+
// Mutable properties
|
|
178
|
+
'EngineVersion': PropertyMutability.MUTABLE, // No interruption
|
|
179
|
+
'MasterUsername': PropertyMutability.MUTABLE, // No interruption (can't actually change)
|
|
180
|
+
'MasterUserPassword': PropertyMutability.MUTABLE, // No interruption
|
|
181
|
+
'BackupRetentionPeriod': PropertyMutability.MUTABLE, // No interruption
|
|
182
|
+
'PreferredBackupWindow': PropertyMutability.MUTABLE, // No interruption
|
|
183
|
+
'PreferredMaintenanceWindow': PropertyMutability.MUTABLE, // No interruption
|
|
184
|
+
'VpcSecurityGroupIds': PropertyMutability.MUTABLE, // No interruption
|
|
185
|
+
'DBSubnetGroupName': PropertyMutability.MUTABLE, // Some interruptions
|
|
186
|
+
'StorageEncrypted': PropertyMutability.MUTABLE, // Some interruptions
|
|
187
|
+
'KmsKeyId': PropertyMutability.MUTABLE, // Some interruptions
|
|
188
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
//
|
|
192
|
+
// AWS::S3::* Resources
|
|
193
|
+
//
|
|
194
|
+
|
|
195
|
+
'AWS::S3::Bucket': {
|
|
196
|
+
// Immutable properties
|
|
197
|
+
'BucketName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
198
|
+
|
|
199
|
+
// Mutable properties
|
|
200
|
+
'AccelerateConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
201
|
+
'AccessControl': PropertyMutability.MUTABLE, // No interruption
|
|
202
|
+
'AnalyticsConfigurations': PropertyMutability.MUTABLE, // No interruption
|
|
203
|
+
'BucketEncryption': PropertyMutability.MUTABLE, // No interruption
|
|
204
|
+
'CorsConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
205
|
+
'IntelligentTieringConfigurations': PropertyMutability.MUTABLE, // No interruption
|
|
206
|
+
'InventoryConfigurations': PropertyMutability.MUTABLE, // No interruption
|
|
207
|
+
'LifecycleConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
208
|
+
'LoggingConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
209
|
+
'MetricsConfigurations': PropertyMutability.MUTABLE, // No interruption
|
|
210
|
+
'NotificationConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
211
|
+
'ObjectLockConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
212
|
+
'ObjectLockEnabled': PropertyMutability.MUTABLE, // No interruption
|
|
213
|
+
'OwnershipControls': PropertyMutability.MUTABLE, // No interruption
|
|
214
|
+
'PublicAccessBlockConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
215
|
+
'ReplicationConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
216
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
217
|
+
'VersioningConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
218
|
+
'WebsiteConfiguration': PropertyMutability.MUTABLE, // No interruption
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
//
|
|
222
|
+
// AWS::KMS::* Resources
|
|
223
|
+
//
|
|
224
|
+
|
|
225
|
+
'AWS::KMS::Key': {
|
|
226
|
+
// All KMS key properties are mutable (updates don't require replacement)
|
|
227
|
+
'Description': PropertyMutability.MUTABLE, // No interruption
|
|
228
|
+
'Enabled': PropertyMutability.MUTABLE, // No interruption
|
|
229
|
+
'EnableKeyRotation': PropertyMutability.MUTABLE, // No interruption
|
|
230
|
+
'KeyPolicy': PropertyMutability.MUTABLE, // No interruption
|
|
231
|
+
'KeyUsage': PropertyMutability.MUTABLE, // No interruption
|
|
232
|
+
'MultiRegion': PropertyMutability.MUTABLE, // No interruption
|
|
233
|
+
'PendingWindowInDays': PropertyMutability.MUTABLE, // No interruption
|
|
234
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
//
|
|
238
|
+
// AWS::DynamoDB::* Resources
|
|
239
|
+
//
|
|
240
|
+
|
|
241
|
+
'AWS::DynamoDB::Table': {
|
|
242
|
+
// Immutable properties
|
|
243
|
+
'TableName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
244
|
+
'KeySchema': PropertyMutability.IMMUTABLE, // Replacement required
|
|
245
|
+
'AttributeDefinitions': PropertyMutability.CONDITIONAL, // Some changes require replacement
|
|
246
|
+
|
|
247
|
+
// Mutable properties
|
|
248
|
+
'BillingMode': PropertyMutability.MUTABLE, // No interruption
|
|
249
|
+
'ProvisionedThroughput': PropertyMutability.MUTABLE, // No interruption
|
|
250
|
+
'GlobalSecondaryIndexes': PropertyMutability.MUTABLE, // No interruption
|
|
251
|
+
'LocalSecondaryIndexes': PropertyMutability.IMMUTABLE, // Replacement required
|
|
252
|
+
'StreamSpecification': PropertyMutability.MUTABLE, // No interruption
|
|
253
|
+
'SSESpecification': PropertyMutability.MUTABLE, // No interruption
|
|
254
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
255
|
+
'TimeToLiveSpecification': PropertyMutability.MUTABLE, // No interruption
|
|
256
|
+
'PointInTimeRecoverySpecification': PropertyMutability.MUTABLE, // No interruption
|
|
257
|
+
'ContributorInsightsSpecification': PropertyMutability.MUTABLE, // No interruption
|
|
258
|
+
'KinesisStreamSpecification': PropertyMutability.MUTABLE, // No interruption
|
|
259
|
+
},
|
|
260
|
+
|
|
261
|
+
//
|
|
262
|
+
// AWS::SQS::* Resources
|
|
263
|
+
//
|
|
264
|
+
|
|
265
|
+
'AWS::SQS::Queue': {
|
|
266
|
+
// Immutable properties
|
|
267
|
+
'QueueName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
268
|
+
'FifoQueue': PropertyMutability.IMMUTABLE, // Replacement required
|
|
269
|
+
|
|
270
|
+
// Mutable properties
|
|
271
|
+
'ContentBasedDeduplication': PropertyMutability.MUTABLE, // No interruption
|
|
272
|
+
'DeduplicationScope': PropertyMutability.MUTABLE, // No interruption
|
|
273
|
+
'DelaySeconds': PropertyMutability.MUTABLE, // No interruption
|
|
274
|
+
'FifoThroughputLimit': PropertyMutability.MUTABLE, // No interruption
|
|
275
|
+
'KmsMasterKeyId': PropertyMutability.MUTABLE, // No interruption
|
|
276
|
+
'KmsDataKeyReusePeriodSeconds': PropertyMutability.MUTABLE, // No interruption
|
|
277
|
+
'MaximumMessageSize': PropertyMutability.MUTABLE, // No interruption
|
|
278
|
+
'MessageRetentionPeriod': PropertyMutability.MUTABLE, // No interruption
|
|
279
|
+
'ReceiveMessageWaitTimeSeconds': PropertyMutability.MUTABLE, // No interruption
|
|
280
|
+
'RedrivePolicy': PropertyMutability.MUTABLE, // No interruption
|
|
281
|
+
'RedriveAllowPolicy': PropertyMutability.MUTABLE, // No interruption
|
|
282
|
+
'SqsManagedSseEnabled': PropertyMutability.MUTABLE, // No interruption
|
|
283
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
284
|
+
'VisibilityTimeout': PropertyMutability.MUTABLE, // No interruption
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
//
|
|
288
|
+
// AWS::SNS::* Resources
|
|
289
|
+
//
|
|
290
|
+
|
|
291
|
+
'AWS::SNS::Topic': {
|
|
292
|
+
// Immutable properties
|
|
293
|
+
'TopicName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
294
|
+
'FifoTopic': PropertyMutability.IMMUTABLE, // Replacement required
|
|
295
|
+
|
|
296
|
+
// Mutable properties
|
|
297
|
+
'ContentBasedDeduplication': PropertyMutability.MUTABLE, // No interruption
|
|
298
|
+
'DataProtectionPolicy': PropertyMutability.MUTABLE, // No interruption
|
|
299
|
+
'DisplayName': PropertyMutability.MUTABLE, // No interruption
|
|
300
|
+
'KmsMasterKeyId': PropertyMutability.MUTABLE, // No interruption
|
|
301
|
+
'SignatureVersion': PropertyMutability.MUTABLE, // No interruption
|
|
302
|
+
'Subscription': PropertyMutability.MUTABLE, // No interruption
|
|
303
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
304
|
+
'TracingConfig': PropertyMutability.MUTABLE, // No interruption
|
|
305
|
+
},
|
|
306
|
+
|
|
307
|
+
//
|
|
308
|
+
// AWS::IAM::* Resources
|
|
309
|
+
//
|
|
310
|
+
|
|
311
|
+
'AWS::IAM::Role': {
|
|
312
|
+
// Immutable properties
|
|
313
|
+
'RoleName': PropertyMutability.IMMUTABLE, // Replacement required
|
|
314
|
+
|
|
315
|
+
// Mutable properties
|
|
316
|
+
'AssumeRolePolicyDocument': PropertyMutability.MUTABLE, // No interruption
|
|
317
|
+
'Description': PropertyMutability.MUTABLE, // No interruption
|
|
318
|
+
'ManagedPolicyArns': PropertyMutability.MUTABLE, // No interruption
|
|
319
|
+
'MaxSessionDuration': PropertyMutability.MUTABLE, // No interruption
|
|
320
|
+
'Path': PropertyMutability.MUTABLE, // No interruption
|
|
321
|
+
'PermissionsBoundary': PropertyMutability.MUTABLE, // No interruption
|
|
322
|
+
'Policies': PropertyMutability.MUTABLE, // No interruption
|
|
323
|
+
'Tags': PropertyMutability.MUTABLE, // No interruption
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Get property mutability for a given resource type and property path
|
|
329
|
+
*
|
|
330
|
+
* @param {string} resourceType - CloudFormation resource type (e.g., 'AWS::Lambda::Function')
|
|
331
|
+
* @param {string} propertyPath - Property path from drift detection (e.g., 'VpcConfig.SubnetIds')
|
|
332
|
+
* @returns {PropertyMutability} Property mutability (defaults to MUTABLE if not found)
|
|
333
|
+
*/
|
|
334
|
+
function getPropertyMutability(resourceType, propertyPath) {
|
|
335
|
+
// Check if resource type has configuration
|
|
336
|
+
if (!PROPERTY_MUTABILITY_CONFIG[resourceType]) {
|
|
337
|
+
// Unknown resource type - default to MUTABLE (safe, allows reconciliation attempt)
|
|
338
|
+
return PropertyMutability.MUTABLE;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const resourceConfig = PROPERTY_MUTABILITY_CONFIG[resourceType];
|
|
342
|
+
|
|
343
|
+
// Check exact property path match
|
|
344
|
+
if (resourceConfig[propertyPath]) {
|
|
345
|
+
return resourceConfig[propertyPath];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check parent property for nested paths (e.g., 'VpcConfig' for 'VpcConfig.SubnetIds')
|
|
349
|
+
const parentPath = propertyPath.split('.')[0];
|
|
350
|
+
if (resourceConfig[parentPath]) {
|
|
351
|
+
return resourceConfig[parentPath];
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Property not found in config - default to MUTABLE
|
|
355
|
+
return PropertyMutability.MUTABLE;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Check if a resource type is supported in the configuration
|
|
360
|
+
*
|
|
361
|
+
* @param {string} resourceType - CloudFormation resource type
|
|
362
|
+
* @returns {boolean} True if resource type is configured
|
|
363
|
+
*/
|
|
364
|
+
function isResourceTypeConfigured(resourceType) {
|
|
365
|
+
return resourceType in PROPERTY_MUTABILITY_CONFIG;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Get all configured resource types
|
|
370
|
+
*
|
|
371
|
+
* @returns {string[]} Array of resource type names
|
|
372
|
+
*/
|
|
373
|
+
function getConfiguredResourceTypes() {
|
|
374
|
+
return Object.keys(PROPERTY_MUTABILITY_CONFIG);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
module.exports = {
|
|
378
|
+
PROPERTY_MUTABILITY_CONFIG,
|
|
379
|
+
getPropertyMutability,
|
|
380
|
+
isResourceTypeConfigured,
|
|
381
|
+
getConfiguredResourceTypes,
|
|
382
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UpdateProgressMonitor - Monitor CloudFormation Update Operation Progress
|
|
3
|
+
*
|
|
4
|
+
* Domain Layer - Service
|
|
5
|
+
*
|
|
6
|
+
* Monitors CloudFormation UPDATE operations by polling stack events and tracking
|
|
7
|
+
* resource update progress. Provides real-time progress callbacks and detects
|
|
8
|
+
* failures, rollbacks, and timeouts.
|
|
9
|
+
*
|
|
10
|
+
* Responsibilities:
|
|
11
|
+
* - Poll CloudFormation stack events during update
|
|
12
|
+
* - Track progress per resource (IN_PROGRESS, COMPLETE, FAILED)
|
|
13
|
+
* - Detect stack rollback states
|
|
14
|
+
* - Timeout after 5 minutes
|
|
15
|
+
* - Provide progress callbacks for UI updates
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
class UpdateProgressMonitor {
|
|
19
|
+
/**
|
|
20
|
+
* Create progress monitor with CloudFormation repository dependency
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} params
|
|
23
|
+
* @param {Object} params.cloudFormationRepository - CloudFormation operations
|
|
24
|
+
*/
|
|
25
|
+
constructor({ cloudFormationRepository }) {
|
|
26
|
+
if (!cloudFormationRepository) {
|
|
27
|
+
throw new Error('cloudFormationRepository is required');
|
|
28
|
+
}
|
|
29
|
+
this.cfRepo = cloudFormationRepository;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Monitor update operation progress
|
|
34
|
+
*
|
|
35
|
+
* Polls CloudFormation stack events every 2 seconds to track resource update progress.
|
|
36
|
+
* Calls onProgress callback with status updates for each resource.
|
|
37
|
+
* Detects failures, rollbacks, and timeouts.
|
|
38
|
+
*
|
|
39
|
+
* @param {Object} params
|
|
40
|
+
* @param {Object} params.stackIdentifier - Stack identifier { stackName, region }
|
|
41
|
+
* @param {Array<string>} params.resourceLogicalIds - Logical IDs to track
|
|
42
|
+
* @param {Function} params.onProgress - Progress callback function
|
|
43
|
+
* @returns {Promise<Object>} Update result
|
|
44
|
+
*/
|
|
45
|
+
async monitorUpdate({ stackIdentifier, resourceLogicalIds, onProgress }) {
|
|
46
|
+
// If no resources to track, return immediately
|
|
47
|
+
if (!resourceLogicalIds || resourceLogicalIds.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
success: true,
|
|
50
|
+
updatedCount: 0,
|
|
51
|
+
failedCount: 0,
|
|
52
|
+
failedResources: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const updatedResources = new Set();
|
|
57
|
+
const failedResources = [];
|
|
58
|
+
const processedEvents = new Set(); // Track processed events by timestamp + logicalId
|
|
59
|
+
let elapsedTime = 0; // Track elapsed time manually for fake timers compatibility
|
|
60
|
+
const TIMEOUT_MS = 300000; // 5 minutes
|
|
61
|
+
const POLL_INTERVAL_MS = 2000; // 2 seconds
|
|
62
|
+
|
|
63
|
+
// Continue polling until all resources are complete or failed
|
|
64
|
+
while (
|
|
65
|
+
updatedResources.size + failedResources.length <
|
|
66
|
+
resourceLogicalIds.length
|
|
67
|
+
) {
|
|
68
|
+
// Wait 2 seconds before polling
|
|
69
|
+
await this._delay(POLL_INTERVAL_MS);
|
|
70
|
+
elapsedTime += POLL_INTERVAL_MS;
|
|
71
|
+
|
|
72
|
+
// Check for timeout
|
|
73
|
+
if (elapsedTime > TIMEOUT_MS) {
|
|
74
|
+
throw new Error('Update operation timed out');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Get stack events
|
|
78
|
+
const events = await this.cfRepo.getStackEvents({
|
|
79
|
+
stackIdentifier,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Sort events by timestamp (oldest first) for consistent processing
|
|
83
|
+
const sortedEvents = [...events].sort(
|
|
84
|
+
(a, b) => new Date(a.Timestamp) - new Date(b.Timestamp)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Process events for tracked resources
|
|
88
|
+
for (const event of sortedEvents) {
|
|
89
|
+
const logicalId = event.LogicalResourceId;
|
|
90
|
+
|
|
91
|
+
// Skip if not a tracked resource
|
|
92
|
+
if (!resourceLogicalIds.includes(logicalId)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Create unique event key to avoid duplicate processing
|
|
97
|
+
const eventKey = `${event.Timestamp.toISOString()}_${logicalId}_${event.ResourceStatus}`;
|
|
98
|
+
|
|
99
|
+
// Skip if already processed
|
|
100
|
+
if (processedEvents.has(eventKey)) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
processedEvents.add(eventKey);
|
|
105
|
+
|
|
106
|
+
// Handle different resource statuses
|
|
107
|
+
if (event.ResourceStatus === 'UPDATE_IN_PROGRESS') {
|
|
108
|
+
// Call progress callback with IN_PROGRESS status
|
|
109
|
+
if (onProgress) {
|
|
110
|
+
onProgress({
|
|
111
|
+
logicalId,
|
|
112
|
+
status: 'IN_PROGRESS',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
} else if (event.ResourceStatus === 'UPDATE_COMPLETE') {
|
|
116
|
+
// Mark resource as updated
|
|
117
|
+
updatedResources.add(logicalId);
|
|
118
|
+
|
|
119
|
+
// Call progress callback with COMPLETE status
|
|
120
|
+
if (onProgress) {
|
|
121
|
+
onProgress({
|
|
122
|
+
logicalId,
|
|
123
|
+
status: 'COMPLETE',
|
|
124
|
+
progress: updatedResources.size,
|
|
125
|
+
total: resourceLogicalIds.length,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} else if (event.ResourceStatus === 'UPDATE_FAILED') {
|
|
129
|
+
// Add to failed resources
|
|
130
|
+
const reason = event.ResourceStatusReason || 'Unknown error';
|
|
131
|
+
failedResources.push({
|
|
132
|
+
logicalId,
|
|
133
|
+
reason,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Call progress callback with FAILED status
|
|
137
|
+
if (onProgress) {
|
|
138
|
+
onProgress({
|
|
139
|
+
logicalId,
|
|
140
|
+
status: 'FAILED',
|
|
141
|
+
reason,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Check if all resources are now accounted for
|
|
148
|
+
const allResourcesProcessed =
|
|
149
|
+
updatedResources.size + failedResources.length >=
|
|
150
|
+
resourceLogicalIds.length;
|
|
151
|
+
|
|
152
|
+
// If all resources processed, exit loop to return result
|
|
153
|
+
if (allResourcesProcessed) {
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Check stack status AFTER processing events - if rollback in progress, throw
|
|
158
|
+
const stackStatus = await this.cfRepo.getStackStatus(stackIdentifier);
|
|
159
|
+
if (
|
|
160
|
+
stackStatus.includes('ROLLBACK') &&
|
|
161
|
+
stackStatus !== 'UPDATE_ROLLBACK_COMPLETE'
|
|
162
|
+
) {
|
|
163
|
+
throw new Error('Update operation failed and rolled back');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check final stack status before returning
|
|
168
|
+
const finalStackStatus = await this.cfRepo.getStackStatus(stackIdentifier);
|
|
169
|
+
|
|
170
|
+
// Return result (success = no failures)
|
|
171
|
+
const success = failedResources.length === 0;
|
|
172
|
+
return {
|
|
173
|
+
success,
|
|
174
|
+
updatedCount: updatedResources.size,
|
|
175
|
+
failedCount: failedResources.length,
|
|
176
|
+
failedResources,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Delay helper for polling intervals
|
|
182
|
+
*
|
|
183
|
+
* @param {number} ms - Milliseconds to delay
|
|
184
|
+
* @returns {Promise<void>}
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
async _delay(ms) {
|
|
188
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = { UpdateProgressMonitor };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@friggframework/devtools",
|
|
3
3
|
"prettier": "@friggframework/prettier-config",
|
|
4
|
-
"version": "2.0.0--canary.474.
|
|
4
|
+
"version": "2.0.0--canary.474.ca45ad3.0",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@aws-sdk/client-ec2": "^3.835.0",
|
|
7
7
|
"@aws-sdk/client-kms": "^3.835.0",
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"@babel/eslint-parser": "^7.18.9",
|
|
12
12
|
"@babel/parser": "^7.25.3",
|
|
13
13
|
"@babel/traverse": "^7.25.3",
|
|
14
|
-
"@friggframework/schemas": "2.0.0--canary.474.
|
|
15
|
-
"@friggframework/test": "2.0.0--canary.474.
|
|
14
|
+
"@friggframework/schemas": "2.0.0--canary.474.ca45ad3.0",
|
|
15
|
+
"@friggframework/test": "2.0.0--canary.474.ca45ad3.0",
|
|
16
16
|
"@hapi/boom": "^10.0.1",
|
|
17
17
|
"@inquirer/prompts": "^5.3.8",
|
|
18
18
|
"axios": "^1.7.2",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"serverless-http": "^2.7.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@friggframework/eslint-config": "2.0.0--canary.474.
|
|
38
|
-
"@friggframework/prettier-config": "2.0.0--canary.474.
|
|
37
|
+
"@friggframework/eslint-config": "2.0.0--canary.474.ca45ad3.0",
|
|
38
|
+
"@friggframework/prettier-config": "2.0.0--canary.474.ca45ad3.0",
|
|
39
39
|
"aws-sdk-client-mock": "^4.1.0",
|
|
40
40
|
"aws-sdk-client-mock-jest": "^4.1.0",
|
|
41
41
|
"jest": "^30.1.3",
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"publishConfig": {
|
|
68
68
|
"access": "public"
|
|
69
69
|
},
|
|
70
|
-
"gitHead": "
|
|
70
|
+
"gitHead": "ca45ad3b737f85241453177ba87eb05fba6d389e"
|
|
71
71
|
}
|