From 6fa6278212c27790d872c0a6e62b7776368ce687 Mon Sep 17 00:00:00 2001 From: Michael Hart Date: Wed, 31 Aug 2016 15:52:47 -0400 Subject: [PATCH] Wait until all `finish` tasks have completed before exiting Lambda --- actions/build.js | 29 +++++++++++++++++++---------- notifications/slack.js | 31 +++++++++++++++++-------------- notifications/sns.js | 23 +++++++++++++---------- sources/github.js | 15 +++++++++------ utils/log.js | 7 +++++-- 5 files changed, 63 insertions(+), 42 deletions(-) diff --git a/actions/build.js b/actions/build.js index d01980a..3d05626 100644 --- a/actions/build.js +++ b/actions/build.js @@ -1,4 +1,5 @@ var path = require('path') +var EventEmitter = require('events') var spawn = require('child_process').spawn var async = require('async') var AWS = require('aws-sdk') @@ -103,28 +104,31 @@ function patchUncaughtHandlers(build, cb) { var done = utils.once(function(err) { process.removeListener('uncaughtException', done) origListeners.forEach(listener => process.on('uncaughtException', listener)) - buildDone(err, build, cb) + build.error = err + buildDone(build, cb) }) process.removeAllListeners('uncaughtException') process.on('uncaughtException', done) return done } -function buildDone(err, build, cb) { +function buildDone(build, cb) { // Don't update statuses if we're doing a docker build and we launched successfully - if (!err && build.config.docker) return cb() + if (!build.error && build.config.docker) return cb() - log.info(err ? `Build #${build.buildNum} failed: ${err.message}` : + log.info(build.error ? `Build #${build.buildNum} failed: ${build.error.message}` : `Build #${build.buildNum} successful!`) build.endedAt = new Date() - build.status = err ? 'failure' : 'success' - build.statusEmitter.emit('finish', err, build) + build.status = build.error ? 'failure' : 'success' + build.statusEmitter.emit('finish', build) - db.finishBuild(build, function(dbErr) { - log.logIfErr(dbErr) - cb(err) + var finishTasks = build.statusEmitter.finishTasks.concat(db.finishBuild) + + async.forEach(finishTasks, (task, cb) => task(build, cb), function(taskErr) { + log.logIfErr(taskErr) + cb(build.error) }) } @@ -318,7 +322,11 @@ function BuildInfo(buildData, context) { this.endedAt = null this.status = 'pending' - this.statusEmitter = new (require('events'))() + this.statusEmitter = new EventEmitter() + + // Any async functions to run on 'finish' should be added to this array, + // and be of the form: function(build, cb) + this.statusEmitter.finishTasks = [] this.project = buildData.project this.buildNum = buildData.buildNum || 0 @@ -365,5 +373,6 @@ function BuildInfo(buildData, context) { this.logUrl = '' this.lambdaLogUrl = '' this.buildDirUrl = '' + this.error = null } diff --git a/notifications/slack.js b/notifications/slack.js index 502b52f..babf031 100644 --- a/notifications/slack.js +++ b/notifications/slack.js @@ -27,33 +27,36 @@ function SlackClient(token, options, build) { this.statusQueue = async.queue(this.updateStatus.bind(this), 1) - build.statusEmitter.on('start', (buildInfo) => { + build.statusEmitter.on('start', (build) => { var status = { color: 'warning', - fallback: `Started: ${buildInfo.repo} #${buildInfo.buildNum}`, - title: `Build #${buildInfo.buildNum} started...`, + fallback: `Started: ${build.repo} #${build.buildNum}`, + title: `Build #${build.buildNum} started...`, } this.statusQueue.push(status, log.logIfErr) }) - build.statusEmitter.on('finish', (err, buildInfo) => { - var status = {}, elapsedTxt = utils.elapsedTxt(buildInfo.startedAt, buildInfo.endedAt) - if (err) { - var txt = err.message - if (err.logTail) { - txt = `${err.logTail}\n${txt}` + build.statusEmitter.finishTasks.push((build, cb) => { + var status = {}, elapsedTxt = utils.elapsedTxt(build.startedAt, build.endedAt) + if (build.error) { + var txt = build.error.message + if (build.error.logTail) { + txt = `${build.error.logTail}\n${txt}` } status.color = 'danger' - status.fallback = `Failed: ${buildInfo.repo} #${buildInfo.buildNum} (${elapsedTxt})` - status.title = `Build #${buildInfo.buildNum} failed (${elapsedTxt})` + status.fallback = `Failed: ${build.repo} #${build.buildNum} (${elapsedTxt})` + status.title = `Build #${build.buildNum} failed (${elapsedTxt})` status.text = '```' + txt.replace(/```/g, "'''") + '```' // TODO: not sure best way to escape ``` } else { status.color = 'good' - status.fallback = `Success: ${buildInfo.repo} #${buildInfo.buildNum} (${elapsedTxt})` - status.title = `Build #${buildInfo.buildNum} successful (${elapsedTxt})` + status.fallback = `Success: ${build.repo} #${build.buildNum} (${elapsedTxt})` + status.title = `Build #${build.buildNum} successful (${elapsedTxt})` } - this.statusQueue.push(status, log.logIfErr) + this.statusQueue.push(status, function(err) { + log.logIfErr(err) + cb() + }) }) } diff --git a/notifications/sns.js b/notifications/sns.js index 4e096ae..74e7978 100644 --- a/notifications/sns.js +++ b/notifications/sns.js @@ -55,20 +55,20 @@ function SnsClient(options, build) { this.commit = build.commit this.logUrl = build.logUrl - build.statusEmitter.on('finish', (err, buildInfo) => { - var subject = `LambCI Build #${buildInfo.buildNum} successful!` - var message = `LambCI Build #${buildInfo.buildNum} + build.statusEmitter.finishTasks.push((build, cb) => { + var subject = `LambCI Build #${build.buildNum} successful!` + var message = `LambCI Build #${build.buildNum} Repo: ${this.repo} ${this.prNum ? `Pull Request: ${this.prNum}` : `Branch: ${this.branch}`} Commit: ${this.commit} Log: ${this.logUrl} ` - if (err) { - message += `Error: ${err.message}` - if (err.logTail) { - message += `\n${err.logTail}` + if (build.error) { + message += `Error: ${build.error.message}` + if (build.error.logTail) { + message += `\n${build.error.logTail}` } - subject = `LambCI Build #${buildInfo.buildNum} failed` + subject = `LambCI Build #${build.buildNum} failed` } sns.publish({ TopicArn: this.topicArn, @@ -77,10 +77,13 @@ Log: ${this.logUrl} MessageAttributes: { status: { DataType: 'String', - StringValue: err ? 'failure' : 'success', + StringValue: build.error ? 'failure' : 'success', }, }, - }, log.logIfErr) + }, function(err) { + log.logIfErr(err) + cb() + }) }) } diff --git a/sources/github.js b/sources/github.js index 1990049..4f7fb67 100644 --- a/sources/github.js +++ b/sources/github.js @@ -22,20 +22,23 @@ function GithubClient(build) { if (!build.statusEmitter) return - build.statusEmitter.on('start', (buildInfo) => { + build.statusEmitter.on('start', (build) => { var status = { state: 'pending', - description: `Build #${buildInfo.buildNum} started...`, + description: `Build #${build.buildNum} started...`, } this.statusQueue.push(status, log.logIfErr) }) - build.statusEmitter.on('finish', (err, buildInfo) => { + build.statusEmitter.finishTasks.push((build, cb) => { var status = { - state: err ? 'failure' : 'success', - description: err ? err.message : `Build #${buildInfo.buildNum} successful!`, + state: build.error ? 'failure' : 'success', + description: build.error ? build.error.message : `Build #${build.buildNum} successful!`, } - this.statusQueue.push(status, log.logIfErr) + this.statusQueue.push(status, function(err) { + log.logIfErr(err) + cb() + }) }) } diff --git a/utils/log.js b/utils/log.js index 459829a..a7613c4 100644 --- a/utils/log.js +++ b/utils/log.js @@ -107,10 +107,13 @@ exports.initBuildLog = function(build) { }) } - build.statusEmitter.on('finish', () => { + build.statusEmitter.finishTasks.push((build, cb) => { finished = true clearTimeout(s3timeout) - uploadS3Log(build, bucket, buildKey, branchKey, branchStatusKey, makeS3Public, exports.logIfErr) + uploadS3Log(build, bucket, buildKey, branchKey, branchStatusKey, makeS3Public, function(err) { + exports.logIfErr(err) + cb() + }) }) if (build.eventType == 'push') {