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