1 | fs = require 'fs'
|
2 | vm = require 'vm'
|
3 | path = require 'path'
|
4 | CoffeeScript = require 'coffee-script'
|
5 |
|
6 | class CloudFormationTemplateContext
|
7 | constructor: ->
|
8 | @_resources = {}
|
9 | @_parameters = {}
|
10 | @_mappings = null
|
11 | @_outputs = {}
|
12 | @_description = null
|
13 | @_conditions = null
|
14 | @Params = {}
|
15 | @Resources = {}
|
16 | @Mappings = {}
|
17 | @Conditions = {}
|
18 | @AWS =
|
19 | AutoScaling:
|
20 | AutoScalingGroup: null
|
21 | LaunchConfiguration: null
|
22 | ScalingPolicy: null
|
23 | LifecycleHook: null
|
24 | ScheduledAction: null
|
25 | Trigger: null
|
26 | CloudFormation:
|
27 | Authentication: null
|
28 | CustomResource: null
|
29 | Stack: null
|
30 | WaitCondition: null
|
31 | WaitConditionHandle: null
|
32 | CloudFront:
|
33 | Distribution: null
|
34 | CloudWatch:
|
35 | Alarm: null
|
36 | CodeDeploy:
|
37 | Application: null
|
38 | DeploymentConfig: null
|
39 | DeploymentGroup: null
|
40 | CodePipeline:
|
41 | Pipeline: null
|
42 | CustomActionType: null
|
43 | Config:
|
44 | ConfigRule: null
|
45 | ConfigurationRecorder: null
|
46 | DeliveryChannel: null
|
47 | DirectoryService:
|
48 | MicrosoftAD: null
|
49 | SimpleAD: null
|
50 | CloudTrail:
|
51 | Trail: null
|
52 | DataPipeline:
|
53 | Pipeline: null
|
54 | DynamoDB:
|
55 | Table: null
|
56 | EC2:
|
57 | CustomerGateway: null
|
58 | DHCPOptions: null
|
59 | EIP: null
|
60 | EIPAssociation: null
|
61 | Instance: null
|
62 | InternetGateway: null
|
63 | NetworkAcl: null
|
64 | NetworkAclEntry: null
|
65 | NetworkInterface: null
|
66 | PlacementGroup: null
|
67 | Route: null
|
68 | RouteTable: null
|
69 | SecurityGroup: null
|
70 | SecurityGroupIngress: null
|
71 | SecurityGroupEgress: null
|
72 | Subnet: null
|
73 | SubnetNetworkAclAssociation: null
|
74 | SubnetRouteTableAssociation: null
|
75 | SpotFleet: null
|
76 | Volume: null
|
77 | VolumeAttachment: null
|
78 | VPC: null
|
79 | VPCDHCPOptionsAssociation: null
|
80 | VPCEndpoint: null
|
81 | VPCGatewayAttachment: null
|
82 | VPNConnection: null
|
83 | VPNGateway: null
|
84 | ECS:
|
85 | Cluster: null
|
86 | Service: null
|
87 | TaskDefinition: null
|
88 | ElastiCache:
|
89 | CacheCluster: null
|
90 | ParameterGroup: null
|
91 | ReplicationGroup: null
|
92 | SecurityGroup: null
|
93 | SecurityGroupIngress: null
|
94 | SubnetGroup: null
|
95 | ElasticBeanstalk:
|
96 | Application: null
|
97 | ApplicationVersion: null
|
98 | Environment: null
|
99 | ConfigurationTemplate: null
|
100 | ElasticLoadBalancing:
|
101 | LoadBalancer: null
|
102 | EFS:
|
103 | FileSystem: null
|
104 | MountTarget: null
|
105 | IAM:
|
106 | AccessKey: null
|
107 | Group: null
|
108 | InstanceProfile: null
|
109 | ManagedPolicy: null
|
110 | Policy: null
|
111 | Role: null
|
112 | User: null
|
113 | UserToGroupAddition: null
|
114 | Kinesis:
|
115 | Stream: null
|
116 | KMS:
|
117 | Key: null
|
118 | Logs:
|
119 | Destination: null
|
120 | LogGroup: null
|
121 | LogStream: null
|
122 | MetricFilter: null
|
123 | SubscriptionFilter: null
|
124 | Lambda:
|
125 | EventSourceMapping: null
|
126 | Function: null
|
127 | Permission: null
|
128 | OpsWorks:
|
129 | App: null
|
130 | Instance: null
|
131 | Layer: null
|
132 | Stack: null
|
133 | Redshift:
|
134 | Cluster: null
|
135 | ClusterParameterGroup: null
|
136 | ClusterSecurityGroup: null
|
137 | ClusterSubnetGroup: null
|
138 | RDS:
|
139 | DBCluster: null
|
140 | DBClusterParameterGroup: null
|
141 | DBInstance: null
|
142 | DBParameterGroup: null
|
143 | DBSubnetGroup: null
|
144 | DBSecurityGroup: null
|
145 | DBSecurityGroupIngress: null
|
146 | EventSubscription: null
|
147 | OptionGroup: null
|
148 | Route53:
|
149 | RecordSet: null
|
150 | RecordSetGroup: null
|
151 | HostedZone: null
|
152 | HealthCheck: null
|
153 | SDB:
|
154 | Domain: null
|
155 | S3:
|
156 | Bucket: null
|
157 | BucketPolicy: null
|
158 | SNS:
|
159 | Topic: null
|
160 | TopicPolicy: null
|
161 | SQS:
|
162 | Queue: null
|
163 | QueuePolicy: null
|
164 | SSM:
|
165 | Document: null
|
166 | WAF:
|
167 | ByteMatchSet: null
|
168 | IPSet: null
|
169 | Rule: null
|
170 | SqlInjectionMatchSet: null
|
171 | WebACL: null
|
172 | WorkSpaces:
|
173 | Workspace: null
|
174 | @Param =
|
175 | String: (name, arg1, arg2) => @_paramByType 'String', name, arg1, arg2
|
176 | Number: (name, arg1, arg2) => @_paramByType 'Number', name, arg1, arg2
|
177 | CommaDelimitedList: (name, arg1, arg2) => @_paramByType 'CommaDelimitedList', name, arg1, arg2
|
178 | AWS: (type, name, arg1, arg2) => @_paramByType "AWS::#{type}", name, arg1, arg2
|
179 | AWSList: (type, name, arg1, arg2) => @_paramByType "List<AWS::#{type}>", name, arg1, arg2
|
180 | @_buildCall null, null, 'AWS', @AWS
|
181 |
|
182 | _paramByType: (type, name, arg1, arg2) =>
|
183 | result = {}
|
184 | if not arg1?
|
185 | result[name] = {}
|
186 | else if not arg2?
|
187 | result[name] = if typeof arg1 is 'string' then Description: arg1 else arg1
|
188 | else
|
189 | result[name] = arg2
|
190 | result[name].Description = arg1
|
191 | result[name].Type = type
|
192 | @_set result, @_parameters
|
193 | @Params[name] = Ref: name
|
194 |
|
195 | _buildCall: (parent, lastKey, awsType, leaf) =>
|
196 | if leaf?
|
197 | for key, val of leaf
|
198 | @_buildCall leaf, key, "#{awsType}::#{key}", val
|
199 | return
|
200 | parent[lastKey] = (name, props) =>
|
201 | @_resourceByType awsType, name, props
|
202 |
|
203 |
|
204 | DeclareResource: (name) =>
|
205 | @Resources[name] ?= Ref: name
|
206 |
|
207 | _resourceByType: (type, name, props) =>
|
208 | result = {}
|
209 | if props?.Metadata? or props?.Properties? or props?.DependsOn? or props?.UpdatePolicy? or props?.CreationPolicy? or props?.Condition?
|
210 | result[name] = props
|
211 | result[name].Type = type
|
212 | else
|
213 | result[name] =
|
214 | Type: type
|
215 | Properties: props
|
216 | @_set result, @_resources
|
217 | @DeclareResource name
|
218 |
|
219 | _set: (source, target) ->
|
220 | for key, val of source
|
221 | target[key] = val
|
222 |
|
223 | Mapping: (name, map) =>
|
224 | @_mappings ?= {}
|
225 | result = {}
|
226 | result[name] = map
|
227 | @_set result, @_mappings
|
228 |
|
229 | Output: (name, args...) =>
|
230 | result = {}
|
231 | if args.length is 1
|
232 | result[name] =
|
233 | Value: args[0]
|
234 | if args.length is 2
|
235 | result[name] =
|
236 | Description: args[0]
|
237 | Value: args[1]
|
238 | @_set result, @_outputs
|
239 |
|
240 | Condition: (name, intrinsicfn) =>
|
241 | @_conditions ?= {}
|
242 | result = {}
|
243 | result[name] = intrinsicfn
|
244 | @_set result, @_conditions
|
245 |
|
246 | Description: (d) => @_description = d
|
247 |
|
248 | Tag: (key, val) ->
|
249 | Key: key
|
250 | Value: val
|
251 |
|
252 |
|
253 | Join: (delimiter, args...) ->
|
254 | if args.length is 1 and (args[0] instanceof Array)
|
255 | 'Fn::Join': [ delimiter, args[0] ]
|
256 | else
|
257 | 'Fn::Join': [ delimiter, args ]
|
258 | FindInMap: (args...) ->
|
259 | 'Fn::FindInMap': args
|
260 | GetAtt: (args...) ->
|
261 | 'Fn::GetAtt': args
|
262 | Base64: (arg) ->
|
263 | 'Fn::Base64': arg
|
264 | GetAZs: (arg) ->
|
265 | 'Fn::GetAZs': arg
|
266 | Select: (index, args...) ->
|
267 | if args.length is 1 and (args[0] instanceof Array)
|
268 | 'Fn::Select': [index, args[0]]
|
269 | else
|
270 | 'Fn::Select': [index, args]
|
271 | And: (condition, conditions...) ->
|
272 | if conditions.length is 1 and (conditions[0] instanceof Array)
|
273 | 'Fn::And': [condition, conditions[0]]
|
274 | else
|
275 | 'Fn::And': [condition, conditions]
|
276 | Equals: (value_1, value_2) ->
|
277 | 'Fn::Equals': [ value_1, value_2 ]
|
278 | If: (condition, value_if_true, value_if_false) ->
|
279 | 'Fn::If': [condition, value_if_true, value_if_false]
|
280 | Not: (condition) ->
|
281 | 'Fn::Not': [condition]
|
282 | Or: (condition, conditions...) ->
|
283 | if conditions.length is 1 and (conditions[0] instanceof Array)
|
284 | 'Fn::Or': [condition, conditions[0]]
|
285 | else
|
286 | 'Fn::Or': [condition, conditions]
|
287 | AccountId: Ref: 'AWS::AccountId'
|
288 | NotificationARNs: Ref: 'AWS::NotificationARNs'
|
289 | NoValue: Ref: 'AWS::NoValue'
|
290 | Region: Ref: 'AWS::Region'
|
291 | StackId: Ref: 'AWS::StackId'
|
292 | StackName: Ref: 'AWS::StackName'
|
293 | InitScript: (arg) ->
|
294 | existsSyncFunc = if fs.existsSync? then fs.existsSync else path.existsSync
|
295 | if not existsSyncFunc(arg)
|
296 | text = arg
|
297 | else
|
298 | text = fs.readFileSync(arg).toString()
|
299 | chunks = []
|
300 |
|
301 | pattern = /((.|\n)*?)%{([^}?]+)}?((.|\n)*)/
|
302 | match = text.match pattern
|
303 | while match
|
304 | chunks.push match[1]
|
305 | compiled = CoffeeScript.compile match[3], {bare: true}
|
306 | chunks.push eval compiled
|
307 | text = match[4]
|
308 | match = text.match pattern
|
309 | chunks.push text if text and text.length > 0
|
310 | @Base64 @Join '', chunks
|
311 |
|
312 | module.exports.CloudFormationTemplateContext = CloudFormationTemplateContext
|
313 |
|
314 | module.exports = (func) ->
|
315 | context = new CloudFormationTemplateContext
|
316 | func.apply context, [context]
|
317 | template = AWSTemplateFormatVersion: '2010-09-09'
|
318 | template.Description = context._description if context._description?
|
319 | template.Parameters = context._parameters
|
320 | template.Mappings = context._mappings if context._mappings?
|
321 | template.Resources = context._resources
|
322 | template.Outputs = context._outputs
|
323 | template.Conditions = context._conditions if context._conditions?
|
324 | template
|
325 |
|
326 | require('pkginfo')(module, 'version')
|