From ee3f3cb96bb58a52ea9c335b6430a88b8cf699fd Mon Sep 17 00:00:00 2001 From: Tomasz Czubocha Date: Wed, 7 Jan 2026 17:08:17 +0000 Subject: [PATCH] fix: migrate domain plugin to AWS SDK v3 (#13198) --- package-lock.json | 192 ++---------------- .../plugins/aws/domains/aws/acm-wrapper.js | 41 ++-- .../aws/domains/aws/api-gateway-v1-wrapper.js | 82 ++++---- .../aws/domains/aws/api-gateway-v2-wrapper.js | 81 ++++---- .../domains/aws/cloud-formation-wrapper.js | 32 +-- .../aws/domains/aws/route53-wrapper.js | 42 ++-- .../lib/plugins/aws/domains/aws/s3-wrapper.js | 14 +- .../lib/plugins/aws/domains/globals.js | 39 ++-- .../lib/plugins/aws/domains/index.js | 48 +---- .../lib/plugins/aws/domains/utils.js | 22 +- packages/serverless/package.json | 4 + 11 files changed, 226 insertions(+), 371 deletions(-) diff --git a/package-lock.json b/package-lock.json index ca8393fb2..53063bc4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -800,6 +800,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.953.0.tgz", "integrity": "sha512-QeSFxXgRjpr8M2wiLUsgg+mXEDtdhcuMnBWbXyjqUwca38pLEFJzJdFyOGul9RoQ2ICseuAy2/RZt0Ri1UgeZQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -1317,6 +1318,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.953.0.tgz", "integrity": "sha512-Lxxdhq5nt6ONulu1UHbIS0tVIar7itXv1m4TJfkVzuSm/yQzxIwnFkLtgW/0P5KIE+FS1yUoE2lS+dJBS1PLFw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -2518,6 +2520,7 @@ "version": "7.26.10", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -3435,7 +3438,6 @@ "version": "8.57.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } @@ -3548,7 +3550,6 @@ "version": "0.11.14", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@humanwhocodes/object-schema": "^2.0.2", "debug": "^4.3.1", @@ -3851,6 +3852,7 @@ "version": "29.7.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -4480,6 +4482,7 @@ "node_modules/@octokit/core": { "version": "6.1.4", "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^5.0.0", "@octokit/graphql": "^8.1.2", @@ -5962,6 +5965,7 @@ "version": "8.14.1", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6945,16 +6949,6 @@ "version": "3.0.2", "license": "Apache-2.0" }, - "node_modules/bignumber.js": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", - "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "license": "MIT", @@ -7120,6 +7114,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -7185,7 +7180,6 @@ "version": "3.3.0", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -7197,7 +7191,6 @@ "version": "5.1.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.0.0" } @@ -7906,16 +7899,6 @@ "node": ">=0.12" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "license": "MIT", @@ -8863,6 +8846,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -9183,6 +9167,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9259,13 +9244,6 @@ "type": "^2.7.2" } }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "license": "MIT", - "optional": true - }, "node_modules/fast-content-type-parse": { "version": "2.0.1", "funding": [ @@ -9378,30 +9356,6 @@ "bser": "2.1.1" } }, - "node_modules/fetch-blob": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", - "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "paypal", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "optional": true, - "dependencies": { - "node-domexception": "^1.0.0", - "web-streams-polyfill": "^3.0.3" - }, - "engines": { - "node": "^12.20 || >= 14.13" - } - }, "node_modules/fetch-retry": { "version": "6.0.0", "license": "MIT" @@ -9563,19 +9517,6 @@ "node": ">= 6" } }, - "node_modules/formdata-polyfill": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", - "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", - "license": "MIT", - "optional": true, - "dependencies": { - "fetch-blob": "^3.1.2" - }, - "engines": { - "node": ">=12.20.0" - } - }, "node_modules/forwarded": { "version": "0.2.0", "license": "MIT", @@ -9656,38 +9597,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gaxios": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", - "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^7.0.1", - "node-fetch": "^3.3.2", - "rimraf": "^5.0.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/gaxios/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -9910,16 +9819,6 @@ "dev": true, "license": "MIT" }, - "node_modules/google-logging-utils": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", - "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=14" - } - }, "node_modules/gopd": { "version": "1.2.0", "license": "MIT", @@ -10314,7 +10213,6 @@ "version": "3.2.1", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "builtin-modules": "^3.3.0" }, @@ -10806,6 +10704,7 @@ "version": "29.7.0", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -11449,16 +11348,6 @@ "node": ">=6" } }, - "node_modules/json-bigint": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", - "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "bignumber.js": "^9.0.0" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "dev": true, @@ -12431,46 +12320,6 @@ "version": "1.1.0", "license": "ISC" }, - "node_modules/node-domexception": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", - "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", - "deprecated": "Use your platform's native DOMException instead", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/jimmywarting" - }, - { - "type": "github", - "url": "https://paypal.me/jimmywarting" - } - ], - "license": "MIT", - "optional": true, - "engines": { - "node": ">=10.5.0" - } - }, - "node_modules/node-fetch": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", - "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", - "license": "MIT", - "optional": true, - "dependencies": { - "data-uri-to-buffer": "^4.0.0", - "fetch-blob": "^3.1.4", - "formdata-polyfill": "^4.0.10" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/node-fetch" - } - }, "node_modules/node-int64": { "version": "0.4.0", "dev": true, @@ -15188,16 +15037,6 @@ "makeerror": "1.0.12" } }, - "node_modules/web-streams-polyfill": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", - "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 8" - } - }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -15435,6 +15274,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -15668,6 +15508,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -15881,6 +15722,7 @@ "version": "4.0.0", "license": "MIT", "dependencies": { + "@aws-sdk/client-acm": "3.953.0", "@aws-sdk/client-api-gateway": "3.953.0", "@aws-sdk/client-apigatewayv2": "3.953.0", "@aws-sdk/client-cloudformation": "3.953.0", @@ -15890,12 +15732,15 @@ "@aws-sdk/client-iam": "3.953.0", "@aws-sdk/client-iot": "3.953.0", "@aws-sdk/client-lambda": "3.953.0", + "@aws-sdk/client-route-53": "3.953.0", "@aws-sdk/client-s3": "3.953.0", "@aws-sdk/client-sts": "3.953.0", + "@aws-sdk/credential-providers": "3.953.0", "@aws-sdk/lib-storage": "3.953.0", "@iarna/toml": "^2.2.5", "@serverlessinc/sf-core": "*", "@smithy/node-http-handler": "^4.4.5", + "@smithy/util-retry": "^4.2.6", "adm-zip": "^0.5.16", "ajv": "8.17.1", "ajv-formats": "2.1.1", @@ -16195,7 +16040,7 @@ }, "packages/sf-core": { "name": "@serverlessinc/sf-core", - "version": "4.29.3", + "version": "4.29.4", "license": "ISC", "dependencies": { "@aws-sdk/client-cloudformation": "3.953.0", @@ -16258,7 +16103,7 @@ }, "packages/sf-core-installer": { "name": "serverless", - "version": "4.29.3", + "version": "4.29.4", "hasInstallScript": true, "dependencies": { "axios": "^1.13.2", @@ -16314,6 +16159,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", diff --git a/packages/serverless/lib/plugins/aws/domains/aws/acm-wrapper.js b/packages/serverless/lib/plugins/aws/domains/aws/acm-wrapper.js index 54f54df9a..37ac4165c 100644 --- a/packages/serverless/lib/plugins/aws/domains/aws/acm-wrapper.js +++ b/packages/serverless/lib/plugins/aws/domains/aws/acm-wrapper.js @@ -1,10 +1,21 @@ -import AWS from 'aws-sdk' +import { + ACMClient, + CertificateStatus, + ListCertificatesCommand, + RequestCertificateCommand, + DescribeCertificateCommand, +} from '@aws-sdk/client-acm' +import { addProxyToAwsClient } from '@serverless/util' import Globals from '../globals.js' import { getAWSPagedResults, sleep } from '../utils.js' import Logging from '../logging.js' import { ServerlessError, ServerlessErrorCodes } from '@serverless/util' -const certStatuses = ['PENDING_VALIDATION', 'ISSUED', 'INACTIVE'] +const certStatuses = [ + CertificateStatus.PENDING_VALIDATION, + CertificateStatus.ISSUED, + CertificateStatus.INACTIVE, +] class ACMWrapper { constructor(credentials, endpointType) { @@ -12,15 +23,14 @@ class ACMWrapper { const config = { region: isEdge ? Globals.defaultRegion : Globals.getRegion(), endpoint: Globals.getServiceEndpoint('acm'), - ...Globals.getRetryStrategy(), - ...Globals.getRequestHandler(), + retryStrategy: Globals.getRetryStrategy(), } if (credentials) { config.credentials = credentials } - this.acm = new AWS.ACM(config) + this.acm = addProxyToAwsClient(new ACMClient(config)) } async getCertArn(domain) { @@ -30,11 +40,10 @@ class ACMWrapper { try { const certificates = await getAWSPagedResults( this.acm, - 'listCertificates', 'CertificateSummaryList', 'NextToken', 'NextToken', - { CertificateStatuses: certStatuses }, + new ListCertificatesCommand({ CertificateStatuses: certStatuses }), ) // enhancement idea: weight the choice of cert so longer expires // and RenewalEligibility = ELIGIBLE is more preferable @@ -50,7 +59,9 @@ class ACMWrapper { certificateName, ) } - Logging.logInfo(`Found a certificate ARN: '${certificateArn}'`) + if (certificateArn) { + Logging.logInfo(`Found existing certificate ARN: '${certificateArn}'`) + } } catch (err) { throw new ServerlessError( `Could not search certificates in Certificate Manager.\n${err.message}`, @@ -129,10 +140,9 @@ class ACMWrapper { const params = { DomainName: domainName, ValidationMethod: 'DNS', - // SubjectAlternativeNames: [], } - const result = await this.acm.requestCertificate(params).promise() + const result = await this.acm.send(new RequestCertificateCommand(params)) Logging.logInfo( `Certificate created with ARN: '${result.CertificateArn}'`, ) @@ -168,7 +178,9 @@ class ACMWrapper { while (Date.now() - startTime < maxWaitTimeMs) { try { const params = { CertificateArn: certificateArn } - const result = await this.acm.describeCertificate(params).promise() + const result = await this.acm.send( + new DescribeCertificateCommand(params), + ) if ( result.Certificate.DomainValidationOptions && @@ -193,7 +205,8 @@ class ACMWrapper { } catch (err) { throw new ServerlessError( `Failed to get validation records for certificate '${certificateArn}': ${err.message}`, - ServerlessErrorCodes.domains.ACM_CERTIFICATE_VALIDATION_RECORDS_FAILED, + ServerlessErrorCodes.domains + .ACM_CERTIFICATE_VALIDATION_RECORDS_FAILED, { originalMessage: err.message }, ) } @@ -227,7 +240,9 @@ class ACMWrapper { while (Date.now() - startTime < maxWaitTimeMs) { try { const params = { CertificateArn: certificateArn } - const result = await this.acm.describeCertificate(params).promise() + const result = await this.acm.send( + new DescribeCertificateCommand(params), + ) if (result.Certificate.Status === 'ISSUED') { Logging.logInfo('Certificate validation completed successfully!') diff --git a/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v1-wrapper.js b/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v1-wrapper.js index e60d2fe55..ce5c10c90 100644 --- a/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v1-wrapper.js +++ b/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v1-wrapper.js @@ -1,10 +1,19 @@ /** * Wrapper class for AWS APIGateway provider */ -import DomainConfig from '../models/domain-config.js' +import { + APIGatewayClient, + CreateDomainNameCommand, + GetDomainNameCommand, + DeleteDomainNameCommand, + CreateBasePathMappingCommand, + GetBasePathMappingsCommand, + UpdateBasePathMappingCommand, + DeleteBasePathMappingCommand, +} from '@aws-sdk/client-api-gateway' +import { addProxyToAwsClient } from '@serverless/util' import DomainInfo from '../models/domain-info.js' import Globals from '../globals.js' -import AWS from 'aws-sdk' import ApiGatewayMap from '../models/api-gateway-map.js' import APIGatewayBase from '../models/apigateway-base.js' import Logging from '../logging.js' @@ -17,15 +26,14 @@ class APIGatewayV1Wrapper extends APIGatewayBase { const config = { region: Globals.getRegion(), endpoint: Globals.getServiceEndpoint('apigateway'), - ...Globals.getRetryStrategy(), - ...Globals.getRequestHandler(), + retryStrategy: Globals.getRetryStrategy(), } if (credentials) { config.credentials = credentials } - this.apiGateway = new AWS.APIGateway(config) + this.apiGateway = addProxyToAwsClient(new APIGatewayClient(config)) } async createCustomDomain(domain) { @@ -62,9 +70,9 @@ class APIGatewayV1Wrapper extends APIGatewayBase { } try { - const domainInfo = await this.apiGateway - .createDomainName(params) - .promise() + const domainInfo = await this.apiGateway.send( + new CreateDomainNameCommand(params), + ) return new DomainInfo(domainInfo) } catch (err) { throw new ServerlessError( @@ -84,31 +92,33 @@ class APIGatewayV1Wrapper extends APIGatewayBase { async getCustomDomain(domain, silent = true) { // Make API call try { - const domainInfo = await this.apiGateway - .getDomainName({ + const domainInfo = await this.apiGateway.send( + new GetDomainNameCommand({ domainName: domain.givenDomainName, - }) - .promise() + }), + ) return new DomainInfo(domainInfo) } catch (err) { - if (!err.statusCode || err.statusCode !== 404 || !silent) { + const statusCode = err.$metadata?.httpStatusCode + if (!statusCode || statusCode !== 404 || !silent) { throw new ServerlessError( `V1 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}`, ServerlessErrorCodes.domains.API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED, { originalMessage: err.message }, ) } + Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist.`) } } async deleteCustomDomain(domain) { // Make API call try { - await this.apiGateway - .deleteDomainName({ + await this.apiGateway.send( + new DeleteDomainNameCommand({ domainName: domain.givenDomainName, - }) - .promise() + }), + ) } catch (err) { throw new ServerlessError( `V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`, @@ -120,21 +130,22 @@ class APIGatewayV1Wrapper extends APIGatewayBase { async createBasePathMapping(domain) { try { - await this.apiGateway - .createBasePathMapping({ + await this.apiGateway.send( + new CreateBasePathMappingCommand({ basePath: domain.basePath, domainName: domain.givenDomainName, restApiId: domain.apiId, stage: domain.stage, - }) - .promise() + }), + ) Logging.logInfo( `V1 - Created API mapping '${Logging.formatBasePathForDisplay(domain.basePath)}' for '${domain.givenDomainName}'`, ) } catch (err) { throw new ServerlessError( `V1 - Unable to create base path mapping for '${domain.givenDomainName}':\n${err.message}`, - ServerlessErrorCodes.domains.API_GATEWAY_BASE_PATH_MAPPING_CREATION_FAILED, + ServerlessErrorCodes.domains + .API_GATEWAY_BASE_PATH_MAPPING_CREATION_FAILED, { originalMessage: err.message }, ) } @@ -144,13 +155,12 @@ class APIGatewayV1Wrapper extends APIGatewayBase { try { const items = await getAWSPagedResults( this.apiGateway, - 'getBasePathMappings', 'items', 'position', 'position', - { + new GetBasePathMappingsCommand({ domainName: domain.givenDomainName, - }, + }), ) return items.map((item) => { return new ApiGatewayMap( @@ -174,8 +184,8 @@ class APIGatewayV1Wrapper extends APIGatewayBase { Logging.logInfo(`V1 - Updating API mapping from '${Logging.formatBasePathForDisplay(domain.apiMapping.basePath)}' to '${Logging.formatBasePathForDisplay(domain.basePath)}' for '${domain.givenDomainName}'`) try { - await this.apiGateway - .updateBasePathMapping({ + await this.apiGateway.send( + new UpdateBasePathMappingCommand({ basePath: domain.apiMapping.basePath, domainName: domain.givenDomainName, patchOperations: [ @@ -185,12 +195,13 @@ class APIGatewayV1Wrapper extends APIGatewayBase { value: domain.basePath, }, ], - }) - .promise() + }), + ) } catch (err) { throw new ServerlessError( `V1 - Unable to update base path mapping for '${domain.givenDomainName}':\n${err.message}`, - ServerlessErrorCodes.domains.API_GATEWAY_BASE_PATH_MAPPING_UPDATE_FAILED, + ServerlessErrorCodes.domains + .API_GATEWAY_BASE_PATH_MAPPING_UPDATE_FAILED, { originalMessage: err.message }, ) } @@ -198,19 +209,20 @@ class APIGatewayV1Wrapper extends APIGatewayBase { async deleteBasePathMapping(domain) { try { - await this.apiGateway - .deleteBasePathMapping({ + await this.apiGateway.send( + new DeleteBasePathMappingCommand({ basePath: domain.apiMapping.basePath, domainName: domain.givenDomainName, - }) - .promise() + }), + ) Logging.logInfo( `V1 - Removed '${Logging.formatBasePathForDisplay(domain.apiMapping.basePath)}' base path mapping`, ) } catch (err) { throw new ServerlessError( `V1 - Unable to remove base path mapping for '${domain.givenDomainName}':\n${err.message}`, - ServerlessErrorCodes.domains.API_GATEWAY_BASE_PATH_MAPPING_DELETION_FAILED, + ServerlessErrorCodes.domains + .API_GATEWAY_BASE_PATH_MAPPING_DELETION_FAILED, { originalMessage: err.message }, ) } diff --git a/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v2-wrapper.js b/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v2-wrapper.js index 763729cd2..8a00acbcd 100644 --- a/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v2-wrapper.js +++ b/packages/serverless/lib/plugins/aws/domains/aws/api-gateway-v2-wrapper.js @@ -1,12 +1,21 @@ /** * Wrapper class for AWS APIGatewayV2 provider */ -import DomainConfig from '../models/domain-config.js' +import { + ApiGatewayV2Client, + CreateDomainNameCommand, + GetDomainNameCommand, + DeleteDomainNameCommand, + CreateApiMappingCommand, + GetApiMappingsCommand, + UpdateApiMappingCommand, + DeleteApiMappingCommand, +} from '@aws-sdk/client-apigatewayv2' +import { addProxyToAwsClient } from '@serverless/util' import DomainInfo from '../models/domain-info.js' import Globals from '../globals.js' import ApiGatewayMap from '../models/api-gateway-map.js' import APIGatewayBase from '../models/apigateway-base.js' -import AWS from 'aws-sdk' import Logging from '../logging.js' import { getAWSPagedResults } from '../utils.js' import { ServerlessError, ServerlessErrorCodes } from '@serverless/util' @@ -17,15 +26,14 @@ class APIGatewayV2Wrapper extends APIGatewayBase { const config = { region: Globals.getRegion(), endpoint: Globals.getServiceEndpoint('apigatewayv2'), - ...Globals.getRetryStrategy(), - ...Globals.getRequestHandler(), + retryStrategy: Globals.getRetryStrategy(), } if (credentials) { config.credentials = credentials } - this.apiGateway = new AWS.ApiGatewayV2(config) + this.apiGateway = addProxyToAwsClient(new ApiGatewayV2Client(config)) } /** @@ -64,9 +72,9 @@ class APIGatewayV2Wrapper extends APIGatewayBase { } try { - const domainInfo = await this.apiGateway - .createDomainName(params) - .promise() + const domainInfo = await this.apiGateway.send( + new CreateDomainNameCommand(params), + ) return new DomainInfo(domainInfo) } catch (err) { throw new ServerlessError( @@ -86,14 +94,15 @@ class APIGatewayV2Wrapper extends APIGatewayBase { async getCustomDomain(domain, silent = true) { // Make API call try { - const domainInfo = await this.apiGateway - .getDomainName({ + const domainInfo = await this.apiGateway.send( + new GetDomainNameCommand({ DomainName: domain.givenDomainName, - }) - .promise() + }), + ) return new DomainInfo(domainInfo) } catch (err) { - if (!err.statusCode || err.statusCode !== 404 || !silent) { + const statusCode = err.$metadata?.httpStatusCode + if (!statusCode || statusCode !== 404 || !silent) { throw new ServerlessError( `V2 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}`, ServerlessErrorCodes.domains.API_GATEWAY_CUSTOM_DOMAIN_FETCH_FAILED, @@ -112,11 +121,11 @@ class APIGatewayV2Wrapper extends APIGatewayBase { async deleteCustomDomain(domain) { // Make API call try { - await this.apiGateway - .deleteDomainName({ + await this.apiGateway.send( + new DeleteDomainNameCommand({ DomainName: domain.givenDomainName, - }) - .promise() + }), + ) } catch (err) { throw new ServerlessError( `V2 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`, @@ -133,8 +142,8 @@ class APIGatewayV2Wrapper extends APIGatewayBase { */ async createBasePathMapping(domain) { try { - await this.apiGateway - .createApiMapping({ + await this.apiGateway.send( + new CreateApiMappingCommand({ ApiId: domain.apiId, ApiMappingKey: domain.basePath, DomainName: domain.givenDomainName, @@ -142,15 +151,16 @@ class APIGatewayV2Wrapper extends APIGatewayBase { domain.apiType === Globals.apiTypes.http ? '$default' : domain.stage, - }) - .promise() + }), + ) Logging.logInfo( `V2 - Created API mapping '${Logging.formatBasePathForDisplay(domain.basePath)}' for '${domain.givenDomainName}'`, ) } catch (err) { throw new ServerlessError( `V2 - Unable to create base path mapping for '${domain.givenDomainName}':\n${err.message}`, - ServerlessErrorCodes.domains.API_GATEWAY_BASE_PATH_MAPPING_CREATION_FAILED, + ServerlessErrorCodes.domains + .API_GATEWAY_BASE_PATH_MAPPING_CREATION_FAILED, { originalMessage: err.message }, ) } @@ -165,13 +175,12 @@ class APIGatewayV2Wrapper extends APIGatewayBase { try { const items = await getAWSPagedResults( this.apiGateway, - 'getApiMappings', 'Items', 'NextToken', 'NextToken', - { + new GetApiMappingsCommand({ DomainName: domain.givenDomainName, - }, + }), ) return items.map( (item) => @@ -198,8 +207,8 @@ class APIGatewayV2Wrapper extends APIGatewayBase { */ async updateBasePathMapping(domain) { try { - await this.apiGateway - .updateApiMapping({ + await this.apiGateway.send( + new UpdateApiMappingCommand({ ApiId: domain.apiId, ApiMappingId: domain.apiMapping.apiMappingId, ApiMappingKey: domain.basePath, @@ -208,15 +217,16 @@ class APIGatewayV2Wrapper extends APIGatewayBase { domain.apiType === Globals.apiTypes.http ? '$default' : domain.stage, - }) - .promise() + }), + ) Logging.logInfo( `V2 - Updated API mapping to '${Logging.formatBasePathForDisplay(domain.basePath)}' for '${domain.givenDomainName}'`, ) } catch (err) { throw new ServerlessError( `V2 - Unable to update base path mapping for '${domain.givenDomainName}':\n${err.message}`, - ServerlessErrorCodes.domains.API_GATEWAY_BASE_PATH_MAPPING_UPDATE_FAILED, + ServerlessErrorCodes.domains + .API_GATEWAY_BASE_PATH_MAPPING_UPDATE_FAILED, { originalMessage: err.message }, ) } @@ -229,19 +239,20 @@ class APIGatewayV2Wrapper extends APIGatewayBase { */ async deleteBasePathMapping(domain) { try { - await this.apiGateway - .deleteApiMapping({ + await this.apiGateway.send( + new DeleteApiMappingCommand({ ApiMappingId: domain.apiMapping.apiMappingId, DomainName: domain.givenDomainName, - }) - .promise() + }), + ) Logging.logInfo( `V2 - Removed API Mapping with id: '${domain.apiMapping.apiMappingId}'`, ) } catch (err) { throw new ServerlessError( `V2 - Unable to remove base path mapping for '${domain.givenDomainName}':\n${err.message}`, - ServerlessErrorCodes.domains.API_GATEWAY_BASE_PATH_MAPPING_DELETION_FAILED, + ServerlessErrorCodes.domains + .API_GATEWAY_BASE_PATH_MAPPING_DELETION_FAILED, { originalMessage: err.message }, ) } diff --git a/packages/serverless/lib/plugins/aws/domains/aws/cloud-formation-wrapper.js b/packages/serverless/lib/plugins/aws/domains/aws/cloud-formation-wrapper.js index 7ba243a6c..6236160d3 100644 --- a/packages/serverless/lib/plugins/aws/domains/aws/cloud-formation-wrapper.js +++ b/packages/serverless/lib/plugins/aws/domains/aws/cloud-formation-wrapper.js @@ -2,11 +2,20 @@ * Wrapper class for AWS CloudFormation provider */ -import AWS from 'aws-sdk' +import { + CloudFormationClient, + DescribeStackResourceCommand, + DescribeStacksCommand, + ListExportsCommand, +} from '@aws-sdk/client-cloudformation' +import { + addProxyToAwsClient, + ServerlessError, + ServerlessErrorCodes, +} from '@serverless/util' import Globals from '../globals.js' import Logging from '../logging.js' import { getAWSPagedResults } from '../utils.js' -import { ServerlessError, ServerlessErrorCodes } from '@serverless/util' class CloudFormationWrapper { constructor(credentials) { @@ -19,15 +28,14 @@ class CloudFormationWrapper { const config = { region: Globals.getRegion(), endpoint: Globals.getServiceEndpoint('cloudformation'), - ...Globals.getRetryStrategy(), - ...Globals.getRequestHandler(), + retryStrategy: Globals.getRetryStrategy(), } if (credentials) { config.credentials = credentials } - this.cloudFormation = new AWS.CloudFormation(config) + this.cloudFormation = addProxyToAwsClient(new CloudFormationClient(config)) } /** @@ -142,11 +150,10 @@ class CloudFormationWrapper { async getImportValues(names) { const exports = await getAWSPagedResults( this.cloudFormation, - 'listExports', 'Exports', 'NextToken', 'NextToken', - {}, + new ListExportsCommand({}), ) // filter Exports by names which we need const filteredExports = exports.filter( @@ -168,12 +175,12 @@ class CloudFormationWrapper { */ async getStack(logicalResourceId, stackName) { try { - return await this.cloudFormation - .describeStackResource({ + return await this.cloudFormation.send( + new DescribeStackResourceCommand({ LogicalResourceId: logicalResourceId, StackName: stackName, - }) - .promise() + }), + ) } catch (err) { throw new ServerlessError( `Failed to find CloudFormation resources with an error: ${err.message}\n`, @@ -193,11 +200,10 @@ class CloudFormationWrapper { // get all stacks from the CloudFormation const stacks = await getAWSPagedResults( this.cloudFormation, - 'describeStacks', 'Stacks', 'NextToken', 'NextToken', - {}, + new DescribeStacksCommand({}), ) // filter stacks by given stackName and check by nested stack RootId diff --git a/packages/serverless/lib/plugins/aws/domains/aws/route53-wrapper.js b/packages/serverless/lib/plugins/aws/domains/aws/route53-wrapper.js index 2d0d57d44..0e1f14c72 100644 --- a/packages/serverless/lib/plugins/aws/domains/aws/route53-wrapper.js +++ b/packages/serverless/lib/plugins/aws/domains/aws/route53-wrapper.js @@ -1,21 +1,16 @@ +import { + Route53Client, + ChangeResourceRecordSetsCommand, + ListHostedZonesCommand, + ChangeAction, + RRType, +} from '@aws-sdk/client-route-53' +import { addProxyToAwsClient } from '@serverless/util' import Globals from '../globals.js' -import DomainConfig from '../models/domain-config.js' import Logging from '../logging.js' -import AWS from 'aws-sdk' import { getAWSPagedResults } from '../utils.js' import { ServerlessError, ServerlessErrorCodes } from '@serverless/util' -// Define constants that were imported from v3 SDK -const ChangeAction = { - UPSERT: 'UPSERT', - DELETE: 'DELETE', -} - -const RRType = { - A: 'A', - AAAA: 'AAAA', -} - class Route53Wrapper { constructor(credentials, region) { // not null and not undefined @@ -23,8 +18,7 @@ class Route53Wrapper { const config = { region: region || Globals.getRegion(), endpoint: serviceEndpoint, - ...Globals.getRetryStrategy(), - ...Globals.getRequestHandler(), + retryStrategy: Globals.getRetryStrategy(), } if (credentials) { @@ -32,7 +26,7 @@ class Route53Wrapper { } this.region = config.region - this.route53 = new AWS.Route53(config) + this.route53 = addProxyToAwsClient(new Route53Client(config)) } /** @@ -57,11 +51,10 @@ class Route53Wrapper { try { hostedZones = await getAWSPagedResults( this.route53, - 'listHostedZones', 'HostedZones', 'Marker', 'NextMarker', - {}, + new ListHostedZonesCommand({}), ) Logging.logInfo( `Found hosted zones list: ${hostedZones.map((zone) => zone.Name)}.`, @@ -114,11 +107,10 @@ class Route53Wrapper { try { const hostedZones = await getAWSPagedResults( this.route53, - 'listHostedZones', 'HostedZones', 'Marker', 'NextMarker', - {}, + new ListHostedZonesCommand({}), ) // removing the first part of the domain name, api.test.com => test.com @@ -149,11 +141,10 @@ class Route53Wrapper { try { const hostedZones = await getAWSPagedResults( this.route53, - 'listHostedZones', 'HostedZones', 'Marker', 'NextMarker', - {}, + new ListHostedZonesCommand({}), ) for (const record of validationRecords) { @@ -205,12 +196,13 @@ class Route53Wrapper { HostedZoneId: hostedZoneId, } - await this.route53.changeResourceRecordSets(params).promise() + await this.route53.send(new ChangeResourceRecordSetsCommand(params)) } } catch (err) { throw new ServerlessError( `Failed to create certificate validation records: ${err.message}`, - ServerlessErrorCodes.route53.ROUTE53_CERTIFICATE_VALIDATION_RECORDS_FAILED, + ServerlessErrorCodes.route53 + .ROUTE53_CERTIFICATE_VALIDATION_RECORDS_FAILED, { originalMessage: err.message }, ) } @@ -300,7 +292,7 @@ class Route53Wrapper { } // Make API call try { - await this.route53.changeResourceRecordSets(params).promise() + await this.route53.send(new ChangeResourceRecordSetsCommand(params)) } catch (err) { throw new ServerlessError( `Failed to ${action} ${recordsToCreate.join(',')} Alias for '${domain.givenDomainName}':\n diff --git a/packages/serverless/lib/plugins/aws/domains/aws/s3-wrapper.js b/packages/serverless/lib/plugins/aws/domains/aws/s3-wrapper.js index 0200a3ef3..d3d548e56 100644 --- a/packages/serverless/lib/plugins/aws/domains/aws/s3-wrapper.js +++ b/packages/serverless/lib/plugins/aws/domains/aws/s3-wrapper.js @@ -1,6 +1,6 @@ -import DomainConfig from '../models/domain-config.js' +import { S3Client, HeadObjectCommand } from '@aws-sdk/client-s3' +import { addProxyToAwsClient } from '@serverless/util' import Logging from '../logging.js' -import AWS from 'aws-sdk' import Globals from '../globals.js' import { ServerlessError, ServerlessErrorCodes } from '@serverless/util' @@ -9,15 +9,14 @@ class S3Wrapper { const config = { region: Globals.getRegion(), endpoint: Globals.getServiceEndpoint('s3'), - ...Globals.getRetryStrategy(), - ...Globals.getRequestHandler(), + retryStrategy: Globals.getRetryStrategy(), } if (credentials) { config.credentials = credentials } - this.s3 = new AWS.S3(config) + this.s3 = addProxyToAwsClient(new S3Client(config)) } /** @@ -35,9 +34,10 @@ class S3Wrapper { } try { - await this.s3.headObject(params).promise() + await this.s3.send(new HeadObjectCommand(params)) } catch (err) { - if (!err.statusCode || err.statusCode !== 403) { + const statusCode = err.$metadata?.httpStatusCode + if (!statusCode || statusCode !== 403) { throw new ServerlessError( `Could not head S3 object at ${domain.tlsTruststoreUri}.\n${err.message}`, ServerlessErrorCodes.domains.S3_TLS_CERTIFICATE_OBJECT_NOT_FOUND, diff --git a/packages/serverless/lib/plugins/aws/domains/globals.js b/packages/serverless/lib/plugins/aws/domains/globals.js index 2fae8028a..ae7a06dfc 100644 --- a/packages/serverless/lib/plugins/aws/domains/globals.js +++ b/packages/serverless/lib/plugins/aws/domains/globals.js @@ -1,4 +1,5 @@ -import AWS from 'aws-sdk' +import { fromIni } from '@aws-sdk/credential-providers' +import { ConfiguredRetryStrategy } from '@smithy/util-retry' export default class Globals { static pluginName = 'domains' @@ -75,28 +76,30 @@ export default class Globals { } static getRegion() { - const slsRegion = - Globals.options.region || Globals.serverless.service.provider.region - return slsRegion || Globals.currentRegion || Globals.defaultRegion + return Globals.currentRegion || Globals.defaultRegion } + /** + * Get credentials for a specific AWS profile using AWS SDK V3 + * @param {string} profile - The AWS profile name + * @returns {Promise} - The resolved credentials + */ static async getProfileCreds(profile) { - const credentials = new AWS.SharedIniFileCredentials({ profile }) - return credentials + return fromIni({ profile })() } + /** + * Get retry strategy for AWS SDK V3 clients + * @param {number} attempts - Maximum retry attempts (default: 5) + * @param {number} delay - Delay in ms per attempt (default: 3000) + * @param {number} backoff - Base backoff in ms (default: 500) + * @returns {ConfiguredRetryStrategy} - The retry strategy instance + */ static getRetryStrategy(attempts = 5, delay = 3000, backoff = 500) { - return { - retryDelayOptions: { - base: backoff, - customBackoff: (retryCount) => backoff + retryCount * delay, - }, - maxRetries: attempts, - } - } - - static getRequestHandler() { - // AWS SDK v2 handles proxy configuration automatically - return {} + return new ConfiguredRetryStrategy( + attempts, + // Backoff function: base backoff + delay per attempt + (attempt) => backoff + attempt * delay, + ) } } diff --git a/packages/serverless/lib/plugins/aws/domains/index.js b/packages/serverless/lib/plugins/aws/domains/index.js index c2582bd5b..8cb02a57b 100644 --- a/packages/serverless/lib/plugins/aws/domains/index.js +++ b/packages/serverless/lib/plugins/aws/domains/index.js @@ -10,7 +10,6 @@ import { sleep } from './utils.js' import APIGatewayV1Wrapper from './aws/api-gateway-v1-wrapper.js' import APIGatewayV2Wrapper from './aws/api-gateway-v2-wrapper.js' import Logging from './logging.js' -import AWS from 'aws-sdk' import { ServerlessError, ServerlessErrorCodes } from '@serverless/util' class ServerlessCustomDomain { @@ -81,26 +80,6 @@ class ServerlessCustomDomain { await this.initAWSRegion() await this.initAWSResources() - // start of the legacy AWS SDK V2 creds support - // TODO: remove it in case serverless will add V3 support - const domain = this.domains[0] - if (domain) { - try { - await this.getApiGateway(domain).getCustomDomain(domain) - } catch (error) { - if ( - error.message.includes( - 'Could not load credentials from any providers', - ) - ) { - Globals.credentials = - await this.serverless.providers.aws.getCredentials() - await this.initAWSResources() - } - } - } - // end of the legacy AWS SDK V2 creds support - return lifecycleFunc.call(this) } @@ -211,7 +190,8 @@ class ServerlessCustomDomain { throw new ServerlessError( "'EDGE' endpointType is not compatible with HTTP APIs. Please change the endpointType to 'REGIONAL' or the apiType to 'rest' or 'websocket'.\n" + 'https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html', - ServerlessErrorCodes.domains.DOMAIN_VALIDATION_INCOMPATIBLE_ENDPOINT_TYPE, + ServerlessErrorCodes.domains + .DOMAIN_VALIDATION_INCOMPATIBLE_ENDPOINT_TYPE, ) } } else if (domain.apiType === Globals.apiTypes.websocket) { @@ -219,7 +199,8 @@ class ServerlessCustomDomain { if (domain.endpointType === Globals.endpointTypes.edge) { throw new ServerlessError( "'EDGE' endpointType is not compatible with WebSocket APIs", - ServerlessErrorCodes.domains.DOMAIN_VALIDATION_INCOMPATIBLE_ENDPOINT_TYPE, + ServerlessErrorCodes.domains + .DOMAIN_VALIDATION_INCOMPATIBLE_ENDPOINT_TYPE, ) } } @@ -227,30 +208,17 @@ class ServerlessCustomDomain { } /** - * Init AWS credentials based on sls `provider.profile` + * Init AWS credentials from the framework (already honors provider.profile and --aws-profile) */ async initSLSCredentials() { - const slsProfile = - Globals.options['aws-profile'] || - Globals.serverless.service.provider.profile - Globals.credentials = slsProfile - ? await Globals.getProfileCreds(slsProfile) - : null + Globals.credentials = await this.serverless.providers.aws.getCredentials() } /** - * Init AWS current region based on Node options + * Init AWS current region from the framework (already honors --region and provider.region) */ async initAWSRegion() { - try { - // For AWS SDK v2, we can get region from config or environment - Globals.currentRegion = - AWS.config.region || - process.env.AWS_REGION || - process.env.AWS_DEFAULT_REGION - } catch (err) { - Logging.logInfo('Node region was not found.') - } + Globals.currentRegion = this.serverless.providers.aws.getRegion() } /** diff --git a/packages/serverless/lib/plugins/aws/domains/utils.js b/packages/serverless/lib/plugins/aws/domains/utils.js index 0e2e34250..42705ddb7 100644 --- a/packages/serverless/lib/plugins/aws/domains/utils.js +++ b/packages/serverless/lib/plugins/aws/domains/utils.js @@ -42,32 +42,30 @@ function evaluateBoolean(value, defaultValue) { } /** - * Iterate through the pages of a AWS SDK v2 response and collect them into a single array + * Iterate through the pages of an AWS SDK v3 response and collect them into a single array * - * @param {Object} client - The AWS service instance to use to make the calls - * @param {string} methodName - The method name to call on the client + * @param {Object} client - The AWS SDK v3 client instance * @param {string} resultsKey - The key name in the response that contains the items to return * @param {string} nextTokenKey - The request key name to append to the request that has the paging token value * @param {string} nextResponseTokenKey - The response key name that has the next paging token value - * @param {Object} params - Parameters to send in the request + * @param {Object} command - The AWS SDK v3 Command instance to execute * @returns {Promise} Promise that resolves to an array of results */ async function getAWSPagedResults( client, - methodName, resultsKey, nextTokenKey, nextResponseTokenKey, - params, + command, ) { let results = [] - let response = await client[methodName](params).promise() - results = results.concat(response[resultsKey] || []) + let response = await client.send(command) + results = results.concat(response[resultsKey] || results) - while (response[nextResponseTokenKey]) { - params[nextTokenKey] = response[nextResponseTokenKey] - response = await client[methodName](params).promise() - results = results.concat(response[resultsKey] || []) + while (nextResponseTokenKey in response && response[nextResponseTokenKey]) { + command.input[nextTokenKey] = response[nextResponseTokenKey] + response = await client.send(command) + results = results.concat(response[resultsKey]) } return results diff --git a/packages/serverless/package.json b/packages/serverless/package.json index cc81416f6..6ec88db76 100644 --- a/packages/serverless/package.json +++ b/packages/serverless/package.json @@ -19,6 +19,7 @@ ], "main": "lib/serverless.js", "dependencies": { + "@aws-sdk/client-acm": "3.953.0", "@aws-sdk/client-api-gateway": "3.953.0", "@aws-sdk/client-apigatewayv2": "3.953.0", "@aws-sdk/client-cloudformation": "3.953.0", @@ -28,12 +29,15 @@ "@aws-sdk/client-iam": "3.953.0", "@aws-sdk/client-iot": "3.953.0", "@aws-sdk/client-lambda": "3.953.0", + "@aws-sdk/client-route-53": "3.953.0", "@aws-sdk/client-s3": "3.953.0", "@aws-sdk/client-sts": "3.953.0", + "@aws-sdk/credential-providers": "3.953.0", "@aws-sdk/lib-storage": "3.953.0", "@iarna/toml": "^2.2.5", "@serverlessinc/sf-core": "*", "@smithy/node-http-handler": "^4.4.5", + "@smithy/util-retry": "^4.2.6", "adm-zip": "^0.5.16", "ajv": "8.17.1", "ajv-formats": "2.1.1",