UNPKG

12.4 kBJavaScriptView Raw
1var _ = require('underscore')
2var fs = require('fs')
3var utils = require('./utils')
4var txain = require('txain')
5var path = require('path')
6var inquirer = require('inquirer')
7var multiline = require('multiline')
8var AWS = require('aws-sdk')
9require('colors')
10var temp = require('temp').track()
11
12var configurationKeys = ['timeout', 'memory_size', 'role', 'description']
13
14module.exports = function(yargs) {
15 yargs.command('defaults', 'Handles default values for new controllers', function (yargs) {
16
17 yargs.command('set', 'Sets the default value of a key for new controllers', function(yargs) {
18 var questions = [
19 {
20 'name': 'key',
21 'type': 'list',
22 'choices': configurationKeys,
23 'message': 'Choose the setting you want to set',
24 },
25 {
26 'name': 'value',
27 'message': 'Write the value for this setting',
28 },
29 ]
30 inquirer.prompt(questions, function(answers) {
31 var config = utils.readConfig()
32 config.defaults = config.defaults || {}
33 config.defaults.controllers = config.defaults.controllers || {}
34 config.defaults.controllers[answers.key] = answers.value
35 utils.writeConfig(config)
36 console.log('Updated controllers defaults configuration successfully')
37 })
38 })
39
40 yargs.command('get', 'Gets the current defaults values', function(yargs) {
41 var config = utils.readConfig()
42 config.defaults = config.defaults || {}
43 config.defaults.controllers = config.defaults.controllers || {}
44 configurationKeys.forEach(function(key) {
45 console.log(key.white.bold)
46 console.log(' ', config.defaults.controllers[key] || 'not defined')
47 })
48 })
49
50 yargs.help('help')
51 })
52 .command('create', 'Creates a new controller and the required API resources to map it to a URL', function (yargs) {
53 var questions = [
54 {
55 'name': 'function',
56 'message': 'Function name',
57 },
58 {
59 'name': 'method',
60 'message': 'HTTP method',
61 'type': 'list',
62 'choices': ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'PATCH', 'OPTIONS'],
63 'default': 'GET',
64 },
65 {
66 'name': 'path',
67 'message': 'HTTP path',
68 'default': '/',
69 },
70 {
71 'name': 'handler',
72 'message': 'JavaScript method name',
73 'default': 'run',
74 },
75 {
76 'name': 'input',
77 'message': 'HTTP Content-Type input type',
78 'type': 'list',
79 'choices': ['json', 'form'],
80 'default': 'json',
81 },
82 {
83 'name': 'output',
84 'message': 'HTTP Content-Type output type',
85 'type': 'list',
86 'choices': ['json', 'html'],
87 'default': 'json',
88 },
89 ]
90
91 inquirer.prompt(questions, function(answers) {
92 txain(function(callback) {
93 var filename = path.join(process.cwd(), 'web/controllers/'+answers['function']+'.js')
94 if (answers.output === 'html') {
95 var code = multiline.stripIndent(function() {;/*
96 var aws = require('aws-sdk')
97
98 exports.HANDLER = function(event, context) {
99 context.succeed({ html: '<h1>hello world</h2>' })
100 }
101 */})
102 } else {
103 var code = multiline.stripIndent(function() {;/*
104 var aws = require('aws-sdk')
105
106 exports.HANDLER = function(event, context) {
107 context.succeed({ message: 'hello world' })
108 }
109 */})
110 }
111 code = code.replace('HANDLER', answers.handler)
112 fs.writeFile(filename, code, 'utf-8', callback)
113 })
114 .then(function(data, callback) {
115 var routes = utils.readRoutes()
116 routes.push(_.pick(answers, 'method', 'path', 'function', 'handler', 'input', 'output'))
117 utils.writeRoutes(routes)
118 callback()
119 })
120 .end(utils.end)
121 })
122 })
123 .command('delete', 'Deletes a controller', function (yargs) {
124 utils.notImplemented()
125 })
126 .command('set', 'Sets the value of a configuration key for an existing controller', function(yargs) {
127 utils.notImplemented()
128 })
129 .command('sync', 'Synchronizes the controller with AWS. It creates or updates the lambda function and all the API resources if needed', function(yargs) {
130 utils.requiresRegion()
131 utils.requiresAPI()
132
133 var choices = recentControllers().map(function(route) {
134 return route['function']
135 })
136 var question = {
137 'name': 'function',
138 'message': 'Function to be synched',
139 'type': 'list',
140 'choices': choices,
141 'validate': function(foo) {
142 return true
143 }
144 }
145 inquirer.prompt([question], function(answers) {
146 var functionName = answers['function']
147 module.exports.sync(functionName, utils.end)
148 })
149 })
150 .command('list', 'Lists the available controllers and their mappings', function(yargs) {
151 var argv = yargs
152 .help('help')
153 .argv
154 })
155 // TODO: open, test
156 .wrap(yargs.terminalWidth())
157 .help('help')
158 .argv
159}
160
161function recentControllers() {
162 var routes = utils.readRoutes()
163 routes.forEach(function(route) {
164 // TODO: if controller no longer exists
165 route.stat = fs.statSync(path.join('web/controllers', route['function']+'.js'))
166 })
167 return routes.sort(function(a, b) {
168 return a.stat.mtime < b.stat.mtime ? 1 : -1
169 }) // .slice(0, 20)
170}
171
172module.exports.sync = function(functionName, callback) {
173 var region = utils.requiresRegion()
174 var api = utils.requiresAPI()
175 var routes = utils.readRoutes()
176 var route = _.findWhere(routes, { 'function': functionName })
177
178 if (!route) {
179 console.log('No controller found with that function name')
180 process.exit(1)
181 }
182
183 var config = utils.readConfig()
184 config.defaults = config.defaults || {}
185 config.defaults.controllers = config.defaults.controllers || {}
186
187 var controller = _.extend({}, route, config.defaults.controllers)
188
189 var lambda = new AWS.Lambda()
190 var apigateway = new AWS.APIGateway()
191
192 var parentResource, resourceId
193
194 txain(function(callback) {
195 var zipfile = temp.path({ suffix: '.zip' })
196 var filename = path.join(process.cwd(), 'web/controllers/'+controller['function']+'.js')
197 var code = fs.readFileSync(filename, 'utf-8')
198 var writeStream = fs.createWriteStream(zipfile)
199 var archiver = require('archiver')('zip')
200 archiver.pipe(writeStream)
201 archiver.append(code, { name: 'index.js' })
202 archiver.finalize()
203 writeStream.on('close', function() {
204 fs.readFile(zipfile, callback)
205 })
206 })
207 .then(function(data, callback) {
208 if (!route.functionArn) {
209 txain(function(callback) {
210 var params = {
211 Code: {
212 ZipFile: data,
213 },
214 FunctionName: functionName,
215 Handler: 'index.'+route.handler,
216 Role: controller.role,
217 Runtime: 'nodejs',
218 Description: controller.description,
219 MemorySize: controller.memorySize,
220 Publish: false,
221 Timeout: controller.timeout,
222 }
223 console.log('Creating lambda function')
224 lambda.createFunction(params, callback)
225 })
226 .then(function(body, callback) {
227 route.functionArn = body.FunctionArn
228 utils.writeRoutes(routes)
229
230 console.log('Adding permission to lambda function')
231
232 var uuid = require('node-uuid')
233 var params = {
234 Action: 'lambda:InvokeFunction',
235 FunctionName: functionName,
236 Principal: 'apigateway.amazonaws.com',
237 StatementId: uuid.v4(),
238 }
239 lambda.addPermission(params, callback)
240 })
241 .end(callback)
242 } else {
243 txain(function(callback) {
244 var params = {
245 ZipFile: data,
246 FunctionName: functionName,
247 Publish: false,
248 }
249 console.log('Updating lambda function code')
250 lambda.updateFunctionCode(params, callback)
251 })
252 .then(function(callback) {
253 var params = {
254 FunctionName: functionName,
255 Handler: 'index.handler',
256 Role: controller.role,
257 Description: controller.description,
258 MemorySize: controller.memorySize,
259 Timeout: controller.timeout,
260 }
261 console.log('Updating function configuration')
262 lambda.updateFunctionConfiguration(params, callback)
263 })
264 .end(callback)
265 }
266 })
267 .then(function(callback) {
268 console.log('Creating API resources')
269 apigateway.getResources({ restApiId: api.id }, callback)
270 })
271 .then(function(data, callback) {
272 var resources = data.items
273 resources.forEach(function(resource) {
274 if (controller.path.indexOf(resource.path) === 0
275 && (!parentResource || resource.path.length > parentResource.path.length)) {
276 parentResource = resource
277 }
278 })
279 var pathParts = _.compact(controller.path.substring(parentResource.path.length).split('/'))
280 callback(null, pathParts)
281 })
282 .each(function(part, callback) {
283 txain(function(callback) {
284 var params = {
285 restApiId: api.id,
286 parentId: parentResource.id,
287 pathPart: part,
288 }
289 apigateway.createResource(params, callback)
290 })
291 .then(function(body, callback) {
292 parentResource = body
293 callback()
294 })
295 .end(callback)
296 })
297 .then(function(callback) {
298 resourceId = parentResource.id
299 var method = parentResource.resourceMethods && parentResource.resourceMethods[controller.method]
300
301 txain(function(callback) {
302 if (!method) return callback()
303 var params = {
304 restApiId: api.id,
305 resourceId: resourceId,
306 httpMethod: controller.method,
307 }
308 apigateway.deleteMethod(params, callback)
309 })
310 .then(function(callback) {
311 var params = {
312 authorizationType: 'none',
313 httpMethod: controller.method,
314 resourceId: resourceId,
315 restApiId: api.id,
316 apiKeyRequired: false,
317 }
318 apigateway.putMethod(params, callback)
319 })
320 .end(callback)
321 })
322 .then(function(body, callback) {
323 // console.log('body', body)
324 console.log('Creating integration')
325 var params = {
326 restApiId: api.id,
327 resourceId: resourceId,
328 httpMethod: controller.method,
329 integrationHttpMethod: 'POST', // always POST?
330 type: 'AWS',
331 uri: 'arn:aws:apigateway:'+AWS.config.region+':lambda:path/2015-03-31/functions/'+route.functionArn+'/invocations',
332 requestTemplates: {
333 'application/json': multiline.stripIndent(function() {;/*
334 {
335 "querystring" : {
336 #foreach($key in $input.params().querystring.keySet())
337 #if($foreach.index > 0), #end
338 "$util.escapeJavaScript($key)": "$util.escapeJavaScript($input.params().querystring.get($key))"
339 #end
340 },
341 "header" : {
342 #foreach($key in $input.params().header.keySet())
343 #if($foreach.index > 0), #end
344 "$util.escapeJavaScript($key)": "$util.escapeJavaScript($input.params().header.get($key))"
345 #end
346 },
347 "path" : {
348 #foreach($key in $input.params().path.keySet())
349 #if($foreach.index > 0), #end
350 "$util.escapeJavaScript($key)": "$util.escapeJavaScript($input.params().path.get($key))"
351 #end
352 },
353 "body" : $input.json('$')
354 }
355 */})
356 },
357 }
358 apigateway.putIntegration(params, callback)
359 })
360 .then(function(callback) {
361 console.log('Creating integration response')
362 var params = {
363 httpMethod: controller.method,
364 resourceId: resourceId,
365 restApiId: api.id,
366 statusCode: '200',
367 responseParameters: {},
368 responseTemplates: {},
369 selectionPattern: '.*',
370 }
371 if (route.output === 'html') {
372 params.responseTemplates['text/html'] = '$input.path(\'$.html\')'
373 }
374 apigateway.putIntegrationResponse(params, callback)
375 })
376 .then(function(callback) {
377 console.log('Creating method response')
378 var params = {
379 httpMethod: controller.method,
380 resourceId: resourceId,
381 restApiId: api.id,
382 statusCode: '200',
383 responseModels: {},
384 responseParameters: {},
385 }
386 if (route.output === 'html') {
387 params.responseModels['text/html'] = 'Empty'
388 }
389 apigateway.putMethodResponse(params, callback)
390 })
391 .end(callback)
392}