Skip to content

Commit 447580b

Browse files
authored
feat(config-resolver): resolve hostname from variants (#2980)
* feat(config-resolver): resolve hostname from variants * test(functional): fips+dualstack endpoints
1 parent ba4386d commit 447580b

15 files changed

+12206
-478
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { EndpointVariant } from "./EndpointVariant";
2+
import { getHostnameFromVariants, GetHostnameFromVariantsOptions } from "./getHostnameFromVariants";
3+
4+
describe(getHostnameFromVariants.name, () => {
5+
const getMockHostname = (options: GetHostnameFromVariantsOptions) => JSON.stringify(options);
6+
const getMockTags = ({ useFipsEndpoint, useDualstackEndpoint }: GetHostnameFromVariantsOptions) => [
7+
...(useFipsEndpoint ? ["fips"] : []),
8+
...(useDualstackEndpoint ? ["dualstack"] : []),
9+
];
10+
const getMockVariants = () =>
11+
[
12+
{ useFipsEndpoint: false, useDualstackEndpoint: false },
13+
{ useFipsEndpoint: false, useDualstackEndpoint: true },
14+
{ useFipsEndpoint: true, useDualstackEndpoint: false },
15+
{ useFipsEndpoint: true, useDualstackEndpoint: true },
16+
].map((options) => ({
17+
hostname: getMockHostname(options),
18+
tags: getMockTags(options),
19+
}));
20+
21+
const testCases = [
22+
[false, false],
23+
[false, true],
24+
[true, false],
25+
[true, true],
26+
];
27+
28+
describe("returns hostname if present in variants", () => {
29+
it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => {
30+
const options = { useFipsEndpoint, useDualstackEndpoint };
31+
const variants = getMockVariants() as EndpointVariant[];
32+
expect(getHostnameFromVariants(variants, options)).toEqual(getMockHostname(options));
33+
});
34+
});
35+
36+
describe("returns undefined if not present in variants", () => {
37+
it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => {
38+
const options = { useFipsEndpoint, useDualstackEndpoint };
39+
const variants = getMockVariants() as EndpointVariant[];
40+
expect(
41+
getHostnameFromVariants(
42+
variants.filter(({ tags }) => JSON.stringify(tags) !== JSON.stringify(getMockTags(options))),
43+
options
44+
)
45+
).toBeUndefined();
46+
});
47+
});
48+
49+
describe("returns undefined if variants in undefined", () => {
50+
it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => {
51+
const options = { useFipsEndpoint, useDualstackEndpoint };
52+
expect(getHostnameFromVariants(undefined, options)).toBeUndefined();
53+
});
54+
});
55+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { EndpointVariant } from "./EndpointVariant";
2+
3+
export interface GetHostnameFromVariantsOptions {
4+
useFipsEndpoint: boolean;
5+
useDualstackEndpoint: boolean;
6+
}
7+
8+
export const getHostnameFromVariants = (
9+
variants: EndpointVariant[] = [],
10+
{ useFipsEndpoint, useDualstackEndpoint }: GetHostnameFromVariantsOptions
11+
) =>
12+
variants.find(
13+
({ tags }) => useFipsEndpoint === tags.includes("fips") && useDualstackEndpoint === tags.includes("dualstack")
14+
)?.hostname;

packages/config-resolver/src/regionInfo/getHostnameTemplate.spec.ts

-18
This file was deleted.

packages/config-resolver/src/regionInfo/getHostnameTemplate.ts

-8
This file was deleted.

packages/config-resolver/src/regionInfo/getRegionInfo.spec.ts

+44-12
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
import { getHostnameFromVariants } from "./getHostnameFromVariants";
12
import { getRegionInfo } from "./getRegionInfo";
23
import { getResolvedHostname } from "./getResolvedHostname";
34
import { getResolvedPartition } from "./getResolvedPartition";
45
import { getResolvedSigningRegion } from "./getResolvedSigningRegion";
56
import { PartitionHash } from "./PartitionHash";
67
import { RegionHash } from "./RegionHash";
78

9+
jest.mock("./getHostnameFromVariants");
810
jest.mock("./getResolvedHostname");
911
jest.mock("./getResolvedPartition");
1012
jest.mock("./getResolvedSigningRegion");
@@ -64,12 +66,14 @@ describe(getRegionInfo.name, () => {
6466
});
6567

6668
beforeEach(() => {
69+
(getHostnameFromVariants as jest.Mock).mockReturnValue(mockHostname);
6770
(getResolvedHostname as jest.Mock).mockReturnValue(mockHostname);
6871
(getResolvedPartition as jest.Mock).mockReturnValue(mockPartition);
6972
(getResolvedSigningRegion as jest.Mock).mockReturnValue(undefined);
7073
});
7174

7275
afterEach(() => {
76+
expect(getHostnameFromVariants).toHaveBeenCalledTimes(2);
7377
expect(getResolvedHostname).toHaveBeenCalledTimes(1);
7478
expect(getResolvedPartition).toHaveBeenCalledTimes(1);
7579
jest.clearAllMocks();
@@ -83,17 +87,22 @@ describe(getRegionInfo.name, () => {
8387
const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash);
8488
const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions);
8589

90+
const mockResolvedRegion = getMockResolvedRegion(regionCase);
91+
const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname;
92+
const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname;
93+
94+
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname);
95+
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname);
96+
8697
expect(getRegionInfo(mockRegion, mockGetRegionInfoOptions)).toEqual({
8798
signingService: mockSigningService,
8899
hostname: mockHostname,
89100
partition: mockPartition,
90101
});
91102

92-
const mockResolvedRegion = getMockResolvedRegion(regionCase);
93103
expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, {
94-
signingService: mockSigningService,
95-
regionHostname: mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname,
96-
partitionHostname: mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname,
104+
regionHostname: mockRegionHostname,
105+
partitionHostname: mockPartitionHostname,
97106
});
98107
expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions);
99108
expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockRegion, {
@@ -133,6 +142,13 @@ describe(getRegionInfo.name, () => {
133142
const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash);
134143
const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions);
135144

145+
const mockResolvedRegion = getMockResolvedRegion(regionCase);
146+
const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname;
147+
const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname;
148+
149+
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname);
150+
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname);
151+
136152
const mockRegionHashWithSigningRegion = getMockRegionHashWithSigningRegion(
137153
regionCase,
138154
mockRegionHash,
@@ -148,11 +164,9 @@ describe(getRegionInfo.name, () => {
148164
signingRegion: mockSigningRegion,
149165
});
150166

151-
const mockResolvedRegion = getMockResolvedRegion(regionCase);
152167
expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, {
153-
signingService: mockSigningService,
154-
regionHostname: mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname,
155-
partitionHostname: mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname,
168+
regionHostname: mockRegionHostname,
169+
partitionHostname: mockPartitionHostname,
156170
});
157171
expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions);
158172
expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockRegion, {
@@ -192,6 +206,13 @@ describe(getRegionInfo.name, () => {
192206
const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash);
193207
const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions);
194208

209+
const mockResolvedRegion = getMockResolvedRegion(regionCase);
210+
const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname;
211+
const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname;
212+
213+
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname);
214+
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname);
215+
195216
const mockRegionHashWithSigningRegion = getMockRegionHashWithSigningService(
196217
regionCase,
197218
mockRegionHash,
@@ -206,11 +227,9 @@ describe(getRegionInfo.name, () => {
206227
partition: mockPartition,
207228
});
208229

209-
const mockResolvedRegion = getMockResolvedRegion(regionCase);
210230
expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, {
211-
signingService: mockSigningService,
212-
regionHostname: mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname,
213-
partitionHostname: mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname,
231+
regionHostname: mockRegionHostname,
232+
partitionHostname: mockPartitionHostname,
214233
});
215234
expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions);
216235
expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockRegion, {
@@ -219,4 +238,17 @@ describe(getRegionInfo.name, () => {
219238
});
220239
});
221240
});
241+
242+
it("throws error if hostname is not defined", () => {
243+
(getResolvedHostname as jest.Mock).mockReturnValueOnce(undefined);
244+
const mockRegionHash = getMockRegionHash(RegionCase.REGION);
245+
const mockPartitionHash = getMockPartitionHash(RegionCase.REGION);
246+
expect(() => {
247+
getRegionInfo(mockRegion, {
248+
signingService: mockSigningService,
249+
regionHash: mockRegionHash,
250+
partitionHash: mockPartitionHash,
251+
});
252+
}).toThrow();
253+
});
222254
});

packages/config-resolver/src/regionInfo/getRegionInfo.ts

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,42 @@
11
import { RegionInfo } from "@aws-sdk/types";
22

3+
import { getHostnameFromVariants } from "./getHostnameFromVariants";
34
import { getResolvedHostname } from "./getResolvedHostname";
45
import { getResolvedPartition } from "./getResolvedPartition";
56
import { getResolvedSigningRegion } from "./getResolvedSigningRegion";
67
import { PartitionHash } from "./PartitionHash";
78
import { RegionHash } from "./RegionHash";
89

910
export interface GetRegionInfoOptions {
11+
useFipsEndpoint?: boolean;
12+
useDualstackEndpoint?: boolean;
1013
signingService: string;
1114
regionHash: RegionHash;
1215
partitionHash: PartitionHash;
1316
}
1417

1518
export const getRegionInfo = (
1619
region: string,
17-
{ signingService, regionHash, partitionHash }: GetRegionInfoOptions
20+
{
21+
useFipsEndpoint = false,
22+
useDualstackEndpoint = false,
23+
signingService,
24+
regionHash,
25+
partitionHash,
26+
}: GetRegionInfoOptions
1827
): RegionInfo => {
1928
const partition = getResolvedPartition(region, { partitionHash });
2029
const resolvedRegion = region in regionHash ? region : partitionHash[partition]?.endpoint ?? region;
2130

22-
const hostname = getResolvedHostname(resolvedRegion, {
23-
signingService,
24-
regionHostname: regionHash[resolvedRegion]?.hostname,
25-
partitionHostname: partitionHash[partition]?.hostname,
26-
});
31+
const hostnameOptions = { useFipsEndpoint, useDualstackEndpoint };
32+
const regionHostname = getHostnameFromVariants(regionHash[resolvedRegion]?.variants, hostnameOptions);
33+
const partitionHostname = getHostnameFromVariants(partitionHash[partition]?.variants, hostnameOptions);
34+
const hostname = getResolvedHostname(resolvedRegion, { regionHostname, partitionHostname });
35+
36+
if (hostname === undefined) {
37+
throw new Error(`Endpoint resolution failed for: ${{ resolvedRegion, useFipsEndpoint, useDualstackEndpoint }}`);
38+
}
39+
2740
const signingRegion = getResolvedSigningRegion(region, {
2841
hostname,
2942
signingRegion: regionHash[resolvedRegion]?.signingRegion,
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,30 @@
1-
import { getHostnameTemplate } from "./getHostnameTemplate";
21
import { getResolvedHostname } from "./getResolvedHostname";
32

4-
jest.mock("./getHostnameTemplate");
5-
63
describe(getResolvedHostname.name, () => {
7-
const mockSigningService = "mockSigningService";
84
const mockRegion = "mockRegion";
95
const mockHostname = "{region}.mockHostname.com";
106

117
afterEach(() => {
128
jest.clearAllMocks();
139
});
1410

15-
it("returns hostname if available in regionHash", () => {
11+
it("returns hostname if available in regionHostname", () => {
1612
expect(
1713
getResolvedHostname(mockRegion, {
18-
signingService: mockSigningService,
1914
regionHostname: mockHostname,
2015
})
2116
).toBe(mockHostname);
22-
expect(getHostnameTemplate).not.toHaveBeenCalled();
2317
});
2418

25-
it("returns hostname from hostname template when not available in regionHash", () => {
26-
(getHostnameTemplate as jest.Mock).mockReturnValue(mockHostname);
27-
19+
it("returns hostname from partitionHostname when not available in partitionHostname", () => {
2820
expect(
2921
getResolvedHostname(mockRegion, {
30-
signingService: mockSigningService,
3122
partitionHostname: mockHostname,
3223
})
3324
).toBe(mockHostname.replace("{region}", mockRegion));
25+
});
3426

35-
expect(getHostnameTemplate).toHaveBeenCalledTimes(1);
36-
expect(getHostnameTemplate).toHaveBeenCalledWith(mockSigningService, {
37-
partitionHostname: mockHostname,
38-
});
27+
it("returns undefined not available in either regionHostname or partitionHostname", () => {
28+
expect(getResolvedHostname(mockRegion, {})).toBeUndefined();
3929
});
4030
});
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { getHostnameTemplate } from "./getHostnameTemplate";
2-
31
export interface GetResolvedHostnameOptions {
4-
signingService: string;
52
regionHostname?: string;
63
partitionHostname?: string;
74
}
85

96
export const getResolvedHostname = (
107
resolvedRegion: string,
11-
{ signingService, regionHostname, partitionHostname }: GetResolvedHostnameOptions
12-
) => regionHostname ?? getHostnameTemplate(signingService, { partitionHostname }).replace("{region}", resolvedRegion);
8+
{ regionHostname, partitionHostname }: GetResolvedHostnameOptions
9+
): string | undefined =>
10+
regionHostname
11+
? regionHostname
12+
: partitionHostname
13+
? partitionHostname.replace("{region}", resolvedRegion)
14+
: undefined;

tests/functional/endpoints/fips/index.spec.ts renamed to tests/functional/endpoints/fips-pseudo-region/index.spec.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const getClientPackageName = (sdkId: string) =>
88
.map((word) => word.toLowerCase())
99
.join("-")}`;
1010

11-
describe("endpoints.fips", () => {
11+
// These tests should be removed when pseudo regions are deprecated.
12+
describe("endpoints.fips-pseudo-region", () => {
1213
for (const { sdkId, region, signingRegion, hostname } of testCases) {
1314
const clientPackageName = getClientPackageName(sdkId);
1415
it(`testing "${clientPackageName}" with region: ${region}`, async () => {

0 commit comments

Comments
 (0)