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

Katafalkas support nested stacks #384

Merged
merged 13 commits into from
Sep 18, 2020
122 changes: 45 additions & 77 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "serverless-domain-manager",
"version": "4.2.2",
"version": "4.2.3",
"engines": {
"node": ">=4.0"
},
Expand Down Expand Up @@ -36,7 +36,7 @@
"*.js",
"*.ts",
"*.json",
"dist"
"dist/**/*.js"
],
"nyc": {
"extension": [
Expand All @@ -47,7 +47,7 @@
"@types/chai": "^4.2.12",
"@types/chai-spies": "^1.0.2",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.58",
"@types/node": "^12.12.62",
"aws-sdk-mock": "^1.7.0",
"chai": "^3.5.0",
"chai-spies": "^1.0.0",
Expand All @@ -59,6 +59,7 @@
"request": "^2.88.2",
"request-promise-native": "^1.0.9",
"serverless": "^1.83.0",
"serverless-plugin-split-stacks": "^1.9.3",
"shelljs": "^0.8.4",
"ts-node": "^8.10.2",
"tslint": "^5.20.1",
Expand All @@ -67,7 +68,7 @@
"wrappy": "^1.0.2"
},
"dependencies": {
"aws-sdk": "^2.752.0",
"aws-sdk": "^2.755.0",
"chalk": "^2.4.2"
}
}
34 changes: 33 additions & 1 deletion src/Globals.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ServerlessInstance, ServerlessOptions } from "./types";
import {ServerlessInstance, ServerlessOptions} from "./types";

export default class Globals {

Expand All @@ -20,4 +20,36 @@ export default class Globals {
tls_1_0: "TLS_1_0",
tls_1_2: "TLS_1_2",
};

/**
* Logs error message
* @param message: message to be printed
* @param domain: domain name
* @param debug: if true then show log only if SLS_DEBUG enabled on else anytime
*/
public static logError(message: any, domain?: string, debug?: boolean): void {
debug = debug === undefined ? true : debug;
const canLog = debug && process.env.SLS_DEBUG || !debug;
if (canLog) {
Globals.serverless.cli.log(
`Error: ${domain ? domain + ": " : ""} ${message}`, "Serverless Domain Manager",
);
}
}

/**
* Logs info message
* @param message: message to be printed
* @param domain: domain name
* @param debug: if true then show log only if SLS_DEBUG enabled on else anytime
*/
public static logInfo(message: any, domain?: string, debug?: boolean): void {
debug = debug === undefined ? true : debug;
const canLog = debug && process.env.SLS_DEBUG || !debug;
if (canLog) {
Globals.serverless.cli.log(
`Info: ${domain ? domain + ": " : ""} ${message}`, "Serverless Domain Manager",
);
}
}
}
73 changes: 61 additions & 12 deletions src/aws/cloud-formation-wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,37 @@ class CloudFormationWrapper {
}

/**
* Gets rest API id from CloudFormation stack
* Gets rest API id from CloudFormation stack or nested stack
*/
public async getApiId(domain: DomainConfig, stackName: string): Promise<string> {
let LogicalResourceId = "ApiGatewayRestApi";
let logicalResourceId = "ApiGatewayRestApi";
if (domain.apiType === Globals.apiTypes.http) {
LogicalResourceId = "HttpApi";
logicalResourceId = "HttpApi";
}
if (domain.apiType === Globals.apiTypes.websocket) {
LogicalResourceId = "WebsocketsApi";
logicalResourceId = "WebsocketsApi";
}

const params = {
LogicalResourceId,
StackName: stackName,
};

let response;
try {
response = await throttledCall(this.provider, "describeStackResource", params);
} catch (err) {
throw new Error(`Failed to find CloudFormation resources with an error: ${err}\n`);
// trying to get information for specified stack name
response = await this.getStack(logicalResourceId, stackName);
} catch {
// in case error trying to get information from the some of nested stacks
response = await this.getNestedStack(logicalResourceId, stackName);
}

if (!response) {
throw new Error(`Failed to find a stack ${stackName}\n`);
}

const apiId = response.StackResourceDetail.PhysicalResourceId;
if (!apiId) {
throw new Error(`No ApiId associated with CloudFormation stack ${stackName}`);
}

Globals.logInfo(`Found apiId: ${apiId}`, domain.givenDomainName, false);

return apiId;
}

Expand All @@ -64,6 +68,51 @@ class CloudFormationWrapper {
// [{Name: "export-name", Value: "export-value"}, ...] - > {"export-name": "export-value"}
return filteredExports.reduce((prev, current) => ({...prev, [current.Name]: current.Value}), {});
}

/**
* Returns a description of the specified resource in the specified stack.
*/
public async getStack(logicalResourceId: string, stackName: string) {
try {
return await throttledCall(this.provider, "describeStackResource", {
LogicalResourceId: logicalResourceId,
StackName: stackName,
});
} catch (err) {
throw new Error(`Failed to find CloudFormation resources with an error: ${err}\n`);
}
}

/**
* Returns a description of the specified resource in the specified nested stack.
*/
public async getNestedStack(logicalResourceId: string, stackName?: string) {
// get all stacks from the CloudFormation
const stacks = await getAWSPagedResults(
this.provider,
"describeStacks",
"Stacks",
"NextToken",
"NextToken",
{},
);

// filter stacks by given stackName
const filteredStackNames = stacks
.map((stack) => stack.StackName) // collect list of stack names
.filter((name) => name.includes(stackName)); // filter by stackName

let response;
for (const name of filteredStackNames) {
try {
response = await this.getStack(logicalResourceId, name);
break;
} catch (err) {
Globals.logError(err);
}
}
return response;
}
}

export = CloudFormationWrapper;