Skip to content

Commit

Permalink
feat(config-resolver): resolve hostname from variants (#2980)
Browse files Browse the repository at this point in the history
* feat(config-resolver): resolve hostname from variants

* test(functional): fips+dualstack endpoints
  • Loading branch information
trivikr authored Nov 4, 2021
1 parent ba4386d commit 447580b
Show file tree
Hide file tree
Showing 15 changed files with 12,206 additions and 478 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { EndpointVariant } from "./EndpointVariant";
import { getHostnameFromVariants, GetHostnameFromVariantsOptions } from "./getHostnameFromVariants";

describe(getHostnameFromVariants.name, () => {
const getMockHostname = (options: GetHostnameFromVariantsOptions) => JSON.stringify(options);
const getMockTags = ({ useFipsEndpoint, useDualstackEndpoint }: GetHostnameFromVariantsOptions) => [
...(useFipsEndpoint ? ["fips"] : []),
...(useDualstackEndpoint ? ["dualstack"] : []),
];
const getMockVariants = () =>
[
{ useFipsEndpoint: false, useDualstackEndpoint: false },
{ useFipsEndpoint: false, useDualstackEndpoint: true },
{ useFipsEndpoint: true, useDualstackEndpoint: false },
{ useFipsEndpoint: true, useDualstackEndpoint: true },
].map((options) => ({
hostname: getMockHostname(options),
tags: getMockTags(options),
}));

const testCases = [
[false, false],
[false, true],
[true, false],
[true, true],
];

describe("returns hostname if present in variants", () => {
it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => {
const options = { useFipsEndpoint, useDualstackEndpoint };
const variants = getMockVariants() as EndpointVariant[];
expect(getHostnameFromVariants(variants, options)).toEqual(getMockHostname(options));
});
});

describe("returns undefined if not present in variants", () => {
it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => {
const options = { useFipsEndpoint, useDualstackEndpoint };
const variants = getMockVariants() as EndpointVariant[];
expect(
getHostnameFromVariants(
variants.filter(({ tags }) => JSON.stringify(tags) !== JSON.stringify(getMockTags(options))),
options
)
).toBeUndefined();
});
});

describe("returns undefined if variants in undefined", () => {
it.each(testCases)("useFipsEndpoint: %s, useDualstackEndpoint: %s", (useFipsEndpoint, useDualstackEndpoint) => {
const options = { useFipsEndpoint, useDualstackEndpoint };
expect(getHostnameFromVariants(undefined, options)).toBeUndefined();
});
});
});
14 changes: 14 additions & 0 deletions packages/config-resolver/src/regionInfo/getHostnameFromVariants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { EndpointVariant } from "./EndpointVariant";

export interface GetHostnameFromVariantsOptions {
useFipsEndpoint: boolean;
useDualstackEndpoint: boolean;
}

export const getHostnameFromVariants = (
variants: EndpointVariant[] = [],
{ useFipsEndpoint, useDualstackEndpoint }: GetHostnameFromVariantsOptions
) =>
variants.find(
({ tags }) => useFipsEndpoint === tags.includes("fips") && useDualstackEndpoint === tags.includes("dualstack")
)?.hostname;

This file was deleted.

This file was deleted.

56 changes: 44 additions & 12 deletions packages/config-resolver/src/regionInfo/getRegionInfo.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { getHostnameFromVariants } from "./getHostnameFromVariants";
import { getRegionInfo } from "./getRegionInfo";
import { getResolvedHostname } from "./getResolvedHostname";
import { getResolvedPartition } from "./getResolvedPartition";
import { getResolvedSigningRegion } from "./getResolvedSigningRegion";
import { PartitionHash } from "./PartitionHash";
import { RegionHash } from "./RegionHash";

jest.mock("./getHostnameFromVariants");
jest.mock("./getResolvedHostname");
jest.mock("./getResolvedPartition");
jest.mock("./getResolvedSigningRegion");
Expand Down Expand Up @@ -64,12 +66,14 @@ describe(getRegionInfo.name, () => {
});

beforeEach(() => {
(getHostnameFromVariants as jest.Mock).mockReturnValue(mockHostname);
(getResolvedHostname as jest.Mock).mockReturnValue(mockHostname);
(getResolvedPartition as jest.Mock).mockReturnValue(mockPartition);
(getResolvedSigningRegion as jest.Mock).mockReturnValue(undefined);
});

afterEach(() => {
expect(getHostnameFromVariants).toHaveBeenCalledTimes(2);
expect(getResolvedHostname).toHaveBeenCalledTimes(1);
expect(getResolvedPartition).toHaveBeenCalledTimes(1);
jest.clearAllMocks();
Expand All @@ -83,17 +87,22 @@ describe(getRegionInfo.name, () => {
const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash);
const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions);

const mockResolvedRegion = getMockResolvedRegion(regionCase);
const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname;
const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname;

(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname);
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname);

expect(getRegionInfo(mockRegion, mockGetRegionInfoOptions)).toEqual({
signingService: mockSigningService,
hostname: mockHostname,
partition: mockPartition,
});

const mockResolvedRegion = getMockResolvedRegion(regionCase);
expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, {
signingService: mockSigningService,
regionHostname: mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname,
partitionHostname: mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname,
regionHostname: mockRegionHostname,
partitionHostname: mockPartitionHostname,
});
expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions);
expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockRegion, {
Expand Down Expand Up @@ -133,6 +142,13 @@ describe(getRegionInfo.name, () => {
const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash);
const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions);

const mockResolvedRegion = getMockResolvedRegion(regionCase);
const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname;
const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname;

(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname);
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname);

const mockRegionHashWithSigningRegion = getMockRegionHashWithSigningRegion(
regionCase,
mockRegionHash,
Expand All @@ -148,11 +164,9 @@ describe(getRegionInfo.name, () => {
signingRegion: mockSigningRegion,
});

const mockResolvedRegion = getMockResolvedRegion(regionCase);
expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, {
signingService: mockSigningService,
regionHostname: mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname,
partitionHostname: mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname,
regionHostname: mockRegionHostname,
partitionHostname: mockPartitionHostname,
});
expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions);
expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockRegion, {
Expand Down Expand Up @@ -192,6 +206,13 @@ describe(getRegionInfo.name, () => {
const mockGetResolvedPartitionOptions = getMockResolvedPartitionOptions(mockPartitionHash);
const mockGetRegionInfoOptions = getMockRegionInfoOptions(mockRegionHash, mockGetResolvedPartitionOptions);

const mockResolvedRegion = getMockResolvedRegion(regionCase);
const mockRegionHostname = mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname;
const mockPartitionHostname = mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname;

(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockRegionHostname);
(getHostnameFromVariants as jest.Mock).mockReturnValueOnce(mockPartitionHostname);

const mockRegionHashWithSigningRegion = getMockRegionHashWithSigningService(
regionCase,
mockRegionHash,
Expand All @@ -206,11 +227,9 @@ describe(getRegionInfo.name, () => {
partition: mockPartition,
});

const mockResolvedRegion = getMockResolvedRegion(regionCase);
expect(getResolvedHostname).toHaveBeenCalledWith(mockResolvedRegion, {
signingService: mockSigningService,
regionHostname: mockGetRegionInfoOptions.regionHash[mockResolvedRegion]?.hostname,
partitionHostname: mockGetRegionInfoOptions.partitionHash[mockPartition]?.hostname,
regionHostname: mockRegionHostname,
partitionHostname: mockPartitionHostname,
});
expect(getResolvedPartition).toHaveBeenCalledWith(mockRegion, mockGetResolvedPartitionOptions);
expect(getResolvedSigningRegion).toHaveBeenCalledWith(mockRegion, {
Expand All @@ -219,4 +238,17 @@ describe(getRegionInfo.name, () => {
});
});
});

it("throws error if hostname is not defined", () => {
(getResolvedHostname as jest.Mock).mockReturnValueOnce(undefined);
const mockRegionHash = getMockRegionHash(RegionCase.REGION);
const mockPartitionHash = getMockPartitionHash(RegionCase.REGION);
expect(() => {
getRegionInfo(mockRegion, {
signingService: mockSigningService,
regionHash: mockRegionHash,
partitionHash: mockPartitionHash,
});
}).toThrow();
});
});
25 changes: 19 additions & 6 deletions packages/config-resolver/src/regionInfo/getRegionInfo.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
import { RegionInfo } from "@aws-sdk/types";

import { getHostnameFromVariants } from "./getHostnameFromVariants";
import { getResolvedHostname } from "./getResolvedHostname";
import { getResolvedPartition } from "./getResolvedPartition";
import { getResolvedSigningRegion } from "./getResolvedSigningRegion";
import { PartitionHash } from "./PartitionHash";
import { RegionHash } from "./RegionHash";

export interface GetRegionInfoOptions {
useFipsEndpoint?: boolean;
useDualstackEndpoint?: boolean;
signingService: string;
regionHash: RegionHash;
partitionHash: PartitionHash;
}

export const getRegionInfo = (
region: string,
{ signingService, regionHash, partitionHash }: GetRegionInfoOptions
{
useFipsEndpoint = false,
useDualstackEndpoint = false,
signingService,
regionHash,
partitionHash,
}: GetRegionInfoOptions
): RegionInfo => {
const partition = getResolvedPartition(region, { partitionHash });
const resolvedRegion = region in regionHash ? region : partitionHash[partition]?.endpoint ?? region;

const hostname = getResolvedHostname(resolvedRegion, {
signingService,
regionHostname: regionHash[resolvedRegion]?.hostname,
partitionHostname: partitionHash[partition]?.hostname,
});
const hostnameOptions = { useFipsEndpoint, useDualstackEndpoint };
const regionHostname = getHostnameFromVariants(regionHash[resolvedRegion]?.variants, hostnameOptions);
const partitionHostname = getHostnameFromVariants(partitionHash[partition]?.variants, hostnameOptions);
const hostname = getResolvedHostname(resolvedRegion, { regionHostname, partitionHostname });

if (hostname === undefined) {
throw new Error(`Endpoint resolution failed for: ${{ resolvedRegion, useFipsEndpoint, useDualstackEndpoint }}`);
}

const signingRegion = getResolvedSigningRegion(region, {
hostname,
signingRegion: regionHash[resolvedRegion]?.signingRegion,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,40 +1,30 @@
import { getHostnameTemplate } from "./getHostnameTemplate";
import { getResolvedHostname } from "./getResolvedHostname";

jest.mock("./getHostnameTemplate");

describe(getResolvedHostname.name, () => {
const mockSigningService = "mockSigningService";
const mockRegion = "mockRegion";
const mockHostname = "{region}.mockHostname.com";

afterEach(() => {
jest.clearAllMocks();
});

it("returns hostname if available in regionHash", () => {
it("returns hostname if available in regionHostname", () => {
expect(
getResolvedHostname(mockRegion, {
signingService: mockSigningService,
regionHostname: mockHostname,
})
).toBe(mockHostname);
expect(getHostnameTemplate).not.toHaveBeenCalled();
});

it("returns hostname from hostname template when not available in regionHash", () => {
(getHostnameTemplate as jest.Mock).mockReturnValue(mockHostname);

it("returns hostname from partitionHostname when not available in partitionHostname", () => {
expect(
getResolvedHostname(mockRegion, {
signingService: mockSigningService,
partitionHostname: mockHostname,
})
).toBe(mockHostname.replace("{region}", mockRegion));
});

expect(getHostnameTemplate).toHaveBeenCalledTimes(1);
expect(getHostnameTemplate).toHaveBeenCalledWith(mockSigningService, {
partitionHostname: mockHostname,
});
it("returns undefined not available in either regionHostname or partitionHostname", () => {
expect(getResolvedHostname(mockRegion, {})).toBeUndefined();
});
});
12 changes: 7 additions & 5 deletions packages/config-resolver/src/regionInfo/getResolvedHostname.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { getHostnameTemplate } from "./getHostnameTemplate";

export interface GetResolvedHostnameOptions {
signingService: string;
regionHostname?: string;
partitionHostname?: string;
}

export const getResolvedHostname = (
resolvedRegion: string,
{ signingService, regionHostname, partitionHostname }: GetResolvedHostnameOptions
) => regionHostname ?? getHostnameTemplate(signingService, { partitionHostname }).replace("{region}", resolvedRegion);
{ regionHostname, partitionHostname }: GetResolvedHostnameOptions
): string | undefined =>
regionHostname
? regionHostname
: partitionHostname
? partitionHostname.replace("{region}", resolvedRegion)
: undefined;
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ const getClientPackageName = (sdkId: string) =>
.map((word) => word.toLowerCase())
.join("-")}`;

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

0 comments on commit 447580b

Please sign in to comment.