@pagopa/dx-savemoney 0.2.5 → 0.2.6

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 (80) hide show
  1. package/dist/azure/analyzer.d.ts +31 -16
  2. package/dist/azure/analyzer.d.ts.map +1 -1
  3. package/dist/azure/analyzer.js +110 -81
  4. package/dist/azure/analyzer.js.map +1 -1
  5. package/dist/azure/analyzers/index.d.ts +6 -0
  6. package/dist/azure/analyzers/index.d.ts.map +1 -0
  7. package/dist/azure/analyzers/index.js +5 -0
  8. package/dist/azure/analyzers/index.js.map +1 -0
  9. package/dist/azure/analyzers/registry.d.ts +21 -0
  10. package/dist/azure/analyzers/registry.d.ts.map +1 -0
  11. package/dist/azure/analyzers/registry.js +69 -0
  12. package/dist/azure/analyzers/registry.js.map +1 -0
  13. package/dist/azure/analyzers/types.d.ts +62 -0
  14. package/dist/azure/analyzers/types.d.ts.map +1 -0
  15. package/dist/azure/analyzers/types.js +15 -0
  16. package/dist/azure/analyzers/types.js.map +1 -0
  17. package/dist/azure/config.d.ts.map +1 -1
  18. package/dist/azure/config.js +1 -0
  19. package/dist/azure/config.js.map +1 -1
  20. package/dist/azure/resources/app-service.d.ts +2 -1
  21. package/dist/azure/resources/app-service.d.ts.map +1 -1
  22. package/dist/azure/resources/app-service.js +3 -3
  23. package/dist/azure/resources/app-service.js.map +1 -1
  24. package/dist/azure/resources/container-app.d.ts +2 -1
  25. package/dist/azure/resources/container-app.d.ts.map +1 -1
  26. package/dist/azure/resources/container-app.js +9 -9
  27. package/dist/azure/resources/container-app.js.map +1 -1
  28. package/dist/azure/resources/public-ip.d.ts +2 -1
  29. package/dist/azure/resources/public-ip.d.ts.map +1 -1
  30. package/dist/azure/resources/public-ip.js +2 -2
  31. package/dist/azure/resources/public-ip.js.map +1 -1
  32. package/dist/azure/resources/static-web-app.d.ts +2 -1
  33. package/dist/azure/resources/static-web-app.d.ts.map +1 -1
  34. package/dist/azure/resources/static-web-app.js +3 -3
  35. package/dist/azure/resources/static-web-app.js.map +1 -1
  36. package/dist/azure/resources/storage.d.ts +2 -1
  37. package/dist/azure/resources/storage.d.ts.map +1 -1
  38. package/dist/azure/resources/storage.js +2 -2
  39. package/dist/azure/resources/storage.js.map +1 -1
  40. package/dist/azure/resources/vm.d.ts +2 -1
  41. package/dist/azure/resources/vm.d.ts.map +1 -1
  42. package/dist/azure/resources/vm.js +3 -3
  43. package/dist/azure/resources/vm.js.map +1 -1
  44. package/dist/azure/types.d.ts +6 -0
  45. package/dist/azure/types.d.ts.map +1 -1
  46. package/dist/azure/utils.d.ts +35 -3
  47. package/dist/azure/utils.d.ts.map +1 -1
  48. package/dist/azure/utils.js +70 -29
  49. package/dist/azure/utils.js.map +1 -1
  50. package/dist/finding.d.ts +114 -0
  51. package/dist/finding.d.ts.map +1 -0
  52. package/dist/finding.js +51 -0
  53. package/dist/finding.js.map +1 -0
  54. package/dist/index.d.ts +3 -0
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +3 -0
  57. package/dist/index.js.map +1 -1
  58. package/dist/schema.d.ts +1 -0
  59. package/dist/schema.d.ts.map +1 -1
  60. package/dist/schema.js +5 -0
  61. package/dist/schema.js.map +1 -1
  62. package/package.json +2 -1
  63. package/src/__tests__/finding.test.ts +149 -0
  64. package/src/azure/__tests__/utils.test.ts +164 -2
  65. package/src/azure/analyzer.ts +140 -165
  66. package/src/azure/analyzers/index.ts +6 -0
  67. package/src/azure/analyzers/registry.ts +184 -0
  68. package/src/azure/analyzers/types.ts +66 -0
  69. package/src/azure/config.ts +1 -0
  70. package/src/azure/resources/app-service.ts +4 -0
  71. package/src/azure/resources/container-app.ts +10 -0
  72. package/src/azure/resources/public-ip.ts +3 -0
  73. package/src/azure/resources/static-web-app.ts +4 -0
  74. package/src/azure/resources/storage.ts +3 -0
  75. package/src/azure/resources/vm.ts +4 -0
  76. package/src/azure/types.ts +6 -0
  77. package/src/azure/utils.ts +110 -39
  78. package/src/finding.ts +152 -0
  79. package/src/index.ts +18 -0
  80. package/src/schema.ts +5 -0
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../src/azure/utils.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Unified Finding model — the single representation of a cost-related
3
+ * observation, regardless of its source (custom analyzers, Azure Advisor,
4
+ * future AWS Trusted Advisor, …).
5
+ *
6
+ * Introduced in Phase 0 of the savemoney evolution roadmap. Existing
7
+ * `AnalysisResult`-based analyzers keep working untouched: an adapter
8
+ * (`findingsFromAnalysisResult`) splits the concatenated `reason` string
9
+ * into one `Finding` per sentence, so downstream consumers can already
10
+ * reason in terms of `Finding[]`.
11
+ */
12
+ import type { CostRisk } from "./types.js";
13
+ /**
14
+ * A single, atomic observation about a resource.
15
+ *
16
+ * One resource can produce multiple findings (e.g. "no tags" + "low CPU").
17
+ * Findings are designed to be deduplicated by `(resourceId, source, code)`.
18
+ */
19
+ export type Finding = {
20
+ /**
21
+ * Cloud category. Today every custom finding is "cost"; Advisor may
22
+ * surface other categories that we currently ignore.
23
+ */
24
+ category: FindingCategory;
25
+ /**
26
+ * Stable machine-readable identifier for the kind of finding, e.g.
27
+ * `vm.deallocated`, `disk.unattached`, `advisor.right-size-vm`.
28
+ * Used for deduplication and grouping. Free-form for now to keep the
29
+ * adapter from existing analyzers cheap; can be tightened later.
30
+ */
31
+ code: string;
32
+ /**
33
+ * Estimated monthly cost that could be recovered by acting on this
34
+ * finding. Populated by Advisor and (in later phases) by the Retail
35
+ * Prices integration. Absent when the analyzer cannot estimate it.
36
+ */
37
+ estimatedMonthlySavings?: Money;
38
+ /**
39
+ * Free-text, human-readable description. Backward compatible with the
40
+ * legacy `AnalysisResult.reason` field.
41
+ */
42
+ reason: string;
43
+ /**
44
+ * Optional, machine-friendly hint about how to remediate. For Advisor
45
+ * this typically maps to `shortDescription.solution`.
46
+ */
47
+ recommendedAction?: string;
48
+ /**
49
+ * Fully qualified Azure / cloud resource ID this finding refers to.
50
+ */
51
+ resourceId: string;
52
+ /**
53
+ * Cost risk classification. Maps Advisor's `impact` (High/Medium/Low)
54
+ * 1:1 onto the savemoney scale.
55
+ */
56
+ severity: CostRisk;
57
+ /**
58
+ * Provenance of the finding.
59
+ */
60
+ source: FindingSource;
61
+ };
62
+ /**
63
+ * Cloud category a finding belongs to. For now we focus on cost, but the
64
+ * model is open to future Advisor categories.
65
+ */
66
+ export type FindingCategory = "cost" | "operationalExcellence" | "performance" | "reliability" | "security";
67
+ /**
68
+ * Where the finding originated from.
69
+ *
70
+ * - `custom` → emitted by a savemoney analyzer plugin
71
+ * - `advisor` → fetched from Azure Advisor recommendations
72
+ * - `aws` → reserved for future AWS Trusted Advisor / Compute Optimizer
73
+ */
74
+ export type FindingSource = "advisor" | "aws" | "custom";
75
+ /**
76
+ * Monetary value associated with a finding, when known.
77
+ * Amounts use ISO 4217 currency codes (e.g. "EUR", "USD").
78
+ */
79
+ export type Money = {
80
+ amount: number;
81
+ currency: string;
82
+ };
83
+ /**
84
+ * Aggregate view: one resource with the list of findings emitted for it.
85
+ *
86
+ * This is the type future report generators should consume. The current
87
+ * report layer still works on `AzureDetailedResourceReport`; a helper
88
+ * (`legacyReportFromResourceReport`) bridges the two until the report
89
+ * layer is migrated.
90
+ */
91
+ export type ResourceReport<TResource = unknown> = {
92
+ findings: Finding[];
93
+ resource: TResource;
94
+ };
95
+ /**
96
+ * Adapter: converts a legacy `AnalysisResult` (single concatenated
97
+ * reason) into a list of `Finding`s, one per sentence.
98
+ *
99
+ * @param resourceId Fully qualified resource ID
100
+ * @param severity Cost risk classification produced by the analyzer
101
+ * @param reason Concatenated reason string (sentences joined by ". ")
102
+ * @param source Provenance (default: "custom")
103
+ * @param code Optional stable identifier for the finding kind
104
+ * (e.g. `"vm.deallocated"`). Defaults to
105
+ * `"custom.unknown"` when omitted.
106
+ */
107
+ export declare function findingsFromAnalysisResult(args: {
108
+ code?: string;
109
+ reason: string;
110
+ resourceId: string;
111
+ severity: CostRisk;
112
+ source?: FindingSource;
113
+ }): Finding[];
114
+ //# sourceMappingURL=finding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.d.ts","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG;IACpB;;;OAGG;IACH,QAAQ,EAAE,eAAe,CAAC;IAC1B;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,KAAK,CAAC;IAChC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,QAAQ,EAAE,QAAQ,CAAC;IACnB;;OAEG;IACH,MAAM,EAAE,aAAa,CAAC;CACvB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,uBAAuB,GACvB,aAAa,GACb,aAAa,GACb,UAAU,CAAC;AAEf;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,SAAS,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEzD;;;GAGG;AACH,MAAM,MAAM,KAAK,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,CAAC,SAAS,GAAG,OAAO,IAAI;IAChD,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB,GAAG,OAAO,EAAE,CAcZ"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Unified Finding model — the single representation of a cost-related
3
+ * observation, regardless of its source (custom analyzers, Azure Advisor,
4
+ * future AWS Trusted Advisor, …).
5
+ *
6
+ * Introduced in Phase 0 of the savemoney evolution roadmap. Existing
7
+ * `AnalysisResult`-based analyzers keep working untouched: an adapter
8
+ * (`findingsFromAnalysisResult`) splits the concatenated `reason` string
9
+ * into one `Finding` per sentence, so downstream consumers can already
10
+ * reason in terms of `Finding[]`.
11
+ */
12
+ /**
13
+ * Adapter: converts a legacy `AnalysisResult` (single concatenated
14
+ * reason) into a list of `Finding`s, one per sentence.
15
+ *
16
+ * @param resourceId Fully qualified resource ID
17
+ * @param severity Cost risk classification produced by the analyzer
18
+ * @param reason Concatenated reason string (sentences joined by ". ")
19
+ * @param source Provenance (default: "custom")
20
+ * @param code Optional stable identifier for the finding kind
21
+ * (e.g. `"vm.deallocated"`). Defaults to
22
+ * `"custom.unknown"` when omitted.
23
+ */
24
+ export function findingsFromAnalysisResult(args) {
25
+ const { code, reason, resourceId, severity, source = "custom" } = args;
26
+ const sentences = splitReasonIntoSentences(reason);
27
+ if (sentences.length === 0) {
28
+ return [];
29
+ }
30
+ return sentences.map((sentence) => ({
31
+ category: "cost",
32
+ code: code ?? "custom.unknown",
33
+ reason: sentence,
34
+ resourceId,
35
+ severity,
36
+ source,
37
+ }));
38
+ }
39
+ /**
40
+ * Splits a concatenated reason string (sentences joined by ". ") into
41
+ * individual non-empty sentences. Mirrors the logic already used by the
42
+ * lint reporter so behaviour stays consistent.
43
+ */
44
+ function splitReasonIntoSentences(reason) {
45
+ return reason
46
+ .split(/\.\s+|\.$/)
47
+ .map((s) => s.trim())
48
+ .filter((s) => s.length > 0)
49
+ .map((s) => (s.endsWith(".") ? s : `${s}.`));
50
+ }
51
+ //# sourceMappingURL=finding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finding.js","sourceRoot":"","sources":["../src/finding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAgGH;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAM1C;IACC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,EAAE,GAAG,IAAI,CAAC;IACvE,MAAM,SAAS,GAAG,wBAAwB,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClC,QAAQ,EAAE,MAAe;QACzB,IAAI,EAAE,IAAI,IAAI,gBAAgB;QAC9B,MAAM,EAAE,QAAQ;QAChB,UAAU;QACV,QAAQ;QACR,MAAM;KACP,CAAC,CAAC,CAAC;AACN,CAAC;AAED;;;;GAIG;AACH,SAAS,wBAAwB,CAAC,MAAc;IAC9C,OAAO,MAAM;SACV,KAAK,CAAC,WAAW,CAAC;SAClB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;AACjD,CAAC"}
package/dist/index.d.ts CHANGED
@@ -12,9 +12,12 @@
12
12
  *
13
13
  * This tool does NOT modify, tag, or delete any resources.
14
14
  */
15
+ export { type Analyzer, type AnalyzerContext, type AzureClients, createDefaultAnalyzers, } from "./azure/analyzers/index.js";
15
16
  export type { AzureConfig } from "./azure/types.js";
16
17
  import * as azureModule from "./azure/index.js";
17
18
  export declare const azure: typeof azureModule;
19
+ export { type MetricsCache, type MonitorClientLike } from "./azure/utils.js";
20
+ export { type Finding, type FindingCategory, findingsFromAnalysisResult, type FindingSource, type Money, type ResourceReport, } from "./finding.js";
18
21
  export * from "./types.js";
19
22
  import type { AzureConfig } from "./azure/types.js";
20
23
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,YAAY,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,eAAO,MAAM,KAAK,oBAAc,CAAC;AAEjC,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"}
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;AAGpD,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/index.js CHANGED
@@ -12,9 +12,12 @@
12
12
  *
13
13
  * This tool does NOT modify, tag, or delete any resources.
14
14
  */
15
+ export { createDefaultAnalyzers, } from "./azure/analyzers/index.js";
15
16
  // Export Azure module
16
17
  import * as azureModule from "./azure/index.js";
17
18
  export const azure = azureModule;
19
+ // Phase 0: unified Finding model and analyzer plugin layer
20
+ export { findingsFromAnalysisResult, } from "./finding.js";
18
21
  export * from "./types.js";
19
22
  import { loadAzureConfig } from "./azure/config.js";
20
23
  /**
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAEjC,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,sBAAsB,GACvB,MAAM,4BAA4B,CAAC;AAKpC,sBAAsB;AACtB,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,MAAM,CAAC,MAAM,KAAK,GAAG,WAAW,CAAC;AAIjC,2DAA2D;AAC3D,OAAO,EAGL,0BAA0B,GAI3B,MAAM,cAAc,CAAC;AACtB,cAAc,YAAY,CAAC;AAI3B,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,UAAmB;IAClD,OAAO,eAAe,CAAC,UAAU,CAAC,CAAC;AACrC,CAAC"}
package/dist/schema.d.ts CHANGED
@@ -86,6 +86,7 @@ export declare const ThresholdsSchema: z.ZodObject<{
86
86
  */
87
87
  export declare const ConfigSchema: z.ZodObject<{
88
88
  azure: z.ZodObject<{
89
+ concurrency: z.ZodOptional<z.ZodNumber>;
89
90
  preferredLocation: z.ZodDefault<z.ZodString>;
90
91
  subscriptionIds: z.ZodArray<z.ZodString>;
91
92
  thresholds: z.ZodPipe<z.ZodOptional<z.ZodObject<{
@@ -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;AAoBZ;;;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"}
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;AAyBZ;;;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
@@ -81,6 +81,11 @@ export const ThresholdsSchema = z
81
81
  // ── top-level config schema ──────────────────────────────────────────────────
82
82
  const AzureSectionSchema = z
83
83
  .object({
84
+ /**
85
+ * Maximum number of resources analyzed in parallel within a single
86
+ * subscription. Defaults to 8 when not provided.
87
+ */
88
+ concurrency: z.number().int().positive().optional(),
84
89
  preferredLocation: z.string().default("italynorth"),
85
90
  subscriptionIds: z
86
91
  .array(z.string())
@@ -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.2.5",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "description": "Azure resource analyzer for finding unused or cost-inefficient resources.",
6
6
  "repository": {
@@ -36,6 +36,7 @@
36
36
  "@azure/identity": "^4.13.1",
37
37
  "@logtape/logtape": "^1.3.8",
38
38
  "js-yaml": "^4.1.1",
39
+ "p-limit": "^7.3.0",
39
40
  "zod": "^4.4.2"
40
41
  },
41
42
  "devDependencies": {
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Tests for findingsFromAnalysisResult — verifies the adapter that converts
3
+ * a legacy `AnalysisResult.reason` string into a structured `Finding[]`.
4
+ *
5
+ * Behaviours covered:
6
+ * 1. Empty / whitespace-only reason → empty array.
7
+ * 2. Single sentence (with or without trailing period) → one finding.
8
+ * 3. Multi-sentence reason (". " separator) → one finding per sentence.
9
+ * 4. Trailing period on the whole string → not duplicated.
10
+ * 5. Custom `code` → propagated to every finding.
11
+ * 6. Omitted `code` → defaults to "custom.unknown".
12
+ * 7. Custom `source` → propagated.
13
+ * 8. Omitted `source` → defaults to "custom".
14
+ * 9. Every finding carries the correct `resourceId`, `severity`, `category`.
15
+ */
16
+
17
+ import { describe, expect, it } from "vitest";
18
+
19
+ import { findingsFromAnalysisResult } from "../finding.js";
20
+
21
+ const BASE_ARGS = {
22
+ reason: "Low CPU usage.",
23
+ resourceId:
24
+ "/subscriptions/sub1/resourceGroups/rg/providers/Microsoft.Compute/virtualMachines/vm",
25
+ severity: "high" as const,
26
+ };
27
+
28
+ describe("findingsFromAnalysisResult", () => {
29
+ describe("empty / whitespace input", () => {
30
+ it("returns [] for an empty string", () => {
31
+ expect(findingsFromAnalysisResult({ ...BASE_ARGS, reason: "" })).toEqual(
32
+ [],
33
+ );
34
+ });
35
+
36
+ it("returns [] for a whitespace-only string", () => {
37
+ expect(
38
+ findingsFromAnalysisResult({ ...BASE_ARGS, reason: " " }),
39
+ ).toEqual([]);
40
+ });
41
+
42
+ it("returns [] for a bare period", () => {
43
+ expect(findingsFromAnalysisResult({ ...BASE_ARGS, reason: "." })).toEqual(
44
+ [],
45
+ );
46
+ });
47
+ });
48
+
49
+ describe("single sentence", () => {
50
+ it("returns one finding with a trailing period", () => {
51
+ const result = findingsFromAnalysisResult({
52
+ ...BASE_ARGS,
53
+ reason: "VM is deallocated.",
54
+ });
55
+ expect(result).toHaveLength(1);
56
+ expect(result[0].reason).toBe("VM is deallocated.");
57
+ });
58
+
59
+ it("appends a trailing period when missing", () => {
60
+ const result = findingsFromAnalysisResult({
61
+ ...BASE_ARGS,
62
+ reason: "VM is deallocated",
63
+ });
64
+ expect(result).toHaveLength(1);
65
+ expect(result[0].reason).toBe("VM is deallocated.");
66
+ });
67
+ });
68
+
69
+ describe("multi-sentence reason", () => {
70
+ it("splits on '. ' into separate findings", () => {
71
+ const result = findingsFromAnalysisResult({
72
+ ...BASE_ARGS,
73
+ reason: "VM is deallocated. No tags found. Low CPU usage.",
74
+ });
75
+ expect(result).toHaveLength(3);
76
+ expect(result[0].reason).toBe("VM is deallocated.");
77
+ expect(result[1].reason).toBe("No tags found.");
78
+ expect(result[2].reason).toBe("Low CPU usage.");
79
+ });
80
+
81
+ it("does not create an extra empty finding for a trailing period", () => {
82
+ const result = findingsFromAnalysisResult({
83
+ ...BASE_ARGS,
84
+ reason: "Sentence one. Sentence two.",
85
+ });
86
+ expect(result).toHaveLength(2);
87
+ });
88
+ });
89
+
90
+ describe("code field", () => {
91
+ it("uses the provided code for every finding", () => {
92
+ const result = findingsFromAnalysisResult({
93
+ ...BASE_ARGS,
94
+ code: "vm.deallocated",
95
+ reason: "A. B.",
96
+ });
97
+ expect(result.every((f) => f.code === "vm.deallocated")).toBe(true);
98
+ });
99
+
100
+ it("defaults code to 'custom.unknown' when omitted", () => {
101
+ const result = findingsFromAnalysisResult(BASE_ARGS);
102
+ expect(result[0].code).toBe("custom.unknown");
103
+ });
104
+ });
105
+
106
+ describe("source field", () => {
107
+ it("uses the provided source for every finding", () => {
108
+ const result = findingsFromAnalysisResult({
109
+ ...BASE_ARGS,
110
+ source: "advisor",
111
+ });
112
+ expect(result.every((f) => f.source === "advisor")).toBe(true);
113
+ });
114
+
115
+ it("defaults source to 'custom' when omitted", () => {
116
+ const result = findingsFromAnalysisResult(BASE_ARGS);
117
+ expect(result[0].source).toBe("custom");
118
+ });
119
+ });
120
+
121
+ describe("static fields on every finding", () => {
122
+ it("propagates resourceId to every finding", () => {
123
+ const result = findingsFromAnalysisResult({
124
+ ...BASE_ARGS,
125
+ reason: "A. B.",
126
+ });
127
+ expect(result.every((f) => f.resourceId === BASE_ARGS.resourceId)).toBe(
128
+ true,
129
+ );
130
+ });
131
+
132
+ it("propagates severity to every finding", () => {
133
+ const result = findingsFromAnalysisResult({
134
+ ...BASE_ARGS,
135
+ reason: "A. B.",
136
+ severity: "medium",
137
+ });
138
+ expect(result.every((f) => f.severity === "medium")).toBe(true);
139
+ });
140
+
141
+ it("sets category to 'cost' on every finding", () => {
142
+ const result = findingsFromAnalysisResult({
143
+ ...BASE_ARGS,
144
+ reason: "A. B.",
145
+ });
146
+ expect(result.every((f) => f.category === "cost")).toBe(true);
147
+ });
148
+ });
149
+ });
@@ -5,13 +5,24 @@
5
5
  * 3. Key missing on resource → exclude.
6
6
  * 4. Key present but wrong value → exclude.
7
7
  * 5. Multiple tags: all match → include; any mismatch → exclude.
8
+ *
9
+ * Tests for getMetric() cache behaviour:
10
+ * 6. resetMetricsCache() clears state between runs.
11
+ * 7. Concurrent calls for the same key coalesce into one network call.
12
+ * 8. A failed call is cached as a fulfilled null result (not retried silently).
8
13
  */
9
14
 
10
15
  import type { GenericResource } from "@azure/arm-resources";
11
16
 
12
- import { describe, expect, it } from "vitest";
17
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
13
18
 
14
- import { matchesTags } from "../utils.js";
19
+ import {
20
+ _metricsCacheSize,
21
+ getMetric,
22
+ matchesTags,
23
+ type MonitorClientLike,
24
+ resetMetricsCache,
25
+ } from "../utils.js";
15
26
 
16
27
  function makeResource(tags?: Record<string, string>): GenericResource {
17
28
  return { id: "r1", name: "res", tags };
@@ -122,3 +133,154 @@ describe("matchesTags", () => {
122
133
  });
123
134
  });
124
135
  });
136
+
137
+ // ── metrics cache ──────────────────────────────────────────────────────────
138
+
139
+ function makeFailingMonitorClient(calls: number[] = []): MonitorClientLike {
140
+ return {
141
+ metrics: {
142
+ list: vi.fn().mockImplementation(async () => {
143
+ calls.push(1);
144
+ throw new Error("network error");
145
+ }),
146
+ },
147
+ };
148
+ }
149
+
150
+ function makeMonitorClient(
151
+ returnValue: null | number,
152
+ calls: number[] = [],
153
+ ): MonitorClientLike {
154
+ return {
155
+ metrics: {
156
+ list: vi.fn().mockImplementation(async () => {
157
+ calls.push(1);
158
+ if (returnValue === null) {
159
+ return { value: [] };
160
+ }
161
+ return {
162
+ value: [
163
+ {
164
+ timeseries: [{ data: [{ average: returnValue }] }],
165
+ },
166
+ ],
167
+ };
168
+ }),
169
+ },
170
+ };
171
+ }
172
+
173
+ describe("getMetric — in-memory cache", () => {
174
+ beforeEach(() => {
175
+ resetMetricsCache();
176
+ });
177
+
178
+ afterEach(() => {
179
+ vi.restoreAllMocks();
180
+ });
181
+
182
+ it("resetMetricsCache clears all cached entries", async () => {
183
+ const calls: number[] = [];
184
+ const client = makeMonitorClient(42, calls);
185
+
186
+ await getMetric(client, "/res/1", "Percentage CPU", "Average", 7);
187
+ expect(_metricsCacheSize()).toBe(1);
188
+
189
+ resetMetricsCache();
190
+ expect(_metricsCacheSize()).toBe(0);
191
+ });
192
+
193
+ it("does not call the API a second time for the same key", async () => {
194
+ const calls: number[] = [];
195
+ const client = makeMonitorClient(10, calls);
196
+
197
+ await getMetric(client, "/res/1", "Percentage CPU", "Average", 7);
198
+ await getMetric(client, "/res/1", "Percentage CPU", "Average", 7);
199
+
200
+ expect(calls.length).toBe(1);
201
+ });
202
+
203
+ it("concurrent calls for the same key coalesce into one network call", async () => {
204
+ const calls: number[] = [];
205
+ const client = makeMonitorClient(5, calls);
206
+
207
+ await Promise.all([
208
+ getMetric(client, "/res/2", "Network In Total", "Average", 30),
209
+ getMetric(client, "/res/2", "Network In Total", "Average", 30),
210
+ getMetric(client, "/res/2", "Network In Total", "Average", 30),
211
+ ]);
212
+
213
+ expect(calls.length).toBe(1);
214
+ });
215
+
216
+ it("different keys result in separate network calls", async () => {
217
+ const calls: number[] = [];
218
+ const client = makeMonitorClient(1, calls);
219
+
220
+ await getMetric(client, "/res/a", "Percentage CPU", "Average", 7);
221
+ await getMetric(client, "/res/b", "Percentage CPU", "Average", 7);
222
+
223
+ expect(calls.length).toBe(2);
224
+ });
225
+
226
+ it("returns null and caches the null result when no data points are available", async () => {
227
+ const calls: number[] = [];
228
+ const client = makeMonitorClient(null, calls);
229
+
230
+ const first = await getMetric(
231
+ client,
232
+ "/res/3",
233
+ "Percentage CPU",
234
+ "Average",
235
+ 7,
236
+ );
237
+ const second = await getMetric(
238
+ client,
239
+ "/res/3",
240
+ "Percentage CPU",
241
+ "Average",
242
+ 7,
243
+ );
244
+
245
+ expect(first).toBeNull();
246
+ expect(second).toBeNull();
247
+ expect(calls.length).toBe(1);
248
+ });
249
+
250
+ it("a failing call is cached and returns null on retry", async () => {
251
+ const calls: number[] = [];
252
+ const client = makeFailingMonitorClient(calls);
253
+
254
+ const first = await getMetric(
255
+ client,
256
+ "/res/4",
257
+ "Percentage CPU",
258
+ "Average",
259
+ 7,
260
+ );
261
+ const second = await getMetric(
262
+ client,
263
+ "/res/4",
264
+ "Percentage CPU",
265
+ "Average",
266
+ 7,
267
+ );
268
+
269
+ // Both calls return null (error is swallowed by getMetric)
270
+ expect(first).toBeNull();
271
+ expect(second).toBeNull();
272
+ // Only one actual network call despite two getMetric calls
273
+ expect(calls.length).toBe(1);
274
+ });
275
+
276
+ it("after resetMetricsCache the API is called again", async () => {
277
+ const calls: number[] = [];
278
+ const client = makeMonitorClient(7, calls);
279
+
280
+ await getMetric(client, "/res/5", "Percentage CPU", "Average", 7);
281
+ resetMetricsCache();
282
+ await getMetric(client, "/res/5", "Percentage CPU", "Average", 7);
283
+
284
+ expect(calls.length).toBe(2);
285
+ });
286
+ });