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
+ }
33
231
}
34
232
35
- module . exports = listAllCloudFormationStacksInAllRegions
233
+ module . exports = sync ;
0 commit comments