1 |
|
2 | (function() {
|
3 | var CoffeeScript, buildCfnPath, checkChar, coffinChar, colors, commandHelper, commander, compileCommand, compileTemplate, createStack, crossChar, decompileCfnTemplate, decompileCommand, exec, fs, generateOutputFileName, generateTempFileName, parseTemplate, path, pretty, printCommand, ref, showHelp, spawn, stackCommand, updateOrCreateStack, validateArgs, validateCommand, validateTemplate, writeJsonTemplate,
|
4 | slice = [].slice;
|
5 |
|
6 | fs = require('fs');
|
7 |
|
8 | path = require('path');
|
9 |
|
10 | colors = require('colors');
|
11 |
|
12 | commander = require('commander');
|
13 |
|
14 | CoffeeScript = require('coffee-script');
|
15 |
|
16 | ref = require('child_process'), spawn = ref.spawn, exec = ref.exec;
|
17 |
|
18 | parseTemplate = require('./parseTemplate');
|
19 |
|
20 | commandHelper = require('./commandHelper');
|
21 |
|
22 | coffinChar = '\u26B0'.grey;
|
23 |
|
24 | checkChar = '\u2713'.green;
|
25 |
|
26 | crossChar = '\u2717'.red;
|
27 |
|
28 | validateArgs = function() {
|
29 | var valid;
|
30 | valid = true;
|
31 | if (commander.args.length === 0) {
|
32 | console.error("You need to specify a coffin template to act on.");
|
33 | valid = false;
|
34 | }
|
35 | if ((commander.validate != null) || (commander.createStack != null) || (commander.updateStack != null)) {
|
36 | if (commander.print != null) {
|
37 | console.error("I can't run that command if you're just printing to the console.");
|
38 | valid = false;
|
39 | }
|
40 | if (!process.env.AWS_CLOUDFORMATION_HOME && (commander['cfn-home'] == null)) {
|
41 | console.error("Either an AWS_CLOUDFORMATION_HOME environment variable or a --cfnHome switch is required.");
|
42 | valid = false;
|
43 | }
|
44 | }
|
45 | if (!valid) {
|
46 | process.stdout.write(commander.helpInformation());
|
47 | return process.exit(0);
|
48 | }
|
49 | };
|
50 |
|
51 | compileTemplate = (function(_this) {
|
52 | return function(source, params, callback) {
|
53 | var pre;
|
54 | pre = "require('../lib/coffin') ->\n";
|
55 | return fs.readFile(source, function(err, code) {
|
56 | var compiled, i, len, line, ref1, tabbedLines, template, templateString;
|
57 | if (err) {
|
58 | console.error(source + " not found");
|
59 | process.exit(1);
|
60 | }
|
61 | tabbedLines = [];
|
62 | if (params == null) {
|
63 | params = [];
|
64 | }
|
65 | tabbedLines.push(" @ARGV = " + (JSON.stringify(params)));
|
66 | ref1 = code.toString().split('\n');
|
67 | for (i = 0, len = ref1.length; i < len; i++) {
|
68 | line = ref1[i];
|
69 | tabbedLines.push(' ' + line);
|
70 | }
|
71 | tabbedLines.push(' return');
|
72 | code = tabbedLines.join('\n');
|
73 | code = pre + code;
|
74 | compiled = CoffeeScript.compile(code, {
|
75 | source: source,
|
76 | bare: true
|
77 | });
|
78 | template = eval(compiled, source);
|
79 | templateString = commander.pretty ? JSON.stringify(template, null, 2) : JSON.stringify(template);
|
80 | return typeof callback === "function" ? callback(templateString) : void 0;
|
81 | });
|
82 | };
|
83 | })(this);
|
84 |
|
85 | decompileCfnTemplate = function(source, callback) {
|
86 | return fs.readFile(source, "utf8", function(err, cfnTemp) {
|
87 | var decompiled;
|
88 | if (err) {
|
89 | console.log(source + " not found");
|
90 | process.exit(1);
|
91 | }
|
92 | decompiled = parseTemplate(JSON.parse(cfnTemp));
|
93 | return callback(decompiled);
|
94 | });
|
95 | };
|
96 |
|
97 | writeJsonTemplate = function(json, templatePath, callback) {
|
98 | var base, write;
|
99 | write = function() {
|
100 | if (json.length <= 0) {
|
101 | json = ' ';
|
102 | }
|
103 | return fs.writeFile(templatePath, json, function(err) {
|
104 | if (err != null) {
|
105 | console.error("failed to write to " + templatePath);
|
106 | console.error(err.message);
|
107 | process.exit(1);
|
108 | }
|
109 | return typeof callback === "function" ? callback() : void 0;
|
110 | });
|
111 | };
|
112 | base = path.dirname(templatePath);
|
113 | return path.exists(base, function(exists) {
|
114 | if (exists) {
|
115 | return write();
|
116 | } else {
|
117 | return exec("mkdir -p " + base, write);
|
118 | }
|
119 | });
|
120 | };
|
121 |
|
122 | generateTempFileName = function() {
|
123 | var dateStamp, e, name, now, rand, tmpDir;
|
124 | e = process.env;
|
125 | tmpDir = e.TMPDIR || e.TMP || e.TEMP || '/tmp';
|
126 | now = new Date();
|
127 | dateStamp = now.getYear();
|
128 | dateStamp <<= 4;
|
129 | dateStamp |= now.getMonth();
|
130 | dateStamp <<= 5;
|
131 | dateStamp |= now.getDay();
|
132 | rand = (Math.random() * 0x100000000 + 1).toString(36);
|
133 | name = (dateStamp.toString(36)) + "-" + (process.pid.toString(36)) + "-" + rand + ".template";
|
134 | return path.join(tmpDir, name);
|
135 | };
|
136 |
|
137 | generateOutputFileName = function(source, extension) {
|
138 | var base, filename;
|
139 | base = commander.output || path.dirname(source);
|
140 | filename = path.basename(source, path.extname(source)) + extension;
|
141 | return path.join(base, filename);
|
142 | };
|
143 |
|
144 | buildCfnPath = function() {
|
145 | var cfnHome;
|
146 | cfnHome = commander['cfn-home'] || process.env.AWS_CLOUDFORMATION_HOME;
|
147 | return path.normalize(path.join(cfnHome, 'bin'));
|
148 | };
|
149 |
|
150 | validateTemplate = (function(_this) {
|
151 | return function(templatePath, callback) {
|
152 | var errorText, resultText, validateExec;
|
153 | validateExec = spawn(path.join(buildCfnPath(), 'cfn-validate-template'), ['--template-file', templatePath]);
|
154 | errorText = '';
|
155 | resultText = '';
|
156 | validateExec.stderr.on('data', function(data) {
|
157 | return errorText += data.toString();
|
158 | });
|
159 | validateExec.stdout.on('data', function(data) {
|
160 | return resultText += data.toString();
|
161 | });
|
162 | return validateExec.on('exit', function(code) {
|
163 | if (code === 0) {
|
164 | process.stdout.write(checkChar + "\n");
|
165 | process.stdout.write(resultText);
|
166 | } else {
|
167 | process.stdout.write(crossChar + "\n");
|
168 | process.stderr.write(errorText);
|
169 | }
|
170 | return typeof callback === "function" ? callback(code) : void 0;
|
171 | });
|
172 | };
|
173 | })(this);
|
174 |
|
175 | updateOrCreateStack = (function(_this) {
|
176 | return function(name, templatePath, compiled, callback) {
|
177 | var args, resultText, updateErrorText, updateExec;
|
178 | args = ['--template-file', templatePath, '--stack-name', name];
|
179 | if (commandHelper.doesTemplateReferenceIAM(compiled)) {
|
180 | args.push('-c');
|
181 | args.push('CAPABILITY_IAM');
|
182 | }
|
183 | updateExec = spawn((buildCfnPath()) + "/cfn-update-stack", args);
|
184 | updateErrorText = '';
|
185 | resultText = '';
|
186 | updateExec.stderr.on('data', function(data) {
|
187 | return updateErrorText += data.toString();
|
188 | });
|
189 | updateExec.stdout.on('data', function(data) {
|
190 | return resultText += data.toString();
|
191 | });
|
192 | return updateExec.on('exit', function(code) {
|
193 | var existsSyncFunc;
|
194 | existsSyncFunc = fs.existsSync != null ? fs.existsSync : path.existsSync;
|
195 | if (existsSyncFunc((buildCfnPath()) + "/cfn-update-stack")) {
|
196 | if (code === 0) {
|
197 | process.stdout.write("stack '" + name + "' (updated) " + checkChar + "\n");
|
198 | process.stdout.write(resultText);
|
199 | if (typeof callback === "function") {
|
200 | callback(code);
|
201 | }
|
202 | return;
|
203 | }
|
204 | if (updateErrorText.match(/^cfn-update-stack: Malformed input-No updates are to be performed/) != null) {
|
205 | process.stdout.write("stack '" + name + "' (no changes)\n");
|
206 | process.stdout.write(resultText);
|
207 | if (typeof callback === "function") {
|
208 | callback(0);
|
209 | }
|
210 | return;
|
211 | }
|
212 | if (updateErrorText.match(/^cfn-update-stack: Malformed input-Stack with ID\/name/) == null) {
|
213 | console.error(updateErrorText);
|
214 | if (typeof callback === "function") {
|
215 | callback(code);
|
216 | }
|
217 | return;
|
218 | }
|
219 | }
|
220 | return createStack(name, templatePath, compiled, callback);
|
221 | });
|
222 | };
|
223 | })(this);
|
224 |
|
225 | createStack = (function(_this) {
|
226 | return function(name, templatePath, compiled, callback) {
|
227 | var args, createExec, errorText, resultText;
|
228 | args = ['--template-file', templatePath, '--stack-name', name];
|
229 | if (commandHelper.doesTemplateReferenceIAM(compiled)) {
|
230 | args.push('-c');
|
231 | args.push('CAPABILITY_IAM');
|
232 | }
|
233 | createExec = spawn((buildCfnPath()) + "/cfn-create-stack", args);
|
234 | errorText = '';
|
235 | resultText = '';
|
236 | createExec.stdout.on('data', function(data) {
|
237 | return resultText += data.toString();
|
238 | });
|
239 | createExec.stderr.on('data', function(data) {
|
240 | return errorText += data.toString();
|
241 | });
|
242 | return createExec.on('exit', function(code) {
|
243 | if (code !== 0) {
|
244 | if (errorText.match(/^cfn-create-stack: Malformed input-AlreadyExistsException/) != null) {
|
245 | process.stderr.write("stack '" + name + "' already exists " + crossChar + "\n");
|
246 | return;
|
247 | }
|
248 | process.stderr.write(errorText);
|
249 | return;
|
250 | }
|
251 | process.stdout.write("stack '" + name + "' (created) " + checkChar + "\n");
|
252 | process.stdout.write(resultText);
|
253 | return typeof callback === "function" ? callback(code) : void 0;
|
254 | });
|
255 | };
|
256 | })(this);
|
257 |
|
258 | pretty = {
|
259 | "switch": '-p, --pretty',
|
260 | text: 'Add spaces and newlines to the resulting json to make it a little prettier'
|
261 | };
|
262 |
|
263 | commander.version(require('./coffin').version);
|
264 |
|
265 | commander.usage('[options] <coffin template>');
|
266 |
|
267 | commander.option('-o, --output [dir]', 'Directory to output compiled file(s) to');
|
268 |
|
269 | commander.option('--cfn-home [dir]', 'The home of your AWS Cloudformation tools. Defaults to your AWS_CLOUDFORMATION_HOME environment variable.');
|
270 |
|
271 | commander.option(pretty["switch"], pretty.text);
|
272 |
|
273 | printCommand = commander.command('print [template]');
|
274 |
|
275 | printCommand.description('Print the compiled template.');
|
276 |
|
277 | printCommand.action(function() {
|
278 | var params, template;
|
279 | template = arguments[0], params = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
280 | validateArgs();
|
281 | return compileTemplate(template, params, function(compiled) {
|
282 | return console.log(compiled);
|
283 | });
|
284 | });
|
285 |
|
286 | validateCommand = commander.command('validate [template]');
|
287 |
|
288 | validateCommand.description('Validate the compiled template. Either an AWS_CLOUDFORMATION_HOME environment variable or a --cfn-home switch is required.');
|
289 |
|
290 | validateCommand.action(function() {
|
291 | var params, template;
|
292 | template = arguments[0], params = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
293 | validateArgs();
|
294 | return compileTemplate(template, params, function(compiled) {
|
295 | var tempFileName;
|
296 | process.stdout.write(coffinChar + " " + template + " ");
|
297 | tempFileName = generateTempFileName();
|
298 | return writeJsonTemplate(compiled, tempFileName, function() {
|
299 | return validateTemplate(tempFileName, function(resultCode) {});
|
300 | });
|
301 | });
|
302 | });
|
303 |
|
304 | stackCommand = commander.command('stack [name] [template]');
|
305 |
|
306 | stackCommand.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.');
|
307 |
|
308 | stackCommand.action(function() {
|
309 | var name, params, template;
|
310 | name = arguments[0], template = arguments[1], params = 3 <= arguments.length ? slice.call(arguments, 2) : [];
|
311 | validateArgs();
|
312 | return compileTemplate(template, params, function(compiled) {
|
313 | var tempFileName;
|
314 | tempFileName = generateTempFileName();
|
315 | return writeJsonTemplate(compiled, tempFileName, function() {
|
316 | process.stdout.write(coffinChar + " " + template + " -> ");
|
317 | return updateOrCreateStack(name, tempFileName, compiled, function(resultCode) {});
|
318 | });
|
319 | });
|
320 | });
|
321 |
|
322 | compileCommand = commander.command('compile [template]');
|
323 |
|
324 | compileCommand.description('Compile and write the template. The output file will have the same name as the coffin template plus a shiny new ".template" extension.');
|
325 |
|
326 | compileCommand.action(function() {
|
327 | var params, template;
|
328 | template = arguments[0], params = 2 <= arguments.length ? slice.call(arguments, 1) : [];
|
329 | validateArgs();
|
330 | return compileTemplate(template, params, function(compiled) {
|
331 | var fileName;
|
332 | process.stdout.write(coffinChar + " " + template + " -> ");
|
333 | fileName = generateOutputFileName(template, ".template");
|
334 | return writeJsonTemplate(compiled, fileName, function() {
|
335 | return process.stdout.write(fileName + "\n");
|
336 | });
|
337 | });
|
338 | });
|
339 |
|
340 | decompileCommand = commander.command('decompile [cfn-template]');
|
341 |
|
342 | decompileCommand.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.');
|
343 |
|
344 | decompileCommand.action(function(cfnTemplate) {
|
345 | validateArgs();
|
346 | return decompileCfnTemplate(cfnTemplate, function(decompiled) {
|
347 | var fileName;
|
348 | process.stdout.write(coffinChar + " " + cfnTemplate + " -> ");
|
349 | fileName = generateOutputFileName(cfnTemplate, ".coffin");
|
350 | return writeJsonTemplate(decompiled, fileName, function() {
|
351 | return process.stdout.write(fileName + "\n");
|
352 | });
|
353 | });
|
354 | });
|
355 |
|
356 | showHelp = function() {
|
357 | process.stdout.write(commander.helpInformation());
|
358 | return process.exit(1);
|
359 | };
|
360 |
|
361 | commander.command('').action(showHelp);
|
362 |
|
363 | commander.command('*').action(showHelp);
|
364 |
|
365 | if (process.argv.length <= 2) {
|
366 | showHelp();
|
367 | }
|
368 |
|
369 | module.exports.run = function() {
|
370 | return commander.parse(process.argv);
|
371 | };
|
372 |
|
373 | }).call(this);
|