implement DNS features to update zerigo automagically
This commit is contained in:
Родитель
9c4e6d5239
Коммит
d13057af05
155
awsbox.js
155
awsbox.js
|
@ -8,6 +8,7 @@ path = require('path');
|
|||
vm = require('./lib/vm.js'),
|
||||
key = require('./lib/key.js'),
|
||||
ssh = require('./lib/ssh.js'),
|
||||
dns = require('./lib/dns.js'),
|
||||
git = require('./lib/git.js'),
|
||||
optimist = require('optimist'),
|
||||
urlparse = require('urlparse'),
|
||||
|
@ -17,17 +18,20 @@ var verbs = {};
|
|||
|
||||
function checkErr(err) {
|
||||
if (err) {
|
||||
process.stderr.write('fatal error: ' + err + "\n");
|
||||
process.stderr.write('ERRORE FATALE: ' + err + "\n");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function printInstructions(name, deets) {
|
||||
function printInstructions(name, host, url, deets) {
|
||||
if (!url) url = 'http://' + deets.ipAddress;
|
||||
if (!host) host = deets.ipAddress;
|
||||
console.log("");
|
||||
console.log("Yay! You have your very own deployment. Here's the basics:\n");
|
||||
console.log(" 1. deploy your code: git push " + name + " <mybranch>:master");
|
||||
console.log(" 2. visit your server on the web: http://" + deets.ipAddress);
|
||||
console.log(" 3. ssh in with sudo: ssh ec2-user@" + deets.ipAddress);
|
||||
console.log(" 4. ssh as the deployment user: ssh app@" + deets.ipAddress);
|
||||
console.log(" 1. deploy your code: git push " + name + " HEAD:master");
|
||||
console.log(" 2. visit your server on the web: " + url);
|
||||
console.log(" 3. ssh in with sudo: ssh ec2-user@" + host);
|
||||
console.log(" 4. ssh as the deployment user: ssh app@" + host);
|
||||
console.log("\n Here are your server's details:", JSON.stringify(deets, null, 4));
|
||||
}
|
||||
|
||||
|
@ -52,6 +56,26 @@ verbs['destroy'] = function(args) {
|
|||
process.stdout.write("trying to remove git remote: ");
|
||||
git.removeRemote(name, deets.ipAddress, function(err) {
|
||||
console.log(err ? "failed: " + err : "done");
|
||||
|
||||
if (process.env['ZERIGO_DNS_KEY']) {
|
||||
process.stdout.write("trying to remove DNS: ");
|
||||
var dnsKey = process.env['ZERIGO_DNS_KEY'];
|
||||
dns.findByIP(dnsKey, deets.ipAddress, function(err, fqdns) {
|
||||
checkErr(err);
|
||||
if (!fqdns.length) return console.log("no dns entries found");
|
||||
console.log(fqdns.join(', '));
|
||||
function removeNext() {
|
||||
if (!fqdns.length) return;
|
||||
var fqdn = fqdns.shift();
|
||||
process.stdout.write("deleting " + fqdn + ": ");
|
||||
dns.deleteRecord(dnsKey, fqdn, function(err) {
|
||||
console.log(err ? "failed: " + err : "done");
|
||||
removeNext();
|
||||
});
|
||||
}
|
||||
removeNext();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -65,9 +89,16 @@ verbs['test'] = function() {
|
|||
});
|
||||
}
|
||||
|
||||
verbs['findByIP'] = function(args) {
|
||||
dns.findByIP(process.env['ZERIGO_DNS_KEY'], args[0], function(err, fqdns) {
|
||||
console.log(err, fqdns);
|
||||
});
|
||||
};
|
||||
|
||||
verbs['create'] = function(args) {
|
||||
var parser = optimist(args)
|
||||
.usage('awsbox create: Create a VM')
|
||||
.describe('d', 'setup DNS via zerigo (requires ZERIGO_DNS_KEY in env)')
|
||||
.describe('n', 'a short nickname for the VM.')
|
||||
.describe('u', 'publically visible URL for the instance')
|
||||
.check(function(argv) {
|
||||
|
@ -112,68 +143,88 @@ verbs['create'] = function(args) {
|
|||
try {
|
||||
var awsboxJson = JSON.parse(fs.readFileSync("./.awsbox.json"));
|
||||
} catch(e) {
|
||||
console.log("Fatal error! Can't read awsbox.json: " + e);
|
||||
process.exit(1);
|
||||
checkErr("Can't read awsbox.json: " + e);
|
||||
}
|
||||
|
||||
console.log("attempting to set up VM \"" + name + "\"");
|
||||
|
||||
vm.startImage({
|
||||
type: opts.t
|
||||
}, function(err, r) {
|
||||
var dnsKey;
|
||||
var dnsHost;
|
||||
if (opts.d) {
|
||||
if (!opts.u) checkErr('-d is meaningless without -u (to set DNS I need a hostname)');
|
||||
if (!process.env['ZERIGO_DNS_KEY']) checkErr('-d requires ZERIGO_DNS_KEY env var');
|
||||
dnsKey = process.env['ZERIGO_DNS_KEY'];
|
||||
dnsHost = urlparse(opts.u).host;
|
||||
console.log(" ... Checking for DNS availability of " + dnsHost);
|
||||
}
|
||||
|
||||
dns.inUse(dnsKey, dnsHost, function(err, res) {
|
||||
checkErr(err);
|
||||
console.log(" ... VM launched, waiting for startup (should take about 20s)");
|
||||
if (res) checkErr('that domain is in use, pointing at ' + res.data);
|
||||
|
||||
vm.waitForInstance(r.instanceId, function(err, deets) {
|
||||
vm.startImage({
|
||||
type: opts.t
|
||||
}, function(err, r) {
|
||||
checkErr(err);
|
||||
console.log(" ... Instance ready, setting human readable name in aws");
|
||||
vm.setName(r.instanceId, longName, function(err) {
|
||||
console.log(" ... VM launched, waiting for startup (should take about 20s)");
|
||||
|
||||
vm.waitForInstance(r.instanceId, function(err, deets) {
|
||||
checkErr(err);
|
||||
console.log(" ... name set, waiting for ssh access and configuring");
|
||||
var config = { public_url: (opts.u || "http://" + deets.ipAddress) };
|
||||
|
||||
if (argv.x) {
|
||||
console.log(" ... adding addition configuration values");
|
||||
var x = JSON.parse(fs.readFileSync(argv.x));
|
||||
Object.keys(x).forEach(function(key) {
|
||||
config[key] = x[key];
|
||||
});
|
||||
}
|
||||
if (dnsHost) console.log(" ... Adding DNS Record for " + dnsHost);
|
||||
|
||||
console.log(" ... public url will be:", config.public_url);
|
||||
dns.updateRecord(dnsKey, dnsHost, deets.ipAddress, function(err) {
|
||||
checkErr(err ? 'updating DNS: ' + err : err);
|
||||
|
||||
ssh.copyUpConfig(deets.ipAddress, config, function(err, r) {
|
||||
checkErr(err);
|
||||
console.log(" ... victory! server is accessible and configured");
|
||||
git.addRemote(name, deets.ipAddress, function(err, r) {
|
||||
if (err && /already exists/.test(err)) {
|
||||
console.log("OOPS! you already have a git remote named '" + name + "'!");
|
||||
console.log("to create a new one: git remote add <name> " +
|
||||
"app@" + deets.ipAddress + ":git");
|
||||
} else {
|
||||
checkErr(err);
|
||||
console.log(" ... Instance ready, setting human readable name in aws");
|
||||
vm.setName(r.instanceId, longName, function(err) {
|
||||
checkErr(err);
|
||||
console.log(" ... name set, waiting for ssh access and configuring");
|
||||
var config = { public_url: (opts.u || "http://" + deets.ipAddress) };
|
||||
|
||||
if (opts.x) {
|
||||
console.log(" ... adding additional configuration values");
|
||||
var x = JSON.parse(fs.readFileSync(opts.x));
|
||||
Object.keys(x).forEach(function(key) {
|
||||
config[key] = x[key];
|
||||
});
|
||||
}
|
||||
console.log(" ... and your git remote is all set up");
|
||||
|
||||
if (awsboxJson.packages) {
|
||||
console.log(" ... finally, installing custom packages: " + awsboxJson.packages.join(', '));
|
||||
console.log("");
|
||||
}
|
||||
ssh.installPackages(deets.ipAddress, awsboxJson.packages, function(err, r) {
|
||||
console.log(" ... public url will be:", config.public_url);
|
||||
|
||||
ssh.copyUpConfig(deets.ipAddress, config, function(err, r) {
|
||||
checkErr(err);
|
||||
var postcreate = (awsboxJson.hooks && awsboxJson.hooks.postcreate) || null;
|
||||
ssh.runScript(deets.ipAddress, postcreate, 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);
|
||||
printInstructions(name, deets);
|
||||
});
|
||||
console.log(" ... victory! server is accessible and configured");
|
||||
git.addRemote(name, deets.ipAddress, function(err, r) {
|
||||
if (err && /already exists/.test(err)) {
|
||||
console.log("OOPS! you already have a git remote named '" + name + "'!");
|
||||
console.log("to create a new one: git remote add <name> " +
|
||||
"app@" + deets.ipAddress + ":git");
|
||||
} else {
|
||||
printInstructions(name, deets);
|
||||
checkErr(err);
|
||||
}
|
||||
console.log(" ... and your git remote is all set up");
|
||||
|
||||
if (awsboxJson.packages) {
|
||||
console.log(" ... finally, installing custom packages: " + awsboxJson.packages.join(', '));
|
||||
}
|
||||
ssh.installPackages(deets.ipAddress, awsboxJson.packages, function(err, r) {
|
||||
checkErr(err);
|
||||
var postcreate = (awsboxJson.hooks && awsboxJson.hooks.postcreate) || null;
|
||||
ssh.runScript(deets.ipAddress, postcreate, 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);
|
||||
printInstructions(name, dnsHost, opts.u, deets);
|
||||
});
|
||||
} else {
|
||||
printInstructions(name, dnsHost, opts.u, deets);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
75
lib/dns.js
75
lib/dns.js
|
@ -9,9 +9,7 @@ if (!process.env[envVar]) {
|
|||
+ envVar;
|
||||
}
|
||||
|
||||
const api_key = process.env[envVar];
|
||||
|
||||
function doRequest(method, path, body, cb) {
|
||||
function doRequest(api_key, method, path, body, cb) {
|
||||
var req = http.request({
|
||||
auth: 'lloyd@hilaiel.com:' + api_key,
|
||||
host: 'ns.zerigo.com',
|
||||
|
@ -23,15 +21,15 @@ function doRequest(method, path, body, cb) {
|
|||
'Content-Length': body ? body.length : 0
|
||||
}
|
||||
}, function(r) {
|
||||
if ((r.statusCode / 100).toFixed(0) != 2 &&
|
||||
r.statusCode != 404) {
|
||||
return cb("non 200 status: " + r.statusCode);
|
||||
}
|
||||
buf = "";
|
||||
var buf = "";
|
||||
r.on('data', function(chunk) {
|
||||
buf += chunk;
|
||||
});
|
||||
r.on('end', function() {
|
||||
if ((r.statusCode / 100).toFixed(0) != 2 &&
|
||||
r.statusCode != 404) {
|
||||
return cb("non 200 status: " + r.statusCode + "(" + body + ")");
|
||||
}
|
||||
xml2js.parseString(buf, cb);
|
||||
});
|
||||
});
|
||||
|
@ -39,31 +37,69 @@ function doRequest(method, path, body, cb) {
|
|||
req.end();
|
||||
};
|
||||
|
||||
exports.updateRecord = function (hostname, zone, ip, cb) {
|
||||
doRequest('GET', '/api/1.1/zones.xml', null, function(err, r) {
|
||||
exports.updateRecord = function (key, hostname, ip, cb) {
|
||||
if (!key || !hostname) return process.nextTick(function() { cb(null) });
|
||||
|
||||
doRequest(key, 'GET', '/api/1.1/zones.xml', null, function(err, r) {
|
||||
var host = "";
|
||||
if (err) return cb(err);
|
||||
var m = jsel.match('object:has(:root > .domain:val(?)) > .id .#',
|
||||
[ zone ], r);
|
||||
if (m.length != 1) return cb("couldn't extract domain id from zerigo");
|
||||
[ hostname ], r);
|
||||
if (m.length != 1) {
|
||||
host = hostname.split('.')[0];
|
||||
hostname = hostname.split('.').slice(1).join('.');
|
||||
var m = jsel.match('object:has(:root > .domain:val(?)) > .id .#',
|
||||
[ hostname ], r);
|
||||
if (m.length != 1) return cb("couldn't extract domain id from zerigo");
|
||||
}
|
||||
|
||||
var path = '/api/1.1/hosts.xml?zone_id=' + m[0];
|
||||
var body = '<host><data>' + ip + '</data><host-type>A</host-type>';
|
||||
body += '<hostname>' + hostname + '</hostname>'
|
||||
body += '<hostname>' + host + '</hostname>'
|
||||
body += '</host>';
|
||||
doRequest('POST', path, body, function(err, r) {
|
||||
doRequest(key, 'POST', path, body, function(err, r) {
|
||||
cb(err);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.deleteRecord = function (hostname, cb) {
|
||||
doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) {
|
||||
exports.findByIP = function (key, ip, cb) {
|
||||
if (!key) return cb(null, null);
|
||||
|
||||
doRequest(key, 'GET', '/api/1.1/zones.xml', null, function(err, r) {
|
||||
if (err) return cb(err);
|
||||
var zones = jsel.match('.id .#', r);
|
||||
|
||||
if (!zones.length) return cb(null, []);
|
||||
|
||||
var done = 0;
|
||||
var found = [];
|
||||
var failed;
|
||||
|
||||
zones.forEach(function(zone) {
|
||||
doRequest(key, 'GET', '/api/1.1/zones/' + zone + '/hosts.xml?per_page=1000', null, function(err, r) {
|
||||
if (failed) return;
|
||||
if (err) {
|
||||
failed = err;
|
||||
return cb(err);
|
||||
}
|
||||
var m = jsel.match('object:has(:root > .data:val(?)) > .fqdn', [ ip ], r);
|
||||
found = found.concat(m);
|
||||
if (++done === zones.length) cb(null, found);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.deleteRecord = function (key, hostname, cb) {
|
||||
doRequest(key, 'GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) {
|
||||
if (err) return cb(err);
|
||||
var m = jsel.match('.host .id > .#', r);
|
||||
if (!m.length) return cb("no such DNS record");
|
||||
function deleteOne() {
|
||||
if (!m.length) return cb(null);
|
||||
var one = m.shift();
|
||||
doRequest('DELETE', '/api/1.1/hosts/' + one + '.xml', null, function(err) {
|
||||
doRequest(key, 'DELETE', '/api/1.1/hosts/' + one + '.xml', null, function(err) {
|
||||
if (err) return cb(err);
|
||||
deleteOne();
|
||||
});
|
||||
|
@ -72,8 +108,9 @@ exports.deleteRecord = function (hostname, cb) {
|
|||
});
|
||||
};
|
||||
|
||||
exports.inUse = function (hostname, cb) {
|
||||
doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) {
|
||||
exports.inUse = function (key, hostname, cb) {
|
||||
if (!key || !hostname) return process.nextTick(function() { cb(null) });
|
||||
doRequest(key, 'GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) {
|
||||
if (err) return cb(err);
|
||||
var m = jsel.match('.host', r);
|
||||
// we shouldn't have multiple! oops! let's return the first one
|
||||
|
|
|
@ -93,12 +93,19 @@ function dateBasedVersion() {
|
|||
+ pad(d.getUTCMinutes());
|
||||
}
|
||||
|
||||
exports.createAMI = function(name, cb) {
|
||||
exports.find = function(name, cb) {
|
||||
exports.list(function(err, r) {
|
||||
if (err) return cb('failed to list vms: ' + err);
|
||||
|
||||
deets = findInstance(r, name);
|
||||
if (!deets) return cb('no such vm');
|
||||
cb(null, deets);
|
||||
});
|
||||
};
|
||||
|
||||
exports.createAMI = function(name, cb) {
|
||||
exports.find(name, function(err, deets) {
|
||||
if (err) return cb(err);
|
||||
|
||||
aws.call('CreateImage', {
|
||||
InstanceId: deets.instanceId,
|
||||
|
|
Загрузка…
Ссылка в новой задаче