Skip to content

Commit

Permalink
Merge pull request #563 from amplify-education/feature/AT-8646-aws-sd…
Browse files Browse the repository at this point in the history
…k-v3

Move from AWS SDK V2 to V3
  • Loading branch information
rddimon committed Mar 22, 2023
2 parents c5d74af + 9283e81 commit 6998493
Show file tree
Hide file tree
Showing 30 changed files with 5,180 additions and 3,704 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
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
@@ -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
@@ -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
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

0 comments on commit 6998493

Please sign in to comment.