@friggframework/devtools 2.0.0--canary.474.82ba370.0 → 2.0.0--canary.474.efd7936.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/ports/IPropertyReconciler.js +164 -0
- package/infrastructure/domains/health/application/ports/IResourceDetector.js +129 -0
- package/infrastructure/domains/health/application/ports/IResourceImporter.js +142 -0
- package/infrastructure/domains/health/application/ports/IStackRepository.js +131 -0
- package/infrastructure/domains/health/application/ports/index.js +26 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +7 -4
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +28 -4
- package/infrastructure/domains/health/domain/entities/stack-health-report.js +306 -0
- package/infrastructure/domains/health/domain/entities/stack-health-report.test.js +601 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.js +165 -0
- package/infrastructure/domains/health/domain/services/health-score-calculator.test.js +400 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.js +234 -0
- package/infrastructure/domains/health/domain/services/mismatch-analyzer.test.js +431 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +13 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +29 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.js +386 -0
- package/infrastructure/domains/health/infrastructure/adapters/aws-stack-repository.test.js +580 -0
- package/package.json +6 -6
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StackHealthReport Aggregate Root
|
|
3
|
+
*
|
|
4
|
+
* Represents the complete health assessment of a CloudFormation stack,
|
|
5
|
+
* including all resources, detected issues, and overall health score.
|
|
6
|
+
*
|
|
7
|
+
* As an aggregate root, this entity controls access to Resource and Issue entities
|
|
8
|
+
* and maintains consistency across the entire health report.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const StackIdentifier = require('../value-objects/stack-identifier');
|
|
12
|
+
const HealthScore = require('../value-objects/health-score');
|
|
13
|
+
const Resource = require('./resource');
|
|
14
|
+
const Issue = require('./issue');
|
|
15
|
+
|
|
16
|
+
class StackHealthReport {
|
|
17
|
+
/**
|
|
18
|
+
* Create a new StackHealthReport
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} params
|
|
21
|
+
* @param {StackIdentifier} params.stackIdentifier - Stack identifier
|
|
22
|
+
* @param {HealthScore} params.healthScore - Overall health score (0-100)
|
|
23
|
+
* @param {Resource[]} [params.resources=[]] - Resources in the stack
|
|
24
|
+
* @param {Issue[]} [params.issues=[]] - Detected issues
|
|
25
|
+
* @param {Date} [params.timestamp=new Date()] - When the health check was performed
|
|
26
|
+
* @param {Object} [params.metadata={}] - Additional metadata (e.g., scan duration)
|
|
27
|
+
*/
|
|
28
|
+
constructor({
|
|
29
|
+
stackIdentifier,
|
|
30
|
+
healthScore,
|
|
31
|
+
resources = [],
|
|
32
|
+
issues = [],
|
|
33
|
+
timestamp = new Date(),
|
|
34
|
+
metadata = {},
|
|
35
|
+
}) {
|
|
36
|
+
// Validate required fields
|
|
37
|
+
if (!stackIdentifier) {
|
|
38
|
+
throw new Error('stackIdentifier is required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!healthScore) {
|
|
42
|
+
throw new Error('healthScore is required');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!(stackIdentifier instanceof StackIdentifier)) {
|
|
46
|
+
throw new Error('stackIdentifier must be a StackIdentifier instance');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!(healthScore instanceof HealthScore)) {
|
|
50
|
+
throw new Error('healthScore must be a HealthScore instance');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate resources array
|
|
54
|
+
if (!Array.isArray(resources)) {
|
|
55
|
+
throw new Error('resources must be an array');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const resource of resources) {
|
|
59
|
+
if (!(resource instanceof Resource)) {
|
|
60
|
+
throw new Error('All resources must be Resource instances');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Validate issues array
|
|
65
|
+
if (!Array.isArray(issues)) {
|
|
66
|
+
throw new Error('issues must be an array');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const issue of issues) {
|
|
70
|
+
if (!(issue instanceof Issue)) {
|
|
71
|
+
throw new Error('All issues must be Issue instances');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.stackIdentifier = stackIdentifier;
|
|
76
|
+
this.healthScore = healthScore;
|
|
77
|
+
this.resources = [...resources]; // Copy to prevent external mutation
|
|
78
|
+
this.issues = [...issues]; // Copy to prevent external mutation
|
|
79
|
+
this.timestamp = timestamp;
|
|
80
|
+
this.metadata = metadata;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ========================================
|
|
84
|
+
// Resource Queries
|
|
85
|
+
// ========================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get all orphaned resources (exist in cloud but not in stack)
|
|
89
|
+
* @returns {Resource[]}
|
|
90
|
+
*/
|
|
91
|
+
getOrphanedResources() {
|
|
92
|
+
return this.resources.filter((r) => r.isOrphaned());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get all missing resources (in stack but not in cloud)
|
|
97
|
+
* @returns {Resource[]}
|
|
98
|
+
*/
|
|
99
|
+
getMissingResources() {
|
|
100
|
+
return this.resources.filter((r) => r.isMissing());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get all drifted resources (properties differ)
|
|
105
|
+
* @returns {Resource[]}
|
|
106
|
+
*/
|
|
107
|
+
getDriftedResources() {
|
|
108
|
+
return this.resources.filter((r) => r.isDrifted());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all healthy resources (in stack with no issues)
|
|
113
|
+
* @returns {Resource[]}
|
|
114
|
+
*/
|
|
115
|
+
getHealthyResources() {
|
|
116
|
+
return this.resources.filter((r) => r.isInStack() && r.isHealthy());
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get all resources that are in the stack
|
|
121
|
+
* @returns {Resource[]}
|
|
122
|
+
*/
|
|
123
|
+
getResourcesInStack() {
|
|
124
|
+
return this.resources.filter((r) => r.isInStack());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Get total resource count
|
|
129
|
+
* @returns {number}
|
|
130
|
+
*/
|
|
131
|
+
getResourceCount() {
|
|
132
|
+
return this.resources.length;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get count of orphaned resources
|
|
137
|
+
* @returns {number}
|
|
138
|
+
*/
|
|
139
|
+
getOrphanedResourceCount() {
|
|
140
|
+
return this.getOrphanedResources().length;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get count of missing resources
|
|
145
|
+
* @returns {number}
|
|
146
|
+
*/
|
|
147
|
+
getMissingResourceCount() {
|
|
148
|
+
return this.getMissingResources().length;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get count of drifted resources
|
|
153
|
+
* @returns {number}
|
|
154
|
+
*/
|
|
155
|
+
getDriftedResourceCount() {
|
|
156
|
+
return this.getDriftedResources().length;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ========================================
|
|
160
|
+
// Issue Queries
|
|
161
|
+
// ========================================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Get all critical issues
|
|
165
|
+
* @returns {Issue[]}
|
|
166
|
+
*/
|
|
167
|
+
getCriticalIssues() {
|
|
168
|
+
return this.issues.filter((i) => i.isCritical());
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get all warning-level issues
|
|
173
|
+
* @returns {Issue[]}
|
|
174
|
+
*/
|
|
175
|
+
getWarnings() {
|
|
176
|
+
return this.issues.filter((i) => i.isWarning());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get all info-level issues
|
|
181
|
+
* @returns {Issue[]}
|
|
182
|
+
*/
|
|
183
|
+
getInfoIssues() {
|
|
184
|
+
return this.issues.filter((i) => i.isInfo());
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get total issue count
|
|
189
|
+
* @returns {number}
|
|
190
|
+
*/
|
|
191
|
+
getIssueCount() {
|
|
192
|
+
return this.issues.length;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get count of critical issues
|
|
197
|
+
* @returns {number}
|
|
198
|
+
*/
|
|
199
|
+
getCriticalIssueCount() {
|
|
200
|
+
return this.getCriticalIssues().length;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get count of warnings
|
|
205
|
+
* @returns {number}
|
|
206
|
+
*/
|
|
207
|
+
getWarningCount() {
|
|
208
|
+
return this.getWarnings().length;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Get count of info issues
|
|
213
|
+
* @returns {number}
|
|
214
|
+
*/
|
|
215
|
+
getInfoIssueCount() {
|
|
216
|
+
return this.getInfoIssues().length;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Check if report has any critical issues
|
|
221
|
+
* @returns {boolean}
|
|
222
|
+
*/
|
|
223
|
+
hasCriticalIssues() {
|
|
224
|
+
return this.getCriticalIssueCount() > 0;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ========================================
|
|
228
|
+
// Health Assessment
|
|
229
|
+
// ========================================
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Check if stack is healthy (score >= 80)
|
|
233
|
+
* @returns {boolean}
|
|
234
|
+
*/
|
|
235
|
+
isHealthy() {
|
|
236
|
+
return this.healthScore.isHealthy();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get qualitative health assessment
|
|
241
|
+
* @returns {string} 'healthy', 'degraded', or 'unhealthy'
|
|
242
|
+
*/
|
|
243
|
+
getQualitativeAssessment() {
|
|
244
|
+
return this.healthScore.qualitativeAssessment();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ========================================
|
|
248
|
+
// Summary
|
|
249
|
+
// ========================================
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Get summary statistics for the health report
|
|
253
|
+
* @returns {Object}
|
|
254
|
+
*/
|
|
255
|
+
getSummary() {
|
|
256
|
+
return {
|
|
257
|
+
stackName: this.stackIdentifier.stackName,
|
|
258
|
+
region: this.stackIdentifier.region,
|
|
259
|
+
healthScore: this.healthScore.value,
|
|
260
|
+
qualitativeAssessment: this.getQualitativeAssessment(),
|
|
261
|
+
isHealthy: this.isHealthy(),
|
|
262
|
+
resourceCount: this.getResourceCount(),
|
|
263
|
+
issueCount: this.getIssueCount(),
|
|
264
|
+
criticalIssueCount: this.getCriticalIssueCount(),
|
|
265
|
+
warningCount: this.getWarningCount(),
|
|
266
|
+
orphanedResourceCount: this.getOrphanedResourceCount(),
|
|
267
|
+
missingResourceCount: this.getMissingResourceCount(),
|
|
268
|
+
driftedResourceCount: this.getDriftedResourceCount(),
|
|
269
|
+
timestamp: this.timestamp.toISOString(),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ========================================
|
|
274
|
+
// Serialization
|
|
275
|
+
// ========================================
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Get string representation
|
|
279
|
+
* @returns {string}
|
|
280
|
+
*/
|
|
281
|
+
toString() {
|
|
282
|
+
return `StackHealthReport: ${this.stackIdentifier.toString()} - Score: ${this.healthScore.value} (${this.getQualitativeAssessment()}) - Resources: ${this.getResourceCount()}, Issues: ${this.getIssueCount()}`;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Serialize to JSON
|
|
287
|
+
* @returns {Object}
|
|
288
|
+
*/
|
|
289
|
+
toJSON() {
|
|
290
|
+
return {
|
|
291
|
+
stackIdentifier: this.stackIdentifier.toJSON(),
|
|
292
|
+
healthScore: this.healthScore.value,
|
|
293
|
+
qualitativeAssessment: this.getQualitativeAssessment(),
|
|
294
|
+
isHealthy: this.isHealthy(),
|
|
295
|
+
resources: this.resources.map((r) => r.toJSON()),
|
|
296
|
+
issues: this.issues.map((i) => i.toJSON()),
|
|
297
|
+
resourceCount: this.getResourceCount(),
|
|
298
|
+
issueCount: this.getIssueCount(),
|
|
299
|
+
summary: this.getSummary(),
|
|
300
|
+
timestamp: this.timestamp.toISOString(),
|
|
301
|
+
metadata: this.metadata,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = StackHealthReport;
|