Skip to content

feat(config-resolver): resolve hostname from variants #2980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Nov 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
});
});
});
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