UNPKG

29.4 kBJavaScriptView Raw
1'use strict'
2
3const path = require('path')
4const os = require('os')
5const aws = require('aws-sdk')
6const {exec, execSync, execFile} = require('child_process')
7const fs = require('fs-extra')
8const klaw = require('klaw')
9const packageJson = require(path.join(__dirname, '..', 'package.json'))
10const minimatch = require('minimatch')
11const archiver = require('archiver')
12const dotenv = require('dotenv')
13const proxy = require('proxy-agent')
14const ScheduleEvents = require(path.join(__dirname, 'schedule_events'))
15const S3Events = require(path.join(__dirname, 's3_events'))
16const S3Deploy = require(path.join(__dirname, 's3_deploy'))
17const CloudWatchLogs = require(path.join(__dirname, 'cloudwatch_logs'))
18
19const AWSXRay = require('aws-xray-sdk-core')
20const {createNamespace} = require('continuation-local-storage')
21
22const maxBufferSize = 50 * 1024 * 1024
23
24class Lambda {
25 constructor () {
26 this.version = packageJson.version
27 }
28
29 _createSampleFile (file, boilerplateName) {
30 const exampleFile = path.join(process.cwd(), file)
31 const boilerplateFile = path.join(
32 __dirname,
33 (boilerplateName || file) + '.example'
34 )
35
36 if (!fs.existsSync(exampleFile)) {
37 fs.writeFileSync(exampleFile, fs.readFileSync(boilerplateFile))
38 console.log(exampleFile + ' file successfully created')
39 }
40 }
41
42 setup (program) {
43 console.log('Running setup.')
44 this._createSampleFile('.env', '.env')
45 this._createSampleFile(program.eventFile, 'event.json')
46 this._createSampleFile('deploy.env', 'deploy.env')
47 this._createSampleFile(program.contextFile, 'context.json')
48 this._createSampleFile('event_sources.json', 'event_sources.json')
49 console.log(`Setup done.
50Edit the .env, deploy.env, ${program.contextFile}, \
51event_sources.json and ${program.eventFile} files as needed.`)
52 }
53
54 run (program) {
55 if (!['nodejs4.3', 'nodejs6.10', 'nodejs8.10'].includes(program.runtime)) {
56 console.error(`Runtime [${program.runtime}] is not supported.`)
57 process.exit(254)
58 }
59
60 this._createSampleFile(program.eventFile, 'event.json')
61 const splitHandler = program.handler.split('.')
62 const filename = splitHandler[0] + '.js'
63 const handlername = splitHandler[1]
64
65 // Set custom environment variables if program.configFile is defined
66 if (program.configFile) {
67 this._setRunTimeEnvironmentVars(program)
68 }
69
70 const handler = require(path.join(process.cwd(), filename))[handlername]
71 const event = require(path.join(process.cwd(), program.eventFile))
72 const context = require(path.join(process.cwd(), program.contextFile))
73 const enableRunMultipleEvents = (() => {
74 if (typeof program.enableRunMultipleEvents === 'boolean') {
75 return program.enableRunMultipleEvents
76 }
77 return program.enableRunMultipleEvents === 'true'
78 })()
79
80 if (Array.isArray(event) && enableRunMultipleEvents === true) {
81 return this._runMultipleHandlers(event)
82 }
83 context.local = true
84 this._runHandler(handler, event, program, context)
85 }
86
87 _runHandler (handler, event, program, context) {
88 const startTime = new Date()
89 const timeout = Math.min(program.timeout, 300) * 1000 // convert the timeout into milliseconds
90
91 const callback = (err, result) => {
92 if (err) {
93 process.exitCode = 255
94 console.log('Error: ' + err)
95 } else {
96 process.exitCode = 0
97 console.log('Success:')
98 if (result) {
99 console.log(JSON.stringify(result))
100 }
101 }
102 if (context.callbackWaitsForEmptyEventLoop === false) {
103 process.exit()
104 }
105 }
106
107 context.getRemainingTimeInMillis = () => {
108 const currentTime = new Date()
109 return timeout - (currentTime - startTime)
110 }
111
112 // The following three functions are deprecated in AWS Lambda.
113 // Since it is sometimes used by other SDK,
114 // it is a simple one that does not result in `not function` error
115 context.succeed = (result) => console.log(JSON.stringify(result))
116 context.fail = (error) => console.log(JSON.stringify(error))
117 context.done = (error, results) => {
118 console.log(JSON.stringify(error))
119 console.log(JSON.stringify(results))
120 }
121
122 const nameSpace = createNamespace('AWSXRay')
123 nameSpace.run(() => {
124 nameSpace.set('segment', new AWSXRay.Segment('annotations'))
125 const result = handler(event, context, callback)
126 if (result != null) {
127 Promise.resolve(result).then(
128 resolved => {
129 console.log('Result:')
130 console.log(JSON.stringify(resolved))
131 },
132 rejected => {
133 console.log('Error:')
134 console.log(rejected)
135 }
136 )
137 }
138 })
139 }
140
141 _runMultipleHandlers (events) {
142 console.log(`!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
143Usually you will receive a single Object from AWS Lambda.
144We added support for event.json to contain an array,
145so you can easily test run multiple events.
146!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
147`)
148
149 const _argv = process.argv
150 const eventFileOptionIndex = (() => {
151 const index = _argv.indexOf('-j')
152 if (index >= 0) return index
153 return _argv.indexOf('--eventFile')
154 })()
155 _argv[0] = 'node' // For Windows support
156
157 // In order to reproduce the logic of callbackWaitsForEmptyEventLoop,
158 // we are going to execute `node-lambda run`.
159 events.forEach((event, i) => {
160 const tmpEventFile = `.${i}_tmp_event.json`
161 const command = () => {
162 if (eventFileOptionIndex === -1) {
163 return _argv.concat(['-j', tmpEventFile]).join(' ')
164 }
165 _argv[eventFileOptionIndex + 1] = tmpEventFile
166 return _argv.join(' ')
167 }
168
169 fs.writeFileSync(tmpEventFile, JSON.stringify(event))
170 const stdout = execSync(command(), {
171 maxBuffer: maxBufferSize,
172 env: process.env
173 })
174 console.log('>>> Event:', event, '<<<')
175 console.log(stdout.toString())
176 fs.unlinkSync(tmpEventFile)
177 })
178 }
179
180 _isUseS3 (program) {
181 if (typeof program.deployUseS3 === 'boolean') {
182 return program.deployUseS3
183 }
184 return program.deployUseS3 === 'true'
185 }
186
187 _params (program, buffer) {
188 const params = {
189 FunctionName: program.functionName +
190 (program.environment ? '-' + program.environment : '') +
191 (program.lambdaVersion ? '-' + program.lambdaVersion : ''),
192 Code: {},
193 Handler: program.handler,
194 Role: program.role,
195 Runtime: program.runtime,
196 Description: program.description,
197 MemorySize: program.memorySize,
198 Timeout: program.timeout,
199 Publish: (() => {
200 if (typeof program.publish === 'boolean') {
201 return program.publish
202 }
203 return program.publish === 'true'
204 })(),
205 VpcConfig: {
206 SubnetIds: [],
207 SecurityGroupIds: []
208 },
209 Environment: {
210 Variables: null
211 },
212 KMSKeyArn: program.kmsKeyArn,
213 DeadLetterConfig: {
214 TargetArn: null
215 },
216 TracingConfig: {
217 Mode: null
218 }
219 }
220
221 if (this._isUseS3(program)) {
222 params.Code = {
223 S3Bucket: null,
224 S3Key: null
225 }
226 } else {
227 params.Code = { ZipFile: buffer }
228 }
229
230 // Escape characters that is not allowed by AWS Lambda
231 params.FunctionName = params.FunctionName.replace(/[^a-zA-Z0-9-_]/g, '_')
232
233 if (program.vpcSubnets && program.vpcSecurityGroups) {
234 params.VpcConfig = {
235 'SubnetIds': program.vpcSubnets.split(','),
236 'SecurityGroupIds': program.vpcSecurityGroups.split(',')
237 }
238 }
239 if (program.configFile) {
240 const configValues = fs.readFileSync(program.configFile)
241 const config = dotenv.parse(configValues)
242 // If `configFile` is an empty file, `config` value will be `{}`
243 params.Environment = {
244 Variables: config
245 }
246 }
247 if (program.deadLetterConfigTargetArn !== undefined) {
248 params.DeadLetterConfig = {
249 TargetArn: program.deadLetterConfigTargetArn
250 }
251 }
252 if (program.tracingConfig) {
253 params.TracingConfig.Mode = program.tracingConfig
254 }
255
256 return params
257 }
258
259 _eventSourceList (program) {
260 if (!program.eventSourceFile) {
261 return {
262 EventSourceMappings: null,
263 ScheduleEvents: null,
264 S3Events: null
265 }
266 }
267 const list = fs.readJsonSync(program.eventSourceFile)
268
269 if (Array.isArray(list)) {
270 // backward-compatible
271 return {
272 EventSourceMappings: list,
273 ScheduleEvents: [],
274 S3Events: []
275 }
276 }
277 if (!list.EventSourceMappings) {
278 list.EventSourceMappings = []
279 }
280 if (!list.ScheduleEvents) {
281 list.ScheduleEvents = []
282 }
283 if (!list.S3Events) {
284 list.S3Events = []
285 }
286 return list
287 }
288
289 _fileCopy (program, src, dest, excludeNodeModules) {
290 const excludes = (() => {
291 return [
292 '.git*',
293 '*.swp',
294 '.editorconfig',
295 '.lambda',
296 'deploy.env',
297 '*.log',
298 path.join('build', path.sep)
299 ]
300 .concat(program.excludeGlobs ? program.excludeGlobs.split(' ') : [])
301 .concat(excludeNodeModules ? [path.join('node_modules')] : [])
302 })()
303
304 // Formatting for `filter` of `fs.copy`
305 const dirBlobs = []
306 const pattern = '{' + excludes.map((str) => {
307 if (str.charAt(str.length - 1) === path.sep) {
308 str = str.substr(0, str.length - 1)
309 dirBlobs.push(str)
310 }
311 return str
312 }).join(',') + '}'
313 const dirPatternRegExp = new RegExp(`(${dirBlobs.join('|')})$`)
314
315 return new Promise((resolve, reject) => {
316 fs.mkdirs(dest, (err) => {
317 if (err) return reject(err)
318 const options = {
319 dereference: true, // same meaning as `-L` of `rsync` command
320 filter: (src, dest) => {
321 if (!program.prebuiltDirectory && src === 'package.json') {
322 // include package.json unless prebuiltDirectory is set
323 return true
324 }
325
326 if (!minimatch(src, pattern, { matchBase: true })) {
327 return true
328 }
329 // Directory check. Even if `src` is a directory it will not end with '/'.
330 if (!dirPatternRegExp.test(src)) {
331 return false
332 }
333 return !fs.statSync(src).isDirectory()
334 }
335 }
336 fs.copy(src, dest, options, (err) => {
337 if (err) return reject(err)
338 resolve()
339 })
340 })
341 })
342 }
343
344 _npmInstall (program, codeDirectory) {
345 const dockerBaseOptions = [
346 'run', '--rm', '-v', `${codeDirectory}:/var/task`, '-w', '/var/task',
347 program.dockerImage,
348 'npm', '-s', 'install', '--production'
349 ]
350 const npmInstallBaseOptions = [
351 '-s',
352 'install',
353 '--production',
354 '--prefix', codeDirectory
355 ]
356
357 const params = (() => {
358 // reference: https://nodejs.org/api/child_process.html#child_process_spawning_bat_and_cmd_files_on_windows
359
360 // with docker
361 if (program.dockerImage) {
362 if (process.platform === 'win32') {
363 return {
364 command: 'cmd.exe',
365 options: ['/c', 'docker'].concat(dockerBaseOptions)
366 }
367 }
368 return {
369 command: 'docker',
370 options: dockerBaseOptions
371 }
372 }
373
374 // simple npm install
375 if (process.platform === 'win32') {
376 return {
377 command: 'cmd.exe',
378 options: ['/c', 'npm']
379 .concat(npmInstallBaseOptions)
380 .concat(['--cwd', codeDirectory])
381 }
382 }
383 return {
384 command: 'npm',
385 options: npmInstallBaseOptions
386 }
387 })()
388
389 return new Promise((resolve, reject) => {
390 execFile(params.command, params.options, {
391 maxBuffer: maxBufferSize,
392 env: process.env
393 }, (err) => {
394 if (err) return reject(err)
395 resolve()
396 })
397 })
398 }
399
400 _postInstallScript (program, codeDirectory) {
401 const scriptFilename = 'post_install.sh'
402 const filePath = path.join(codeDirectory, scriptFilename)
403 if (!fs.existsSync(filePath)) return Promise.resolve()
404
405 const cmd = path.join(codeDirectory, scriptFilename) + ' ' + program.environment
406 console.log('=> Running post install script ' + scriptFilename)
407
408 return new Promise((resolve, reject) => {
409 exec(cmd, {
410 env: process.env,
411 cwd: codeDirectory,
412 maxBuffer: maxBufferSize
413 }, (error, stdout, stderr) => {
414 if (error) {
415 return reject(new Error(`${error} stdout: ${stdout} stderr: ${stderr}`))
416 }
417 console.log('\t\t' + stdout)
418 resolve()
419 })
420 })
421 }
422
423 _zip (program, codeDirectory) {
424 console.log('=> Zipping repo. This might take up to 30 seconds')
425
426 const tmpZipFile = path.join(os.tmpdir(), +(new Date()) + '.zip')
427 const output = fs.createWriteStream(tmpZipFile)
428 const archive = archiver('zip', {
429 zlib: { level: 9 } // Sets the compression level.
430 })
431 return new Promise((resolve) => {
432 output.on('close', () => {
433 const contents = fs.readFileSync(tmpZipFile)
434 fs.unlinkSync(tmpZipFile)
435 resolve(contents)
436 })
437 archive.pipe(output)
438 klaw(codeDirectory)
439 .on('data', (file) => {
440 if (file.stats.isDirectory()) return
441
442 const filePath = file.path.replace(path.join(codeDirectory, path.sep), '')
443 if (file.stats.isSymbolicLink()) {
444 return archive.symlink(filePath, fs.readlinkSync(file.path))
445 }
446
447 archive.append(
448 fs.createReadStream(file.path),
449 {
450 name: filePath,
451 stats: file.stats
452 }
453 )
454 })
455 .on('end', () => {
456 archive.finalize()
457 })
458 })
459 }
460
461 _codeDirectory () {
462 return path.join(os.tmpdir(), `${path.basename(path.resolve('.'))}-lambda`)
463 }
464
465 _cleanDirectory (codeDirectory) {
466 return new Promise((resolve, reject) => {
467 fs.remove(codeDirectory, (err) => {
468 if (err) return reject(err)
469 resolve()
470 })
471 }).then(() => {
472 return new Promise((resolve, reject) => {
473 fs.mkdirs(codeDirectory, (err) => {
474 if (err) return reject(err)
475 resolve()
476 })
477 })
478 })
479 }
480
481 _setRunTimeEnvironmentVars (program) {
482 const configValues = fs.readFileSync(program.configFile)
483 const config = dotenv.parse(configValues)
484
485 for (let k in config) {
486 if (!config.hasOwnProperty(k)) {
487 continue
488 }
489
490 process.env[k] = config[k]
491 }
492 }
493
494 _uploadExisting (lambda, params) {
495 const _params = Object.assign({
496 'FunctionName': params.FunctionName,
497 'Publish': params.Publish
498 }, params.Code)
499 return new Promise((resolve, reject) => {
500 const request = lambda.updateFunctionCode(_params, (err) => {
501 if (err) return reject(err)
502
503 lambda.updateFunctionConfiguration({
504 'FunctionName': params.FunctionName,
505 'Description': params.Description,
506 'Handler': params.Handler,
507 'MemorySize': params.MemorySize,
508 'Role': params.Role,
509 'Timeout': params.Timeout,
510 'Runtime': params.Runtime,
511 'VpcConfig': params.VpcConfig,
512 'Environment': params.Environment,
513 'KMSKeyArn': params.KMSKeyArn,
514 'DeadLetterConfig': params.DeadLetterConfig,
515 'TracingConfig': params.TracingConfig
516 }, (err, data) => {
517 if (err) return reject(err)
518 resolve(data)
519 })
520 })
521
522 request.on('retry', (response) => {
523 console.log(response.error.message)
524 console.log('=> Retrying')
525 })
526 })
527 }
528
529 _uploadNew (lambda, params) {
530 return new Promise((resolve, reject) => {
531 const request = lambda.createFunction(params, (err, data) => {
532 if (err) return reject(err)
533 resolve(data)
534 })
535 request.on('retry', (response) => {
536 console.log(response.error.message)
537 console.log('=> Retrying')
538 })
539 })
540 }
541
542 _readArchive (program) {
543 if (!fs.existsSync(program.deployZipfile)) {
544 const err = new Error('No such Zipfile [' + program.deployZipfile + ']')
545 return Promise.reject(err)
546 }
547 return new Promise((resolve, reject) => {
548 fs.readFile(program.deployZipfile, (err, data) => {
549 if (err) return reject(err)
550 resolve(data)
551 })
552 })
553 }
554
555 _archive (program) {
556 if (program.deployZipfile && fs.existsSync(program.deployZipfile)) {
557 return this._readArchive(program)
558 }
559 return program.prebuiltDirectory
560 ? this._archivePrebuilt(program)
561 : this._buildAndArchive(program)
562 }
563
564 _archivePrebuilt (program) {
565 const codeDirectory = this._codeDirectory()
566
567 return this._fileCopy(program, program.prebuiltDirectory, codeDirectory, false).then(() => {
568 console.log('=> Zipping deployment package')
569 return this._zip(program, codeDirectory)
570 })
571 }
572
573 _buildAndArchive (program) {
574 if (!fs.existsSync('.env')) {
575 console.warn('[Warning] `.env` file does not exist.')
576 console.info('Execute `node-lambda setup` as necessary and set it up.')
577 }
578
579 // Warn if not building on 64-bit linux
580 const arch = process.platform + '.' + process.arch
581 if (arch !== 'linux.x64' && !program.dockerImage) {
582 console.warn(`Warning!!! You are building on a platform that is not 64-bit Linux (${arch}).
583If any of your Node dependencies include C-extensions, \
584they may not work as expected in the Lambda environment.
585
586`)
587 }
588
589 const codeDirectory = this._codeDirectory()
590 const lambdaSrcDirectory = program.sourceDirectory ? program.sourceDirectory.replace(/\/$/, '') : '.'
591
592 return Promise.resolve().then(() => {
593 return this._cleanDirectory(codeDirectory)
594 }).then(() => {
595 console.log('=> Moving files to temporary directory')
596 return this._fileCopy(program, lambdaSrcDirectory, codeDirectory, true)
597 }).then(() => {
598 console.log('=> Running npm install --production')
599 return this._npmInstall(program, codeDirectory)
600 }).then(() => {
601 return this._postInstallScript(program, codeDirectory)
602 }).then(() => {
603 console.log('=> Zipping deployment package')
604 return this._zip(program, codeDirectory)
605 })
606 }
607
608 _listEventSourceMappings (lambda, params) {
609 return new Promise((resolve, reject) => {
610 lambda.listEventSourceMappings(params, (err, data) => {
611 if (err) return reject(err)
612 if (data && data.EventSourceMappings) {
613 return resolve(data.EventSourceMappings)
614 }
615 return resolve([])
616 })
617 })
618 }
619
620 _updateEventSources (lambda, functionName, existingEventSourceList, eventSourceList) {
621 if (eventSourceList == null) {
622 return Promise.resolve([])
623 }
624 const updateEventSourceList = []
625 // Checking new and update event sources
626 for (let i in eventSourceList) {
627 let isExisting = false
628 for (let j in existingEventSourceList) {
629 if (eventSourceList[i]['EventSourceArn'] === existingEventSourceList[j]['EventSourceArn']) {
630 isExisting = true
631 updateEventSourceList.push({
632 'type': 'update',
633 'FunctionName': functionName,
634 'Enabled': eventSourceList[i]['Enabled'],
635 'BatchSize': eventSourceList[i]['BatchSize'],
636 'UUID': existingEventSourceList[j]['UUID']
637 })
638 break
639 }
640 }
641
642 // If it is new source
643 if (!isExisting) {
644 updateEventSourceList.push({
645 'type': 'create',
646 'FunctionName': functionName,
647 'EventSourceArn': eventSourceList[i]['EventSourceArn'],
648 'Enabled': eventSourceList[i]['Enabled'] ? eventSourceList[i]['Enabled'] : false,
649 'BatchSize': eventSourceList[i]['BatchSize'] ? eventSourceList[i]['BatchSize'] : 100,
650 'StartingPosition': eventSourceList[i]['StartingPosition'] ? eventSourceList[i]['StartingPosition'] : 'LATEST'
651 })
652 }
653 }
654
655 // Checking delete event sources
656 for (let i in existingEventSourceList) {
657 let isExisting = false
658 for (let j in eventSourceList) {
659 if (eventSourceList[j]['EventSourceArn'] === existingEventSourceList[i]['EventSourceArn']) {
660 isExisting = true
661 break
662 }
663 }
664
665 // If delete the source
666 if (!isExisting) {
667 updateEventSourceList.push({
668 'type': 'delete',
669 'UUID': existingEventSourceList[i]['UUID']
670 })
671 }
672 }
673
674 return Promise.all(updateEventSourceList.map((updateEventSource) => {
675 switch (updateEventSource['type']) {
676 case 'create':
677 delete updateEventSource['type']
678 return new Promise((resolve, reject) => {
679 lambda.createEventSourceMapping(updateEventSource, (err, data) => {
680 if (err) return reject(err)
681 resolve(data)
682 })
683 })
684 case 'update':
685 delete updateEventSource['type']
686 return new Promise((resolve, reject) => {
687 lambda.updateEventSourceMapping(updateEventSource, (err, data) => {
688 if (err) return reject(err)
689 resolve(data)
690 })
691 })
692 case 'delete':
693 delete updateEventSource['type']
694 return new Promise((resolve, reject) => {
695 lambda.deleteEventSourceMapping(updateEventSource, (err, data) => {
696 if (err) return reject(err)
697 resolve(data)
698 })
699 })
700 }
701 return Promise.resolve()
702 }))
703 }
704
705 _updateScheduleEvents (scheduleEvents, functionArn, scheduleList) {
706 if (scheduleList == null) {
707 return Promise.resolve([])
708 }
709
710 const paramsList = scheduleList.map((schedule) =>
711 Object.assign(schedule, { FunctionArn: functionArn }))
712
713 // series
714 return paramsList.map((params) => {
715 return scheduleEvents.add(params)
716 }).reduce((a, b) => {
717 return a.then(b)
718 }, Promise.resolve()).then(() => {
719 // Since `scheduleEvents.add(params)` returns only `{}` if it succeeds
720 // it is not very meaningful.
721 // Therefore, return the params used for execution
722 return paramsList
723 })
724 }
725
726 _updateS3Events (s3Events, functionArn, s3EventsList) {
727 if (s3EventsList == null) return Promise.resolve([])
728
729 const paramsList = s3EventsList.map(s3event =>
730 Object.assign(s3event, { FunctionArn: functionArn }))
731
732 return s3Events.add(paramsList).then(() => {
733 // Since it is similar to _updateScheduleEvents, it returns meaningful values
734 return paramsList
735 })
736 }
737
738 _setLogsRetentionPolicy (cloudWatchLogs, program, functionName) {
739 const days = parseInt(program.retentionInDays)
740 if (!Number.isInteger(days)) return Promise.resolve({})
741 return cloudWatchLogs.setLogsRetentionPolicy({
742 FunctionName: functionName,
743 retentionInDays: days
744 }).then(() => {
745 // Since it is similar to _updateScheduleEvents, it returns meaningful values
746 return { retentionInDays: days }
747 })
748 }
749
750 package (program) {
751 if (!program.packageDirectory) {
752 throw new Error('packageDirectory not specified!')
753 }
754 try {
755 const isDir = fs.lstatSync(program.packageDirectory).isDirectory()
756
757 if (!isDir) {
758 throw new Error(program.packageDirectory + ' is not a directory!')
759 }
760 } catch (err) {
761 if (err.code !== 'ENOENT') {
762 throw err
763 }
764 console.log('=> Creating package directory')
765 fs.mkdirsSync(program.packageDirectory)
766 }
767
768 return this._archive(program).then((buffer) => {
769 const basename = program.functionName + (program.environment ? '-' + program.environment : '')
770 const zipfile = path.join(program.packageDirectory, basename + '.zip')
771 console.log('=> Writing packaged zip')
772 fs.writeFile(zipfile, buffer, (err) => {
773 if (err) {
774 throw err
775 }
776 console.log('Packaged zip created: ' + zipfile)
777 })
778 }).catch((err) => {
779 throw err
780 })
781 }
782
783 _awsConfigUpdate (program, region) {
784 const awsSecurity = { region: region }
785
786 if (program.profile) {
787 aws.config.credentials = new aws.SharedIniFileCredentials({
788 profile: program.profile
789 })
790 } else {
791 awsSecurity.accessKeyId = program.accessKey
792 awsSecurity.secretAccessKey = program.secretKey
793 }
794
795 if (program.sessionToken) {
796 awsSecurity.sessionToken = program.sessionToken
797 }
798
799 if (program.deployTimeout) {
800 aws.config.httpOptions.timeout = parseInt(program.deployTimeout)
801 }
802
803 if (program.proxy) {
804 aws.config.httpOptions.agent = proxy(program.proxy)
805 }
806
807 if (program.endpoint) {
808 aws.config.endpoint = program.endpoint
809 }
810
811 aws.config.update(awsSecurity)
812 }
813
814 _isFunctionDoesNotExist (err) {
815 return err.code === 'ResourceNotFoundException' &&
816 !!err.message.match(/^Function not found:/)
817 }
818
819 _deployToRegion (program, params, region, buffer) {
820 this._awsConfigUpdate(program, region)
821
822 console.log('=> Reading event source file to memory')
823 const eventSourceList = this._eventSourceList(program)
824
825 return Promise.resolve().then(() => {
826 if (this._isUseS3(program)) {
827 const s3Deploy = new S3Deploy(aws, region)
828 return s3Deploy.putPackage(params, region, buffer)
829 }
830 return null
831 }).then((code) => {
832 if (code != null) params.Code = code
833 }).then(() => {
834 if (!this._isUseS3(program)) {
835 console.log(`=> Uploading zip file to AWS Lambda ${region} with parameters:`)
836 } else {
837 console.log(`=> Uploading AWS Lambda ${region} with parameters:`)
838 }
839 console.log(params)
840
841 const lambda = new aws.Lambda({
842 region: region,
843 apiVersion: '2015-03-31'
844 })
845 const scheduleEvents = new ScheduleEvents(aws, region)
846 const s3Events = new S3Events(aws, region)
847 const cloudWatchLogs = new CloudWatchLogs(aws, region)
848
849 // Checking function
850 return lambda.getFunction({
851 'FunctionName': params.FunctionName
852 }).promise().then(() => {
853 // Function exists
854 return this._listEventSourceMappings(lambda, {
855 'FunctionName': params.FunctionName
856 }).then((existingEventSourceList) => {
857 return Promise.all([
858 this._uploadExisting(lambda, params).then((results) => {
859 console.log('=> Done uploading. Results follow: ')
860 console.log(results)
861 return results
862 }).then(results => {
863 return Promise.all([
864 this._updateScheduleEvents(
865 scheduleEvents,
866 results.FunctionArn,
867 eventSourceList.ScheduleEvents
868 ),
869 this._updateS3Events(
870 s3Events,
871 results.FunctionArn,
872 eventSourceList.S3Events
873 )
874 ])
875 }),
876 this._updateEventSources(
877 lambda,
878 params.FunctionName,
879 existingEventSourceList,
880 eventSourceList.EventSourceMappings
881 ),
882 this._setLogsRetentionPolicy(
883 cloudWatchLogs,
884 program,
885 params.FunctionName
886 )
887 ])
888 })
889 }).catch((err) => {
890 if (!this._isFunctionDoesNotExist(err)) {
891 throw err
892 }
893 // Function does not exist
894 return this._uploadNew(lambda, params).then((results) => {
895 console.log('=> Done uploading. Results follow: ')
896 console.log(results)
897
898 return Promise.all([
899 this._updateEventSources(
900 lambda,
901 params.FunctionName,
902 [],
903 eventSourceList.EventSourceMappings
904 ),
905 this._updateScheduleEvents(
906 scheduleEvents,
907 results.FunctionArn,
908 eventSourceList.ScheduleEvents
909 ),
910 this._updateS3Events(
911 s3Events,
912 results.FunctionArn,
913 eventSourceList.S3Events
914 ),
915 this._setLogsRetentionPolicy(
916 cloudWatchLogs,
917 program,
918 params.FunctionName
919 )
920 ])
921 })
922 })
923 })
924 }
925
926 _printDeployResults (results, isFirst) {
927 if (!Array.isArray(results)) {
928 if (results == null) return
929 console.log(results)
930 return
931 }
932 if (results.length === 0) return
933
934 if (isFirst === true) console.log('=> All tasks done. Results follow:')
935 results.forEach(result => {
936 this._printDeployResults(result)
937 })
938 }
939
940 deploy (program) {
941 const regions = program.region.split(',')
942 return this._archive(program).then((buffer) => {
943 console.log('=> Reading zip file to memory')
944 return buffer
945 }).then((buffer) => {
946 const params = this._params(program, buffer)
947
948 return Promise.all(regions.map((region) => {
949 return this._deployToRegion(
950 program,
951 params,
952 region,
953 this._isUseS3(program) ? buffer : null
954 )
955 })).then(results => {
956 this._printDeployResults(results, true)
957 })
958 }).catch((err) => {
959 process.exitCode = 1
960 console.log(err)
961 })
962 }
963}
964
965module.exports = new Lambda()