Skip to content
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

Read service specific endpoints from env/config #1014

Merged
merged 7 commits into from
Oct 10, 2023
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
5 changes: 5 additions & 0 deletions .changeset/cool-mugs-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/middleware-endpoint": minor
---

Read service specific endpoints from env/config
7 changes: 7 additions & 0 deletions packages/middleware-endpoint/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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:^",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const getEndpointFromConfig = async (serviceId: string) => undefined;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { loadConfig } from "@smithy/node-config-provider";

import { getEndpointUrlConfig } from "./getEndpointUrlConfig";

export const getEndpointFromConfig = async (serviceId: string) => loadConfig(getEndpointUrlConfig(serviceId))();
trivikr marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -36,6 +38,13 @@ export const getEndpointFromInstructions = async <
clientConfig: Partial<EndpointResolvedConfig<T>> & Config,
context?: HandlerExecutionContext
): Promise<EndpointV2> => {
if (!clientConfig.endpoint) {
const endpointFromConfig = await getEndpointFromConfig(clientConfig.serviceId || "");
if (endpointFromConfig) {
clientConfig.endpoint = () => Promise.resolve(toEndpointV1(endpointFromConfig));
}
}
trivikr marked this conversation as resolved.
Show resolved Hide resolved

const endpointParams = await resolveParams(commandInput, instructionsSupplier, clientConfig);

if (typeof clientConfig.endpointProvider !== "function") {
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
});
});
Original file line number Diff line number Diff line change
@@ -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<string | undefined> => ({
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,
});
6 changes: 6 additions & 0 deletions packages/middleware-endpoint/src/resolveEndpointConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ export interface EndpointResolvedConfig<T extends EndpointParameters = EndpointP
* Resolved value for input {@link EndpointsInputConfig.useFipsEndpoint}
*/
useFipsEndpoint: Provider<boolean>;

/**
* Unique service identifier.
* @internal
*/
serviceId?: string;
}

/**
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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:^"
Expand Down
Loading