@pagopa/dx-savemoney 0.2.5 → 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 +49 -21
- package/dist/azure/analyzer.d.ts.map +1 -1
- package/dist/azure/analyzer.js +369 -93
- 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 +8 -0
- package/dist/azure/analyzers/index.d.ts.map +1 -0
- package/dist/azure/analyzers/index.js +6 -0
- package/dist/azure/analyzers/index.js.map +1 -0
- package/dist/azure/analyzers/registry.d.ts +29 -0
- package/dist/azure/analyzers/registry.d.ts.map +1 -0
- package/dist/azure/analyzers/registry.js +79 -0
- package/dist/azure/analyzers/registry.js.map +1 -0
- 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/analyzers/types.d.ts +62 -0
- package/dist/azure/analyzers/types.d.ts.map +1 -0
- package/dist/azure/analyzers/types.js +15 -0
- package/dist/azure/analyzers/types.js.map +1 -0
- package/dist/azure/config.d.ts.map +1 -1
- package/dist/azure/config.js +2 -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/resources/app-service.d.ts +2 -1
- package/dist/azure/resources/app-service.d.ts.map +1 -1
- package/dist/azure/resources/app-service.js +3 -3
- package/dist/azure/resources/app-service.js.map +1 -1
- package/dist/azure/resources/container-app.d.ts +2 -1
- package/dist/azure/resources/container-app.d.ts.map +1 -1
- package/dist/azure/resources/container-app.js +9 -9
- package/dist/azure/resources/container-app.js.map +1 -1
- package/dist/azure/resources/public-ip.d.ts +2 -1
- package/dist/azure/resources/public-ip.d.ts.map +1 -1
- package/dist/azure/resources/public-ip.js +2 -2
- package/dist/azure/resources/public-ip.js.map +1 -1
- package/dist/azure/resources/static-web-app.d.ts +2 -1
- package/dist/azure/resources/static-web-app.d.ts.map +1 -1
- package/dist/azure/resources/static-web-app.js +3 -3
- package/dist/azure/resources/static-web-app.js.map +1 -1
- package/dist/azure/resources/storage.d.ts +2 -1
- package/dist/azure/resources/storage.d.ts.map +1 -1
- package/dist/azure/resources/storage.js +2 -2
- package/dist/azure/resources/storage.js.map +1 -1
- package/dist/azure/resources/vm.d.ts +2 -1
- package/dist/azure/resources/vm.d.ts.map +1 -1
- package/dist/azure/resources/vm.js +3 -3
- package/dist/azure/resources/vm.js.map +1 -1
- package/dist/azure/types.d.ts +34 -1
- package/dist/azure/types.d.ts.map +1 -1
- package/dist/azure/utils.d.ts +35 -3
- package/dist/azure/utils.d.ts.map +1 -1
- package/dist/azure/utils.js +70 -29
- package/dist/azure/utils.js.map +1 -1
- package/dist/finding.d.ts +114 -0
- package/dist/finding.d.ts.map +1 -0
- package/dist/finding.js +51 -0
- package/dist/finding.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +5 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +14 -0
- package/dist/schema.js.map +1 -1
- package/package.json +4 -1
- package/src/__tests__/finding.test.ts +149 -0
- package/src/azure/__tests__/analyzer-tags.test.ts +74 -0
- package/src/azure/__tests__/report.test.ts +27 -0
- package/src/azure/__tests__/utils.test.ts +164 -2
- package/src/azure/analyzer.ts +513 -182
- package/src/azure/analyzers/__tests__/advisor.test.ts +367 -0
- package/src/azure/analyzers/advisor.ts +324 -0
- package/src/azure/analyzers/index.ts +14 -0
- package/src/azure/analyzers/registry.ts +196 -0
- package/src/azure/analyzers/subscription.ts +56 -0
- package/src/azure/analyzers/types.ts +66 -0
- package/src/azure/config.ts +2 -0
- package/src/azure/index.ts +1 -0
- package/src/azure/report.ts +206 -35
- package/src/azure/resources/app-service.ts +4 -0
- package/src/azure/resources/container-app.ts +10 -0
- package/src/azure/resources/public-ip.ts +3 -0
- package/src/azure/resources/static-web-app.ts +4 -0
- package/src/azure/resources/storage.ts +3 -0
- package/src/azure/resources/vm.ts +4 -0
- package/src/azure/types.ts +35 -1
- package/src/azure/utils.ts +110 -39
- package/src/finding.ts +152 -0
- package/src/index.ts +19 -1
- package/src/schema.ts +14 -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
|
|
132
|
-
await azure.analyzeAzureResources(config
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
315
|
+
const reports = await azure.analyzeAzureResources(config);
|
|
316
|
+
await azure.generateReport(reports, "json");
|
|
311
317
|
```
|
|
312
318
|
|
|
313
319
|
## AWS (Coming Soon)
|
package/dist/azure/analyzer.d.ts
CHANGED
|
@@ -1,34 +1,62 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Azure resource analyzer - Main orchestration logic
|
|
3
|
+
*
|
|
4
|
+
* Iterates every resource in every configured subscription, dispatches it
|
|
5
|
+
* to the registered analyzers (see `./analyzers/registry.ts`), and feeds
|
|
6
|
+
* the resulting findings into the report generator.
|
|
7
|
+
*
|
|
8
|
+
* Phase 0 refactor:
|
|
9
|
+
* - Resources are dispatched through a plugin-style `Analyzer` registry
|
|
10
|
+
* instead of a hard-coded `switch` statement.
|
|
11
|
+
* - Per-subscription resources are analyzed in parallel via a small
|
|
12
|
+
* in-process limiter (default concurrency 8, configurable via
|
|
13
|
+
* `AzureConfig.concurrency`).
|
|
14
|
+
* - Azure Monitor metric calls are memoized for the duration of a run via
|
|
15
|
+
* a per-run `MetricsCache` passed through `AnalyzerContext`, so concurrent
|
|
16
|
+
* calls to `analyzeAzureResources` stay fully isolated.
|
|
17
|
+
*
|
|
18
|
+
* The output schema (`AzureDetailedResourceReport`) is unchanged so the
|
|
19
|
+
* existing report formats keep working untouched.
|
|
3
20
|
*/
|
|
4
|
-
import { ContainerAppsAPIClient } from "@azure/arm-appcontainers";
|
|
5
|
-
import { WebSiteManagementClient } from "@azure/arm-appservice";
|
|
6
|
-
import { ComputeManagementClient } from "@azure/arm-compute";
|
|
7
|
-
import { MonitorClient } from "@azure/arm-monitor";
|
|
8
|
-
import { NetworkManagementClient } from "@azure/arm-network";
|
|
9
21
|
import * as armResources from "@azure/arm-resources";
|
|
10
|
-
import type {
|
|
22
|
+
import type { Finding } from "../finding.js";
|
|
23
|
+
import type { AzureConfig, AzureDetailedResourceReport } from "./types.js";
|
|
11
24
|
import { type AnalysisResult, type Thresholds } from "../types.js";
|
|
25
|
+
import { type Analyzer, type AzureClients } from "./analyzers/index.js";
|
|
26
|
+
import { type MetricsCache } from "./utils.js";
|
|
12
27
|
/**
|
|
13
|
-
* Analyzes resources in
|
|
28
|
+
* Analyzes resources in every configured Azure subscription and returns
|
|
29
|
+
* the structured report.
|
|
30
|
+
*
|
|
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.
|
|
14
40
|
*
|
|
15
|
-
* @param config - Azure configuration with subscription IDs and settings
|
|
16
|
-
*
|
|
41
|
+
* @param config - Azure configuration with subscription IDs and settings.
|
|
42
|
+
* `config.sources` controls which analyzers run.
|
|
17
43
|
*/
|
|
18
|
-
export declare function analyzeAzureResources(config: AzureConfig
|
|
44
|
+
export declare function analyzeAzureResources(config: AzureConfig): Promise<AzureDetailedResourceReport[]>;
|
|
19
45
|
/**
|
|
20
|
-
* Analyzes a single Azure resource
|
|
46
|
+
* Analyzes a single Azure resource by dispatching it to every registered
|
|
47
|
+
* analyzer that supports it. Generic checks (missing tags, location
|
|
48
|
+
* mismatch) are applied around the analyzer-specific logic.
|
|
21
49
|
*
|
|
22
|
-
* @param resource
|
|
23
|
-
* @param
|
|
24
|
-
* @param
|
|
25
|
-
* @param
|
|
26
|
-
* @param
|
|
27
|
-
* @param
|
|
28
|
-
* @param
|
|
29
|
-
* @param
|
|
30
|
-
* @param verbose - Whether verbose logging is enabled
|
|
50
|
+
* @param resource The Azure resource to analyze
|
|
51
|
+
* @param analyzers Registered analyzers (typically `createDefaultAnalyzers()`)
|
|
52
|
+
* @param clients Bundle of Azure SDK clients shared across analyzers
|
|
53
|
+
* @param metricsCache Run-scoped metrics cache to pass through to analyzers
|
|
54
|
+
* @param preferredLocation Preferred Azure region (resources elsewhere are flagged)
|
|
55
|
+
* @param timespanDays Look-back window for Azure Monitor metrics
|
|
56
|
+
* @param thresholds Numeric thresholds used during analysis
|
|
57
|
+
* @param verbose Whether verbose logging is enabled
|
|
31
58
|
* @returns Analysis result with cost risk and reason
|
|
32
59
|
*/
|
|
33
|
-
export declare function analyzeResource(resource: armResources.GenericResource,
|
|
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;
|
|
34
62
|
//# sourceMappingURL=analyzer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/azure/analyzer.ts"],"names":[],"mappings":"AAAA
|
|
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"}
|