serverless-domain-manager
Version:
Serverless plugin for managing custom domains with API Gateways.
182 lines (181 loc) • 8.53 kB
JavaScript
/**
* Wrapper class for AWS CloudFormation provider
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
const globals_1 = __importDefault(require("../globals"));
const logging_1 = __importDefault(require("../logging"));
const utils_1 = require("../utils");
class CloudFormationWrapper {
constructor(credentials) {
// for the CloudFormation stack we should use the `base` stage not the plugin custom stage
const defaultStackName = globals_1.default.serverless.service.service + "-" + globals_1.default.getBaseStage();
this.stackName = globals_1.default.serverless.service.provider.stackName || defaultStackName;
this.cloudFormation = new client_cloudformation_1.CloudFormationClient({
credentials,
region: globals_1.default.getRegion(),
retryStrategy: globals_1.default.getRetryStrategy(),
requestHandler: globals_1.default.getRequestHandler(),
endpoint: globals_1.default.getServiceEndpoint("cloudformation")
});
}
/**
* Get an API id from the existing config or CloudFormation stack resources or outputs
*/
findApiId(apiType) {
return __awaiter(this, void 0, void 0, function* () {
const configApiId = yield this.getConfigId(apiType);
if (configApiId) {
return configApiId;
}
return yield this.getStackApiId(apiType);
});
}
/**
* Get an API id from the existing config or CloudFormation stack based on provider.apiGateway params
*/
getConfigId(apiType) {
return __awaiter(this, void 0, void 0, function* () {
const apiGateway = globals_1.default.serverless.service.provider.apiGateway || {};
const apiIdKey = globals_1.default.gatewayAPIIdKeys[apiType];
const apiGatewayValue = apiGateway[apiIdKey];
if (apiGatewayValue) {
if (typeof apiGatewayValue === "string") {
return apiGatewayValue;
}
return yield this.getCloudformationId(apiGatewayValue, apiType);
}
return null;
});
}
getCloudformationId(apiGatewayValue, apiType) {
return __awaiter(this, void 0, void 0, function* () {
// in case object and Fn::ImportValue try to get API id from the CloudFormation outputs
const importName = apiGatewayValue[globals_1.default.CFFuncNames.fnImport];
if (importName) {
const importValues = yield this.getImportValues([importName]);
const nameValue = importValues[importName];
if (!nameValue) {
logging_1.default.logWarning(`CloudFormation ImportValue '${importName}' not found in the outputs`);
}
return nameValue;
}
const ref = apiGatewayValue[globals_1.default.CFFuncNames.ref];
if (ref) {
try {
return yield this.getStackApiId(apiType, ref);
}
catch (error) {
logging_1.default.logWarning(`Unable to get ref ${ref} value.\n ${error.message}`);
return null;
}
}
// log warning not supported restApiId
logging_1.default.logWarning(`Unsupported apiGateway.${apiType} object`);
return null;
});
}
/**
* Gets rest API id from CloudFormation stack or nested stack
*/
getStackApiId(apiType_1) {
return __awaiter(this, arguments, void 0, function* (apiType, logicalResourceId = null) {
if (!logicalResourceId) {
logicalResourceId = globals_1.default.CFResourceIds[apiType];
}
let response;
try {
// trying to get information for specified stack name
response = yield this.getStack(logicalResourceId, this.stackName);
}
catch (_a) {
// in case error trying to get information from some of nested stacks
response = yield this.getNestedStack(logicalResourceId, this.stackName);
}
if (!response) {
throw new Error(`Failed to find logicalResourceId '${logicalResourceId}' for the stack ${this.stackName}\n` +
"Make sure the stack exists and the API gateway event is added");
}
const apiId = response.StackResourceDetail.PhysicalResourceId;
if (!apiId) {
throw new Error(`No ApiId associated with CloudFormation stack ${this.stackName}`);
}
return apiId;
});
}
/**
* Gets values by names from cloudformation exports
*/
getImportValues(names) {
return __awaiter(this, void 0, void 0, function* () {
const exports = yield (0, utils_1.getAWSPagedResults)(this.cloudFormation, "Exports", "NextToken", "NextToken", new client_cloudformation_1.ListExportsCommand({}));
// filter Exports by names which we need
const filteredExports = exports.filter((item) => names.indexOf(item.Name) !== -1);
// converting a list of unique values to dict
// [{Name: "export-name", Value: "export-value"}, ...] - > {"export-name": "export-value"}
return filteredExports.reduce((prev, current) => (Object.assign(Object.assign({}, prev), { [current.Name]: current.Value })), {});
});
}
/**
* Returns a description of the specified resource in the specified stack.
*/
getStack(logicalResourceId, stackName) {
return __awaiter(this, void 0, void 0, function* () {
try {
return yield this.cloudFormation.send(new client_cloudformation_1.DescribeStackResourceCommand({
LogicalResourceId: logicalResourceId,
StackName: stackName
}));
}
catch (err) {
throw new Error(`Failed to find CloudFormation resources with an error: ${err.message}\n`);
}
});
}
/**
* Returns a description of the specified resource in the specified nested stack.
*/
getNestedStack(logicalResourceId, stackName) {
return __awaiter(this, void 0, void 0, function* () {
// get all stacks from the CloudFormation
const stacks = yield (0, utils_1.getAWSPagedResults)(this.cloudFormation, "Stacks", "NextToken", "NextToken", new client_cloudformation_1.DescribeStacksCommand({}));
// filter stacks by given stackName and check by nested stack RootId
const regex = new RegExp("/" + stackName + "/");
const filteredStackNames = stacks
.reduce((acc, stack) => {
if (!stack.RootId) {
return acc;
}
const match = stack.RootId.match(regex);
if (match) {
acc.push(stack.StackName);
}
return acc;
}, []);
for (const name of filteredStackNames) {
try {
// stop the loop and return the stack details in case the first one found
// in case of error continue the looping
return yield this.getStack(logicalResourceId, name);
}
catch (err) {
logging_1.default.logWarning(err.message);
}
}
return null;
});
}
}
module.exports = CloudFormationWrapper;
;