@pagopa/dx-savemoney 0.3.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/__tests__/finding.test.js +22 -0
- package/dist/__tests__/finding.test.js.map +1 -1
- package/dist/azure/__tests__/analyzer-tags.test.js +28 -1
- package/dist/azure/__tests__/analyzer-tags.test.js.map +1 -1
- package/dist/azure/__tests__/config.test.js +31 -1
- package/dist/azure/__tests__/config.test.js.map +1 -1
- package/dist/azure/__tests__/report.test.js +71 -0
- package/dist/azure/__tests__/report.test.js.map +1 -1
- package/dist/azure/analyzer.d.ts +8 -1
- package/dist/azure/analyzer.d.ts.map +1 -1
- package/dist/azure/analyzer.js +57 -41
- package/dist/azure/analyzer.js.map +1 -1
- package/dist/azure/analyzers/registry.d.ts.map +1 -1
- package/dist/azure/analyzers/registry.js +4 -4
- package/dist/azure/analyzers/registry.js.map +1 -1
- package/dist/azure/analyzers/types.d.ts +6 -0
- package/dist/azure/analyzers/types.d.ts.map +1 -1
- package/dist/azure/config.d.ts.map +1 -1
- package/dist/azure/config.js +1 -5
- package/dist/azure/config.js.map +1 -1
- package/dist/azure/pricing/__tests__/cache.test.d.ts +8 -0
- package/dist/azure/pricing/__tests__/cache.test.d.ts.map +1 -0
- package/dist/azure/pricing/__tests__/cache.test.js +56 -0
- package/dist/azure/pricing/__tests__/cache.test.js.map +1 -0
- package/dist/azure/pricing/__tests__/client.test.d.ts +9 -0
- package/dist/azure/pricing/__tests__/client.test.d.ts.map +1 -0
- package/dist/azure/pricing/__tests__/client.test.js +181 -0
- package/dist/azure/pricing/__tests__/client.test.js.map +1 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.d.ts +6 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.d.ts.map +1 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.js +283 -0
- package/dist/azure/pricing/__tests__/pricing-service.test.js.map +1 -0
- package/dist/azure/pricing/cache.d.ts +45 -0
- package/dist/azure/pricing/cache.d.ts.map +1 -0
- package/dist/azure/pricing/cache.js +79 -0
- package/dist/azure/pricing/cache.js.map +1 -0
- package/dist/azure/pricing/client.d.ts +67 -0
- package/dist/azure/pricing/client.d.ts.map +1 -0
- package/dist/azure/pricing/client.js +96 -0
- package/dist/azure/pricing/client.js.map +1 -0
- package/dist/azure/pricing/index.d.ts +6 -0
- package/dist/azure/pricing/index.d.ts.map +1 -0
- package/dist/azure/pricing/index.js +6 -0
- package/dist/azure/pricing/index.js.map +1 -0
- package/dist/azure/pricing/pricing-service.d.ts +46 -0
- package/dist/azure/pricing/pricing-service.d.ts.map +1 -0
- package/dist/azure/pricing/pricing-service.js +84 -0
- package/dist/azure/pricing/pricing-service.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts +5 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.js +137 -0
- package/dist/azure/pricing/resolvers/__tests__/app-service.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts +5 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.js +160 -0
- package/dist/azure/pricing/resolvers/__tests__/disk.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts +5 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js +113 -0
- package/dist/azure/pricing/resolvers/__tests__/public-ip.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts +9 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.js +204 -0
- package/dist/azure/pricing/resolvers/__tests__/vm.test.js.map +1 -0
- package/dist/azure/pricing/resolvers/app-service.d.ts +27 -0
- package/dist/azure/pricing/resolvers/app-service.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/app-service.js +67 -0
- package/dist/azure/pricing/resolvers/app-service.js.map +1 -0
- package/dist/azure/pricing/resolvers/disk.d.ts +59 -0
- package/dist/azure/pricing/resolvers/disk.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/disk.js +140 -0
- package/dist/azure/pricing/resolvers/disk.js.map +1 -0
- package/dist/azure/pricing/resolvers/index.d.ts +12 -0
- package/dist/azure/pricing/resolvers/index.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/index.js +8 -0
- package/dist/azure/pricing/resolvers/index.js.map +1 -0
- package/dist/azure/pricing/resolvers/public-ip.d.ts +32 -0
- package/dist/azure/pricing/resolvers/public-ip.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/public-ip.js +56 -0
- package/dist/azure/pricing/resolvers/public-ip.js.map +1 -0
- package/dist/azure/pricing/resolvers/vm.d.ts +51 -0
- package/dist/azure/pricing/resolvers/vm.d.ts.map +1 -0
- package/dist/azure/pricing/resolvers/vm.js +89 -0
- package/dist/azure/pricing/resolvers/vm.js.map +1 -0
- package/dist/azure/pricing/schema.d.ts +88 -0
- package/dist/azure/pricing/schema.d.ts.map +1 -0
- package/dist/azure/pricing/schema.js +64 -0
- package/dist/azure/pricing/schema.js.map +1 -0
- package/dist/azure/pricing/test-cache-dir.d.ts +3 -0
- package/dist/azure/pricing/test-cache-dir.d.ts.map +1 -0
- package/dist/azure/pricing/test-cache-dir.js +17 -0
- package/dist/azure/pricing/test-cache-dir.js.map +1 -0
- package/dist/azure/report.d.ts.map +1 -1
- package/dist/azure/report.js +57 -18
- package/dist/azure/report.js.map +1 -1
- package/dist/azure/resources/__tests__/app-service.test.d.ts +5 -0
- package/dist/azure/resources/__tests__/app-service.test.d.ts.map +1 -0
- package/dist/azure/resources/__tests__/app-service.test.js +105 -0
- package/dist/azure/resources/__tests__/app-service.test.js.map +1 -0
- package/dist/azure/resources/app-service.d.ts +8 -3
- package/dist/azure/resources/app-service.d.ts.map +1 -1
- package/dist/azure/resources/app-service.js +35 -2
- package/dist/azure/resources/app-service.js.map +1 -1
- package/dist/azure/resources/disk.d.ts +3 -1
- package/dist/azure/resources/disk.d.ts.map +1 -1
- package/dist/azure/resources/disk.js +35 -3
- package/dist/azure/resources/disk.js.map +1 -1
- package/dist/azure/resources/public-ip.d.ts +3 -1
- package/dist/azure/resources/public-ip.d.ts.map +1 -1
- package/dist/azure/resources/public-ip.js +41 -3
- package/dist/azure/resources/public-ip.js.map +1 -1
- package/dist/azure/resources/vm.d.ts +4 -1
- package/dist/azure/resources/vm.d.ts.map +1 -1
- package/dist/azure/resources/vm.js +49 -6
- package/dist/azure/resources/vm.js.map +1 -1
- package/dist/azure/types.d.ts +18 -3
- package/dist/azure/types.d.ts.map +1 -1
- package/dist/finding.d.ts +3 -3
- package/dist/finding.d.ts.map +1 -1
- package/dist/finding.js +7 -0
- package/dist/finding.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +5 -1
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +5 -3
- package/dist/schema.js.map +1 -1
- package/dist/types.d.ts +8 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/finding.test.ts +24 -0
- package/src/azure/__tests__/analyzer-tags.test.ts +36 -1
- package/src/azure/__tests__/config.test.ts +43 -1
- package/src/azure/__tests__/fixtures/full-override.yaml +2 -0
- package/src/azure/__tests__/report.test.ts +78 -0
- package/src/azure/analyzer.ts +70 -52
- package/src/azure/analyzers/registry.ts +8 -2
- package/src/azure/analyzers/types.ts +6 -0
- package/src/azure/config.ts +1 -5
- package/src/azure/pricing/__tests__/cache.test.ts +67 -0
- package/src/azure/pricing/__tests__/client.test.ts +212 -0
- package/src/azure/pricing/__tests__/fixtures/vm-b2s-westeurope.json +51 -0
- package/src/azure/pricing/__tests__/pricing-service.test.ts +315 -0
- package/src/azure/pricing/cache.ts +92 -0
- package/src/azure/pricing/client.ts +137 -0
- package/src/azure/pricing/index.ts +6 -0
- package/src/azure/pricing/pricing-service.ts +125 -0
- package/src/azure/pricing/resolvers/__tests__/app-service.test.ts +154 -0
- package/src/azure/pricing/resolvers/__tests__/disk.test.ts +189 -0
- package/src/azure/pricing/resolvers/__tests__/public-ip.test.ts +130 -0
- package/src/azure/pricing/resolvers/__tests__/vm.test.ts +232 -0
- package/src/azure/pricing/resolvers/app-service.ts +102 -0
- package/src/azure/pricing/resolvers/disk.ts +170 -0
- package/src/azure/pricing/resolvers/index.ts +31 -0
- package/src/azure/pricing/resolvers/public-ip.ts +87 -0
- package/src/azure/pricing/resolvers/vm.ts +125 -0
- package/src/azure/pricing/schema.ts +69 -0
- package/src/azure/pricing/test-cache-dir.ts +26 -0
- package/src/azure/report.ts +86 -21
- package/src/azure/resources/__tests__/app-service.test.ts +165 -0
- package/src/azure/resources/app-service.ts +65 -4
- package/src/azure/resources/disk.ts +46 -4
- package/src/azure/resources/public-ip.ts +61 -2
- package/src/azure/resources/vm.ts +89 -11
- package/src/azure/types.ts +19 -3
- package/src/finding.ts +10 -3
- package/src/index.ts +4 -3
- package/src/schema.ts +7 -3
- package/src/types.ts +12 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the VM Retail Prices resolver.
|
|
3
|
+
*
|
|
4
|
+
* The Retail Prices API is mocked via the `FetchLike` indirection so the
|
|
5
|
+
* tests run hermetically. Fixtures live next to the tests to keep the
|
|
6
|
+
* surface explicit.
|
|
7
|
+
*/
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
9
|
+
import { DiskCache } from "../../cache.js";
|
|
10
|
+
import { PricingClient } from "../../client.js";
|
|
11
|
+
import { makeTestCacheDir, removeTestCacheDir } from "../../test-cache-dir.js";
|
|
12
|
+
import { HOURS_PER_MONTH, normalizeArmRegion, resolveVmMonthlyPrice, } from "../vm.js";
|
|
13
|
+
function makeFetch(body) {
|
|
14
|
+
return vi.fn().mockResolvedValue({
|
|
15
|
+
json: async () => body,
|
|
16
|
+
ok: true,
|
|
17
|
+
status: 200,
|
|
18
|
+
statusText: "OK",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
let dir;
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
dir = await makeTestCacheDir("vm-resolver");
|
|
24
|
+
});
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
await removeTestCacheDir(dir);
|
|
27
|
+
});
|
|
28
|
+
describe("normalizeArmRegion", () => {
|
|
29
|
+
it.each([
|
|
30
|
+
["westeurope", "westeurope"],
|
|
31
|
+
["West Europe", "westeurope"],
|
|
32
|
+
["WEST EUROPE", "westeurope"],
|
|
33
|
+
[" West Europe ", "westeurope"],
|
|
34
|
+
["italynorth", "italynorth"],
|
|
35
|
+
])("normalises %j → %j", (input, expected) => {
|
|
36
|
+
expect(normalizeArmRegion(input)).toBe(expected);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe("resolveVmMonthlyPrice", () => {
|
|
40
|
+
it("picks the cheapest Linux consumption hourly item and returns 730×price", async () => {
|
|
41
|
+
const body = {
|
|
42
|
+
Items: [
|
|
43
|
+
{
|
|
44
|
+
armRegionName: "westeurope",
|
|
45
|
+
armSkuName: "Standard_B2s",
|
|
46
|
+
currencyCode: "EUR",
|
|
47
|
+
meterName: "B2s",
|
|
48
|
+
productName: "Virtual Machines BS Series",
|
|
49
|
+
retailPrice: 0.05,
|
|
50
|
+
skuName: "B2s",
|
|
51
|
+
type: "Consumption",
|
|
52
|
+
unitOfMeasure: "1 Hour",
|
|
53
|
+
unitPrice: 0.05,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
armRegionName: "westeurope",
|
|
57
|
+
armSkuName: "Standard_B2s",
|
|
58
|
+
currencyCode: "EUR",
|
|
59
|
+
meterName: "B2s",
|
|
60
|
+
productName: "Virtual Machines BS Series Windows",
|
|
61
|
+
retailPrice: 0.09,
|
|
62
|
+
skuName: "B2s",
|
|
63
|
+
type: "Consumption",
|
|
64
|
+
unitOfMeasure: "1 Hour",
|
|
65
|
+
unitPrice: 0.09,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
NextPageLink: null,
|
|
69
|
+
};
|
|
70
|
+
const client = new PricingClient({
|
|
71
|
+
cache: new DiskCache({ dir }),
|
|
72
|
+
fetch: makeFetch(body),
|
|
73
|
+
});
|
|
74
|
+
const money = await resolveVmMonthlyPrice(client, {
|
|
75
|
+
armRegionName: "westeurope",
|
|
76
|
+
armSkuName: "Standard_B2s",
|
|
77
|
+
});
|
|
78
|
+
expect(money).toEqual({
|
|
79
|
+
amount: Math.round(0.05 * HOURS_PER_MONTH * 100) / 100,
|
|
80
|
+
currency: "EUR",
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
it("picks the Windows item when os hint is 'windows'", async () => {
|
|
84
|
+
const body = {
|
|
85
|
+
Items: [
|
|
86
|
+
{
|
|
87
|
+
armRegionName: "westeurope",
|
|
88
|
+
armSkuName: "Standard_B2s",
|
|
89
|
+
currencyCode: "EUR",
|
|
90
|
+
productName: "Virtual Machines BS Series",
|
|
91
|
+
retailPrice: 0.05,
|
|
92
|
+
skuName: "B2s",
|
|
93
|
+
type: "Consumption",
|
|
94
|
+
unitOfMeasure: "1 Hour",
|
|
95
|
+
unitPrice: 0.05,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
armRegionName: "westeurope",
|
|
99
|
+
armSkuName: "Standard_B2s",
|
|
100
|
+
currencyCode: "EUR",
|
|
101
|
+
productName: "Virtual Machines BS Series Windows",
|
|
102
|
+
retailPrice: 0.09,
|
|
103
|
+
skuName: "B2s",
|
|
104
|
+
type: "Consumption",
|
|
105
|
+
unitOfMeasure: "1 Hour",
|
|
106
|
+
unitPrice: 0.09,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
NextPageLink: null,
|
|
110
|
+
};
|
|
111
|
+
const client = new PricingClient({
|
|
112
|
+
cache: new DiskCache({ dir }),
|
|
113
|
+
fetch: makeFetch(body),
|
|
114
|
+
});
|
|
115
|
+
const money = await resolveVmMonthlyPrice(client, {
|
|
116
|
+
armRegionName: "westeurope",
|
|
117
|
+
armSkuName: "Standard_B2s",
|
|
118
|
+
os: "windows",
|
|
119
|
+
});
|
|
120
|
+
expect(money?.amount).toBeCloseTo(0.09 * HOURS_PER_MONTH, 2);
|
|
121
|
+
});
|
|
122
|
+
it("skips Spot / Low Priority items", async () => {
|
|
123
|
+
const body = {
|
|
124
|
+
Items: [
|
|
125
|
+
{
|
|
126
|
+
armRegionName: "westeurope",
|
|
127
|
+
armSkuName: "Standard_B2s",
|
|
128
|
+
currencyCode: "EUR",
|
|
129
|
+
productName: "Virtual Machines BS Series",
|
|
130
|
+
retailPrice: 0.01,
|
|
131
|
+
skuName: "B2s Spot",
|
|
132
|
+
type: "Consumption",
|
|
133
|
+
unitOfMeasure: "1 Hour",
|
|
134
|
+
unitPrice: 0.01,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
armRegionName: "westeurope",
|
|
138
|
+
armSkuName: "Standard_B2s",
|
|
139
|
+
currencyCode: "EUR",
|
|
140
|
+
productName: "Virtual Machines BS Series",
|
|
141
|
+
retailPrice: 0.05,
|
|
142
|
+
skuName: "B2s",
|
|
143
|
+
type: "Consumption",
|
|
144
|
+
unitOfMeasure: "1 Hour",
|
|
145
|
+
unitPrice: 0.05,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
NextPageLink: null,
|
|
149
|
+
};
|
|
150
|
+
const client = new PricingClient({
|
|
151
|
+
cache: new DiskCache({ dir }),
|
|
152
|
+
fetch: makeFetch(body),
|
|
153
|
+
});
|
|
154
|
+
const money = await resolveVmMonthlyPrice(client, {
|
|
155
|
+
armRegionName: "westeurope",
|
|
156
|
+
armSkuName: "Standard_B2s",
|
|
157
|
+
});
|
|
158
|
+
expect(money?.amount).toBeCloseTo(0.05 * HOURS_PER_MONTH, 2);
|
|
159
|
+
});
|
|
160
|
+
it("ignores Reservation entries", async () => {
|
|
161
|
+
const body = {
|
|
162
|
+
Items: [
|
|
163
|
+
{
|
|
164
|
+
armRegionName: "westeurope",
|
|
165
|
+
armSkuName: "Standard_B2s",
|
|
166
|
+
currencyCode: "EUR",
|
|
167
|
+
productName: "Virtual Machines BS Series",
|
|
168
|
+
reservationTerm: "1 Year",
|
|
169
|
+
retailPrice: 0.03,
|
|
170
|
+
skuName: "B2s",
|
|
171
|
+
type: "Reservation",
|
|
172
|
+
unitOfMeasure: "1 Hour",
|
|
173
|
+
unitPrice: 0.03,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
NextPageLink: null,
|
|
177
|
+
};
|
|
178
|
+
const client = new PricingClient({
|
|
179
|
+
cache: new DiskCache({ dir }),
|
|
180
|
+
fetch: makeFetch(body),
|
|
181
|
+
});
|
|
182
|
+
const money = await resolveVmMonthlyPrice(client, {
|
|
183
|
+
armRegionName: "westeurope",
|
|
184
|
+
armSkuName: "Standard_B2s",
|
|
185
|
+
});
|
|
186
|
+
expect(money).toBeUndefined();
|
|
187
|
+
});
|
|
188
|
+
it("returns undefined when no items match", async () => {
|
|
189
|
+
const body = {
|
|
190
|
+
Items: [],
|
|
191
|
+
NextPageLink: null,
|
|
192
|
+
};
|
|
193
|
+
const client = new PricingClient({
|
|
194
|
+
cache: new DiskCache({ dir }),
|
|
195
|
+
fetch: makeFetch(body),
|
|
196
|
+
});
|
|
197
|
+
const money = await resolveVmMonthlyPrice(client, {
|
|
198
|
+
armRegionName: "westeurope",
|
|
199
|
+
armSkuName: "Standard_B2s",
|
|
200
|
+
});
|
|
201
|
+
expect(money).toBeUndefined();
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
//# sourceMappingURL=vm.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vm.test.js","sourceRoot":"","sources":["../../../../../src/azure/pricing/resolvers/__tests__/vm.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver: estimated monthly cost of an Azure App Service Plan.
|
|
3
|
+
*
|
|
4
|
+
* App Service Plan meters are published as hourly prices per plan instance.
|
|
5
|
+
* The resolver looks up all App Service meters in the target region, then
|
|
6
|
+
* matches the ARM plan SKU locally because Retail Prices uses inconsistent
|
|
7
|
+
* spacing for some families (for example `P1v3` vs `P1 v3`).
|
|
8
|
+
*/
|
|
9
|
+
import type { Money } from "../../../finding.js";
|
|
10
|
+
import type { PricingClient } from "../client.js";
|
|
11
|
+
export type AppServicePlanOs = "linux" | "windows";
|
|
12
|
+
export type ResolveAppServicePlanInput = {
|
|
13
|
+
/** ARM region name, e.g. `westeurope`. */
|
|
14
|
+
armRegionName: string;
|
|
15
|
+
/** Optional OS hint. When absent, the cheapest matching meter is used. */
|
|
16
|
+
os?: AppServicePlanOs;
|
|
17
|
+
/** App Service Plan SKU name, e.g. `S1`, `P1v3`, `P1mv3`. */
|
|
18
|
+
skuName: string;
|
|
19
|
+
/** Number of plan workers/instances to price. Defaults to 1. */
|
|
20
|
+
workerCount?: number;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the estimated monthly cost for an App Service Plan. Returns
|
|
24
|
+
* `undefined` if no matching consumption meter can be found.
|
|
25
|
+
*/
|
|
26
|
+
export declare function resolveAppServicePlanMonthlyPrice(client: PricingClient, input: ResolveAppServicePlanInput): Promise<Money | undefined>;
|
|
27
|
+
//# sourceMappingURL=app-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-service.d.ts","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/app-service.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAKlD,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,SAAS,CAAC;AAEnD,MAAM,MAAM,0BAA0B,GAAG;IACvC,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,EAAE,CAAC,EAAE,gBAAgB,CAAC;IACtB,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,iCAAiC,CACrD,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAoB5B"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver: estimated monthly cost of an Azure App Service Plan.
|
|
3
|
+
*
|
|
4
|
+
* App Service Plan meters are published as hourly prices per plan instance.
|
|
5
|
+
* The resolver looks up all App Service meters in the target region, then
|
|
6
|
+
* matches the ARM plan SKU locally because Retail Prices uses inconsistent
|
|
7
|
+
* spacing for some families (for example `P1v3` vs `P1 v3`).
|
|
8
|
+
*/
|
|
9
|
+
import { HOURS_PER_MONTH, normalizeArmRegion } from "./vm.js";
|
|
10
|
+
/**
|
|
11
|
+
* Resolves the estimated monthly cost for an App Service Plan. Returns
|
|
12
|
+
* `undefined` if no matching consumption meter can be found.
|
|
13
|
+
*/
|
|
14
|
+
export async function resolveAppServicePlanMonthlyPrice(client, input) {
|
|
15
|
+
const filter = [
|
|
16
|
+
"serviceName eq 'Azure App Service'",
|
|
17
|
+
`armRegionName eq '${normalizeArmRegion(input.armRegionName)}'`,
|
|
18
|
+
].join(" and ");
|
|
19
|
+
const items = await client.query(filter);
|
|
20
|
+
const item = pickAppServicePlanItem(items, input);
|
|
21
|
+
if (!item) {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
amount: round2(item.unitPrice *
|
|
26
|
+
HOURS_PER_MONTH *
|
|
27
|
+
normalizeWorkerCount(input.workerCount)),
|
|
28
|
+
currency: item.currencyCode,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function matchesOs(item, os) {
|
|
32
|
+
if (!os)
|
|
33
|
+
return true;
|
|
34
|
+
// Retail meters encode the OS variant in the product name, not in a
|
|
35
|
+
// dedicated field.
|
|
36
|
+
const product = item.productName?.toLowerCase() ?? "";
|
|
37
|
+
const isLinuxItem = product.includes("linux");
|
|
38
|
+
return os === "linux" ? isLinuxItem : !isLinuxItem;
|
|
39
|
+
}
|
|
40
|
+
function normalizeSkuName(value) {
|
|
41
|
+
// ARM uses compact SKU names (`P1v3`), while Retail Prices sometimes
|
|
42
|
+
// inserts spaces (`P1 v3`).
|
|
43
|
+
return value?.toLowerCase().replace(/\s+/g, "") ?? "";
|
|
44
|
+
}
|
|
45
|
+
function normalizeWorkerCount(workerCount) {
|
|
46
|
+
// App Service Plan billing is per whole worker instance; invalid or missing
|
|
47
|
+
// values fall back to the minimum billable instance.
|
|
48
|
+
if (!workerCount || workerCount < 1)
|
|
49
|
+
return 1;
|
|
50
|
+
return Math.ceil(workerCount);
|
|
51
|
+
}
|
|
52
|
+
function pickAppServicePlanItem(items, input) {
|
|
53
|
+
const skuName = normalizeSkuName(input.skuName);
|
|
54
|
+
const candidates = items.filter((it) => it.type === "Consumption" &&
|
|
55
|
+
it.unitOfMeasure === "1 Hour" &&
|
|
56
|
+
normalizeSkuName(it.skuName) === skuName &&
|
|
57
|
+
(it.productName?.toLowerCase().includes("app service") ?? false) &&
|
|
58
|
+
(it.productName?.toLowerCase().includes("plan") ?? false) &&
|
|
59
|
+
matchesOs(it, input.os));
|
|
60
|
+
if (candidates.length === 0)
|
|
61
|
+
return undefined;
|
|
62
|
+
return candidates.reduce((cheapest, it) => it.unitPrice < cheapest.unitPrice ? it : cheapest);
|
|
63
|
+
}
|
|
64
|
+
function round2(value) {
|
|
65
|
+
return Math.round(value * 100) / 100;
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=app-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-service.js","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/app-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver: estimated monthly cost of an Azure Managed Disk.
|
|
3
|
+
*
|
|
4
|
+
* Azure prices managed disks per discrete capacity tier (P1…P80,
|
|
5
|
+
* E1…E80, S4…S80) rather than per actual `diskSizeGB`. The resolver:
|
|
6
|
+
*
|
|
7
|
+
* 1. Maps the disk SKU (`Premium_LRS`, `StandardSSD_ZRS`, …) and
|
|
8
|
+
* `diskSizeGB` to the corresponding billing tier (e.g. `P10 LRS`).
|
|
9
|
+
* 2. Queries the Azure Retail Prices API with the tier-derived
|
|
10
|
+
* `skuName` for the right service / region.
|
|
11
|
+
* 3. Returns the consumption price (already `1/Month` in the API) as
|
|
12
|
+
* `Money`, or `undefined` if no match (e.g. Ultra disks, which use
|
|
13
|
+
* a different per-GB / IOPS / throughput pricing model and are not
|
|
14
|
+
* yet supported).
|
|
15
|
+
*
|
|
16
|
+
* The mapping is intentionally encoded in a single Zod-validated
|
|
17
|
+
* lookup table so a malformed input fails fast and loud rather than
|
|
18
|
+
* silently producing a wrong tier.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import type { Money } from "../../../finding.js";
|
|
22
|
+
import type { PricingClient } from "../client.js";
|
|
23
|
+
/**
|
|
24
|
+
* Replication mode supported by the resolver. Ultra disks (`UltraSSD_LRS`)
|
|
25
|
+
* are intentionally excluded because their pricing is multi-dimensional
|
|
26
|
+
* (capacity + IOPS + throughput) and would need a dedicated resolver.
|
|
27
|
+
*/
|
|
28
|
+
export declare const DiskSkuSchema: z.ZodEnum<{
|
|
29
|
+
Premium_LRS: "Premium_LRS";
|
|
30
|
+
Premium_ZRS: "Premium_ZRS";
|
|
31
|
+
StandardSSD_LRS: "StandardSSD_LRS";
|
|
32
|
+
StandardSSD_ZRS: "StandardSSD_ZRS";
|
|
33
|
+
Standard_LRS: "Standard_LRS";
|
|
34
|
+
}>;
|
|
35
|
+
export type DiskSku = z.infer<typeof DiskSkuSchema>;
|
|
36
|
+
export declare const ResolveDiskInputSchema: z.ZodObject<{
|
|
37
|
+
armRegionName: z.ZodString;
|
|
38
|
+
diskSizeGiB: z.ZodNumber;
|
|
39
|
+
sku: z.ZodEnum<{
|
|
40
|
+
Premium_LRS: "Premium_LRS";
|
|
41
|
+
Premium_ZRS: "Premium_ZRS";
|
|
42
|
+
StandardSSD_LRS: "StandardSSD_LRS";
|
|
43
|
+
StandardSSD_ZRS: "StandardSSD_ZRS";
|
|
44
|
+
Standard_LRS: "Standard_LRS";
|
|
45
|
+
}>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
export type ResolveDiskInput = z.infer<typeof ResolveDiskInputSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* Pure helper exposed for testing: resolves the size→tier mapping
|
|
50
|
+
* without touching the network.
|
|
51
|
+
*/
|
|
52
|
+
export declare function pickTier(diskSizeGiB: number): number | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the estimated monthly cost for a Managed Disk. Returns
|
|
55
|
+
* `undefined` if the SKU/size combo is unsupported (e.g. exceeds the
|
|
56
|
+
* largest tier) or if no matching Retail Prices entry is found.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveDiskMonthlyPrice(client: PricingClient, rawInput: ResolveDiskInput): Promise<Money | undefined>;
|
|
59
|
+
//# sourceMappingURL=disk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disk.d.ts","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/disk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAKlD;;;;GAIG;AACH,eAAO,MAAM,aAAa;;;;;;EAMxB,CAAC;AACH,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AA0DpD,eAAO,MAAM,sBAAsB;;;;;;;;;;iBAOjC,CAAC;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEhE;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,gBAAgB,GACzB,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAuB5B"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver: estimated monthly cost of an Azure Managed Disk.
|
|
3
|
+
*
|
|
4
|
+
* Azure prices managed disks per discrete capacity tier (P1…P80,
|
|
5
|
+
* E1…E80, S4…S80) rather than per actual `diskSizeGB`. The resolver:
|
|
6
|
+
*
|
|
7
|
+
* 1. Maps the disk SKU (`Premium_LRS`, `StandardSSD_ZRS`, …) and
|
|
8
|
+
* `diskSizeGB` to the corresponding billing tier (e.g. `P10 LRS`).
|
|
9
|
+
* 2. Queries the Azure Retail Prices API with the tier-derived
|
|
10
|
+
* `skuName` for the right service / region.
|
|
11
|
+
* 3. Returns the consumption price (already `1/Month` in the API) as
|
|
12
|
+
* `Money`, or `undefined` if no match (e.g. Ultra disks, which use
|
|
13
|
+
* a different per-GB / IOPS / throughput pricing model and are not
|
|
14
|
+
* yet supported).
|
|
15
|
+
*
|
|
16
|
+
* The mapping is intentionally encoded in a single Zod-validated
|
|
17
|
+
* lookup table so a malformed input fails fast and loud rather than
|
|
18
|
+
* silently producing a wrong tier.
|
|
19
|
+
*/
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
import { normalizeArmRegion } from "./vm.js";
|
|
22
|
+
/**
|
|
23
|
+
* Replication mode supported by the resolver. Ultra disks (`UltraSSD_LRS`)
|
|
24
|
+
* are intentionally excluded because their pricing is multi-dimensional
|
|
25
|
+
* (capacity + IOPS + throughput) and would need a dedicated resolver.
|
|
26
|
+
*/
|
|
27
|
+
export const DiskSkuSchema = z.enum([
|
|
28
|
+
"Premium_LRS",
|
|
29
|
+
"Premium_ZRS",
|
|
30
|
+
"StandardSSD_LRS",
|
|
31
|
+
"StandardSSD_ZRS",
|
|
32
|
+
"Standard_LRS",
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Inclusive upper bound (GiB) → tier name. Order matters: the resolver
|
|
36
|
+
* picks the FIRST entry whose `maxSizeGiB` is greater-or-equal to the
|
|
37
|
+
* requested size.
|
|
38
|
+
*
|
|
39
|
+
* See https://learn.microsoft.com/azure/virtual-machines/disks-types for
|
|
40
|
+
* the canonical tier table.
|
|
41
|
+
*/
|
|
42
|
+
const TIER_TABLE = [
|
|
43
|
+
{ maxSizeGiB: 4, tier: 1 },
|
|
44
|
+
{ maxSizeGiB: 8, tier: 2 },
|
|
45
|
+
{ maxSizeGiB: 16, tier: 3 },
|
|
46
|
+
{ maxSizeGiB: 32, tier: 4 },
|
|
47
|
+
{ maxSizeGiB: 64, tier: 6 },
|
|
48
|
+
{ maxSizeGiB: 128, tier: 10 },
|
|
49
|
+
{ maxSizeGiB: 256, tier: 15 },
|
|
50
|
+
{ maxSizeGiB: 512, tier: 20 },
|
|
51
|
+
{ maxSizeGiB: 1024, tier: 30 },
|
|
52
|
+
{ maxSizeGiB: 2048, tier: 40 },
|
|
53
|
+
{ maxSizeGiB: 4096, tier: 50 },
|
|
54
|
+
{ maxSizeGiB: 8192, tier: 60 },
|
|
55
|
+
{ maxSizeGiB: 16384, tier: 70 },
|
|
56
|
+
{ maxSizeGiB: 32768, tier: 80 },
|
|
57
|
+
];
|
|
58
|
+
const SKU_METADATA = {
|
|
59
|
+
Premium_LRS: {
|
|
60
|
+
prefix: "P",
|
|
61
|
+
productName: "Premium SSD Managed Disks",
|
|
62
|
+
replication: "LRS",
|
|
63
|
+
},
|
|
64
|
+
Premium_ZRS: {
|
|
65
|
+
prefix: "P",
|
|
66
|
+
productName: "Premium SSD Managed Disks",
|
|
67
|
+
replication: "ZRS",
|
|
68
|
+
},
|
|
69
|
+
Standard_LRS: {
|
|
70
|
+
prefix: "S",
|
|
71
|
+
productName: "Standard HDD Managed Disks",
|
|
72
|
+
replication: "LRS",
|
|
73
|
+
},
|
|
74
|
+
StandardSSD_LRS: {
|
|
75
|
+
prefix: "E",
|
|
76
|
+
productName: "Standard SSD Managed Disks",
|
|
77
|
+
replication: "LRS",
|
|
78
|
+
},
|
|
79
|
+
StandardSSD_ZRS: {
|
|
80
|
+
prefix: "E",
|
|
81
|
+
productName: "Standard SSD Managed Disks",
|
|
82
|
+
replication: "ZRS",
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
export const ResolveDiskInputSchema = z.object({
|
|
86
|
+
/** ARM region name, e.g. `westeurope`. */
|
|
87
|
+
armRegionName: z.string().min(1),
|
|
88
|
+
/** Disk capacity in GiB as reported by the Compute API (`diskSizeGB`). */
|
|
89
|
+
diskSizeGiB: z.number().int().positive(),
|
|
90
|
+
/** Managed disk SKU. */
|
|
91
|
+
sku: DiskSkuSchema,
|
|
92
|
+
});
|
|
93
|
+
/**
|
|
94
|
+
* Pure helper exposed for testing: resolves the size→tier mapping
|
|
95
|
+
* without touching the network.
|
|
96
|
+
*/
|
|
97
|
+
export function pickTier(diskSizeGiB) {
|
|
98
|
+
return TIER_TABLE.find((row) => diskSizeGiB <= row.maxSizeGiB)?.tier;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Resolves the estimated monthly cost for a Managed Disk. Returns
|
|
102
|
+
* `undefined` if the SKU/size combo is unsupported (e.g. exceeds the
|
|
103
|
+
* largest tier) or if no matching Retail Prices entry is found.
|
|
104
|
+
*/
|
|
105
|
+
export async function resolveDiskMonthlyPrice(client, rawInput) {
|
|
106
|
+
const input = ResolveDiskInputSchema.parse(rawInput);
|
|
107
|
+
const tier = pickTier(input.diskSizeGiB);
|
|
108
|
+
if (tier === undefined)
|
|
109
|
+
return undefined;
|
|
110
|
+
const { prefix, productName, replication } = SKU_METADATA[input.sku];
|
|
111
|
+
const skuName = `${prefix}${tier} ${replication}`;
|
|
112
|
+
const filter = [
|
|
113
|
+
"serviceName eq 'Storage'",
|
|
114
|
+
`armRegionName eq '${normalizeArmRegion(input.armRegionName)}'`,
|
|
115
|
+
`productName eq '${productName}'`,
|
|
116
|
+
`skuName eq '${skuName}'`,
|
|
117
|
+
].join(" and ");
|
|
118
|
+
const items = await client.query(filter);
|
|
119
|
+
const item = pickDiskItem(items, skuName);
|
|
120
|
+
if (!item)
|
|
121
|
+
return undefined;
|
|
122
|
+
return {
|
|
123
|
+
amount: round2(item.unitPrice),
|
|
124
|
+
currency: item.currencyCode,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function pickDiskItem(items, skuName) {
|
|
128
|
+
// Managed Disk meters are reported as `1/Month`; we keep the filter
|
|
129
|
+
// explicit to avoid accidentally picking a per-hour or per-IOPS entry.
|
|
130
|
+
const candidates = items.filter((it) => it.type === "Consumption" &&
|
|
131
|
+
it.unitOfMeasure === "1/Month" &&
|
|
132
|
+
it.skuName === skuName);
|
|
133
|
+
if (candidates.length === 0)
|
|
134
|
+
return undefined;
|
|
135
|
+
return candidates.reduce((cheapest, it) => it.unitPrice < cheapest.unitPrice ? it : cheapest);
|
|
136
|
+
}
|
|
137
|
+
function round2(value) {
|
|
138
|
+
return Math.round(value * 100) / 100;
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=disk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"disk.js","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/disk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public exports for resolvers.
|
|
3
|
+
*/
|
|
4
|
+
export type { AppServicePlanOs, ResolveAppServicePlanInput, } from "./app-service.js";
|
|
5
|
+
export { resolveAppServicePlanMonthlyPrice } from "./app-service.js";
|
|
6
|
+
export type { DiskSku, ResolveDiskInput } from "./disk.js";
|
|
7
|
+
export { DiskSkuSchema, pickTier, ResolveDiskInputSchema, resolveDiskMonthlyPrice, } from "./disk.js";
|
|
8
|
+
export type { PublicIpAllocation, PublicIpSku, ResolvePublicIpInput, } from "./public-ip.js";
|
|
9
|
+
export { resolvePublicIpMonthlyPrice } from "./public-ip.js";
|
|
10
|
+
export type { ResolveVmInput, VmOs } from "./vm.js";
|
|
11
|
+
export { HOURS_PER_MONTH, normalizeArmRegion, resolveVmMonthlyPrice, } from "./vm.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,YAAY,EACV,gBAAgB,EAChB,0BAA0B,GAC3B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iCAAiC,EAAE,MAAM,kBAAkB,CAAC;AAErE,YAAY,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EACL,aAAa,EACb,QAAQ,EACR,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AAEnB,YAAY,EACV,kBAAkB,EAClB,WAAW,EACX,oBAAoB,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAE7D,YAAY,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public exports for resolvers.
|
|
3
|
+
*/
|
|
4
|
+
export { resolveAppServicePlanMonthlyPrice } from "./app-service.js";
|
|
5
|
+
export { DiskSkuSchema, pickTier, ResolveDiskInputSchema, resolveDiskMonthlyPrice, } from "./disk.js";
|
|
6
|
+
export { resolvePublicIpMonthlyPrice } from "./public-ip.js";
|
|
7
|
+
export { HOURS_PER_MONTH, normalizeArmRegion, resolveVmMonthlyPrice, } from "./vm.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,OAAO,EAAE,iCAAiC,EAAE,MAAM,kBAAkB,CAAC;AAGrE,OAAO,EACL,aAAa,EACb,QAAQ,EACR,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,WAAW,CAAC;AAOnB,OAAO,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAG7D,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver: estimated monthly cost of an Azure Public IP address.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Azure Retail Prices API to look up the pay-as-you-go price for
|
|
5
|
+
* the given SKU + region + allocation method combination, then converts
|
|
6
|
+
* the hourly rate (when reported as `1 Hour`) to a monthly figure using
|
|
7
|
+
* the conventional 730-hours-per-month constant.
|
|
8
|
+
*
|
|
9
|
+
* Public IP pricing is published per `(sku, allocation)` pair. Both
|
|
10
|
+
* Standard SKU and a Static Basic SKU carry a billable hourly rate; a
|
|
11
|
+
* Dynamic Basic SKU is typically free when associated, and the lookup
|
|
12
|
+
* returns `undefined` in that case.
|
|
13
|
+
*/
|
|
14
|
+
import type { Money } from "../../../finding.js";
|
|
15
|
+
import type { PricingClient } from "../client.js";
|
|
16
|
+
export type PublicIpAllocation = "dynamic" | "static";
|
|
17
|
+
export type PublicIpSku = "basic" | "standard";
|
|
18
|
+
export type ResolvePublicIpInput = {
|
|
19
|
+
/** Allocation method (`Static` / `Dynamic`). */
|
|
20
|
+
allocation: PublicIpAllocation;
|
|
21
|
+
/** ARM region name, e.g. `westeurope`. */
|
|
22
|
+
armRegionName: string;
|
|
23
|
+
/** SKU (`Basic` / `Standard`). */
|
|
24
|
+
sku: PublicIpSku;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the estimated monthly cost for a Public IP. Returns
|
|
28
|
+
* `undefined` when no matching consumption meter is found (e.g. the
|
|
29
|
+
* combination is genuinely free).
|
|
30
|
+
*/
|
|
31
|
+
export declare function resolvePublicIpMonthlyPrice(client: PricingClient, input: ResolvePublicIpInput): Promise<Money | undefined>;
|
|
32
|
+
//# sourceMappingURL=public-ip.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"public-ip.d.ts","sourceRoot":"","sources":["../../../../src/azure/pricing/resolvers/public-ip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAKlD,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG,QAAQ,CAAC;AACtD,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/C,MAAM,MAAM,oBAAoB,GAAG;IACjC,gDAAgD;IAChD,UAAU,EAAE,kBAAkB,CAAC;IAC/B,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAC;IACtB,kCAAkC;IAClC,GAAG,EAAE,WAAW,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,oBAAoB,GAC1B,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAkB5B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolver: estimated monthly cost of an Azure Public IP address.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Azure Retail Prices API to look up the pay-as-you-go price for
|
|
5
|
+
* the given SKU + region + allocation method combination, then converts
|
|
6
|
+
* the hourly rate (when reported as `1 Hour`) to a monthly figure using
|
|
7
|
+
* the conventional 730-hours-per-month constant.
|
|
8
|
+
*
|
|
9
|
+
* Public IP pricing is published per `(sku, allocation)` pair. Both
|
|
10
|
+
* Standard SKU and a Static Basic SKU carry a billable hourly rate; a
|
|
11
|
+
* Dynamic Basic SKU is typically free when associated, and the lookup
|
|
12
|
+
* returns `undefined` in that case.
|
|
13
|
+
*/
|
|
14
|
+
import { HOURS_PER_MONTH, normalizeArmRegion } from "./vm.js";
|
|
15
|
+
/**
|
|
16
|
+
* Resolves the estimated monthly cost for a Public IP. Returns
|
|
17
|
+
* `undefined` when no matching consumption meter is found (e.g. the
|
|
18
|
+
* combination is genuinely free).
|
|
19
|
+
*/
|
|
20
|
+
export async function resolvePublicIpMonthlyPrice(client, input) {
|
|
21
|
+
// Public IPs are billed under the "Virtual Network" service.
|
|
22
|
+
const filter = [
|
|
23
|
+
"serviceName eq 'Virtual Network'",
|
|
24
|
+
`armRegionName eq '${normalizeArmRegion(input.armRegionName)}'`,
|
|
25
|
+
`skuName eq '${capitalize(input.sku)}'`,
|
|
26
|
+
].join(" and ");
|
|
27
|
+
const items = await client.query(filter);
|
|
28
|
+
const item = pickPublicIpItem(items, input.allocation);
|
|
29
|
+
if (!item) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
amount: round2(item.unitPrice * HOURS_PER_MONTH),
|
|
34
|
+
currency: item.currencyCode,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function capitalize(value) {
|
|
38
|
+
if (value.length === 0)
|
|
39
|
+
return value;
|
|
40
|
+
return value[0].toUpperCase() + value.slice(1).toLowerCase();
|
|
41
|
+
}
|
|
42
|
+
function pickPublicIpItem(items, allocation) {
|
|
43
|
+
const allocationToken = allocation === "static" ? "static" : "dynamic";
|
|
44
|
+
const candidates = items.filter((it) => it.type === "Consumption" &&
|
|
45
|
+
it.unitOfMeasure === "1 Hour" &&
|
|
46
|
+
(it.meterName?.toLowerCase().includes("ip address") ?? false) &&
|
|
47
|
+
(it.meterName?.toLowerCase().includes(allocationToken) ?? false));
|
|
48
|
+
if (candidates.length === 0)
|
|
49
|
+
return undefined;
|
|
50
|
+
// Pick the cheapest entry as a conservative lower bound.
|
|
51
|
+
return candidates.reduce((cheapest, it) => it.unitPrice < cheapest.unitPrice ? it : cheapest);
|
|
52
|
+
}
|
|
53
|
+
function round2(value) {
|
|
54
|
+
return Math.round(value * 100) / 100;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=public-ip.js.map
|