diff --git a/packages/util-endpoints/package.json b/packages/util-endpoints/package.json index 59afdc133abb..8b736436643c 100644 --- a/packages/util-endpoints/package.json +++ b/packages/util-endpoints/package.json @@ -23,6 +23,7 @@ "license": "Apache-2.0", "dependencies": { "@aws-sdk/types": "*", + "@smithy/node-config-provider": "^2.0.13", "tslib": "^2.5.0" }, "engines": { diff --git a/packages/util-endpoints/src/getEndpointUrlConfig.spec.ts b/packages/util-endpoints/src/getEndpointUrlConfig.spec.ts new file mode 100644 index 000000000000..145551650886 --- /dev/null +++ b/packages/util-endpoints/src/getEndpointUrlConfig.spec.ts @@ -0,0 +1,91 @@ +import { getEndpointUrlConfig } from "./getEndpointUrlConfig"; + +const ENV_ENDPOINT_URL = "AWS_ENDPOINT_URL"; +const CONFIG_ENDPOINT_URL = "endpoint_url"; + +describe(getEndpointUrlConfig.name, () => { + const serviceId = "mockServiceId"; + 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", () => { + const profile = { [CONFIG_ENDPOINT_URL]: mockEndpoint }; + + // ToDo: Enable once support for services section is added. + it.skip.each([ + ["foo", "foo"], + ["foobar", "foobar"], + ["foo bar", "foo_bar"], + ])("returns endpoint for '%s' from config file '%s'", (serviceId, serviceConfigId) => { + const serviceMockEndpoint = `${mockEndpoint}/${serviceConfigId}`; + const serviceSectionName = `services ${serviceConfigId}_dev`; + + const profileWithServices = { + ...profile, + services: serviceSectionName, + }; + const parsedIni = { + profileName: profileWithServices, + [serviceSectionName]: { + [serviceConfigId]: { + [CONFIG_ENDPOINT_URL]: serviceMockEndpoint, + }, + }, + }; + + // @ts-ignore + expect(endpointUrlConfig.environmentVariableSelector(profileWithServices, parsedIni)).toEqual( + serviceMockEndpoint + ); + }); + + it("returns endpoint from config file, if available", () => { + 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/util-endpoints/src/getEndpointUrlConfig.ts b/packages/util-endpoints/src/getEndpointUrlConfig.ts new file mode 100644 index 000000000000..c0f73e7c37fa --- /dev/null +++ b/packages/util-endpoints/src/getEndpointUrlConfig.ts @@ -0,0 +1,36 @@ +import { IniSection } from "@aws-sdk/types"; +import { LoadedConfigSelectors } from "@smithy/node-config-provider"; + +const ENV_ENDPOINT_URL = "AWS_ENDPOINT_URL"; +const CONFIG_ENDPOINT_URL = "endpoint_url"; + +export const getEndpointUrlConfig = (serviceId: string): LoadedConfigSelectors => ({ + environmentVariableSelector: (env: NodeJS.ProcessEnv) => { + // The value provided by a service-specific environment variable. + const serviceEndpointUrlSections = [ENV_ENDPOINT_URL, ...serviceId.split(" ").map((w) => w.toUpperCase())]; + const serviceEndpointUrl = env[serviceEndpointUrlSections.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: IniSection) => { + // The value provided by a service-specific parameter from a services definition section + // referenced in a profile in the shared configuration file. + + // ToDo: profile is selected one. It does not have access to other 'services' section. + // The configFileSelector interface needs to be modified to pass ParsedIniData as optional second parameter. + + // 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, +});