@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.
Files changed (54) hide show
  1. package/README.md +33 -27
  2. package/dist/azure/analyzer.d.ts +18 -5
  3. package/dist/azure/analyzer.d.ts.map +1 -1
  4. package/dist/azure/analyzer.js +295 -48
  5. package/dist/azure/analyzer.js.map +1 -1
  6. package/dist/azure/analyzers/advisor.d.ts +68 -0
  7. package/dist/azure/analyzers/advisor.d.ts.map +1 -0
  8. package/dist/azure/analyzers/advisor.js +234 -0
  9. package/dist/azure/analyzers/advisor.js.map +1 -0
  10. package/dist/azure/analyzers/index.d.ts +3 -1
  11. package/dist/azure/analyzers/index.d.ts.map +1 -1
  12. package/dist/azure/analyzers/index.js +2 -1
  13. package/dist/azure/analyzers/index.js.map +1 -1
  14. package/dist/azure/analyzers/registry.d.ts +8 -0
  15. package/dist/azure/analyzers/registry.d.ts.map +1 -1
  16. package/dist/azure/analyzers/registry.js +10 -0
  17. package/dist/azure/analyzers/registry.js.map +1 -1
  18. package/dist/azure/analyzers/subscription.d.ts +53 -0
  19. package/dist/azure/analyzers/subscription.d.ts.map +1 -0
  20. package/dist/azure/analyzers/subscription.js +18 -0
  21. package/dist/azure/analyzers/subscription.js.map +1 -0
  22. package/dist/azure/config.d.ts.map +1 -1
  23. package/dist/azure/config.js +1 -0
  24. package/dist/azure/config.js.map +1 -1
  25. package/dist/azure/index.d.ts +1 -0
  26. package/dist/azure/index.d.ts.map +1 -1
  27. package/dist/azure/index.js +1 -0
  28. package/dist/azure/index.js.map +1 -1
  29. package/dist/azure/report.d.ts.map +1 -1
  30. package/dist/azure/report.js +178 -29
  31. package/dist/azure/report.js.map +1 -1
  32. package/dist/azure/types.d.ts +28 -1
  33. package/dist/azure/types.d.ts.map +1 -1
  34. package/dist/index.d.ts +1 -1
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/schema.d.ts +4 -0
  37. package/dist/schema.d.ts.map +1 -1
  38. package/dist/schema.js +9 -0
  39. package/dist/schema.js.map +1 -1
  40. package/package.json +3 -1
  41. package/src/azure/__tests__/analyzer-tags.test.ts +74 -0
  42. package/src/azure/__tests__/report.test.ts +27 -0
  43. package/src/azure/analyzer.ts +421 -65
  44. package/src/azure/analyzers/__tests__/advisor.test.ts +367 -0
  45. package/src/azure/analyzers/advisor.ts +324 -0
  46. package/src/azure/analyzers/index.ts +9 -1
  47. package/src/azure/analyzers/registry.ts +12 -0
  48. package/src/azure/analyzers/subscription.ts +56 -0
  49. package/src/azure/config.ts +1 -0
  50. package/src/azure/index.ts +1 -0
  51. package/src/azure/report.ts +206 -35
  52. package/src/azure/types.ts +29 -1
  53. package/src/index.ts +1 -1
  54. package/src/schema.ts +9 -0
package/README.md CHANGED
@@ -75,6 +75,7 @@ yarn add @pagopa/dx-savemoney
75
75
 
76
76
  - **Multi-Subscription Analysis**: Scans multiple Azure subscriptions in a single command.
77
77
  - **Intelligent Detection**: Uses Azure Monitor metrics (e.g. CPU, network traffic, transactions) to scientifically identify inactive resources.
78
+ - **Azure Advisor Integration**: Fetches Cost recommendations directly from Azure Advisor, including Reserved Instance and Savings Plan opportunities with estimated monthly savings.
78
79
  - **Orphaned Resource Identification**: Detects commonly "forgotten" resources like unattached disks, unassociated public IPs, and unused network interfaces.
79
80
  - **Flexible Reporting**: Offers multiple output formats:
80
81
  - `table`: A human-readable summary for the terminal.
@@ -89,17 +90,18 @@ yarn add @pagopa/dx-savemoney
89
90
 
90
91
  The tool analyzes the following Azure resource types with specific detection methods and risk levels:
91
92
 
92
- | Resource Type | Detection Method | Cost Risk | What's Checked |
93
- | :---------------------- | :---------------------- | :-------: | :------------------------------------------------------------------------------------------------------------------ |
94
- | **Virtual Machines** | Instance View + Metrics | 🔴 High | Deallocated/stopped state, Low CPU usage (<1%), Low network traffic (<3MB per days) |
95
- | **App Service Plans** | API Details + Metrics | 🔴 High | No apps deployed, Very low CPU (<5%), Very low memory (<10%), Oversized Premium tier |
96
- | **Container Apps** | API Details + Metrics | 🟡 Medium | Not running state, Zero replicas configured, Low CPU (<0.001 cores), Low memory (<10MB), Low network traffic (<1MB) |
97
- | **Managed Disks** | API Details | 🟡 Medium | Unattached state, No `managedBy` property |
98
- | **Public IP Addresses** | API Details + Metrics | 🟡 Medium | Not associated with any resource, Static IP not in use, Very low network traffic (<~340KB per day) |
99
- | **Network Interfaces** | API Details | 🟡 Medium | Not attached to VM or Private Endpoint, No public IP assigned |
100
- | **Private Endpoints** | API Details | 🟡 Medium | No private link connections, Rejected/disconnected connections, No network interfaces |
101
- | **Storage Accounts** | Metrics | 🟡 Medium | Very low transaction count (<10 per days in timespan) |
102
- | **Static Web Apps** | Metrics | 🟢 Low | No traffic data available, Very low site hits (<100 requests in 30 days), Very low data transfer (<1MB in 30 days) |
93
+ | Resource Type | Detection Method | Cost Risk | What's Checked |
94
+ | :---------------------- | :---------------------- | :-------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | **Virtual Machines** | Instance View + Metrics | 🔴 High | Deallocated/stopped state, Low CPU usage (<1%), Low network traffic (<3MB per days) |
96
+ | **App Service Plans** | API Details + Metrics | 🔴 High | No apps deployed, Very low CPU (<5%), Very low memory (<10%), Oversized Premium tier |
97
+ | **Container Apps** | API Details + Metrics | 🟡 Medium | Not running state, Zero replicas configured, Low CPU (<0.001 cores), Low memory (<10MB), Low network traffic (<1MB) |
98
+ | **Managed Disks** | API Details | 🟡 Medium | Unattached state, No `managedBy` property |
99
+ | **Public IP Addresses** | API Details + Metrics | 🟡 Medium | Not associated with any resource, Static IP not in use, Very low network traffic (<~340KB per day) |
100
+ | **Network Interfaces** | API Details | 🟡 Medium | Not attached to VM or Private Endpoint, No public IP assigned |
101
+ | **Private Endpoints** | API Details | 🟡 Medium | No private link connections, Rejected/disconnected connections, No network interfaces |
102
+ | **Storage Accounts** | Metrics | 🟡 Medium | Very low transaction count (<10 per days in timespan) |
103
+ | **Static Web Apps** | Metrics | 🟢 Low | No traffic data available, Very low site hits (<100 requests in 30 days), Very low data transfer (<1MB in 30 days) |
104
+ | **Azure Advisor** | Advisor API | ⚪ Varies | Reserved Instance and Savings Plan opportunities, right-sizing suggestions, and other Cost recommendations — with estimated monthly savings where available |
103
105
 
104
106
  #### Generic Checks
105
107
 
@@ -128,8 +130,9 @@ import { azure, loadConfig } from "@pagopa/dx-savemoney";
128
130
  // Load configuration (from file, env vars, or interactive prompt)
129
131
  const config = await loadConfig("./config.yaml");
130
132
 
131
- // Run analysis and generate report
132
- await azure.analyzeAzureResources(config, "table");
133
+ // Run analysis, then generate report
134
+ const reports = await azure.analyzeAzureResources(config);
135
+ await azure.generateReport(reports, "table");
133
136
  ```
134
137
 
135
138
  #### Configuration Inputs
@@ -141,6 +144,7 @@ The tool requires the following configuration:
141
144
  | `subscriptionIds` | `string[]` | ✅ | - | Array of Azure subscription IDs to analyze |
142
145
  | `preferredLocation` | `string` | ❌ | `italynorth` | Preferred Azure region (resources elsewhere will be flagged) |
143
146
  | `timespanDays` | `number` | ❌ | `30` | Number of days to look back for metrics analysis |
147
+ | `sources` | `FindingSource[]` | ❌ | all sources | Restrict analysis to specific sources: `"advisor"`, `"custom"`, or both |
144
148
  | `verbose` | `boolean` | ❌ | `false` | Enable detailed logging for each resource analyzed |
145
149
  | `filterTags` | `Record<string, string>` | ❌ | - | Only analyze resources matching **all** the specified tag key-value pairs |
146
150
  | `thresholds` | `Thresholds` | ❌ | see below | Override the default numeric thresholds used during analysis |
@@ -250,16 +254,14 @@ import { azure, loadConfig } from "@pagopa/dx-savemoney";
250
254
 
251
255
  const config = await loadConfig("./config.yaml");
252
256
  // Analyze only resources tagged with environment=prod AND team=platform
253
- await azure.analyzeAzureResources(
254
- {
255
- ...config,
256
- filterTags: new Map([
257
- ["environment", "prod"],
258
- ["team", "platform"],
259
- ]),
260
- },
261
- "lint",
262
- );
257
+ const reports = await azure.analyzeAzureResources({
258
+ ...config,
259
+ filterTags: new Map([
260
+ ["environment", "prod"],
261
+ ["team", "platform"],
262
+ ]),
263
+ });
264
+ await azure.generateReport(reports, "lint");
263
265
  ```
264
266
 
265
267
  ##### Basic Usage
@@ -269,7 +271,8 @@ import { azure, loadConfig } from "@pagopa/dx-savemoney";
269
271
 
270
272
  // Load from config file
271
273
  const config = await loadConfig("./config.yaml");
272
- await azure.analyzeAzureResources(config, "table");
274
+ const reports = await azure.analyzeAzureResources(config);
275
+ await azure.generateReport(reports, "table");
273
276
  ```
274
277
 
275
278
  ##### Custom Configuration
@@ -285,7 +288,8 @@ const config: AzureConfig = {
285
288
  verbose: true,
286
289
  };
287
290
 
288
- await azure.analyzeAzureResources(config, "json");
291
+ const reports = await azure.analyzeAzureResources(config);
292
+ await azure.generateReport(reports, "json");
289
293
  ```
290
294
 
291
295
  ##### Generate Detailed Report
@@ -295,7 +299,8 @@ import { azure, loadConfig } from "@pagopa/dx-savemoney";
295
299
 
296
300
  const config = await loadConfig();
297
301
  // Generate detailed JSON with full resource metadata
298
- await azure.analyzeAzureResources(config, "detailed-json");
302
+ const reports = await azure.analyzeAzureResources(config);
303
+ await azure.generateReport(reports, "detailed-json");
299
304
  ```
300
305
 
301
306
  ##### Using Environment Variables
@@ -307,7 +312,8 @@ import { loadConfig, azure } from "@pagopa/dx-savemoney";
307
312
  // ARM_SUBSCRIPTION_ID=sub1,sub2
308
313
 
309
314
  const config = await loadConfig(); // Will read from env vars
310
- await azure.analyzeAzureResources(config, "json");
315
+ const reports = await azure.analyzeAzureResources(config);
316
+ await azure.generateReport(reports, "json");
311
317
  ```
312
318
 
313
319
  ## AWS (Coming Soon)
@@ -19,17 +19,29 @@
19
19
  * existing report formats keep working untouched.
20
20
  */
21
21
  import * as armResources from "@azure/arm-resources";
22
- import type { AzureConfig } from "./types.js";
22
+ import type { Finding } from "../finding.js";
23
+ import type { AzureConfig, AzureDetailedResourceReport } from "./types.js";
23
24
  import { type AnalysisResult, type Thresholds } from "../types.js";
24
25
  import { type Analyzer, type AzureClients } from "./analyzers/index.js";
25
26
  import { type MetricsCache } from "./utils.js";
26
27
  /**
27
- * Analyzes resources in multiple Azure subscriptions and generates a report.
28
+ * Analyzes resources in every configured Azure subscription and returns
29
+ * the structured report.
28
30
  *
29
- * @param config - Azure configuration with subscription IDs and settings
30
- * @param format - Output format (table, json, detailed-json, or lint)
31
+ * Phase 1 change: this function no longer emits a report to stdout. The
32
+ * orchestrator returns `AzureDetailedResourceReport[]` and the caller
33
+ * (the CLI today, future GUI / API consumers tomorrow) chooses how to
34
+ * render it via `generateReport`.
35
+ *
36
+ * Each entry carries both the legacy `analysis` summary and the unified
37
+ * `findings: Finding[]` so consumers can pick the level of detail they
38
+ * need. Azure Advisor recommendations and per-resource analyzer outputs
39
+ * are merged into the same entry when they refer to the same resource.
40
+ *
41
+ * @param config - Azure configuration with subscription IDs and settings.
42
+ * `config.sources` controls which analyzers run.
31
43
  */
32
- export declare function analyzeAzureResources(config: AzureConfig, format: "detailed-json" | "json" | "lint" | "table"): Promise<void>;
44
+ export declare function analyzeAzureResources(config: AzureConfig): Promise<AzureDetailedResourceReport[]>;
33
45
  /**
34
46
  * Analyzes a single Azure resource by dispatching it to every registered
35
47
  * analyzer that supports it. Generic checks (missing tags, location
@@ -46,4 +58,5 @@ export declare function analyzeAzureResources(config: AzureConfig, format: "deta
46
58
  * @returns Analysis result with cost risk and reason
47
59
  */
48
60
  export declare function analyzeResource(resource: armResources.GenericResource, analyzers: Analyzer[], clients: AzureClients, metricsCache: MetricsCache | undefined, preferredLocation: string, timespanDays: number, thresholds: Thresholds, verbose?: boolean): Promise<AnalysisResult>;
61
+ export declare function shouldIncludeAdvisorFindingForTags(finding: Finding, taggedResourceIds: ReadonlySet<string>, tagFilterActive: boolean): boolean;
49
62
  //# sourceMappingURL=analyzer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAOH,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAE,WAAW,EAA+B,MAAM,YAAY,CAAC;AAE3E,OAAO,EACL,KAAK,cAAc,EAGnB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,YAAY,EAElB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAI5D;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,iBA6GpD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,YAAY,EACrB,YAAY,EAAE,YAAY,YAAY,EACtC,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA8CzB"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AASH,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAKrD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EACV,WAAW,EACX,2BAA2B,EAE5B,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,KAAK,cAAc,EAInB,KAAK,UAAU,EAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,KAAK,QAAQ,EAEb,KAAK,YAAY,EAIlB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAO5D;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,2BAA2B,EAAE,CAAC,CA4GxC;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,SAAS,EAAE,QAAQ,EAAE,EACrB,OAAO,EAAE,YAAY,EACrB,YAAY,EAAE,YAAY,YAAY,EACtC,iBAAiB,EAAE,MAAM,EACzB,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,EACtB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA8CzB;AAED,wBAAgB,kCAAkC,CAChD,OAAO,EAAE,OAAO,EAChB,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,EACtC,eAAe,EAAE,OAAO,GACvB,OAAO,CAYT"}
@@ -27,22 +27,41 @@ import * as armResources from "@azure/arm-resources";
27
27
  import { DefaultAzureCredential } from "@azure/identity";
28
28
  import { getLogger } from "@logtape/logtape";
29
29
  import pLimit from "p-limit";
30
+ import { findingsFromAnalysisResult } from "../finding.js";
30
31
  import { DEFAULT_THRESHOLDS, mergeResults, } from "../types.js";
31
- import { createDefaultAnalyzers, } from "./analyzers/index.js";
32
- import { generateReport } from "./report.js";
32
+ import { createDefaultAnalyzers, createDefaultSubscriptionAnalyzers, } from "./analyzers/index.js";
33
33
  import { matchesTags } from "./utils.js";
34
34
  const DEFAULT_CONCURRENCY = 8;
35
+ const DEFAULT_SOURCES = ["advisor", "custom"];
36
+ const RISK_ORDER = { high: 0, low: 2, medium: 1 };
35
37
  /**
36
- * Analyzes resources in multiple Azure subscriptions and generates a report.
38
+ * Analyzes resources in every configured Azure subscription and returns
39
+ * the structured report.
37
40
  *
38
- * @param config - Azure configuration with subscription IDs and settings
39
- * @param format - Output format (table, json, detailed-json, or lint)
41
+ * Phase 1 change: this function no longer emits a report to stdout. The
42
+ * orchestrator returns `AzureDetailedResourceReport[]` and the caller
43
+ * (the CLI today, future GUI / API consumers tomorrow) chooses how to
44
+ * render it via `generateReport`.
45
+ *
46
+ * Each entry carries both the legacy `analysis` summary and the unified
47
+ * `findings: Finding[]` so consumers can pick the level of detail they
48
+ * need. Azure Advisor recommendations and per-resource analyzer outputs
49
+ * are merged into the same entry when they refer to the same resource.
50
+ *
51
+ * @param config - Azure configuration with subscription IDs and settings.
52
+ * `config.sources` controls which analyzers run.
40
53
  */
41
- export async function analyzeAzureResources(config, format) {
54
+ export async function analyzeAzureResources(config) {
42
55
  const logger = getLogger(["savemoney", "azure"]);
43
56
  const credential = new DefaultAzureCredential();
44
57
  const allReports = [];
58
+ const sources = config.sources ?? DEFAULT_SOURCES;
59
+ const customEnabled = sources.includes("custom");
60
+ const advisorEnabled = sources.includes("advisor");
45
61
  const analyzers = createDefaultAnalyzers();
62
+ const subscriptionAnalyzers = advisorEnabled
63
+ ? createDefaultSubscriptionAnalyzers()
64
+ : [];
46
65
  const thresholds = config.thresholds ?? DEFAULT_THRESHOLDS;
47
66
  // Normalise concurrency the same way p-limit does to keep maxInFlight
48
67
  // consistent. A raw value of 0/NaN would produce maxInFlight = 0/NaN and
@@ -58,6 +77,11 @@ export async function analyzeAzureResources(config, format) {
58
77
  for (const subscriptionId of config.subscriptionIds) {
59
78
  logger.info(`Analyzing subscription: ${subscriptionId}`);
60
79
  const sid = subscriptionId.trim();
80
+ // Per-subscription index keyed by lowercased resourceId so subscription-
81
+ // level analyzers (Advisor, future quotas, …) can merge their findings
82
+ // back into the matching resource report.
83
+ const reportsById = new Map();
84
+ const taggedResourceIds = new Set();
61
85
  // Fresh cache per subscription — bounds peak memory to one subscription's
62
86
  // worth of metrics and keeps concurrent analyzeAzureResources calls isolated.
63
87
  const runCache = new Map();
@@ -68,56 +92,60 @@ export async function analyzeAzureResources(config, format) {
68
92
  network: new NetworkManagementClient(credential, sid),
69
93
  webSite: new WebSiteManagementClient(credential, sid),
70
94
  };
71
- const resourceClient = new armResources.ResourceManagementClient(credential, sid);
72
- const inFlight = new Set();
73
- // Use the async iterator to avoid loading all resources into memory at once.
74
- for await (const resource of resourceClient.resources.list()) {
75
- if (!matchesTags(resource, config.filterTags)) {
76
- continue;
77
- }
78
- // Backpressure: wait for a slot before enqueuing the next task so that
79
- // the inFlight Set stays bounded by maxInFlight instead of growing to the
80
- // total resource count in the subscription.
81
- while (inFlight.size >= maxInFlight) {
82
- await Promise.race(inFlight).catch(() => undefined);
83
- }
84
- const task = limit(async () => {
85
- const { costRisk, reason, suspectedUnused } = await analyzeResource(resource, analyzers, clients, runCache, config.preferredLocation, config.timespanDays, thresholds, config.verbose || false);
86
- if (suspectedUnused) {
87
- allReports.push({
88
- analysis: {
89
- costRisk,
90
- reason: reason || "No specific findings.",
91
- suspectedUnused,
92
- },
93
- resource,
94
- });
95
- }
95
+ if (customEnabled) {
96
+ await runPerResourceAnalysis({
97
+ analyzers,
98
+ clients,
99
+ config,
100
+ credential,
101
+ limit,
102
+ logger,
103
+ maxInFlight,
104
+ reports: allReports,
105
+ reportsById,
106
+ runCache,
107
+ sid,
108
+ taggedResourceIds,
109
+ thresholds,
96
110
  });
97
- inFlight.add(task);
98
- // Suppress the unhandled-rejection that would occur between task creation
99
- // and the Promise.allSettled drain below. The .catch() handler is a no-op
100
- // because the actual error is still visible to allSettled (which logs it)
101
- // via the original `task` reference kept in inFlight.
102
- void task.catch(() => undefined).finally(() => inFlight.delete(task));
103
111
  }
104
- // Drain remaining tasks; surface any unexpected errors so they don't
105
- // disappear silently and produce an incomplete report without a signal.
106
- const results = await Promise.allSettled(inFlight);
107
- for (const result of results) {
108
- if (result.status === "rejected") {
109
- logger.error(`Resource analysis failed: ${String(result.reason)}`);
110
- }
112
+ if (!customEnabled && advisorEnabled && hasTagFilter(config.filterTags)) {
113
+ await collectTaggedResourceIds({
114
+ config,
115
+ credential,
116
+ sid,
117
+ taggedResourceIds,
118
+ });
119
+ }
120
+ if (advisorEnabled && subscriptionAnalyzers.length > 0) {
121
+ await runSubscriptionAnalyzers({
122
+ analyzers: subscriptionAnalyzers,
123
+ credential,
124
+ logger,
125
+ reports: allReports,
126
+ reportsById,
127
+ sid,
128
+ tagFilterActive: hasTagFilter(config.filterTags),
129
+ taggedResourceIds,
130
+ verbose: config.verbose ?? false,
131
+ });
111
132
  }
112
133
  }
113
- // Sort to make the output more readable
134
+ // Sort to make the output more readable:
135
+ // - Subscription-scoped findings (Reserved Instances, savings plans, ...)
136
+ // sink to the bottom: they aggregate many recommendations into a single
137
+ // fat row and are easier to consume after the per-resource rows.
138
+ // - Within each group, sort by cost risk then by resource name.
114
139
  allReports.sort((a, b) => {
140
+ const aSub = isSubscriptionScopedReport(a);
141
+ const bSub = isSubscriptionScopedReport(b);
142
+ if (aSub !== bSub)
143
+ return aSub ? 1 : -1;
115
144
  if (a.analysis.costRisk === b.analysis.costRisk)
116
145
  return (a.resource.name ?? "").localeCompare(b.resource.name ?? "");
117
- const order = { high: 0, low: 2, medium: 1 };
118
- return order[a.analysis.costRisk] - order[b.analysis.costRisk];
146
+ return RISK_ORDER[a.analysis.costRisk] - RISK_ORDER[b.analysis.costRisk];
119
147
  });
120
- await generateReport(allReports, format);
148
+ return allReports;
121
149
  }
122
150
  /**
123
151
  * Analyzes a single Azure resource by dispatching it to every registered
@@ -173,4 +201,223 @@ export async function analyzeResource(resource, analyzers, clients, metricsCache
173
201
  }
174
202
  return { ...result, reason: result.reason.trim() };
175
203
  }
204
+ export function shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, tagFilterActive) {
205
+ if (!tagFilterActive) {
206
+ return true;
207
+ }
208
+ if (finding.source !== "advisor") {
209
+ return true;
210
+ }
211
+ if (!isResourceScopedFinding(finding.resourceId)) {
212
+ // Subscription-level findings are intentionally always global.
213
+ return true;
214
+ }
215
+ return taggedResourceIds.has(normalizeResourceId(finding.resourceId));
216
+ }
217
+ /**
218
+ * Derives a legacy `AnalysisResult` summary from a `Finding`, so the
219
+ * existing report formats keep working untouched on Advisor-only
220
+ * resources.
221
+ */
222
+ function analysisFromFinding(finding) {
223
+ const trimmed = finding.reason.trim();
224
+ const reason = trimmed.endsWith(".") ? trimmed : `${trimmed}.`;
225
+ return {
226
+ costRisk: finding.severity,
227
+ reason,
228
+ suspectedUnused: true,
229
+ };
230
+ }
231
+ /**
232
+ * Builds a minimal `GenericResource` from a resource ID. Used when a
233
+ * subscription-level analyzer surfaces a resource the per-resource pass
234
+ * did not see — we have neither tags nor location, but `name` and `type`
235
+ * can be parsed deterministically from the resource ID structure.
236
+ *
237
+ * Handles three shapes:
238
+ * - Fully qualified: /subscriptions/{sub}/resourceGroups/{rg}/providers/{provider}/{type}/{name}
239
+ * - Resource-group-scoped: /subscriptions/{sub}/resourceGroups/{rg}
240
+ * - Subscription-scoped: /subscriptions/{sub}
241
+ */
242
+ function buildResourceStub(resourceId) {
243
+ const parts = resourceId.split("/").filter((s) => s.length > 0);
244
+ const providersIdx = parts.indexOf("providers");
245
+ if (providersIdx >= 0 && parts.length > providersIdx + 2) {
246
+ // Fully qualified resource ID.
247
+ const provider = parts[providersIdx + 1];
248
+ const tail = parts.slice(providersIdx + 2); // [type, name, subtype, subname, ...]
249
+ const typeSegments = [provider];
250
+ for (let i = 0; i < tail.length; i += 2) {
251
+ typeSegments.push(tail[i]);
252
+ }
253
+ return {
254
+ id: resourceId,
255
+ name: tail[tail.length - 1],
256
+ type: typeSegments.join("/"),
257
+ };
258
+ }
259
+ const rgIdx = parts.indexOf("resourceGroups");
260
+ if (rgIdx >= 0 && parts.length > rgIdx + 1) {
261
+ // Resource-group-scoped ID.
262
+ return {
263
+ id: resourceId,
264
+ name: parts[rgIdx + 1],
265
+ type: "Microsoft.Resources/resourceGroups",
266
+ };
267
+ }
268
+ const subIdx = parts.indexOf("subscriptions");
269
+ if (subIdx >= 0 && parts.length > subIdx + 1) {
270
+ // Subscription-scoped ID (e.g. Reserved Instance recommendations).
271
+ return {
272
+ id: resourceId,
273
+ name: parts[subIdx + 1],
274
+ type: "Microsoft.Subscription",
275
+ };
276
+ }
277
+ // Fallback for completely unknown shapes.
278
+ return { id: resourceId, name: parts[parts.length - 1], type: undefined };
279
+ }
280
+ async function collectTaggedResourceIds(args) {
281
+ const { config, credential, sid, taggedResourceIds } = args;
282
+ const resourceClient = new armResources.ResourceManagementClient(credential, sid);
283
+ for await (const resource of resourceClient.resources.list()) {
284
+ if (!matchesTags(resource, config.filterTags)) {
285
+ continue;
286
+ }
287
+ const resourceId = normalizeResourceId(resource.id);
288
+ if (resourceId) {
289
+ taggedResourceIds.add(resourceId);
290
+ }
291
+ }
292
+ }
293
+ function hasTagFilter(filterTags) {
294
+ return Boolean(filterTags && filterTags.size > 0);
295
+ }
296
+ function isResourceScopedFinding(resourceId) {
297
+ return /\/providers\//i.test(resourceId);
298
+ }
299
+ function isSubscriptionScopedReport(r) {
300
+ return r.resource.type === "Microsoft.Subscription";
301
+ }
302
+ /**
303
+ * Inserts a `Finding` into the right report entry, creating a stub
304
+ * resource entry on the fly when the finding refers to a resource that
305
+ * the per-resource pass did not analyze.
306
+ */
307
+ function mergeFinding(finding, reports, reportsById) {
308
+ const idKey = finding.resourceId.toLowerCase();
309
+ const existing = reportsById.get(idKey);
310
+ if (existing) {
311
+ existing.findings = [...(existing.findings ?? []), finding];
312
+ const added = analysisFromFinding(finding);
313
+ // Use max costRisk (not last-wins) and join reasons with a space so we
314
+ // don't produce "Sentence one.Sentence two." when the existing reason is
315
+ // already trimmed (i.e. has no trailing separator space).
316
+ existing.analysis = {
317
+ costRisk: RISK_ORDER[existing.analysis.costRisk] <= RISK_ORDER[added.costRisk]
318
+ ? existing.analysis.costRisk
319
+ : added.costRisk,
320
+ reason: existing.analysis.reason && added.reason
321
+ ? `${existing.analysis.reason.trimEnd()} ${added.reason.trimStart()}`
322
+ : existing.analysis.reason || added.reason,
323
+ suspectedUnused: existing.analysis.suspectedUnused || added.suspectedUnused,
324
+ };
325
+ return;
326
+ }
327
+ const stub = buildResourceStub(finding.resourceId);
328
+ const report = {
329
+ analysis: analysisFromFinding(finding),
330
+ findings: [finding],
331
+ resource: stub,
332
+ };
333
+ reports.push(report);
334
+ reportsById.set(idKey, report);
335
+ }
336
+ function normalizeResourceId(resourceId) {
337
+ return (resourceId ?? "").trim().toLowerCase();
338
+ }
339
+ /**
340
+ * Runs the per-resource analyzer plugins against every resource in the
341
+ * given subscription. Extracted from `analyzeAzureResources` to keep that
342
+ * function readable now that subscription-level analyzers were added.
343
+ */
344
+ async function runPerResourceAnalysis(args) {
345
+ const { analyzers, clients, config, credential, limit, logger, maxInFlight, reports, reportsById, runCache, sid, taggedResourceIds, thresholds, } = args;
346
+ const resourceClient = new armResources.ResourceManagementClient(credential, sid);
347
+ const inFlight = new Set();
348
+ // Use the async iterator to avoid loading all resources into memory at once.
349
+ for await (const resource of resourceClient.resources.list()) {
350
+ if (!matchesTags(resource, config.filterTags)) {
351
+ continue;
352
+ }
353
+ const taggedId = normalizeResourceId(resource.id);
354
+ if (taggedId) {
355
+ taggedResourceIds.add(taggedId);
356
+ }
357
+ // Backpressure: wait for a slot before enqueuing the next task so that
358
+ // the inFlight Set stays bounded by maxInFlight instead of growing to the
359
+ // total resource count in the subscription.
360
+ while (inFlight.size >= maxInFlight) {
361
+ await Promise.race(inFlight).catch(() => undefined);
362
+ }
363
+ const task = limit(async () => {
364
+ const analysis = await analyzeResource(resource, analyzers, clients, runCache, config.preferredLocation, config.timespanDays, thresholds, config.verbose || false);
365
+ if (analysis.suspectedUnused) {
366
+ const reason = analysis.reason || "No specific findings.";
367
+ const report = {
368
+ analysis: { ...analysis, reason },
369
+ findings: findingsFromAnalysisResult({
370
+ reason,
371
+ resourceId: resource.id ?? "",
372
+ severity: analysis.costRisk,
373
+ source: "custom",
374
+ }),
375
+ resource,
376
+ };
377
+ reports.push(report);
378
+ const idKey = (resource.id ?? "").toLowerCase();
379
+ if (idKey)
380
+ reportsById.set(idKey, report);
381
+ }
382
+ });
383
+ inFlight.add(task);
384
+ // Suppress the unhandled-rejection that would occur between task creation
385
+ // and the Promise.allSettled drain below. The .catch() handler is a no-op
386
+ // because the actual error is still visible to allSettled (which logs it)
387
+ // via the original `task` reference kept in inFlight.
388
+ void task.catch(() => undefined).finally(() => inFlight.delete(task));
389
+ }
390
+ // Drain remaining tasks; surface any unexpected errors so they don't
391
+ // disappear silently and produce an incomplete report without a signal.
392
+ const results = await Promise.allSettled(inFlight);
393
+ for (const result of results) {
394
+ if (result.status === "rejected") {
395
+ logger.error(`Resource analysis failed: ${String(result.reason)}`);
396
+ }
397
+ }
398
+ }
399
+ /**
400
+ * Runs every subscription-level analyzer in parallel and merges their
401
+ * findings into the per-resource reports. Findings about resources that
402
+ * the per-resource pass did not surface (typical for Advisor, which
403
+ * reaches SQL DBs, Front Doors, etc.) produce new report entries with a
404
+ * minimal `GenericResource` stub derived from the resource ID.
405
+ */
406
+ async function runSubscriptionAnalyzers(args) {
407
+ const { analyzers, credential, logger, reports, reportsById, sid, tagFilterActive, taggedResourceIds, verbose, } = args;
408
+ const allFindings = await Promise.all(analyzers.map((a) => a
409
+ .analyze({ credential, subscriptionId: sid, verbose })
410
+ .catch((err) => {
411
+ logger.error(`Subscription analyzer ${a.id} failed: ${String(err)}`);
412
+ return [];
413
+ })));
414
+ for (const findings of allFindings) {
415
+ for (const finding of findings) {
416
+ if (!shouldIncludeAdvisorFindingForTags(finding, taggedResourceIds, tagFilterActive)) {
417
+ continue;
418
+ }
419
+ mergeFinding(finding, reports, reportsById);
420
+ }
421
+ }
422
+ }
176
423
  //# sourceMappingURL=analyzer.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"analyzer.js","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"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"}