Skip to content
This repository was archived by the owner on Dec 9, 2024. It is now read-only.

Commit 4c41f44

Browse files
committed
fix cloudformation methods
1 parent 7b9232e commit 4c41f44

File tree

3 files changed

+233
-35
lines changed

3 files changed

+233
-35
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@serverless/aws-sdk-extra",
3-
"version": "1.1.0-beta.1",
3+
"version": "1.2.0",
44
"description": "The AWS SDK that includes with a handful of extra convenience methods.",
55
"main": "src/index.js",
66
"author": "Serverless, Inc.",

src/listAllCloudFormationStacksInARegion.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ const listAllCloudFormationStacksInARegion = async (
4343
summary.Region = region
4444
})
4545

46-
stacks = stacks.concat(stacks, res.StackSummaries);
46+
stacks = stacks.concat(res.StackSummaries);
4747
}
4848

4949
// If NextToken, call again...
Lines changed: 231 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,233 @@
1-
const AWS = require('aws-sdk')
2-
const listAllAwsRegions = require('./listAllAwsRegions')
3-
const listAllCloudFormationStacksInARegion = require('./listAllCloudFormationStacksInARegion')
4-
5-
const listAllCloudFormationStacksInAllRegions = async (
6-
config,
7-
{
8-
stackStatusFilter = null
9-
},
10-
stacks = []) => {
11-
12-
// Default to Stacks with an Active state, defined by these status values
13-
stackStatusFilter = stackStatusFilter || [
14-
'CREATE_COMPLETE',
15-
'UPDATE_COMPLETE',
16-
'ROLLBACK_COMPLETE',
17-
'IMPORT_COMPLETE',
18-
'IMPORT_ROLLBACK_COMPLETE',
19-
]
20-
21-
const regions = listAllAwsRegions()
22-
23-
const listAll = Promise.all(regions.map((region) => {
24-
return listAllCloudFormationStacksInARegion(
25-
this.config,
26-
{ region }
27-
)
28-
}))
29-
30-
const allRegionalStacks = await listAll()
31-
32-
return allRegionalStacks
1+
/**
2+
* Sync - Syncs a CloudFormation Stack
3+
*/
4+
5+
const getDefaults = require('./getDefaults');
6+
const { getClients } = require('../utils')
7+
8+
const sync = async (credentials, {
9+
cloudFormationStackName = null,
10+
region = null
11+
}) => {
12+
13+
// Validate
14+
if (!cloudFormationStackName) {
15+
throw new Error(`Missing "cloudFormationStackName" input.`)
16+
}
17+
if (!region) {
18+
throw new Error(`Missing "region" input.`)
19+
}
20+
21+
// Check credentials exist
22+
if (Object.keys(credentials).length === 0) {
23+
const msg =
24+
'AWS Credentials not found. Make sure you have a .env file in the current working directory. - Docs: https://git.io/JvArp';
25+
throw new Error(msg);
26+
}
27+
28+
let outputs = getDefaults('outputs')
29+
30+
await syncCloudFormation(outputs, credentials, cloudFormationStackName, region)
31+
32+
await syncApiGateway(outputs, credentials, region)
33+
34+
await syncAwsLambda(outputs, credentials, region)
35+
36+
return outputs;
37+
}
38+
39+
/**
40+
* Sync CloudFormation Stack
41+
*/
42+
43+
const syncCloudFormation = async (outputs, credentials, cloudFormationStackName, region) => {
44+
45+
const { cloudFormation, extras } = getClients(credentials, region)
46+
47+
/**
48+
* Fetch CloudFormation Stack Summary & Resources
49+
*/
50+
const stack = await Promise.all([
51+
cloudFormation.describeStacks({
52+
StackName: cloudFormationStackName
53+
}).promise(),
54+
extras.listAllCloudFormationStackResources({
55+
region,
56+
stackName: cloudFormationStackName
57+
}),
58+
])
59+
.then((res) => {
60+
const summary = res[0].Stacks[0]
61+
62+
return {
63+
summary,
64+
resources: res[1]
65+
}
66+
})
67+
68+
stack.resources.forEach((r) => {
69+
70+
// AWS::Lambda::Function
71+
if (r.ResourceType === 'AWS::Lambda::Function') {
72+
// Skip Serverless Framework Dashboard Lambda
73+
if (r.PhysicalResourceId.includes('custom-resource-apigw')) {
74+
return
75+
}
76+
outputs.functions = outputs.functions || {}
77+
outputs.functions[r.PhysicalResourceId] = getDefaults('fn')
78+
outputs.functions[r.PhysicalResourceId].name = r.PhysicalResourceId
79+
}
80+
81+
// AWS::IAM::Role
82+
if (r.ResourceType === 'AWS::IAM::Role') {
83+
// Skip Serverless Framework Dashboard role
84+
if (r.PhysicalResourceId.includes('EnterpriseLog')) {
85+
return
86+
}
87+
outputs.defaultRole = outputs.defaultRole || getDefaults('defaultRole')
88+
outputs.defaultRole.name = r.PhysicalResourceId
89+
}
90+
91+
// AWS::ApiGateway::RestApi
92+
if (r.ResourceType === 'AWS::ApiGateway::RestApi') {
93+
outputs.apiV1 = outputs.apiV1 || getDefaults('apiV1')
94+
outputs.apiV1.id = r.PhysicalResourceId
95+
}
96+
97+
// AWS::ApiGateway::RestApi
98+
if (r.ResourceType === 'AWS::ApiGateway::Deployment') {
99+
outputs.apiV1 = outputs.apiV1 || getDefaults('apiV1')
100+
outputs.apiV1.deploymentId = r.PhysicalResourceId
101+
}
102+
})
103+
104+
// Add service name
105+
let serviceName = cloudFormationStackName
106+
if (serviceName.includes('-')) {
107+
serviceName = serviceName.split('-')
108+
serviceName.pop()
109+
serviceName = serviceName.join('-')
110+
}
111+
outputs.service = serviceName
112+
113+
return outputs
114+
}
115+
116+
/**
117+
* Sync API Gateway
118+
*/
119+
120+
const syncApiGateway = async (outputs, credentials, region) => {
121+
122+
// If not API ID, skip...
123+
if (!outputs.apiV1) {
124+
return
125+
}
126+
127+
const { apiV1 } = getClients(credentials, region)
128+
129+
const paramsStages = {
130+
restApiId: outputs.apiV1.id,
131+
deploymentId: outputs.apiV1.deploymentId,
132+
};
133+
const resStages = await apiV1.getStages(paramsStages).promise()
134+
outputs.apiV1.stage = resStages.item[0].stageName
135+
136+
const paramsExport = {
137+
exportType: 'oas30', /* required */
138+
restApiId: outputs.apiV1.id, /* required */
139+
stageName: outputs.apiV1.stage,
140+
accepts: 'application/json',
141+
parameters: {
142+
'extensions': 'apigateway',
143+
}
144+
}
145+
const resExport = await apiV1.getExport(paramsExport).promise()
146+
147+
const openapi = JSON.parse(resExport.body.toString())
148+
149+
// Remove components for now, they are not needed and take up space
150+
if (openapi.components) {
151+
delete openapi.components
152+
}
153+
154+
// Remove unnecessary openapi data by traversing paths and methods
155+
for (const p in openapi.paths) {
156+
for (const m in openapi.paths[p]) {
157+
158+
const method = openapi.paths[p][m]
159+
160+
// Check the integration
161+
if (method['x-amazon-apigateway-integration']) {
162+
const integration = method['x-amazon-apigateway-integration']
163+
164+
// Pull Lambda data
165+
if (integration.uri && integration.uri.includes(':lambda:')) {
166+
let functionName = integration.uri.split('function:')[1]
167+
functionName = functionName.replace('/invocations', '')
168+
169+
// Add this to functions
170+
if (outputs.functions[functionName]) {
171+
outputs.functions[functionName].events.push({
172+
type: 'apiV1',
173+
path: p,
174+
method: m,
175+
// Add more data in the future
176+
})
177+
}
178+
}
179+
}
180+
}
181+
}
182+
183+
outputs.apiV1.openApi = openapi
184+
185+
}
186+
187+
/**
188+
* Sync AWS Lambda
189+
*/
190+
191+
const syncAwsLambda = async (outputs, credentials, region) => {
192+
193+
// If no functions, skip...
194+
if (!outputs.functions) {
195+
return
196+
}
197+
198+
const { lambda } = getClients(credentials, region)
199+
200+
for (let fn in outputs.functions) {
201+
fn = outputs.functions[fn]
202+
203+
const params = {
204+
FunctionName: fn.name,
205+
// Qualifier: "1"
206+
};
207+
const resFn = await lambda.getFunction(params).promise()
208+
209+
// Copy data
210+
fn.arn = resFn.Configuration.FunctionArn
211+
fn.description = resFn.Configuration.Description
212+
fn.version = resFn.Configuration.Version
213+
if (resFn.Tags.Variables && Object.keys(resFn.Tags.Variables).length) { fn.tags = resFn.Tags.Variables }
214+
if (resFn.Configuration.Environment.Variables && Object.keys(resFn.Configuration.Environment.Variables).length) { fn.tags = resFn.Configuration.Environment.Variables }
215+
fn.src.handler = resFn.Configuration.Handler
216+
fn.image = resFn.Configuration.ImageConfigResponse
217+
fn.runtime = resFn.Configuration.Runtime
218+
fn.timeout = resFn.Configuration.Timeout
219+
fn.role = resFn.Configuration.Role
220+
fn.memory = resFn.Configuration.MemorySize
221+
fn.reservedConcurrency = resFn.Concurrency
222+
if (resFn.Configuration.Layers && Object.keys(resFn.Configuration.Layers).length) { fn.layers = resFn.Configuration.Layers }
223+
if (resFn.Configuration.FileSystemConfigs && Object.keys(resFn.Configuration.FileSystemConfigs).length) { fn.fileSystem = resFn.Configuration.FileSystemConfigs }
224+
fn.deadLetter = resFn.Configuration.DeadLetterConfig
225+
// VPC
226+
if (resFn.Configuration.VpcConfig && resFn.Configuration.VpcConfig.SubnetIds.length ||
227+
resFn.Configuration.VpcConfig && resFn.Configuration.VpcConfig.SecurityGroupIds.length) {
228+
fn.vpc = resFn.Configuration.VpcConfig
229+
}
230+
}
33231
}
34232

35-
module.exports = listAllCloudFormationStacksInAllRegions
233+
module.exports = sync;

0 commit comments

Comments
 (0)