UNPKG

9.13 kBtext/coffeescriptView Raw
1fs = require 'fs'
2path = require 'path'
3colors = require 'colors'
4commander = require 'commander'
5CoffeeScript = require 'coffee-script'
6{spawn, exec} = require 'child_process'
7parseTemplate = require './parseTemplate'
8commandHelper = require './commandHelper'
9coffinChar = '\u26B0'.grey
10checkChar = '\u2713'.green
11crossChar = '\u2717'.red
12
13validateArgs = ->
14 valid = true
15 if commander.args.length is 0
16 console.error "You need to specify a coffin template to act on."
17 valid = false
18 if commander.validate? or commander.createStack? or commander.updateStack?
19 if commander.print?
20 console.error "I can't run that command if you're just printing to the console."
21 valid = false
22 if not process.env.AWS_CLOUDFORMATION_HOME and not commander['cfn-home']?
23 console.error "Either an AWS_CLOUDFORMATION_HOME environment variable or a --cfnHome switch is required."
24 valid = false
25 if not valid
26 process.stdout.write commander.helpInformation()
27 process.exit 0
28
29compileTemplate = (source, params, callback) =>
30 pre = "require('../lib/coffin') ->\n"
31 fs.readFile source, (err, code) =>
32 if err
33 console.error "#{source} not found"
34 process.exit 1
35 tabbedLines = []
36 if !params?
37 params = []
38 tabbedLines.push " @ARGV = #{JSON.stringify params}"
39 (tabbedLines.push(' ' + line) for line in code.toString().split '\n')
40 tabbedLines.push ' return'
41 code = tabbedLines.join '\n'
42 code = pre + code
43 compiled = CoffeeScript.compile code, {source, bare: true}
44 template = eval compiled, source
45 templateString = if commander.pretty then JSON.stringify template, null, 2 else JSON.stringify template
46 callback? templateString
47
48decompileCfnTemplate = (source, callback) ->
49 fs.readFile source, "utf8", (err, cfnTemp) ->
50 if err
51 console.log "#{source} not found"
52 process.exit 1
53 decompiled = parseTemplate JSON.parse(cfnTemp)
54 callback decompiled
55
56writeJsonTemplate = (json, templatePath, callback) ->
57 write = ->
58 json = ' ' if json.length <= 0
59 fs.writeFile templatePath, json, (err) ->
60 if err?
61 console.error "failed to write to #{templatePath}"
62 console.error err.message
63 process.exit 1
64 callback?()
65 base = path.dirname templatePath
66 path.exists base, (exists) ->
67 if exists then write() else exec "mkdir -p #{base}", write
68
69generateTempFileName = ->
70 e = process.env
71 tmpDir = e.TMPDIR || e.TMP || e.TEMP || '/tmp'
72 now = new Date()
73 dateStamp = now.getYear()
74 dateStamp <<= 4
75 dateStamp |= now.getMonth()
76 dateStamp <<= 5
77 dateStamp |= now.getDay()
78 rand = (Math.random() * 0x100000000 + 1).toString(36)
79 name = "#{dateStamp.toString(36)}-#{process.pid.toString(36)}-#{rand}.template"
80 path.join tmpDir, name
81
82generateOutputFileName = (source, extension) ->
83 base = commander.output || path.dirname source
84 filename = path.basename(source, path.extname(source)) + extension
85 path.join base, filename
86
87buildCfnPath = ->
88 cfnHome = commander['cfn-home'] || process.env.AWS_CLOUDFORMATION_HOME
89 return path.normalize path.join cfnHome, 'bin'
90
91validateTemplate = (templatePath, callback) =>
92 validateExec = spawn path.join(buildCfnPath(), 'cfn-validate-template'), ['--template-file', templatePath]
93 errorText = ''
94 resultText = ''
95 validateExec.stderr.on 'data', (data) -> errorText += data.toString()
96 validateExec.stdout.on 'data', (data) -> resultText += data.toString()
97 validateExec.on 'exit', (code) ->
98 if code is 0
99 process.stdout.write "#{checkChar}\n"
100 process.stdout.write resultText
101 else
102 process.stdout.write "#{crossChar}\n"
103 process.stderr.write errorText
104 callback?(code)
105
106updateOrCreateStack = (name, templatePath, compiled, callback) =>
107 args = ['--template-file', templatePath, '--stack-name', name]
108 if commandHelper.doesTemplateReferenceIAM compiled
109 args.push '-c'
110 args.push 'CAPABILITY_IAM'
111 updateExec = spawn "#{buildCfnPath()}/cfn-update-stack", args
112 updateErrorText = ''
113 resultText = ''
114 updateExec.stderr.on 'data', (data) -> updateErrorText += data.toString()
115 updateExec.stdout.on 'data', (data) -> resultText += data.toString()
116 updateExec.on 'exit', (code) ->
117 existsSyncFunc = if fs.existsSync? then fs.existsSync else path.existsSync
118 if existsSyncFunc "#{buildCfnPath()}/cfn-update-stack"
119 if code is 0
120 process.stdout.write "stack '#{name}' (updated) #{checkChar}\n"
121 process.stdout.write resultText
122 callback? code
123 return
124 if updateErrorText.match(/^cfn-update-stack: Malformed input-No updates are to be performed/)?
125 process.stdout.write "stack '#{name}' (no changes)\n"
126 process.stdout.write resultText
127 callback? 0
128 return
129 if not updateErrorText.match(/^cfn-update-stack: Malformed input-Stack with ID\/name/)?
130 console.error updateErrorText
131 callback? code
132 return
133 createStack name, templatePath, compiled, callback
134
135createStack = (name, templatePath, compiled, callback) =>
136 args = ['--template-file', templatePath, '--stack-name', name]
137 if commandHelper.doesTemplateReferenceIAM compiled
138 args.push '-c'
139 args.push 'CAPABILITY_IAM'
140 createExec = spawn "#{buildCfnPath()}/cfn-create-stack", args
141 errorText = ''
142 resultText = ''
143 createExec.stdout.on 'data', (data) -> resultText += data.toString()
144 createExec.stderr.on 'data', (data) -> errorText += data.toString()
145 createExec.on 'exit', (code) ->
146 if code isnt 0
147 if errorText.match(/^cfn-create-stack: Malformed input-AlreadyExistsException/)?
148 process.stderr.write "stack '#{name}' already exists #{crossChar}\n"
149 return
150 process.stderr.write errorText
151 return
152 process.stdout.write "stack '#{name}' (created) #{checkChar}\n"
153 process.stdout.write resultText
154 callback? code
155
156
157pretty =
158 switch: '-p, --pretty'
159 text: 'Add spaces and newlines to the resulting json to make it a little prettier'
160
161commander.version require('./coffin').version
162commander.usage '[options] <coffin template>'
163
164commander.option '-o, --output [dir]', 'Directory to output compiled file(s) to'
165commander.option '--cfn-home [dir]', 'The home of your AWS Cloudformation tools. Defaults to your AWS_CLOUDFORMATION_HOME environment variable.'
166commander.option pretty.switch, pretty.text
167
168printCommand = commander.command 'print [template]'
169printCommand.description 'Print the compiled template.'
170printCommand.action (template, params...) ->
171 validateArgs()
172 compileTemplate template, params, (compiled) ->
173 console.log compiled
174
175validateCommand = commander.command 'validate [template]'
176validateCommand.description 'Validate the compiled template. Either an AWS_CLOUDFORMATION_HOME environment variable or a --cfn-home switch is required.'
177validateCommand.action (template, params...) ->
178 validateArgs()
179 compileTemplate template, params, (compiled) ->
180 process.stdout.write "#{coffinChar} #{template} "
181 tempFileName = generateTempFileName()
182 writeJsonTemplate compiled, tempFileName, ->
183 validateTemplate tempFileName, (resultCode) ->
184
185stackCommand = commander.command 'stack [name] [template]'
186stackCommand.description 'Create or update the named stack using the compiled template. Either an AWS_CLOUDFORMATION_HOME environment variable or a --cfn-home switch is required.'
187stackCommand.action (name, template, params...) ->
188 validateArgs()
189 compileTemplate template, params, (compiled) ->
190 tempFileName = generateTempFileName()
191 writeJsonTemplate compiled, tempFileName, ->
192 process.stdout.write "#{coffinChar} #{template} -> "
193 updateOrCreateStack name, tempFileName, compiled, (resultCode) ->
194
195compileCommand = commander.command 'compile [template]'
196compileCommand.description 'Compile and write the template. The output file will have the same name as the coffin template plus a shiny new ".template" extension.'
197compileCommand.action (template, params...) ->
198 validateArgs()
199 compileTemplate template, params, (compiled) ->
200 process.stdout.write "#{coffinChar} #{template} -> "
201 fileName = generateOutputFileName template, ".template"
202 writeJsonTemplate compiled, fileName, ->
203 process.stdout.write "#{fileName}\n"
204
205decompileCommand = commander.command 'decompile [cfn-template]'
206decompileCommand.description 'experimental - Convert the given cloud formation template to coffin (or as best as we can). It will output a file of the same name with ".coffin" extension.'
207decompileCommand.action (cfnTemplate) ->
208 validateArgs()
209 decompileCfnTemplate cfnTemplate, (decompiled) ->
210 process.stdout.write "#{coffinChar} #{cfnTemplate} -> "
211 fileName = generateOutputFileName cfnTemplate, ".coffin"
212 writeJsonTemplate decompiled, fileName, ->
213 process.stdout.write "#{fileName}\n"
214
215
216
217showHelp = ->
218 process.stdout.write commander.helpInformation()
219 process.exit 1
220commander.command('').action showHelp
221commander.command('*').action showHelp
222
223if process.argv.length <=2
224 showHelp()
225
226module.exports.run = ->
227 commander.parse process.argv