From ccf1872648a89132848e215da1038429f0082494 Mon Sep 17 00:00:00 2001 From: Shane Tomlinson Date: Mon, 29 Oct 2012 14:29:36 +0000 Subject: [PATCH 1/4] Adding the notion of local_hooks to run on the local host. The only local_hook is "postcreate" which is run after the vm is created. --- awsbox.js | 41 ++++++++++++++++++++++------------------- lib/config.js | 14 ++++++++++++++ lib/git.js | 4 ++-- lib/hooks.js | 39 +++++++++++++++++++++++++++++++++++++++ lib/process.js | 24 ++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 21 deletions(-) create mode 100644 lib/config.js create mode 100644 lib/hooks.js create mode 100644 lib/process.js diff --git a/awsbox.js b/awsbox.js index 7700f1d..1e458f8 100755 --- a/awsbox.js +++ b/awsbox.js @@ -3,8 +3,8 @@ process.title = 'awsbox'; const -aws = require('./lib/aws.js'); -path = require('path'); +aws = require('./lib/aws.js'), +path = require('path'), vm = require('./lib/vm.js'), key = require('./lib/key.js'), ssh = require('./lib/ssh.js'), @@ -12,6 +12,8 @@ dns = require('./lib/dns.js'), git = require('./lib/git.js'), optimist = require('optimist'), urlparse = require('urlparse'), +hooks = require('./lib/hooks'), +config = require('./lib/config'), fs = require('fs'), relativeDate = require('relative-date'); @@ -42,6 +44,18 @@ function validateName(name) { } } +function copySSLCertIfAvailable(opts, deets, cb) { + if (opts.p && opts.s) { + console.log(" ... copying up SSL cert"); + ssh.copySSL(deets.ipAddress, opts.p, opts.s, function(err) { + checkErr(err); + cb && cb(null, null); + }); + } else { + cb && cb(null, null); + } +} + verbs['destroy'] = function(args) { if (!args || args.length != 1) { throw 'missing required argument: name of instance'; @@ -179,11 +193,7 @@ verbs['create'] = function(args) { console.log("reading .awsbox.json"); - try { - var awsboxJson = JSON.parse(fs.readFileSync("./.awsbox.json")); - } catch(e) { - checkErr("Can't read awsbox.json: " + e); - } + var awsboxJson = config.get(); console.log("attempting to set up VM \"" + name + "\""); @@ -257,22 +267,15 @@ verbs['create'] = function(args) { } ssh.installPackages(deets.ipAddress, awsboxJson.packages, function(err, r) { checkErr(err); - var postcreate = (awsboxJson.hooks && awsboxJson.hooks.postcreate) || null; - if (postcreate) { - console.log(" ... running post_create hook"); - } - ssh.runScript(deets.ipAddress, postcreate, function(err, r) { + hooks.runRemoteHook('postcreate', deets, function(err, r) { checkErr(err); - if (opts.p && opts.s) { - console.log(" ... copying up SSL cert"); - ssh.copySSL(deets.ipAddress, opts.p, opts.s, function(err) { - checkErr(err); + copySSLCertIfAvailable(opts, deets, function(err, status) { + checkErr(err); + hooks.runLocalHook('postcreate', deets, function(err) { printInstructions(name, dnsHost, opts.u, deets); }); - } else { - printInstructions(name, dnsHost, opts.u, deets); - } + }); }); }); }); diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..e7e1149 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,14 @@ +const fs = require('fs'); + +exports.get = function() { + try { + var awsboxJson = JSON.parse(fs.readFileSync("./.awsbox.json")); + } catch(e) { + checkErr("Can't read awsbox.json: " + e); + } + + return awsboxJson; +} + + + diff --git a/lib/git.js b/lib/git.js index 1d7546e..7febb96 100644 --- a/lib/git.js +++ b/lib/git.js @@ -1,5 +1,5 @@ const -child_process = require('child_process'); +child_process = require('child_process'), spawn = child_process.spawn, path = require('path'); @@ -71,7 +71,7 @@ exports.push = function(dir, host, pr, cb) { cb = pr; pr = host; host = dir; - dir = path.join(__dirname, '..', '..'); + dir = path.join(__dirname, '..', '..', '..'); } var p = spawn('git', [ 'push', 'app@' + host + ":git", 'HEAD:master' ], { diff --git a/lib/hooks.js b/lib/hooks.js new file mode 100644 index 0000000..af71c9e --- /dev/null +++ b/lib/hooks.js @@ -0,0 +1,39 @@ + +const +path = require('path'), +fs = require('fs'), +child_process = require('./process'), +ssh = require('./ssh.js'), +config = require('./config.js'); + +function getRemoteHook(which) { + var awsboxJson = config.get(); + var remoteHooks = awsboxJson.remote_hooks || awsboxJson.hooks; + return (remoteHooks && remoteHooks[which]) || null; +} + +function getLocalHook(which) { + var awsboxJson = config.get(); + var localHooks = awsboxJson.local_hooks; + return (localHooks && localHooks[which]) || null; +} + +exports.runRemoteHook = function(which, deets, cb) { + var cmd = getRemoteHook(which); + if (cmd) { + console.log(" ... running remote", which, "hook"); + } + ssh.runScript(deets.ipAddress, cmd, cb); +} + +exports.runLocalHook = function(which, deets, cb) { + var cmd = getLocalHook(which); + if (cmd) { + console.log(" ... running local ", which, "hook"); + } + + // let each local hook now what the remote AWS host is. + process.env['AWS_IP_ADDRESS'] = deets.ipAddress; + var childProcess = child_process.exec(cmd, cb); +} + diff --git a/lib/process.js b/lib/process.js new file mode 100644 index 0000000..cec8959 --- /dev/null +++ b/lib/process.js @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const +child_process = require('child_process'), +util = require('util'); + +exports.exec = function(cmd, done) { + if (!cmd) done(null); + + var childProcess = child_process.exec(cmd, { + env: process.env + }, done); + + childProcess.stdout.on('data', function(data) { + util.print(data.toString()); + }); + + childProcess.stderr.on('data', function(data) { + util.error(data.toString()); + }); +}; + From 5e6550c6ca168054caffb8956fbc7718141f23ff Mon Sep 17 00:00:00 2001 From: Shane Tomlinson Date: Mon, 29 Oct 2012 14:29:36 +0000 Subject: [PATCH 2/4] Adding the notion of local_hooks to run on the local host. The only local_hook is "postcreate" which is run after the vm is created. --- awsbox.js | 41 ++++++++++++++++++++++------------------- lib/config.js | 14 ++++++++++++++ lib/hooks.js | 39 +++++++++++++++++++++++++++++++++++++++ lib/process.js | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 lib/config.js create mode 100644 lib/hooks.js create mode 100644 lib/process.js diff --git a/awsbox.js b/awsbox.js index 7700f1d..1e458f8 100755 --- a/awsbox.js +++ b/awsbox.js @@ -3,8 +3,8 @@ process.title = 'awsbox'; const -aws = require('./lib/aws.js'); -path = require('path'); +aws = require('./lib/aws.js'), +path = require('path'), vm = require('./lib/vm.js'), key = require('./lib/key.js'), ssh = require('./lib/ssh.js'), @@ -12,6 +12,8 @@ dns = require('./lib/dns.js'), git = require('./lib/git.js'), optimist = require('optimist'), urlparse = require('urlparse'), +hooks = require('./lib/hooks'), +config = require('./lib/config'), fs = require('fs'), relativeDate = require('relative-date'); @@ -42,6 +44,18 @@ function validateName(name) { } } +function copySSLCertIfAvailable(opts, deets, cb) { + if (opts.p && opts.s) { + console.log(" ... copying up SSL cert"); + ssh.copySSL(deets.ipAddress, opts.p, opts.s, function(err) { + checkErr(err); + cb && cb(null, null); + }); + } else { + cb && cb(null, null); + } +} + verbs['destroy'] = function(args) { if (!args || args.length != 1) { throw 'missing required argument: name of instance'; @@ -179,11 +193,7 @@ verbs['create'] = function(args) { console.log("reading .awsbox.json"); - try { - var awsboxJson = JSON.parse(fs.readFileSync("./.awsbox.json")); - } catch(e) { - checkErr("Can't read awsbox.json: " + e); - } + var awsboxJson = config.get(); console.log("attempting to set up VM \"" + name + "\""); @@ -257,22 +267,15 @@ verbs['create'] = function(args) { } ssh.installPackages(deets.ipAddress, awsboxJson.packages, function(err, r) { checkErr(err); - var postcreate = (awsboxJson.hooks && awsboxJson.hooks.postcreate) || null; - if (postcreate) { - console.log(" ... running post_create hook"); - } - ssh.runScript(deets.ipAddress, postcreate, function(err, r) { + hooks.runRemoteHook('postcreate', deets, function(err, r) { checkErr(err); - if (opts.p && opts.s) { - console.log(" ... copying up SSL cert"); - ssh.copySSL(deets.ipAddress, opts.p, opts.s, function(err) { - checkErr(err); + copySSLCertIfAvailable(opts, deets, function(err, status) { + checkErr(err); + hooks.runLocalHook('postcreate', deets, function(err) { printInstructions(name, dnsHost, opts.u, deets); }); - } else { - printInstructions(name, dnsHost, opts.u, deets); - } + }); }); }); }); diff --git a/lib/config.js b/lib/config.js new file mode 100644 index 0000000..e7e1149 --- /dev/null +++ b/lib/config.js @@ -0,0 +1,14 @@ +const fs = require('fs'); + +exports.get = function() { + try { + var awsboxJson = JSON.parse(fs.readFileSync("./.awsbox.json")); + } catch(e) { + checkErr("Can't read awsbox.json: " + e); + } + + return awsboxJson; +} + + + diff --git a/lib/hooks.js b/lib/hooks.js new file mode 100644 index 0000000..af71c9e --- /dev/null +++ b/lib/hooks.js @@ -0,0 +1,39 @@ + +const +path = require('path'), +fs = require('fs'), +child_process = require('./process'), +ssh = require('./ssh.js'), +config = require('./config.js'); + +function getRemoteHook(which) { + var awsboxJson = config.get(); + var remoteHooks = awsboxJson.remote_hooks || awsboxJson.hooks; + return (remoteHooks && remoteHooks[which]) || null; +} + +function getLocalHook(which) { + var awsboxJson = config.get(); + var localHooks = awsboxJson.local_hooks; + return (localHooks && localHooks[which]) || null; +} + +exports.runRemoteHook = function(which, deets, cb) { + var cmd = getRemoteHook(which); + if (cmd) { + console.log(" ... running remote", which, "hook"); + } + ssh.runScript(deets.ipAddress, cmd, cb); +} + +exports.runLocalHook = function(which, deets, cb) { + var cmd = getLocalHook(which); + if (cmd) { + console.log(" ... running local ", which, "hook"); + } + + // let each local hook now what the remote AWS host is. + process.env['AWS_IP_ADDRESS'] = deets.ipAddress; + var childProcess = child_process.exec(cmd, cb); +} + diff --git a/lib/process.js b/lib/process.js new file mode 100644 index 0000000..0db9a26 --- /dev/null +++ b/lib/process.js @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const +child_process = require('child_process'), +util = require('util'); + +exports.exec = function(cmd, options, done) { + // options is optional, so if done does not exist, create a default options + // object, update the done reference. + if (!done) { + done = options; + options = { + env: process.env + }; + } + + if (!cmd) { + done && done(null); + return; + } + + var childProcess = child_process.exec(cmd, options, done); + + childProcess.stdout.on('data', function(data) { + util.print(data.toString()); + }); + + childProcess.stderr.on('data', function(data) { + util.error(data.toString()); + }); +}; + From 621f62b1488b9b4f3a004058df95c678041b9ace Mon Sep 17 00:00:00 2001 From: Shane Tomlinson Date: Mon, 29 Oct 2012 14:29:36 +0000 Subject: [PATCH 3/4] Add an 'update' verb which pushes the repo to the remote server and runs a poststart local_hook. Fix a bug in the git lib that tried to push the wrong directory. --- awsbox.js | 31 +++++++++++++++++++++++++++++++ lib/git.js | 4 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/awsbox.js b/awsbox.js index 1e458f8..2bc5422 100755 --- a/awsbox.js +++ b/awsbox.js @@ -364,6 +364,37 @@ verbs['list'] = function(args) { }); }; +verbs['update'] = function(args) { + if (!args || args.length != 1) { + throw 'missing required argument: name of instance'; + } + var name = args[0]; + validateName(name); + + vm.find(name, function(err, deets) { + checkErr(err); + + if (deets && deets.ipAddress) { + console.log("pushing to git repo", deets.ipAddress); + git.push(deets.ipAddress, function(line) { + console.log(line); + }, function(status) { + if (!status) { + hooks.runLocalHook('poststart', deets); + } + else { + checkErr("Could not push git instance"); + } + + }); + } + else { + console.log(name, "is not an awsbox instance"); + } + }); + +}; + var error = (process.argv.length <= 2); if (!error) { diff --git a/lib/git.js b/lib/git.js index 1d7546e..7febb96 100644 --- a/lib/git.js +++ b/lib/git.js @@ -1,5 +1,5 @@ const -child_process = require('child_process'); +child_process = require('child_process'), spawn = child_process.spawn, path = require('path'); @@ -71,7 +71,7 @@ exports.push = function(dir, host, pr, cb) { cb = pr; pr = host; host = dir; - dir = path.join(__dirname, '..', '..'); + dir = path.join(__dirname, '..', '..', '..'); } var p = spawn('git', [ 'push', 'app@' + host + ":git", 'HEAD:master' ], { From dcecd025484a49d20998f89de77941a7eebef69e Mon Sep 17 00:00:00 2001 From: Shane Tomlinson Date: Wed, 31 Oct 2012 13:25:32 +0000 Subject: [PATCH 4/4] Pass all environment variables along to the GIT commands. This prevents the user from being asked for their password when pushing to a remote host. --- lib/git.js | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/lib/git.js b/lib/git.js index 7febb96..6daa859 100644 --- a/lib/git.js +++ b/lib/git.js @@ -3,6 +3,26 @@ child_process = require('child_process'), spawn = child_process.spawn, path = require('path'); +// getEnv is used to pass all the rest of the environment variables to git. +// This prevents the user from being required to enter their password on a git +// push +function getEnv(extraEnv) { + var env = {}; + + // copy over the original environment + for(var key in process.env) { + env[key] = process.env[key]; + } + + // add each item in extraEnv + for(var key in extraEnv) { + env[key] = extraEnv[key]; + } + + return env; +} + + exports.addRemote = function(name, host, cb) { var cmd = 'git remote add "' + name + '" app@'+ host + ':git'; child_process.exec(cmd, cb); @@ -41,7 +61,7 @@ exports.currentSHA = function(dir, cb) { } var p = spawn('git', [ 'log', '--pretty=%h', '-1' ], { - env: { GIT_DIR: path.join(dir, ".git") } + env: getEnv({ GIT_DIR: path.join(dir, ".git") }) }); var buf = ""; p.stdout.on('data', function(d) { @@ -75,10 +95,10 @@ exports.push = function(dir, host, pr, cb) { } var p = spawn('git', [ 'push', 'app@' + host + ":git", 'HEAD:master' ], { - env: { + env: getEnv({ GIT_DIR: path.join(dir, ".git"), GIT_WORK_TREE: dir - } + }) }); p.stdout.on('data', function(c) { splitAndEmit(c, pr); }); p.stderr.on('data', function(c) { splitAndEmit(c, pr); }); @@ -89,11 +109,11 @@ exports.push = function(dir, host, pr, cb) { exports.pull = function(dir, remote, branch, pr, cb) { var p = spawn('git', [ 'pull', "-f", remote, branch + ":" + branch ], { - env: { + env: getEnv({ GIT_DIR: path.join(dir, ".git"), GIT_WORK_TREE: dir, PWD: dir - }, + }), cwd: dir }); @@ -107,10 +127,10 @@ exports.pull = function(dir, remote, branch, pr, cb) { exports.init = function(dir, cb) { var p = spawn('git', [ 'init' ], { - env: { + env: getEnv({ GIT_DIR: path.join(dir, ".git"), GIT_WORK_TREE: dir - } + }) }); p.on('exit', function(code, signal) { return cb(code = 0);