@pagopa/dx-savemoney 0.1.1

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 (83) hide show
  1. package/README.md +233 -0
  2. package/dist/azure/analyzer.d.ts +32 -0
  3. package/dist/azure/analyzer.d.ts.map +1 -0
  4. package/dist/azure/analyzer.js +128 -0
  5. package/dist/azure/analyzer.js.map +1 -0
  6. package/dist/azure/config.d.ts +19 -0
  7. package/dist/azure/config.d.ts.map +1 -0
  8. package/dist/azure/config.js +62 -0
  9. package/dist/azure/config.js.map +1 -0
  10. package/dist/azure/index.d.ts +9 -0
  11. package/dist/azure/index.d.ts.map +1 -0
  12. package/dist/azure/index.js +9 -0
  13. package/dist/azure/index.js.map +1 -0
  14. package/dist/azure/report.d.ts +12 -0
  15. package/dist/azure/report.d.ts.map +1 -0
  16. package/dist/azure/report.js +40 -0
  17. package/dist/azure/report.js.map +1 -0
  18. package/dist/azure/resources/app-service.d.ts +18 -0
  19. package/dist/azure/resources/app-service.d.ts.map +1 -0
  20. package/dist/azure/resources/app-service.js +65 -0
  21. package/dist/azure/resources/app-service.js.map +1 -0
  22. package/dist/azure/resources/disk.d.ts +16 -0
  23. package/dist/azure/resources/disk.d.ts.map +1 -0
  24. package/dist/azure/resources/disk.js +56 -0
  25. package/dist/azure/resources/disk.js.map +1 -0
  26. package/dist/azure/resources/index.d.ts +11 -0
  27. package/dist/azure/resources/index.d.ts.map +1 -0
  28. package/dist/azure/resources/index.js +11 -0
  29. package/dist/azure/resources/index.js.map +1 -0
  30. package/dist/azure/resources/nic.d.ts +15 -0
  31. package/dist/azure/resources/nic.d.ts.map +1 -0
  32. package/dist/azure/resources/nic.js +56 -0
  33. package/dist/azure/resources/nic.js.map +1 -0
  34. package/dist/azure/resources/private-endpoint.d.ts +15 -0
  35. package/dist/azure/resources/private-endpoint.d.ts.map +1 -0
  36. package/dist/azure/resources/private-endpoint.js +66 -0
  37. package/dist/azure/resources/private-endpoint.js.map +1 -0
  38. package/dist/azure/resources/public-ip.d.ts +18 -0
  39. package/dist/azure/resources/public-ip.d.ts.map +1 -0
  40. package/dist/azure/resources/public-ip.js +61 -0
  41. package/dist/azure/resources/public-ip.js.map +1 -0
  42. package/dist/azure/resources/storage.d.ts +16 -0
  43. package/dist/azure/resources/storage.d.ts.map +1 -0
  44. package/dist/azure/resources/storage.js +39 -0
  45. package/dist/azure/resources/storage.js.map +1 -0
  46. package/dist/azure/resources/vm.d.ts +19 -0
  47. package/dist/azure/resources/vm.d.ts.map +1 -0
  48. package/dist/azure/resources/vm.js +77 -0
  49. package/dist/azure/resources/vm.js.map +1 -0
  50. package/dist/azure/types.d.ts +34 -0
  51. package/dist/azure/types.d.ts.map +1 -0
  52. package/dist/azure/types.js +5 -0
  53. package/dist/azure/types.js.map +1 -0
  54. package/dist/azure/utils.d.ts +40 -0
  55. package/dist/azure/utils.d.ts.map +1 -0
  56. package/dist/azure/utils.js +104 -0
  57. package/dist/azure/utils.js.map +1 -0
  58. package/dist/index.d.ts +34 -0
  59. package/dist/index.d.ts.map +1 -0
  60. package/dist/index.js +77 -0
  61. package/dist/index.js.map +1 -0
  62. package/dist/types.d.ts +21 -0
  63. package/dist/types.d.ts.map +1 -0
  64. package/dist/types.js +14 -0
  65. package/dist/types.js.map +1 -0
  66. package/package.json +60 -0
  67. package/src/azure/analyzer.ts +213 -0
  68. package/src/azure/config.ts +81 -0
  69. package/src/azure/index.ts +9 -0
  70. package/src/azure/report.ts +52 -0
  71. package/src/azure/resources/app-service.ts +118 -0
  72. package/src/azure/resources/disk.ts +88 -0
  73. package/src/azure/resources/index.ts +11 -0
  74. package/src/azure/resources/nic.ts +90 -0
  75. package/src/azure/resources/private-endpoint.ts +112 -0
  76. package/src/azure/resources/public-ip.ts +106 -0
  77. package/src/azure/resources/storage.ts +67 -0
  78. package/src/azure/resources/vm.ts +129 -0
  79. package/src/azure/types.ts +38 -0
  80. package/src/azure/utils.ts +141 -0
  81. package/src/index.test.ts +95 -0
  82. package/src/index.ts +99 -0
  83. package/src/types.ts +34 -0
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Azure App Service Plan analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { getMetric, verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure App Service Plan for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param webSiteClient - Azure Web Site client for App Service Plan details
11
+ * @param monitorClient - Azure Monitor client for metrics
12
+ * @param timespanDays - Number of days to analyze metrics
13
+ * @returns Analysis result with cost risk and reason
14
+ */
15
+ export async function analyzeAppServicePlan(resource, webSiteClient, monitorClient, timespanDays, verbose = false) {
16
+ verboseLogResourceStart(verbose, resource.name || "unknown", "App Service Plan (microsoft.web/serverfarms)");
17
+ verboseLog(verbose, "Resource details:", resource);
18
+ const costRisk = "high";
19
+ let reason = "";
20
+ if (!resource.id) {
21
+ return {
22
+ costRisk,
23
+ reason: "Resource ID is missing.",
24
+ suspectedUnused: false,
25
+ };
26
+ }
27
+ // Extract resource group and App Service Plan name from resource ID
28
+ const resourceParts = resource.id.split("/");
29
+ const resourceGroupName = resourceParts[4];
30
+ const planName = resourceParts[8];
31
+ try {
32
+ // Get detailed App Service Plan information
33
+ const planDetails = await webSiteClient.appServicePlans.get(resourceGroupName, planName);
34
+ verboseLog(verbose, "App Service Plan API details:", planDetails);
35
+ // Check if the plan has no apps
36
+ if (!planDetails.numberOfSites || planDetails.numberOfSites === 0) {
37
+ reason += "App Service Plan has no apps deployed. ";
38
+ }
39
+ // Check CPU and Memory metrics
40
+ const cpuPercentage = await getMetric(monitorClient, resource.id, "CpuPercentage", "Average", timespanDays);
41
+ const memoryPercentage = await getMetric(monitorClient, resource.id, "MemoryPercentage", "Average", timespanDays);
42
+ if (cpuPercentage !== null && cpuPercentage < 5) {
43
+ reason += `Very low CPU usage (${cpuPercentage.toFixed(2)}%). `;
44
+ }
45
+ if (memoryPercentage !== null && memoryPercentage < 10) {
46
+ reason += `Very low memory usage (${memoryPercentage.toFixed(2)}%). `;
47
+ }
48
+ // Check if it's an oversized plan (Premium tier with low usage)
49
+ if (planDetails.sku?.tier?.includes("Premium") &&
50
+ cpuPercentage &&
51
+ cpuPercentage < 10) {
52
+ reason += "Premium tier with low resource utilization. ";
53
+ }
54
+ }
55
+ catch (error) {
56
+ const logger = getLogger(["savemoney", "azure"]);
57
+ logger.warn(`Failed to get App Service Plan details for ${planName}: ${error instanceof Error ? error.message : error}`);
58
+ reason += "Could not retrieve detailed App Service Plan information. ";
59
+ }
60
+ const suspectedUnused = reason.length > 0;
61
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
62
+ verboseLogAnalysisResult(verbose, result);
63
+ return result;
64
+ }
65
+ //# sourceMappingURL=app-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-service.js","sourceRoot":"","sources":["../../../src/azure/resources/app-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Azure Managed Disk analysis
3
+ */
4
+ import type { ComputeManagementClient } from "@azure/arm-compute";
5
+ import * as armResources from "@azure/arm-resources";
6
+ import type { AnalysisResult } from "../../types.js";
7
+ /**
8
+ * Analyzes an Azure Managed Disk for potential cost optimization.
9
+ *
10
+ * @param resource - The Azure resource object
11
+ * @param computeClient - Azure Compute client for disk details
12
+ * @param verbose - Whether verbose logging is enabled
13
+ * @returns Analysis result with cost risk and reason
14
+ */
15
+ export declare function analyzeDisk(resource: armResources.GenericResource, computeClient: ComputeManagementClient, verbose?: boolean): Promise<AnalysisResult>;
16
+ //# sourceMappingURL=disk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/disk.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQrD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,uBAAuB,EACtC,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA0DzB"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Azure Managed Disk analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure Managed Disk for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param computeClient - Azure Compute client for disk details
11
+ * @param verbose - Whether verbose logging is enabled
12
+ * @returns Analysis result with cost risk and reason
13
+ */
14
+ export async function analyzeDisk(resource, computeClient, verbose = false) {
15
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Managed Disk (microsoft.compute/disks)");
16
+ verboseLog(verbose, "Resource details:", resource);
17
+ const costRisk = "medium";
18
+ if (!resource.id) {
19
+ return {
20
+ costRisk,
21
+ reason: "Resource ID is missing.",
22
+ suspectedUnused: false,
23
+ };
24
+ }
25
+ // Extract resource group and disk name from resource ID
26
+ const resourceParts = resource.id.split("/");
27
+ const resourceGroupName = resourceParts[4];
28
+ const diskName = resourceParts[8];
29
+ try {
30
+ // Get the actual disk details to check attachment state
31
+ const diskDetails = await computeClient.disks.get(resourceGroupName, diskName);
32
+ verboseLog(verbose, "Disk API details:", diskDetails);
33
+ // Check if disk is unattached
34
+ if (diskDetails.diskState?.toLowerCase() === "unattached" ||
35
+ !diskDetails.managedBy) {
36
+ const result = {
37
+ costRisk,
38
+ reason: "Disk is unattached. ",
39
+ suspectedUnused: true,
40
+ };
41
+ verboseLogAnalysisResult(verbose, result);
42
+ return result;
43
+ }
44
+ }
45
+ catch (error) {
46
+ const logger = getLogger(["savemoney", "azure"]);
47
+ logger.warn(`Failed to get disk details for ${diskName}: ${error instanceof Error ? error.message : error}`);
48
+ // Fallback to checking properties if API call fails
49
+ // Note: GenericResource doesn't have diskState property
50
+ // Without API access, we can't determine if disk is unattached
51
+ }
52
+ const result = { costRisk, reason: "", suspectedUnused: false };
53
+ verboseLogAnalysisResult(verbose, result);
54
+ return result;
55
+ }
56
+ //# sourceMappingURL=disk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disk.js","sourceRoot":"","sources":["../../../src/azure/resources/disk.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EACL,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,QAAsC,EACtC,aAAsC,EACtC,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,wCAAwC,CACzC,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IAErD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,wDAAwD;IACxD,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAElC,IAAI,CAAC;QACH,wDAAwD;QACxD,MAAM,WAAW,GAAG,MAAM,aAAa,CAAC,KAAK,CAAC,GAAG,CAC/C,iBAAiB,EACjB,QAAQ,CACT,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,WAAW,CAAC,CAAC;QAEtD,8BAA8B;QAC9B,IACE,WAAW,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,YAAY;YACrD,CAAC,WAAW,CAAC,SAAS,EACtB,CAAC;YACD,MAAM,MAAM,GAAG;gBACb,QAAQ;gBACR,MAAM,EAAE,sBAAsB;gBAC9B,eAAe,EAAE,IAAI;aACtB,CAAC;YACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC;QAChB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CACT,kCAAkC,QAAQ,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAChG,CAAC;QACF,oDAAoD;QACpD,wDAAwD;QACxD,+DAA+D;IACjE,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAChE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Azure resource-specific analysis functions
3
+ */
4
+ export { analyzeAppServicePlan } from "./app-service.js";
5
+ export { analyzeDisk } from "./disk.js";
6
+ export { analyzeNic } from "./nic.js";
7
+ export { analyzePrivateEndpoint } from "./private-endpoint.js";
8
+ export { analyzePublicIp } from "./public-ip.js";
9
+ export { analyzeStorageAccount } from "./storage.js";
10
+ export { analyzeVM } from "./vm.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Azure resource-specific analysis functions
3
+ */
4
+ export { analyzeAppServicePlan } from "./app-service.js";
5
+ export { analyzeDisk } from "./disk.js";
6
+ export { analyzeNic } from "./nic.js";
7
+ export { analyzePrivateEndpoint } from "./private-endpoint.js";
8
+ export { analyzePublicIp } from "./public-ip.js";
9
+ export { analyzeStorageAccount } from "./storage.js";
10
+ export { analyzeVM } from "./vm.js";
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/azure/resources/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Azure Network Interface analysis
3
+ */
4
+ import type { NetworkManagementClient } from "@azure/arm-network";
5
+ import * as armResources from "@azure/arm-resources";
6
+ import type { AnalysisResult } from "../../types.js";
7
+ /**
8
+ * Analyzes an Azure Network Interface for potential cost optimization.
9
+ *
10
+ * @param resource - The Azure resource object
11
+ * @param networkClient - Azure Network client for NIC details
12
+ * @returns Analysis result with cost risk and reason
13
+ */
14
+ export declare function analyzeNic(resource: armResources.GenericResource, networkClient: NetworkManagementClient, verbose?: boolean): Promise<AnalysisResult>;
15
+ //# sourceMappingURL=nic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nic.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/nic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQrD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,uBAAuB,EACtC,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA6DzB"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Azure Network Interface analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure Network Interface for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param networkClient - Azure Network client for NIC details
11
+ * @returns Analysis result with cost risk and reason
12
+ */
13
+ export async function analyzeNic(resource, networkClient, verbose = false) {
14
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Network Interface (microsoft.network/networkinterfaces)");
15
+ verboseLog(verbose, "Resource details:", resource);
16
+ const costRisk = "medium";
17
+ let reason = "";
18
+ if (!resource.id) {
19
+ return {
20
+ costRisk,
21
+ reason: "Resource ID is missing.",
22
+ suspectedUnused: false,
23
+ };
24
+ }
25
+ // Extract resource group and NIC name from resource ID
26
+ const resourceParts = resource.id.split("/");
27
+ const resourceGroupName = resourceParts[4];
28
+ const nicName = resourceParts[8];
29
+ try {
30
+ // Get detailed NIC information
31
+ const nicDetails = await networkClient.networkInterfaces.get(resourceGroupName, nicName);
32
+ verboseLog(verbose, "NIC API details:", nicDetails);
33
+ // Check if NIC is not attached to any VM or private endpoint
34
+ if (!nicDetails.virtualMachine && !nicDetails.privateEndpoint) {
35
+ reason += "NIC not attached to any VM or private endpoint. ";
36
+ }
37
+ // Check if NIC has no public IP
38
+ const hasPublicIP = nicDetails.ipConfigurations?.some((config) => config.publicIPAddress);
39
+ if (!hasPublicIP) {
40
+ reason += "No public IP assigned. ";
41
+ }
42
+ // Note: Network Interface metrics are not available for Private Endpoint NICs
43
+ // and most Azure NICs don't expose standard traffic metrics through Azure Monitor
44
+ // The primary checks (attachment to VM and public IP assignment) are sufficient
45
+ }
46
+ catch (error) {
47
+ const logger = getLogger(["savemoney", "azure"]);
48
+ logger.warn(`Failed to get NIC details for ${nicName}: ${error instanceof Error ? error.message : error}`);
49
+ reason += "Could not retrieve detailed NIC information. ";
50
+ }
51
+ const suspectedUnused = reason.length > 0;
52
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
53
+ verboseLogAnalysisResult(verbose, result);
54
+ return result;
55
+ }
56
+ //# sourceMappingURL=nic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nic.js","sourceRoot":"","sources":["../../../src/azure/resources/nic.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EACL,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAsC,EACtC,aAAsC,EACtC,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,yDAAyD,CAC1D,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,uDAAuD;IACvD,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAEjC,IAAI,CAAC;QACH,+BAA+B;QAC/B,MAAM,UAAU,GAAG,MAAM,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAC1D,iBAAiB,EACjB,OAAO,CACR,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,kBAAkB,EAAE,UAAU,CAAC,CAAC;QAEpD,6DAA6D;QAC7D,IAAI,CAAC,UAAU,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC;YAC9D,MAAM,IAAI,kDAAkD,CAAC;QAC/D,CAAC;QAED,gCAAgC;QAChC,MAAM,WAAW,GAAG,UAAU,CAAC,gBAAgB,EAAE,IAAI,CACnD,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,eAAe,CACnC,CAAC;QACF,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,IAAI,yBAAyB,CAAC;QACtC,CAAC;QAED,8EAA8E;QAC9E,kFAAkF;QAClF,gFAAgF;IAClF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CACT,iCAAiC,OAAO,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAC9F,CAAC;QACF,MAAM,IAAI,+CAA+C,CAAC;IAC5D,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC;IACpE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Azure Private Endpoint analysis
3
+ */
4
+ import type { NetworkManagementClient } from "@azure/arm-network";
5
+ import * as armResources from "@azure/arm-resources";
6
+ import type { AnalysisResult } from "../../types.js";
7
+ /**
8
+ * Analyzes an Azure Private Endpoint for potential cost optimization.
9
+ *
10
+ * @param resource - The Azure resource object
11
+ * @param networkClient - Azure Network client for Private Endpoint details
12
+ * @returns Analysis result with cost risk and reason
13
+ */
14
+ export declare function analyzePrivateEndpoint(resource: armResources.GenericResource, networkClient: NetworkManagementClient, verbose?: boolean): Promise<AnalysisResult>;
15
+ //# sourceMappingURL=private-endpoint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"private-endpoint.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/private-endpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAQrD;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,uBAAuB,EACtC,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CAmFzB"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Azure Private Endpoint analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure Private Endpoint for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param networkClient - Azure Network client for Private Endpoint details
11
+ * @returns Analysis result with cost risk and reason
12
+ */
13
+ export async function analyzePrivateEndpoint(resource, networkClient, verbose = false) {
14
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Private Endpoint (microsoft.network/privateendpoints)");
15
+ verboseLog(verbose, "Resource details:", resource);
16
+ const costRisk = "medium";
17
+ let reason = "";
18
+ if (!resource.id) {
19
+ return {
20
+ costRisk,
21
+ reason: "Resource ID is missing.",
22
+ suspectedUnused: false,
23
+ };
24
+ }
25
+ // Extract resource group and private endpoint name from resource ID
26
+ const resourceParts = resource.id.split("/");
27
+ const resourceGroupName = resourceParts[4];
28
+ const privateEndpointName = resourceParts[8];
29
+ try {
30
+ // Get detailed Private Endpoint information
31
+ const privateEndpointDetails = await networkClient.privateEndpoints.get(resourceGroupName, privateEndpointName);
32
+ verboseLog(verbose, "Private Endpoint API details:", privateEndpointDetails);
33
+ // Check if Private Endpoint has no private link service connection
34
+ if (!privateEndpointDetails.privateLinkServiceConnections ||
35
+ privateEndpointDetails.privateLinkServiceConnections.length === 0) {
36
+ reason +=
37
+ "Private Endpoint has no private link service connections configured. ";
38
+ }
39
+ // Check connection state
40
+ const hasFailedConnection = privateEndpointDetails.privateLinkServiceConnections?.some((connection) => connection.privateLinkServiceConnectionState?.status === "Rejected" ||
41
+ connection.privateLinkServiceConnectionState?.status ===
42
+ "Disconnected");
43
+ if (hasFailedConnection) {
44
+ reason += "Private Endpoint has rejected or disconnected connections. ";
45
+ }
46
+ // Check if Private Endpoint has network interfaces
47
+ if (!privateEndpointDetails.networkInterfaces ||
48
+ privateEndpointDetails.networkInterfaces.length === 0) {
49
+ reason += "Private Endpoint has no network interfaces attached. ";
50
+ }
51
+ // Check subnet configuration
52
+ if (!privateEndpointDetails.subnet) {
53
+ reason += "Private Endpoint is not associated with a subnet. ";
54
+ }
55
+ }
56
+ catch (error) {
57
+ const logger = getLogger(["savemoney", "azure"]);
58
+ logger.warn(`Failed to get Private Endpoint details for ${privateEndpointName}: ${error instanceof Error ? error.message : error}`);
59
+ reason += "Could not retrieve detailed Private Endpoint information. ";
60
+ }
61
+ const suspectedUnused = reason.length > 0;
62
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
63
+ verboseLogAnalysisResult(verbose, result);
64
+ return result;
65
+ }
66
+ //# sourceMappingURL=private-endpoint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"private-endpoint.js","sourceRoot":"","sources":["../../../src/azure/resources/private-endpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EACL,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,QAAsC,EACtC,aAAsC,EACtC,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,uDAAuD,CACxD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,mBAAmB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAE7C,IAAI,CAAC;QACH,4CAA4C;QAC5C,MAAM,sBAAsB,GAAG,MAAM,aAAa,CAAC,gBAAgB,CAAC,GAAG,CACrE,iBAAiB,EACjB,mBAAmB,CACpB,CAAC;QAEF,UAAU,CACR,OAAO,EACP,+BAA+B,EAC/B,sBAAsB,CACvB,CAAC;QAEF,mEAAmE;QACnE,IACE,CAAC,sBAAsB,CAAC,6BAA6B;YACrD,sBAAsB,CAAC,6BAA6B,CAAC,MAAM,KAAK,CAAC,EACjE,CAAC;YACD,MAAM;gBACJ,uEAAuE,CAAC;QAC5E,CAAC;QAED,yBAAyB;QACzB,MAAM,mBAAmB,GACvB,sBAAsB,CAAC,6BAA6B,EAAE,IAAI,CACxD,CAAC,UAAU,EAAE,EAAE,CACb,UAAU,CAAC,iCAAiC,EAAE,MAAM,KAAK,UAAU;YACnE,UAAU,CAAC,iCAAiC,EAAE,MAAM;gBAClD,cAAc,CACnB,CAAC;QAEJ,IAAI,mBAAmB,EAAE,CAAC;YACxB,MAAM,IAAI,6DAA6D,CAAC;QAC1E,CAAC;QAED,mDAAmD;QACnD,IACE,CAAC,sBAAsB,CAAC,iBAAiB;YACzC,sBAAsB,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,EACrD,CAAC;YACD,MAAM,IAAI,uDAAuD,CAAC;QACpE,CAAC;QAED,6BAA6B;QAC7B,IAAI,CAAC,sBAAsB,CAAC,MAAM,EAAE,CAAC;YACnC,MAAM,IAAI,oDAAoD,CAAC;QACjE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CACT,8CAA8C,mBAAmB,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CACvH,CAAC;QACF,MAAM,IAAI,4DAA4D,CAAC;IACzE,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC;IACpE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Azure Public IP analysis
3
+ */
4
+ import type { MonitorClient } from "@azure/arm-monitor";
5
+ import type { NetworkManagementClient } from "@azure/arm-network";
6
+ import * as armResources from "@azure/arm-resources";
7
+ import type { AnalysisResult } from "../../types.js";
8
+ /**
9
+ * Analyzes an Azure Public IP for potential cost optimization.
10
+ *
11
+ * @param resource - The Azure resource object
12
+ * @param networkClient - Azure Network client for Public IP details
13
+ * @param monitorClient - Azure Monitor client for metrics
14
+ * @param timespanDays - Number of days to analyze metrics
15
+ * @returns Analysis result with cost risk and reason
16
+ */
17
+ export declare function analyzePublicIp(resource: armResources.GenericResource, networkClient: NetworkManagementClient, monitorClient: MonitorClient, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
18
+ //# sourceMappingURL=public-ip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-ip.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/public-ip.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAElE,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AASrD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,uBAAuB,EACtC,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CAuEzB"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Azure Public IP analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { getMetric, verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure Public IP for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param networkClient - Azure Network client for Public IP details
11
+ * @param monitorClient - Azure Monitor client for metrics
12
+ * @param timespanDays - Number of days to analyze metrics
13
+ * @returns Analysis result with cost risk and reason
14
+ */
15
+ export async function analyzePublicIp(resource, networkClient, monitorClient, timespanDays, verbose = false) {
16
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Public IP (microsoft.network/publicipaddresses)");
17
+ verboseLog(verbose, "Resource details:", resource);
18
+ const costRisk = "medium";
19
+ let reason = "";
20
+ if (!resource.id) {
21
+ return {
22
+ costRisk,
23
+ reason: "Resource ID is missing.",
24
+ suspectedUnused: false,
25
+ };
26
+ }
27
+ // Extract resource group and public IP name from resource ID
28
+ const resourceParts = resource.id.split("/");
29
+ const resourceGroupName = resourceParts[4];
30
+ const publicIpName = resourceParts[8];
31
+ try {
32
+ // Get detailed Public IP information
33
+ const publicIpDetails = await networkClient.publicIPAddresses.get(resourceGroupName, publicIpName);
34
+ verboseLog(verbose, "Public IP API details:", publicIpDetails);
35
+ // Check if Public IP is not associated with any resource
36
+ if (!publicIpDetails.ipConfiguration && !publicIpDetails.natGateway) {
37
+ reason += "Public IP not associated with any resource. ";
38
+ }
39
+ // Check if it's a static IP that might be unused
40
+ if (publicIpDetails.publicIPAllocationMethod === "Static" &&
41
+ !publicIpDetails.ipConfiguration) {
42
+ reason += "Static IP not in use. ";
43
+ }
44
+ // Check network metrics for low usage
45
+ const bytesInDDoS = await getMetric(monitorClient, resource.id, "BytesInDDoS", "Total", timespanDays);
46
+ if (bytesInDDoS !== null && bytesInDDoS < 1000000) {
47
+ // Less than 1MB total in 30 days
48
+ reason += `Very low network traffic (${(bytesInDDoS / 1024 / 1024).toFixed(2)} MB). `;
49
+ }
50
+ }
51
+ catch (error) {
52
+ const logger = getLogger(["savemoney", "azure"]);
53
+ logger.warn(`Failed to get Public IP details for ${publicIpName}: ${error instanceof Error ? error.message : error}`);
54
+ reason += "Could not retrieve detailed Public IP information. ";
55
+ }
56
+ const suspectedUnused = reason.length > 0;
57
+ const result = { costRisk, reason: reason.trim(), suspectedUnused };
58
+ verboseLogAnalysisResult(verbose, result);
59
+ return result;
60
+ }
61
+ //# sourceMappingURL=public-ip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-ip.js","sourceRoot":"","sources":["../../../src/azure/resources/public-ip.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAI7C,OAAO,EACL,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAAsC,EACtC,aAAsC,EACtC,aAA4B,EAC5B,YAAoB,EACpB,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,iDAAiD,CAClD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,MAAM,GAAG,EAAE,CAAC;IAEhB,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,MAAM,aAAa,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,MAAM,iBAAiB,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAC3C,MAAM,YAAY,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;IAEtC,IAAI,CAAC;QACH,qCAAqC;QACrC,MAAM,eAAe,GAAG,MAAM,aAAa,CAAC,iBAAiB,CAAC,GAAG,CAC/D,iBAAiB,EACjB,YAAY,CACb,CAAC;QAEF,UAAU,CAAC,OAAO,EAAE,wBAAwB,EAAE,eAAe,CAAC,CAAC;QAE/D,yDAAyD;QACzD,IAAI,CAAC,eAAe,CAAC,eAAe,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,CAAC;YACpE,MAAM,IAAI,8CAA8C,CAAC;QAC3D,CAAC;QAED,iDAAiD;QACjD,IACE,eAAe,CAAC,wBAAwB,KAAK,QAAQ;YACrD,CAAC,eAAe,CAAC,eAAe,EAChC,CAAC;YACD,MAAM,IAAI,wBAAwB,CAAC;QACrC,CAAC;QAED,sCAAsC;QACtC,MAAM,WAAW,GAAG,MAAM,SAAS,CACjC,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,aAAa,EACb,OAAO,EACP,YAAY,CACb,CAAC;QAEF,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,GAAG,OAAO,EAAE,CAAC;YAClD,iCAAiC;YACjC,MAAM,IAAI,6BAA6B,CAAC,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;QACxF,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAG,SAAS,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CACT,uCAAuC,YAAY,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CACzG,CAAC;QACF,MAAM,IAAI,qDAAqD,CAAC;IAClE,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,eAAe,EAAE,CAAC;IACpE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Azure Storage Account analysis
3
+ */
4
+ import type { MonitorClient } from "@azure/arm-monitor";
5
+ import * as armResources from "@azure/arm-resources";
6
+ import type { AnalysisResult } from "../../types.js";
7
+ /**
8
+ * Analyzes an Azure Storage Account for potential cost optimization.
9
+ *
10
+ * @param resource - The Azure resource object
11
+ * @param monitorClient - Azure Monitor client for metrics
12
+ * @param timespanDays - Number of days to analyze metrics
13
+ * @returns Analysis result with cost risk and reason
14
+ */
15
+ export declare function analyzeStorageAccount(resource: armResources.GenericResource, monitorClient: MonitorClient, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
16
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAErD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AASrD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CAoCzB"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Azure Storage Account analysis
3
+ */
4
+ import { getMetric, verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
5
+ /**
6
+ * Analyzes an Azure Storage Account for potential cost optimization.
7
+ *
8
+ * @param resource - The Azure resource object
9
+ * @param monitorClient - Azure Monitor client for metrics
10
+ * @param timespanDays - Number of days to analyze metrics
11
+ * @returns Analysis result with cost risk and reason
12
+ */
13
+ export async function analyzeStorageAccount(resource, monitorClient, timespanDays, verbose = false) {
14
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Storage Account (microsoft.storage/storageaccounts)");
15
+ verboseLog(verbose, "Resource details:", resource);
16
+ const costRisk = "medium";
17
+ if (!resource.id) {
18
+ return {
19
+ costRisk,
20
+ reason: "Resource ID is missing.",
21
+ suspectedUnused: false,
22
+ };
23
+ }
24
+ const transactions = await getMetric(monitorClient, resource.id, "Transactions", "Total", timespanDays);
25
+ if (transactions !== null && transactions < 100) {
26
+ // Very low transactions
27
+ const result = {
28
+ costRisk,
29
+ reason: `Very low transaction count (${transactions}). `,
30
+ suspectedUnused: true,
31
+ };
32
+ verboseLogAnalysisResult(verbose, result);
33
+ return result;
34
+ }
35
+ const result = { costRisk, reason: "", suspectedUnused: false };
36
+ verboseLogAnalysisResult(verbose, result);
37
+ return result;
38
+ }
39
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/azure/resources/storage.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,OAAO,EACL,SAAS,EACT,UAAU,EACV,wBAAwB,EACxB,uBAAuB,GACxB,MAAM,aAAa,CAAC;AAErB;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,QAAsC,EACtC,aAA4B,EAC5B,YAAoB,EACpB,OAAO,GAAG,KAAK;IAEf,uBAAuB,CACrB,OAAO,EACP,QAAQ,CAAC,IAAI,IAAI,SAAS,EAC1B,qDAAqD,CACtD,CAAC;IACF,UAAU,CAAC,OAAO,EAAE,mBAAmB,EAAE,QAAQ,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAA8B,QAAQ,CAAC;IACrD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,OAAO;YACL,QAAQ;YACR,MAAM,EAAE,yBAAyB;YACjC,eAAe,EAAE,KAAK;SACvB,CAAC;IACJ,CAAC;IACD,MAAM,YAAY,GAAG,MAAM,SAAS,CAClC,aAAa,EACb,QAAQ,CAAC,EAAE,EACX,cAAc,EACd,OAAO,EACP,YAAY,CACb,CAAC;IACF,IAAI,YAAY,KAAK,IAAI,IAAI,YAAY,GAAG,GAAG,EAAE,CAAC;QAChD,wBAAwB;QACxB,MAAM,MAAM,GAAG;YACb,QAAQ;YACR,MAAM,EAAE,+BAA+B,YAAY,KAAK;YACxD,eAAe,EAAE,IAAI;SACtB,CAAC;QACF,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,eAAe,EAAE,KAAK,EAAE,CAAC;IAChE,wBAAwB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Azure Virtual Machine analysis
3
+ */
4
+ import type { ComputeManagementClient } from "@azure/arm-compute";
5
+ import type { MonitorClient } from "@azure/arm-monitor";
6
+ import * as armResources from "@azure/arm-resources";
7
+ import type { AnalysisResult } from "../../types.js";
8
+ /**
9
+ * Analyzes an Azure Virtual Machine for potential cost optimization.
10
+ *
11
+ * @param resource - The Azure resource object
12
+ * @param monitorClient - Azure Monitor client for fetching metrics
13
+ * @param computeClient - Azure Compute client for VM details
14
+ * @param timespanDays - Number of days to analyze metrics
15
+ * @param verbose - Whether verbose logging is enabled
16
+ * @returns Analysis result with cost risk and reason
17
+ */
18
+ export declare function analyzeVM(resource: armResources.GenericResource, monitorClient: MonitorClient, computeClient: ComputeManagementClient, timespanDays: number, verbose?: boolean): Promise<AnalysisResult>;
19
+ //# sourceMappingURL=vm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vm.d.ts","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAExD,OAAO,KAAK,YAAY,MAAM,sBAAsB,CAAC;AAGrD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AASrD;;;;;;;;;GASG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,YAAY,CAAC,eAAe,EACtC,aAAa,EAAE,aAAa,EAC5B,aAAa,EAAE,uBAAuB,EACtC,YAAY,EAAE,MAAM,EACpB,OAAO,UAAQ,GACd,OAAO,CAAC,cAAc,CAAC,CA6FzB"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Azure Virtual Machine analysis
3
+ */
4
+ import { getLogger } from "@logtape/logtape";
5
+ import { getMetric, verboseLog, verboseLogAnalysisResult, verboseLogResourceStart, } from "../utils.js";
6
+ /**
7
+ * Analyzes an Azure Virtual Machine for potential cost optimization.
8
+ *
9
+ * @param resource - The Azure resource object
10
+ * @param monitorClient - Azure Monitor client for fetching metrics
11
+ * @param computeClient - Azure Compute client for VM details
12
+ * @param timespanDays - Number of days to analyze metrics
13
+ * @param verbose - Whether verbose logging is enabled
14
+ * @returns Analysis result with cost risk and reason
15
+ */
16
+ export async function analyzeVM(resource, monitorClient, computeClient, timespanDays, verbose = false) {
17
+ verboseLogResourceStart(verbose, resource.name || "unknown", "Virtual Machine (microsoft.compute/virtualmachines)");
18
+ verboseLog(verbose, "Resource details:", resource);
19
+ const costRisk = "high";
20
+ let reason = "";
21
+ if (!resource.id) {
22
+ return {
23
+ costRisk,
24
+ reason: "Resource ID is missing.",
25
+ suspectedUnused: false,
26
+ };
27
+ }
28
+ // Extract resource group and VM name from resource ID
29
+ const resourceParts = resource.id.split("/");
30
+ const resourceGroupName = resourceParts[4];
31
+ const vmName = resourceParts[8];
32
+ try {
33
+ // Get the actual VM instance view to check power state
34
+ const instanceView = await computeClient.virtualMachines.instanceView(resourceGroupName, vmName);
35
+ verboseLog(verbose, "VM Instance View:", instanceView);
36
+ // Check power state from instance view
37
+ const vmStatus = instanceView.statuses?.find((s) => s.code?.startsWith("PowerState/"));
38
+ if (vmStatus?.code === "PowerState/deallocated") {
39
+ const result = {
40
+ costRisk,
41
+ reason: "VM is deallocated. ",
42
+ suspectedUnused: true,
43
+ };
44
+ verboseLogAnalysisResult(verbose, result);
45
+ return result;
46
+ }
47
+ if (vmStatus?.code === "PowerState/stopped") {
48
+ const result = {
49
+ costRisk,
50
+ reason: "VM is stopped. ",
51
+ suspectedUnused: true,
52
+ };
53
+ verboseLogAnalysisResult(verbose, result);
54
+ return result;
55
+ }
56
+ }
57
+ catch (error) {
58
+ const logger = getLogger(["savemoney", "azure"]);
59
+ logger.warn(`Failed to get VM instance view for ${vmName}: ${error instanceof Error ? error.message : error}`);
60
+ // Continue with metric analysis if instance view fails
61
+ }
62
+ // Check metrics for low utilization
63
+ const cpuUsage = await getMetric(monitorClient, resource.id, "Percentage CPU", "Average", timespanDays);
64
+ const networkIn = await getMetric(monitorClient, resource.id, "Network In Total", "Total", timespanDays);
65
+ if (cpuUsage !== null && cpuUsage < 1) {
66
+ // Less than 1% average CPU
67
+ reason += `Low CPU usage (avg ${cpuUsage.toFixed(2)}%). `;
68
+ }
69
+ if (networkIn !== null && networkIn < 1024 * 1024 * 10) {
70
+ // Less than 10MB total network in
71
+ reason += `Low network traffic. `;
72
+ }
73
+ const result = { costRisk, reason, suspectedUnused: reason.length > 0 };
74
+ verboseLogAnalysisResult(verbose, result);
75
+ return result;
76
+ }
77
+ //# sourceMappingURL=vm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vm.js","sourceRoot":"","sources":["../../../src/azure/resources/vm.ts"],"names":[],"mappings":"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"}