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:
Родитель
c69ee2fdcb
Коммит
75fd510bdd
35
awsbox.js
35
awsbox.js
|
@ -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");
|
||||
|
||||
|
|
88
lib/aws.js
88
lib/aws.js
|
@ -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,
|
||||
|
|
17
lib/vm.js
17
lib/vm.js
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче