@pagopa/dx-savemoney 0.2.6 → 0.3.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/README.md +33 -27
- package/dist/azure/analyzer.d.ts +18 -5
- package/dist/azure/analyzer.d.ts.map +1 -1
- package/dist/azure/analyzer.js +295 -48
- package/dist/azure/analyzer.js.map +1 -1
- package/dist/azure/analyzers/advisor.d.ts +68 -0
- package/dist/azure/analyzers/advisor.d.ts.map +1 -0
- package/dist/azure/analyzers/advisor.js +234 -0
- package/dist/azure/analyzers/advisor.js.map +1 -0
- package/dist/azure/analyzers/index.d.ts +3 -1
- package/dist/azure/analyzers/index.d.ts.map +1 -1
- package/dist/azure/analyzers/index.js +2 -1
- package/dist/azure/analyzers/index.js.map +1 -1
- package/dist/azure/analyzers/registry.d.ts +8 -0
- package/dist/azure/analyzers/registry.d.ts.map +1 -1
- package/dist/azure/analyzers/registry.js +10 -0
- package/dist/azure/analyzers/registry.js.map +1 -1
- package/dist/azure/analyzers/subscription.d.ts +53 -0
- package/dist/azure/analyzers/subscription.d.ts.map +1 -0
- package/dist/azure/analyzers/subscription.js +18 -0
- package/dist/azure/analyzers/subscription.js.map +1 -0
- package/dist/azure/config.d.ts.map +1 -1
- package/dist/azure/config.js +1 -0
- package/dist/azure/config.js.map +1 -1
- package/dist/azure/index.d.ts +1 -0
- package/dist/azure/index.d.ts.map +1 -1
- package/dist/azure/index.js +1 -0
- package/dist/azure/index.js.map +1 -1
- package/dist/azure/report.d.ts.map +1 -1
- package/dist/azure/report.js +178 -29
- package/dist/azure/report.js.map +1 -1
- package/dist/azure/types.d.ts +28 -1
- package/dist/azure/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/schema.d.ts +4 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -0
- package/dist/schema.js.map +1 -1
- package/package.json +3 -1
- package/src/azure/__tests__/analyzer-tags.test.ts +74 -0
- package/src/azure/__tests__/report.test.ts +27 -0
- package/src/azure/analyzer.ts +421 -65
- package/src/azure/analyzers/__tests__/advisor.test.ts +367 -0
- package/src/azure/analyzers/advisor.ts +324 -0
- package/src/azure/analyzers/index.ts +9 -1
- package/src/azure/analyzers/registry.ts +12 -0
- package/src/azure/analyzers/subscription.ts +56 -0
- package/src/azure/config.ts +1 -0
- package/src/azure/index.ts +1 -0
- package/src/azure/report.ts +206 -35
- package/src/azure/types.ts +29 -1
- package/src/index.ts +1 -1
- package/src/schema.ts +9 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure Advisor analyzer.
|
|
3
|
+
*
|
|
4
|
+
* Fetches all `Cost` recommendations for the target subscription via the
|
|
5
|
+
* `@azure/arm-advisor` SDK and normalises them into the unified `Finding`
|
|
6
|
+
* model.
|
|
7
|
+
*
|
|
8
|
+
* ## Two kinds of Advisor cost recommendations
|
|
9
|
+
*
|
|
10
|
+
* ### Resource-specific (resourceMetadata.resourceId contains /providers/)
|
|
11
|
+
* Recommendations about a particular Azure resource (e.g. "right-size this
|
|
12
|
+
* VM"). Each is emitted as a separate Finding so it appears next to the
|
|
13
|
+
* resource in the per-resource section of the report. `enrichReason` adds
|
|
14
|
+
* SKU / region / term context from `extendedProperties` so findings for
|
|
15
|
+
* the same recommendation type on different resources are distinguishable.
|
|
16
|
+
*
|
|
17
|
+
* ### Subscription-scoped (no resourceId, or resourceId without /providers/)
|
|
18
|
+
* Reserved Instance and Savings Plan recommendations. The Advisor API
|
|
19
|
+
* returns **one entry per qualifying combination** (term, scope, quantity,
|
|
20
|
+
* etc.) but the Azure Portal **groups them by `recommendationTypeId`** and
|
|
21
|
+
* shows a single entry for the best option — matching the mental model
|
|
22
|
+
* a user has when deciding whether to buy a commitment.
|
|
23
|
+
*
|
|
24
|
+
* Different amounts within the same `recommendationTypeId` represent
|
|
25
|
+
* **mutually exclusive purchase options** (e.g. different DB sizes or
|
|
26
|
+
* quantities); the user would choose one, not buy all. We therefore report
|
|
27
|
+
* the **maximum** savings amount across all unique configurations, matching
|
|
28
|
+
* the portal's behaviour. Scope variants (Shared / Single / ResourceGroup)
|
|
29
|
+
* that carry the same amount are deduplicated so they do not count more
|
|
30
|
+
* than once. True duplicates — recommendations with the same ARM ID —
|
|
31
|
+
* are also removed before taking the maximum.
|
|
32
|
+
*/
|
|
33
|
+
import type { SubscriptionAnalyzer, SubscriptionContext } from "./subscription.js";
|
|
34
|
+
/** Minimal client shape used by the analyzer (and injectable in tests). */
|
|
35
|
+
type AdvisorClientLike = {
|
|
36
|
+
recommendations: {
|
|
37
|
+
list(options?: {
|
|
38
|
+
filter?: string;
|
|
39
|
+
}): AsyncIterable<RecommendationInfo>;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
/** Minimal shape of a recommendation entry needed by the helpers. */
|
|
43
|
+
type RecommendationInfo = {
|
|
44
|
+
category?: string;
|
|
45
|
+
extendedProperties?: Record<string, unknown>;
|
|
46
|
+
id?: string;
|
|
47
|
+
impact?: string;
|
|
48
|
+
recommendationTypeId?: string;
|
|
49
|
+
resourceMetadata?: {
|
|
50
|
+
resourceId?: string;
|
|
51
|
+
};
|
|
52
|
+
shortDescription?: {
|
|
53
|
+
problem?: string;
|
|
54
|
+
solution?: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Builds the Advisor subscription-level analyzer.
|
|
59
|
+
*
|
|
60
|
+
* @param clientFactory Optional override to inject a mock client in tests.
|
|
61
|
+
* In production the default factory builds a real
|
|
62
|
+
* `AdvisorManagementClient` from the credential.
|
|
63
|
+
*/
|
|
64
|
+
export declare function createAdvisorAnalyzer(clientFactory?: {
|
|
65
|
+
build(credential: SubscriptionContext["credential"], subscriptionId: string): AdvisorClientLike;
|
|
66
|
+
}): SubscriptionAnalyzer;
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=advisor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisor.d.ts","sourceRoot":"","sources":["../../../src/azure/analyzers/advisor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAOH,OAAO,KAAK,EACV,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,mBAAmB,CAAC;AAE3B,2EAA2E;AAC3E,KAAK,iBAAiB,GAAG;IACvB,eAAe,EAAE;QACf,IAAI,CAAC,OAAO,CAAC,EAAE;YAAE,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,GAAG,aAAa,CAAC,kBAAkB,CAAC,CAAC;KACxE,CAAC;CACH,CAAC;AAEF,qEAAqE;AACrE,KAAK,kBAAkB,GAAG;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,gBAAgB,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC5D,CAAC;AAuBF;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,aAAa,CAAC,EAAE;IACpD,KAAK,CACH,UAAU,EAAE,mBAAmB,CAAC,YAAY,CAAC,EAC7C,cAAc,EAAE,MAAM,GACrB,iBAAiB,CAAC;CACtB,GAAG,oBAAoB,CAwCvB"}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Azure Advisor analyzer.
|
|
3
|
+
*
|
|
4
|
+
* Fetches all `Cost` recommendations for the target subscription via the
|
|
5
|
+
* `@azure/arm-advisor` SDK and normalises them into the unified `Finding`
|
|
6
|
+
* model.
|
|
7
|
+
*
|
|
8
|
+
* ## Two kinds of Advisor cost recommendations
|
|
9
|
+
*
|
|
10
|
+
* ### Resource-specific (resourceMetadata.resourceId contains /providers/)
|
|
11
|
+
* Recommendations about a particular Azure resource (e.g. "right-size this
|
|
12
|
+
* VM"). Each is emitted as a separate Finding so it appears next to the
|
|
13
|
+
* resource in the per-resource section of the report. `enrichReason` adds
|
|
14
|
+
* SKU / region / term context from `extendedProperties` so findings for
|
|
15
|
+
* the same recommendation type on different resources are distinguishable.
|
|
16
|
+
*
|
|
17
|
+
* ### Subscription-scoped (no resourceId, or resourceId without /providers/)
|
|
18
|
+
* Reserved Instance and Savings Plan recommendations. The Advisor API
|
|
19
|
+
* returns **one entry per qualifying combination** (term, scope, quantity,
|
|
20
|
+
* etc.) but the Azure Portal **groups them by `recommendationTypeId`** and
|
|
21
|
+
* shows a single entry for the best option — matching the mental model
|
|
22
|
+
* a user has when deciding whether to buy a commitment.
|
|
23
|
+
*
|
|
24
|
+
* Different amounts within the same `recommendationTypeId` represent
|
|
25
|
+
* **mutually exclusive purchase options** (e.g. different DB sizes or
|
|
26
|
+
* quantities); the user would choose one, not buy all. We therefore report
|
|
27
|
+
* the **maximum** savings amount across all unique configurations, matching
|
|
28
|
+
* the portal's behaviour. Scope variants (Shared / Single / ResourceGroup)
|
|
29
|
+
* that carry the same amount are deduplicated so they do not count more
|
|
30
|
+
* than once. True duplicates — recommendations with the same ARM ID —
|
|
31
|
+
* are also removed before taking the maximum.
|
|
32
|
+
*/
|
|
33
|
+
import { AdvisorManagementClient } from "@azure/arm-advisor";
|
|
34
|
+
import { getLogger } from "@logtape/logtape";
|
|
35
|
+
/**
|
|
36
|
+
* Builds the Advisor subscription-level analyzer.
|
|
37
|
+
*
|
|
38
|
+
* @param clientFactory Optional override to inject a mock client in tests.
|
|
39
|
+
* In production the default factory builds a real
|
|
40
|
+
* `AdvisorManagementClient` from the credential.
|
|
41
|
+
*/
|
|
42
|
+
export function createAdvisorAnalyzer(clientFactory) {
|
|
43
|
+
const build = clientFactory?.build ??
|
|
44
|
+
((credential, subscriptionId) => new AdvisorManagementClient(credential, subscriptionId));
|
|
45
|
+
return {
|
|
46
|
+
async analyze(ctx) {
|
|
47
|
+
const logger = getLogger(["savemoney", "azure", "advisor"]);
|
|
48
|
+
const client = build(ctx.credential, ctx.subscriptionId);
|
|
49
|
+
const resourceFindings = [];
|
|
50
|
+
const subGroups = new Map();
|
|
51
|
+
for await (const rec of client.recommendations.list({
|
|
52
|
+
filter: "Category eq 'Cost'",
|
|
53
|
+
})) {
|
|
54
|
+
if (rec.category?.toLowerCase() !== "cost")
|
|
55
|
+
continue;
|
|
56
|
+
const rawResourceId = rec.resourceMetadata?.resourceId;
|
|
57
|
+
const props = rec.extendedProperties;
|
|
58
|
+
const savings = parseSavings(props);
|
|
59
|
+
if (rawResourceId && /\/providers\//i.test(rawResourceId)) {
|
|
60
|
+
resourceFindings.push(buildResourceFinding(rec, savings, props, rawResourceId));
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
addToSubGroups(subGroups, rec, savings, ctx, logger);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const findings = [...resourceFindings, ...flushSubGroups(subGroups)];
|
|
67
|
+
logger.info(`Advisor: ${findings.length} cost finding(s) for ${ctx.subscriptionId}` +
|
|
68
|
+
` (${resourceFindings.length} resource-specific, ${subGroups.size} subscription-level)`);
|
|
69
|
+
return findings;
|
|
70
|
+
},
|
|
71
|
+
id: "azure.advisor",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
// ── helpers — extracted to keep `analyze` complexity within linter limits —
|
|
75
|
+
function addToSubGroups(subGroups, rec, savings, ctx, logger) {
|
|
76
|
+
const typeKey = rec.recommendationTypeId ??
|
|
77
|
+
`unknown.${rec.shortDescription?.problem ?? ""}`;
|
|
78
|
+
const existing = subGroups.get(typeKey);
|
|
79
|
+
if (existing) {
|
|
80
|
+
updateSubGroup(existing, rec.id ?? "", savings);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
subGroups.set(typeKey, createSubGroup(rec, typeKey, savings, ctx, logger));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function buildResourceFinding(rec, savings, props, rawResourceId) {
|
|
87
|
+
const problem = rec.shortDescription?.problem ??
|
|
88
|
+
rec.shortDescription?.solution ??
|
|
89
|
+
"Azure Advisor cost recommendation";
|
|
90
|
+
return {
|
|
91
|
+
category: "cost",
|
|
92
|
+
code: `advisor.${rec.recommendationTypeId ?? "unknown"}`,
|
|
93
|
+
estimatedMonthlySavings: savings,
|
|
94
|
+
reason: enrichReason(problem, props),
|
|
95
|
+
recommendedAction: rec.shortDescription?.solution,
|
|
96
|
+
resourceId: rawResourceId,
|
|
97
|
+
severity: mapImpact(rec.impact),
|
|
98
|
+
source: "advisor",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createSubGroup(rec, typeKey, savings, ctx, logger) {
|
|
102
|
+
const problem = rec.shortDescription?.problem ??
|
|
103
|
+
rec.shortDescription?.solution ??
|
|
104
|
+
"Azure Advisor cost recommendation";
|
|
105
|
+
const reason = problem.endsWith(".") ? problem : `${problem}.`;
|
|
106
|
+
const rawResourceId = rec.resourceMetadata?.resourceId;
|
|
107
|
+
const resourceId = rawResourceId ?? `/subscriptions/${ctx.subscriptionId}`;
|
|
108
|
+
if (!rawResourceId && ctx.verbose) {
|
|
109
|
+
logger.debug(`Advisor recommendation has no resourceId, attributed to subscription: ${typeKey}`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
bestAmount: savings?.amount ?? 0,
|
|
113
|
+
count: savings ? 1 : 0,
|
|
114
|
+
currency: savings?.currency ?? "USD",
|
|
115
|
+
proto: {
|
|
116
|
+
category: "cost",
|
|
117
|
+
code: `advisor.${typeKey}`,
|
|
118
|
+
reason,
|
|
119
|
+
recommendedAction: rec.shortDescription?.solution,
|
|
120
|
+
resourceId,
|
|
121
|
+
severity: mapImpact(rec.impact),
|
|
122
|
+
source: "advisor",
|
|
123
|
+
},
|
|
124
|
+
recIds: new Set(rec.id ? [rec.id] : []),
|
|
125
|
+
uniqueSavingsKeys: new Set(savings ? [`${savings.amount}`] : []),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Appends discriminating context (SKU, region, term, scope, …) to the
|
|
130
|
+
* Advisor short description so visually-duplicated reasons can be told
|
|
131
|
+
* apart. RI / Savings Plan recommendations in particular return the same
|
|
132
|
+
* `problem` string for every SKU+region+term combination, which made the
|
|
133
|
+
* report look like it carried duplicated rows. Each call stays a single
|
|
134
|
+
* Finding — we do NOT deduplicate, because every recommendation refers
|
|
135
|
+
* to a different commitment with its own savings figure.
|
|
136
|
+
*/
|
|
137
|
+
function enrichReason(problem, props) {
|
|
138
|
+
const base = problem.endsWith(".") ? problem : `${problem}.`;
|
|
139
|
+
if (!props)
|
|
140
|
+
return base;
|
|
141
|
+
const parts = [];
|
|
142
|
+
// armSkuName covers VM RIs; productName/serviceType cover SQL/Cosmos/etc.
|
|
143
|
+
const sku = props.armSkuName ?? props.productName ?? props.serviceType ?? props.sku;
|
|
144
|
+
if (sku)
|
|
145
|
+
parts.push(sku);
|
|
146
|
+
const region = props.region ?? props.location;
|
|
147
|
+
if (region)
|
|
148
|
+
parts.push(region);
|
|
149
|
+
// Normalise term values (P1Y / P3Y / 1_Year / 3_Year) into a compact label.
|
|
150
|
+
const rawTerm = props.term;
|
|
151
|
+
if (rawTerm) {
|
|
152
|
+
const term = /3/.test(rawTerm) ? "3y" : /1/.test(rawTerm) ? "1y" : rawTerm;
|
|
153
|
+
parts.push(term);
|
|
154
|
+
}
|
|
155
|
+
const scope = props.scope;
|
|
156
|
+
if (scope)
|
|
157
|
+
parts.push(scope.toLowerCase());
|
|
158
|
+
const qty = props.displayQuantity ?? props.quantity;
|
|
159
|
+
if (qty)
|
|
160
|
+
parts.push(`x${qty}`);
|
|
161
|
+
if (parts.length === 0)
|
|
162
|
+
return base;
|
|
163
|
+
return `${base} (${parts.join(", ")})`;
|
|
164
|
+
}
|
|
165
|
+
function flushSubGroups(subGroups) {
|
|
166
|
+
const findings = [];
|
|
167
|
+
for (const { bestAmount, count, currency, proto } of subGroups.values()) {
|
|
168
|
+
const estimatedMonthlySavings = bestAmount > 0 ? { amount: bestAmount, currency } : undefined;
|
|
169
|
+
const reason = count > 1
|
|
170
|
+
? proto.reason.replace(/\.$/, ` (${count} options).`)
|
|
171
|
+
: proto.reason;
|
|
172
|
+
findings.push({ ...proto, estimatedMonthlySavings, reason });
|
|
173
|
+
}
|
|
174
|
+
return findings;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Maps Advisor's `impact` (`High` | `Medium` | `Low`) onto the savemoney
|
|
178
|
+
* `CostRisk` scale. Falls back to `low` for any unexpected value to keep
|
|
179
|
+
* the analyzer resilient to future Advisor enum extensions.
|
|
180
|
+
*/
|
|
181
|
+
function mapImpact(impact) {
|
|
182
|
+
switch (impact?.toLowerCase()) {
|
|
183
|
+
case "high":
|
|
184
|
+
return "high";
|
|
185
|
+
case "medium":
|
|
186
|
+
return "medium";
|
|
187
|
+
default:
|
|
188
|
+
return "low";
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Parses the savings amount from Advisor's `extendedProperties`. Advisor
|
|
193
|
+
* returns it as a string; treat anything non-numeric as "no estimate".
|
|
194
|
+
*/
|
|
195
|
+
function parseSavings(extendedProperties) {
|
|
196
|
+
if (!extendedProperties)
|
|
197
|
+
return undefined;
|
|
198
|
+
const raw = extendedProperties.savingsAmount ?? extendedProperties.savings;
|
|
199
|
+
if (!raw)
|
|
200
|
+
return undefined;
|
|
201
|
+
const amount = Number(raw);
|
|
202
|
+
if (!Number.isFinite(amount))
|
|
203
|
+
return undefined;
|
|
204
|
+
const currency = extendedProperties.savingsCurrency ?? extendedProperties.currency ?? "USD";
|
|
205
|
+
return { amount, currency };
|
|
206
|
+
}
|
|
207
|
+
function updateSubGroup(group, recId, savings) {
|
|
208
|
+
if (recId && group.recIds.has(recId))
|
|
209
|
+
return; // true API duplicate
|
|
210
|
+
if (recId)
|
|
211
|
+
group.recIds.add(recId);
|
|
212
|
+
if (!savings)
|
|
213
|
+
return;
|
|
214
|
+
// Bootstrap currency from the first recommendation that carries savings.
|
|
215
|
+
// createSubGroup() defaults currency to "USD" when the first entry has no
|
|
216
|
+
// savings; without this check, later entries with a different currency (e.g.
|
|
217
|
+
// "EUR") would be silently dropped and no savings would ever be surfaced.
|
|
218
|
+
if (group.count === 0) {
|
|
219
|
+
group.currency = savings.currency;
|
|
220
|
+
group.uniqueSavingsKeys.add(`${savings.amount}`);
|
|
221
|
+
group.bestAmount = savings.amount;
|
|
222
|
+
group.count = 1;
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (savings.currency !== group.currency)
|
|
226
|
+
return;
|
|
227
|
+
const key = `${savings.amount}`;
|
|
228
|
+
if (group.uniqueSavingsKeys.has(key))
|
|
229
|
+
return; // scope variant, already seen
|
|
230
|
+
group.uniqueSavingsKeys.add(key);
|
|
231
|
+
group.bestAmount = Math.max(group.bestAmount, savings.amount);
|
|
232
|
+
group.count++;
|
|
233
|
+
}
|
|
234
|
+
//# sourceMappingURL=advisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"advisor.js","sourceRoot":"","sources":["../../../src/azure/analyzers/advisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Re-exports for the Azure analyzer plugin layer.
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
4
|
+
export { createAdvisorAnalyzer } from "./advisor.js";
|
|
5
|
+
export { createDefaultAnalyzers, createDefaultSubscriptionAnalyzers, } from "./registry.js";
|
|
6
|
+
export type { SubscriptionAnalyzer, SubscriptionContext, } from "./subscription.js";
|
|
5
7
|
export type { Analyzer, AnalyzerContext, AzureClients } from "./types.js";
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/azure/analyzers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/azure/analyzers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,sBAAsB,EACtB,kCAAkC,GACnC,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Re-exports for the Azure analyzer plugin layer.
|
|
3
3
|
*/
|
|
4
|
-
export {
|
|
4
|
+
export { createAdvisorAnalyzer } from "./advisor.js";
|
|
5
|
+
export { createDefaultAnalyzers, createDefaultSubscriptionAnalyzers, } from "./registry.js";
|
|
5
6
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/azure/analyzers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/azure/analyzers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EACL,sBAAsB,EACtB,kCAAkC,GACnC,MAAM,eAAe,CAAC"}
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Adding a new analyzer is a single insertion here.
|
|
11
11
|
*/
|
|
12
|
+
import type { SubscriptionAnalyzer } from "./subscription.js";
|
|
12
13
|
import type { Analyzer } from "./types.js";
|
|
13
14
|
/**
|
|
14
15
|
* Builds the default set of analyzers in the same order they were
|
|
@@ -18,4 +19,11 @@ import type { Analyzer } from "./types.js";
|
|
|
18
19
|
* logging and to ease future debugging.
|
|
19
20
|
*/
|
|
20
21
|
export declare function createDefaultAnalyzers(): Analyzer[];
|
|
22
|
+
/**
|
|
23
|
+
* Builds the default set of subscription-level analyzers.
|
|
24
|
+
*
|
|
25
|
+
* Today this is just Azure Advisor; Phase 4 will add a quota / usages
|
|
26
|
+
* analyzer here. Adding new sources is a single insertion.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createDefaultSubscriptionAnalyzers(): SubscriptionAnalyzer[];
|
|
21
29
|
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/azure/analyzers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/azure/analyzers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAe3C;;;;;;GAMG;AACH,wBAAgB,sBAAsB,IAAI,QAAQ,EAAE,CAsJnD;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,IAAI,oBAAoB,EAAE,CAE3E"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Adding a new analyzer is a single insertion here.
|
|
11
11
|
*/
|
|
12
12
|
import { analyzeAppServicePlan, analyzeContainerApp, analyzeDisk, analyzeNic, analyzePrivateEndpoint, analyzePublicIp, analyzeStaticSite, analyzeStorageAccount, analyzeVM, } from "../resources/index.js";
|
|
13
|
+
import { createAdvisorAnalyzer } from "./advisor.js";
|
|
13
14
|
/**
|
|
14
15
|
* Builds the default set of analyzers in the same order they were
|
|
15
16
|
* previously evaluated by the orchestrator's `switch` statement. The
|
|
@@ -66,4 +67,13 @@ export function createDefaultAnalyzers() {
|
|
|
66
67
|
},
|
|
67
68
|
];
|
|
68
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Builds the default set of subscription-level analyzers.
|
|
72
|
+
*
|
|
73
|
+
* Today this is just Azure Advisor; Phase 4 will add a quota / usages
|
|
74
|
+
* analyzer here. Adding new sources is a single insertion.
|
|
75
|
+
*/
|
|
76
|
+
export function createDefaultSubscriptionAnalyzers() {
|
|
77
|
+
return [createAdvisorAnalyzer()];
|
|
78
|
+
}
|
|
69
79
|
//# sourceMappingURL=registry.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/azure/analyzers/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/azure/analyzers/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription-level analyzer plugin contract.
|
|
3
|
+
*
|
|
4
|
+
* Some data sources are not naturally per-resource. Azure Advisor, for
|
|
5
|
+
* example, returns a flat list of recommendations for an entire
|
|
6
|
+
* subscription in a single call; future quota / `Microsoft.Capacity/usages`
|
|
7
|
+
* analyzers will follow the same shape.
|
|
8
|
+
*
|
|
9
|
+
* A `SubscriptionAnalyzer` is invoked once per subscription. It returns a
|
|
10
|
+
* flat list of `Finding`s carrying the `resourceId` they refer to; the
|
|
11
|
+
* orchestrator merges those findings into the per-resource reports.
|
|
12
|
+
*
|
|
13
|
+
* This interface is intentionally additive: per-resource `Analyzer`s
|
|
14
|
+
* (see `./types.ts`) keep working untouched. Sources written against the
|
|
15
|
+
* two interfaces can coexist in the same run.
|
|
16
|
+
*/
|
|
17
|
+
import type { TokenCredential } from "@azure/identity";
|
|
18
|
+
import type { Finding } from "../../finding.js";
|
|
19
|
+
/**
|
|
20
|
+
* Contract every subscription-level analyzer must satisfy.
|
|
21
|
+
*/
|
|
22
|
+
export type SubscriptionAnalyzer = {
|
|
23
|
+
/**
|
|
24
|
+
* Runs the analyzer for the given subscription. Implementations should
|
|
25
|
+
* be resilient: a failure here must not abort the whole run, the
|
|
26
|
+
* orchestrator logs and continues with the remaining analyzers.
|
|
27
|
+
*/
|
|
28
|
+
analyze(ctx: SubscriptionContext): Promise<Finding[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Stable identifier of the analyzer (e.g. `azure.advisor`,
|
|
31
|
+
* `azure.quota`). Used for logging and future deduplication logic.
|
|
32
|
+
*/
|
|
33
|
+
readonly id: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Context passed to every subscription-level analyzer.
|
|
37
|
+
*/
|
|
38
|
+
export type SubscriptionContext = {
|
|
39
|
+
/**
|
|
40
|
+
* Azure credential to instantiate management clients. The orchestrator
|
|
41
|
+
* builds it once and reuses it across analyzers.
|
|
42
|
+
*/
|
|
43
|
+
credential: TokenCredential;
|
|
44
|
+
/**
|
|
45
|
+
* Subscription ID to analyze.
|
|
46
|
+
*/
|
|
47
|
+
subscriptionId: string;
|
|
48
|
+
/**
|
|
49
|
+
* Whether verbose logging is enabled.
|
|
50
|
+
*/
|
|
51
|
+
verbose: boolean;
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=subscription.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.d.ts","sourceRoot":"","sources":["../../../src/azure/analyzers/subscription.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG;IACjC;;;;OAIG;IACH,OAAO,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACtD;;;OAGG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC;;;OAGG;IACH,UAAU,EAAE,eAAe,CAAC;IAC5B;;OAEG;IACH,cAAc,EAAE,MAAM,CAAC;IACvB;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscription-level analyzer plugin contract.
|
|
3
|
+
*
|
|
4
|
+
* Some data sources are not naturally per-resource. Azure Advisor, for
|
|
5
|
+
* example, returns a flat list of recommendations for an entire
|
|
6
|
+
* subscription in a single call; future quota / `Microsoft.Capacity/usages`
|
|
7
|
+
* analyzers will follow the same shape.
|
|
8
|
+
*
|
|
9
|
+
* A `SubscriptionAnalyzer` is invoked once per subscription. It returns a
|
|
10
|
+
* flat list of `Finding`s carrying the `resourceId` they refer to; the
|
|
11
|
+
* orchestrator merges those findings into the per-resource reports.
|
|
12
|
+
*
|
|
13
|
+
* This interface is intentionally additive: per-resource `Analyzer`s
|
|
14
|
+
* (see `./types.ts`) keep working untouched. Sources written against the
|
|
15
|
+
* two interfaces can coexist in the same run.
|
|
16
|
+
*/
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=subscription.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subscription.js","sourceRoot":"","sources":["../../../src/azure/analyzers/subscription.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/azure/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,eAAe,CACnC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/azure/config.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,eAAe,CACnC,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,CAAC,CA4CtB;AAED;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAW9D"}
|
package/dist/azure/config.js
CHANGED
|
@@ -37,6 +37,7 @@ export async function loadAzureConfig(configPath) {
|
|
|
37
37
|
return {
|
|
38
38
|
concurrency: parsed.azure.concurrency,
|
|
39
39
|
preferredLocation: parsed.azure.preferredLocation,
|
|
40
|
+
sources: parsed.azure.sources,
|
|
40
41
|
subscriptionIds: parsed.azure.subscriptionIds,
|
|
41
42
|
thresholds: parsed.azure.thresholds,
|
|
42
43
|
timespanDays: parsed.azure.timespanDays,
|
package/dist/azure/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/azure/config.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/azure/config.ts"],"names":[],"mappings":"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"}
|
package/dist/azure/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/azure/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/azure/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/azure/index.js
CHANGED
package/dist/azure/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/azure/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/azure/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/azure/report.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../../src/azure/report.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,KAAK,EACV,2BAA2B,EAE5B,MAAM,YAAY,CAAC;AAmCpB;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,2BAA2B,EAAE,EACrC,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,iBAmDpD"}
|