preliminary multi-region support - remaining issue is how to reasonably create AMIs that are available in all regions - issue #21

This commit is contained in:
Lloyd Hilaiel 2012-10-23 09:19:39 -06:00
Родитель c69ee2fdcb
Коммит 75fd510bdd
5 изменённых файлов: 128 добавлений и 22 удалений

Просмотреть файл

@ -103,6 +103,22 @@ verbs['findByIP'] = function(args) {
});
};
verbs['zones'] = function(args) {
aws.zones(function(err, r) {
if (err) {
console.log("ERROR:", err);
proxess.exit(1);
}
Object.keys(r).forEach(function(region) {
console.log(region, "(" + r[region].endpoint + "):");
var zones = r[region].zones;
zones.forEach(function(zone) {
console.log(" *", zone.name, "(" + zone.state + ")");
});
});
});
};
verbs['create'] = function(args) {
var parser = optimist(args)
.usage('awsbox create: Create a VM')
@ -351,14 +367,23 @@ if (!error) {
var verb = process.argv[2];
if (!verbs[verb]) error = "no such command: " + verb;
else {
try {
verbs[verb](process.argv.slice(3));
} catch(e) {
error = "error running '" + verb + "' command: " + e;
}
// if there is a region supplied, then let's use it
aws.setRegion(process.env['AWS_REGION'], function(err, region) {
if (err) {
error = err;
} else {
if (region) console.log("(Using region", region.region + ")");
try {
verbs[verb](process.argv.slice(3));
} catch(e) {
error = "error running '" + verb + "' command: " + e;
}
}
});
}
}
if (error) {
if (typeof error === 'string') process.stderr.write('fatal error: ' + error + "\n\n");

Просмотреть файл

@ -1,7 +1,87 @@
const
awslib = require('aws-lib');
aws = require('aws-lib');
module.exports = awslib.createEC2Client(process.env['AWS_ID'], process.env['AWS_SECRET'], {
version: '2011-12-15'
});
function allocateClient(endpoint) {
return aws.createEC2Client(process.env['AWS_ID'], process.env['AWS_SECRET'], {
version: '2011-12-15',
host: endpoint
});
}
// parse a response blob from amazon, return a string error if that response
// is an error response, null otherwise
exports.getError = function(respBlob) {
var e;
try { e = respBlob.Errors.Error.Code + ": " + respBlob.Errors.Error.Message } catch(ex) {};
return e;
};
// client, by default, is set to amazon default. if setRegion is called,
// a new client will be allocated that will talk to a specific region's
// API endpoint.
exports.client = allocateClient(null);
// get an aws client instance for the specified region.
// if region is null, the default will be used.
exports.setRegion = function(region, cb) {
if (typeof region === 'function') {
cb = region;
region = null;
}
if (!region) return process.nextTick(cb);
// let's turn the region into an endpoint
exports.client.call('DescribeRegions', {}, function(r) {
if (exports.getError(r)) return cb(exports.getError(r));
var endpoint;
r.regionInfo.item.forEach(function(row) {
if (row.regionName === region) {
endpoint = row.regionEndpoint;
}
});
if (endpoint) {
exports.client = allocateClient(endpoint);
cb(null, { region: region, endpoint: endpoint });
} else {
cb("no such region: " + region);
}
});
};
exports.zones = function(cb) {
exports.client.call('DescribeRegions', {}, function(r) {
if (exports.getError(r)) return cb(exports.getError(r));
var regionMap = {};
var complete = 0;
r.regionInfo.item.forEach(function(row) {
regionMap[row.regionName] = {
endpoint: row.regionEndpoint,
zones: []
};
allocateClient(row.regionEndpoint).call('DescribeAvailabilityZones', {
"Filter.1.Name": "zone-name",
"Filter.1.Value.1": row.regionName + "*"
}, function(r) {
if (complete === -1) return;
complete++;
if (exports.getError(r)) {
complete = -1;
return cb(exports.getError(r));
}
if (r.availabilityZoneInfo && r.availabilityZoneInfo.item && r.availabilityZoneInfo.item.length) {
r.availabilityZoneInfo.item.forEach(function(zone) {
regionMap[row.regionName].zones.push({
name: zone.zoneName,
state: zone.zoneState
});
});
}
if (complete === Object.keys(regionMap).length) {
cb(null, regionMap);
}
});
});
});
};

Просмотреть файл

@ -30,13 +30,13 @@ exports.getName = function(cb) {
var keyName = "awsbox deploy key (" + fingerprint + ")";
// is this fingerprint known?
aws.call('DescribeKeyPairs', {}, function(result) {
aws.client.call('DescribeKeyPairs', {}, function(result) {
var found = jsel.match(":has(.keyName:val(?)) > .keyName", [ keyName ], result);
if (found.length) return cb(null, keyName);
// key isn't yet installed!
exports.read(function(err, key) {
aws.call('ImportKeyPair', {
aws.client.call('ImportKeyPair', {
KeyName: keyName,
PublicKeyMaterial: new Buffer(key).toString('base64')
}, function(result) {

Просмотреть файл

@ -17,18 +17,18 @@ exports.getName = function(cb) {
var groupName = "awsbox group v" + SECURITY_GROUP_VERSION;
// is this fingerprint known?
aws.call('DescribeSecurityGroups', {
aws.client.call('DescribeSecurityGroups', {
GroupName: groupName
}, function(r) {
if (jsel.match('.Code:val("InvalidGroup.NotFound")', r).length) {
aws.call('CreateSecurityGroup', {
aws.client.call('CreateSecurityGroup', {
GroupName: groupName,
GroupDescription: 'A security group for awsbox deployments'
}, function(r) {
if (!r || !r.return === 'true') {
return cb(createError('failed to create security group', r));
}
aws.call('AuthorizeSecurityGroupIngress', {
aws.client.call('AuthorizeSecurityGroupIngress', {
GroupName: groupName,
"IpPermissions.1.IpProtocol": 'tcp',
"IpPermissions.1.FromPort": 80,

Просмотреть файл

@ -7,6 +7,7 @@ sec = require('./sec.js');
const TEMPLATE_IMAGE_ID = 'ami-4c55ea25';
function extractInstanceDeets(horribleBlob) {
console.log(horribleBlob);
var instance = {};
["instanceId", "imageId", "instanceState", "dnsName", "keyName", "instanceType",
"ipAddress"].forEach(function(key) {
@ -40,7 +41,7 @@ exports.describe = function(name, cb) {
};
exports.list = function(cb) {
aws.call('DescribeInstances', {}, function(result) {
aws.client.call('DescribeInstances', {}, function(result) {
var instances = {};
var i = 1;
jsel.forEach(
@ -80,7 +81,7 @@ exports.destroy = function(name, cb) {
deets = findInstance(r, name);
if (!deets) return cb('no such vm');
aws.call('TerminateInstances', {
aws.client.call('TerminateInstances', {
InstanceId: deets.instanceId
}, function(result) {
try { return cb(result.Errors.Error.Message); } catch(e) {};
@ -113,7 +114,7 @@ exports.createAMI = function(name, cb) {
exports.find(name, function(err, deets) {
if (err) return cb(err);
aws.call('CreateImage', {
aws.client.call('CreateImage', {
InstanceId: deets.instanceId,
Name: "awsbox deployment image v" + dateBasedVersion(),
Description: "An image for use with awsbox.org, a DIY PaaS for noders"
@ -129,7 +130,7 @@ exports.makeAMIPublic = function(imageId, progress, cb) {
var startTime = new Date();
function attempt() {
aws.call('ModifyImageAttribute', {
aws.client.call('ModifyImageAttribute', {
ImageId: imageId,
'LaunchPermission.Add.1.Group': 'all'
}, function(result) {
@ -179,7 +180,7 @@ exports.startImage = function(opts, cb) {
if (err) return cb(err);
sec.getName(function(err, groupName) {
if (err) return cb(err);
aws.call('RunInstances', {
aws.client.call('RunInstances', {
ImageId: TEMPLATE_IMAGE_ID,
KeyName: keyName,
SecurityGroup: groupName,
@ -194,7 +195,7 @@ exports.startImage = function(opts, cb) {
};
exports.waitForInstance = function(id, cb) {
aws.call('DescribeInstanceStatus', {
aws.client.call('DescribeInstanceStatus', {
InstanceId: id
}, function(r) {
if (!r) return cb('no response from ec2');
@ -209,7 +210,7 @@ exports.waitForInstance = function(id, cb) {
if (Object.keys(r.instanceStatusSet).length) {
var deets = extractInstanceDeets(r.instanceStatusSet.item);
if (deets && deets.instanceState && deets.instanceState.name === 'running') {
return aws.call('DescribeInstances', { InstanceId: id }, function(result) {
return aws.client.call('DescribeInstances', { InstanceId: id }, function(result) {
returnSingleImageInfo(result, cb);
});
}
@ -219,7 +220,7 @@ exports.waitForInstance = function(id, cb) {
};
exports.setName = function(id, name, cb) {
aws.call('CreateTags', {
aws.client.call('CreateTags', {
"ResourceId.0": id,
"Tag.0.Key": 'Name',
"Tag.0.Value": name