├── .gitignore ├── .npmignore ├── README.md ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Any hidden files 2 | **/.* 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-aws-beanstalk 2 | 3 | A module that helps you automate AWS beanstalk application deployment. 4 | All application and environment configuration is managed in the codebase. So you can version control everything and automate the deployment instead of click click click in AWS console. 5 | 6 | Inspired by [https://github.com/ThoughtWorksStudios/node-aws-lambda](https://github.com/ThoughtWorksStudios/node-aws-lambda) 7 | 8 | # Gulp example: 9 | 10 | gulpfile.js 11 | ```node 12 | var gulp = require('gulp'); 13 | var zip = require('gulp-zip'); 14 | var del = require('del'); 15 | var install = require('gulp-install'); 16 | var runSequence = require('run-sequence'); 17 | var awsBeanstalk = require("node-aws-beanstalk"); 18 | 19 | gulp.task('clean', function() { 20 | return del(['./dist', './dist.zip']); 21 | }); 22 | 23 | gulp.task('js', function() { 24 | return gulp.src('index.js') 25 | .pipe(gulp.dest('dist/')); 26 | }); 27 | 28 | gulp.task('node-mods', function() { 29 | return gulp.src('./package.json') 30 | .pipe(gulp.dest('dist/')) 31 | .pipe(install({production: true})); 32 | }); 33 | 34 | gulp.task('zip', function() { 35 | return gulp.src(['dist/**/*', '!dist/package.json']) 36 | .pipe(zip('dist.zip')) 37 | .pipe(gulp.dest('./')); 38 | }); 39 | 40 | gulp.task('upload', function(callback) { 41 | awsBeanstalk.deploy('./dist.zip', require("./beanstalk-config.js"), callback); 42 | }); 43 | 44 | // update task can be used to update the configured environment 45 | gulp.task('update', function(callback) { 46 | awsBeanstalk.update(require("./beanstalk-config.js"), callback); 47 | }); 48 | 49 | gulp.task('deploy', function(callback) { 50 | return runSequence( 51 | ['clean'], 52 | ['js', 'node-mods'], 53 | ['zip'], 54 | ['upload'], 55 | callback 56 | ); 57 | }); 58 | ``` 59 | beanstalk-config.js 60 | ```node 61 | module.exports = { 62 | accessKeyId: , // optional 63 | secretAccessKey: , // optional 64 | profile: , // optional for loading AWS credientail from custom profile 65 | region: '', 66 | appName: 'MyApp', 67 | 68 | // either the 'solutionStack' OR 'template' key MUST be provided, but not both 69 | solutionStack: '64bit Amazon Linux 2015.03 v2.0.6 running Node.js', 70 | template: 'myEnvironmentTemplate', 71 | 72 | version: '0.1.0', // optional, else will pull version from package.json 73 | S3Bucket: 'mys3bucket', // DEPRECATED - use bucketConfig.Bucket 74 | tier: 'Worker', // optional, else will use 'WebServer' 75 | environmentSettings: [ 76 | { 77 | Namespace: 'aws:autoscaling:launchconfiguration', 78 | OptionName: 'IamInstanceProfile', 79 | Value: 'ElasticBeanstalkProfile' 80 | }, 81 | // ... 82 | ], 83 | environmentTags: [ // optional 84 | { 85 | key: 'foo', 86 | value: 'bar' 87 | }, 88 | // ... 89 | ], 90 | bucketConfig: { // optional - passed into S3.createBucket() 91 | Bucket: 'mys3bucket', // optional, else will attempt to use appName 92 | // ... 93 | CreateBucketConfiguration: { 94 | LocationConstraint: 'eu-west-1', // optional, note this region should match the application's target region (see http://blog.mikebabineau.com/2013/08/21/multi-region-gotcha-on-elastic-beanstalk/) 95 | }, 96 | }, 97 | bucketTags: [ // optional 98 | { 99 | key: 'foo', 100 | value: 'bar' 101 | }, 102 | // ... 103 | ] 104 | } 105 | ``` 106 | Additional environment settings can be found [here](http://docs.aws.amazon.com/elasticbeanstalk/latest/dg/command-options.html#command-options-general). 107 | 108 | # Proxy setup 109 | Deployment via https proxy is supported by setting environment variable "HTTPS_PROXY". For example: 110 | 111 | ```terminal 112 | > HTTPS_PROXY="https://myproxy:8080" gulp deploy 113 | ``` 114 | 115 | # License 116 | 117 | (The MIT License) 118 | 119 | Copyright (c) 2015 David Hutchings 120 | 121 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 122 | 123 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 124 | 125 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 126 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var AWS = require('aws-sdk'); 3 | var util = require( 'util' ); 4 | var extend = require('util')._extend; 5 | var chalk = require( 'chalk' ); 6 | var defaultVersion = '1.0.0'; 7 | 8 | function pick(src, keys) { 9 | var ret = {}; 10 | keys.forEach(function(key) { 11 | ret[key] = src[key]; 12 | if (ret[key] === undefined) { 13 | delete ret[key]; 14 | } 15 | }); 16 | return ret; 17 | } 18 | 19 | function setup(config, codePackage) { 20 | var packageName, params; 21 | 22 | config.version = config.version !== undefined ? config.version : defaultVersion; 23 | params = { 24 | ApplicationName: config.appName, 25 | EnvironmentName: config.envName || config.appName + '-env', 26 | Description: config.description, 27 | VersionLabel: config.version, 28 | AutoCreateApplication: true, 29 | SolutionStackName: config.solutionStack, 30 | TemplateName: config.template, 31 | CNAMEPrefix: config.CNAMEPrefix, 32 | GroupName: config.GroupName, 33 | Tier: { 34 | Name: config.tier || 'WebServer', 35 | Type: config.tier === 'Worker' ? 'SQS/HTTP' : 'Standard', 36 | Version: '1.0' 37 | }, 38 | Tags: config.environmentTags, 39 | OptionSettings: config.environmentSettings 40 | }; 41 | if (codePackage) { 42 | packageName = codePackage.split('/'); 43 | params.SourceBundle = { 44 | S3Bucket: (config.bucketConfig && config.bucketConfig.Bucket ? config.bucketConfig.Bucket : config.appName).toLowerCase(), 45 | S3Key: config.version + '-' + packageName[packageName.length - 1] 46 | }; 47 | } 48 | 49 | return params; 50 | } 51 | 52 | function waitForEnv(beanstalk, params, status, logger, callback, count, start) { 53 | var maxTries = 80; 54 | if (!count) { 55 | logger('Waiting for environment "' + params.EnvironmentName + '"...'); 56 | } 57 | start = start || +(new Date()); 58 | count = count || 0; 59 | status = Array.isArray(status) ? status : [status]; 60 | beanstalk.describeEnvironments( 61 | { 62 | ApplicationName: params.ApplicationName, 63 | EnvironmentNames: [params.EnvironmentName] 64 | }, 65 | function(err, data) { 66 | var waitTime = (maxTries / 2) - (count * 2); 67 | if (waitTime < 2) { 68 | waitTime = 2; 69 | } 70 | if (err) { 71 | logger('beanstalk.describeEnvironments request failed. Check your AWS credentials and permissions.'); 72 | callback(err); 73 | } else { 74 | if (data.Environments && data.Environments.length > 0) { 75 | if (status.indexOf(data.Environments[0].Status) === -1) { 76 | if (count >= maxTries) { 77 | logger(' "' + params.EnvironmentName + '" waited too long; aborting. Please manually clean up the environment and try again'); 78 | callback(true); 79 | } else { 80 | logger(' "' + params.EnvironmentName + '" not one of [' + status + '] (currently ' + data.Environments[0].Status + '); next check in ' + waitTime + 'sec (attempt: ' + (count + 1) + '/' + maxTries + ')'); 81 | setTimeout(function () { 82 | waitForEnv(beanstalk, params, status, logger, callback, count + 1, start); 83 | }, waitTime * 1000); 84 | } 85 | } else { 86 | logger(' "' + params.EnvironmentName + '" is ' + data.Environments[0].Status + '; Done (' + ((+(new Date) - start)/1000) + 'sec)'); 87 | callback(err, data); 88 | } 89 | } else { 90 | logger('Environment "' + params.EnvironmentName + '" not found.'); 91 | callback(true); 92 | } 93 | } 94 | } 95 | ); 96 | } 97 | 98 | function updateEnvironment(beanstalk, params, logger, callback) { 99 | logger('Updating environment "' + params.EnvironmentName + '"...'); 100 | beanstalk.updateEnvironment( 101 | pick(params,['EnvironmentName', 'Description', 'OptionSettings', 'SolutionStackName', 'TemplateName', 'VersionLabel', 'Tier', 'GroupName']), 102 | function(err, data) { 103 | if (err) { 104 | logger('Create environment failed.'); 105 | callback(err); 106 | } else { 107 | logger('Environment "' + params.EnvironmentName + '" updated and is now being launched...'); 108 | waitForEnv(beanstalk, params, 'Ready', logger, callback); 109 | } 110 | } 111 | ); 112 | }; 113 | 114 | function createApplicationVersion(beanstalk, params, logger, callback) { 115 | logger('Creating application version "' + params.VersionLabel + '"...'); 116 | beanstalk.createApplicationVersion( 117 | pick(params,['ApplicationName', 'VersionLabel', 'Description', 'SourceBundle']), 118 | function(err, data) { 119 | if (err) { 120 | logger('Create application version failed.'); 121 | callback(err); 122 | } else { 123 | logger('Version "' + params.VersionLabel + '" created.'); 124 | callback(err, data); 125 | } 126 | } 127 | ); 128 | }; 129 | 130 | function createEnvironment(beanstalk, params, logger, callback, fast) { 131 | logger('Creating environment "' + params.EnvironmentName + '"...'); 132 | beanstalk.createEnvironment( 133 | pick(params,['ApplicationName', 'EnvironmentName', 'Description', 'OptionSettings', 'SolutionStackName', 'TemplateName', 'VersionLabel', 'Tier', 'Tags', 'CNAMEPrefix', 'GroupName']), 134 | function(err, data) { 135 | if (err) { 136 | logger('Create environment failed.'); 137 | callback(err); 138 | } else { 139 | logger('Environment "' + params.EnvironmentName + '" created and is now being launched...'); 140 | if (fast === true) { 141 | logger('Environment "' + params.EnvironmentName + '": skipping wait'); 142 | callback( err, { 143 | newRecord: true, 144 | data: data 145 | }); 146 | } else { 147 | waitForEnv(beanstalk, params, 'Ready', logger, function ( err, data ) { 148 | callback( err, { 149 | newRecord: true, 150 | data: data 151 | }); 152 | }); 153 | } 154 | } 155 | } 156 | ); 157 | }; 158 | 159 | function terminateEnvironment(beanstalk, params, logger, callback) { 160 | logger('Terminating environment "' + params.EnvironmentName + '"...'); 161 | waitForEnv(beanstalk, params, ['Terminated','Ready'], logger, function (err) { 162 | if (err) { 163 | callback(err); 164 | } else { 165 | beanstalk.terminateEnvironment( 166 | { 167 | EnvironmentName: params.EnvironmentName 168 | }, 169 | function(err, data) { 170 | if (err) { 171 | logger('Terminate environment failed.'); 172 | callback(err); 173 | } else { 174 | logger('Environment "' + params.EnvironmentName + '" is now being terminated...'); 175 | waitForEnv(beanstalk, params, 'Terminated', logger, callback); 176 | } 177 | } 178 | ); 179 | } 180 | }); 181 | }; 182 | 183 | function swapEnvironments(beanstalk, sourceName, destinationName, logger, callback) { 184 | logger('Swapping environments "' + sourceName + '" and "' + destinationName + '"...'); 185 | beanstalk.swapEnvironmentCNAMEs( 186 | { 187 | SourceEnvironmentName: sourceName, 188 | DestinationEnvironmentName: destinationName 189 | }, 190 | function(err, data) { 191 | if (err) { 192 | logger('Swap environments failed.'); 193 | callback(err); 194 | } else { 195 | logger('Environments "' + sourceName + '" and "' + destinationName + '" swapped.'); 196 | callback(err, data); 197 | } 198 | } 199 | ); 200 | }; 201 | 202 | function describeEnvironment(beanstalk, params, logger, callback, forSwap) { 203 | logger('Checking for environment "' + params.EnvironmentName + '"...'); 204 | beanstalk.describeEnvironments( 205 | { 206 | ApplicationName: params.ApplicationName, 207 | EnvironmentNames: [params.EnvironmentName] 208 | }, 209 | function(err, data) { 210 | var version; 211 | if (err) { 212 | logger('beanstalk.describeEnvironments request failed. Check your AWS credentials and permissions.'); 213 | callback(err); 214 | } else { 215 | if (data.Environments && data.Environments.length > 0) { 216 | if (data.Environments[0].Status !== 'Ready' && !forSwap) { 217 | waitForEnv(beanstalk, params, 'Ready', logger, callback); 218 | } else { 219 | callback(err, data); 220 | } 221 | } else { 222 | params.VersionLabel = params.EnvironmentName + '-' + defaultVersion; 223 | createApplicationVersion(beanstalk, params, logger, function (err, data) { 224 | createEnvironment(beanstalk, params, logger, callback, true); 225 | }); 226 | } 227 | } 228 | } 229 | ); 230 | }; 231 | 232 | function createApplication(beanstalk, params, logger, callback) { 233 | logger('Creating application "' + params.ApplicationName + '...'); 234 | beanstalk.createApplication( 235 | pick(params,['ApplicationName', 'Description']), 236 | function(err, data) { 237 | if (err) { 238 | logger('Create application failed.'); 239 | callback(err); 240 | } else { 241 | callback(err, data); 242 | } 243 | }); 244 | }; 245 | 246 | function describeApplication(beanstalk, params, logger, callback) { 247 | logger('Checking for application "' + params.ApplicationName + '"...'); 248 | beanstalk.describeApplications( 249 | { 250 | ApplicationNames: [params.ApplicationName] 251 | }, 252 | function(err, data) { 253 | if (err) { 254 | logger('beanstalk.describeApplication request failed. Check your AWS credentials and permissions.'); 255 | callback(err); 256 | } else { 257 | if (data.Applications && data.Applications.length > 0) { 258 | callback(err, data); 259 | } else { 260 | createApplication(beanstalk, params, logger, callback); 261 | } 262 | } 263 | } 264 | ); 265 | }; 266 | 267 | function getLatestVersion(beanstalk, params, logger, callback) { 268 | logger('Getting latest application version for "' + params.ApplicationName + '"...'); 269 | beanstalk.describeApplicationVersions( 270 | { 271 | ApplicationName: params.ApplicationName 272 | }, 273 | function(err, data) { 274 | if (err) { 275 | logger('beanstalk.describeApplicationVersions request failed. Check your AWS credentials and permissions.'); 276 | callback(err); 277 | } else { 278 | if (data.ApplicationVersions && data.ApplicationVersions.length > 0) { 279 | if (!data.ApplicationVersions.some(function (version) { 280 | if (version.VersionLabel.indexOf(params.EnvironmentName) > -1) { 281 | callback(err, version.VersionLabel); 282 | return true; 283 | } 284 | })) { 285 | callback(err, null); 286 | } 287 | } else { 288 | callback(err, null); 289 | } 290 | } 291 | } 292 | ); 293 | }; 294 | 295 | function uploadCode(S3, params, codePackage, logger, callback) { 296 | logger('Preparing to upload code to S3 bucket "' + params.SourceBundle.S3Bucket + '"...'); 297 | fs.readFile(codePackage, function(err, data) { 298 | if (err) { 299 | return callback('Error reading specified package "'+ codePackage + '"'); 300 | } 301 | logger('Uploading code to S3 bucket "' + params.SourceBundle.S3Bucket + '"...'); 302 | S3.upload( 303 | { 304 | Bucket: params.SourceBundle.S3Bucket, 305 | Key: params.SourceBundle.S3Key, 306 | Body: data, 307 | ContentType: 'binary/octet-stream' 308 | }, 309 | function(err, data) { 310 | if (err) { 311 | logger('Upload of "' + codePackage + '" to S3 bucket failed.'); 312 | callback(err); 313 | } else { 314 | logger('Uploading code to S3 bucket "' + params.SourceBundle.S3Bucket + '" done.'); 315 | callback(err, data); 316 | } 317 | } 318 | ); 319 | }); 320 | }; 321 | 322 | function createBucket(S3, config, params, logger, callback) { 323 | var args = {}; 324 | extend(args, config.bucketConfig || {}); 325 | args.Bucket = params.SourceBundle.S3Bucket; 326 | logger('Creating S3 bucket "' + args.Bucket + '"...'); 327 | S3.createBucket( 328 | args, 329 | function(err, bucketData) { 330 | if (err) { 331 | logger('Create S3 bucket "' + args.Bucket + '" failed.'); 332 | callback(err); 333 | } else { 334 | S3.waitFor('bucketExists', { 335 | Bucket: args.Bucket 336 | }, function (err) { 337 | if (err) { 338 | logger('Create S3 bucket "' + args.Bucket + '" failed.'); 339 | callback(err); 340 | } else { 341 | updateBucketTags(S3, config, params, logger, function(err) { 342 | callback(err, bucketData); 343 | }); 344 | } 345 | }); 346 | } 347 | } 348 | ); 349 | }; 350 | 351 | function updateBucketTags(S3, config, params, logger, callback) { 352 | if (config.bucketTags) { 353 | logger('Updating S3 bucket tags for "' + params.SourceBundle.S3Bucket + '"...'); 354 | S3.putBucketTagging({ 355 | Bucket: params.SourceBundle.S3Bucket, 356 | Tagging: { 357 | TagSet: config.bucketTags 358 | } 359 | }, function (err, data) { 360 | if (err) { 361 | logger('Adding tags to S3 bucket "' + params.SourceBundle.S3Bucket + '" failed.'); 362 | callback(err); 363 | } else { 364 | logger('Adding tags to S3 bucket "' + params.SourceBundle.S3Bucket + '" done.'); 365 | S3.waitFor('bucketExists', { 366 | Bucket: params.SourceBundle.S3Bucket 367 | }, callback); 368 | } 369 | }); 370 | } else { 371 | callback(null, null); 372 | } 373 | }; 374 | 375 | function updateBucket(S3, config, params, logger, callback) { 376 | var args = {}; 377 | if (config.bucketConfig) { 378 | extend(args, config.bucketConfig || {}); 379 | args.Bucket = params.SourceBundle.S3Bucket; 380 | logger('Updating S3 bucket "' + args.Bucket + '"...'); 381 | S3.putBucketAcl( 382 | args, 383 | function(err, bucketData) { 384 | if (err) { 385 | logger('Updating S3 bucket "' + args.Bucket + '" failed.'); 386 | callback(err); 387 | } else { 388 | updateBucketTags(S3, config, params, logger, function (err) { 389 | callback(err, bucketData); 390 | }); 391 | } 392 | } 393 | ); 394 | } else { 395 | updateBucketTags(S3, config, params, logger, callback); 396 | } 397 | }; 398 | 399 | function getBucket(S3, config, params, logger, callback) { 400 | logger('Checking for S3 bucket "' + params.SourceBundle.S3Bucket + '"...'); 401 | S3.headBucket( 402 | { 403 | Bucket: params.SourceBundle.S3Bucket 404 | }, 405 | function(err, data) { 406 | if (err) { 407 | if (err.statusCode === 404) { 408 | createBucket(S3, config, params, logger, callback); 409 | } else { 410 | logger('S3.headBucket request failed. Check your AWS credentials and permissions.'); 411 | callback(err); 412 | } 413 | } else { 414 | updateBucket(S3, config, params, logger, function(err) { 415 | callback(err, data); 416 | }); 417 | } 418 | } 419 | ); 420 | }; 421 | 422 | exports.deploy = function(codePackage, config, callback, logger, beanstalk, S3) { 423 | var params, 424 | newEnvironment = false; 425 | 426 | params = setup(config, codePackage); 427 | if (!logger) { 428 | logger = function (msg) { 429 | console.log( util.format( '[%s] %s', chalk.green( params.ApplicationName + '[' + params.EnvironmentName + ']' ), msg ) ); 430 | }; 431 | } 432 | 433 | if(!beanstalk || !S3) { 434 | if("profile" in config) { 435 | var credentials = new AWS.SharedIniFileCredentials({profile: config.profile}); 436 | AWS.config.credentials = credentials; 437 | } 438 | 439 | if (process.env.HTTPS_PROXY) { 440 | if (!AWS.config.httpOptions) { 441 | AWS.config.httpOptions = {}; 442 | } 443 | var HttpsProxyAgent = require('https-proxy-agent'); 444 | AWS.config.httpOptions.agent = new HttpsProxyAgent(process.env.HTTPS_PROXY); 445 | } 446 | if ('accessKeyId' in config && 'secretAccessKey' in config) { 447 | if (!beanstalk) { 448 | logger('Creating beanstalk with accessKeyId "' + config.accessKeyId + '" in region "' + config.region + '"'); 449 | beanstalk = new AWS.ElasticBeanstalk({ 450 | region: config.region, 451 | accessKeyId: config.accessKeyId, 452 | secretAccessKey: config.secretAccessKey 453 | }); 454 | } 455 | if (!S3) { 456 | logger('Creating S3 with accessKeyId "' + config.accessKeyId + '"'); 457 | S3 = new AWS.S3({ 458 | apiVersion: '2006-03-01', 459 | signatureVersion: 'v4', 460 | accessKeyId: config.accessKeyId, 461 | secretAccessKey: config.secretAccessKey 462 | }); 463 | } 464 | } else { 465 | if (!beanstalk) { 466 | beanstalk = new AWS.ElasticBeanstalk({ 467 | region: config.region 468 | }); 469 | } 470 | if (!S3) { 471 | S3 = new AWS.S3({ 472 | apiVersion: '2006-03-01', 473 | signatureVersion: 'v4' 474 | }); 475 | } 476 | } 477 | } 478 | 479 | if (!params.SolutionStackName && !params.TemplateName) { 480 | return callback('Missing either "solutionStack" or "template" config'); 481 | } 482 | if (params.SolutionStackName && params.TemplateName) { 483 | return callback('Provided both "solutionStack" and "template" config; only one or the other supported'); 484 | } 485 | if (!params.SourceBundle) { 486 | return callback('Missing/invalid codePackage'); 487 | } 488 | 489 | getBucket(S3, config, params, logger, function (err, data) { 490 | if (err) { 491 | callback(err); 492 | } else { 493 | uploadCode(S3, params, codePackage,logger, function (err, data) { 494 | if (err) { 495 | callback(err); 496 | } else { 497 | describeApplication(beanstalk, params, logger, function (err, data) { 498 | if (err) { 499 | callback(err); 500 | } else { 501 | describeEnvironment(beanstalk, params, logger, function (err, data) { 502 | if (err) { 503 | callback(err); 504 | } else { 505 | if ( data && data.newRecord ) { 506 | callback(); 507 | } else { 508 | getLatestVersion(beanstalk, params, logger, function (err, data ) { 509 | var version; 510 | if (err) { 511 | callback(err); 512 | } else { 513 | version = (data || params.VersionLabel).split('.'); 514 | if (version.length > 3) { 515 | version[version.length - 1] = parseInt(version[version.length - 1]) + 1; 516 | } else { 517 | version.push(0); 518 | } 519 | params.VersionLabel = version.join('.'); 520 | createApplicationVersion(beanstalk, params, logger, function (err, data) { 521 | var swapName = 'tmp-' + (+new Date()); 522 | if (err) { 523 | callback(err); 524 | } else { 525 | if (newEnvironment) { 526 | callback(); 527 | } else { 528 | if (params.Tags || config.abswap === true) { 529 | swap(beanstalk, params, logger, params.EnvironmentName, swapName, function (err, data) { 530 | if (err) { 531 | callback(err); 532 | } else { 533 | swap(beanstalk, params, logger, swapName, params.EnvironmentName, function (err, data) { 534 | if (err) { 535 | callback(err); 536 | } else { 537 | callback(); 538 | } 539 | }); 540 | } 541 | }); 542 | } else { 543 | updateEnvironment(beanstalk, params, logger, function (err, data) { 544 | if (err) { 545 | callback(err); 546 | } else { 547 | callback(); 548 | } 549 | }); 550 | } 551 | } 552 | } 553 | }); 554 | } 555 | }); 556 | } 557 | } 558 | }, params.Tags || config.abswap === true); 559 | } 560 | }); 561 | } 562 | }); 563 | } 564 | }); 565 | }; 566 | 567 | function swap(beanstalk, params, logger, from, to, callback) { 568 | params.EnvironmentName = to; 569 | createEnvironment(beanstalk, params, logger, function (err, data) { 570 | if (err) { 571 | callback(err); 572 | } else { 573 | if (params.Tier.Name === 'WebServer') { 574 | swapEnvironments(beanstalk, from, params.EnvironmentName, logger, function (err, data) { 575 | if (err) { 576 | callback(err); 577 | } else { 578 | params.EnvironmentName = from 579 | terminateEnvironment(beanstalk, params, logger, function (err, data) { 580 | if (err) { 581 | callback(err); 582 | } else { 583 | callback(err, data); 584 | } 585 | }); 586 | } 587 | }); 588 | } else { 589 | params.EnvironmentName = from; 590 | terminateEnvironment(beanstalk, params, logger, function (err, data) { 591 | if (err) { 592 | callback(err); 593 | } else { 594 | callback(err, data); 595 | } 596 | }); 597 | } 598 | } 599 | }); 600 | } 601 | 602 | exports.update = function(config, callback, logger, beanstalk) { 603 | var params; 604 | 605 | params = setup(config); 606 | if (!logger) { 607 | logger = function (msg) { 608 | console.log( util.format( '[%s] %s', chalk.green( params.ApplicationName + '[' + params.EnvironmentName + ']' ), msg ) ); 609 | }; 610 | } 611 | 612 | if(!beanstalk) { 613 | if("profile" in config) { 614 | var credentials = new AWS.SharedIniFileCredentials({profile: config.profile}); 615 | AWS.config.credentials = credentials; 616 | } 617 | 618 | if (process.env.HTTPS_PROXY) { 619 | if (!AWS.config.httpOptions) { 620 | AWS.config.httpOptions = {}; 621 | } 622 | var HttpsProxyAgent = require('https-proxy-agent'); 623 | AWS.config.httpOptions.agent = new HttpsProxyAgent(process.env.HTTPS_PROXY); 624 | } 625 | } 626 | if (!beanstalk) { 627 | beanstalk = new AWS.ElasticBeanstalk({ 628 | region: config.region, 629 | accessKeyId: 'accessKeyId' in config ? config.accessKeyId : '', 630 | secretAccessKey: 'secretAccessKey' in config ? config.secretAccessKey : '' 631 | }); 632 | } 633 | if (config.S3Bucket) { 634 | logger(' DEPRECATION NOTICE: "S3Bucket" configuration is deprecated. Pleae use "bucketConfig.Bucket"'); 635 | config.bucketConfig = config.bucketConfig || {}; 636 | config.bucketConfig.Bucket = config.bucketConfig.Bucket || config.S3Bucket; 637 | delete config.S3Bucket; 638 | } 639 | 640 | if (!params.SolutionStackName && !params.TemplateName) { 641 | return callback('Missing either "solutionStack" or "template" config'); 642 | } 643 | if (params.SolutionStackName && params.TemplateName) { 644 | return callback('Provided both "solutionStack" and "template" config; only one or the other supported'); 645 | } 646 | 647 | describeApplication(beanstalk, params, logger, function (err, data) { 648 | if (err) { 649 | callback(err); 650 | } else { 651 | describeEnvironment(beanstalk, params, logger, function (err, data) { 652 | var swapName = 'tmp-' + (+new Date()); 653 | if (err) { 654 | callback(err); 655 | } else { 656 | if (params.Tags || config.abswap === true) { 657 | getLatestVersion(beanstalk, params, logger, function (err, data ) { 658 | params.VersionLabel = data || params.VersionLabel + '.0'; 659 | swap(beanstalk, params, logger, params.EnvironmentName, swapName, function (err, data) { 660 | if (err) { 661 | callback(err); 662 | } else { 663 | swap(beanstalk, params, logger, swapName, params.EnvironmentName, function (err, data) { 664 | if (err) { 665 | callback(err); 666 | } else { 667 | callback(); 668 | } 669 | }); 670 | } 671 | }); 672 | }); 673 | } else { 674 | params.VersionLabel = data.Environments[0].VersionLabel; 675 | updateEnvironment(beanstalk, params, logger, function (err, data) { 676 | if (err) { 677 | callback(err); 678 | } else { 679 | callback(); 680 | } 681 | }); 682 | } 683 | } 684 | }, params.Tags || config.abswap === true); 685 | } 686 | }); 687 | }; 688 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-aws-beanstalk", 3 | "version": "3.4.8", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "agent-base": { 8 | "version": "4.3.0", 9 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", 10 | "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", 11 | "requires": { 12 | "es6-promisify": "^5.0.0" 13 | } 14 | }, 15 | "ansi-regex": { 16 | "version": "2.1.1", 17 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 18 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 19 | }, 20 | "ansi-styles": { 21 | "version": "2.2.1", 22 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 23 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" 24 | }, 25 | "aws-sdk": { 26 | "version": "2.629.0", 27 | "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.629.0.tgz", 28 | "integrity": "sha512-QJABaXOq78Cd2dYa5WZctpfG4EoFQyp6KL8YPc8oZrVAEhvipiPxSgIm4nI6hi9gjb3rFQD5SPFBwbS10lXf5A==", 29 | "requires": { 30 | "buffer": "4.9.1", 31 | "events": "1.1.1", 32 | "ieee754": "1.1.13", 33 | "jmespath": "0.15.0", 34 | "querystring": "0.2.0", 35 | "sax": "1.2.1", 36 | "url": "0.10.3", 37 | "uuid": "3.3.2", 38 | "xml2js": "0.4.19" 39 | } 40 | }, 41 | "base64-js": { 42 | "version": "1.3.1", 43 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", 44 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" 45 | }, 46 | "buffer": { 47 | "version": "4.9.1", 48 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", 49 | "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", 50 | "requires": { 51 | "base64-js": "^1.0.2", 52 | "ieee754": "^1.1.4", 53 | "isarray": "^1.0.0" 54 | } 55 | }, 56 | "chalk": { 57 | "version": "1.1.3", 58 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 59 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 60 | "requires": { 61 | "ansi-styles": "^2.2.1", 62 | "escape-string-regexp": "^1.0.2", 63 | "has-ansi": "^2.0.0", 64 | "strip-ansi": "^3.0.0", 65 | "supports-color": "^2.0.0" 66 | } 67 | }, 68 | "debug": { 69 | "version": "3.2.6", 70 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 71 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 72 | "requires": { 73 | "ms": "^2.1.1" 74 | } 75 | }, 76 | "es6-promise": { 77 | "version": "4.2.8", 78 | "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", 79 | "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" 80 | }, 81 | "es6-promisify": { 82 | "version": "5.0.0", 83 | "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", 84 | "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", 85 | "requires": { 86 | "es6-promise": "^4.0.3" 87 | } 88 | }, 89 | "escape-string-regexp": { 90 | "version": "1.0.5", 91 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 92 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 93 | }, 94 | "events": { 95 | "version": "1.1.1", 96 | "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", 97 | "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" 98 | }, 99 | "has-ansi": { 100 | "version": "2.0.0", 101 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 102 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 103 | "requires": { 104 | "ansi-regex": "^2.0.0" 105 | } 106 | }, 107 | "https-proxy-agent": { 108 | "version": "2.2.4", 109 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", 110 | "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", 111 | "requires": { 112 | "agent-base": "^4.3.0", 113 | "debug": "^3.1.0" 114 | } 115 | }, 116 | "ieee754": { 117 | "version": "1.1.13", 118 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", 119 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" 120 | }, 121 | "isarray": { 122 | "version": "1.0.0", 123 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 124 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 125 | }, 126 | "jmespath": { 127 | "version": "0.15.0", 128 | "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", 129 | "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" 130 | }, 131 | "ms": { 132 | "version": "2.1.2", 133 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 134 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" 135 | }, 136 | "punycode": { 137 | "version": "1.3.2", 138 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", 139 | "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" 140 | }, 141 | "querystring": { 142 | "version": "0.2.0", 143 | "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", 144 | "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" 145 | }, 146 | "sax": { 147 | "version": "1.2.1", 148 | "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", 149 | "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" 150 | }, 151 | "strip-ansi": { 152 | "version": "3.0.1", 153 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 154 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 155 | "requires": { 156 | "ansi-regex": "^2.0.0" 157 | } 158 | }, 159 | "supports-color": { 160 | "version": "2.0.0", 161 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 162 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" 163 | }, 164 | "url": { 165 | "version": "0.10.3", 166 | "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", 167 | "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", 168 | "requires": { 169 | "punycode": "1.3.2", 170 | "querystring": "0.2.0" 171 | } 172 | }, 173 | "uuid": { 174 | "version": "3.3.2", 175 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", 176 | "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" 177 | }, 178 | "xml2js": { 179 | "version": "0.4.19", 180 | "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", 181 | "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", 182 | "requires": { 183 | "sax": ">=0.6.0", 184 | "xmlbuilder": "~9.0.1" 185 | } 186 | }, 187 | "xmlbuilder": { 188 | "version": "9.0.7", 189 | "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", 190 | "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-aws-beanstalk", 3 | "version": "3.4.9", 4 | "description": "A module to help you automate AWS beanstalk app deployment.", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/dlhdesign/node-aws-beanstalk" 8 | }, 9 | "keywords": [ 10 | "aws", 11 | "beanstalk", 12 | "deploy" 13 | ], 14 | "author": "David Hutchings", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/dlhdesign/node-aws-beanstalk/issues" 18 | }, 19 | "dependencies": { 20 | "aws-sdk": "2.x.x", 21 | "https-proxy-agent": "^2.2.3", 22 | "chalk": "^1.1.1" 23 | } 24 | } 25 | --------------------------------------------------------------------------------