@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
package/dist/azure/report.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Azure report generation
|
|
3
3
|
*/
|
|
4
|
+
import Table from "cli-table3";
|
|
5
|
+
// Fixed column widths — keeps the Reason column within a readable width
|
|
6
|
+
// while allowing cli-table3 to word-wrap long content across multiple lines.
|
|
7
|
+
const COL_WIDTHS = [30, 35, 25, 8, 55];
|
|
4
8
|
// ANSI color codes — only applied when stdout is a TTY to avoid cluttering redirected output
|
|
5
9
|
const isTTY = process.stdout.isTTY ?? false;
|
|
6
10
|
const RED = isTTY ? "\x1b[31m" : "";
|
|
7
11
|
const YELLOW = isTTY ? "\x1b[33m" : "";
|
|
8
12
|
const BLUE = isTTY ? "\x1b[34m" : "";
|
|
13
|
+
const GREEN = isTTY ? "\x1b[32m" : "";
|
|
14
|
+
const CYAN = isTTY ? "\x1b[36m" : "";
|
|
9
15
|
const BOLD = isTTY ? "\x1b[1m" : "";
|
|
10
16
|
const RESET = isTTY ? "\x1b[0m" : "";
|
|
11
17
|
const DIM = isTTY ? "\x1b[2m" : "";
|
|
@@ -30,66 +36,209 @@ export async function generateReport(report, format) {
|
|
|
30
36
|
console.log(JSON.stringify(report, null, 2));
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
|
-
// For other formats, we extract the summary data.
|
|
34
|
-
const summaryReport = report.map((r) => ({
|
|
35
|
-
costRisk: r.analysis.costRisk,
|
|
36
|
-
location: r.resource.location ?? "",
|
|
37
|
-
name: r.resource.name ?? "unknown",
|
|
38
|
-
reason: r.analysis.reason,
|
|
39
|
-
resourceGroup: r.resource.id?.split("/")[4],
|
|
40
|
-
subscriptionId: r.resource.id?.split("/")[2] ?? "unknown",
|
|
41
|
-
suspectedUnused: r.analysis.suspectedUnused,
|
|
42
|
-
type: r.resource.type ?? "unknown",
|
|
43
|
-
}));
|
|
44
39
|
if (format === "json") {
|
|
40
|
+
const summaryReport = report.map((r) => ({
|
|
41
|
+
costRisk: r.analysis.costRisk,
|
|
42
|
+
location: r.resource.location ?? "",
|
|
43
|
+
name: r.resource.name ?? "unknown",
|
|
44
|
+
reason: r.analysis.reason,
|
|
45
|
+
resourceGroup: r.resource.id?.split("/")[4],
|
|
46
|
+
subscriptionId: r.resource.id?.split("/")[2] ?? "unknown",
|
|
47
|
+
suspectedUnused: r.analysis.suspectedUnused,
|
|
48
|
+
type: r.resource.type ?? "unknown",
|
|
49
|
+
}));
|
|
45
50
|
console.log(JSON.stringify(summaryReport, null, 2));
|
|
46
51
|
}
|
|
47
52
|
else if (format === "lint") {
|
|
48
53
|
generateLintReport(report);
|
|
49
54
|
}
|
|
50
55
|
else {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
const summaryReport = report.map((r) => ({
|
|
57
|
+
costRisk: r.analysis.costRisk,
|
|
58
|
+
location: r.resource.location ?? "",
|
|
59
|
+
name: r.resource.name ?? "unknown",
|
|
60
|
+
reason: r.analysis.reason,
|
|
61
|
+
resourceGroup: r.resource.id?.split("/")[4],
|
|
62
|
+
subscriptionId: r.resource.id?.split("/")[2] ?? "unknown",
|
|
63
|
+
suspectedUnused: r.analysis.suspectedUnused,
|
|
64
|
+
type: r.resource.type ?? "unknown",
|
|
65
|
+
}));
|
|
66
|
+
const table = new Table({
|
|
67
|
+
colWidths: [...COL_WIDTHS],
|
|
68
|
+
head: ["Name", "Type", "Resource Group", "Risk", "Reason"],
|
|
69
|
+
wordWrap: true,
|
|
70
|
+
});
|
|
71
|
+
for (const r of summaryReport) {
|
|
72
|
+
table.push([
|
|
73
|
+
r.name,
|
|
74
|
+
r.type,
|
|
75
|
+
r.resourceGroup || "N/A",
|
|
76
|
+
r.costRisk,
|
|
77
|
+
r.reason,
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
console.log(table.toString());
|
|
81
|
+
// Same summary block the lint format prints, so the table view also
|
|
82
|
+
// surfaces totals, source breakdown and estimated savings at a glance.
|
|
83
|
+
printSummary(computeSummary(report));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Walks the report once to compute totals, source breakdown and savings
|
|
88
|
+
* per currency without mutating it. Used by the table format, which has
|
|
89
|
+
* no per-line walk of its own.
|
|
90
|
+
*/
|
|
91
|
+
function computeSummary(report) {
|
|
92
|
+
const summary = {
|
|
93
|
+
counts: { high: 0, low: 0, medium: 0 },
|
|
94
|
+
savingsByCurrency: new Map(),
|
|
95
|
+
sourceCounts: {},
|
|
96
|
+
};
|
|
97
|
+
for (const entry of report) {
|
|
98
|
+
const lines = entry.findings?.length
|
|
99
|
+
? entry.findings.map((f) => ({
|
|
100
|
+
savings: f.estimatedMonthlySavings,
|
|
101
|
+
severity: f.severity,
|
|
102
|
+
source: f.source,
|
|
103
|
+
}))
|
|
104
|
+
: splitReasons(entry.analysis.reason).map(() => ({
|
|
105
|
+
savings: undefined,
|
|
106
|
+
severity: entry.analysis.costRisk,
|
|
107
|
+
source: "custom",
|
|
108
|
+
}));
|
|
109
|
+
for (const line of lines) {
|
|
110
|
+
summary.counts[line.severity]++;
|
|
111
|
+
summary.sourceCounts[line.source] =
|
|
112
|
+
(summary.sourceCounts[line.source] ?? 0) + 1;
|
|
113
|
+
if (line.savings) {
|
|
114
|
+
summary.savingsByCurrency.set(line.savings.currency, (summary.savingsByCurrency.get(line.savings.currency) ?? 0) +
|
|
115
|
+
line.savings.amount);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return summary;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Formats a monetary amount with an ISO 4217 currency code, falling back
|
|
123
|
+
* to the raw code prefix when the runtime locale data lacks the currency
|
|
124
|
+
* symbol.
|
|
125
|
+
*/
|
|
126
|
+
function formatAmount(amount, currency) {
|
|
127
|
+
try {
|
|
128
|
+
return new Intl.NumberFormat("en-US", {
|
|
129
|
+
currency,
|
|
130
|
+
maximumFractionDigits: 2,
|
|
131
|
+
minimumFractionDigits: 2,
|
|
132
|
+
style: "currency",
|
|
133
|
+
}).format(amount);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return `${currency} ${amount.toFixed(2)}`;
|
|
58
137
|
}
|
|
59
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Renders an estimated monthly savings value as the short "(€ 12.50/mo)"
|
|
141
|
+
* label that ships next to each lint line. Returns an empty string when
|
|
142
|
+
* the analyzer didn't provide an estimate.
|
|
143
|
+
*/
|
|
144
|
+
function formatSavings(savings) {
|
|
145
|
+
if (!savings)
|
|
146
|
+
return "";
|
|
147
|
+
return `(${formatAmount(savings.amount, savings.currency)}/mo)`;
|
|
148
|
+
}
|
|
60
149
|
/**
|
|
61
150
|
* Renders a linter-style report to stdout, grouping findings by resource.
|
|
62
151
|
*
|
|
152
|
+
* When `Finding[]` is attached to a report (Phase 1+), each finding is
|
|
153
|
+
* rendered with its `source` badge (e.g. `[advisor]`) and the estimated
|
|
154
|
+
* monthly savings, when known. For older payloads without `findings` we
|
|
155
|
+
* fall back to splitting `analysis.reason` like before so the format stays
|
|
156
|
+
* backward compatible.
|
|
157
|
+
*
|
|
63
158
|
* Example output:
|
|
64
159
|
*
|
|
65
160
|
* /subscriptions/.../virtualMachines/my-vm
|
|
66
|
-
* ✖ HIGH VM
|
|
67
|
-
* ✖ HIGH No tags found.
|
|
161
|
+
* ✖ HIGH [advisor] Right-size your VM. (€ 12.50/mo)
|
|
162
|
+
* ✖ HIGH [custom] No tags found.
|
|
68
163
|
*
|
|
69
164
|
* Summary: 3 issues found (2 high, 0 medium, 1 low)
|
|
165
|
+
* Estimated monthly savings: € 12.50
|
|
70
166
|
*/
|
|
71
167
|
function generateLintReport(report) {
|
|
72
|
-
const
|
|
168
|
+
const summary = {
|
|
169
|
+
counts: { high: 0, low: 0, medium: 0 },
|
|
170
|
+
savingsByCurrency: new Map(),
|
|
171
|
+
sourceCounts: {},
|
|
172
|
+
};
|
|
73
173
|
for (const entry of report) {
|
|
74
174
|
const resourceId = entry.resource.id ?? `unknown/${entry.resource.name ?? "unknown"}`;
|
|
75
|
-
const risk = entry.analysis.costRisk;
|
|
76
|
-
const findings = splitReasons(entry.analysis.reason);
|
|
77
175
|
console.log(`${BOLD}${resourceId}${RESET}`);
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
176
|
+
const lines = entry.findings?.length
|
|
177
|
+
? entry.findings.map((f) => ({
|
|
178
|
+
extra: formatSavings(f.estimatedMonthlySavings),
|
|
179
|
+
severity: f.severity,
|
|
180
|
+
source: f.source,
|
|
181
|
+
text: f.reason,
|
|
182
|
+
}))
|
|
183
|
+
: splitReasons(entry.analysis.reason).map((text) => ({
|
|
184
|
+
extra: "",
|
|
185
|
+
severity: entry.analysis.costRisk,
|
|
186
|
+
source: "custom",
|
|
187
|
+
text,
|
|
188
|
+
}));
|
|
189
|
+
for (const line of lines) {
|
|
190
|
+
const icon = RISK_ICON[line.severity];
|
|
191
|
+
const color = RISK_COLOR[line.severity];
|
|
192
|
+
const label = `${color}${line.severity.toUpperCase().padEnd(6)}${RESET}`;
|
|
193
|
+
const sourceBadge = `${CYAN}[${line.source}]${RESET}`;
|
|
194
|
+
const extra = line.extra ? ` ${GREEN}${line.extra}${RESET}` : "";
|
|
195
|
+
console.log(` ${icon} ${label} ${sourceBadge} ${DIM}${line.text}${RESET}${extra}`);
|
|
196
|
+
summary.counts[line.severity]++;
|
|
197
|
+
summary.sourceCounts[line.source] =
|
|
198
|
+
(summary.sourceCounts[line.source] ?? 0) + 1;
|
|
199
|
+
}
|
|
200
|
+
if (entry.findings) {
|
|
201
|
+
for (const f of entry.findings) {
|
|
202
|
+
if (f.estimatedMonthlySavings) {
|
|
203
|
+
const { amount, currency } = f.estimatedMonthlySavings;
|
|
204
|
+
summary.savingsByCurrency.set(currency, (summary.savingsByCurrency.get(currency) ?? 0) + amount);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
84
207
|
}
|
|
85
208
|
console.log();
|
|
86
209
|
}
|
|
210
|
+
printSummary(summary);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Prints the shared trailer (issues, sources, estimated savings) used by
|
|
214
|
+
* both the lint and the table format.
|
|
215
|
+
*
|
|
216
|
+
* Savings are kept grouped by currency intentionally: Azure Advisor
|
|
217
|
+
* returns each recommendation in the subscription's native billing
|
|
218
|
+
* currency, so the same report can carry EUR and USD figures at the
|
|
219
|
+
* same time when subscriptions are billed in different regions. We do
|
|
220
|
+
* NOT convert across currencies — the rates would be both volatile and
|
|
221
|
+
* out of scope for this tool.
|
|
222
|
+
*/
|
|
223
|
+
function printSummary(summary) {
|
|
224
|
+
const { counts, savingsByCurrency, sourceCounts } = summary;
|
|
87
225
|
const total = counts.high + counts.medium + counts.low;
|
|
88
226
|
const summaryLine = `${BOLD}Summary:${RESET} ${total} issue${total !== 1 ? "s" : ""} found` +
|
|
89
227
|
` ${RED}(${counts.high} high${RESET}` +
|
|
90
228
|
`, ${YELLOW}${counts.medium} medium${RESET}` +
|
|
91
229
|
`, ${BLUE}${counts.low} low${RESET})`;
|
|
92
230
|
console.log(summaryLine);
|
|
231
|
+
const sourceBreakdown = Object.entries(sourceCounts)
|
|
232
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
233
|
+
.map(([source, n]) => `${n} ${source}`)
|
|
234
|
+
.join(", ");
|
|
235
|
+
if (sourceBreakdown) {
|
|
236
|
+
console.log(`${BOLD}Sources:${RESET} ${sourceBreakdown}`);
|
|
237
|
+
}
|
|
238
|
+
if (savingsByCurrency.size > 0) {
|
|
239
|
+
const parts = [...savingsByCurrency.entries()].map(([currency, amount]) => `${GREEN}${formatAmount(amount, currency)}${RESET}`);
|
|
240
|
+
console.log(`${BOLD}Estimated monthly savings:${RESET} ${parts.join(", ")}`);
|
|
241
|
+
}
|
|
93
242
|
}
|
|
94
243
|
/**
|
|
95
244
|
* Splits a concatenated reason string (sentences separated by ". ") into
|
package/dist/azure/report.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/azure/report.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/azure/report.ts"],"names":[],"mappings":"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"}
|
package/dist/azure/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Azure-specific types
|
|
3
3
|
*/
|
|
4
4
|
import type * as armResources from "@azure/arm-resources";
|
|
5
|
+
import type { Finding } from "../finding.js";
|
|
5
6
|
import type { AnalysisResult, BaseConfig, CostRisk, Thresholds } from "../types.js";
|
|
6
7
|
/**
|
|
7
8
|
* Azure configuration extending base config
|
|
@@ -18,6 +19,14 @@ export type AzureConfig = BaseConfig & {
|
|
|
18
19
|
* If omitted, all resources are analyzed.
|
|
19
20
|
*/
|
|
20
21
|
filterTags?: Map<string, string>;
|
|
22
|
+
/**
|
|
23
|
+
* Which finding sources to include in the run.
|
|
24
|
+
* - `"custom"` → enables the per-resource analyzer plugins
|
|
25
|
+
* - `"advisor"` → enables the Azure Advisor subscription-level analyzer
|
|
26
|
+
*
|
|
27
|
+
* Defaults to `["advisor", "custom"]` when omitted (i.e. all sources).
|
|
28
|
+
*/
|
|
29
|
+
sources?: AzureSource[];
|
|
21
30
|
subscriptionIds: string[];
|
|
22
31
|
/**
|
|
23
32
|
* Analysis thresholds. Defaults from DEFAULT_THRESHOLDS are used when not provided.
|
|
@@ -26,10 +35,22 @@ export type AzureConfig = BaseConfig & {
|
|
|
26
35
|
verbose?: boolean;
|
|
27
36
|
};
|
|
28
37
|
/**
|
|
29
|
-
* Detailed report for a single Azure resource with full resource object
|
|
38
|
+
* Detailed report for a single Azure resource with full resource object.
|
|
39
|
+
*
|
|
40
|
+
* Phase 1 introduces the optional `findings` field carrying the unified
|
|
41
|
+
* `Finding[]` model alongside the legacy `analysis` summary. The two are
|
|
42
|
+
* kept in sync by the orchestrator so existing report formats keep
|
|
43
|
+
* working untouched while new consumers (GUI, JSON exports, Phase 2
|
|
44
|
+
* pricing aggregation) can read structured findings directly.
|
|
30
45
|
*/
|
|
31
46
|
export type AzureDetailedResourceReport = {
|
|
32
47
|
analysis: AnalysisResult;
|
|
48
|
+
/**
|
|
49
|
+
* Structured findings attached to the resource. Always populated by the
|
|
50
|
+
* orchestrator (possibly empty). Optional only for backward compatibility
|
|
51
|
+
* with serialised payloads produced before Phase 1.
|
|
52
|
+
*/
|
|
53
|
+
findings?: Finding[];
|
|
33
54
|
resource: armResources.GenericResource;
|
|
34
55
|
};
|
|
35
56
|
/**
|
|
@@ -45,4 +66,10 @@ export type AzureResourceReport = {
|
|
|
45
66
|
suspectedUnused: boolean;
|
|
46
67
|
type: string;
|
|
47
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Finding sources that are valid for Azure analysis.
|
|
71
|
+
* Narrowed from `FindingSource` to exclude "aws", which is not a valid
|
|
72
|
+
* filter for Azure runs and would silently produce an empty report.
|
|
73
|
+
*/
|
|
74
|
+
export type AzureSource = "advisor" | "custom";
|
|
48
75
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/azure/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/azure/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAE1D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EACV,cAAc,EACd,UAAU,EACV,QAAQ,EACR,UAAU,EACX,MAAM,aAAa,CAAC;AAErB;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG;IACrC;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,UAAU,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B;;OAEG;IACH,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IACrB,QAAQ,EAAE,YAAY,CAAC,eAAe,CAAC;CACxC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,QAAQ,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,QAAQ,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* This tool does NOT modify, tag, or delete any resources.
|
|
14
14
|
*/
|
|
15
15
|
export { type Analyzer, type AnalyzerContext, type AzureClients, createDefaultAnalyzers, } from "./azure/analyzers/index.js";
|
|
16
|
-
export type { AzureConfig } from "./azure/types.js";
|
|
16
|
+
export type { AzureConfig, AzureSource } from "./azure/types.js";
|
|
17
17
|
import * as azureModule from "./azure/index.js";
|
|
18
18
|
export declare const azure: typeof azureModule;
|
|
19
19
|
export { type MetricsCache, type MonitorClientLike } from "./azure/utils.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGjE,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,eAAO,MAAM,KAAK,oBAAc,CAAC;AAEjC,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAG7E,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,eAAe,EACpB,0BAA0B,EAC1B,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,cAAc,GACpB,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAE3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAIpD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE1E"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -88,6 +88,10 @@ export declare const ConfigSchema: z.ZodObject<{
|
|
|
88
88
|
azure: z.ZodObject<{
|
|
89
89
|
concurrency: z.ZodOptional<z.ZodNumber>;
|
|
90
90
|
preferredLocation: z.ZodDefault<z.ZodString>;
|
|
91
|
+
sources: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
92
|
+
custom: "custom";
|
|
93
|
+
advisor: "advisor";
|
|
94
|
+
}>>>;
|
|
91
95
|
subscriptionIds: z.ZodArray<z.ZodString>;
|
|
92
96
|
thresholds: z.ZodPipe<z.ZodOptional<z.ZodObject<{
|
|
93
97
|
appService: z.ZodPipe<z.ZodOptional<z.ZodObject<{
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBlB,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAqBlB,CAAC;AAkCZ;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAAmD,CAAC;AAI7E,2DAA2D;AAC3D,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC"}
|
package/dist/schema.js
CHANGED
|
@@ -87,6 +87,15 @@ const AzureSectionSchema = z
|
|
|
87
87
|
*/
|
|
88
88
|
concurrency: z.number().int().positive().optional(),
|
|
89
89
|
preferredLocation: z.string().default("italynorth"),
|
|
90
|
+
/**
|
|
91
|
+
* Which finding sources to include. Defaults to all known sources.
|
|
92
|
+
* Authors can narrow the run to e.g. `["advisor"]` to fetch only
|
|
93
|
+
* Azure Advisor recommendations, or `["custom"]` to skip Advisor.
|
|
94
|
+
*/
|
|
95
|
+
sources: z
|
|
96
|
+
.array(z.enum(["advisor", "custom"]))
|
|
97
|
+
.nonempty()
|
|
98
|
+
.default(["advisor", "custom"]),
|
|
90
99
|
subscriptionIds: z
|
|
91
100
|
.array(z.string())
|
|
92
101
|
.min(1, "Config file must contain at least one entry in 'azure.subscriptionIds'"),
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pagopa/dx-savemoney",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Azure resource analyzer for finding unused or cost-inefficient resources.",
|
|
6
6
|
"repository": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"DX"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
+
"@azure/arm-advisor": "^3.2.0",
|
|
30
31
|
"@azure/arm-appcontainers": "^3.0.0",
|
|
31
32
|
"@azure/arm-appservice": "^17.0.0",
|
|
32
33
|
"@azure/arm-compute": "^23.3.0",
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
"@azure/arm-resources": "^7.0.0",
|
|
36
37
|
"@azure/identity": "^4.13.1",
|
|
37
38
|
"@logtape/logtape": "^1.3.8",
|
|
39
|
+
"cli-table3": "^0.6.5",
|
|
38
40
|
"js-yaml": "^4.1.1",
|
|
39
41
|
"p-limit": "^7.3.0",
|
|
40
42
|
"zod": "^4.4.2"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for tag-filter behavior on Advisor findings.
|
|
3
|
+
*
|
|
4
|
+
* These tests exercise the pure helper used by the Azure analyzer
|
|
5
|
+
* orchestrator to keep filtering semantics explicit and stable.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, expect, it } from "vitest";
|
|
9
|
+
|
|
10
|
+
import type { Finding } from "../../finding.js";
|
|
11
|
+
|
|
12
|
+
import { shouldIncludeAdvisorFindingForTags } from "../analyzer.js";
|
|
13
|
+
|
|
14
|
+
function mkFinding(resourceId: string, source: Finding["source"]): Finding {
|
|
15
|
+
return {
|
|
16
|
+
category: "cost",
|
|
17
|
+
code: `${source}.test`,
|
|
18
|
+
reason: "Test finding.",
|
|
19
|
+
resourceId,
|
|
20
|
+
severity: "low",
|
|
21
|
+
source,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("shouldIncludeAdvisorFindingForTags", () => {
|
|
26
|
+
it("includes all findings when no tag filter is active", () => {
|
|
27
|
+
const finding = mkFinding(
|
|
28
|
+
"/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
|
29
|
+
"advisor",
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
expect(
|
|
33
|
+
shouldIncludeAdvisorFindingForTags(finding, new Set<string>(), false),
|
|
34
|
+
).toBe(true);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("keeps subscription-level Advisor findings global even with tag filters", () => {
|
|
38
|
+
const finding = mkFinding("/subscriptions/sub1", "advisor");
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
shouldIncludeAdvisorFindingForTags(finding, new Set<string>(), true),
|
|
42
|
+
).toBe(true);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("includes resource-level Advisor findings only when resource id is tag-matched", () => {
|
|
46
|
+
const finding = mkFinding(
|
|
47
|
+
"/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
|
48
|
+
"advisor",
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const taggedResourceIds = new Set<string>([
|
|
52
|
+
"/subscriptions/sub1/resourcegroups/rg1/providers/microsoft.compute/virtualmachines/vm1",
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
expect(
|
|
56
|
+
shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, true),
|
|
57
|
+
).toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("excludes resource-level Advisor findings when resource id is not tag-matched", () => {
|
|
61
|
+
const finding = mkFinding(
|
|
62
|
+
"/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm2",
|
|
63
|
+
"advisor",
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const taggedResourceIds = new Set<string>([
|
|
67
|
+
"/subscriptions/sub1/resourcegroups/rg1/providers/microsoft.compute/virtualmachines/vm1",
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
expect(
|
|
71
|
+
shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, true),
|
|
72
|
+
).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -136,3 +136,30 @@ describe("generateReport — lint format", () => {
|
|
|
136
136
|
expect(calls[0]).toContain("0 issues found");
|
|
137
137
|
});
|
|
138
138
|
});
|
|
139
|
+
|
|
140
|
+
describe("generateReport — table format", () => {
|
|
141
|
+
let logSpy: ReturnType<typeof vi.spyOn>;
|
|
142
|
+
|
|
143
|
+
beforeEach(() => {
|
|
144
|
+
logSpy = vi.spyOn(console, "log").mockImplementation(() => undefined);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
afterEach(() => {
|
|
148
|
+
vi.restoreAllMocks();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("renders without throwing for an empty report", async () => {
|
|
152
|
+
await expect(generateReport([], "table")).resolves.toBeUndefined();
|
|
153
|
+
// Table is always printed; the summary line ("0 issues found") follows.
|
|
154
|
+
expect(logSpy.mock.calls.length).toBeGreaterThanOrEqual(1);
|
|
155
|
+
const output = logSpy.mock.calls.map((c) => String(c[0])).join("\n");
|
|
156
|
+
expect(output).toContain("0 issues found");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("includes resource name and reason in the rendered table", async () => {
|
|
160
|
+
await generateReport([HIGH_ENTRY], "table");
|
|
161
|
+
const output = logSpy.mock.calls.map((c) => String(c[0])).join("\n");
|
|
162
|
+
expect(output).toContain("vm-high");
|
|
163
|
+
expect(output).toContain("VM is deallocated");
|
|
164
|
+
});
|
|
165
|
+
});
|