From f64c4c2db8f2590e10347dd52717370b444da156 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:31:11 -0700 Subject: [PATCH] Read service specific endpoints from env/config (#1014) --- .changeset/cool-mugs-design.md | 5 ++ packages/middleware-endpoint/package.json | 7 ++ .../adaptors/getEndpointFromConfig.browser.ts | 1 + .../src/adaptors/getEndpointFromConfig.ts | 5 ++ .../adaptors/getEndpointFromInstructions.ts | 9 ++ .../src/adaptors/getEndpointUrlConfig.spec.ts | 89 +++++++++++++++++++ .../src/adaptors/getEndpointUrlConfig.ts | 41 +++++++++ .../src/resolveEndpointConfig.ts | 6 ++ yarn.lock | 1 + 9 files changed, 164 insertions(+) create mode 100644 .changeset/cool-mugs-design.md create mode 100644 packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.browser.ts create mode 100644 packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.ts create mode 100644 packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.spec.ts create mode 100644 packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.ts diff --git a/.changeset/cool-mugs-design.md b/.changeset/cool-mugs-design.md new file mode 100644 index 00000000000..dcd865b025f --- /dev/null +++ b/.changeset/cool-mugs-design.md @@ -0,0 +1,5 @@ +--- +"@smithy/middleware-endpoint": minor +--- + +Read service specific endpoints from env/config diff --git a/packages/middleware-endpoint/package.json b/packages/middleware-endpoint/package.json index 4b921b7b712..2389c8e3f87 100644 --- a/packages/middleware-endpoint/package.json +++ b/packages/middleware-endpoint/package.json @@ -24,6 +24,7 @@ "license": "Apache-2.0", "dependencies": { "@smithy/middleware-serde": "workspace:^", + "@smithy/node-config-provider": "workspace:^", "@smithy/types": "workspace:^", "@smithy/url-parser": "workspace:^", "@smithy/util-middleware": "workspace:^", @@ -49,6 +50,12 @@ "files": [ "dist-*/**" ], + "browser": { + "./dist-es/adaptors/getEndpointFromConfig": "./dist-es/adaptors/getEndpointFromConfig.browser" + }, + "react-native": { + "./dist-es/adaptors/getEndpointFromConfig": "./dist-es/adaptors/getEndpointFromConfig.browser" + }, "homepage": "https://github.com/awslabs/smithy-typescript/tree/main/packages/middleware-endpoint", "repository": { "type": "git", diff --git a/packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.browser.ts b/packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.browser.ts new file mode 100644 index 00000000000..4d3a6450862 --- /dev/null +++ b/packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.browser.ts @@ -0,0 +1 @@ +export const getEndpointFromConfig = async (serviceId: string) => undefined; diff --git a/packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.ts b/packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.ts new file mode 100644 index 00000000000..903f66fbb21 --- /dev/null +++ b/packages/middleware-endpoint/src/adaptors/getEndpointFromConfig.ts @@ -0,0 +1,5 @@ +import { loadConfig } from "@smithy/node-config-provider"; + +import { getEndpointUrlConfig } from "./getEndpointUrlConfig"; + +export const getEndpointFromConfig = async (serviceId: string) => loadConfig(getEndpointUrlConfig(serviceId))(); diff --git a/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts b/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts index 0feb4965d1a..3dcbd5466c3 100644 --- a/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts +++ b/packages/middleware-endpoint/src/adaptors/getEndpointFromInstructions.ts @@ -4,6 +4,8 @@ import { EndpointResolvedConfig } from "../resolveEndpointConfig"; import { resolveParamsForS3 } from "../service-customizations"; import { EndpointParameterInstructions } from "../types"; import { createConfigValueProvider } from "./createConfigValueProvider"; +import { getEndpointFromConfig } from "./getEndpointFromConfig"; +import { toEndpointV1 } from "./toEndpointV1"; /** * @internal @@ -36,6 +38,13 @@ export const getEndpointFromInstructions = async < clientConfig: Partial> & Config, context?: HandlerExecutionContext ): Promise => { + if (!clientConfig.endpoint) { + const endpointFromConfig = await getEndpointFromConfig(clientConfig.serviceId || ""); + if (endpointFromConfig) { + clientConfig.endpoint = () => Promise.resolve(toEndpointV1(endpointFromConfig)); + } + } + const endpointParams = await resolveParams(commandInput, instructionsSupplier, clientConfig); if (typeof clientConfig.endpointProvider !== "function") { diff --git a/packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.spec.ts b/packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.spec.ts new file mode 100644 index 00000000000..cf69f78f759 --- /dev/null +++ b/packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.spec.ts @@ -0,0 +1,89 @@ +import { CONFIG_PREFIX_SEPARATOR } from "@smithy/shared-ini-file-loader"; + +import { getEndpointUrlConfig } from "./getEndpointUrlConfig"; + +const ENV_ENDPOINT_URL = "AWS_ENDPOINT_URL"; +const CONFIG_ENDPOINT_URL = "endpoint_url"; + +describe(getEndpointUrlConfig.name, () => { + const serviceId = "foo"; + const endpointUrlConfig = getEndpointUrlConfig(serviceId); + + const mockEndpoint = "https://mock-endpoint.com"; + const ORIGINAL_ENV = process.env; + + beforeEach(() => { + process.env = {}; + }); + + afterEach(() => { + process.env = ORIGINAL_ENV; + }); + + describe("environmentVariableSelector", () => { + beforeEach(() => { + process.env[ENV_ENDPOINT_URL] = mockEndpoint; + }); + + it.each([ + ["foo", `${ENV_ENDPOINT_URL}_FOO`], + ["foobar", `${ENV_ENDPOINT_URL}_FOOBAR`], + ["foo bar", `${ENV_ENDPOINT_URL}_FOO_BAR`], + ])("returns endpoint for '%s' from environment variable %s", (serviceId, envKey) => { + const serviceMockEndpoint = `${mockEndpoint}/${envKey}`; + process.env[envKey] = serviceMockEndpoint; + + const endpointUrlConfig = getEndpointUrlConfig(serviceId); + expect(endpointUrlConfig.environmentVariableSelector(process.env)).toEqual(serviceMockEndpoint); + }); + + it(`returns endpoint from environment variable ${ENV_ENDPOINT_URL}`, () => { + expect(endpointUrlConfig.environmentVariableSelector(process.env)).toEqual(mockEndpoint); + }); + + it("returns undefined, if endpoint not available in environment variables", () => { + process.env[ENV_ENDPOINT_URL] = undefined; + expect(endpointUrlConfig.environmentVariableSelector(process.env)).toBeUndefined(); + }); + }); + + describe("configFileSelector", () => { + it.each([ + ["foo", "foo"], + ["foobar", "foobar"], + ["foo bar", "foo_bar"], + ])("returns endpoint for '%s' from config file '%s'", (serviceId, serviceConfigId) => { + const servicesSectionPrefix = "services"; + const servicesSectionName = "config-services"; + const serviceMockEndpoint = `${mockEndpoint}/${serviceConfigId}`; + + const profile = { + [servicesSectionPrefix]: servicesSectionName, + [CONFIG_ENDPOINT_URL]: mockEndpoint, + }; + + const config = { + [serviceId]: profile, + [[servicesSectionPrefix, servicesSectionName].join(CONFIG_PREFIX_SEPARATOR)]: { + [[serviceConfigId, CONFIG_ENDPOINT_URL].join(CONFIG_PREFIX_SEPARATOR)]: serviceMockEndpoint, + }, + }; + + const endpointUrlConfig = getEndpointUrlConfig(serviceId); + expect(endpointUrlConfig.configFileSelector(profile, config)).toEqual(serviceMockEndpoint); + }); + + it(`returns endpoint from config ${CONFIG_ENDPOINT_URL}`, () => { + const profile = { [CONFIG_ENDPOINT_URL]: mockEndpoint }; + expect(endpointUrlConfig.configFileSelector(profile)).toEqual(mockEndpoint); + }); + + it("returns undefined, if endpoint not available in config", () => { + expect(endpointUrlConfig.environmentVariableSelector({})).toBeUndefined(); + }); + }); + + it("returns undefined by default", () => { + expect(endpointUrlConfig.default).toBeUndefined(); + }); +}); diff --git a/packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.ts b/packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.ts new file mode 100644 index 00000000000..c045529ad6e --- /dev/null +++ b/packages/middleware-endpoint/src/adaptors/getEndpointUrlConfig.ts @@ -0,0 +1,41 @@ +import { LoadedConfigSelectors } from "@smithy/node-config-provider"; +import { CONFIG_PREFIX_SEPARATOR } from "@smithy/shared-ini-file-loader"; + +const ENV_ENDPOINT_URL = "AWS_ENDPOINT_URL"; +const CONFIG_ENDPOINT_URL = "endpoint_url"; + +export const getEndpointUrlConfig = (serviceId: string): LoadedConfigSelectors => ({ + environmentVariableSelector: (env) => { + // The value provided by a service-specific environment variable. + const serviceSuffixParts = serviceId.split(" ").map((w) => w.toUpperCase()); + const serviceEndpointUrl = env[[ENV_ENDPOINT_URL, ...serviceSuffixParts].join("_")]; + if (serviceEndpointUrl) return serviceEndpointUrl; + + // The value provided by the global endpoint environment variable. + const endpointUrl = env[ENV_ENDPOINT_URL]; + if (endpointUrl) return endpointUrl; + + return undefined; + }, + + configFileSelector: (profile, config) => { + // The value provided by a service-specific parameter from a services definition section + if (config && profile.services) { + const servicesSection = config[["services", profile.services].join(CONFIG_PREFIX_SEPARATOR)]; + if (servicesSection) { + const servicePrefixParts = serviceId.split(" ").map((w) => w.toLowerCase()); + const endpointUrl = + servicesSection[[servicePrefixParts.join("_"), CONFIG_ENDPOINT_URL].join(CONFIG_PREFIX_SEPARATOR)]; + if (endpointUrl) return endpointUrl; + } + } + + // The value provided by the global parameter from a profile in the shared configuration file. + const endpointUrl = profile[CONFIG_ENDPOINT_URL]; + if (endpointUrl) return endpointUrl; + + return undefined; + }, + + default: undefined, +}); diff --git a/packages/middleware-endpoint/src/resolveEndpointConfig.ts b/packages/middleware-endpoint/src/resolveEndpointConfig.ts index c786f163383..3bbd3368527 100644 --- a/packages/middleware-endpoint/src/resolveEndpointConfig.ts +++ b/packages/middleware-endpoint/src/resolveEndpointConfig.ts @@ -89,6 +89,12 @@ export interface EndpointResolvedConfig; + + /** + * Unique service identifier. + * @internal + */ + serviceId?: string; } /** diff --git a/yarn.lock b/yarn.lock index c338f9b197c..2fcc014cd3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2071,6 +2071,7 @@ __metadata: resolution: "@smithy/middleware-endpoint@workspace:packages/middleware-endpoint" dependencies: "@smithy/middleware-serde": "workspace:^" + "@smithy/node-config-provider": "workspace:^" "@smithy/types": "workspace:^" "@smithy/url-parser": "workspace:^" "@smithy/util-middleware": "workspace:^"