Comparing version 0.6.4 to 0.6.5
@@ -9,2 +9,10 @@ # Upgrading | ||
**0.6.5** (Jan 2, 2015) | ||
- Rewrite `aws` command to use standard provider API. | ||
- Fix issue where EC2 instances are no longer accessible after a restart. | ||
- Add `aws regions` and `aws instances` commands. | ||
- To improve consistency across providers, `start` and `stop` commands are renamed to `boot` and `shutdown`, though `start` and `stop` are still quietly supported for backward compatibility. | ||
- Add --region option to `aws create` command, standardize to expect --image, though --ami is still quietly supported for backward compatibility. | ||
**0.6.4** (Jan 1, 2015) | ||
@@ -11,0 +19,0 @@ |
@@ -100,2 +100,3 @@ var fs = require('fs'); | ||
shortCircuit = true; | ||
return false; | ||
} | ||
@@ -102,0 +103,0 @@ } |
@@ -1,290 +0,135 @@ | ||
var fs = require('fs'); | ||
var readline = require('readline'); | ||
var _ = require('lodash'); | ||
var Promise = require('bluebird'); | ||
var utils = require('../utils'); | ||
var API = require('../providers/aws'); | ||
var filters = require('../filters'); | ||
var provider = require('../provider'); | ||
var api = require('../providers/aws'); | ||
exports.signatures = function () { | ||
return utils.printSignatures(subcommands); | ||
var DEFAULT_REGION = 'us-east-1'; | ||
var commands = {}; | ||
exports.commands = commands; | ||
commands.boot = { | ||
name: 'boot', | ||
usage: 'overcast aws boot [name]', | ||
description: 'Boot up an EC2 instance.', | ||
required: [ | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeAWS] } | ||
], | ||
async: true, | ||
run: function (args, next) { | ||
provider.boot(api, args, next); | ||
} | ||
}; | ||
exports.help = function () { | ||
utils.printArray([ | ||
'These commands require the following values set in .overcast/variables.json:', | ||
' AWS_KEY', | ||
' AWS_SECRET', | ||
'' | ||
]); | ||
commands.start = _.extend({ alias: true }, commands.boot); | ||
utils.printCommandHelp(subcommands); | ||
commands.create = { | ||
name: 'create', | ||
usage: 'overcast aws create [name] [options...]', | ||
description: [ | ||
'Creates a new EC2 instance.' | ||
], | ||
examples: [ | ||
'# Specified size:', | ||
'$ overcast aws create vm-01 --size m1.small --user ubuntu', | ||
'', | ||
'# Specified image and region (Ubuntu 14.04 64bit, EBS, us-west-2):', | ||
'$ overcast aws create vm-01 --region us-west-2 --image ami-978dd9a7 --user ubuntu', | ||
'', | ||
'# Enable root access:', | ||
'$ overcast aws create vm-02 --user ubuntu', | ||
'$ overcast run vm-02 allow_root_access_on_ec2', | ||
'$ overcast instance update vm-02 --user root' | ||
], | ||
required: [ | ||
{ name: 'name', filters: filters.shouldBeNewInstance } | ||
], | ||
options: [ | ||
{ usage: '--cluster CLUSTER', default: 'default' }, | ||
{ usage: '--image IMAGE', default: 'ami-64e27e0c (Ubuntu 14.04 64bit, EBS, us-east-1)' }, | ||
{ usage: '--monitoring', default: 'false' }, | ||
{ usage: '--region REGION', default: DEFAULT_REGION }, | ||
{ usage: '--size SIZE', default: 't1.micro' }, | ||
{ usage: '--ssh-key PATH', default: 'overcast.key' }, | ||
{ usage: '--ssh-pub-key PATH', default: 'overcast.key.pub' }, | ||
{ usage: '--user NAME', default: 'root' } | ||
], | ||
async: true, | ||
run: function (args, next) { | ||
provider.create(api, args, next); | ||
} | ||
}; | ||
exports.run = function (args) { | ||
utils.argShift(args, 'subcommand'); | ||
utils.runSubcommand(args, subcommands, exports.help); | ||
commands.destroy = { | ||
name: 'destroy', | ||
usage: 'overcast aws destroy [name] [options...]', | ||
description: [ | ||
'Destroys an EC2 instance.', | ||
'Using --force overrides the confirm dialog.' | ||
], | ||
examples: [ | ||
'$ overcast aws destroy vm-01' | ||
], | ||
required: [ | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeAWS] } | ||
], | ||
options: [ | ||
{ usage: '--force', default: 'false' } | ||
], | ||
async: true, | ||
run: function (args, next) { | ||
provider.destroy(api, args, next); | ||
} | ||
}; | ||
var subcommands = {}; | ||
commands.instances = { | ||
name: 'instances', | ||
usage: 'overcast aws instances [options...]', | ||
description: 'List all EC2 instances in your account.', | ||
options: [ | ||
{ usage: '--region REGION', default: DEFAULT_REGION } | ||
], | ||
async: true, | ||
run: function (args, next) { | ||
provider.instances(api, args, next); | ||
} | ||
}; | ||
subcommands.create = utils.module(function (exports) { | ||
exports.signature = 'overcast aws create [name] [options...]'; | ||
commands.reboot = { | ||
name: 'reboot', | ||
usage: 'overcast aws reboot [name]', | ||
description: 'Reboots an EC2 instance.', | ||
required: [ | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeAWS] } | ||
], | ||
async: true, | ||
run: function (args, next) { | ||
provider.reboot(api, args, next); | ||
} | ||
}; | ||
exports.help = function () { | ||
utils.printArray([ | ||
exports.signature, | ||
' Creates a new EC2 instance.'.grey, | ||
'', | ||
' Option | Default'.grey, | ||
' --cluster CLUSTER | default'.grey, | ||
' --ami NAME | ami-018c9568 (Ubuntu 14.04 LTS, 64bit, EBS)'.grey, | ||
' --size NAME | t1.micro'.grey, | ||
' --monitoring BOOLEAN | false'.grey, | ||
' --user NAME | root'.grey, | ||
(' --ssh-key KEY_PATH | overcast.key').grey, | ||
(' --ssh-pub-key KEY_PATH | overcast.key.pub').grey, | ||
'', | ||
' Example:'.grey, | ||
' $ overcast aws create db.01 --cluster db --size m1.small --user ubuntu'.grey | ||
]); | ||
}; | ||
commands.regions = { | ||
name: 'regions', | ||
usage: 'overcast aws regions', | ||
description: 'List all EC2 regions.', | ||
async: true, | ||
run: function (args, next) { | ||
provider.regions(api, next); | ||
} | ||
}; | ||
exports.run = function (args) { | ||
var clusters = utils.getClusters(); | ||
utils.argShift(args, 'name'); | ||
commands.shutdown = { | ||
name: 'shutdown', | ||
usage: 'overcast aws shutdown [name]', | ||
description: 'Shut down an EC2 instance.', | ||
required: [ | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeAWS] } | ||
], | ||
async: true, | ||
run: function (args, next) { | ||
provider.shutdown(api, args, next); | ||
} | ||
}; | ||
if (!args.cluster) { | ||
utils.grey('Using "default" cluster.'); | ||
args.cluster = 'default'; | ||
} | ||
if (!args.name) { | ||
return utils.missingParameter('[name]', exports.help); | ||
} else if (clusters[args.cluster] && clusters[args.cluster].instances[args.name]) { | ||
return utils.dieWithList('Instance "' + args.name + '" already exists.'); | ||
} | ||
if (args['ssh-pub-key']) { | ||
args.keyPath = args['ssh-pub-key']; | ||
} | ||
API.getKeys(args) | ||
.then(API.createKey) | ||
.then(API.createInstance) | ||
.then(function (args) { | ||
args.InstanceId = args.CreatedInstances[0].InstanceId; | ||
args.state = 'running'; | ||
return Promise.resolve(args); | ||
}) | ||
.then(API.waitForInstanceState) | ||
.then(API.getInstances) | ||
.catch(API.catch) | ||
.then(function (args) { | ||
var instance = { | ||
name: args.name, | ||
ip: args.Instances[0].PublicIpAddress, | ||
ssh_key: utils.normalizeKeyPath(args['ssh-key']), | ||
ssh_port: '22', | ||
user: args.user || 'root', | ||
aws: { | ||
id: args.InstanceId, | ||
public_dns_name: args.Instances[0].PublicDnsName, | ||
private_ip: args.Instances[0].PrivateIpAddress, | ||
private_dns_name: args.Instances[0].PrivateDnsName | ||
} | ||
}; | ||
utils.saveInstanceToCluster(args.cluster, instance); | ||
utils.success('New instance "' + instance.name + '" (' + instance.ip + ') created on EC2.'); | ||
utils.waitForBoot(instance); | ||
}); | ||
}; | ||
}); | ||
subcommands.destroy = utils.module(function (exports) { | ||
exports.signature = 'overcast aws destroy [name]'; | ||
exports.help = function () { | ||
utils.printArray([ | ||
exports.signature, | ||
' Destroys an EC2 instance.'.grey, | ||
'', | ||
' Option | Default'.grey, | ||
' --force | false'.grey, | ||
'', | ||
' Example:'.grey, | ||
' $ overcast aws destroy db.01'.grey | ||
]); | ||
}; | ||
exports.run = function (args) { | ||
var clusters = utils.getClusters(); | ||
utils.argShift(args, 'name'); | ||
if (!args.name) { | ||
return utils.missingParameter('[name]', exports.help); | ||
} | ||
var instance = utils.findFirstMatchingInstance(args.name); | ||
utils.handleInstanceNotFound(instance, args); | ||
if (!instance.aws || !instance.aws.id) { | ||
return utils.die('This instance has no EC2 id attached.'); | ||
} | ||
if (args.force) { | ||
return destroy(instance); | ||
} | ||
var rl = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout | ||
}); | ||
rl.question(('Do you really want to destroy instance "' + instance.name + '"? [Y/n]').yellow, function (answer) { | ||
rl.close(); | ||
if (answer === 'n' || answer === 'N') { | ||
utils.grey('No action taken.'); | ||
} else { | ||
destroy(instance); | ||
} | ||
}); | ||
function destroy(instance) { | ||
API.destroyInstance({ InstanceId: instance.aws.id }) | ||
.catch(API.catch) | ||
.then(function (args) { | ||
utils.success('Instance "' + instance.name + '" destroyed.'); | ||
utils.deleteInstance(instance); | ||
}); | ||
} | ||
}; | ||
}); | ||
subcommands.reboot = utils.module(function (exports) { | ||
exports.signature = 'overcast aws reboot [name]'; | ||
exports.help = function () { | ||
utils.printArray([ | ||
exports.signature, | ||
' Reboots an EC2 instance.'.grey, | ||
'', | ||
' Example:'.grey, | ||
' $ overcast aws reboot db.01'.grey | ||
]); | ||
}; | ||
exports.run = function (args) { | ||
var clusters = utils.getClusters(); | ||
utils.argShift(args, 'name'); | ||
if (!args.name) { | ||
return utils.missingParameter('[name]', exports.help); | ||
} | ||
var instance = utils.findFirstMatchingInstance(args.name); | ||
utils.handleInstanceNotFound(instance, args); | ||
if (!instance.aws || !instance.aws.id) { | ||
return utils.die('This instance has no EC2 id attached.'); | ||
} | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
state: 'running' | ||
}; | ||
API.rebootInstance(params) | ||
.then(API.waitForInstanceState) | ||
.catch(API.catch) | ||
.then(function (args) { | ||
utils.success('Instance rebooted.'); | ||
utils.waitForBoot(instance); | ||
}); | ||
}; | ||
}); | ||
subcommands.start = utils.module(function (exports) { | ||
exports.signature = 'overcast aws start [name]'; | ||
exports.help = function () { | ||
utils.printArray([ | ||
exports.signature, | ||
' Starts an EC2 instance.'.grey, | ||
'', | ||
' Example:'.grey, | ||
' $ overcast aws start db.01'.grey | ||
]); | ||
}; | ||
exports.run = function (args) { | ||
var clusters = utils.getClusters(); | ||
utils.argShift(args, 'name'); | ||
if (!args.name) { | ||
return utils.missingParameter('[name]', exports.help); | ||
} | ||
var instance = utils.findFirstMatchingInstance(args.name); | ||
utils.handleInstanceNotFound(instance, args); | ||
if (!instance.aws || !instance.aws.id) { | ||
return utils.die('This instance has no EC2 id attached.'); | ||
} | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
state: 'running' | ||
}; | ||
API.startInstance(params) | ||
.then(API.waitForInstanceState) | ||
.catch(API.catch) | ||
.then(function (args) { | ||
utils.success('Instance started.'); | ||
utils.waitForBoot(instance); | ||
}); | ||
}; | ||
}); | ||
subcommands.stop = utils.module(function (exports) { | ||
exports.signature = 'overcast aws stop [name]'; | ||
exports.help = function () { | ||
utils.printArray([ | ||
exports.signature, | ||
' Stop an EC2 instance.'.grey, | ||
'', | ||
' Example:'.grey, | ||
' $ overcast aws stop db.01'.grey | ||
]); | ||
}; | ||
exports.run = function (args) { | ||
var clusters = utils.getClusters(); | ||
utils.argShift(args, 'name'); | ||
if (!args.name) { | ||
return utils.missingParameter('[name]', exports.help); | ||
} | ||
var instance = utils.findFirstMatchingInstance(args.name); | ||
utils.handleInstanceNotFound(instance, args); | ||
if (!instance.aws || !instance.aws.id) { | ||
return utils.die('This instance has no EC2 id attached.'); | ||
} | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
state: 'stopped' | ||
}; | ||
API.stopInstance(params) | ||
.then(API.waitForInstanceState) | ||
.catch(API.catch) | ||
.then(function (args) { | ||
utils.success('Instance stopped.'); | ||
}); | ||
}; | ||
}); | ||
commands.stop = _.extend({ alias: true }, commands.shutdown); |
@@ -25,2 +25,6 @@ var utils = require('../utils'); | ||
args.command = 'aws'; | ||
args.subcommand = 'destroy'; | ||
args._.unshift(args.name); | ||
delete args.name; | ||
return cli.run(command.commands.destroy, args); | ||
} else if (instance.linode) { | ||
@@ -27,0 +31,0 @@ command = require('./linode.js'); |
@@ -15,3 +15,3 @@ var _ = require('lodash'); | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance } | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] } | ||
], | ||
@@ -51,4 +51,5 @@ async: true, | ||
], | ||
run: function (args) { | ||
provider.create(api, args); | ||
async: true, | ||
run: function (args, next) { | ||
provider.create(api, args, next); | ||
} | ||
@@ -68,3 +69,3 @@ }; | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance } | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] } | ||
], | ||
@@ -84,4 +85,5 @@ options: [ | ||
description: 'List all images, including snapshots.', | ||
run: function (args) { | ||
provider.images(api); | ||
async: true, | ||
run: function (args, next) { | ||
provider.images(api, next); | ||
} | ||
@@ -94,4 +96,5 @@ }; | ||
description: 'List all instances in your account.', | ||
run: function (args) { | ||
provider.instances(api); | ||
async: true, | ||
run: function (args, next) { | ||
provider.instances(api, args, next); | ||
} | ||
@@ -107,3 +110,3 @@ }; | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance } | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] } | ||
], | ||
@@ -120,4 +123,5 @@ async: true, | ||
description: 'List all available regions.', | ||
run: function (args) { | ||
provider.regions(api); | ||
async: true, | ||
run: function (args, next) { | ||
provider.regions(api, next); | ||
} | ||
@@ -141,7 +145,8 @@ }; | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance }, | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] }, | ||
{ name: 'image' } | ||
], | ||
run: function (args) { | ||
provider.rebuild(api, args); | ||
async: true, | ||
run: function (args, next) { | ||
provider.rebuild(api, args, next); | ||
} | ||
@@ -163,3 +168,3 @@ }; | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance }, | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] }, | ||
{ name: 'size' } | ||
@@ -170,4 +175,5 @@ ], | ||
], | ||
run: function (args) { | ||
provider.resize(api, args); | ||
async: true, | ||
run: function (args, next) { | ||
provider.resize(api, args, next); | ||
} | ||
@@ -182,7 +188,8 @@ }; | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance }, | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] }, | ||
{ name: 'snapshot-name', varName: 'snapshotName' } | ||
], | ||
run: function (args) { | ||
provider.snapshot(api, args); | ||
async: true, | ||
run: function (args, next) { | ||
provider.snapshot(api, args, next); | ||
} | ||
@@ -195,4 +202,5 @@ }; | ||
description: 'List all available snapshots in your account.', | ||
run: function (args) { | ||
provider.snapshots(api); | ||
async: true, | ||
run: function (args, next) { | ||
provider.snapshots(api, next); | ||
} | ||
@@ -206,3 +214,3 @@ }; | ||
required: [ | ||
{ name: 'name', filters: filters.findFirstMatchingInstance } | ||
{ name: 'name', filters: [filters.findFirstMatchingInstance, filters.shouldBeDigitalOcean] } | ||
], | ||
@@ -219,4 +227,5 @@ async: true, | ||
description: 'List all available instance sizes.', | ||
run: function (args) { | ||
provider.sizes(api); | ||
async: true, | ||
run: function (args, next) { | ||
provider.sizes(api, next); | ||
} | ||
@@ -223,0 +232,0 @@ }; |
@@ -8,9 +8,7 @@ var _ = require('lodash'); | ||
var API = { | ||
AWS: require('../providers/aws.js'), | ||
DigitalOcean: require('../providers/digitalocean.js'), | ||
AWS: require('./aws.js'), | ||
DigitalOcean: require('./digitalocean.js'), | ||
Linode: require('../providers/linode.js') | ||
}; | ||
var digitalocean = require('./digitalocean.js'); | ||
exports.run = function (args) { | ||
@@ -42,3 +40,3 @@ utils.argShift(args, 'name'); | ||
delete args.name; | ||
cli.run(digitalocean.commands.reboot, args, resolve); | ||
cli.run(API.DigitalOcean.commands.reboot, args, resolve); | ||
}); | ||
@@ -53,8 +51,7 @@ } else if (instance.linode && instance.linode.id) { | ||
addPromise(function (resolve) { | ||
API.AWS.rebootInstance({ InstanceId: instance.aws.id, state: 'running' }) | ||
.then(API.AWS.waitForInstanceState) | ||
.catch(API.AWS.catch) | ||
.then(function (args) { | ||
utils.waitForBoot(instance, resolve); | ||
}); | ||
args.command = 'aws'; | ||
args.subcommand = 'reboot'; | ||
args._.unshift(args.name); | ||
delete args.name; | ||
cli.run(API.AWS.commands.reboot, args, resolve); | ||
}); | ||
@@ -61,0 +58,0 @@ } else { |
@@ -72,1 +72,17 @@ var utils = require('./utils'); | ||
}; | ||
exports.shouldBeAWS = function (name, args) { | ||
if (!args.instance || !args.instance.aws) { | ||
utils.die('This instance has no AWS metadata attached.'); | ||
return false; | ||
} | ||
}; | ||
exports.shouldBeDigitalOcean = function (name, args) { | ||
if (!args.instance || !args.instance.digitalocean) { | ||
utils.red('This instance has no DigitalOcean metadata attached.'); | ||
utils.red('Run this command and then try again:'); | ||
utils.die('overcast digitalocean sync "' + args.instance.name + '"'); | ||
return false; | ||
} | ||
}; |
@@ -136,6 +136,7 @@ var _ = require('lodash'); | ||
// AKA droplets (DO) or linodes (Linode). | ||
exports.instances = function (api, callback) { | ||
exports.instances = function (api, args, callback) { | ||
exports.handleCommandNotFound(api.getInstances); | ||
api.getInstances(function (instances) { | ||
// AWS needs args.region, DigitalOcean does not. | ||
api.getInstances(args, function (instances) { | ||
utils.printCollection('instances', instances); | ||
@@ -142,0 +143,0 @@ if (_.isFunction(callback)) { |
@@ -9,4 +9,173 @@ var fs = require('fs'); | ||
// http://cloud-images.ubuntu.com/releases/14.04/release/ | ||
var DEFAULT_AMI = 'ami-64e27e0c'; // Ubuntu 14.04 64 bit, EBS | ||
var DEFAULT_REGION = 'us-east-1'; | ||
var DEFAULT_SIZE = 't1.micro'; | ||
exports.id = 'aws'; | ||
exports.name = 'AWS'; | ||
exports.pollDelay = 3000; | ||
// Provider interface | ||
exports.boot = function (instance, callback) { | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
region: instance.aws.region, | ||
state: 'running' | ||
}; | ||
exports.startInstance(params) | ||
.then(exports.waitForInstanceState) | ||
.then(exports.getFilteredInstances) | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
utils.updateInstance(instance.name, { | ||
ip: args.Instances[0].PublicIpAddress, | ||
aws: { | ||
id: args.InstanceId, | ||
size: args.Instances[0].InstanceType, | ||
image: args.Instances[0].ImageId, | ||
region: instance.aws.region, | ||
monitoring: args.Instances[0].Monitoring.State, | ||
public_dns_name: args.Instances[0].PublicDnsName, | ||
private_ip: args.Instances[0].PrivateIpAddress, | ||
private_dns_name: args.Instances[0].PrivateDnsName | ||
} | ||
}, callback); | ||
}); | ||
}; | ||
exports.create = function (args, callback) { | ||
if (args['ssh-pub-key']) { | ||
args.keyPath = args['ssh-pub-key']; | ||
} | ||
exports.getKeys(args) | ||
.then(exports.createKey) | ||
.then(exports.createInstance) | ||
.then(function (args) { | ||
args.InstanceId = args.CreatedInstances[0].InstanceId; | ||
args.state = 'running'; | ||
return Promise.resolve(args); | ||
}) | ||
.then(exports.waitForInstanceState) | ||
.then(exports.getFilteredInstances) | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
var instance = { | ||
name: args.name, | ||
ip: args.Instances[0].PublicIpAddress, | ||
ssh_key: utils.normalizeKeyPath(args['ssh-key']), | ||
ssh_port: '22', | ||
user: args.user || 'root', | ||
aws: { | ||
id: args.InstanceId, | ||
size: args.Instances[0].InstanceType, | ||
image: args.Instances[0].ImageId, | ||
monitoring: args.Instances[0].Monitoring.State, | ||
region: args.region || DEFAULT_REGION, | ||
public_dns_name: args.Instances[0].PublicDnsName, | ||
private_ip: args.Instances[0].PrivateIpAddress, | ||
private_dns_name: args.Instances[0].PrivateDnsName | ||
} | ||
}; | ||
if (_.isFunction(callback)) { | ||
callback(instance); | ||
} | ||
}); | ||
}; | ||
exports.destroy = function (instance, callback) { | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
region: instance.aws.region | ||
}; | ||
exports.destroyInstance(params) | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
if (_.isFunction(callback)) { | ||
callback(); | ||
} | ||
}); | ||
}; | ||
exports.shutdown = function (instance, callback) { | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
region: instance.aws.region, | ||
state: 'stopped' | ||
}; | ||
exports.stopInstance(params) | ||
.then(exports.waitForInstanceState) | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
if (_.isFunction(callback)) { | ||
callback(); | ||
} | ||
}); | ||
}; | ||
exports.reboot = function (instance, callback) { | ||
var params = { | ||
InstanceId: instance.aws.id, | ||
region: instance.aws.region, | ||
state: 'running' | ||
}; | ||
exports.rebootInstance(params) | ||
.then(exports.waitForInstanceState) | ||
.then(exports.getFilteredInstances) | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
utils.updateInstance(instance.name, { | ||
ip: args.Instances[0].PublicIpAddress, | ||
aws: { | ||
id: args.InstanceId, | ||
size: args.Instances[0].InstanceType, | ||
image: args.Instances[0].ImageId, | ||
monitoring: args.Instances[0].Monitoring.State, | ||
region: instance.aws.region, | ||
public_dns_name: args.Instances[0].PublicDnsName, | ||
private_ip: args.Instances[0].PrivateIpAddress, | ||
private_dns_name: args.Instances[0].PrivateDnsName | ||
} | ||
}, callback); | ||
}); | ||
}; | ||
exports.getRegions = function (callback) { | ||
exports.describeRegions() | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
_.each(args.Regions, function (region) { | ||
region._name = region.RegionName; | ||
}); | ||
if (_.isFunction(callback)) { | ||
callback(args.Regions); | ||
} | ||
}); | ||
}; | ||
exports.getInstances = function (args, callback) { | ||
exports.getFilteredInstances(args) | ||
.catch(exports.catch) | ||
.then(function (args) { | ||
_.each(args.Instances, function (instance) { | ||
instance._name = instance.InstanceId; | ||
}); | ||
if (_.isFunction(callback)) { | ||
callback(args.Instances); | ||
} | ||
}); | ||
}; | ||
// Internal functions | ||
function debugLog(data) { | ||
@@ -18,3 +187,3 @@ if (DEBUG) { | ||
var ec2 = function () { | ||
var ec2 = function (args) { | ||
if (exports.ec2) { | ||
@@ -33,9 +202,6 @@ return exports.ec2; | ||
accessKeyId: vars.AWS_KEY, | ||
secretAccessKey: vars.AWS_SECRET | ||
secretAccessKey: vars.AWS_SECRET, | ||
region: args.region || DEFAULT_REGION | ||
}); | ||
AWS.config.update({ | ||
region: 'us-east-1' | ||
}); | ||
exports.ec2 = new AWS.EC2(); | ||
@@ -45,2 +211,3 @@ return exports.ec2; | ||
// TODO: Get setRegion working. Seems like the endpoint needs to be updated as well. | ||
exports.setRegion = function (args) { | ||
@@ -50,3 +217,5 @@ args = args || {}; | ||
return new Promise(function (resolve) { | ||
AWS.config.update({ region: args.region || 'us-east-1' }); | ||
AWS.config.update({ | ||
region: args.region || DEFAULT_REGION | ||
}); | ||
resolve(args); | ||
@@ -75,3 +244,3 @@ }); | ||
ec2().importKeyPair(params, function (err, data) { | ||
ec2(args).importKeyPair(params, function (err, data) { | ||
if (err) { | ||
@@ -92,3 +261,3 @@ reject(err); | ||
return new Promise(function (resolve, reject) { | ||
ec2().describeKeyPairs({}, function (err, data) { | ||
ec2(args).describeKeyPairs({}, function (err, data) { | ||
if (err) { | ||
@@ -105,7 +274,7 @@ reject(err); | ||
exports.getRegions = function (args) { | ||
exports.describeRegions = function (args) { | ||
args = args || {}; | ||
return new Promise(function (resolve, reject) { | ||
ec2().describeRegions({}, function (err, data) { | ||
ec2(args).describeRegions({}, function (err, data) { | ||
if (err) { | ||
@@ -126,3 +295,3 @@ reject(err); | ||
return new Promise(function (resolve, reject) { | ||
ec2().describeImages({ | ||
ec2(args).describeImages({ | ||
ImageIds: args.ImageIds || [], | ||
@@ -143,9 +312,9 @@ Owners: args.Owners || [], | ||
exports.getInstances = function (args) { | ||
exports.getFilteredInstances = function (args) { | ||
args = args || {}; | ||
return new Promise(function (resolve, reject) { | ||
ec2().describeInstances({ | ||
ec2(args).describeInstances({ | ||
Filters: args.Filters || [], | ||
InstanceIds: args.InstanceIds || [args.InstanceId] | ||
InstanceIds: args.InstanceIds || args.InstanceId ? [args.InstanceId] : [] | ||
}, function (err, data) { | ||
@@ -156,3 +325,7 @@ if (err) { | ||
debugLog(data); | ||
args.Instances = data.Reservations[0].Instances; | ||
if (data.Reservations[0]) { | ||
args.Instances = data.Reservations[0].Instances; | ||
} else { | ||
args.Instances = []; | ||
} | ||
resolve(args); | ||
@@ -168,4 +341,2 @@ } | ||
utils.grey('Waiting for instance state "' + args.state + '"...'); | ||
function whileFn() { | ||
@@ -178,3 +349,3 @@ return !(state.Code === args.state || state.Name === args.state); | ||
.then(function () { | ||
return exports.getInstances(args); | ||
return exports.getFilteredInstances(args); | ||
}).then(function (args) { | ||
@@ -187,3 +358,2 @@ state = args.Instances[0].State; | ||
return utils.promiseWhile(whileFn, checkState).then(function () { | ||
utils.success('Done.'); | ||
return Promise.resolve(args); | ||
@@ -198,6 +368,5 @@ }); | ||
// http://cloud-images.ubuntu.com/releases/14.04/release/ | ||
var params = { | ||
ImageId: args.ami || 'ami-018c9568', // Ubuntu 14.04 LTS, 64 bit, EBS | ||
InstanceType: args.size || 't1.micro', | ||
ImageId: args.image || args.ami || DEFAULT_AMI, | ||
InstanceType: args.size || DEFAULT_SIZE, | ||
KeyName: utils.createHashedKeyName(keyData), | ||
@@ -207,3 +376,3 @@ MinCount: 1, | ||
Monitoring: { | ||
Enabled: !!args.monitoring | ||
Enabled: utils.argIsTruthy(args.monitoring) | ||
} | ||
@@ -213,3 +382,3 @@ }; | ||
return new Promise(function (resolve, reject) { | ||
ec2().runInstances(params, function (err, data) { | ||
ec2(args).runInstances(params, function (err, data) { | ||
if (err) { | ||
@@ -220,3 +389,2 @@ reject(err); | ||
args.CreatedInstances = data.Instances; | ||
utils.success('Instance ' + args.CreatedInstances[0].InstanceId + ' created.'); | ||
resolve(args); | ||
@@ -235,3 +403,3 @@ } | ||
return new Promise(function (resolve, reject) { | ||
ec2().terminateInstances(params, function (err, data) { | ||
ec2(args).terminateInstances(params, function (err, data) { | ||
if (err) { | ||
@@ -254,3 +422,3 @@ reject(err); | ||
return new Promise(function (resolve, reject) { | ||
ec2().rebootInstances(params, function (err, data) { | ||
ec2(args).rebootInstances(params, function (err, data) { | ||
if (err) { | ||
@@ -273,3 +441,3 @@ reject(err); | ||
return new Promise(function (resolve, reject) { | ||
ec2().stopInstances(params, function (err, data) { | ||
ec2(args).stopInstances(params, function (err, data) { | ||
if (err) { | ||
@@ -292,3 +460,3 @@ reject(err); | ||
return new Promise(function (resolve, reject) { | ||
ec2().startInstances(params, function (err, data) { | ||
ec2(args).startInstances(params, function (err, data) { | ||
if (err) { | ||
@@ -295,0 +463,0 @@ reject(err); |
@@ -65,6 +65,2 @@ var fs = require('fs'); | ||
exports.destroy = function (instance, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.request({ | ||
@@ -78,6 +74,2 @@ endpoint: 'droplets/' + instance.digitalocean.id + '/destroy', | ||
exports.boot = function (instance, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.eventedRequest({ | ||
@@ -90,6 +82,2 @@ endpoint: 'droplets/' + instance.digitalocean.id + '/power_on', | ||
exports.shutdown = function (instance, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.eventedRequest({ | ||
@@ -102,6 +90,2 @@ endpoint: 'droplets/' + instance.digitalocean.id + '/power_off', | ||
exports.snapshot = function (instance, snapshotName, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.shutdown(instance, function () { | ||
@@ -117,6 +101,2 @@ exports.eventedRequest({ | ||
exports.reboot = function (instance, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.eventedRequest({ | ||
@@ -129,6 +109,2 @@ endpoint: 'droplets/' + instance.digitalocean.id + '/reboot', | ||
exports.rebuild = function (instance, image, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.getImages(function (images) { | ||
@@ -149,6 +125,2 @@ var match = getMatching(images, image); | ||
exports.resize = function (instance, size, callback) { | ||
if (exports.handleMetadataNotFound(instance)) { | ||
return false; | ||
} | ||
exports.getSizes(function (sizes) { | ||
@@ -219,3 +191,3 @@ var match = getMatching(sizes, size); | ||
exports.getInstances = function (callback) { | ||
exports.getInstances = function (args, callback) { | ||
exports.request({ | ||
@@ -232,3 +204,2 @@ endpoint: 'droplets', | ||
exports.getInstance = function (instance, callback) { | ||
// Intentionally not guarding against missing metadata here | ||
// exports.create passes in an id, since instance doesn't exist yet. | ||
@@ -249,4 +220,2 @@ var id = _.isPlainObject(instance) && instance.digitalocean && instance.digitalocean.id ? | ||
exports.updateInstanceMetadata = function (instance, callback) { | ||
// Intentionally not guarding against missing metadata here | ||
// user might be trying to sync instance metadata. | ||
exports.getInstance(instance, function (droplet) { | ||
@@ -287,4 +256,2 @@ utils.updateInstance(instance.name, { | ||
exports.sync = function (instance, callback) { | ||
// Intentionally not guarding against missing metadata here. | ||
exports.getInstances(function (instances) { | ||
@@ -324,11 +291,2 @@ var match = utils.findUsingMultipleKeys(instances, instance.name, ['name']); | ||
exports.handleMetadataNotFound = function (instance) { | ||
if (!instance || !instance.digitalocean) { | ||
utils.red('This instance has no DigitalOcean metadata attached.'); | ||
utils.red('Run this command and then try again:'); | ||
utils.die('overcast digitalocean sync "' + instance.name + '"'); | ||
return true; | ||
} | ||
}; | ||
exports.getOrCreateOvercastKeyID = function (pubKeyPath, callback) { | ||
@@ -335,0 +293,0 @@ var keyData = fs.readFileSync(pubKeyPath, 'utf8') + ''; |
@@ -10,3 +10,3 @@ var fs = require('fs'); | ||
exports.VERSION = '0.6.4'; | ||
exports.VERSION = '0.6.5'; | ||
@@ -335,3 +335,3 @@ exports.clustersCache = null; | ||
exports.updateInstance = function (name, updates) { | ||
exports.updateInstance = function (name, updates, callback) { | ||
var clusters = exports.getClusters(); | ||
@@ -346,3 +346,3 @@ _.each(clusters, function (cluster, clusterName) { | ||
exports.saveClusters(clusters); | ||
exports.saveClusters(clusters, callback); | ||
}; | ||
@@ -683,5 +683,6 @@ | ||
_.each(collection, function (obj) { | ||
var name = obj.name || obj.Name || obj._name; | ||
console.log(''); | ||
console.log(' ' + obj.name); | ||
exports.prettyPrint(obj, 4); | ||
console.log(name); | ||
exports.prettyPrint(obj, 2); | ||
}); | ||
@@ -696,2 +697,6 @@ }; | ||
_.each(obj, function (val, key) { | ||
if (key === '_name') { | ||
return; | ||
} | ||
if (_.isArray(val) || _.isPlainObject(val)) { | ||
@@ -698,0 +703,0 @@ exports.grey(prefix + key + ':'); |
{ | ||
"name": "overcast", | ||
"description": "A simple, SSH-based cloud management CLI.", | ||
"version": "0.6.4", | ||
"description": "A simple command line program designed to make it easy to spin up, configure, and interact with any number of machines over SSH.", | ||
"version": "0.6.5", | ||
"repository": "https://github.com/andrewchilds/overcast.git", | ||
@@ -6,0 +6,0 @@ "author": { |
151
README.md
@@ -180,52 +180,104 @@ # ![Overcast Logo](http://i.imgur.com/eCBl2NI.png) | ||
### overcast aws | ||
### overcast aws boot | ||
``` | ||
These commands require the following values set in .overcast/variables.json: | ||
AWS_KEY | ||
AWS_SECRET | ||
Usage: | ||
overcast aws boot [name] | ||
Description: | ||
Boot up an EC2 instance. | ||
``` | ||
### overcast aws create | ||
``` | ||
Usage: | ||
overcast aws create [name] [options...] | ||
Creates a new EC2 instance. | ||
Option | Default | ||
--cluster CLUSTER | default | ||
--ami NAME | ami-018c9568 (Ubuntu 14.04 LTS, 64bit, EBS) | ||
--size NAME | t1.micro | ||
--monitoring BOOLEAN | false | ||
--user NAME | root | ||
--ssh-key KEY_PATH | overcast.key | ||
--ssh-pub-key KEY_PATH | overcast.key.pub | ||
Description: | ||
Creates a new EC2 instance. | ||
Example: | ||
$ overcast aws create db.01 --cluster db --size m1.small --user ubuntu | ||
Options: Defaults: | ||
--cluster CLUSTER default | ||
--image IMAGE ami-64e27e0c (Ubuntu 14.04 64bit, EBS, us-east-1) | ||
--monitoring false | ||
--region REGION us-east-1 | ||
--size SIZE t1.micro | ||
--ssh-key PATH overcast.key | ||
--ssh-pub-key PATH overcast.key.pub | ||
--user NAME root | ||
overcast aws destroy [name] | ||
Destroys an EC2 instance. | ||
Examples: | ||
# Specified size: | ||
$ overcast aws create vm-01 --size m1.small --user ubuntu | ||
Option | Default | ||
--force | false | ||
# Specified image and region (Ubuntu 14.04 64bit, EBS, us-west-2): | ||
$ overcast aws create vm-01 --region us-west-2 --image ami-978dd9a7 --user ubuntu | ||
Example: | ||
$ overcast aws destroy db.01 | ||
# Enable root access: | ||
$ overcast aws create vm-02 --user ubuntu | ||
$ overcast run vm-02 allow_root_access_on_ec2 | ||
$ overcast instance update vm-02 --user root | ||
``` | ||
### overcast aws destroy | ||
``` | ||
Usage: | ||
overcast aws destroy [name] [options...] | ||
Description: | ||
Destroys an EC2 instance. | ||
Using --force overrides the confirm dialog. | ||
Options: Defaults: | ||
--force false | ||
Examples: | ||
$ overcast aws destroy vm-01 | ||
``` | ||
### overcast aws instances | ||
``` | ||
Usage: | ||
overcast aws instances [options...] | ||
Description: | ||
List all EC2 instances in your account. | ||
Options: Defaults: | ||
--region REGION us-east-1 | ||
``` | ||
### overcast aws reboot | ||
``` | ||
Usage: | ||
overcast aws reboot [name] | ||
Reboots an EC2 instance. | ||
Example: | ||
$ overcast aws reboot db.01 | ||
Description: | ||
Reboots an EC2 instance. | ||
``` | ||
overcast aws start [name] | ||
Starts an EC2 instance. | ||
### overcast aws regions | ||
Example: | ||
$ overcast aws start db.01 | ||
``` | ||
Usage: | ||
overcast aws regions | ||
overcast aws stop [name] | ||
Stop an EC2 instance. | ||
Description: | ||
List all EC2 regions. | ||
``` | ||
Example: | ||
$ overcast aws stop db.01 | ||
### overcast aws shutdown | ||
``` | ||
Usage: | ||
overcast aws shutdown [name] | ||
Description: | ||
Shut down an EC2 instance. | ||
``` | ||
### overcast cluster count | ||
@@ -552,2 +604,17 @@ | ||
### overcast get | ||
``` | ||
Usage: | ||
overcast get [instance|cluster|all] [attr...] [options...] | ||
Description: | ||
Returns the attribute(s) for the instance or cluster, one per line, | ||
or space-delimited using the --single-line option. | ||
"origin" is a compound attribute that returns user@ip:ssh-port. | ||
Options: Defaults: | ||
--single-line, -s false | ||
``` | ||
### overcast health | ||
@@ -603,3 +670,3 @@ | ||
``` | ||
Overcast v0.6.4 | ||
Overcast v0.6.5 | ||
@@ -619,4 +686,5 @@ Source code, issues, pull requests: | ||
aliases aws cluster completions destroy digitalocean expose exposed | ||
health import info init instance key linode list ping port pull push | ||
reboot remove run scriptvar slack ssh tunnel var virtualbox wait | ||
get health import info init instance key linode list ping port pull | ||
push reboot remove run scriptvar slack ssh tunnel var virtualbox | ||
wait | ||
@@ -671,10 +739,15 @@ Config directory: | ||
Usage: | ||
overcast instance get [instance|cluster|all] [attr...] | ||
overcast instance get [instance|cluster|all] [attr...] [options...] | ||
Description: | ||
Returns the attribute(s) for the instance or cluster, one per line. | ||
Returns the attribute(s) for the instance or cluster, one per line, | ||
or space-delimited using the --single-line option. | ||
"origin" is a compound attribute that returns user@ip:ssh-port. | ||
Options: Defaults: | ||
--single-line, -s false | ||
Examples: | ||
$ overcast instance get app-01 ssh-port | ||
22 | ||
$ overcast instance get app-01 origin | ||
root@1.2.3.4:22 | ||
@@ -681,0 +754,0 @@ $ overcast instance get app-cluster ip |
Sorry, the diff of this file is not supported yet
309144
5430
1364
27