diff --git a/lib/aws-sns-lifecycle.js b/lib/aws-sns-lifecycle.js index 5440c52..f4cb1ad 100644 --- a/lib/aws-sns-lifecycle.js +++ b/lib/aws-sns-lifecycle.js @@ -1,7 +1,7 @@ const Emitter = require('events'); const bent = require('bent'); const assert = require('assert'); -const PORT = process.env.AWS_SNS_PORT || 3001; +const PORT = process.env.AWS_SNS_PORT || 3010; const {LifeCycleEvents} = require('./constants'); const express = require('express'); const app = express(); @@ -21,6 +21,26 @@ class SnsNotifier extends Emitter { this.logger = logger; } + _doListen(logger, app, port, resolve) { + return app.listen(port, () => { + this.snsEndpoint = `http://${this.publicIp}:${port}`; + logger.info(`SNS lifecycle server listening on http://localhost:${port}`); + resolve(app); + }); + } + + _handleErrors(logger, app, resolve, reject, e) { + if (e.code === 'EADDRINUSE' && + process.env.AWS_SNS_PORT_MAX && + e.port < process.env.AWS_SNS_PORT_MAX) { + + logger.info(`SNS lifecycle server failed to bind port on ${e.port}, will try next port`); + const server = this._doListen(logger, app, ++e.port, resolve); + server.on('error', this._handleErrors.bind(null, logger, app, resolve, reject)); + return; + } + reject(e); + } async _handlePost(req, res) { try { @@ -45,6 +65,7 @@ class SnsNotifier extends Emitter { }, 'response from SNS SubscribeURL'); const data = await this.describeInstance(); this.lifecycleState = data.AutoScalingInstances[0].LifecycleState; + this.emit('SubscriptionConfirmation', {publicIp: this.publicIp}); break; case 'Notification': @@ -80,14 +101,12 @@ class SnsNotifier extends Emitter { async init() { try { - this.logger.info('SnsNotifier: retrieving instance data'); + this.logger.debug('SnsNotifier: retrieving instance data'); this.instanceId = await getString('http://169.254.169.254/latest/meta-data/instance-id'); this.publicIp = await getString('http://169.254.169.254/latest/meta-data/public-ipv4'); - this.snsEndpoint = `http://${this.publicIp}:${PORT}`; this.logger.info({ instanceId: this.instanceId, - publicIp: this.publicIp, - snsEndpoint: this.snsEndpoint + publicIp: this.publicIp }, 'retrieved AWS instance data'); // start listening @@ -99,7 +118,10 @@ class SnsNotifier extends Emitter { this.logger.error(err, 'burped error'); res.status(err.status || 500).json({msg: err.message}); }); - app.listen(PORT); + return new Promise((resolve, reject) => { + const server = this._doListen(this.logger, app, PORT, resolve); + server.on('error', this._handleErrors.bind(null, this.logger, app, resolve, reject)); + }); } catch (err) { this.logger.error({err}, 'Error retrieving AWS instance metadata'); diff --git a/lib/utils.js b/lib/utils.js index 3d5bf74..6d4af5b 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -71,6 +71,26 @@ const systemHealth = async(redisClient, ping, getCount) => { return getCount(); }; +const doListen = (logger, app, port, resolve) => { + return app.listen(port, () => { + logger.info(`Health check server listening on http://localhost:${port}`); + resolve(app); + }); +}; +const handleErrors = (logger, app, resolve, reject, e) => { + if (e.code === 'EADDRINUSE' && + process.env.HTTP_PORT_MAX && + e.port < process.env.HTTP_PORT_MAX) { + + logger.info(`Health check server failed to bind port on ${e.port}, will try next port`); + const server = doListen(logger, app, ++e.port, resolve); + server.on('error', handleErrors.bind(null, logger, app, resolve, reject)); + return; + } + reject(e); +}; + + const createHealthCheckApp = (port, logger) => { const express = require('express'); const app = express(); @@ -78,11 +98,9 @@ const createHealthCheckApp = (port, logger) => { app.use(express.urlencoded({ extended: true })); app.use(express.json()); - return new Promise((resolve) => { - app.listen(port, () => { - logger.info(`Health check server started at http://localhost:${port}`); - resolve(app); - }); + return new Promise((resolve, reject) => { + const server = doListen(logger, app, port, resolve); + server.on('error', handleErrors.bind(null, logger, app, resolve, reject)); }); };