@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.
- package/README.md +233 -0
- package/dist/azure/analyzer.d.ts +32 -0
- package/dist/azure/analyzer.d.ts.map +1 -0
- package/dist/azure/analyzer.js +128 -0
- package/dist/azure/analyzer.js.map +1 -0
- package/dist/azure/config.d.ts +19 -0
- package/dist/azure/config.d.ts.map +1 -0
- package/dist/azure/config.js +62 -0
- package/dist/azure/config.js.map +1 -0
- package/dist/azure/index.d.ts +9 -0
- package/dist/azure/index.d.ts.map +1 -0
- package/dist/azure/index.js +9 -0
- package/dist/azure/index.js.map +1 -0
- package/dist/azure/report.d.ts +12 -0
- package/dist/azure/report.d.ts.map +1 -0
- package/dist/azure/report.js +40 -0
- package/dist/azure/report.js.map +1 -0
- package/dist/azure/resources/app-service.d.ts +18 -0
- package/dist/azure/resources/app-service.d.ts.map +1 -0
- package/dist/azure/resources/app-service.js +65 -0
- package/dist/azure/resources/app-service.js.map +1 -0
- package/dist/azure/resources/disk.d.ts +16 -0
- package/dist/azure/resources/disk.d.ts.map +1 -0
- package/dist/azure/resources/disk.js +56 -0
- package/dist/azure/resources/disk.js.map +1 -0
- package/dist/azure/resources/index.d.ts +11 -0
- package/dist/azure/resources/index.d.ts.map +1 -0
- package/dist/azure/resources/index.js +11 -0
- package/dist/azure/resources/index.js.map +1 -0
- package/dist/azure/resources/nic.d.ts +15 -0
- package/dist/azure/resources/nic.d.ts.map +1 -0
- package/dist/azure/resources/nic.js +56 -0
- package/dist/azure/resources/nic.js.map +1 -0
- package/dist/azure/resources/private-endpoint.d.ts +15 -0
- package/dist/azure/resources/private-endpoint.d.ts.map +1 -0
- package/dist/azure/resources/private-endpoint.js +66 -0
- package/dist/azure/resources/private-endpoint.js.map +1 -0
- package/dist/azure/resources/public-ip.d.ts +18 -0
- package/dist/azure/resources/public-ip.d.ts.map +1 -0
- package/dist/azure/resources/public-ip.js +61 -0
- package/dist/azure/resources/public-ip.js.map +1 -0
- package/dist/azure/resources/storage.d.ts +16 -0
- package/dist/azure/resources/storage.d.ts.map +1 -0
- package/dist/azure/resources/storage.js +39 -0
- package/dist/azure/resources/storage.js.map +1 -0
- package/dist/azure/resources/vm.d.ts +19 -0
- package/dist/azure/resources/vm.d.ts.map +1 -0
- package/dist/azure/resources/vm.js +77 -0
- package/dist/azure/resources/vm.js.map +1 -0
- package/dist/azure/types.d.ts +34 -0
- package/dist/azure/types.d.ts.map +1 -0
- package/dist/azure/types.js +5 -0
- package/dist/azure/types.js.map +1 -0
- package/dist/azure/utils.d.ts +40 -0
- package/dist/azure/utils.d.ts.map +1 -0
- package/dist/azure/utils.js +104 -0
- package/dist/azure/utils.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +21 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +14 -0
- package/dist/types.js.map +1 -0
- package/package.json +60 -0
- package/src/azure/analyzer.ts +213 -0
- package/src/azure/config.ts +81 -0
- package/src/azure/index.ts +9 -0
- package/src/azure/report.ts +52 -0
- package/src/azure/resources/app-service.ts +118 -0
- package/src/azure/resources/disk.ts +88 -0
- package/src/azure/resources/index.ts +11 -0
- package/src/azure/resources/nic.ts +90 -0
- package/src/azure/resources/private-endpoint.ts +112 -0
- package/src/azure/resources/public-ip.ts +106 -0
- package/src/azure/resources/storage.ts +67 -0
- package/src/azure/resources/vm.ts +129 -0
- package/src/azure/types.ts +38 -0
- package/src/azure/utils.ts +141 -0
- package/src/index.test.ts +95 -0
- package/src/index.ts +99 -0
- 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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"}
|