From dca536c2322855ea563e3da14168cb976d532289 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 00:57:53 +0000 Subject: [PATCH 01/48] feat(middleware-endpoint-discovery): add resolveEndpointDiscoveryClientConfig --- .../middleware-endpoint-discovery/LICENSE | 201 ++++++++++++++++++ .../middleware-endpoint-discovery/README.md | 4 + .../jest.config.js | 5 + .../package.json | 45 ++++ .../src/index.ts | 1 + .../resolveEndpointDiscoveryClientConfig.ts | 18 ++ .../tsconfig.cjs.json | 10 + .../tsconfig.es.json | 11 + yarn.lock | 19 +- 9 files changed, 307 insertions(+), 7 deletions(-) create mode 100644 packages/middleware-endpoint-discovery/LICENSE create mode 100644 packages/middleware-endpoint-discovery/README.md create mode 100644 packages/middleware-endpoint-discovery/jest.config.js create mode 100644 packages/middleware-endpoint-discovery/package.json create mode 100644 packages/middleware-endpoint-discovery/src/index.ts create mode 100644 packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts create mode 100644 packages/middleware-endpoint-discovery/tsconfig.cjs.json create mode 100644 packages/middleware-endpoint-discovery/tsconfig.es.json diff --git a/packages/middleware-endpoint-discovery/LICENSE b/packages/middleware-endpoint-discovery/LICENSE new file mode 100644 index 000000000000..dd65ae06be7a --- /dev/null +++ b/packages/middleware-endpoint-discovery/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2018-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/packages/middleware-endpoint-discovery/README.md b/packages/middleware-endpoint-discovery/README.md new file mode 100644 index 000000000000..4a50903c244f --- /dev/null +++ b/packages/middleware-endpoint-discovery/README.md @@ -0,0 +1,4 @@ +# @aws-sdk/middleware-endpoint-discovery + +[![NPM version](https://img.shields.io/npm/v/@aws-sdk/middleware-endpoint-discovery/latest.svg)](https://www.npmjs.com/package/@aws-sdk/middleware-endpoint-discovery) +[![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/middleware-endpoint-discovery.svg)](https://www.npmjs.com/package/@aws-sdk/middleware-endpoint-discovery) diff --git a/packages/middleware-endpoint-discovery/jest.config.js b/packages/middleware-endpoint-discovery/jest.config.js new file mode 100644 index 000000000000..a8d1c2e49912 --- /dev/null +++ b/packages/middleware-endpoint-discovery/jest.config.js @@ -0,0 +1,5 @@ +const base = require("../../jest.config.base.js"); + +module.exports = { + ...base, +}; diff --git a/packages/middleware-endpoint-discovery/package.json b/packages/middleware-endpoint-discovery/package.json new file mode 100644 index 000000000000..5a2939983ee5 --- /dev/null +++ b/packages/middleware-endpoint-discovery/package.json @@ -0,0 +1,45 @@ +{ + "name": "@aws-sdk/middleware-endpoint-discovery", + "version": "3.0.0", + "scripts": { + "prepublishOnly": "yarn build && downlevel-dts dist/types dist/types/ts3.4", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:es": "tsc -p tsconfig.es.json", + "build": "yarn build:es && yarn build:cjs", + "test": "jest" + }, + "main": "./dist/cjs/index.js", + "module": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "author": { + "name": "AWS SDK for JavaScript Team", + "url": "https://aws.amazon.com/javascript/" + }, + "license": "Apache-2.0", + "devDependencies": { + "@types/jest": "^26.0.4", + "@types/lru-cache": "5.1.0", + "jest": "^26.1.0", + "typescript": "~4.2.4" + }, + "dependencies": { + "lru-cache": "6.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "typesVersions": { + "<4.0": { + "types/*": [ + "types/ts3.4/*" + ] + } + }, + "homepage": "https://github.com/aws/aws-sdk-js-v3/tree/main/packages/middleware-endpoint-discovery", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-js-v3.git", + "directory": "packages/middleware-endpoint-discovery" + } +} diff --git a/packages/middleware-endpoint-discovery/src/index.ts b/packages/middleware-endpoint-discovery/src/index.ts new file mode 100644 index 000000000000..8b17325d9059 --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/index.ts @@ -0,0 +1 @@ +export * from "./resolveEndpointDiscoveryClientConfig"; diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts new file mode 100644 index 000000000000..5d1f4d3ee8d0 --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -0,0 +1,18 @@ +import { Client } from "@aws-sdk/types"; +import LRUCache from "lru-cache"; + +export interface EndpointDiscoveryClientInputConfig {} + +interface PreviouslyResolved {} + +export interface EndpointDiscoveryClientResolvedConfig { + client?: Client; + endpointCache: LRUCache; +} + +export const resolveEndpointDiscoveryClientConfig = ( + input: T & PreviouslyResolved & EndpointDiscoveryClientInputConfig +): T & EndpointDiscoveryClientResolvedConfig => ({ + ...input, + endpointCache: new LRUCache(), +}); diff --git a/packages/middleware-endpoint-discovery/tsconfig.cjs.json b/packages/middleware-endpoint-discovery/tsconfig.cjs.json new file mode 100644 index 000000000000..67f09ddd4138 --- /dev/null +++ b/packages/middleware-endpoint-discovery/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/cjs", + "baseUrl": "." + }, + "extends": "../../tsconfig.cjs.json", + "include": ["src/"] +} diff --git a/packages/middleware-endpoint-discovery/tsconfig.es.json b/packages/middleware-endpoint-discovery/tsconfig.es.json new file mode 100644 index 000000000000..6adc0089d91d --- /dev/null +++ b/packages/middleware-endpoint-discovery/tsconfig.es.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "lib": ["es5", "es2015.promise", "es2015.collection", "es2015.iterable", "es2015.symbol.wellknown"], + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/es", + "baseUrl": "." + }, + "extends": "../../tsconfig.es.json", + "include": ["src/"] +} diff --git a/yarn.lock b/yarn.lock index d32a6024eb00..ddbb74fb9a09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1729,6 +1729,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== +"@types/lru-cache@5.1.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" + integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== + "@types/minimatch@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" @@ -7854,6 +7859,13 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= +lru-cache@6.0.0, lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7861,13 +7873,6 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - ltgt@^2.1.2: version "2.2.1" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" From ad96509ecf44b0eab848bc7cdfc13120099fdd75 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 01:32:00 +0000 Subject: [PATCH 02/48] chore(client-timestream-query): call resolveEndpointDiscoveryClientConfig 'super' must be called before accessing 'this' in the constructor of a derived class. That's why client property is populated after calling super. refs: https://www.typescriptlang.org/docs/handbook/2/classes.html#super-calls --- .../TimestreamQueryClient.ts | 13 +++++++++++-- clients/client-timestream-query/package.json | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index 7fdae1c87d61..d6e41bcaf39a 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -17,6 +17,11 @@ import { getHostHeaderPlugin, resolveHostHeaderConfig, } from "@aws-sdk/middleware-host-header"; +import { + EndpointDiscoveryClientInputConfig, + EndpointDiscoveryClientResolvedConfig, + resolveEndpointDiscoveryClientConfig, +} from "@aws-sdk/middleware-endpoint-discovery"; import { getLoggerPlugin } from "@aws-sdk/middleware-logger"; import { RetryInputConfig, RetryResolvedConfig, getRetryPlugin, resolveRetryConfig } from "@aws-sdk/middleware-retry"; import { @@ -155,6 +160,7 @@ export interface ClientDefaults extends Partial<__SmithyResolvedConfiguration<__ type TimestreamQueryClientConfigType = Partial<__SmithyConfiguration<__HttpHandlerOptions>> & ClientDefaults & RegionInputConfig & + EndpointDiscoveryClientInputConfig & EndpointsInputConfig & RetryInputConfig & HostHeaderInputConfig & @@ -168,6 +174,7 @@ export interface TimestreamQueryClientConfig extends TimestreamQueryClientConfig type TimestreamQueryClientResolvedConfigType = __SmithyResolvedConfiguration<__HttpHandlerOptions> & Required & RegionResolvedConfig & + EndpointDiscoveryClientResolvedConfig & EndpointsResolvedConfig & RetryResolvedConfig & HostHeaderResolvedConfig & @@ -205,8 +212,10 @@ export class TimestreamQueryClient extends __Client< let _config_4 = resolveHostHeaderConfig(_config_3); let _config_5 = resolveAwsAuthConfig(_config_4); let _config_6 = resolveUserAgentConfig(_config_5); - super(_config_6); - this.config = _config_6; + let _config_7 = resolveEndpointDiscoveryClientConfig(_config_6); + super(_config_7); + _config_7.client = this; + this.config = _config_7; this.middlewareStack.use(getRetryPlugin(this.config)); this.middlewareStack.use(getContentLengthPlugin(this.config)); this.middlewareStack.use(getHostHeaderPlugin(this.config)); diff --git a/clients/client-timestream-query/package.json b/clients/client-timestream-query/package.json index d126de15c0aa..44a36c4b938c 100644 --- a/clients/client-timestream-query/package.json +++ b/clients/client-timestream-query/package.json @@ -34,6 +34,7 @@ "@aws-sdk/hash-node": "3.13.1", "@aws-sdk/invalid-dependency": "3.13.1", "@aws-sdk/middleware-content-length": "3.13.1", + "@aws-sdk/middleware-endpoint-discovery": "3.0.0", "@aws-sdk/middleware-host-header": "3.13.1", "@aws-sdk/middleware-logger": "3.13.1", "@aws-sdk/middleware-retry": "3.13.1", From e6d00db39d69ebc8add3bdd0fcb4ee2657294306 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 05:55:48 +0000 Subject: [PATCH 03/48] feat(middleware-endpoint-discovery): add getEndpointDiscoveryCommandPlugin --- .../package.json | 4 + .../src/getCacheKey.ts | 21 +++++ .../src/getEndpointDiscoveryCommandPlugin.ts | 80 +++++++++++++++++++ .../src/index.ts | 1 + .../src/updateDiscoveredEndpointInCache.ts | 56 +++++++++++++ 5 files changed, 162 insertions(+) create mode 100644 packages/middleware-endpoint-discovery/src/getCacheKey.ts create mode 100644 packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts create mode 100644 packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts diff --git a/packages/middleware-endpoint-discovery/package.json b/packages/middleware-endpoint-discovery/package.json index 5a2939983ee5..8b8c7bc4289b 100644 --- a/packages/middleware-endpoint-discovery/package.json +++ b/packages/middleware-endpoint-discovery/package.json @@ -23,6 +23,10 @@ "typescript": "~4.2.4" }, "dependencies": { + "@aws-sdk/config-resolver": "3.14.0", + "@aws-sdk/middleware-signing": "3.13.1", + "@aws-sdk/protocol-http": "3.13.1", + "@aws-sdk/types": "3.13.1", "lru-cache": "6.0.0", "tslib": "^2.0.0" }, diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts new file mode 100644 index 000000000000..9013ce50ee5c --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -0,0 +1,21 @@ +import { RegionResolvedConfig } from "@aws-sdk/config-resolver"; +import { AwsAuthResolvedConfig } from "@aws-sdk/middleware-signing"; + +/** + * Generate key to index the endpoints in the cache + */ +export const getCacheKey = async ( + commandName: string, + config: AwsAuthResolvedConfig & RegionResolvedConfig, + getEndpointDiscoveryId: () => string | undefined +) => { + const region = await config.region(); + const { accessKeyId } = await config.credentials(); + const identifiers = getEndpointDiscoveryId ? getEndpointDiscoveryId() : undefined; + return { + commandName, + ...(region && { region }), + ...(accessKeyId && { accessKeyId }), + ...(identifiers && { identifiers }), + }; +}; diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts new file mode 100644 index 000000000000..775222cfc103 --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -0,0 +1,80 @@ +import { HttpRequest } from "@aws-sdk/protocol-http"; +import { + Command, + FinalizeHandler, + FinalizeHandlerArguments, + FinalizeHandlerOutput, + FinalizeRequestHandlerOptions, + HandlerExecutionContext, + MetadataBearer, + Pluggable, +} from "@aws-sdk/types"; + +import { getCacheKey } from "./getCacheKey"; +import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; +import { updateDiscoveredEndpointInCache } from "./updateDiscoveredEndpointInCache"; + +export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = { + name: "endpointDiscoveryMiddleware", + step: "finalizeRequest", + tags: ["ENDPOINT_DISCOVERY"], + override: true, +}; + +export type EndpointDiscoveryMiddlewareConfig = { + isDiscoveredEndpointRequired: boolean; + getEndpointDiscoveryId: () => string | undefined; + discoveryEndpointCommandCtor: new (comandConfig: any) => Command; +}; + +export const endpointDiscoveryMiddleware = ( + config: EndpointDiscoveryClientResolvedConfig, + middlewareConfig: EndpointDiscoveryMiddlewareConfig +) => ( + next: FinalizeHandler, + context: HandlerExecutionContext +): FinalizeHandler => async ( + args: FinalizeHandlerArguments +): Promise> => { + const { isDiscoveredEndpointRequired, discoveryEndpointCommandCtor, getEndpointDiscoveryId } = middlewareConfig; + const { commandName } = context; + + if (isDiscoveredEndpointRequired === true) { + // call await on Endpoint Discovery API utility so that function blocks + // till discovered endpoint is updated in cache + await updateDiscoveredEndpointInCache(config, { + commandName, + discoveryEndpointCommandCtor, + getEndpointDiscoveryId, + }); + } else if (isDiscoveredEndpointRequired === false) { + // Do not call await await on Endpoint Discovery API utility so that function + // does not block, the command will use discovered endpoint, if available. + updateDiscoveredEndpointInCache(config, { + commandName, + discoveryEndpointCommandCtor, + getEndpointDiscoveryId, + }); + } + + const { client } = config; + const { request } = args; + const cacheKey = getCacheKey(commandName, client?.config, getEndpointDiscoveryId); + if (cacheKey && HttpRequest.isInstance(request)) { + const endpoints = client?.config.endpointCache.get(cacheKey); + if (endpoints && endpoints.length > 0) { + request.hostname = endpoints[0].Address; + } + } + + return next(args); +}; + +export const getEndpointDiscoveryCommandPlugin = ( + pluginConfig: EndpointDiscoveryClientResolvedConfig, + middlewareConfig: EndpointDiscoveryMiddlewareConfig +): Pluggable => ({ + applyToStack: (commandStack) => { + commandStack.add(endpointDiscoveryMiddleware(pluginConfig, middlewareConfig), endpointDiscoveryMiddlewareOptions); + }, +}); diff --git a/packages/middleware-endpoint-discovery/src/index.ts b/packages/middleware-endpoint-discovery/src/index.ts index 8b17325d9059..5252da98d36e 100644 --- a/packages/middleware-endpoint-discovery/src/index.ts +++ b/packages/middleware-endpoint-discovery/src/index.ts @@ -1 +1,2 @@ export * from "./resolveEndpointDiscoveryClientConfig"; +export * from "./getEndpointDiscoveryCommandPlugin"; diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts new file mode 100644 index 000000000000..7214af568e7c --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -0,0 +1,56 @@ +import { Command } from "@aws-sdk/types"; + +import { getCacheKey } from "./getCacheKey"; +import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; + +export type updateDiscoveredEndpointInCacheOptions = { + commandName: string; + discoveryEndpointCommandCtor: new (comandConfig: any) => Command; + getEndpointDiscoveryId: () => string | undefined; +}; + +export const updateDiscoveredEndpointInCache = async ( + config: EndpointDiscoveryClientResolvedConfig, + options: updateDiscoveredEndpointInCacheOptions +) => { + const { client } = config; + const { endpointCache } = client?.config; + + const { commandName, getEndpointDiscoveryId } = options; + const cacheKey = getCacheKey(commandName, client?.config, getEndpointDiscoveryId); + + const endpoints = endpointCache.get(cacheKey); + if (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { + // endpoint operation is being made but response not yet received + // or endpoint operation just failed in 1 minute. + return; + } else if (endpoints && endpoints.length > 0) { + // Endpoint record is present in cache. + return; + } else { + // put in a placeholder for endpoints already requested, prevent + // too much in-flight calls. + endpointCache.put(cacheKey, [ + { + Address: "", + CachePeriodInMinutes: 1, + }, + ]); + + try { + const command = new options.discoveryEndpointCommandCtor({ + Operation: commandName.substr(0, commandName.length - 7), // strip "Command" + Identifiers: getEndpointDiscoveryId ? getEndpointDiscoveryId() : undefined, + }); + const { Endpoints } = await client?.send(command); + endpointCache.put(cacheKey, Endpoints); + } catch (error) { + endpointCache.put(cacheKey, [ + { + Address: "", + CachePeriodInMinutes: 1, // Not to make more endpoint operation in next 1 minute + }, + ]); + } + } +}; From 72cb4d2cc0f811572a569818f68826503d1fadb3 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 06:01:42 +0000 Subject: [PATCH 04/48] chore(client-timestream-query): call endpoint discovery from Query command --- .../client-timestream-query/commands/QueryCommand.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clients/client-timestream-query/commands/QueryCommand.ts b/clients/client-timestream-query/commands/QueryCommand.ts index e7308231663d..813438bda475 100644 --- a/clients/client-timestream-query/commands/QueryCommand.ts +++ b/clients/client-timestream-query/commands/QueryCommand.ts @@ -13,6 +13,8 @@ import { MetadataBearer as __MetadataBearer, SerdeContext as __SerdeContext, } from "@aws-sdk/types"; +import { getEndpointDiscoveryCommandPlugin } from "@aws-sdk/middleware-endpoint-discovery"; +import { DescribeEndpointsCommand } from "./DescribeEndpointsCommand"; export interface QueryCommandInput extends QueryRequest {} export interface QueryCommandOutput extends QueryResponse, __MetadataBearer {} @@ -55,6 +57,14 @@ export class QueryCommand extends $Command { this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize)); + this.middlewareStack.use( + getEndpointDiscoveryCommandPlugin(configuration, { + isDiscoveredEndpointRequired: true, + // @ts-ignore + getEndpointDiscoveryId: undefined, + discoveryEndpointCommandCtor: DescribeEndpointsCommand, + }) + ); const stack = clientStack.concat(this.middlewareStack); From 3649ae97149d28efa349f98d6cdb858c6106530c Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 06:08:30 +0000 Subject: [PATCH 05/48] fix: await getCacheKey --- .../src/getEndpointDiscoveryCommandPlugin.ts | 2 +- .../src/updateDiscoveredEndpointInCache.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 775222cfc103..f0f9428d3b68 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -59,7 +59,7 @@ export const endpointDiscoveryMiddleware = ( const { client } = config; const { request } = args; - const cacheKey = getCacheKey(commandName, client?.config, getEndpointDiscoveryId); + const cacheKey = await getCacheKey(commandName, client?.config, getEndpointDiscoveryId); if (cacheKey && HttpRequest.isInstance(request)) { const endpoints = client?.config.endpointCache.get(cacheKey); if (endpoints && endpoints.length > 0) { diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 7214af568e7c..08828a26e16b 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -17,7 +17,7 @@ export const updateDiscoveredEndpointInCache = async ( const { endpointCache } = client?.config; const { commandName, getEndpointDiscoveryId } = options; - const cacheKey = getCacheKey(commandName, client?.config, getEndpointDiscoveryId); + const cacheKey = await getCacheKey(commandName, client?.config, getEndpointDiscoveryId); const endpoints = endpointCache.get(cacheKey); if (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { From 3bbcb5523b9e292a9d043f0bfe64b2d9ab7842de Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 06:11:10 +0000 Subject: [PATCH 06/48] fix: use set/get on LRUCache --- .../src/updateDiscoveredEndpointInCache.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 08828a26e16b..539e928dc83d 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -30,7 +30,7 @@ export const updateDiscoveredEndpointInCache = async ( } else { // put in a placeholder for endpoints already requested, prevent // too much in-flight calls. - endpointCache.put(cacheKey, [ + endpointCache.set(cacheKey, [ { Address: "", CachePeriodInMinutes: 1, @@ -43,9 +43,9 @@ export const updateDiscoveredEndpointInCache = async ( Identifiers: getEndpointDiscoveryId ? getEndpointDiscoveryId() : undefined, }); const { Endpoints } = await client?.send(command); - endpointCache.put(cacheKey, Endpoints); + endpointCache.set(cacheKey, Endpoints); } catch (error) { - endpointCache.put(cacheKey, [ + endpointCache.set(cacheKey, [ { Address: "", CachePeriodInMinutes: 1, // Not to make more endpoint operation in next 1 minute From b33c1eab6ee7ce28e60c2f5c8b4d654bd708859f Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 06:36:28 +0000 Subject: [PATCH 07/48] fix: use string as key for LRUCache --- packages/middleware-endpoint-discovery/src/getCacheKey.ts | 2 +- .../src/resolveEndpointDiscoveryClientConfig.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 9013ce50ee5c..407f55004001 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -17,5 +17,5 @@ export const getCacheKey = async ( ...(region && { region }), ...(accessKeyId && { accessKeyId }), ...(identifiers && { identifiers }), - }; + }.toString(); }; diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 5d1f4d3ee8d0..171a2290e44e 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -7,7 +7,7 @@ interface PreviouslyResolved {} export interface EndpointDiscoveryClientResolvedConfig { client?: Client; - endpointCache: LRUCache; + endpointCache: LRUCache; } export const resolveEndpointDiscoveryClientConfig = ( From 924f4fc7f40f991cc1d1a86c8d71ea189951aa72 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 15:32:54 +0000 Subject: [PATCH 08/48] chore: move setting of discoveryCommandCtor to client config --- clients/client-timestream-query/TimestreamQueryClient.ts | 7 ++++++- clients/client-timestream-query/commands/QueryCommand.ts | 1 - .../src/getEndpointDiscoveryCommandPlugin.ts | 6 +++--- .../src/resolveEndpointDiscoveryClientConfig.ts | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index d6e41bcaf39a..7e278999608a 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -1,5 +1,9 @@ import { CancelQueryCommandInput, CancelQueryCommandOutput } from "./commands/CancelQueryCommand"; -import { DescribeEndpointsCommandInput, DescribeEndpointsCommandOutput } from "./commands/DescribeEndpointsCommand"; +import { + DescribeEndpointsCommand, + DescribeEndpointsCommandInput, + DescribeEndpointsCommandOutput, +} from "./commands/DescribeEndpointsCommand"; import { QueryCommandInput, QueryCommandOutput } from "./commands/QueryCommand"; import { ClientDefaultValues as __ClientDefaultValues } from "./runtimeConfig"; import { @@ -215,6 +219,7 @@ export class TimestreamQueryClient extends __Client< let _config_7 = resolveEndpointDiscoveryClientConfig(_config_6); super(_config_7); _config_7.client = this; + _config_7.discoveryEndpointCommandCtor = DescribeEndpointsCommand; this.config = _config_7; this.middlewareStack.use(getRetryPlugin(this.config)); this.middlewareStack.use(getContentLengthPlugin(this.config)); diff --git a/clients/client-timestream-query/commands/QueryCommand.ts b/clients/client-timestream-query/commands/QueryCommand.ts index 813438bda475..b9b8ddab514f 100644 --- a/clients/client-timestream-query/commands/QueryCommand.ts +++ b/clients/client-timestream-query/commands/QueryCommand.ts @@ -62,7 +62,6 @@ export class QueryCommand extends $Command string | undefined; - discoveryEndpointCommandCtor: new (comandConfig: any) => Command; }; export const endpointDiscoveryMiddleware = ( @@ -36,7 +35,9 @@ export const endpointDiscoveryMiddleware = ( ): FinalizeHandler => async ( args: FinalizeHandlerArguments ): Promise> => { - const { isDiscoveredEndpointRequired, discoveryEndpointCommandCtor, getEndpointDiscoveryId } = middlewareConfig; + const { client } = config; + const { discoveryEndpointCommandCtor } = client?.config; + const { isDiscoveredEndpointRequired, getEndpointDiscoveryId } = middlewareConfig; const { commandName } = context; if (isDiscoveredEndpointRequired === true) { @@ -57,7 +58,6 @@ export const endpointDiscoveryMiddleware = ( }); } - const { client } = config; const { request } = args; const cacheKey = await getCacheKey(commandName, client?.config, getEndpointDiscoveryId); if (cacheKey && HttpRequest.isInstance(request)) { diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 171a2290e44e..d28f033ded9f 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -1,4 +1,4 @@ -import { Client } from "@aws-sdk/types"; +import { Client, Command } from "@aws-sdk/types"; import LRUCache from "lru-cache"; export interface EndpointDiscoveryClientInputConfig {} @@ -7,6 +7,7 @@ interface PreviouslyResolved {} export interface EndpointDiscoveryClientResolvedConfig { client?: Client; + discoveryEndpointCommandCtor?: new (comandConfig: any) => Command; endpointCache: LRUCache; } From 08e2c613bf65e92669c6f29076bd10d79bda4a59 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 15:40:51 +0000 Subject: [PATCH 09/48] chore: rename discoveryEndpointCommandCtor --- clients/client-timestream-query/TimestreamQueryClient.ts | 2 +- clients/client-timestream-query/commands/QueryCommand.ts | 1 - .../src/getEndpointDiscoveryCommandPlugin.ts | 6 +++--- .../src/resolveEndpointDiscoveryClientConfig.ts | 2 +- .../src/updateDiscoveredEndpointInCache.ts | 4 ++-- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index 7e278999608a..0e5d2c1c03a8 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -219,7 +219,7 @@ export class TimestreamQueryClient extends __Client< let _config_7 = resolveEndpointDiscoveryClientConfig(_config_6); super(_config_7); _config_7.client = this; - _config_7.discoveryEndpointCommandCtor = DescribeEndpointsCommand; + _config_7.endpointDiscoveryCommandCtor = DescribeEndpointsCommand; this.config = _config_7; this.middlewareStack.use(getRetryPlugin(this.config)); this.middlewareStack.use(getContentLengthPlugin(this.config)); diff --git a/clients/client-timestream-query/commands/QueryCommand.ts b/clients/client-timestream-query/commands/QueryCommand.ts index b9b8ddab514f..8afac4a0276b 100644 --- a/clients/client-timestream-query/commands/QueryCommand.ts +++ b/clients/client-timestream-query/commands/QueryCommand.ts @@ -14,7 +14,6 @@ import { SerdeContext as __SerdeContext, } from "@aws-sdk/types"; import { getEndpointDiscoveryCommandPlugin } from "@aws-sdk/middleware-endpoint-discovery"; -import { DescribeEndpointsCommand } from "./DescribeEndpointsCommand"; export interface QueryCommandInput extends QueryRequest {} export interface QueryCommandOutput extends QueryResponse, __MetadataBearer {} diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index b547888e7618..a33f1953710c 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -36,7 +36,7 @@ export const endpointDiscoveryMiddleware = ( args: FinalizeHandlerArguments ): Promise> => { const { client } = config; - const { discoveryEndpointCommandCtor } = client?.config; + const { endpointDiscoveryCommandCtor } = client?.config; const { isDiscoveredEndpointRequired, getEndpointDiscoveryId } = middlewareConfig; const { commandName } = context; @@ -45,7 +45,7 @@ export const endpointDiscoveryMiddleware = ( // till discovered endpoint is updated in cache await updateDiscoveredEndpointInCache(config, { commandName, - discoveryEndpointCommandCtor, + endpointDiscoveryCommandCtor, getEndpointDiscoveryId, }); } else if (isDiscoveredEndpointRequired === false) { @@ -53,7 +53,7 @@ export const endpointDiscoveryMiddleware = ( // does not block, the command will use discovered endpoint, if available. updateDiscoveredEndpointInCache(config, { commandName, - discoveryEndpointCommandCtor, + endpointDiscoveryCommandCtor, getEndpointDiscoveryId, }); } diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index d28f033ded9f..977c16201020 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -7,7 +7,7 @@ interface PreviouslyResolved {} export interface EndpointDiscoveryClientResolvedConfig { client?: Client; - discoveryEndpointCommandCtor?: new (comandConfig: any) => Command; + endpointDiscoveryCommandCtor?: new (comandConfig: any) => Command; endpointCache: LRUCache; } diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 539e928dc83d..52dfcd2c3559 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -5,7 +5,7 @@ import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscover export type updateDiscoveredEndpointInCacheOptions = { commandName: string; - discoveryEndpointCommandCtor: new (comandConfig: any) => Command; + endpointDiscoveryCommandCtor: new (comandConfig: any) => Command; getEndpointDiscoveryId: () => string | undefined; }; @@ -38,7 +38,7 @@ export const updateDiscoveredEndpointInCache = async ( ]); try { - const command = new options.discoveryEndpointCommandCtor({ + const command = new options.endpointDiscoveryCommandCtor({ Operation: commandName.substr(0, commandName.length - 7), // strip "Command" Identifiers: getEndpointDiscoveryId ? getEndpointDiscoveryId() : undefined, }); From 6a6ef32c148b914598bacf1266f82d2b8d01b1cd Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 15:58:05 +0000 Subject: [PATCH 10/48] chore: store endpointDiscoveryId in an optional param instead of function --- .../middleware-endpoint-discovery/src/getCacheKey.ts | 8 +++++--- .../src/getEndpointDiscoveryCommandPlugin.ts | 10 +++++----- .../src/updateDiscoveredEndpointInCache.ts | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 407f55004001..5c08ba68984d 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -7,15 +7,17 @@ import { AwsAuthResolvedConfig } from "@aws-sdk/middleware-signing"; export const getCacheKey = async ( commandName: string, config: AwsAuthResolvedConfig & RegionResolvedConfig, - getEndpointDiscoveryId: () => string | undefined + options: { + endpointDiscoveryId?: string; + } ) => { const region = await config.region(); const { accessKeyId } = await config.credentials(); - const identifiers = getEndpointDiscoveryId ? getEndpointDiscoveryId() : undefined; + const { endpointDiscoveryId } = options; return { commandName, ...(region && { region }), ...(accessKeyId && { accessKeyId }), - ...(identifiers && { identifiers }), + ...(endpointDiscoveryId && { endpointDiscoveryId }), }.toString(); }; diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index a33f1953710c..1bff09ce9b6e 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -23,7 +23,7 @@ export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = export type EndpointDiscoveryMiddlewareConfig = { isDiscoveredEndpointRequired: boolean; - getEndpointDiscoveryId: () => string | undefined; + endpointDiscoveryId?: string; }; export const endpointDiscoveryMiddleware = ( @@ -37,7 +37,7 @@ export const endpointDiscoveryMiddleware = ( ): Promise> => { const { client } = config; const { endpointDiscoveryCommandCtor } = client?.config; - const { isDiscoveredEndpointRequired, getEndpointDiscoveryId } = middlewareConfig; + const { isDiscoveredEndpointRequired, endpointDiscoveryId } = middlewareConfig; const { commandName } = context; if (isDiscoveredEndpointRequired === true) { @@ -46,7 +46,7 @@ export const endpointDiscoveryMiddleware = ( await updateDiscoveredEndpointInCache(config, { commandName, endpointDiscoveryCommandCtor, - getEndpointDiscoveryId, + endpointDiscoveryId, }); } else if (isDiscoveredEndpointRequired === false) { // Do not call await await on Endpoint Discovery API utility so that function @@ -54,12 +54,12 @@ export const endpointDiscoveryMiddleware = ( updateDiscoveredEndpointInCache(config, { commandName, endpointDiscoveryCommandCtor, - getEndpointDiscoveryId, + endpointDiscoveryId, }); } const { request } = args; - const cacheKey = await getCacheKey(commandName, client?.config, getEndpointDiscoveryId); + const cacheKey = await getCacheKey(commandName, client?.config, { endpointDiscoveryId }); if (cacheKey && HttpRequest.isInstance(request)) { const endpoints = client?.config.endpointCache.get(cacheKey); if (endpoints && endpoints.length > 0) { diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 52dfcd2c3559..81f9f382fa54 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -6,7 +6,7 @@ import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscover export type updateDiscoveredEndpointInCacheOptions = { commandName: string; endpointDiscoveryCommandCtor: new (comandConfig: any) => Command; - getEndpointDiscoveryId: () => string | undefined; + endpointDiscoveryId?: string; }; export const updateDiscoveredEndpointInCache = async ( @@ -16,8 +16,8 @@ export const updateDiscoveredEndpointInCache = async ( const { client } = config; const { endpointCache } = client?.config; - const { commandName, getEndpointDiscoveryId } = options; - const cacheKey = await getCacheKey(commandName, client?.config, getEndpointDiscoveryId); + const { commandName, endpointDiscoveryId } = options; + const cacheKey = await getCacheKey(commandName, client?.config, { endpointDiscoveryId }); const endpoints = endpointCache.get(cacheKey); if (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { @@ -40,7 +40,7 @@ export const updateDiscoveredEndpointInCache = async ( try { const command = new options.endpointDiscoveryCommandCtor({ Operation: commandName.substr(0, commandName.length - 7), // strip "Command" - Identifiers: getEndpointDiscoveryId ? getEndpointDiscoveryId() : undefined, + Identifiers: endpointDiscoveryId, }); const { Endpoints } = await client?.send(command); endpointCache.set(cacheKey, Endpoints); From 468e7a7f662f14e8e06eed79ba3cec99b70a8cc3 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 16:00:07 +0000 Subject: [PATCH 11/48] fix: remove deprecated param from getEndpointDiscoveryCommandPlugin --- clients/client-timestream-query/commands/QueryCommand.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/clients/client-timestream-query/commands/QueryCommand.ts b/clients/client-timestream-query/commands/QueryCommand.ts index 8afac4a0276b..745c4b863d69 100644 --- a/clients/client-timestream-query/commands/QueryCommand.ts +++ b/clients/client-timestream-query/commands/QueryCommand.ts @@ -59,8 +59,6 @@ export class QueryCommand extends $Command Date: Mon, 3 May 2021 16:14:26 +0000 Subject: [PATCH 12/48] fix: convert identifiers into Map --- .../middleware-endpoint-discovery/src/getCacheKey.ts | 6 +++--- .../src/getEndpointDiscoveryCommandPlugin.ts | 11 +++++------ .../src/updateDiscoveredEndpointInCache.ts | 8 ++++---- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 5c08ba68984d..17a35b073f87 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -8,16 +8,16 @@ export const getCacheKey = async ( commandName: string, config: AwsAuthResolvedConfig & RegionResolvedConfig, options: { - endpointDiscoveryId?: string; + identifiers?: Map; } ) => { const region = await config.region(); const { accessKeyId } = await config.credentials(); - const { endpointDiscoveryId } = options; + const { identifiers } = options; return { commandName, ...(region && { region }), ...(accessKeyId && { accessKeyId }), - ...(endpointDiscoveryId && { endpointDiscoveryId }), + ...(identifiers && { identifiers }), }.toString(); }; diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 1bff09ce9b6e..e1f812993f9b 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -1,6 +1,5 @@ import { HttpRequest } from "@aws-sdk/protocol-http"; import { - Command, FinalizeHandler, FinalizeHandlerArguments, FinalizeHandlerOutput, @@ -23,7 +22,7 @@ export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = export type EndpointDiscoveryMiddlewareConfig = { isDiscoveredEndpointRequired: boolean; - endpointDiscoveryId?: string; + identifiers?: Map; }; export const endpointDiscoveryMiddleware = ( @@ -37,7 +36,7 @@ export const endpointDiscoveryMiddleware = ( ): Promise> => { const { client } = config; const { endpointDiscoveryCommandCtor } = client?.config; - const { isDiscoveredEndpointRequired, endpointDiscoveryId } = middlewareConfig; + const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; const { commandName } = context; if (isDiscoveredEndpointRequired === true) { @@ -46,7 +45,7 @@ export const endpointDiscoveryMiddleware = ( await updateDiscoveredEndpointInCache(config, { commandName, endpointDiscoveryCommandCtor, - endpointDiscoveryId, + identifiers, }); } else if (isDiscoveredEndpointRequired === false) { // Do not call await await on Endpoint Discovery API utility so that function @@ -54,12 +53,12 @@ export const endpointDiscoveryMiddleware = ( updateDiscoveredEndpointInCache(config, { commandName, endpointDiscoveryCommandCtor, - endpointDiscoveryId, + identifiers, }); } const { request } = args; - const cacheKey = await getCacheKey(commandName, client?.config, { endpointDiscoveryId }); + const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); if (cacheKey && HttpRequest.isInstance(request)) { const endpoints = client?.config.endpointCache.get(cacheKey); if (endpoints && endpoints.length > 0) { diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 81f9f382fa54..8c2c6eeb416b 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -6,7 +6,7 @@ import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscover export type updateDiscoveredEndpointInCacheOptions = { commandName: string; endpointDiscoveryCommandCtor: new (comandConfig: any) => Command; - endpointDiscoveryId?: string; + identifiers?: Map; }; export const updateDiscoveredEndpointInCache = async ( @@ -16,8 +16,8 @@ export const updateDiscoveredEndpointInCache = async ( const { client } = config; const { endpointCache } = client?.config; - const { commandName, endpointDiscoveryId } = options; - const cacheKey = await getCacheKey(commandName, client?.config, { endpointDiscoveryId }); + const { commandName, identifiers } = options; + const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); const endpoints = endpointCache.get(cacheKey); if (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { @@ -40,7 +40,7 @@ export const updateDiscoveredEndpointInCache = async ( try { const command = new options.endpointDiscoveryCommandCtor({ Operation: commandName.substr(0, commandName.length - 7), // strip "Command" - Identifiers: endpointDiscoveryId, + Identifiers: identifiers, }); const { Endpoints } = await client?.send(command); endpointCache.set(cacheKey, Endpoints); From 21b8c6d5c85083c1aa01b90520719025b55a8bc9 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 16:54:51 +0000 Subject: [PATCH 13/48] fix: wait for parallel endpoint discovery operation to complete --- .../src/updateDiscoveredEndpointInCache.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 8c2c6eeb416b..ca0f0ce919cc 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -9,6 +9,8 @@ export type updateDiscoveredEndpointInCacheOptions = { identifiers?: Map; }; +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + export const updateDiscoveredEndpointInCache = async ( config: EndpointDiscoveryClientResolvedConfig, options: updateDiscoveredEndpointInCacheOptions @@ -19,12 +21,15 @@ export const updateDiscoveredEndpointInCache = async ( const { commandName, identifiers } = options; const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); - const endpoints = endpointCache.get(cacheKey); - if (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { - // endpoint operation is being made but response not yet received - // or endpoint operation just failed in 1 minute. - return; - } else if (endpoints && endpoints.length > 0) { + let endpoints = endpointCache.get(cacheKey); + + // Wait for other endpoint operations to complete before making new calls. + while (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { + await sleep(1000); + endpoints = endpointCache.get(cacheKey); + } + + if (endpoints && endpoints.length > 0) { // Endpoint record is present in cache. return; } else { From 8c3d48613ab6ca54f4e3649bf04ae955c2693bfc Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Mon, 3 May 2021 17:19:47 +0000 Subject: [PATCH 14/48] fix: rethrow error is client discovered endpoint is required --- .../src/getEndpointDiscoveryCommandPlugin.ts | 8 ++++---- .../src/updateDiscoveredEndpointInCache.ts | 14 ++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index e1f812993f9b..7c7d620e6285 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -39,21 +39,21 @@ export const endpointDiscoveryMiddleware = ( const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; const { commandName } = context; - if (isDiscoveredEndpointRequired === true) { + if (isDiscoveredEndpointRequired) { // call await on Endpoint Discovery API utility so that function blocks // till discovered endpoint is updated in cache await updateDiscoveredEndpointInCache(config, { + ...middlewareConfig, commandName, endpointDiscoveryCommandCtor, - identifiers, }); - } else if (isDiscoveredEndpointRequired === false) { + } else { // Do not call await await on Endpoint Discovery API utility so that function // does not block, the command will use discovered endpoint, if available. updateDiscoveredEndpointInCache(config, { + ...middlewareConfig, commandName, endpointDiscoveryCommandCtor, - identifiers, }); } diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index ca0f0ce919cc..5030b007e1b0 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -1,12 +1,12 @@ import { Command } from "@aws-sdk/types"; import { getCacheKey } from "./getCacheKey"; +import { EndpointDiscoveryMiddlewareConfig } from "./getEndpointDiscoveryCommandPlugin"; import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; -export type updateDiscoveredEndpointInCacheOptions = { +export type updateDiscoveredEndpointInCacheOptions = EndpointDiscoveryMiddlewareConfig & { commandName: string; endpointDiscoveryCommandCtor: new (comandConfig: any) => Command; - identifiers?: Map; }; const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); @@ -50,12 +50,10 @@ export const updateDiscoveredEndpointInCache = async ( const { Endpoints } = await client?.send(command); endpointCache.set(cacheKey, Endpoints); } catch (error) { - endpointCache.set(cacheKey, [ - { - Address: "", - CachePeriodInMinutes: 1, // Not to make more endpoint operation in next 1 minute - }, - ]); + endpointCache.del(cacheKey); + if (options.isDiscoveredEndpointRequired) { + throw error; + } } } }; From 78d7db4799d6b982f7c8b33b6c7f09041fa0eb26 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 May 2021 15:15:02 +0000 Subject: [PATCH 15/48] feat(endpoint-cache): add Endpoint Cache --- packages/endpoint-cache/LICENSE | 201 +++++++++++++++++ packages/endpoint-cache/README.md | 17 ++ packages/endpoint-cache/jest.config.js | 5 + packages/endpoint-cache/package.json | 45 ++++ packages/endpoint-cache/src/Endpoint.ts | 11 + .../endpoint-cache/src/EndpointCache.spec.ts | 208 ++++++++++++++++++ packages/endpoint-cache/src/EndpointCache.ts | 115 ++++++++++ packages/endpoint-cache/src/index.ts | 2 + packages/endpoint-cache/tsconfig.cjs.json | 10 + packages/endpoint-cache/tsconfig.es.json | 11 + yarn.lock | 12 + 11 files changed, 637 insertions(+) create mode 100644 packages/endpoint-cache/LICENSE create mode 100644 packages/endpoint-cache/README.md create mode 100644 packages/endpoint-cache/jest.config.js create mode 100644 packages/endpoint-cache/package.json create mode 100644 packages/endpoint-cache/src/Endpoint.ts create mode 100644 packages/endpoint-cache/src/EndpointCache.spec.ts create mode 100644 packages/endpoint-cache/src/EndpointCache.ts create mode 100644 packages/endpoint-cache/src/index.ts create mode 100644 packages/endpoint-cache/tsconfig.cjs.json create mode 100644 packages/endpoint-cache/tsconfig.es.json diff --git a/packages/endpoint-cache/LICENSE b/packages/endpoint-cache/LICENSE new file mode 100644 index 000000000000..74d4e5c31f2e --- /dev/null +++ b/packages/endpoint-cache/LICENSE @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/packages/endpoint-cache/README.md b/packages/endpoint-cache/README.md new file mode 100644 index 000000000000..5d72b8c4e41e --- /dev/null +++ b/packages/endpoint-cache/README.md @@ -0,0 +1,17 @@ +# @aws-sdk/endpoint-cache + +[![NPM version](https://img.shields.io/npm/v/@aws-sdk/endpoint-cache/latest.svg)](https://www.npmjs.com/package/@aws-sdk/endpoint-cache) +[![NPM downloads](https://img.shields.io/npm/dm/@aws-sdk/endpoint-cache.svg)](https://www.npmjs.com/package/@aws-sdk/endpoint-cache) + +> An internal package + +## Usage + +You probably shouldn't, at least directly. + +## EndpointCache + +- uses `mnemonist/lru-cache` for storing the cache. +- the `set` operation stores milliseconds elapsed since the UNIX epoch in Expires param based on CachePeriodInMinutes provided in Endpoint. +- the `get` operation returns all un-expired endpoints with their Expires values. +- the `getEndpoint` operation returns a randomly selected un-expired endpoint. diff --git a/packages/endpoint-cache/jest.config.js b/packages/endpoint-cache/jest.config.js new file mode 100644 index 000000000000..a8d1c2e49912 --- /dev/null +++ b/packages/endpoint-cache/jest.config.js @@ -0,0 +1,5 @@ +const base = require("../../jest.config.base.js"); + +module.exports = { + ...base, +}; diff --git a/packages/endpoint-cache/package.json b/packages/endpoint-cache/package.json new file mode 100644 index 000000000000..4cd27b72bc52 --- /dev/null +++ b/packages/endpoint-cache/package.json @@ -0,0 +1,45 @@ +{ + "name": "@aws-sdk/endpoint-cache", + "version": "3.0.0", + "scripts": { + "prepublishOnly": "yarn build && downlevel-dts dist/types dist/types/ts3.4", + "build:cjs": "tsc -p tsconfig.cjs.json", + "build:es": "tsc -p tsconfig.es.json", + "build": "yarn build:es && yarn build:cjs", + "test": "jest --passWithNoTests" + }, + "author": { + "name": "AWS SDK for JavaScript Team", + "url": "https://aws.amazon.com/javascript/" + }, + "license": "Apache-2.0", + "main": "./dist/cjs/index.js", + "module": "./dist/es/index.js", + "types": "./dist/types/index.d.ts", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.0.0" + }, + "devDependencies": { + "@types/jest": "^26.0.4", + "@types/node": "^10.0.0", + "jest": "^26.1.0", + "typescript": "~4.2.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "typesVersions": { + "<4.0": { + "types/*": [ + "types/ts3.4/*" + ] + } + }, + "homepage": "https://github.com/aws/aws-sdk-js-v3/tree/main/packages/endpoint-cache", + "repository": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-js-v3.git", + "directory": "packages/endpoint-cache" + } +} diff --git a/packages/endpoint-cache/src/Endpoint.ts b/packages/endpoint-cache/src/Endpoint.ts new file mode 100644 index 000000000000..bfadf0416075 --- /dev/null +++ b/packages/endpoint-cache/src/Endpoint.ts @@ -0,0 +1,11 @@ +export interface Endpoint { + /** + *

An endpoint address.

+ */ + Address: string; + + /** + *

The TTL for the endpoint, in minutes.

+ */ + CachePeriodInMinutes: number; +} diff --git a/packages/endpoint-cache/src/EndpointCache.spec.ts b/packages/endpoint-cache/src/EndpointCache.spec.ts new file mode 100644 index 000000000000..884f21c2f3d1 --- /dev/null +++ b/packages/endpoint-cache/src/EndpointCache.spec.ts @@ -0,0 +1,208 @@ +import { LRUCache } from "mnemonist"; + +import { Endpoint } from "./Endpoint"; +import { EndpointCache } from "./EndpointCache"; + +jest.mock("mnemonist"); + +describe(EndpointCache.name, () => { + let endpointCache; + const capacity = 100; + const key = "key"; + + const now = Date.now(); + const set = jest.fn(); + const get = jest.fn(); + const peek = jest.fn(); + const has = jest.fn(); + const clear = jest.fn(); + + const mockEndpoints = [ + { Address: "addressA", CachePeriodInMinutes: 1 }, + { Address: "addressB", CachePeriodInMinutes: 2 }, + ]; + + const getEndpointsWithExpiry = (endpoints: Endpoint[]) => + endpoints.map(({ Address = "", CachePeriodInMinutes = 1 }) => ({ + Address, + Expires: now + CachePeriodInMinutes * 60 * 1000, + })); + + const getMaxCachePeriodInMins = (endpoints: Endpoint[]) => + Math.max(...endpoints.map((endpoint) => endpoint.CachePeriodInMinutes)); + + beforeEach(() => { + ((LRUCache as unknown) as jest.Mock).mockReturnValueOnce({ + set, + get, + peek, + has, + clear, + }); + endpointCache = new EndpointCache(capacity); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it("passes capacity to LRUCache", () => { + expect(LRUCache).toHaveBeenCalledTimes(1); + expect(LRUCache).toHaveBeenCalledWith(capacity); + }); + + describe("get", () => { + beforeEach(() => { + has.mockReturnValue(true); + const endpointsWithExpiry = getEndpointsWithExpiry(mockEndpoints); + peek.mockReturnValue(endpointsWithExpiry); + get.mockReturnValue(endpointsWithExpiry); + jest.spyOn(Date, "now").mockImplementation(() => now); + }); + + const verifyHasAndGetCalls = () => { + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + expect(get).toHaveBeenCalledTimes(1); + expect(get).toHaveBeenCalledWith(key); + }; + + it("returns undefined if cache doesn't have key", () => { + has.mockReturnValueOnce(false); + expect(endpointCache.get(key)).toBeUndefined(); + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + expect(peek).not.toHaveBeenCalled(); + expect(get).not.toHaveBeenCalled(); + }); + + it("returns undefined if cache has empty array", () => { + has.mockReturnValueOnce(true); + peek.mockReturnValueOnce([]); + expect(endpointCache.get(key)).toBeUndefined(); + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + expect(peek).toHaveBeenCalledTimes(1); + expect(peek).toHaveBeenCalledWith(key); + expect(get).not.toHaveBeenCalled(); + }); + + it("returns undefined if cache returns undefined for key", () => { + get.mockReturnValueOnce(undefined); + expect(endpointCache.get(key)).toBeUndefined(); + verifyHasAndGetCalls(); + expect(set).not.toHaveBeenCalled(); + }); + + it("returns undefined if endpoints have expired", () => { + const maxCachePeriod = getMaxCachePeriodInMins(mockEndpoints); + jest.spyOn(Date, "now").mockImplementation(() => now + (maxCachePeriod + 1) * 60 * 1000); + expect(endpointCache.get(key)).toBeUndefined(); + verifyHasAndGetCalls(); + expect(set).toHaveBeenCalledTimes(1); + expect(set).toHaveBeenCalledWith(key, []); + }); + + describe("getEndpoint", () => { + it("returns one of the un-expired endpoints", () => { + expect(mockEndpoints.map((endpoint) => endpoint.Address)).toContain(endpointCache.getEndpoint(key)); + verifyHasAndGetCalls(); + expect(set).not.toHaveBeenCalled(); + }); + + it("returns un-expired endpoint", () => { + jest.spyOn(Date, "now").mockImplementation(() => now + 90 * 1000); + expect(endpointCache.getEndpoint(key)).toEqual(mockEndpoints[1].Address); + verifyHasAndGetCalls(); + expect(set).not.toHaveBeenCalled(); + }); + + [0, 1].forEach((index) => { + it(`returns un-expired endpoint at index ${index}`, () => { + jest.spyOn(Math, "floor").mockImplementation(() => index); + expect(mockEndpoints.map((endpoint) => endpoint.Address)).toContain(endpointCache.getEndpoint(key)); + verifyHasAndGetCalls(); + expect(set).not.toHaveBeenCalled(); + }); + }); + }); + }); + + describe("set", () => { + beforeEach(() => { + jest.spyOn(Date, "now").mockImplementation(() => now); + }); + + it("converts CachePeriodInMinutes to Expires before caching", () => { + endpointCache.set(key, mockEndpoints); + expect(set).toHaveBeenCalledTimes(1); + expect(set).toHaveBeenCalledWith( + key, + mockEndpoints.map(({ Address, CachePeriodInMinutes }) => ({ + Address, + Expires: now + CachePeriodInMinutes * 60 * 1000, + })) + ); + }); + + it("sets Address to empty string if not passed", () => { + const mockEnpointsNoAddr = [{ CachePeriodInMinutes: 1 }]; + endpointCache.set(key, mockEnpointsNoAddr); + expect(set).toHaveBeenCalledTimes(1); + expect(set).toHaveBeenCalledWith(key, [{ Address: "", Expires: now + 60 * 1000 }]); + }); + + it("sets Expires in one minute if CachePeriodInMinutes is not passed", () => { + const mockEnpointsNoAddr = [{ Address: "address" }]; + endpointCache.set(key, mockEnpointsNoAddr); + expect(set).toHaveBeenCalledTimes(1); + expect(set).toHaveBeenCalledWith(key, [{ Address: "address", Expires: now + 60 * 1000 }]); + }); + }); + + it("delete", () => { + endpointCache.delete(key); + expect(set).toHaveBeenCalledTimes(1); + expect(set).toHaveBeenCalledWith(key, []); + }); + + describe("has", () => { + describe("returns false", () => { + it("when key is not present", () => { + has.mockReturnValueOnce(false); + expect(endpointCache.has(key)).toEqual(false); + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + }); + + it("when key is present and value is empty", () => { + has.mockReturnValueOnce(true); + peek.mockReturnValueOnce([]); + expect(endpointCache.has(key)).toEqual(false); + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + }); + + it("when key is present and value is undefined", () => { + has.mockReturnValueOnce(true); + peek.mockReturnValueOnce(undefined); + expect(endpointCache.has(key)).toEqual(false); + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + }); + }); + + it("returns true when key is present and value is non-empty", () => { + has.mockReturnValueOnce(true); + peek.mockReturnValueOnce(getEndpointsWithExpiry(mockEndpoints)); + expect(endpointCache.has(key)).toEqual(true); + expect(has).toHaveBeenCalledTimes(1); + expect(has).toHaveBeenCalledWith(key); + }); + }); + + it("clear", () => { + endpointCache.clear(); + expect(clear).toHaveBeenCalledTimes(1); + }); +}); diff --git a/packages/endpoint-cache/src/EndpointCache.ts b/packages/endpoint-cache/src/EndpointCache.ts new file mode 100644 index 000000000000..5f61ae0a9e24 --- /dev/null +++ b/packages/endpoint-cache/src/EndpointCache.ts @@ -0,0 +1,115 @@ +import { LRUCache } from "mnemonist"; + +import { Endpoint } from "./Endpoint"; + +export interface EndpointWithExpiry extends Pick { + Expires: number; +} + +export class EndpointCache { + private readonly cache: LRUCache; + + constructor(capacity: number) { + this.cache = new LRUCache(capacity); + } + + /** + * Returns an un-expired endpoint for the given key. + * + * @param endpointsWithExpiry + * @returns + */ + getEndpoint(key: string) { + const endpointsWithExpiry = this.get(key); + if (!endpointsWithExpiry || endpointsWithExpiry.length === 0) { + return undefined; + } + const endpoints = endpointsWithExpiry.map((endpoint) => endpoint.Address); + return endpoints[Math.floor(Math.random() * endpoints.length)]; + } + + /** + * Returns un-expired endpoints for the given key. + * + * @param key + * @returns + */ + get(key: string) { + if (!this.has(key)) { + return; + } + + const value = this.cache.get(key); + if (!value) { + return; + } + + const now = Date.now(); + const endpointsWithExpiry = value.filter((endpoint) => now < endpoint.Expires); + if (endpointsWithExpiry.length === 0) { + this.delete(key); + return undefined; + } + + return endpointsWithExpiry; + } + + /** + * Stores the endpoints passed for the key in cache. + * If not defined, uses empty string for the Address in endpoint. + * If not defined, uses one minute for CachePeriodInMinutes in endpoint. + * Stores milliseconds elapsed since the UNIX epoch in Expires param based + * on value provided in CachePeriodInMinutes. + * + * @param key + * @param endpoints + */ + set(key: string, endpoints: Endpoint[]) { + const now = Date.now(); + this.cache.set( + key, + endpoints.map(({ Address = "", CachePeriodInMinutes = 1 }) => ({ + Address, + Expires: now + CachePeriodInMinutes * 60 * 1000, + })) + ); + } + + /** + * Deletes the value for the given key in the cache. + * + * @param {string} key + */ + delete(key: string) { + // Replace with remove/delete call once support is added upstream + // Refs: https://github.com/Yomguithereal/mnemonist/issues/143 + this.cache.set(key, []); + } + + /** + * Checks whether the key exists in cache. + * + * @param {string} key + * @returns {boolean} + */ + has(key: string): boolean { + if (!this.cache.has(key)) { + return false; + } + + // Remove call for peek, once remove/delete support is added upstream + // Refs: https://github.com/Yomguithereal/mnemonist/issues/143 + const endpoints = this.cache.peek(key); + if (!endpoints) { + return false; + } + return endpoints.length > 0; + } + + /** + * Clears the cache. + */ + clear() { + this.cache.clear(); + } +} diff --git a/packages/endpoint-cache/src/index.ts b/packages/endpoint-cache/src/index.ts new file mode 100644 index 000000000000..41fce6defc0f --- /dev/null +++ b/packages/endpoint-cache/src/index.ts @@ -0,0 +1,2 @@ +export * from "./Endpoint"; +export * from "./EndpointCache"; diff --git a/packages/endpoint-cache/tsconfig.cjs.json b/packages/endpoint-cache/tsconfig.cjs.json new file mode 100644 index 000000000000..67f09ddd4138 --- /dev/null +++ b/packages/endpoint-cache/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/cjs", + "baseUrl": "." + }, + "extends": "../../tsconfig.cjs.json", + "include": ["src/"] +} diff --git a/packages/endpoint-cache/tsconfig.es.json b/packages/endpoint-cache/tsconfig.es.json new file mode 100644 index 000000000000..c5f23ee06fb2 --- /dev/null +++ b/packages/endpoint-cache/tsconfig.es.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "lib": ["es5", "es2015.promise", "es2015.iterable"], + "declarationDir": "./dist/types", + "rootDir": "./src", + "outDir": "./dist/es", + "baseUrl": "." + }, + "extends": "../../tsconfig.es.json", + "include": ["src/"] +} diff --git a/yarn.lock b/yarn.lock index ddbb74fb9a09..ac75812622c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8260,6 +8260,13 @@ mkdirp@0.5.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: dependencies: minimist "^1.2.5" +mnemonist@0.38.3: + version "0.38.3" + resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.3.tgz#35ec79c1c1f4357cfda2fe264659c2775ccd7d9d" + integrity sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw== + dependencies: + obliterator "^1.6.1" + mocha@^8.0.1: version "8.3.2" resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.3.2.tgz#53406f195fa86fbdebe71f8b1c6fb23221d69fcc" @@ -8752,6 +8759,11 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" +obliterator@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" + integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig== + octokit-pagination-methods@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz#cf472edc9d551055f9ef73f6e42b4dbb4c80bea4" From c7b76d97d6df08f83ecc5c2aac4d2966ff1cd785 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 May 2021 15:25:20 +0000 Subject: [PATCH 16/48] chore: remove direct dependency on lru-cache --- .../package.json | 2 -- yarn.lock | 19 +++++++------------ 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/middleware-endpoint-discovery/package.json b/packages/middleware-endpoint-discovery/package.json index 8b8c7bc4289b..038c04858f4c 100644 --- a/packages/middleware-endpoint-discovery/package.json +++ b/packages/middleware-endpoint-discovery/package.json @@ -18,7 +18,6 @@ "license": "Apache-2.0", "devDependencies": { "@types/jest": "^26.0.4", - "@types/lru-cache": "5.1.0", "jest": "^26.1.0", "typescript": "~4.2.4" }, @@ -27,7 +26,6 @@ "@aws-sdk/middleware-signing": "3.13.1", "@aws-sdk/protocol-http": "3.13.1", "@aws-sdk/types": "3.13.1", - "lru-cache": "6.0.0", "tslib": "^2.0.0" }, "engines": { diff --git a/yarn.lock b/yarn.lock index ac75812622c3..54d6e756ba52 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1729,11 +1729,6 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad" integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA== -"@types/lru-cache@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@types/lru-cache/-/lru-cache-5.1.0.tgz#57f228f2b80c046b4a1bd5cac031f81f207f4f03" - integrity sha512-RaE0B+14ToE4l6UqdarKPnXwVDuigfFv+5j9Dze/Nqr23yyuqdNvzcZi3xB+3Agvi5R4EOgAksfv3lXX4vBt9w== - "@types/minimatch@*": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21" @@ -7859,13 +7854,6 @@ lower-case@^1.1.1: resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw= -lru-cache@6.0.0, lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7873,6 +7861,13 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + ltgt@^2.1.2: version "2.2.1" resolved "https://registry.yarnpkg.com/ltgt/-/ltgt-2.2.1.tgz#f35ca91c493f7b73da0e07495304f17b31f87ee5" From 869d914c2f8b05ad174b7b846d5504fd1cd81837 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 May 2021 15:32:47 +0000 Subject: [PATCH 17/48] chore: use EndpointCache instead of LRUCache --- packages/middleware-endpoint-discovery/package.json | 1 + .../src/getEndpointDiscoveryCommandPlugin.ts | 6 +++--- .../src/resolveEndpointDiscoveryClientConfig.ts | 6 +++--- .../src/updateDiscoveredEndpointInCache.ts | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/middleware-endpoint-discovery/package.json b/packages/middleware-endpoint-discovery/package.json index 038c04858f4c..90b07ba20d67 100644 --- a/packages/middleware-endpoint-discovery/package.json +++ b/packages/middleware-endpoint-discovery/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "@aws-sdk/config-resolver": "3.14.0", + "@aws-sdk/endpoint-cache": "3.0.0", "@aws-sdk/middleware-signing": "3.13.1", "@aws-sdk/protocol-http": "3.13.1", "@aws-sdk/types": "3.13.1", diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 7c7d620e6285..2951ee6303f6 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -60,9 +60,9 @@ export const endpointDiscoveryMiddleware = ( const { request } = args; const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); if (cacheKey && HttpRequest.isInstance(request)) { - const endpoints = client?.config.endpointCache.get(cacheKey); - if (endpoints && endpoints.length > 0) { - request.hostname = endpoints[0].Address; + const endpoint = client?.config.endpointCache.getEndpoint(cacheKey); + if (endpoint) { + request.hostname = endpoint; } } diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 977c16201020..23d214ff82db 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -1,5 +1,5 @@ +import { EndpointCache } from "@aws-sdk/endpoint-cache"; import { Client, Command } from "@aws-sdk/types"; -import LRUCache from "lru-cache"; export interface EndpointDiscoveryClientInputConfig {} @@ -8,12 +8,12 @@ interface PreviouslyResolved {} export interface EndpointDiscoveryClientResolvedConfig { client?: Client; endpointDiscoveryCommandCtor?: new (comandConfig: any) => Command; - endpointCache: LRUCache; + endpointCache: EndpointCache; } export const resolveEndpointDiscoveryClientConfig = ( input: T & PreviouslyResolved & EndpointDiscoveryClientInputConfig ): T & EndpointDiscoveryClientResolvedConfig => ({ ...input, - endpointCache: new LRUCache(), + endpointCache: new EndpointCache(1000), }); diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 5030b007e1b0..d3bcb8205d30 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -50,7 +50,7 @@ export const updateDiscoveredEndpointInCache = async ( const { Endpoints } = await client?.send(command); endpointCache.set(cacheKey, Endpoints); } catch (error) { - endpointCache.del(cacheKey); + endpointCache.delete(cacheKey); if (options.isDiscoveredEndpointRequired) { throw error; } From 95b1fbe741645e06b22c0baac2bec607972ac98a Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 May 2021 15:55:07 +0000 Subject: [PATCH 18/48] chore: disable refresh for a min if error in optional cae --- .../src/updateDiscoveredEndpointInCache.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index d3bcb8205d30..adfb4210906d 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -35,12 +35,8 @@ export const updateDiscoveredEndpointInCache = async ( } else { // put in a placeholder for endpoints already requested, prevent // too much in-flight calls. - endpointCache.set(cacheKey, [ - { - Address: "", - CachePeriodInMinutes: 1, - }, - ]); + const placeholderEndpoints = [{ Address: "", CachePeriodInMinutes: 1 }]; + endpointCache.set(cacheKey, placeholderEndpoints); try { const command = new options.endpointDiscoveryCommandCtor({ @@ -50,9 +46,14 @@ export const updateDiscoveredEndpointInCache = async ( const { Endpoints } = await client?.send(command); endpointCache.set(cacheKey, Endpoints); } catch (error) { - endpointCache.delete(cacheKey); if (options.isDiscoveredEndpointRequired) { + // Endpoint Discovery is required, rethrow error. + endpointCache.delete(cacheKey); throw error; + } else { + // Endpoint Discovery is optional. + // Set placeHolder endpoint to disable refresh for one minute. + endpointCache.set(cacheKey, placeholderEndpoints); } } } From 61733c9684bdda03bb6dbae9cea26b9b84e9e312 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 6 May 2021 18:14:41 +0000 Subject: [PATCH 19/48] chore: wip on handling errors --- .../src/updateDiscoveredEndpointInCache.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index adfb4210906d..469209e9e981 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -46,12 +46,15 @@ export const updateDiscoveredEndpointInCache = async ( const { Endpoints } = await client?.send(command); endpointCache.set(cacheKey, Endpoints); } catch (error) { + if (error.name === "InvalidEndpointException" || error.$metadata?.httpStatusCode === 421) { + // Endpoint is invalid, delete the cache entry. + endpointCache.delete(cacheKey); + } if (options.isDiscoveredEndpointRequired) { // Endpoint Discovery is required, rethrow error. - endpointCache.delete(cacheKey); throw error; } else { - // Endpoint Discovery is optional. + // Endpoint Discovery is optional. No error needs to be thrown. // Set placeHolder endpoint to disable refresh for one minute. endpointCache.set(cacheKey, placeholderEndpoints); } From 80d298967d4d57dc3d9c0f3012a1e7c80bf0fd1e Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 7 May 2021 01:37:09 +0000 Subject: [PATCH 20/48] chore: add configuration for endpoint discovery --- .../package.json | 1 + .../src/configurations.ts | 31 +++++++++++++++++++ .../src/index.ts | 1 + .../resolveEndpointDiscoveryClientConfig.ts | 23 ++++++++++++-- 4 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 packages/middleware-endpoint-discovery/src/configurations.ts diff --git a/packages/middleware-endpoint-discovery/package.json b/packages/middleware-endpoint-discovery/package.json index 90b07ba20d67..6d17bbcb3842 100644 --- a/packages/middleware-endpoint-discovery/package.json +++ b/packages/middleware-endpoint-discovery/package.json @@ -17,6 +17,7 @@ }, "license": "Apache-2.0", "devDependencies": { + "@aws-sdk/node-config-provider": "3.13.1", "@types/jest": "^26.0.4", "jest": "^26.1.0", "typescript": "~4.2.4" diff --git a/packages/middleware-endpoint-discovery/src/configurations.ts b/packages/middleware-endpoint-discovery/src/configurations.ts new file mode 100644 index 000000000000..fdb6ada3e66b --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/configurations.ts @@ -0,0 +1,31 @@ +import { LoadedConfigSelectors } from "@aws-sdk/node-config-provider"; + +const ENV_ENDPOINT_DISCOVERY = ["AWS_ENABLE_ENDPOINT_DISCOVERY", "AWS_ENDPOINT_DISCOVERY_ENABLED"]; +const CONFIG_ENDPOINT_DISCOVERY = "endpoint_discovery_enabled"; + +const isFalsy = (value: string) => ["false", "0"].indexOf(value) >= 0; + +export const NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS: LoadedConfigSelectors = { + environmentVariableSelector: (env) => { + for (let i = 0; i < ENV_ENDPOINT_DISCOVERY.length; i++) { + const envKey = ENV_ENDPOINT_DISCOVERY[i]; + if (envKey in env) { + const value = env[envKey]; + if (value === "" || value === undefined) { + throw Error(`Environment variable ${envKey} can't be empty of undefined, got "${value}"`); + } + return !isFalsy(value); + } + } + }, + configFileSelector: (profile) => { + if (CONFIG_ENDPOINT_DISCOVERY in profile) { + const value = profile[CONFIG_ENDPOINT_DISCOVERY]; + if (value === undefined) { + throw Error(`Shared config entry ${CONFIG_ENDPOINT_DISCOVERY} can't be undefined, got "${value}"`); + } + return !isFalsy(value); + } + }, + default: undefined, +}; diff --git a/packages/middleware-endpoint-discovery/src/index.ts b/packages/middleware-endpoint-discovery/src/index.ts index 5252da98d36e..ed291187bb97 100644 --- a/packages/middleware-endpoint-discovery/src/index.ts +++ b/packages/middleware-endpoint-discovery/src/index.ts @@ -1,2 +1,3 @@ export * from "./resolveEndpointDiscoveryClientConfig"; export * from "./getEndpointDiscoveryCommandPlugin"; +export * from "./configurations"; diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 23d214ff82db..74e34e4b20fa 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -1,19 +1,36 @@ import { EndpointCache } from "@aws-sdk/endpoint-cache"; -import { Client, Command } from "@aws-sdk/types"; +import { Client, Command, Provider } from "@aws-sdk/types"; export interface EndpointDiscoveryClientInputConfig {} -interface PreviouslyResolved {} +interface PreviouslyResolved { + endpointCacheSize?: number; + endpointDiscoveryEnabled?: boolean | Provider; +} export interface EndpointDiscoveryClientResolvedConfig { client?: Client; endpointDiscoveryCommandCtor?: new (comandConfig: any) => Command; endpointCache: EndpointCache; + endpointDiscoveryEnabled: Provider; + isClientEndpointDiscoveryEnabled: boolean; } export const resolveEndpointDiscoveryClientConfig = ( input: T & PreviouslyResolved & EndpointDiscoveryClientInputConfig ): T & EndpointDiscoveryClientResolvedConfig => ({ ...input, - endpointCache: new EndpointCache(1000), + endpointCache: new EndpointCache(input.endpointCacheSize ?? 1000), + endpointDiscoveryEnabled: input.endpointDiscoveryEnabled + ? normalizeEndpointDiscoveryEnabled(input.endpointDiscoveryEnabled) + : () => Promise.resolve(undefined), + isClientEndpointDiscoveryEnabled: input.endpointDiscoveryEnabled !== undefined, }); + +const normalizeEndpointDiscoveryEnabled = (endpointDiscoveryEnabled: boolean | Provider) => { + if (typeof endpointDiscoveryEnabled === "boolean") { + const promisified = Promise.resolve(endpointDiscoveryEnabled); + return () => promisified; + } + return endpointDiscoveryEnabled as Provider; +}; From 24ec4b3537f7d128e58d9f0c469b9fe2227e5cd5 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 7 May 2021 02:13:36 +0000 Subject: [PATCH 21/48] chore(clients): add endpointDiscoveryEnabled configuration --- clients/client-timestream-query/TimestreamQueryClient.ts | 9 +++++++++ clients/client-timestream-query/runtimeConfig.browser.ts | 1 + clients/client-timestream-query/runtimeConfig.ts | 2 ++ 3 files changed, 12 insertions(+) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index 0e5d2c1c03a8..ccebb760f15f 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -159,6 +159,15 @@ export interface ClientDefaults extends Partial<__SmithyResolvedConfiguration<__ * @internal */ defaultUserAgentProvider?: Provider<__UserAgent>; + + /** + * Returns whether to call operations with endpoints given by service dynamically. + * Setting this config to `true` will enable endpoint discovery for all applicable operations. + * Setting it to `false` will explicitly disable endpoint discovery even though operations that + * require endpoint discovery will presumably fail. Leaving it to undefined means SDK only do + * endpoint discovery when it's required. Defaults to `undefined`. + */ + endpointDiscoveryEnabled: boolean | undefined | __Provider; } type TimestreamQueryClientConfigType = Partial<__SmithyConfiguration<__HttpHandlerOptions>> & diff --git a/clients/client-timestream-query/runtimeConfig.browser.ts b/clients/client-timestream-query/runtimeConfig.browser.ts index 3fe09aa715c6..1a06eace95eb 100644 --- a/clients/client-timestream-query/runtimeConfig.browser.ts +++ b/clients/client-timestream-query/runtimeConfig.browser.ts @@ -25,6 +25,7 @@ export const ClientDefaultValues: Required = { serviceId: ClientSharedValues.serviceId, clientVersion: packageInfo.version, }), + endpointDiscoveryEnabled: undefined, maxAttempts: DEFAULT_MAX_ATTEMPTS, region: invalidProvider("Region is missing"), requestHandler: new FetchHttpHandler(), diff --git a/clients/client-timestream-query/runtimeConfig.ts b/clients/client-timestream-query/runtimeConfig.ts index 877cc2126251..090833324bf9 100644 --- a/clients/client-timestream-query/runtimeConfig.ts +++ b/clients/client-timestream-query/runtimeConfig.ts @@ -4,6 +4,7 @@ import { decorateDefaultCredentialProvider } from "@aws-sdk/client-sts"; import { NODE_REGION_CONFIG_FILE_OPTIONS, NODE_REGION_CONFIG_OPTIONS } from "@aws-sdk/config-resolver"; import { defaultProvider as credentialDefaultProvider } from "@aws-sdk/credential-provider-node"; import { Hash } from "@aws-sdk/hash-node"; +import { NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS } from "@aws-sdk/middleware-endpoint-discovery"; import { NODE_MAX_ATTEMPT_CONFIG_OPTIONS } from "@aws-sdk/middleware-retry"; import { loadConfig as loadNodeConfig } from "@aws-sdk/node-config-provider"; import { NodeHttpHandler, streamCollector } from "@aws-sdk/node-http-handler"; @@ -28,6 +29,7 @@ export const ClientDefaultValues: Required = { serviceId: ClientSharedValues.serviceId, clientVersion: packageInfo.version, }), + endpointDiscoveryEnabled: loadNodeConfig(NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS), maxAttempts: loadNodeConfig(NODE_MAX_ATTEMPT_CONFIG_OPTIONS), region: loadNodeConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS), requestHandler: new NodeHttpHandler(), From ac3aac9af6c5216d0bf9cdef7c8d77d3500c0ee0 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 7 May 2021 15:42:22 +0000 Subject: [PATCH 22/48] chore: update middleware-endpoint-discovery based on isEndpointDiscoveryEnabled --- .../src/getEndpointDiscoveryCommandPlugin.ts | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 2951ee6303f6..9f1c9fa8f4c8 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -37,9 +37,17 @@ export const endpointDiscoveryMiddleware = ( const { client } = config; const { endpointDiscoveryCommandCtor } = client?.config; const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; - const { commandName } = context; + const { clientName, commandName } = context; + const isEndpointDiscoveryEnabled = await config.endpointDiscoveryEnabled(); if (isDiscoveredEndpointRequired) { + // throw error if endpoint discovery is required, and it's explicitly disabled. + if (isEndpointDiscoveryEnabled === false) { + throw new Error( + `Endpoint Discovery is disabled but ${commandName} on ${clientName} requires it.` + + ` Please check your configurations.` + ); + } // call await on Endpoint Discovery API utility so that function blocks // till discovered endpoint is updated in cache await updateDiscoveredEndpointInCache(config, { @@ -48,13 +56,16 @@ export const endpointDiscoveryMiddleware = ( endpointDiscoveryCommandCtor, }); } else { - // Do not call await await on Endpoint Discovery API utility so that function - // does not block, the command will use discovered endpoint, if available. - updateDiscoveredEndpointInCache(config, { - ...middlewareConfig, - commandName, - endpointDiscoveryCommandCtor, - }); + // Discover endpoints only if endpoint discovery is explicitly enabled. + if (isEndpointDiscoveryEnabled) { + // Do not call await await on Endpoint Discovery API utility so that function + // does not block, the command will use discovered endpoint, if available. + updateDiscoveredEndpointInCache(config, { + ...middlewareConfig, + commandName, + endpointDiscoveryCommandCtor, + }); + } } const { request } = args; From 5d52e3a00e75bb7783723a5319bcfb3dffd7a61c Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 7 May 2021 16:05:53 +0000 Subject: [PATCH 23/48] chore: throw error if endpoint is suuplied and endpoint discovery is enabled --- .../src/getEndpointDiscoveryCommandPlugin.ts | 7 +++++++ .../src/resolveEndpointDiscoveryClientConfig.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 9f1c9fa8f4c8..4175e6328410 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -34,6 +34,13 @@ export const endpointDiscoveryMiddleware = ( ): FinalizeHandler => async ( args: FinalizeHandlerArguments ): Promise> => { + if (config.isCustomEndpoint) { + if (config.isClientEndpointDiscoveryEnabled) { + throw new Error(`Custom endpoint is supplied; endpointDiscoveryEnabled must not be true.`); + } + return next(args); + } + const { client } = config; const { endpointDiscoveryCommandCtor } = client?.config; const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 74e34e4b20fa..0ede3c4852b6 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -4,11 +4,13 @@ import { Client, Command, Provider } from "@aws-sdk/types"; export interface EndpointDiscoveryClientInputConfig {} interface PreviouslyResolved { + isCustomEndpoint: boolean; endpointCacheSize?: number; endpointDiscoveryEnabled?: boolean | Provider; } export interface EndpointDiscoveryClientResolvedConfig { + isCustomEndpoint: boolean; client?: Client; endpointDiscoveryCommandCtor?: new (comandConfig: any) => Command; endpointCache: EndpointCache; From 2997d3e2222e0ad3a3e977f0c3299f3f3c7fe975 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 7 May 2021 16:32:35 +0000 Subject: [PATCH 24/48] fix: use endpointDiscoveryEnabledProvider --- .../TimestreamQueryClient.ts | 9 ++---- .../runtimeConfig.browser.ts | 2 +- .../client-timestream-query/runtimeConfig.ts | 2 +- .../resolveEndpointDiscoveryClientConfig.ts | 29 +++++++++++-------- 4 files changed, 22 insertions(+), 20 deletions(-) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index ccebb760f15f..d3d7dfe65fa7 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -161,13 +161,10 @@ export interface ClientDefaults extends Partial<__SmithyResolvedConfiguration<__ defaultUserAgentProvider?: Provider<__UserAgent>; /** - * Returns whether to call operations with endpoints given by service dynamically. - * Setting this config to `true` will enable endpoint discovery for all applicable operations. - * Setting it to `false` will explicitly disable endpoint discovery even though operations that - * require endpoint discovery will presumably fail. Leaving it to undefined means SDK only do - * endpoint discovery when it's required. Defaults to `undefined`. + * The provider which populates default for endpointDisvoveryEnabled configuration, if it's + * not passed during client creation. */ - endpointDiscoveryEnabled: boolean | undefined | __Provider; + endpointDiscoveryEnabledProvider?: Provider; } type TimestreamQueryClientConfigType = Partial<__SmithyConfiguration<__HttpHandlerOptions>> & diff --git a/clients/client-timestream-query/runtimeConfig.browser.ts b/clients/client-timestream-query/runtimeConfig.browser.ts index 1a06eace95eb..9206655b88e7 100644 --- a/clients/client-timestream-query/runtimeConfig.browser.ts +++ b/clients/client-timestream-query/runtimeConfig.browser.ts @@ -25,7 +25,7 @@ export const ClientDefaultValues: Required = { serviceId: ClientSharedValues.serviceId, clientVersion: packageInfo.version, }), - endpointDiscoveryEnabled: undefined, + endpointDiscoveryEnabledProvider: () => Promise.resolve(undefined), maxAttempts: DEFAULT_MAX_ATTEMPTS, region: invalidProvider("Region is missing"), requestHandler: new FetchHttpHandler(), diff --git a/clients/client-timestream-query/runtimeConfig.ts b/clients/client-timestream-query/runtimeConfig.ts index 090833324bf9..6b8b3fead9a5 100644 --- a/clients/client-timestream-query/runtimeConfig.ts +++ b/clients/client-timestream-query/runtimeConfig.ts @@ -29,7 +29,7 @@ export const ClientDefaultValues: Required = { serviceId: ClientSharedValues.serviceId, clientVersion: packageInfo.version, }), - endpointDiscoveryEnabled: loadNodeConfig(NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS), + endpointDiscoveryEnabledProvider: loadNodeConfig(NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS), maxAttempts: loadNodeConfig(NODE_MAX_ATTEMPT_CONFIG_OPTIONS), region: loadNodeConfig(NODE_REGION_CONFIG_OPTIONS, NODE_REGION_CONFIG_FILE_OPTIONS), requestHandler: new NodeHttpHandler(), diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 0ede3c4852b6..1d1e202fc804 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -5,8 +5,20 @@ export interface EndpointDiscoveryClientInputConfig {} interface PreviouslyResolved { isCustomEndpoint: boolean; + /** + * The size of the client cache storing endpoints from endpoint discovery operations. + * Defaults to 1000. + */ endpointCacheSize?: number; - endpointDiscoveryEnabled?: boolean | Provider; + /** + * Whether to call operations with endpoints given by service dynamically. + * Setting this config to `true` will enable endpoint discovery for all applicable operations. + * Setting it to `false` will explicitly disable endpoint discovery even though operations that + * require endpoint discovery will presumably fail. Leaving it to undefined means SDK only do + * endpoint discovery when it's required. Defaults to `undefined`. + */ + endpointDiscoveryEnabled?: boolean | undefined; + endpointDiscoveryEnabledProvider: Provider; } export interface EndpointDiscoveryClientResolvedConfig { @@ -23,16 +35,9 @@ export const resolveEndpointDiscoveryClientConfig = ( ): T & EndpointDiscoveryClientResolvedConfig => ({ ...input, endpointCache: new EndpointCache(input.endpointCacheSize ?? 1000), - endpointDiscoveryEnabled: input.endpointDiscoveryEnabled - ? normalizeEndpointDiscoveryEnabled(input.endpointDiscoveryEnabled) - : () => Promise.resolve(undefined), + endpointDiscoveryEnabled: + input.endpointDiscoveryEnabled !== undefined + ? () => Promise.resolve(input.endpointDiscoveryEnabled) + : input.endpointDiscoveryEnabledProvider, isClientEndpointDiscoveryEnabled: input.endpointDiscoveryEnabled !== undefined, }); - -const normalizeEndpointDiscoveryEnabled = (endpointDiscoveryEnabled: boolean | Provider) => { - if (typeof endpointDiscoveryEnabled === "boolean") { - const promisified = Promise.resolve(endpointDiscoveryEnabled); - return () => promisified; - } - return endpointDiscoveryEnabled as Provider; -}; From ee708e421570c2cf2910753f3899d1e447a3113e Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 7 May 2021 17:17:11 +0000 Subject: [PATCH 25/48] chore: mark endpointDiscoveryEnabledProvider as internal configuration --- clients/client-timestream-query/TimestreamQueryClient.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index d3d7dfe65fa7..c1f2be5b0653 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -163,6 +163,7 @@ export interface ClientDefaults extends Partial<__SmithyResolvedConfiguration<__ /** * The provider which populates default for endpointDisvoveryEnabled configuration, if it's * not passed during client creation. + * @internal */ endpointDiscoveryEnabledProvider?: Provider; } From fa71e9e8b6ccb8254c4345b06d361ba9450d7078 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 8 May 2021 22:06:43 +0000 Subject: [PATCH 26/48] chore: throw descriptive error when endpoint discovery fails --- .../src/updateDiscoveredEndpointInCache.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 469209e9e981..2dc0a738a2e7 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -52,7 +52,13 @@ export const updateDiscoveredEndpointInCache = async ( } if (options.isDiscoveredEndpointRequired) { // Endpoint Discovery is required, rethrow error. - throw error; + throw Object.assign( + new Error( + `The operation to discover endpoint failed.` + + `Please retry, or provide a custom endpoint and disable endpoint discovery to proceed.` + ), + { reason: error } + ); } else { // Endpoint Discovery is optional. No error needs to be thrown. // Set placeHolder endpoint to disable refresh for one minute. From 8ba316a39fc162441b4c8b3fd3005d9d9f5a4061 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 8 May 2021 22:47:59 +0000 Subject: [PATCH 27/48] chore(test): add tests for configurations --- .../src/configurations.ts | 2 +- .../src/confirurations.spec.ts | 51 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 packages/middleware-endpoint-discovery/src/confirurations.spec.ts diff --git a/packages/middleware-endpoint-discovery/src/configurations.ts b/packages/middleware-endpoint-discovery/src/configurations.ts index fdb6ada3e66b..79bc947d86c4 100644 --- a/packages/middleware-endpoint-discovery/src/configurations.ts +++ b/packages/middleware-endpoint-discovery/src/configurations.ts @@ -11,7 +11,7 @@ export const NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS: LoadedConfigSelectors { + describe("environmentVariableSelector", () => { + const ENV_ENDPOINT_DISCOVERY = ["AWS_ENABLE_ENDPOINT_DISCOVERY", "AWS_ENDPOINT_DISCOVERY_ENABLED"]; + describe.each(ENV_ENDPOINT_DISCOVERY)("env key: %p", (envKey) => { + const envValues = {}; + + beforeEach(() => { + ENV_ENDPOINT_DISCOVERY.forEach((envKey) => { + envValues[envKey] = process.env[envKey]; + delete process.env[envKey]; + }); + }); + + afterEach(() => { + ENV_ENDPOINT_DISCOVERY.forEach((envKey) => { + process.env[envKey] = envValues[envKey]; + delete envValues[envKey]; + }); + }); + + describe(`returns false`, () => { + it.each(["false", "0"])("if value stored is %s", (falsyValue) => { + process.env[envKey] = falsyValue; + expect(environmentVariableSelector(process.env)).toEqual(false); + }); + }); + + describe(`returns true`, () => { + it.each(["true", "1", "randomValue"])("if value stored is %s", (truthyValue) => { + process.env[envKey] = truthyValue; + expect(environmentVariableSelector(process.env)).toEqual(true); + }); + }); + + it(`throws error if value stored is empty`, () => { + process.env[envKey] = ""; + expect(() => { + environmentVariableSelector(process.env); + }).toThrow(); + }); + }); + }); +}); From a49d14a4d46852185a85810e4a2632b482a01f01 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 8 May 2021 22:50:50 +0000 Subject: [PATCH 28/48] chore: remove region as cache is not shared across clients --- packages/middleware-endpoint-discovery/src/getCacheKey.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 17a35b073f87..47c00d836d3e 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -11,12 +11,10 @@ export const getCacheKey = async ( identifiers?: Map; } ) => { - const region = await config.region(); const { accessKeyId } = await config.credentials(); const { identifiers } = options; return { commandName, - ...(region && { region }), ...(accessKeyId && { accessKeyId }), ...(identifiers && { identifiers }), }.toString(); From 1ee8ad7aa6eecb74d5d1841c33c1c94792b04b0a Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 8 May 2021 22:51:53 +0000 Subject: [PATCH 29/48] chore: use commandName in cacheKey only if identifiers are defined --- packages/middleware-endpoint-discovery/src/getCacheKey.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 47c00d836d3e..82453897cf40 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -14,8 +14,7 @@ export const getCacheKey = async ( const { accessKeyId } = await config.credentials(); const { identifiers } = options; return { - commandName, ...(accessKeyId && { accessKeyId }), - ...(identifiers && { identifiers }), + ...(identifiers && { commandName, identifiers }), }.toString(); }; From 3db4484593647b7c4cf6d37bf6d59ac426d1c255 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 8 May 2021 22:55:11 +0000 Subject: [PATCH 30/48] chore: ts-ignore undefined check as if already checks it --- packages/middleware-endpoint-discovery/src/configurations.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/middleware-endpoint-discovery/src/configurations.ts b/packages/middleware-endpoint-discovery/src/configurations.ts index 79bc947d86c4..1a9b483aa45f 100644 --- a/packages/middleware-endpoint-discovery/src/configurations.ts +++ b/packages/middleware-endpoint-discovery/src/configurations.ts @@ -14,6 +14,7 @@ export const NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS: LoadedConfigSelectors Date: Sat, 8 May 2021 23:50:39 +0000 Subject: [PATCH 31/48] chore: add test for getCacheKey --- .../src/getCacheKey.spec.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts new file mode 100644 index 000000000000..5af380be6414 --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts @@ -0,0 +1,26 @@ +import { getCacheKey } from "./getCacheKey"; + +describe(getCacheKey.name, () => { + const commandName = "commandName"; + const mockCredentials = { + accessKeyId: "accessKeyId", + secretAccessKey: "secretAccessKey", + }; + + const config = { + credentials: () => Promise.resolve(mockCredentials), + }; + + it("returns accessKeyId in cacheKey", async () => { + // @ts-ignore is missing the following properties from type 'AwsAuthResolvedConfig' + const cacheKey = await getCacheKey(commandName, config, {}); + expect(cacheKey).toEqual({ accessKeyId: mockCredentials.accessKeyId }.toString()); + }); + + it("returns commandName and identifiers if passed", async () => { + const identifiers = { key: "value" }; + // @ts-ignore is missing the following properties from type 'AwsAuthResolvedConfig' + const cacheKey = await getCacheKey(commandName, config, { identifiers }); + expect(cacheKey).toEqual({ accessKeyId: mockCredentials.accessKeyId, commandName, identifiers }.toString()); + }); +}); From f9fcf07a1c92c76bf86b4469c292dc037200d94f Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sat, 8 May 2021 23:51:13 +0000 Subject: [PATCH 32/48] chore: fix typo in configurations test file --- .../src/{confirurations.spec.ts => configurations.spec.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/middleware-endpoint-discovery/src/{confirurations.spec.ts => configurations.spec.ts} (100%) diff --git a/packages/middleware-endpoint-discovery/src/confirurations.spec.ts b/packages/middleware-endpoint-discovery/src/configurations.spec.ts similarity index 100% rename from packages/middleware-endpoint-discovery/src/confirurations.spec.ts rename to packages/middleware-endpoint-discovery/src/configurations.spec.ts From 1c2f369ee0fd180c2b00286aa907152262efd407 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 00:12:31 +0000 Subject: [PATCH 33/48] test: add resolveEndpointDiscoveryClientConfig.spec.ts --- ...solveEndpointDiscoveryClientConfig.spec.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts new file mode 100644 index 000000000000..ba1dccaf3d4a --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts @@ -0,0 +1,56 @@ +import { EndpointCache } from "@aws-sdk/endpoint-cache"; + +import { resolveEndpointDiscoveryClientConfig } from "./resolveEndpointDiscoveryClientConfig"; + +jest.mock("@aws-sdk/endpoint-cache"); + +describe(resolveEndpointDiscoveryClientConfig.name, () => { + const isCustomEndpoint = false; + const endpointDiscoveryEnabledProvider = jest.fn().mockResolvedValue(undefined); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe("endpointCache", () => { + it("creates cache of size endpointCacheSize if passed", () => { + const endpointCacheSize = 100; + resolveEndpointDiscoveryClientConfig({ + isCustomEndpoint, + endpointCacheSize, + endpointDiscoveryEnabledProvider, + }); + expect(EndpointCache).toBeCalledWith(endpointCacheSize); + }); + + it("creates cache of size 1000 if endpointCacheSize not passed", () => { + resolveEndpointDiscoveryClientConfig({ + isCustomEndpoint, + endpointDiscoveryEnabledProvider, + }); + expect(EndpointCache).toBeCalledWith(1000); + }); + }); + + describe("endpointDiscoveryEnabled", () => { + it.each([false, true])(`sets to value passed in the config: %s`, (endpointDiscoveryEnabled) => { + const resolvedConfig = resolveEndpointDiscoveryClientConfig({ + isCustomEndpoint, + endpointDiscoveryEnabled, + endpointDiscoveryEnabledProvider, + }); + expect(resolvedConfig.endpointDiscoveryEnabled()).resolves.toBe(endpointDiscoveryEnabled); + expect(endpointDiscoveryEnabledProvider).not.toHaveBeenCalled(); + expect(resolvedConfig.isClientEndpointDiscoveryEnabled).toStrictEqual(true); + }); + + it(`sets to endpointDiscoveryEnabledProvider if value is not passed`, () => { + const resolvedConfig = resolveEndpointDiscoveryClientConfig({ + isCustomEndpoint, + endpointDiscoveryEnabledProvider, + }); + expect(resolvedConfig.endpointDiscoveryEnabled).toBe(endpointDiscoveryEnabledProvider); + expect(resolvedConfig.isClientEndpointDiscoveryEnabled).toStrictEqual(false); + }); + }); +}); From 2179fb7c806b5554c1baf3bfcf94308b2c1c6fd0 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 00:27:48 +0000 Subject: [PATCH 34/48] test: add configSelector and defaultSelector tests --- .../src/configurations.spec.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/middleware-endpoint-discovery/src/configurations.spec.ts b/packages/middleware-endpoint-discovery/src/configurations.spec.ts index 80d94d0b4fe7..b1d1992c254d 100644 --- a/packages/middleware-endpoint-discovery/src/configurations.spec.ts +++ b/packages/middleware-endpoint-discovery/src/configurations.spec.ts @@ -34,12 +34,16 @@ describe("NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS", () => { }); describe(`returns true`, () => { - it.each(["true", "1", "randomValue"])("if value stored is %s", (truthyValue) => { + it.each(["true", "1", "non-empty-value"])("if value stored is %s", (truthyValue) => { process.env[envKey] = truthyValue; expect(environmentVariableSelector(process.env)).toEqual(true); }); }); + it(`returns undefined if value is not stored`, () => { + expect(environmentVariableSelector(process.env)).not.toBeDefined(); + }); + it(`throws error if value stored is empty`, () => { process.env[envKey] = ""; expect(() => { @@ -48,4 +52,34 @@ describe("NODE_ENDPOINT_DISCOVERY_CONFIG_OPTIONS", () => { }); }); }); + + describe("configFileSelector", () => { + const CONFIG_ENDPOINT_DISCOVERY = "endpoint_discovery_enabled"; + + describe(`returns false`, () => { + it.each(["false", "0"])("if value stored is %s", (falsyValue) => { + expect(configFileSelector({ [CONFIG_ENDPOINT_DISCOVERY]: falsyValue })).toEqual(false); + }); + }); + + describe(`returns true`, () => { + it.each(["true", "1", "non-empty-value"])("if value stored is %s", (truthyValue) => { + expect(configFileSelector({ [CONFIG_ENDPOINT_DISCOVERY]: truthyValue })).toEqual(true); + }); + }); + + it(`returns undefined if value is not available`, () => { + expect(configFileSelector({})).not.toBeDefined(); + }); + + it(`throws if value stored is undefined`, () => { + expect(() => { + configFileSelector({ [CONFIG_ENDPOINT_DISCOVERY]: undefined }); + }).toThrow(); + }); + }); + + it("defaultSelector returns undefined", () => { + expect(defaultSelector).toBeUndefined(); + }); }); From 27b9f7574479d08d860e235d45d260d668f5f027 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 04:28:37 +0000 Subject: [PATCH 35/48] test: asrc/updateDiscoveredEndpointInCache.spec.ts --- .../updateDiscoveredEndpointInCache.spec.ts | 173 ++++++++++++++++++ .../src/updateDiscoveredEndpointInCache.ts | 8 +- 2 files changed, 176 insertions(+), 5 deletions(-) create mode 100644 packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts new file mode 100644 index 000000000000..60ea4861c007 --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts @@ -0,0 +1,173 @@ +import { getCacheKey } from "./getCacheKey"; +import { updateDiscoveredEndpointInCache } from "./updateDiscoveredEndpointInCache"; + +jest.mock("./getCacheKey"); + +describe(updateDiscoveredEndpointInCache.name, () => { + const cacheKey = "cacheKey"; + const mockGet = jest.fn(); + const mockSet = jest.fn(); + const mockDelete = jest.fn(); + const mockSend = jest.fn(); + + const mockEndpoints = [{ Address: "mockAddress", CachePeriodInMinutes: 1 }]; + const placeholderEndpoints = [{ Address: "", CachePeriodInMinutes: 1 }]; + + const config = { + client: { + send: mockSend, + config: {}, + }, + endpointCache: { get: mockGet, set: mockSet, delete: mockDelete }, + }; + + const options = { + commandName: "ExampleCommand", + endpointDiscoveryCommandCtor: jest.fn(), + isDiscoveredEndpointRequired: false, + identifiers: { key: "value" }, + }; + + beforeEach(() => { + (getCacheKey as jest.Mock).mockResolvedValue(cacheKey); + mockGet.mockReturnValue(mockEndpoints); + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it(`returns if endpoints are present in cacheKey`, async () => { + // @ts-ignore + await updateDiscoveredEndpointInCache(config, options); + expect(mockGet).toHaveBeenCalledWith(cacheKey); + expect(mockSet).not.toHaveBeenCalled(); + expect(mockDelete).not.toHaveBeenCalled(); + }); + + it.each([1, 2, 3])(`waits for one minute %d times if endpoints in cache are placeholder endpoints`, async (num) => { + expect.assertions(num + 4); + for (let i = 0; i < num; i++) { + mockGet.mockReturnValueOnce(placeholderEndpoints); + } + mockGet.mockReturnValueOnce(mockEndpoints); + + // @ts-ignore + updateDiscoveredEndpointInCache(config, options).then(() => { + expect(mockGet).toHaveBeenCalledTimes(num + 1); + expect(mockSet).not.toHaveBeenCalled(); + expect(mockDelete).not.toHaveBeenCalled(); + + expect(setTimeout).toHaveBeenCalledTimes(num); + for (let i = 0; i < num; i++) { + expect(setTimeout).toHaveBeenNthCalledWith(i + 1, expect.any(Function), 60 * 1000); + } + }); + + for (let i = 0; i <= num * 3; i++) { + jest.advanceTimersByTime(60 * 1000); + await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run + } + }); + + describe("calls endpointDiscoveryCommandCtor", () => { + beforeEach(() => { + mockGet.mockReturnValue(undefined); + }); + + const verifyCallsOnCacheUndefined = () => { + expect(mockGet).toHaveBeenCalledTimes(1); + + expect(options.endpointDiscoveryCommandCtor).toHaveBeenCalledWith({ + Operation: options.commandName.substr(0, options.commandName.length - 7), + Identifiers: options.identifiers, + }); + expect(mockSend).toHaveBeenCalledTimes(1); + }; + + it("on successful call: updates cache", async () => { + mockSend.mockResolvedValueOnce({ Endpoints: mockEndpoints }); + + // @ts-ignore + await updateDiscoveredEndpointInCache(config, options); + + verifyCallsOnCacheUndefined(); + expect(mockDelete).not.toHaveBeenCalled(); + expect(mockSet).toHaveBeenCalledTimes(2); + expect(mockSet).toHaveBeenNthCalledWith(1, cacheKey, placeholderEndpoints); + expect(mockSet).toHaveBeenNthCalledWith(2, cacheKey, mockEndpoints); + }); + + describe("on error", () => { + it(`throws if isDiscoveredEndpointRequired=true`, async () => { + const error = new Error("rejected"); + mockSend.mockRejectedValueOnce(error); + + try { + // @ts-ignore + await updateDiscoveredEndpointInCache(config, { ...options, isDiscoveredEndpointRequired: true }); + fail("updateDiscoveredEndpointInCache should throw"); + } catch (error) { + expect(error).toEqual( + Object.assign( + new Error( + `The operation to discover endpoint failed.` + + ` Please retry, or provide a custom endpoint and disable endpoint discovery to proceed.` + ), + { reason: error } + ) + ); + } + verifyCallsOnCacheUndefined(); + expect(mockDelete).not.toHaveBeenCalled(); + expect(mockSet).toHaveBeenCalledTimes(1); + expect(mockSet).toHaveBeenCalledWith(cacheKey, placeholderEndpoints); + }); + + it(`sets placeholder enpoint if isDiscoveredEndpointRequired=false`, async () => { + const error = new Error("rejected"); + mockSend.mockRejectedValueOnce(error); + + // @ts-ignore + await updateDiscoveredEndpointInCache(config, options); + + verifyCallsOnCacheUndefined(); + expect(mockDelete).not.toHaveBeenCalled(); + expect(mockSet).toHaveBeenCalledTimes(2); + expect(mockSet).toHaveBeenNthCalledWith(1, cacheKey, placeholderEndpoints); + expect(mockSet).toHaveBeenNthCalledWith(2, cacheKey, placeholderEndpoints); + }); + + describe(`deletes cacheKey in case of`, () => { + const verifyCallsOnCacheKeyDelete = () => { + expect(mockDelete).toHaveBeenCalledWith(cacheKey); + expect(mockSet).toHaveBeenCalledTimes(2); + expect(mockSet).toHaveBeenNthCalledWith(1, cacheKey, placeholderEndpoints); + expect(mockSet).toHaveBeenNthCalledWith(2, cacheKey, placeholderEndpoints); + }; + + it(`InvalidEndpointException`, async () => { + const error = Object.assign(new Error("Invalid endpoint!"), { name: "InvalidEndpointException" }); + mockSend.mockRejectedValueOnce(error); + + // @ts-ignore + await updateDiscoveredEndpointInCache(config, options); + verifyCallsOnCacheUndefined(); + verifyCallsOnCacheKeyDelete(); + }); + + it(`Status code: 421`, async () => { + const error = Object.assign(new Error("Invalid endpoint!"), { $metadata: { httpStatusCode: 421 } }); + mockSend.mockRejectedValueOnce(error); + + // @ts-ignore + await updateDiscoveredEndpointInCache(config, options); + verifyCallsOnCacheUndefined(); + verifyCallsOnCacheKeyDelete(); + }); + }); + }); + }); +}); diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 2dc0a738a2e7..35897a26c0f4 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -15,9 +15,7 @@ export const updateDiscoveredEndpointInCache = async ( config: EndpointDiscoveryClientResolvedConfig, options: updateDiscoveredEndpointInCacheOptions ) => { - const { client } = config; - const { endpointCache } = client?.config; - + const { client, endpointCache } = config; const { commandName, identifiers } = options; const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); @@ -25,7 +23,7 @@ export const updateDiscoveredEndpointInCache = async ( // Wait for other endpoint operations to complete before making new calls. while (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { - await sleep(1000); + await sleep(60 * 1000); endpoints = endpointCache.get(cacheKey); } @@ -55,7 +53,7 @@ export const updateDiscoveredEndpointInCache = async ( throw Object.assign( new Error( `The operation to discover endpoint failed.` + - `Please retry, or provide a custom endpoint and disable endpoint discovery to proceed.` + ` Please retry, or provide a custom endpoint and disable endpoint discovery to proceed.` ), { reason: error } ); From a1242dab100e31abfec03ee95d33db4199c73d23 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 14:40:11 +0000 Subject: [PATCH 36/48] test: src/getEndpointDiscoveryCommandPlugin.spec.ts --- .../src/endpointDiscoveryMiddleware.ts | 75 ++++++++++++++++++ .../getEndpointDiscoveryCommandPlugin.spec.ts | 35 +++++++++ .../src/getEndpointDiscoveryCommandPlugin.ts | 76 +------------------ 3 files changed, 112 insertions(+), 74 deletions(-) create mode 100644 packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts create mode 100644 packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.spec.ts diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts new file mode 100644 index 000000000000..ff5f00367c4c --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts @@ -0,0 +1,75 @@ +import { HttpRequest } from "@aws-sdk/protocol-http"; +import { + FinalizeHandler, + FinalizeHandlerArguments, + FinalizeHandlerOutput, + HandlerExecutionContext, + MetadataBearer, +} from "@aws-sdk/types"; + +import { getCacheKey } from "./getCacheKey"; +import { EndpointDiscoveryMiddlewareConfig } from "./getEndpointDiscoveryCommandPlugin"; +import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; +import { updateDiscoveredEndpointInCache } from "./updateDiscoveredEndpointInCache"; + +export const endpointDiscoveryMiddleware = ( + config: EndpointDiscoveryClientResolvedConfig, + middlewareConfig: EndpointDiscoveryMiddlewareConfig +) => ( + next: FinalizeHandler, + context: HandlerExecutionContext +): FinalizeHandler => async ( + args: FinalizeHandlerArguments +): Promise> => { + if (config.isCustomEndpoint) { + if (config.isClientEndpointDiscoveryEnabled) { + throw new Error(`Custom endpoint is supplied; endpointDiscoveryEnabled must not be true.`); + } + return next(args); + } + + const { client } = config; + const { endpointDiscoveryCommandCtor } = client?.config; + const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; + const { clientName, commandName } = context; + const isEndpointDiscoveryEnabled = await config.endpointDiscoveryEnabled(); + + if (isDiscoveredEndpointRequired) { + // throw error if endpoint discovery is required, and it's explicitly disabled. + if (isEndpointDiscoveryEnabled === false) { + throw new Error( + `Endpoint Discovery is disabled but ${commandName} on ${clientName} requires it.` + + ` Please check your configurations.` + ); + } + // call await on Endpoint Discovery API utility so that function blocks + // till discovered endpoint is updated in cache + await updateDiscoveredEndpointInCache(config, { + ...middlewareConfig, + commandName, + endpointDiscoveryCommandCtor, + }); + } else { + // Discover endpoints only if endpoint discovery is explicitly enabled. + if (isEndpointDiscoveryEnabled) { + // Do not call await await on Endpoint Discovery API utility so that function + // does not block, the command will use discovered endpoint, if available. + updateDiscoveredEndpointInCache(config, { + ...middlewareConfig, + commandName, + endpointDiscoveryCommandCtor, + }); + } + } + + const { request } = args; + const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); + if (cacheKey && HttpRequest.isInstance(request)) { + const endpoint = client?.config.endpointCache.getEndpoint(cacheKey); + if (endpoint) { + request.hostname = endpoint; + } + } + + return next(args); +}; diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.spec.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.spec.ts new file mode 100644 index 000000000000..ab09c1f86fdb --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.spec.ts @@ -0,0 +1,35 @@ +import { endpointDiscoveryMiddleware } from "./endpointDiscoveryMiddleware"; +import { + endpointDiscoveryMiddlewareOptions, + getEndpointDiscoveryCommandPlugin, +} from "./getEndpointDiscoveryCommandPlugin"; + +jest.mock("./endpointDiscoveryMiddleware"); + +describe(getEndpointDiscoveryCommandPlugin.name, () => { + const pluginConfig = { + isCustomEndpoint: false, + endpointCache: jest.fn(), + endpointDiscoveryEnabled: jest.fn(), + isClientEndpointDiscoveryEnabled: false, + }; + const middlewareConfig = { + isDiscoveredEndpointRequired: false, + }; + + it(`applyToStack function adds endpoint discovery middleware`, () => { + const middlewareReturn = {}; + (endpointDiscoveryMiddleware as jest.Mock).mockReturnValueOnce(middlewareReturn); + + // @ts-ignore + const plugin = getEndpointDiscoveryCommandPlugin(pluginConfig, middlewareConfig); + const commandStack = { add: jest.fn() }; + + // @ts-ignore + plugin.applyToStack(commandStack); + expect(commandStack.add).toHaveBeenCalled(); + expect(commandStack.add).toHaveBeenCalledWith(middlewareReturn, endpointDiscoveryMiddlewareOptions); + expect(endpointDiscoveryMiddleware).toHaveBeenCalled(); + expect(endpointDiscoveryMiddleware).toHaveBeenCalledWith(pluginConfig, middlewareConfig); + }); +}); diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 4175e6328410..752316e8734f 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -1,17 +1,7 @@ -import { HttpRequest } from "@aws-sdk/protocol-http"; -import { - FinalizeHandler, - FinalizeHandlerArguments, - FinalizeHandlerOutput, - FinalizeRequestHandlerOptions, - HandlerExecutionContext, - MetadataBearer, - Pluggable, -} from "@aws-sdk/types"; +import { FinalizeRequestHandlerOptions, Pluggable } from "@aws-sdk/types"; -import { getCacheKey } from "./getCacheKey"; +import { endpointDiscoveryMiddleware } from "./endpointDiscoveryMiddleware"; import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; -import { updateDiscoveredEndpointInCache } from "./updateDiscoveredEndpointInCache"; export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = { name: "endpointDiscoveryMiddleware", @@ -25,68 +15,6 @@ export type EndpointDiscoveryMiddlewareConfig = { identifiers?: Map; }; -export const endpointDiscoveryMiddleware = ( - config: EndpointDiscoveryClientResolvedConfig, - middlewareConfig: EndpointDiscoveryMiddlewareConfig -) => ( - next: FinalizeHandler, - context: HandlerExecutionContext -): FinalizeHandler => async ( - args: FinalizeHandlerArguments -): Promise> => { - if (config.isCustomEndpoint) { - if (config.isClientEndpointDiscoveryEnabled) { - throw new Error(`Custom endpoint is supplied; endpointDiscoveryEnabled must not be true.`); - } - return next(args); - } - - const { client } = config; - const { endpointDiscoveryCommandCtor } = client?.config; - const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; - const { clientName, commandName } = context; - const isEndpointDiscoveryEnabled = await config.endpointDiscoveryEnabled(); - - if (isDiscoveredEndpointRequired) { - // throw error if endpoint discovery is required, and it's explicitly disabled. - if (isEndpointDiscoveryEnabled === false) { - throw new Error( - `Endpoint Discovery is disabled but ${commandName} on ${clientName} requires it.` + - ` Please check your configurations.` - ); - } - // call await on Endpoint Discovery API utility so that function blocks - // till discovered endpoint is updated in cache - await updateDiscoveredEndpointInCache(config, { - ...middlewareConfig, - commandName, - endpointDiscoveryCommandCtor, - }); - } else { - // Discover endpoints only if endpoint discovery is explicitly enabled. - if (isEndpointDiscoveryEnabled) { - // Do not call await await on Endpoint Discovery API utility so that function - // does not block, the command will use discovered endpoint, if available. - updateDiscoveredEndpointInCache(config, { - ...middlewareConfig, - commandName, - endpointDiscoveryCommandCtor, - }); - } - } - - const { request } = args; - const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); - if (cacheKey && HttpRequest.isInstance(request)) { - const endpoint = client?.config.endpointCache.getEndpoint(cacheKey); - if (endpoint) { - request.hostname = endpoint; - } - } - - return next(args); -}; - export const getEndpointDiscoveryCommandPlugin = ( pluginConfig: EndpointDiscoveryClientResolvedConfig, middlewareConfig: EndpointDiscoveryMiddlewareConfig From fe35899d7a43f16de865cff9d32d191ee0d483e0 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 17:03:02 +0000 Subject: [PATCH 37/48] chore: simplify configuration for getCacheKey --- packages/middleware-endpoint-discovery/package.json | 1 - .../middleware-endpoint-discovery/src/getCacheKey.spec.ts | 4 +--- packages/middleware-endpoint-discovery/src/getCacheKey.ts | 5 ++--- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/middleware-endpoint-discovery/package.json b/packages/middleware-endpoint-discovery/package.json index 6d17bbcb3842..f981a3e7e824 100644 --- a/packages/middleware-endpoint-discovery/package.json +++ b/packages/middleware-endpoint-discovery/package.json @@ -25,7 +25,6 @@ "dependencies": { "@aws-sdk/config-resolver": "3.14.0", "@aws-sdk/endpoint-cache": "3.0.0", - "@aws-sdk/middleware-signing": "3.13.1", "@aws-sdk/protocol-http": "3.13.1", "@aws-sdk/types": "3.13.1", "tslib": "^2.0.0" diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts index 5af380be6414..55bac5ebf3c9 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts @@ -12,14 +12,12 @@ describe(getCacheKey.name, () => { }; it("returns accessKeyId in cacheKey", async () => { - // @ts-ignore is missing the following properties from type 'AwsAuthResolvedConfig' const cacheKey = await getCacheKey(commandName, config, {}); expect(cacheKey).toEqual({ accessKeyId: mockCredentials.accessKeyId }.toString()); }); it("returns commandName and identifiers if passed", async () => { - const identifiers = { key: "value" }; - // @ts-ignore is missing the following properties from type 'AwsAuthResolvedConfig' + const identifiers = new Map().set("key", "string"); const cacheKey = await getCacheKey(commandName, config, { identifiers }); expect(cacheKey).toEqual({ accessKeyId: mockCredentials.accessKeyId, commandName, identifiers }.toString()); }); diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 82453897cf40..561bfce5d1ea 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -1,12 +1,11 @@ -import { RegionResolvedConfig } from "@aws-sdk/config-resolver"; -import { AwsAuthResolvedConfig } from "@aws-sdk/middleware-signing"; +import { Credentials, Provider } from "@aws-sdk/types"; /** * Generate key to index the endpoints in the cache */ export const getCacheKey = async ( commandName: string, - config: AwsAuthResolvedConfig & RegionResolvedConfig, + config: { credentials: Provider }, options: { identifiers?: Map; } From 04e1741026a2dc14b7b876d3f69a50f2aa1e2e9b Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 17:03:40 +0000 Subject: [PATCH 38/48] chore: read endpointDiscoveryCommandCtor from EndpointDiscoveryClientResolvedConfig --- .../src/endpointDiscoveryMiddleware.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts index ff5f00367c4c..38875566baf8 100644 --- a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts @@ -28,8 +28,7 @@ export const endpointDiscoveryMiddleware = ( return next(args); } - const { client } = config; - const { endpointDiscoveryCommandCtor } = client?.config; + const { client, endpointDiscoveryCommandCtor } = config; const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; const { clientName, commandName } = context; const isEndpointDiscoveryEnabled = await config.endpointDiscoveryEnabled(); @@ -47,7 +46,7 @@ export const endpointDiscoveryMiddleware = ( await updateDiscoveredEndpointInCache(config, { ...middlewareConfig, commandName, - endpointDiscoveryCommandCtor, + endpointDiscoveryCommandCtor: endpointDiscoveryCommandCtor!, }); } else { // Discover endpoints only if endpoint discovery is explicitly enabled. @@ -57,7 +56,7 @@ export const endpointDiscoveryMiddleware = ( updateDiscoveredEndpointInCache(config, { ...middlewareConfig, commandName, - endpointDiscoveryCommandCtor, + endpointDiscoveryCommandCtor: endpointDiscoveryCommandCtor!, }); } } From 6deeb4854e06e2f445dfb319fb389c3c88d4fbab Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 17:44:11 +0000 Subject: [PATCH 39/48] fix: sort identifiers keys before stringifying --- .../src/getCacheKey.spec.ts | 17 ++++++++++++++--- .../src/getCacheKey.ts | 13 +++++++++---- .../src/getEndpointDiscoveryCommandPlugin.ts | 2 +- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts index 55bac5ebf3c9..1ed063f6808c 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts @@ -13,12 +13,23 @@ describe(getCacheKey.name, () => { it("returns accessKeyId in cacheKey", async () => { const cacheKey = await getCacheKey(commandName, config, {}); - expect(cacheKey).toEqual({ accessKeyId: mockCredentials.accessKeyId }.toString()); + expect(cacheKey).toEqual(JSON.stringify({ accessKeyId: mockCredentials.accessKeyId })); }); it("returns commandName and identifiers if passed", async () => { - const identifiers = new Map().set("key", "string"); + const identifiers = new Map().set("key", "value"); + const identifiersObj = { key: "value" }; const cacheKey = await getCacheKey(commandName, config, { identifiers }); - expect(cacheKey).toEqual({ accessKeyId: mockCredentials.accessKeyId, commandName, identifiers }.toString()); + expect(cacheKey).toEqual( + JSON.stringify({ accessKeyId: mockCredentials.accessKeyId, commandName, identifiers: identifiersObj }) + ); + }); + + it("returns same cache key irrespective of key order in identifiers", async () => { + const identifiers1 = new Map().set("key1", "value1").set("key2", "value2"); + const cacheKey1 = await getCacheKey(commandName, config, { identifiers: identifiers1 }); + const identifiers2 = new Map().set("key2", "value2").set("key1", "value1"); + const cacheKey2 = await getCacheKey(commandName, config, { identifiers: identifiers2 }); + expect(cacheKey1).toStrictEqual(cacheKey2); }); }); diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index 561bfce5d1ea..da6e68ee1162 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -7,13 +7,18 @@ export const getCacheKey = async ( commandName: string, config: { credentials: Provider }, options: { - identifiers?: Map; + identifiers?: Map; } ) => { const { accessKeyId } = await config.credentials(); const { identifiers } = options; - return { + return JSON.stringify({ ...(accessKeyId && { accessKeyId }), - ...(identifiers && { commandName, identifiers }), - }.toString(); + ...(identifiers && { + commandName, + identifiers: Array.from(identifiers) + .sort() + .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), + }), + }); }; diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 752316e8734f..b856bc0cc68b 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -12,7 +12,7 @@ export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = export type EndpointDiscoveryMiddlewareConfig = { isDiscoveredEndpointRequired: boolean; - identifiers?: Map; + identifiers?: Map; }; export const getEndpointDiscoveryCommandPlugin = ( From ac232ceea7b1e1b322791d2f59c9712d87dadf4a Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 18:06:59 +0000 Subject: [PATCH 40/48] fix: check for endpoints in cache every second --- .../src/updateDiscoveredEndpointInCache.spec.ts | 4 ++-- .../src/updateDiscoveredEndpointInCache.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts index 60ea4861c007..2b0450dcf195 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts @@ -62,12 +62,12 @@ describe(updateDiscoveredEndpointInCache.name, () => { expect(setTimeout).toHaveBeenCalledTimes(num); for (let i = 0; i < num; i++) { - expect(setTimeout).toHaveBeenNthCalledWith(i + 1, expect.any(Function), 60 * 1000); + expect(setTimeout).toHaveBeenNthCalledWith(i + 1, expect.any(Function), 1000); } }); for (let i = 0; i <= num * 3; i++) { - jest.advanceTimersByTime(60 * 1000); + jest.advanceTimersByTime(1000); await Promise.resolve(); // allow any pending jobs in the PromiseJobs queue to run } }); diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 35897a26c0f4..09fbbdda5c88 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -23,7 +23,7 @@ export const updateDiscoveredEndpointInCache = async ( // Wait for other endpoint operations to complete before making new calls. while (endpoints && endpoints.length === 1 && endpoints[0].Address === "") { - await sleep(60 * 1000); + await sleep(1000); endpoints = endpointCache.get(cacheKey); } From 0a2b5e567f4c2872534892c0329f759a3681dafc Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 18:11:13 +0000 Subject: [PATCH 41/48] chore: pass config from EndpointDiscoveryClientResolvedConfig to getCacheKey --- .../src/endpointDiscoveryMiddleware.ts | 2 +- .../src/resolveEndpointDiscoveryClientConfig.spec.ts | 5 +++++ .../src/resolveEndpointDiscoveryClientConfig.ts | 4 +++- .../src/updateDiscoveredEndpointInCache.ts | 2 +- 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts index 38875566baf8..ef5585c02253 100644 --- a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts @@ -62,7 +62,7 @@ export const endpointDiscoveryMiddleware = ( } const { request } = args; - const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); + const cacheKey = await getCacheKey(commandName, config, { identifiers }); if (cacheKey && HttpRequest.isInstance(request)) { const endpoint = client?.config.endpointCache.getEndpoint(cacheKey); if (endpoint) { diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts index ba1dccaf3d4a..9a60db7c1d0c 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts @@ -6,6 +6,7 @@ jest.mock("@aws-sdk/endpoint-cache"); describe(resolveEndpointDiscoveryClientConfig.name, () => { const isCustomEndpoint = false; + const credentials = jest.fn(); const endpointDiscoveryEnabledProvider = jest.fn().mockResolvedValue(undefined); afterEach(() => { @@ -17,6 +18,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { const endpointCacheSize = 100; resolveEndpointDiscoveryClientConfig({ isCustomEndpoint, + credentials, endpointCacheSize, endpointDiscoveryEnabledProvider, }); @@ -26,6 +28,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { it("creates cache of size 1000 if endpointCacheSize not passed", () => { resolveEndpointDiscoveryClientConfig({ isCustomEndpoint, + credentials, endpointDiscoveryEnabledProvider, }); expect(EndpointCache).toBeCalledWith(1000); @@ -36,6 +39,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { it.each([false, true])(`sets to value passed in the config: %s`, (endpointDiscoveryEnabled) => { const resolvedConfig = resolveEndpointDiscoveryClientConfig({ isCustomEndpoint, + credentials, endpointDiscoveryEnabled, endpointDiscoveryEnabledProvider, }); @@ -47,6 +51,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { it(`sets to endpointDiscoveryEnabledProvider if value is not passed`, () => { const resolvedConfig = resolveEndpointDiscoveryClientConfig({ isCustomEndpoint, + credentials, endpointDiscoveryEnabledProvider, }); expect(resolvedConfig.endpointDiscoveryEnabled).toBe(endpointDiscoveryEnabledProvider); diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts index 1d1e202fc804..2dffedcf2654 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts @@ -1,10 +1,11 @@ import { EndpointCache } from "@aws-sdk/endpoint-cache"; -import { Client, Command, Provider } from "@aws-sdk/types"; +import { Client, Command, Credentials, Provider } from "@aws-sdk/types"; export interface EndpointDiscoveryClientInputConfig {} interface PreviouslyResolved { isCustomEndpoint: boolean; + credentials: Provider; /** * The size of the client cache storing endpoints from endpoint discovery operations. * Defaults to 1000. @@ -23,6 +24,7 @@ interface PreviouslyResolved { export interface EndpointDiscoveryClientResolvedConfig { isCustomEndpoint: boolean; + credentials: Provider; client?: Client; endpointDiscoveryCommandCtor?: new (comandConfig: any) => Command; endpointCache: EndpointCache; diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 09fbbdda5c88..0f0ae2aef957 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -17,7 +17,7 @@ export const updateDiscoveredEndpointInCache = async ( ) => { const { client, endpointCache } = config; const { commandName, identifiers } = options; - const cacheKey = await getCacheKey(commandName, client?.config, { identifiers }); + const cacheKey = await getCacheKey(commandName, config, { identifiers }); let endpoints = endpointCache.get(cacheKey); From 316b8b6e676dfc2e2fe28bbdfa96c4d0b1d77c54 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 18:13:32 +0000 Subject: [PATCH 42/48] chore: use endpointCache from EndpointDiscoveryClientResolvedConfig --- .../src/endpointDiscoveryMiddleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts index ef5585c02253..fc6ad41da577 100644 --- a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts @@ -64,7 +64,7 @@ export const endpointDiscoveryMiddleware = ( const { request } = args; const cacheKey = await getCacheKey(commandName, config, { identifiers }); if (cacheKey && HttpRequest.isInstance(request)) { - const endpoint = client?.config.endpointCache.getEndpoint(cacheKey); + const endpoint = config.endpointCache.getEndpoint(cacheKey); if (endpoint) { request.hostname = endpoint; } From 49ad57349a5415f0feaf5c9b078bb203d1bf8f04 Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 18:26:54 +0000 Subject: [PATCH 43/48] fix: use object for identifiers --- .../src/getCacheKey.spec.ts | 11 ++++------- .../middleware-endpoint-discovery/src/getCacheKey.ts | 4 ++-- .../src/getEndpointDiscoveryCommandPlugin.ts | 2 +- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts index 1ed063f6808c..03d4c72811fd 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.spec.ts @@ -17,18 +17,15 @@ describe(getCacheKey.name, () => { }); it("returns commandName and identifiers if passed", async () => { - const identifiers = new Map().set("key", "value"); - const identifiersObj = { key: "value" }; + const identifiers = { key: "value" }; const cacheKey = await getCacheKey(commandName, config, { identifiers }); - expect(cacheKey).toEqual( - JSON.stringify({ accessKeyId: mockCredentials.accessKeyId, commandName, identifiers: identifiersObj }) - ); + expect(cacheKey).toEqual(JSON.stringify({ accessKeyId: mockCredentials.accessKeyId, commandName, identifiers })); }); it("returns same cache key irrespective of key order in identifiers", async () => { - const identifiers1 = new Map().set("key1", "value1").set("key2", "value2"); + const identifiers1 = { key1: "value1", key2: "value2" }; const cacheKey1 = await getCacheKey(commandName, config, { identifiers: identifiers1 }); - const identifiers2 = new Map().set("key2", "value2").set("key1", "value1"); + const identifiers2 = { key2: "value2", key1: "value1" }; const cacheKey2 = await getCacheKey(commandName, config, { identifiers: identifiers2 }); expect(cacheKey1).toStrictEqual(cacheKey2); }); diff --git a/packages/middleware-endpoint-discovery/src/getCacheKey.ts b/packages/middleware-endpoint-discovery/src/getCacheKey.ts index da6e68ee1162..09b36a098425 100644 --- a/packages/middleware-endpoint-discovery/src/getCacheKey.ts +++ b/packages/middleware-endpoint-discovery/src/getCacheKey.ts @@ -7,7 +7,7 @@ export const getCacheKey = async ( commandName: string, config: { credentials: Provider }, options: { - identifiers?: Map; + identifiers?: { [key: string]: string }; } ) => { const { accessKeyId } = await config.credentials(); @@ -16,7 +16,7 @@ export const getCacheKey = async ( ...(accessKeyId && { accessKeyId }), ...(identifiers && { commandName, - identifiers: Array.from(identifiers) + identifiers: Object.entries(identifiers) .sort() .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}), }), diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index b856bc0cc68b..a957564bb1aa 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -12,7 +12,7 @@ export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = export type EndpointDiscoveryMiddlewareConfig = { isDiscoveredEndpointRequired: boolean; - identifiers?: Map; + identifiers?: { [key: string]: string }; }; export const getEndpointDiscoveryCommandPlugin = ( From f26bec3716b4a2439c90cf0f9b0b9fb8cc2c6bfd Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Sun, 9 May 2021 19:43:51 +0000 Subject: [PATCH 44/48] test: src/endpointDiscoveryMiddleware.spec.ts --- .../src/endpointDiscoveryMiddleware.spec.ts | 142 ++++++++++++++++++ .../src/endpointDiscoveryMiddleware.ts | 2 +- 2 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts new file mode 100644 index 000000000000..b1af358626d5 --- /dev/null +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts @@ -0,0 +1,142 @@ +import { EndpointCache } from "@aws-sdk/endpoint-cache"; +import { HttpRequest } from "@aws-sdk/protocol-http"; +import { FinalizeHandlerArguments } from "@aws-sdk/types"; + +import { endpointDiscoveryMiddleware } from "./endpointDiscoveryMiddleware"; +import { getCacheKey } from "./getCacheKey"; +import { updateDiscoveredEndpointInCache } from "./updateDiscoveredEndpointInCache"; + +jest.mock("./updateDiscoveredEndpointInCache"); +jest.mock("./getCacheKey"); +jest.mock("@aws-sdk/protocol-http"); + +describe(endpointDiscoveryMiddleware.name, () => { + const cacheKey = "cacheKey"; + const endpoint = "endpoint"; + const getEndpoint = jest.fn().mockReturnValue(endpoint); + const mockConfig = { + credentials: jest.fn(), + endpointCache: ({ + getEndpoint, + } as unknown) as EndpointCache, + endpointDiscoveryEnabled: jest.fn().mockResolvedValue(undefined), + isCustomEndpoint: false, + isClientEndpointDiscoveryEnabled: false, + }; + + const mockMiddlewareConfig = { + isDiscoveredEndpointRequired: false, + }; + + const mockNext = jest.fn(); + const mockContext = { + clientName: "ExampleClient", + commandName: "ExampleCommand", + }; + const mockArgs = { request: {} }; + + beforeEach(() => { + (getCacheKey as jest.Mock).mockResolvedValue(cacheKey); + (updateDiscoveredEndpointInCache as jest.Mock).mockResolvedValue(undefined); + const { isInstance } = HttpRequest; + ((isInstance as unknown) as jest.Mock).mockReturnValue(true); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe(`isCustomEndpoint=true`, () => { + it(`throw error if isClientEndpointDiscoveryEnabled`, async () => { + try { + await endpointDiscoveryMiddleware( + { ...mockConfig, isCustomEndpoint: true, isClientEndpointDiscoveryEnabled: true }, + mockMiddlewareConfig + )( + mockNext, + mockContext + )(mockArgs as FinalizeHandlerArguments); + fail("should throw error when isCustomEndpoint=true and isClientEndpointDiscoveryEnabled=true"); + } catch (error) { + expect(error).toStrictEqual( + new Error(`Custom endpoint is supplied; endpointDiscoveryEnabled must not be true.`) + ); + } + expect(mockNext).not.toHaveBeenCalled(); + expect(updateDiscoveredEndpointInCache).not.toHaveBeenCalled(); + }); + + it(`returns without endpoint discovery`, async () => { + await endpointDiscoveryMiddleware({ ...mockConfig, isCustomEndpoint: true }, mockMiddlewareConfig)( + mockNext, + mockContext + )(mockArgs as FinalizeHandlerArguments); + expect(mockNext).toHaveBeenCalledWith(mockArgs); + expect(updateDiscoveredEndpointInCache as jest.Mock).not.toHaveBeenCalled(); + }); + }); + + describe(`isDiscoveredEndpointRequired=true`, () => { + it(`throws error when isEndpointDiscoveryEnabled=false`, async () => { + mockConfig.endpointDiscoveryEnabled.mockResolvedValueOnce(false); + try { + await endpointDiscoveryMiddleware(mockConfig, { ...mockMiddlewareConfig, isDiscoveredEndpointRequired: true })( + mockNext, + mockContext + )(mockArgs as FinalizeHandlerArguments); + fail("should throw error when isDiscoveredEndpointRequired=true and isEndpointDiscoveryEnabled=false"); + } catch (error) { + expect(error).toStrictEqual( + new Error( + `Endpoint Discovery is disabled but ${mockContext.commandName} on ${mockContext.clientName} requires it.` + + ` Please check your configurations.` + ) + ); + } + expect(mockNext).not.toHaveBeenCalled(); + expect(updateDiscoveredEndpointInCache).not.toHaveBeenCalled(); + }); + + describe(`calls updateDiscoveredEndpointInCache`, () => { + it(`when isEndpointDiscoveryEnabled=undefined`, async () => { + await endpointDiscoveryMiddleware(mockConfig, { ...mockMiddlewareConfig, isDiscoveredEndpointRequired: true })( + mockNext, + mockContext + )(mockArgs as FinalizeHandlerArguments); + expect(mockNext).toHaveBeenCalledWith(mockArgs); + expect(mockNext).toHaveBeenCalledWith({ request: { hostname: endpoint } }); + expect(updateDiscoveredEndpointInCache).toHaveBeenCalled(); + }); + + it(`when isEndpointDiscoveryEnabled=true`, async () => { + mockConfig.endpointDiscoveryEnabled.mockResolvedValueOnce(true); + await endpointDiscoveryMiddleware(mockConfig, { ...mockMiddlewareConfig, isDiscoveredEndpointRequired: true })( + mockNext, + mockContext + )(mockArgs as FinalizeHandlerArguments); + expect(mockNext).toHaveBeenCalledWith(mockArgs); + expect(mockNext).toHaveBeenCalledWith({ request: { hostname: endpoint } }); + expect(updateDiscoveredEndpointInCache).toHaveBeenCalled(); + }); + }); + + describe(`isDiscoveredEndpointRequired=false`, () => { + it(`calls updateDiscoveredEndpointInCache when isEndpointDiscoveryEnabled=true`, async () => { + mockConfig.endpointDiscoveryEnabled.mockResolvedValueOnce(true); + await endpointDiscoveryMiddleware(mockConfig, mockMiddlewareConfig)(mockNext, mockContext)( + mockArgs as FinalizeHandlerArguments + ); + expect(mockNext).toHaveBeenCalledWith(mockArgs); + expect(updateDiscoveredEndpointInCache).toHaveBeenCalled(); + }); + + it(`does not call updateDiscoveredEndpointInCache when isEndpointDiscoveryEnabled=false`, async () => { + await endpointDiscoveryMiddleware(mockConfig, mockMiddlewareConfig)(mockNext, mockContext)( + mockArgs as FinalizeHandlerArguments + ); + expect(mockNext).toHaveBeenCalledWith(mockArgs); + expect(updateDiscoveredEndpointInCache).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts index fc6ad41da577..125faed71d96 100644 --- a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts @@ -28,7 +28,7 @@ export const endpointDiscoveryMiddleware = ( return next(args); } - const { client, endpointDiscoveryCommandCtor } = config; + const { endpointDiscoveryCommandCtor } = config; const { isDiscoveredEndpointRequired, identifiers } = middlewareConfig; const { clientName, commandName } = context; const isEndpointDiscoveryEnabled = await config.endpointDiscoveryEnabled(); From 983f110ec14a4483d64932644dfd562cbb3fe36f Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Wed, 12 May 2021 23:01:18 +0000 Subject: [PATCH 45/48] chore: remove Client suffix from EndpointDiscovery --- .../src/endpointDiscoveryMiddleware.ts | 4 ++-- .../src/getEndpointDiscoveryCommandPlugin.ts | 4 ++-- packages/middleware-endpoint-discovery/src/index.ts | 2 +- ...pec.ts => resolveEndpointDiscoveryConfig.spec.ts} | 12 ++++++------ ...ntConfig.ts => resolveEndpointDiscoveryConfig.ts} | 10 +++++----- .../src/updateDiscoveredEndpointInCache.ts | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) rename packages/middleware-endpoint-discovery/src/{resolveEndpointDiscoveryClientConfig.spec.ts => resolveEndpointDiscoveryConfig.spec.ts} (82%) rename packages/middleware-endpoint-discovery/src/{resolveEndpointDiscoveryClientConfig.ts => resolveEndpointDiscoveryConfig.ts} (85%) diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts index 125faed71d96..3edd1f6094b3 100644 --- a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.ts @@ -9,11 +9,11 @@ import { import { getCacheKey } from "./getCacheKey"; import { EndpointDiscoveryMiddlewareConfig } from "./getEndpointDiscoveryCommandPlugin"; -import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; +import { EndpointDiscoveryResolvedConfig } from "./resolveEndpointDiscoveryConfig"; import { updateDiscoveredEndpointInCache } from "./updateDiscoveredEndpointInCache"; export const endpointDiscoveryMiddleware = ( - config: EndpointDiscoveryClientResolvedConfig, + config: EndpointDiscoveryResolvedConfig, middlewareConfig: EndpointDiscoveryMiddlewareConfig ) => ( next: FinalizeHandler, diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index a957564bb1aa..3dbc9042ced1 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -1,7 +1,7 @@ import { FinalizeRequestHandlerOptions, Pluggable } from "@aws-sdk/types"; import { endpointDiscoveryMiddleware } from "./endpointDiscoveryMiddleware"; -import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; +import { EndpointDiscoveryResolvedConfig } from "./resolveEndpointDiscoveryConfig"; export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = { name: "endpointDiscoveryMiddleware", @@ -16,7 +16,7 @@ export type EndpointDiscoveryMiddlewareConfig = { }; export const getEndpointDiscoveryCommandPlugin = ( - pluginConfig: EndpointDiscoveryClientResolvedConfig, + pluginConfig: EndpointDiscoveryResolvedConfig, middlewareConfig: EndpointDiscoveryMiddlewareConfig ): Pluggable => ({ applyToStack: (commandStack) => { diff --git a/packages/middleware-endpoint-discovery/src/index.ts b/packages/middleware-endpoint-discovery/src/index.ts index ed291187bb97..4dff25b13d3c 100644 --- a/packages/middleware-endpoint-discovery/src/index.ts +++ b/packages/middleware-endpoint-discovery/src/index.ts @@ -1,3 +1,3 @@ -export * from "./resolveEndpointDiscoveryClientConfig"; +export * from "./resolveEndpointDiscoveryConfig"; export * from "./getEndpointDiscoveryCommandPlugin"; export * from "./configurations"; diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts similarity index 82% rename from packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts rename to packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts index 9a60db7c1d0c..a77c324fef93 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.spec.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts @@ -1,10 +1,10 @@ import { EndpointCache } from "@aws-sdk/endpoint-cache"; -import { resolveEndpointDiscoveryClientConfig } from "./resolveEndpointDiscoveryClientConfig"; +import { resolveEndpointDiscoveryConfig } from "./resolveEndpointDiscoveryConfig"; jest.mock("@aws-sdk/endpoint-cache"); -describe(resolveEndpointDiscoveryClientConfig.name, () => { +describe(resolveEndpointDiscoveryConfig.name, () => { const isCustomEndpoint = false; const credentials = jest.fn(); const endpointDiscoveryEnabledProvider = jest.fn().mockResolvedValue(undefined); @@ -16,7 +16,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { describe("endpointCache", () => { it("creates cache of size endpointCacheSize if passed", () => { const endpointCacheSize = 100; - resolveEndpointDiscoveryClientConfig({ + resolveEndpointDiscoveryConfig({ isCustomEndpoint, credentials, endpointCacheSize, @@ -26,7 +26,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { }); it("creates cache of size 1000 if endpointCacheSize not passed", () => { - resolveEndpointDiscoveryClientConfig({ + resolveEndpointDiscoveryConfig({ isCustomEndpoint, credentials, endpointDiscoveryEnabledProvider, @@ -37,7 +37,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { describe("endpointDiscoveryEnabled", () => { it.each([false, true])(`sets to value passed in the config: %s`, (endpointDiscoveryEnabled) => { - const resolvedConfig = resolveEndpointDiscoveryClientConfig({ + const resolvedConfig = resolveEndpointDiscoveryConfig({ isCustomEndpoint, credentials, endpointDiscoveryEnabled, @@ -49,7 +49,7 @@ describe(resolveEndpointDiscoveryClientConfig.name, () => { }); it(`sets to endpointDiscoveryEnabledProvider if value is not passed`, () => { - const resolvedConfig = resolveEndpointDiscoveryClientConfig({ + const resolvedConfig = resolveEndpointDiscoveryConfig({ isCustomEndpoint, credentials, endpointDiscoveryEnabledProvider, diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts similarity index 85% rename from packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts rename to packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts index 2dffedcf2654..f1d96d606d42 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryClientConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts @@ -1,7 +1,7 @@ import { EndpointCache } from "@aws-sdk/endpoint-cache"; import { Client, Command, Credentials, Provider } from "@aws-sdk/types"; -export interface EndpointDiscoveryClientInputConfig {} +export interface EndpointDiscoveryInputConfig {} interface PreviouslyResolved { isCustomEndpoint: boolean; @@ -22,7 +22,7 @@ interface PreviouslyResolved { endpointDiscoveryEnabledProvider: Provider; } -export interface EndpointDiscoveryClientResolvedConfig { +export interface EndpointDiscoveryResolvedConfig { isCustomEndpoint: boolean; credentials: Provider; client?: Client; @@ -32,9 +32,9 @@ export interface EndpointDiscoveryClientResolvedConfig { isClientEndpointDiscoveryEnabled: boolean; } -export const resolveEndpointDiscoveryClientConfig = ( - input: T & PreviouslyResolved & EndpointDiscoveryClientInputConfig -): T & EndpointDiscoveryClientResolvedConfig => ({ +export const resolveEndpointDiscoveryConfig = ( + input: T & PreviouslyResolved & EndpointDiscoveryInputConfig +): T & EndpointDiscoveryResolvedConfig => ({ ...input, endpointCache: new EndpointCache(input.endpointCacheSize ?? 1000), endpointDiscoveryEnabled: diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 0f0ae2aef957..3f247e54a8a5 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -2,7 +2,7 @@ import { Command } from "@aws-sdk/types"; import { getCacheKey } from "./getCacheKey"; import { EndpointDiscoveryMiddlewareConfig } from "./getEndpointDiscoveryCommandPlugin"; -import { EndpointDiscoveryClientResolvedConfig } from "./resolveEndpointDiscoveryClientConfig"; +import { EndpointDiscoveryResolvedConfig } from "./resolveEndpointDiscoveryConfig"; export type updateDiscoveredEndpointInCacheOptions = EndpointDiscoveryMiddlewareConfig & { commandName: string; @@ -12,7 +12,7 @@ export type updateDiscoveredEndpointInCacheOptions = EndpointDiscoveryMiddleware const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const updateDiscoveredEndpointInCache = async ( - config: EndpointDiscoveryClientResolvedConfig, + config: EndpointDiscoveryResolvedConfig, options: updateDiscoveredEndpointInCacheOptions ) => { const { client, endpointCache } = config; From 5004455ce0cf00788e1705151607b822addeaf5c Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Wed, 12 May 2021 23:08:03 +0000 Subject: [PATCH 46/48] chore(client-timestream-query): remove Client suffix from EndpointDiscovery --- .../client-timestream-query/TimestreamQueryClient.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index c1f2be5b0653..7292f3f2d763 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -22,9 +22,9 @@ import { resolveHostHeaderConfig, } from "@aws-sdk/middleware-host-header"; import { - EndpointDiscoveryClientInputConfig, - EndpointDiscoveryClientResolvedConfig, - resolveEndpointDiscoveryClientConfig, + EndpointDiscoveryInputConfig, + EndpointDiscoveryResolvedConfig, + resolveEndpointDiscoveryConfig, } from "@aws-sdk/middleware-endpoint-discovery"; import { getLoggerPlugin } from "@aws-sdk/middleware-logger"; import { RetryInputConfig, RetryResolvedConfig, getRetryPlugin, resolveRetryConfig } from "@aws-sdk/middleware-retry"; @@ -171,7 +171,7 @@ export interface ClientDefaults extends Partial<__SmithyResolvedConfiguration<__ type TimestreamQueryClientConfigType = Partial<__SmithyConfiguration<__HttpHandlerOptions>> & ClientDefaults & RegionInputConfig & - EndpointDiscoveryClientInputConfig & + EndpointDiscoveryInputConfig & EndpointsInputConfig & RetryInputConfig & HostHeaderInputConfig & @@ -185,7 +185,7 @@ export interface TimestreamQueryClientConfig extends TimestreamQueryClientConfig type TimestreamQueryClientResolvedConfigType = __SmithyResolvedConfiguration<__HttpHandlerOptions> & Required & RegionResolvedConfig & - EndpointDiscoveryClientResolvedConfig & + EndpointDiscoveryResolvedConfig & EndpointsResolvedConfig & RetryResolvedConfig & HostHeaderResolvedConfig & @@ -223,7 +223,7 @@ export class TimestreamQueryClient extends __Client< let _config_4 = resolveHostHeaderConfig(_config_3); let _config_5 = resolveAwsAuthConfig(_config_4); let _config_6 = resolveUserAgentConfig(_config_5); - let _config_7 = resolveEndpointDiscoveryClientConfig(_config_6); + let _config_7 = resolveEndpointDiscoveryConfig(_config_6); super(_config_7); _config_7.client = this; _config_7.endpointDiscoveryCommandCtor = DescribeEndpointsCommand; From 0147d41d60c128d166152ab886ae1e3d42dccdaf Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 13 May 2021 03:03:42 +0000 Subject: [PATCH 47/48] fix: call command.resolveMiddleware instead of client.send refs: https://github.com/trivikr/aws-sdk-js-v3/pull/105 --- .../TimestreamQueryClient.ts | 4 +- .../commands/QueryCommand.ts | 5 +- .../src/getEndpointDiscoveryCommandPlugin.ts | 4 +- .../resolveEndpointDiscoveryConfig.spec.ts | 56 ++++++++++--------- .../src/resolveEndpointDiscoveryConfig.ts | 7 ++- .../updateDiscoveredEndpointInCache.spec.ts | 24 ++++---- .../src/updateDiscoveredEndpointInCache.ts | 7 ++- 7 files changed, 56 insertions(+), 51 deletions(-) diff --git a/clients/client-timestream-query/TimestreamQueryClient.ts b/clients/client-timestream-query/TimestreamQueryClient.ts index 7292f3f2d763..ab90d9ad5ff5 100644 --- a/clients/client-timestream-query/TimestreamQueryClient.ts +++ b/clients/client-timestream-query/TimestreamQueryClient.ts @@ -223,10 +223,8 @@ export class TimestreamQueryClient extends __Client< let _config_4 = resolveHostHeaderConfig(_config_3); let _config_5 = resolveAwsAuthConfig(_config_4); let _config_6 = resolveUserAgentConfig(_config_5); - let _config_7 = resolveEndpointDiscoveryConfig(_config_6); + let _config_7 = resolveEndpointDiscoveryConfig(_config_6, DescribeEndpointsCommand); super(_config_7); - _config_7.client = this; - _config_7.endpointDiscoveryCommandCtor = DescribeEndpointsCommand; this.config = _config_7; this.middlewareStack.use(getRetryPlugin(this.config)); this.middlewareStack.use(getContentLengthPlugin(this.config)); diff --git a/clients/client-timestream-query/commands/QueryCommand.ts b/clients/client-timestream-query/commands/QueryCommand.ts index 745c4b863d69..eb1c7817b309 100644 --- a/clients/client-timestream-query/commands/QueryCommand.ts +++ b/clients/client-timestream-query/commands/QueryCommand.ts @@ -55,10 +55,13 @@ export class QueryCommand extends $Command { + const isDiscoveredEndpointRequired = true; this.middlewareStack.use(getSerdePlugin(configuration, this.serialize, this.deserialize)); this.middlewareStack.use( getEndpointDiscoveryCommandPlugin(configuration, { - isDiscoveredEndpointRequired: true, + isDiscoveredEndpointRequired, + clientStack, + options, }) ); diff --git a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts index 3dbc9042ced1..e2b7b2b2eb2f 100644 --- a/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts +++ b/packages/middleware-endpoint-discovery/src/getEndpointDiscoveryCommandPlugin.ts @@ -1,4 +1,4 @@ -import { FinalizeRequestHandlerOptions, Pluggable } from "@aws-sdk/types"; +import { FinalizeRequestHandlerOptions, HttpHandlerOptions, MiddlewareStack, Pluggable } from "@aws-sdk/types"; import { endpointDiscoveryMiddleware } from "./endpointDiscoveryMiddleware"; import { EndpointDiscoveryResolvedConfig } from "./resolveEndpointDiscoveryConfig"; @@ -12,6 +12,8 @@ export const endpointDiscoveryMiddlewareOptions: FinalizeRequestHandlerOptions = export type EndpointDiscoveryMiddlewareConfig = { isDiscoveredEndpointRequired: boolean; + clientStack: MiddlewareStack; + options?: HttpHandlerOptions; identifiers?: { [key: string]: string }; }; diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts index a77c324fef93..f0d554a2304c 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.spec.ts @@ -5,56 +5,58 @@ import { resolveEndpointDiscoveryConfig } from "./resolveEndpointDiscoveryConfig jest.mock("@aws-sdk/endpoint-cache"); describe(resolveEndpointDiscoveryConfig.name, () => { - const isCustomEndpoint = false; - const credentials = jest.fn(); - const endpointDiscoveryEnabledProvider = jest.fn().mockResolvedValue(undefined); + const endpointDiscoveryCommandCtor = jest.fn(); + const mockInput = { + isCustomEndpoint: false, + credentials: jest.fn(), + endpointDiscoveryEnabledProvider: jest.fn(), + }; afterEach(() => { jest.clearAllMocks(); }); + it("assigns endpointDiscoveryCommandCtor in resolvedConfig", () => { + const resolvedConfig = resolveEndpointDiscoveryConfig(mockInput, endpointDiscoveryCommandCtor); + expect(resolvedConfig.endpointDiscoveryCommandCtor).toStrictEqual(endpointDiscoveryCommandCtor); + }); + describe("endpointCache", () => { it("creates cache of size endpointCacheSize if passed", () => { const endpointCacheSize = 100; - resolveEndpointDiscoveryConfig({ - isCustomEndpoint, - credentials, - endpointCacheSize, - endpointDiscoveryEnabledProvider, - }); + resolveEndpointDiscoveryConfig( + { + ...mockInput, + endpointCacheSize, + }, + endpointDiscoveryCommandCtor + ); expect(EndpointCache).toBeCalledWith(endpointCacheSize); }); it("creates cache of size 1000 if endpointCacheSize not passed", () => { - resolveEndpointDiscoveryConfig({ - isCustomEndpoint, - credentials, - endpointDiscoveryEnabledProvider, - }); + resolveEndpointDiscoveryConfig(mockInput, endpointDiscoveryCommandCtor); expect(EndpointCache).toBeCalledWith(1000); }); }); describe("endpointDiscoveryEnabled", () => { it.each([false, true])(`sets to value passed in the config: %s`, (endpointDiscoveryEnabled) => { - const resolvedConfig = resolveEndpointDiscoveryConfig({ - isCustomEndpoint, - credentials, - endpointDiscoveryEnabled, - endpointDiscoveryEnabledProvider, - }); + const resolvedConfig = resolveEndpointDiscoveryConfig( + { + ...mockInput, + endpointDiscoveryEnabled, + }, + endpointDiscoveryCommandCtor + ); expect(resolvedConfig.endpointDiscoveryEnabled()).resolves.toBe(endpointDiscoveryEnabled); - expect(endpointDiscoveryEnabledProvider).not.toHaveBeenCalled(); + expect(mockInput.endpointDiscoveryEnabledProvider).not.toHaveBeenCalled(); expect(resolvedConfig.isClientEndpointDiscoveryEnabled).toStrictEqual(true); }); it(`sets to endpointDiscoveryEnabledProvider if value is not passed`, () => { - const resolvedConfig = resolveEndpointDiscoveryConfig({ - isCustomEndpoint, - credentials, - endpointDiscoveryEnabledProvider, - }); - expect(resolvedConfig.endpointDiscoveryEnabled).toBe(endpointDiscoveryEnabledProvider); + const resolvedConfig = resolveEndpointDiscoveryConfig(mockInput, endpointDiscoveryCommandCtor); + expect(resolvedConfig.endpointDiscoveryEnabled).toBe(mockInput.endpointDiscoveryEnabledProvider); expect(resolvedConfig.isClientEndpointDiscoveryEnabled).toStrictEqual(false); }); }); diff --git a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts index f1d96d606d42..d3fce50c6a7f 100644 --- a/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts +++ b/packages/middleware-endpoint-discovery/src/resolveEndpointDiscoveryConfig.ts @@ -25,17 +25,18 @@ interface PreviouslyResolved { export interface EndpointDiscoveryResolvedConfig { isCustomEndpoint: boolean; credentials: Provider; - client?: Client; - endpointDiscoveryCommandCtor?: new (comandConfig: any) => Command; + endpointDiscoveryCommandCtor: new (comandConfig: any) => Command; endpointCache: EndpointCache; endpointDiscoveryEnabled: Provider; isClientEndpointDiscoveryEnabled: boolean; } export const resolveEndpointDiscoveryConfig = ( - input: T & PreviouslyResolved & EndpointDiscoveryInputConfig + input: T & PreviouslyResolved & EndpointDiscoveryInputConfig, + endpointDiscoveryCommandCtor: new (comandConfig: any) => Command ): T & EndpointDiscoveryResolvedConfig => ({ ...input, + endpointDiscoveryCommandCtor, endpointCache: new EndpointCache(input.endpointCacheSize ?? 1000), endpointDiscoveryEnabled: input.endpointDiscoveryEnabled !== undefined diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts index 2b0450dcf195..71e5f36c1930 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.spec.ts @@ -8,22 +8,20 @@ describe(updateDiscoveredEndpointInCache.name, () => { const mockGet = jest.fn(); const mockSet = jest.fn(); const mockDelete = jest.fn(); - const mockSend = jest.fn(); - const mockEndpoints = [{ Address: "mockAddress", CachePeriodInMinutes: 1 }]; + const mockHandler = jest.fn(); + const mockResolveMiddleware = jest.fn().mockReturnValue(mockHandler); + + const mockEndpoints = [{ Address: "mockAddress", CachePeriodInMinutes: 2 }]; const placeholderEndpoints = [{ Address: "", CachePeriodInMinutes: 1 }]; const config = { - client: { - send: mockSend, - config: {}, - }, endpointCache: { get: mockGet, set: mockSet, delete: mockDelete }, }; const options = { commandName: "ExampleCommand", - endpointDiscoveryCommandCtor: jest.fn(), + endpointDiscoveryCommandCtor: jest.fn().mockReturnValue({ resolveMiddleware: mockResolveMiddleware }), isDiscoveredEndpointRequired: false, identifiers: { key: "value" }, }; @@ -84,11 +82,11 @@ describe(updateDiscoveredEndpointInCache.name, () => { Operation: options.commandName.substr(0, options.commandName.length - 7), Identifiers: options.identifiers, }); - expect(mockSend).toHaveBeenCalledTimes(1); + expect(mockHandler).toHaveBeenCalledTimes(1); }; it("on successful call: updates cache", async () => { - mockSend.mockResolvedValueOnce({ Endpoints: mockEndpoints }); + mockHandler.mockResolvedValueOnce({ output: { Endpoints: mockEndpoints } }); // @ts-ignore await updateDiscoveredEndpointInCache(config, options); @@ -103,7 +101,7 @@ describe(updateDiscoveredEndpointInCache.name, () => { describe("on error", () => { it(`throws if isDiscoveredEndpointRequired=true`, async () => { const error = new Error("rejected"); - mockSend.mockRejectedValueOnce(error); + mockHandler.mockRejectedValueOnce(error); try { // @ts-ignore @@ -128,7 +126,7 @@ describe(updateDiscoveredEndpointInCache.name, () => { it(`sets placeholder enpoint if isDiscoveredEndpointRequired=false`, async () => { const error = new Error("rejected"); - mockSend.mockRejectedValueOnce(error); + mockHandler.mockRejectedValueOnce(error); // @ts-ignore await updateDiscoveredEndpointInCache(config, options); @@ -150,7 +148,7 @@ describe(updateDiscoveredEndpointInCache.name, () => { it(`InvalidEndpointException`, async () => { const error = Object.assign(new Error("Invalid endpoint!"), { name: "InvalidEndpointException" }); - mockSend.mockRejectedValueOnce(error); + mockHandler.mockRejectedValueOnce(error); // @ts-ignore await updateDiscoveredEndpointInCache(config, options); @@ -160,7 +158,7 @@ describe(updateDiscoveredEndpointInCache.name, () => { it(`Status code: 421`, async () => { const error = Object.assign(new Error("Invalid endpoint!"), { $metadata: { httpStatusCode: 421 } }); - mockSend.mockRejectedValueOnce(error); + mockHandler.mockRejectedValueOnce(error); // @ts-ignore await updateDiscoveredEndpointInCache(config, options); diff --git a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts index 3f247e54a8a5..08614c86367e 100644 --- a/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts +++ b/packages/middleware-endpoint-discovery/src/updateDiscoveredEndpointInCache.ts @@ -15,7 +15,7 @@ export const updateDiscoveredEndpointInCache = async ( config: EndpointDiscoveryResolvedConfig, options: updateDiscoveredEndpointInCacheOptions ) => { - const { client, endpointCache } = config; + const { endpointCache } = config; const { commandName, identifiers } = options; const cacheKey = await getCacheKey(commandName, config, { identifiers }); @@ -41,8 +41,9 @@ export const updateDiscoveredEndpointInCache = async ( Operation: commandName.substr(0, commandName.length - 7), // strip "Command" Identifiers: identifiers, }); - const { Endpoints } = await client?.send(command); - endpointCache.set(cacheKey, Endpoints); + const handler = command.resolveMiddleware(options.clientStack, config, options.options); + const result = await handler(command); + endpointCache.set(cacheKey, result.output.Endpoints); } catch (error) { if (error.name === "InvalidEndpointException" || error.$metadata?.httpStatusCode === 421) { // Endpoint is invalid, delete the cache entry. From 9048c5e6bbecab42694a10c8fe2c08f6b47960bc Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Thu, 13 May 2021 01:41:54 +0000 Subject: [PATCH 48/48] fix(middleware-endpoint-discovery): call resolveMiddleware instead of client.send --- .../src/endpointDiscoveryMiddleware.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts index b1af358626d5..38157ee4c471 100644 --- a/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts +++ b/packages/middleware-endpoint-discovery/src/endpointDiscoveryMiddleware.spec.ts @@ -1,6 +1,6 @@ import { EndpointCache } from "@aws-sdk/endpoint-cache"; import { HttpRequest } from "@aws-sdk/protocol-http"; -import { FinalizeHandlerArguments } from "@aws-sdk/types"; +import { FinalizeHandlerArguments, MiddlewareStack } from "@aws-sdk/types"; import { endpointDiscoveryMiddleware } from "./endpointDiscoveryMiddleware"; import { getCacheKey } from "./getCacheKey"; @@ -20,12 +20,14 @@ describe(endpointDiscoveryMiddleware.name, () => { getEndpoint, } as unknown) as EndpointCache, endpointDiscoveryEnabled: jest.fn().mockResolvedValue(undefined), + endpointDiscoveryCommandCtor: jest.fn(), isCustomEndpoint: false, isClientEndpointDiscoveryEnabled: false, }; const mockMiddlewareConfig = { isDiscoveredEndpointRequired: false, + clientStack: {} as MiddlewareStack, }; const mockNext = jest.fn();