Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move from AWS SDK V2 to V3 #563

Merged
merged 7 commits into from
Mar 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [7.0.0] - 2022-03-22

### Changed
- Switched to the AWS SDK V3. Small improvements related to it.
- Test refactoring.

## [6.4.4] - 2023-02-24

### Fixed
Expand Down
2,152 changes: 1,950 additions & 202 deletions package-lock.json

Large diffs are not rendered by default.

33 changes: 23 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-domain-manager",
"version": "6.4.4",
"version": "7.0.0",
"engines": {
"node": ">=14"
},
Expand All @@ -27,10 +27,10 @@
"main": "dist/src/index.js",
"bin": {},
"scripts": {
"integration-basic": "nyc mocha -r ts-node/register --project tsconfig.json --parallel test/integration-tests/basic.test.ts",
"integration-deploy": "nyc mocha -r ts-node/register --project tsconfig.json --parallel test/integration-tests/deploy.test.ts",
"integration-debug": "nyc mocha -r ts-node/register --project tsconfig.json test/integration-tests/debug.test.ts",
"test": "nyc mocha -r ts-node/register --project tsconfig.json test/unit-tests/index.test.ts && nyc report --reporter=text-summary",
"integration-basic": "nyc mocha -r ts-node/register --project tsconfig.json --parallel test/integration-tests/basic/basic.test.ts",
"integration-deploy": "nyc mocha -r ts-node/register --project tsconfig.json --parallel test/integration-tests/deploy/deploy.test.ts",
"integration-debug": "nyc mocha -r ts-node/register --project tsconfig.json test/integration-tests/debug/debug.test.ts",
"test": "find ./test/unit-tests -name '*.test.ts' | xargs nyc mocha -r ts-node/register --project tsconfig.json --timeout 5000 && nyc report --reporter=text-summary",
"test:debug": "NODE_OPTIONS='--inspect-brk' mocha -j 1 -r ts-node/register --project tsconfig.json test/unit-tests/index.test.ts",
"integration-test": "npm run integration-basic && npm run integration-deploy",
"lint": "tslint --project . && tslint --project tsconfig.json",
Expand All @@ -49,22 +49,35 @@
},
"devDependencies": {
"@types/mocha": "^10.0.1",
"@types/node": "^18.14.1",
"aws-sdk-mock": "^5.8.0",
"@types/node": "^18.15.5",
"@types/shelljs": "^0.8.11",
"aws-sdk-client-mock": "^2.1.1",
"chai": "^4.3.7",
"chai-spies": "^1.0.0",
"mocha": "^10.2.0",
"mocha-param": "^2.0.1",
"nyc": "^15.1.0",
"randomstring": "^1.2.3",
"serverless": "^3.27.0",
"serverless": "^3.28.1",
"serverless-plugin-split-stacks": "^1.12.0",
"shelljs": "^0.8.5",
"ts-node": "^10.9.1",
"tslint": "^6.1.3",
"typescript": "^4.9.5"
"typescript": "^5.0.2"
},
"dependencies": {
"aws-sdk": "^2.1322.0"
"@aws-sdk/client-acm": "^3.295.0",
"@aws-sdk/client-api-gateway": "^3.295.0",
"@aws-sdk/client-apigatewayv2": "^3.295.0",
"@aws-sdk/client-cloudformation": "^3.295.0",
"@aws-sdk/client-route-53": "^3.295.0",
"@aws-sdk/client-s3": "^3.295.0",
"@aws-sdk/config-resolver": "^3.295.0",
"@aws-sdk/credential-providers": "^3.295.0",
"@aws-sdk/node-config-provider": "^3.295.0",
"@aws-sdk/smithy-client": "^3.295.0"
},
"peerDependencies": {
"serverless": "^2.60 || 3"
}
}
51 changes: 17 additions & 34 deletions src/aws/acm-wrapper.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,36 @@
import {ACM} from "aws-sdk";
import {
ACMClient,
ListCertificatesCommand,
ListCertificatesCommandOutput,
} from "@aws-sdk/client-acm";
import Globals from "../globals";
import {getAWSPagedResults} from "../utils";
import DomainConfig = require("../models/domain-config");

const certStatuses = ["PENDING_VALIDATION", "ISSUED", "INACTIVE"];

class ACMWrapper {
public acm: ACM;
public acm: ACMClient;

constructor(endpointType: string) {
const credentials = Globals.serverless.providers.aws.getCredentials();
credentials.region = Globals.defaultRegion;
if (endpointType === Globals.endpointTypes.regional) {
credentials.region = Globals.serverless.providers.aws.getRegion();
}
this.acm = new Globals.serverless.providers.aws.sdk.ACM(credentials);
const isEdge = endpointType === Globals.endpointTypes.edge;
this.acm = new ACMClient({region: isEdge ? Globals.defaultRegion : Globals.currentRegion});
}

/**
* * Gets Certificate ARN that most closely matches domain name OR given Cert ARN if provided
*/
public async getCertArn(domain: DomainConfig): Promise<string> {
let certificateArn; // The arn of the selected certificate
let certificateName = domain.certificateName; // The certificate name

try {
const certificates = await getAWSPagedResults(
this.acm,
"listCertificates",
"CertificateSummaryList",
"NextToken",
"NextToken",
{CertificateStatuses: certStatuses},
);
// enhancement idea: weight the choice of cert so longer expiries
const response: ListCertificatesCommandOutput = await this.acm.send(
new ListCertificatesCommand({CertificateStatuses: certStatuses})
)
// enhancement idea: weight the choice of cert so longer expires
// and RenewalEligibility = ELIGIBLE is more preferable
if (certificateName != null) {
certificateArn = this.getCertArnByCertName(certificates, certificateName);
if (certificateName) {
certificateArn = this.getCertArnByCertName(response.CertificateSummaryList, certificateName);
} else {
certificateName = domain.givenDomainName;
certificateArn = this.getCertArnByDomainName(certificates, certificateName);
certificateArn = this.getCertArnByDomainName(response.CertificateSummaryList, certificateName);
}
} catch (err) {
throw Error(`Could not search certificates in Certificate Manager.\n${err.message}`);
Expand All @@ -50,20 +41,14 @@ class ACMWrapper {
return certificateArn;
}

/**
* * Gets Certificate ARN that most closely matches Cert ARN and not expired
*/
private getCertArnByCertName(certificates, certName): string {
const found = certificates.find((c) => c.DomainName === certName);
if (found) {
return found.CertificateArn;
return found.CertificateArn;
}
return null;
}

/**
* * Gets Certificate ARN that most closely matches domain name
*/
private getCertArnByDomainName(certificates, domainName): string {
// The more specific name will be the longest
let nameLength = 0;
Expand All @@ -81,9 +66,7 @@ class ACMWrapper {
}
// Looks to see if the name in the list is within the given domain
// Also checks if the name is more specific than previous ones
if (domainName.includes(certificateListName)
&& certificateListName.length > nameLength
) {
if (domainName.includes(certificateListName) && certificateListName.length > nameLength) {
nameLength = certificateListName.length;
certificateArn = currCert.CertificateArn;
}
Expand Down
105 changes: 54 additions & 51 deletions src/aws/api-gateway-v1-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,29 @@
import DomainConfig = require("../models/domain-config");
import DomainInfo = require("../models/domain-info");
import Globals from "../globals";
import {APIGateway} from "aws-sdk";
import {getAWSPagedResults, throttledCall} from "../utils";
import {
APIGatewayClient,
CreateBasePathMappingCommand,
CreateDomainNameCommand,
CreateDomainNameCommandOutput,
DeleteBasePathMappingCommand,
DeleteDomainNameCommand,
GetBasePathMappingsCommand,
GetBasePathMappingsCommandOutput,
GetDomainNameCommand,
GetDomainNameCommandOutput,
UpdateBasePathMappingCommand,
} from "@aws-sdk/client-api-gateway";
import ApiGatewayMap = require("../models/api-gateway-map");
import APIGatewayBase = require("../models/apigateway-base");
import Logging from "../logging";

class APIGatewayV1Wrapper extends APIGatewayBase {
constructor(credentials: any) {
constructor() {
super();
this.apiGateway = new APIGateway(credentials);
this.apiGateway = new APIGatewayClient({region: Globals.currentRegion});
}

/**
* Creates Custom Domain Name
* @param domain: DomainConfig
*/
public async createCustomDomain(domain: DomainConfig): Promise<DomainInfo> {
const providerTags = {
...Globals.serverless.service.provider.stackTags,
Expand Down Expand Up @@ -52,7 +60,9 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
}

try {
const domainInfo = await throttledCall(this.apiGateway, "createDomainName", params);
const domainInfo: CreateDomainNameCommandOutput = await this.apiGateway.send(
new CreateDomainNameCommand(params)
);
return new DomainInfo(domainInfo);
} catch (err) {
throw new Error(
Expand All @@ -61,49 +71,45 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
}
}

/**
* Get Custom Domain Info
*/
public async getCustomDomain(domain: DomainConfig): Promise<DomainInfo> {
// Make API call
try {
const domainInfo = await throttledCall(this.apiGateway, "getDomainName", {
domainName: domain.givenDomainName,
});
const domainInfo: GetDomainNameCommandOutput = await this.apiGateway.send(
new GetDomainNameCommand({
domainName: domain.givenDomainName,
})
);
return new DomainInfo(domainInfo);
} catch (err) {
if (err.code !== "NotFoundException") {
if (!err.$metadata || err.$metadata.httpStatusCode !== 404) {
throw new Error(
`V1 - Unable to fetch information about '${domain.givenDomainName}':\n${err.message}`
);
}
Globals.logInfo(`V1 - '${domain.givenDomainName}' does not exist.`);
Logging.logWarning(`V1 - '${domain.givenDomainName}' does not exist.`);
}
}

/**
* Delete Custom Domain Name through API Gateway
*/
public async deleteCustomDomain(domain: DomainConfig): Promise<void> {
// Make API call
try {
await throttledCall(this.apiGateway, "deleteDomainName", {
await this.apiGateway.send(new DeleteDomainNameCommand({
domainName: domain.givenDomainName,
});
}));
} catch (err) {
throw new Error(`V1 - Failed to delete custom domain '${domain.givenDomainName}':\n${err.message}`);
}
}

public async createBasePathMapping(domain: DomainConfig): Promise<void> {
try {
await throttledCall(this.apiGateway, "createBasePathMapping", {
await this.apiGateway.send(new CreateBasePathMappingCommand({
basePath: domain.basePath,
domainName: domain.givenDomainName,
restApiId: domain.apiId,
stage: domain.baseStage,
});
Globals.logInfo(`V1 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`);
}));
Logging.logInfo(`V1 - Created API mapping '${domain.basePath}' for '${domain.givenDomainName}'`);
} catch (err) {
throw new Error(
`V1 - Make sure the '${domain.givenDomainName}' exists.
Expand All @@ -114,14 +120,12 @@ class APIGatewayV1Wrapper extends APIGatewayBase {

public async getBasePathMappings(domain: DomainConfig): Promise<ApiGatewayMap[]> {
try {
const items = await getAWSPagedResults(
this.apiGateway,
"getBasePathMappings",
"items",
"position",
"position",
{domainName: domain.givenDomainName},
const response: GetBasePathMappingsCommandOutput = await this.apiGateway.send(
new GetBasePathMappingsCommand({
domainName: domain.givenDomainName
})
);
const items = response.items || [];
return items.map((item) => {
return new ApiGatewayMap(item.restApiId, item.basePath, item.stage, null);
}
Expand All @@ -136,16 +140,17 @@ class APIGatewayV1Wrapper extends APIGatewayBase {

public async updateBasePathMapping(domain: DomainConfig): Promise<void> {
try {
await throttledCall(this.apiGateway, "updateBasePathMapping", {
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName,
patchOperations: [{
op: "replace",
path: "/basePath",
value: domain.basePath,
}]
});
Globals.logInfo(`V1 - Updated API mapping from '${domain.apiMapping.basePath}'
await this.apiGateway.send(new UpdateBasePathMappingCommand({
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName,
patchOperations: [{
op: "replace",
path: "/basePath",
value: domain.basePath,
}]
}
));
Logging.logInfo(`V1 - Updated API mapping from '${domain.apiMapping.basePath}'
to '${domain.basePath}' for '${domain.givenDomainName}'`);
} catch (err) {
throw new Error(
Expand All @@ -154,17 +159,15 @@ class APIGatewayV1Wrapper extends APIGatewayBase {
}
}

/**
* Deletes basepath mapping
*/
public async deleteBasePathMapping(domain: DomainConfig): Promise<void> {
// Make API call
try {
await throttledCall(this.apiGateway, "deleteBasePathMapping", {
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName,
});
Globals.logInfo(`V1 - Removed '${domain.apiMapping.basePath}' base path mapping`);
await this.apiGateway.send(
new DeleteBasePathMappingCommand({
basePath: domain.apiMapping.basePath,
domainName: domain.givenDomainName,
})
);
Logging.logInfo(`V1 - Removed '${domain.apiMapping.basePath}' base path mapping`);
} catch (err) {
throw new Error(
`V1 - Unable to remove base path mapping for '${domain.givenDomainName}':\n${err.message}`
Expand Down
Loading