├── .gitignore ├── README.md ├── docs └── pseudo.js ├── lib ├── base │ ├── collection.js │ ├── joinedCollection.js │ ├── loadableCollection.js │ ├── loadableModel.js │ └── model.js ├── chain │ └── index.js ├── cloudfront │ └── .gitkeep ├── decorators │ ├── decorator.js │ └── index.js ├── ec2 │ ├── base │ │ ├── collection.js │ │ └── model.js │ ├── index.js │ ├── regions │ │ ├── addresses │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── base │ │ │ ├── collection.js │ │ │ └── model.js │ │ ├── images │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── index.js │ │ ├── instances │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── keyPairs │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── model.js │ │ ├── securityGroups │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── snapshots │ │ │ ├── index.js │ │ │ └── model.js │ │ ├── tags │ │ │ └── index.js │ │ ├── volumes │ │ │ ├── index.js │ │ │ └── model.js │ │ └── zones │ │ │ └── index.js │ └── utils │ │ └── index.js ├── index.js ├── route53 │ ├── hostedZones │ │ ├── index.js │ │ ├── model.js │ │ └── recordSets │ │ │ ├── index.js │ │ │ └── model.js │ └── index.js └── utils │ └── logger.js ├── makefile ├── package.json ├── scripts └── flushAWSAccount.sh └── test ├── ec2 ├── addresses │ └── basic-test.js ├── core │ └── basic-test.js ├── images │ └── basic-test.js ├── instances │ ├── basic-test.js │ ├── securityGroup-test.js │ ├── update-test.js │ └── volumes-test.js ├── keyPairs │ └── basic-test.js ├── regions │ └── basic-test.js ├── securityGroups │ └── basic-test.js ├── snapshots │ └── basic-test.js ├── volumes │ └── basic-test.js └── zones │ └── basic-test.js └── helpers └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | test/helpers/config.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | *Note: This project is currently alpha-ish.* 2 | 3 | ### Awsm gives you a mongodb-like interface for controlling AWS. 4 | 5 | Here's how: 6 | 7 | ```javascript 8 | var aws = require("awsm")(require("./awsConfig")).chain(); 9 | 10 | // let's go ahead and provision a few ubuntu instances around the globe. 11 | aws.regions().all().createInstance({ type: "m3.medium", imageId: "ami-a73264ce" }).start().then(function (err, instances) { 12 | console.log(instances); 13 | }); 14 | 15 | ``` 16 | 17 | How about migrating a pre-existing image? Easy: 18 | 19 | ```javascript 20 | aws.regions().all(function (err, regions) { 21 | aws.images().find({ "tags.type": "my-awesome-application" }).migrate(regions).then(function (err, images) { 22 | 23 | // should be roughly ~ 7 images that have been migrated to every 24 | // AWS region. 25 | console.log(images); 26 | }); 27 | }); 28 | ``` 29 | 30 | Awsm is also extendable. Want to add SSH? No problem: 31 | 32 | ```javascript 33 | var aws = require("awsm")(require("./awsConfig")); 34 | aws.use(require("awsm-ssh"); 35 | var awsc = aws.chain(); 36 | 37 | awsc. 38 | instances(). 39 | 40 | // find all servers in the staging environment 41 | find({ "tags.env": "staging", "tags.type": "my-mega-awesome-application" }). 42 | 43 | // let's copy some local files to all mega awesome applications. 44 | rsync("~/Developer/applications/my-mega-awesome-application", "/remote/app/directory"). 45 | 46 | parallel(). 47 | 48 | // let's start this sucker. 49 | exec("node /remote/app/directory"). 50 | 51 | // after the process closes - this will never happen since exec (above) won't close. Have 52 | // you taken a look at node-awsm-cli? 53 | then(function (err, instances) { 54 | // donezo. 55 | }); 56 | ``` 57 | 58 | Want a command line interface? This is not the repository you're looking for. Checkout [awsm-cli](https://github.com/crcn/node-awsm-cli). 59 | 60 | 61 | ## Plugins 62 | 63 | - [awsm-keypair-save](https://github.com/crcn/node-awsm-keypair-save) - allows you to save keypairs locally immediately after creating them. 64 | - [awsm-ssh](https://github.com/crcn/node-awsm-ssh) - super nice utility that allows you to ssh, execute scripts, and rsync files on your EC2 instances. 65 | 66 | 67 | 68 | ## Node API 69 | 70 | #### awsm awsm(config) 71 | 72 | Initializes the awsm library. 73 | 74 | - `key` - aws key 75 | - `secret` - aws secret 76 | - `ec2` - the ec2 config 77 | - `regions` (array) - regions to use (["us-east-1", "us-west-1", ...]) 78 | - `log` - log config 79 | - `level` - the log level to use - `notice`, `verbose`, `warn`, `error` 80 | 81 | 82 | ## Resource Collections 83 | 84 | Resource collections share a common API, and are used for every object type which includes `regions`, `instances`, `images`, `securityGroups`, and `keyPairs`. 85 | 86 | #### collection.find(query, callback) 87 | 88 | Finds many resources in the target collection. 89 | 90 | ```javascript 91 | 92 | // find all U.S. regions 93 | awsm.ec2.regions.find({ name: /^us/ }, onUsRegions); 94 | 95 | // find all running instances in a given region 96 | region.instances.find({ state: "running" }, onAllRunningInstances); 97 | ``` 98 | 99 | #### collection.findOne(query, callback) 100 | 101 | Finds one resource against a target collection. 102 | 103 | ```javascript 104 | 105 | // find an image with the given name 106 | region.images.findOne({ name: "some-image-name" }, onImage); 107 | ``` 108 | 109 | #### Available Collections 110 | 111 | Below are a list of available collections to search against 112 | 113 | - `awsm.ec2.instances` - `all` instances across `all` regions. 114 | - `awsm.ec2.images` - `all` images across `all regions`. 115 | - `awsm.ec2.regions` - `all` regions. 116 | - `region.instances` - instances specific to the region. 117 | - `region.images` - images specific to the region. 118 | 119 | 120 | ## EC2 Regions 121 | 122 | Awsm allows you to interface against multiple EC2 regions pretty easily - all you need to do is pass which regions you want to use in the main config. 123 | 124 | ```javascript 125 | var aws = awsm({ 126 | key: "KEY", 127 | secret: "SECRET" 128 | ec2: { 129 | regions: ["us-east-1", "us-west-2"] 130 | } 131 | }) 132 | ``` 133 | 134 | Note that the `regions` property is completely optional - awsm will automatically default to `all` EC2 regions if the property is omitted. 135 | 136 | Here are a few examples of how you might interact with awsm regions: 137 | 138 | ```javascript 139 | 140 | // init awsm 141 | var awsm = awsm({ key: "key", secret: "secret" }); 142 | 143 | // get all regions 144 | awsm.ec2.regions.all(onAllRegions); 145 | 146 | // find all U.S. regions 147 | awsm.ec2.regions.find({ name: /^us/ }, onAllUSRegions); 148 | 149 | // find ALL instances in ALL regions 150 | awsm.ec2.instances.all(onAllInstancesFromAllRegions); 151 | 152 | // find ALL RUNNING instances in ALL regions 153 | awsm.ec2.instances.find({ state: "running" }, onAllRunningInstances); 154 | ``` 155 | 156 | ## EC2 Tags 157 | 158 | Allows you to tag specific instances, images, security groups, or key pairs. 159 | 160 | ### resource.tags.update(tags, next) 161 | 162 | Updates the tags on the specific resource 163 | 164 | ```javascript 165 | instance.tags.update({ type: "mongodb" }, function () { 166 | console.log(instance.get("tags")); // { type: mongodb } 167 | }) 168 | ``` 169 | 170 | ## EC2 Instances 171 | 172 | #### region.instances.create(options, callback) 173 | 174 | Creates a new instance in the target region. 175 | 176 | - `imageId` - (required) imageId to use 177 | - `count` - (default = 1) number of instances to create 178 | - `flavor` - (default = t1.micro) type of instance to use (t1.micro, m1.medium) 179 | - `securityGroup` - (optional) the security group object to use with the instance 180 | - `keyPair` - (optional) the key pair object to use with the instance 181 | 182 | ```javascript 183 | region.instances.create({ imageId: "ami-a73264ce" }, function (err, instance) { 184 | console.log(instance.get("state")); // running 185 | console.log(instance.get("_id")); // instance id 186 | }) 187 | ``` 188 | 189 | #### instance.start(callback) 190 | 191 | starts the instance 192 | 193 | #### instance.restart(callback) 194 | 195 | restarts the instance 196 | 197 | #### instance.stop(callback) 198 | 199 | stops the instance 200 | 201 | #### instance.createImage(options, callback) 202 | 203 | creates an image out of the instance 204 | 205 | #### instance.getVolumes(callback) 206 | 207 | returns all the volumes attached to the instance 208 | 209 | #### instance.getSecurityGroups(callback) 210 | 211 | returns all the securityGroups attached to the instance 212 | 213 | #### instance.getAddress(callback) 214 | 215 | returns the allocated address assigned to the specific instance 216 | 217 | #### instance.getImage(callback) 218 | 219 | returns the image used to create the instance 220 | 221 | #### instance.getKeyPair(callback) 222 | 223 | returns the keypair assigned to the instance 224 | 225 | #### tags instance.tags 226 | 227 | instance tags collection 228 | 229 | #### instance.destroy(callback) 230 | 231 | destroys the instance 232 | 233 | ## EC2 Images 234 | 235 | #### region.images.create(options, callback) 236 | 237 | creates a new image 238 | 239 | #### image.createInstance(options, next) 240 | 241 | creates a new instance from the image 242 | 243 | #### image.migrate(regions, next) 244 | 245 | migrates the image to another region 246 | 247 | #### image.destroy(callback) 248 | 249 | destroys the image 250 | 251 | ## EC2 Addresses 252 | 253 | #### region.addresses.create(options, callback) 254 | 255 | allocates a new address 256 | 257 | #### address.attach(instance, callback) 258 | 259 | associates an address with an instance 260 | 261 | #### address.detach(instance, callback) 262 | 263 | detaches from an instance 264 | 265 | #### address.getInstance(callback) 266 | 267 | returns the instance associated with the address 268 | 269 | #### tags address.tags 270 | 271 | address tags collection 272 | 273 | #### address.destroy(callback) 274 | 275 | releases the address 276 | 277 | ## EC2 Volumes 278 | 279 | #### region.volumes.create(options, callback) 280 | 281 | creates a new volume 282 | 283 | #### volume.attach(instance, callback) 284 | 285 | attaches to an instance 286 | 287 | #### volume.detach(callback) 288 | 289 | detaches from an instance 290 | 291 | #### volume.getInstances(instance, callback) 292 | 293 | returns all the instances this volume is attached to 294 | 295 | #### volume.createSnapshot([description, ]callback) 296 | 297 | creates a new snapshot of the volume 298 | 299 | #### volume.destroy(callback) 300 | 301 | destroys the volume 302 | 303 | ## EC2 Snapshots 304 | 305 | #### region.snapshots.create(options, callback) 306 | 307 | creates a snapshot 308 | 309 | #### snapshot.createVolume(options, callback) 310 | 311 | creates a volume out of the snapshot 312 | 313 | #### snapshot.getVolume(callback) 314 | 315 | returns the snapshot associated with the given volume 316 | 317 | #### snapshot.destroy(callback) 318 | 319 | destroys the snapshot 320 | 321 | ## EC2 Key Pairs 322 | 323 | #### region.keyPairs.create(options, callback) 324 | 325 | creates a new keypair 326 | 327 | #### keyPair.destroy(callback) 328 | 329 | destroys the keypair 330 | 331 | ## EC2 Security Groups 332 | 333 | #### region.securityGroups.create(options, callback) 334 | 335 | creates a security group 336 | 337 | #### securityGroup.authorize(port, callback) 338 | 339 | authorizes a port in the security group 340 | 341 | #### securityGroup.revoke(port, callback) 342 | 343 | revokes port 344 | 345 | #### securityGroup.destroy(callback) 346 | 347 | destroys the security group. 348 | 349 | ## Chaining 350 | 351 | TODO - Checkout the examples at the top. 352 | 353 | 354 | 355 | 356 | 357 | -------------------------------------------------------------------------------- /docs/pseudo.js: -------------------------------------------------------------------------------- 1 | var awsm = require("awsm"); 2 | 3 | var aws = awsm({ 4 | key: "AWS_KEY", 5 | secret: "AWS_SECRET" 6 | ec2: { 7 | regions: ["us-east-1"] 8 | } 9 | }); 10 | 11 | 12 | aws.ec2.regions.findOne({ name: "us-east-1" }) -------------------------------------------------------------------------------- /lib/base/collection.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"), 2 | bindable = require("bindable"); 3 | 4 | 5 | function Collection (options) { 6 | this.options = options || {}; 7 | bindable.Object.call(this, this); 8 | } 9 | 10 | bindable.Object.extend(Collection, { 11 | 12 | /** 13 | */ 14 | 15 | isCollection: true, 16 | 17 | /** 18 | */ 19 | 20 | length: 0, 21 | 22 | /** 23 | */ 24 | 25 | empty: true, 26 | 27 | /** 28 | */ 29 | 30 | all: function (next) { 31 | this.find(next); 32 | }, 33 | 34 | /** 35 | */ 36 | 37 | find: function (query, next) { 38 | // override me 39 | }, 40 | 41 | /** 42 | */ 43 | 44 | findOne: function (query, next) { 45 | // override me 46 | }, 47 | 48 | /** 49 | */ 50 | 51 | toString: function () { 52 | return this.logger.name; 53 | } 54 | }); 55 | 56 | module.exports = Collection; -------------------------------------------------------------------------------- /lib/base/joinedCollection.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("./collection"), 2 | _ = require("underscore"), 3 | toarray = require("toarray"), 4 | async = require("async"), 5 | outcome = require("outcome"), 6 | flatten = require("flatten"); 7 | 8 | 9 | function JoinedCollection(collection, selector) { 10 | this.collection = collection; 11 | this._selectCollection = this._getSelector(selector); 12 | this._selectorName = selector; 13 | } 14 | 15 | BaseCollection.extend(JoinedCollection, { 16 | 17 | /** 18 | */ 19 | 20 | _getSelector: function (selector) { 21 | 22 | if (typeof selector === "string") { 23 | return function (model) { 24 | return model[selector]; 25 | } 26 | } 27 | 28 | return selector; 29 | }, 30 | 31 | /** 32 | */ 33 | 34 | find: function (query, next) { 35 | 36 | if (arguments.length == 1) { 37 | next = query; 38 | query = function () { return true }; 39 | } 40 | 41 | var self = this; 42 | this._call("find", [query], next); 43 | }, 44 | 45 | /** 46 | */ 47 | 48 | findOne: function (query, next) { 49 | this.find(query, outcome.e(next).s(function (items) { 50 | next(null, items.shift()); 51 | })) 52 | }, 53 | 54 | 55 | /** 56 | */ 57 | 58 | _call: function (method, args, next) { 59 | var self = this; 60 | this.collection.all(outcome.e(next).s(function (models) { 61 | async.mapSeries(models, function (model, next) { 62 | 63 | var collection = self._selectCollection(model); 64 | 65 | collection[method].apply(collection, args.concat(next)); 66 | 67 | }, outcome.e(next).s(function (items) { 68 | next(null, flatten(items)); 69 | })); 70 | })); 71 | }, 72 | 73 | /** 74 | */ 75 | 76 | toString: function () { 77 | return this.collection.toString() + "." + this._selectorName; 78 | } 79 | }); 80 | 81 | module.exports = JoinedCollection; -------------------------------------------------------------------------------- /lib/base/loadableCollection.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("./collection"), 2 | BaseModel = require("./model"), 3 | memoize = require("memoizee"), 4 | _ = require("underscore"), 5 | outcome = require("outcome"), 6 | sift = require("sift"), 7 | hurryup = require("hurryup"), 8 | comerr = require("comerr"), 9 | traverse = require("traverse"); 10 | 11 | 12 | function LoadableCollection (options) { 13 | 14 | BaseCollection.call(this, options); 15 | 16 | // memoize load 17 | this.load = memoize(_.bind(this.reload, this), { expire: false }); 18 | this._source = []; 19 | } 20 | 21 | BaseCollection.extend(LoadableCollection, { 22 | 23 | /** 24 | */ 25 | 26 | _retryTimeout : 1000 * 5, 27 | _retryCount : 2, 28 | 29 | /** 30 | */ 31 | 32 | createModel: function (data) { 33 | var modelClass = this.options.modelClass || BaseModel; 34 | return new modelClass(data, this); 35 | }, 36 | 37 | /** 38 | */ 39 | 40 | wait: function (query, timeout, next) { 41 | 42 | if (arguments.length === 2) { 43 | next = timeout; 44 | timeout = 1000 * 60 * 20; 45 | } 46 | 47 | var self = this; 48 | 49 | 50 | hurryup(function (next) { 51 | 52 | self.logger.verbose("wait(%s)", query); 53 | 54 | self.reload(function () { 55 | self.find(query, outcome.e(next).s(function (models) { 56 | 57 | if (!models.length) return next(comerr.notFound()); 58 | 59 | next(null, models); 60 | })); 61 | }); 62 | }, { 63 | timeout: timeout, 64 | retry: true, 65 | retryTimeout: 1000 * 5 66 | }).call(null, next); 67 | }, 68 | 69 | /** 70 | */ 71 | 72 | waitForOne: function(query, timeout, next) { 73 | 74 | if (arguments.length === 2) { 75 | next = timeout; 76 | timeout = 1000 * 60 * 20; 77 | } 78 | 79 | this.wait(query, timeout, outcome.e(next).s(function (models) { 80 | next(null, models.shift()); 81 | })); 82 | }, 83 | 84 | /** 85 | */ 86 | 87 | find: function (query, next) { 88 | 89 | // return everything 90 | if (arguments.length === 1) { 91 | next = query; 92 | query = function () { return true }; 93 | } 94 | 95 | if (typeof query === "string") { 96 | query = { _id: query }; 97 | } 98 | 99 | 100 | 101 | var self = this; 102 | 103 | this.load({}, outcome.e(next).s(function(models) { 104 | 105 | // doesn't work for regexp 106 | 107 | if (typeof query === "object") { 108 | query = traverse.clone(query); 109 | // query = JSON.parse(JSON.stringify(query)); 110 | 111 | } 112 | 113 | 114 | var sifter = sift(query); 115 | 116 | 117 | next(null, models.filter(function (model) { 118 | return sifter.test(model.context()); 119 | })); 120 | })); 121 | }, 122 | 123 | /** 124 | */ 125 | 126 | findOne: function (query, next) { 127 | this.find(query, outcome.e(next).s(function (models) { 128 | next(null, models.shift()); 129 | })); 130 | }, 131 | 132 | /** 133 | */ 134 | 135 | 136 | reload: function (options, next) { 137 | 138 | if (arguments.length === 1) { 139 | next = options; 140 | options = {}; 141 | } 142 | 143 | if (!next) next = function () { }; 144 | 145 | var self = this; 146 | 147 | this.logger.verbose("reload(%s)", options); 148 | 149 | this._load(options, outcome.e(next).s(function (source) { 150 | 151 | // may only be loading one item - don't remove if that's the case 152 | next(null, self._reset(source, !Object.keys(options).length)); 153 | })); 154 | }, 155 | 156 | /** 157 | */ 158 | 159 | _reset: function (source, remove) { 160 | 161 | var emodels = this._source, 162 | nmodels = source.concat(), 163 | emodel, 164 | nmodel, 165 | rmodel; 166 | 167 | // remove items 168 | if (remove) 169 | for (var i = emodels.length; i--;) { 170 | 171 | emodel = emodels[i]; 172 | 173 | for (var j = nmodels.length; j--;) { 174 | nmodel = nmodels[j]; 175 | if (emodel.get("_id") === nmodel._id) { 176 | break; 177 | } 178 | } 179 | 180 | if (!~j) { 181 | this.emit("remove", emodels.splice(i, 1).shift().dispose()); 182 | } 183 | } 184 | 185 | 186 | // updating existing 187 | for (var i = 0, n = emodels.length; i < n; i++) { 188 | emodel = emodels[i]; 189 | for (var j = 0, n2 = nmodels.length; j < n2; j++) { 190 | nmodel = nmodels[j]; 191 | 192 | if (emodel.get("_id") == nmodel._id) { 193 | emodel.setProperties(nmodel); 194 | 195 | // exists, remove from the new models collection 196 | nmodels.splice(j, 1); 197 | 198 | this.emit("update", emodel); 199 | break; 200 | } 201 | } 202 | } 203 | 204 | 205 | // add the new items 206 | for (var i = nmodels.length; i--;) { 207 | nmodel = nmodels[i]; 208 | emodel = this.createModel(nmodel); 209 | emodels.push(emodel); 210 | this.emit("create", emodel); 211 | } 212 | 213 | 214 | // return the source - needed for reload() 215 | return emodels.concat(); 216 | } 217 | }); 218 | 219 | module.exports = LoadableCollection; -------------------------------------------------------------------------------- /lib/base/loadableModel.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("./model"), 2 | outcome = require("outcome"), 3 | sift = require("sift"), 4 | hurryup = require("hurryup"); 5 | 6 | function LoadableModel () { 7 | BaseModel.apply(this, arguments); 8 | } 9 | 10 | BaseModel.extend(LoadableModel, { 11 | 12 | /** 13 | */ 14 | 15 | reload: function (next) { 16 | this._load(next); 17 | }, 18 | 19 | /** 20 | */ 21 | 22 | skip: function (properties, skip, load) { 23 | 24 | if (this.hasAllProperties(properties)) { 25 | return skip(null, this); 26 | } 27 | 28 | load(); 29 | }, 30 | 31 | /** 32 | */ 33 | 34 | hasAllProperties: function (properties) { 35 | return sift(properties).test(this.context()); 36 | }, 37 | 38 | /** 39 | */ 40 | 41 | wait: function (properties, next) { 42 | 43 | var self = this; 44 | 45 | var next2 = next; 46 | 47 | function load (next) { 48 | 49 | self.logger.verbose("wait(%s)", properties); 50 | 51 | if (self._disposed) { 52 | self.logger.notice("cannotWaitSinceDisposed()"); 53 | return next(new Error("disposed")); 54 | } 55 | 56 | self.reload(outcome.e(next).s(function () { 57 | 58 | if (!self.hasAllProperties(properties)) { 59 | return next(new Error("unable to sync properties")); 60 | } 61 | 62 | next(null, self); 63 | 64 | })); 65 | } 66 | 67 | hurryup(load, { 68 | retry : true, 69 | timeout : 1000 * 60 * 20, 70 | retryTimeout : 1000 * 3 71 | }).call(this, next); 72 | }, 73 | 74 | /** 75 | */ 76 | 77 | destroy: function (next) { 78 | 79 | this.logger.notice("destroy()"); 80 | 81 | if (!next) next = function () {} 82 | var self = this; 83 | this._destroy(outcome.e(next).s(function() { 84 | self.collection.reload(function () { 85 | next(null, self); 86 | }) 87 | })); 88 | }, 89 | 90 | /** 91 | */ 92 | 93 | _load: function (next) { 94 | 95 | if (!next) { 96 | next = function () {}; 97 | } 98 | 99 | this.collection.reload({ _id: this.get("_id") }, outcome.e(next).s(function (models) { 100 | next(null, models.shift()); 101 | })); 102 | }, 103 | 104 | /** 105 | */ 106 | 107 | _destroy: function (next) { 108 | next(); // OVERRIDE ME 109 | } 110 | }); 111 | 112 | module.exports = LoadableModel; -------------------------------------------------------------------------------- /lib/base/model.js: -------------------------------------------------------------------------------- 1 | var bindable = require("bindable"); 2 | 3 | function BaseModel (data, collection) { 4 | bindable.Object.call(this, data); 5 | this.collection = collection; 6 | this.awsm = collection.awsm; 7 | 8 | // plugin additional, third-party features 9 | this.awsm.decorators.decorate(this); 10 | } 11 | 12 | bindable.Object.extend(BaseModel, { 13 | 14 | /** 15 | */ 16 | 17 | isModel: true, 18 | 19 | /** 20 | */ 21 | 22 | dispose: function () { 23 | this._disposed = true; 24 | this.emit("dispose"); 25 | return this; 26 | }, 27 | 28 | /** 29 | */ 30 | 31 | toString: function () { 32 | return this.logger.name; 33 | } 34 | }); 35 | 36 | module.exports = BaseModel; -------------------------------------------------------------------------------- /lib/chain/index.js: -------------------------------------------------------------------------------- 1 | var chain = require("brasslet")(); 2 | 3 | 4 | function loadCollection (collection, next) { 5 | collection.all(function () { 6 | next(null, collection); 7 | }) 8 | } 9 | 10 | function reload (model, next) { 11 | model.all(function() { 12 | next(null, model); 13 | }) 14 | } 15 | 16 | 17 | function createChainedCollection (key) { 18 | var nameParts = key.split(":"), 19 | name = nameParts.shift(), 20 | plural = name + (nameParts.shift() || "s"); 21 | 22 | 23 | chain.add(plural, { 24 | all: { 25 | type: name 26 | }, 27 | find: { 28 | type: name 29 | }, 30 | findOne: { 31 | type: name 32 | }, 33 | reload: { 34 | type: plural, 35 | call: function (next) { 36 | reload(this, next); 37 | } 38 | }, 39 | create: { 40 | type: name 41 | } 42 | }); 43 | } 44 | 45 | chain.add("awsm", { 46 | ec2: { 47 | type: "ec2", 48 | call: function (next) { 49 | return next(null, this.ec2); 50 | } 51 | }, 52 | route53: { 53 | type: "route53", 54 | call: function (next) { 55 | return next(null, this.route53); 56 | } 57 | } 58 | }); 59 | 60 | chain.add("ec2", { 61 | regions: { 62 | type: "regions", 63 | call: function (next) { 64 | loadCollection(this.regions, next); 65 | } 66 | }, 67 | images: { 68 | type: "images", 69 | call: function (next) { 70 | loadCollection(this.images, next); 71 | } 72 | }, 73 | allImages: { 74 | type: "images", 75 | call: function (next) { 76 | loadCollection(this.allImages, next); 77 | } 78 | }, 79 | instances: { 80 | type: "instances", 81 | call: function (next) { 82 | loadCollection(this.instances, next); 83 | } 84 | }, 85 | volumes: { 86 | type: "volumes", 87 | call: function (next) { 88 | loadCollection(this.volumes, next); 89 | } 90 | }, 91 | addresses: { 92 | type: "addresses", 93 | call: function (next) { 94 | loadCollection(this.addresses, next); 95 | } 96 | }, 97 | zones: { 98 | type: "zones", 99 | call: function (next) { 100 | loadCollection(this.zones, next); 101 | } 102 | }, 103 | snapshots: { 104 | type: "snapshots", 105 | call: function (next) { 106 | loadCollection(this.snapshots, next); 107 | } 108 | }, 109 | keyPairs: { 110 | type: "keyPairs", 111 | call: function (next) { 112 | loadCollection(this.keyPairs, next); 113 | } 114 | }, 115 | securityGroups: { 116 | type: "securityGroups", 117 | call: function (next) { 118 | loadCollection(this.securityGroups, next); 119 | } 120 | } 121 | }); 122 | 123 | [ 124 | "instance", 125 | "image", 126 | "keyPair", 127 | "volume", 128 | "securityGroup", 129 | "snapshot", 130 | "address:es", 131 | "region", 132 | "hostedZone", 133 | "recordSet" 134 | ].forEach(createChainedCollection); 135 | 136 | 137 | chain.add("zones", { 138 | find: { 139 | type: "zone" 140 | } 141 | }); 142 | 143 | chain.add("region", { 144 | instances: { 145 | type: "instances", 146 | call: function (next) { 147 | loadCollection(this.instances, next); 148 | } 149 | }, 150 | images: { 151 | type: "images", 152 | call: function (next) { 153 | loadCollection(this.images, next); 154 | } 155 | }, 156 | allImages: { 157 | type: "images", 158 | call: function (next) { 159 | loadCollection(this.allImages, next); 160 | } 161 | }, 162 | addresses: { 163 | type: "addresses", 164 | call: function (next) { 165 | loadCollection(this.addresses, next); 166 | } 167 | }, 168 | volumes: { 169 | type: "volumes", 170 | call: function (next) { 171 | loadCollection(this.volumes, next); 172 | } 173 | }, 174 | zones: { 175 | type: "zones", 176 | call: function (next) { 177 | loadCollection(this.zones, next); 178 | } 179 | }, 180 | snapshots: { 181 | type: "snapshots", 182 | call: function (next) { 183 | loadCollection(this.snapshots, next); 184 | } 185 | }, 186 | keyPairs: { 187 | type: "keyPairs", 188 | call: function (next) { 189 | loadCollection(this.keyPairs, next); 190 | } 191 | }, 192 | securityGroups: { 193 | type: "securityGroups", 194 | call: function (next) { 195 | loadCollection(this.securityGroups, next); 196 | } 197 | } 198 | }); 199 | 200 | chain.add("image", { 201 | createInstance : { type: "instance" }, 202 | tag : { type: "image" }, 203 | migrate : { type: "image" }, 204 | destroy : { type: "image" } 205 | }); 206 | 207 | 208 | chain.add("volume", { 209 | attach : { type: "volume" }, 210 | detach : { type: "volume" }, 211 | tag : { type: "volume" }, 212 | createSnapshot : { type: "snapshot" }, 213 | destroy : { type: "volume" } 214 | }); 215 | 216 | chain.add("instance", { 217 | start : { type: "instance" }, 218 | stop : { type: "instance" }, 219 | restart : { type: "instance" }, 220 | destroy : { type: "instance" }, 221 | createImage : { type: "image" }, 222 | clone : { type: "instance" }, 223 | tag : { type: "tag" }, 224 | getAddress : { type: "address" }, 225 | getImage : { type: "image" }, 226 | resize : { type: "instance" }, 227 | getSecurityGroups : { type: "securityGroup" }, 228 | getKeyPair : { type: "keyPair" }, 229 | update : { type: "instance" }, 230 | getVolumes : { type: "volume" }, 231 | getStatus : { type: "object" } 232 | }); 233 | 234 | chain.add("keyPair", { 235 | destroy : { type: "keyPair" } 236 | }); 237 | 238 | 239 | chain.add("securityGroup", { 240 | authorize : { type: "securityGroup" }, 241 | revoke : { type: "securityGroup" }, 242 | destroy : { type: "securityGroup" } 243 | }); 244 | 245 | chain.add("address", { 246 | attach : { type: "address" }, 247 | detach : { type: "address" }, 248 | destroy : { type: "address" } 249 | }); 250 | 251 | chain.add("zone", { 252 | 253 | }); 254 | 255 | 256 | chain.add("route53", { 257 | hostedZones: { 258 | type: "hostedZones", 259 | call: function (next) { 260 | next(null, this.hostedZones); 261 | } 262 | } 263 | }); 264 | 265 | chain.add("hostedZone", { 266 | recordSets: { 267 | type: "recordSets", 268 | call: function (next) { 269 | next(null, this.recordSets); 270 | } 271 | } 272 | }); 273 | 274 | chain.add("recordSet", { 275 | }); 276 | 277 | chain.all({ 278 | pluck: { 279 | call: function () { 280 | var props = Array.prototype.slice.call(arguments, 0), 281 | next = props.pop(); 282 | var data = {}; 283 | 284 | 285 | for(var i = props.length; i--;) { 286 | var key = props[i]; 287 | data[key] = this.get(key); 288 | } 289 | 290 | next(null, data); 291 | } 292 | } 293 | }); 294 | 295 | module.exports = chain -------------------------------------------------------------------------------- /lib/cloudfront/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crcn/node-awsm/7d6237aa4ab0fe96fb2b60e40f744242b7b2a7d6/lib/cloudfront/.gitkeep -------------------------------------------------------------------------------- /lib/decorators/decorator.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"); 2 | 3 | function Decorator (options) { 4 | this._options = options; 5 | } 6 | 7 | protoclass(Decorator, { 8 | 9 | /** 10 | */ 11 | 12 | test: function (target) { 13 | return this._options.test(target); 14 | }, 15 | 16 | /** 17 | */ 18 | 19 | decorate: function (target) { 20 | 21 | if (!this.test(target)) { 22 | return; 23 | } 24 | 25 | this._options.decorate(target); 26 | } 27 | }); 28 | 29 | module.exports = Decorator; -------------------------------------------------------------------------------- /lib/decorators/index.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"), 2 | Decorator = require("./decorator"); 3 | 4 | function Decorators () { 5 | this._decorators = []; 6 | } 7 | 8 | protoclass(Decorators, { 9 | 10 | /** 11 | */ 12 | 13 | push: function (decorator) { 14 | this._decorators.push(new Decorator(decorator)); 15 | }, 16 | 17 | /** 18 | */ 19 | 20 | decorate: function (target) { 21 | for (var i = this._decorators.length; i--;) { 22 | this._decorators[i].decorate(target); 23 | } 24 | } 25 | }); 26 | 27 | module.exports = Decorators; -------------------------------------------------------------------------------- /lib/ec2/base/collection.js: -------------------------------------------------------------------------------- 1 | var BaseLoadableCollection = require("../../base/loadableCollection"); 2 | 3 | function BaseEC2Collection (ec2) { 4 | BaseLoadableCollection.call(this, { }); 5 | this.ec2 = ec2; 6 | this.awsm = ec2.awsm; 7 | } 8 | 9 | BaseLoadableCollection.extend(BaseEC2Collection); 10 | 11 | module.exports = BaseEC2Collection; -------------------------------------------------------------------------------- /lib/ec2/base/model.js: -------------------------------------------------------------------------------- 1 | BaseModel = require("../../base/loadableModel"); 2 | 3 | function BaseEC2Model (data, collection) { 4 | BaseModel.call(this, data, collection); 5 | this.ec2 = collection.ec2; 6 | this.awsm = this.ec2.awsm; 7 | this.awsm.decorators.decorate(this); 8 | } 9 | 10 | BaseModel.extend(BaseEC2Model); 11 | 12 | module.exports = BaseEC2Model; -------------------------------------------------------------------------------- /lib/ec2/index.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"), 2 | bindable = require("bindable"), 3 | Regions = require("./regions"), 4 | JoinedCollection = require("../base/joinedCollection"), 5 | AWS = require("aws-sdk"); 6 | 7 | function EC2 (options, awsm) { 8 | 9 | this.config = new bindable.Object(options); 10 | this.awsm = awsm; 11 | this.logger = awsm.logger.child("ec2"); 12 | 13 | this.regions = new Regions(this); 14 | 15 | var self = this; 16 | 17 | [ 18 | "instances", 19 | "images", 20 | "addresses", 21 | "keyPairs", 22 | "zones", 23 | "snapshots", 24 | "volumes", 25 | "securityGroups" 26 | ].forEach(function (collectionName) { 27 | self[collectionName] = new JoinedCollection(self.regions, collectionName); 28 | }); 29 | } 30 | 31 | protoclass(EC2, { 32 | toString: function () { 33 | return this.logger.name; 34 | } 35 | }); 36 | 37 | module.exports = EC2; -------------------------------------------------------------------------------- /lib/ec2/regions/addresses/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | Address = require("./model"), 3 | outcome = require("outcome"), 4 | flatten = require("flatten"), 5 | async = require("async"), 6 | utils = require("../../utils"); 7 | 8 | function AddressCollection (region) { 9 | BaseCollection.call(this, region); 10 | } 11 | 12 | BaseCollection.extend(AddressCollection, { 13 | 14 | /** 15 | */ 16 | 17 | name: "addresses", 18 | 19 | /** 20 | */ 21 | 22 | createModel: function (data) { 23 | return new Address(data, this); 24 | }, 25 | 26 | /** 27 | */ 28 | 29 | create: function (next) { 30 | var self = this; 31 | this.api.allocateAddress(outcome.e(next).s(function (result) { 32 | self.waitForOne({ publicIp: result.PublicIp }, next); 33 | })) 34 | }, 35 | 36 | /** 37 | */ 38 | 39 | _load: function (options, next) { 40 | 41 | var search = {}, self = this; 42 | 43 | if (typeof options._id === "string") { 44 | search.PublicIps = [options._id]; 45 | } 46 | 47 | this.api.describeAddresses(search, outcome.e(next).s(function (result) { 48 | 49 | var addresses = result.Addresses; 50 | 51 | next(null, addresses.map(function (address) { 52 | return { 53 | _id : address.PublicIp, 54 | publicIp : address.PublicIp, 55 | region : self.region.get("_id"), 56 | instanceId : address.InstanceId 57 | }; 58 | })); 59 | })); 60 | } 61 | 62 | 63 | 64 | }); 65 | 66 | module.exports = AddressCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/addresses/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"), 4 | toarray = require("toarray"), 5 | Tags = require("../tags"), 6 | async = require("async"); 7 | 8 | 9 | function Address () { 10 | BaseModel.apply(this, arguments); 11 | } 12 | 13 | 14 | BaseModel.extend(Address, { 15 | 16 | /** 17 | */ 18 | 19 | name: "address", 20 | 21 | /** 22 | */ 23 | 24 | getInstance: function (next) { 25 | this.region.instances.findOne({ _id: this.get("instanceId") }, next); 26 | }, 27 | 28 | /** 29 | */ 30 | 31 | attach: function (instanceOrInstanceId, next) { 32 | 33 | instanceId = typeof instanceOrInstanceId === "object" ? instanceOrInstanceId.get("_id") : instanceOrInstanceId; 34 | 35 | var self = this; 36 | 37 | this.api.associateAddress({ 38 | PublicIp: this.get("publicIp"), 39 | InstanceId: instanceId 40 | }, outcome.e(next).s(function () { 41 | self.region.instances.reload(function () { 42 | self.reload(next); 43 | }) 44 | })); 45 | }, 46 | 47 | /** 48 | */ 49 | 50 | detach: function (next) { 51 | var self = this; 52 | this.api.disassociateAddress({ PublicIp: this.get("publicIp") }, outcome.e(next).s(function (result) { 53 | self.reload(next); 54 | })); 55 | }, 56 | 57 | /** 58 | */ 59 | 60 | _destroy: function (next) { 61 | this.api.releaseAddress({ PublicIp: this.get("publicIp") }, next); 62 | } 63 | }); 64 | 65 | module.exports = Address; -------------------------------------------------------------------------------- /lib/ec2/regions/base/collection.js: -------------------------------------------------------------------------------- 1 | var BaseEc2Collection = require("../../base/collection"), 2 | Instance = require("./model"); 3 | 4 | 5 | function BaseRegionCollection (region) { 6 | BaseEc2Collection.call(this, region.ec2); 7 | this.region = region; 8 | this.api = region.api; 9 | this.logger = region.logger.child(this.name); 10 | } 11 | 12 | BaseEc2Collection.extend(BaseRegionCollection); 13 | 14 | module.exports = BaseRegionCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/base/model.js: -------------------------------------------------------------------------------- 1 | var BaseEc2Model = require("../../base/model"), 2 | Instance = require("./model"); 3 | 4 | 5 | function BaseRegionModel (data, collection) { 6 | BaseEc2Model.call(this, data, collection); 7 | this.region = collection.region; 8 | this.api = this.region.api; 9 | this.logger = collection.logger.child(this.get("_id")); 10 | } 11 | 12 | BaseEc2Model.extend(BaseRegionModel); 13 | 14 | module.exports = BaseRegionModel; -------------------------------------------------------------------------------- /lib/ec2/regions/images/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | Image = require("./model"), 3 | outcome = require("outcome"), 4 | flatten = require("flatten"), 5 | _ = require("underscore"), 6 | utils = require("../../utils"); 7 | 8 | function ImageCollection (region, search) { 9 | this.search = search || { }; 10 | BaseCollection.call(this, region); 11 | } 12 | 13 | BaseCollection.extend(ImageCollection, { 14 | 15 | /** 16 | */ 17 | 18 | name: "images", 19 | 20 | /** 21 | */ 22 | 23 | createModel: function (data) { 24 | return new Image(data, this); 25 | }, 26 | 27 | /** 28 | */ 29 | 30 | _load: function (options, next) { 31 | 32 | var search = _.extend({}, this.search), 33 | self = this; 34 | 35 | if (typeof options._id === "string") { 36 | search.ImageIds = [options._id]; 37 | delete search.Owners; 38 | } 39 | 40 | this.api.describeImages(search, outcome.e(next).s(function (result) { 41 | 42 | var images = result.Images; 43 | 44 | next(null, images.map(function (image) { 45 | return { 46 | _id : image.ImageId, 47 | name : image.Name, 48 | region : self.region.get("_id"), 49 | state : image.State, 50 | isPublic : image.Public, 51 | platform : image.Platform, 52 | architecture : image.Architecture, 53 | tags : utils.mapTags(image.Tags) 54 | }; 55 | })); 56 | })); 57 | } 58 | 59 | }); 60 | 61 | 62 | module.exports = ImageCollection; 63 | -------------------------------------------------------------------------------- /lib/ec2/regions/images/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"), 4 | toarray = require("toarray"), 5 | Tags = require("../tags"), 6 | async = require("async"); 7 | 8 | /* 9 | 10 | Server States: 11 | 12 | +--------+---------------+ 13 | | Code | State | 14 | +--------+---------------+ 15 | | ? | pending | 16 | | ? | available | 17 | +--------+---------------+ 18 | 19 | */ 20 | 21 | function Image () { 22 | BaseModel.apply(this, arguments); 23 | this.tags = new Tags(this); 24 | } 25 | 26 | 27 | BaseModel.extend(Image, { 28 | 29 | /** 30 | */ 31 | 32 | name: "image", 33 | 34 | /** 35 | */ 36 | 37 | createInstance: function (options, next) { 38 | 39 | if (typeof options === "number") { 40 | options = { count: options }; 41 | } 42 | 43 | if (arguments.length === 1) { 44 | next = options; 45 | options = {}; 46 | } 47 | 48 | options.imageId = this.get("_id"); 49 | options.tags = this.get("tags"); 50 | 51 | var self = this; 52 | 53 | this.wait({ state: "available" }, function () { 54 | self.region.instances.create(options, outcome.e(next).s(function (instance) { 55 | instance.tags.update(self.get("tags"), outcome.e(next).s(function () { 56 | next(null, instance); 57 | })); 58 | })); 59 | }); 60 | }, 61 | 62 | /** 63 | */ 64 | 65 | migrate: function (regions, next) { 66 | 67 | // TODO - check if regions are region objects 68 | 69 | var regions = toarray(regions), 70 | self = this; 71 | 72 | function _onRegions (regions) { 73 | async.map(regions, _.bind(self._migrateToRegion, self), next); 74 | } 75 | 76 | self.wait({ state: "available" }, outcome.e(next).s(function () { 77 | 78 | if (typeof regions[0] === "string") { 79 | self.region.collection.find({ name: { $in: regions }}, outcome.e(next).s(_onRegions)); 80 | } else { 81 | _onRegions(regions); 82 | } 83 | 84 | })); 85 | }, 86 | 87 | /** 88 | */ 89 | 90 | _migrateToRegion: function (region, next) { 91 | 92 | var o = outcome.e(next), self = this; 93 | 94 | 95 | this.logger.notice("migrateTo(%s)", region.get("_id")); 96 | 97 | region.api.copyImage({ 98 | "SourceRegion" : this.region.get("_id"), 99 | "SourceImageId" : this.get("_id"), 100 | "Description" : this.get("description") || this.get("_id"), 101 | "Name" : this.get("name") || this.get("_id") 102 | }, o.s(function (image) { 103 | region.images.waitForOne({ _id: image.ImageId, state: "available" }, o.s(function (image) { 104 | image.tags.update(self.get("tags"), o.s(function () { 105 | next(null, image); 106 | })); 107 | })); 108 | })); 109 | }, 110 | 111 | /** 112 | */ 113 | 114 | _destroy: function (next) { 115 | this.api.deregisterImage({ ImageId: this.get("_id") }, next); 116 | } 117 | }); 118 | 119 | module.exports = Image; -------------------------------------------------------------------------------- /lib/ec2/regions/index.js: -------------------------------------------------------------------------------- 1 | var BaseEc2Collection = require("../base/collection"), 2 | Region = require("./model"); 3 | 4 | function RegionsCollection (ec2) { 5 | BaseEc2Collection.call(this, ec2); 6 | this.logger = ec2.logger.child("regions"); 7 | } 8 | 9 | RegionsCollection.SUPPORTED_REGIONS = [ 10 | "us-west-1", 11 | "us-west-2", 12 | "us-east-1", 13 | "eu-west-1", 14 | "sa-east-1", 15 | "ap-southeast-1", 16 | "ap-southeast-2", 17 | "ap-northeast-1" 18 | ]; 19 | 20 | BaseEc2Collection.extend(RegionsCollection, { 21 | 22 | /** 23 | */ 24 | 25 | name: "regions", 26 | 27 | /** 28 | */ 29 | 30 | createModel: function (data) { 31 | return new Region(data, this); 32 | }, 33 | 34 | /** 35 | */ 36 | 37 | _load: function (options, next) { 38 | 39 | var regions = this.ec2.config.get("regions") || RegionsCollection.SUPPORTED_REGIONS; 40 | 41 | next(null, regions.map(function (name) { 42 | return { 43 | name: name, 44 | _id: name 45 | }; 46 | })); 47 | } 48 | }); 49 | 50 | module.exports = RegionsCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/instances/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | Instance = require("./model"), 3 | outcome = require("outcome"), 4 | flatten = require("flatten"), 5 | async = require("async"), 6 | utils = require("../../utils"); 7 | 8 | function InstanceCollection (region) { 9 | BaseCollection.call(this, region); 10 | } 11 | 12 | BaseCollection.extend(InstanceCollection, { 13 | 14 | /** 15 | */ 16 | 17 | name: "instances", 18 | 19 | /** 20 | */ 21 | 22 | createModel: function (data) { 23 | return new Instance(data, this); 24 | }, 25 | 26 | /** 27 | */ 28 | 29 | create: function (options, next) { 30 | 31 | var self = this, count = options.count || 1; 32 | 33 | 34 | async.waterfall([ 35 | 36 | function createInstance (next) { 37 | 38 | if (options.volumeSize && !options.deviceName) { 39 | options.deviceName = "/dev/sda1"; 40 | } 41 | 42 | // one security group id 43 | if (options.securityGroupId) { 44 | options.securityGroupIds = [options.securityGroupId]; 45 | } 46 | 47 | // one security group 48 | if (options.securityGroup) { 49 | options.securityGroups = [options.securityGroup]; 50 | } 51 | 52 | // many security groups - need to map the IDs 53 | if (options.securityGroups) { 54 | options.securityGroupIds = options.securityGroups.map(function (securityGroup) { 55 | return securityGroup.get("_id"); 56 | }); 57 | } 58 | 59 | if (options.image) { 60 | options.imageId = options.image.get("_id"); 61 | } 62 | 63 | if (options.keyPair) { 64 | options.keyName = options.keyPair.get("name"); 65 | } 66 | 67 | if (typeof options.zone === "object") { 68 | options.zone = options.zone.get("_id"); 69 | } 70 | 71 | var ops = utils.cleanObject({ 72 | "ImageId" : options.imageId, 73 | "MinCount" : count, 74 | "MaxCount" : count, 75 | "KeyName" : options.keyName, 76 | "SecurityGroupIds" : options.securityGroupIds, 77 | "InstanceType" : options.flavor || options.type || "t1.micro", 78 | "Placement.AvailabilityZone" : options.zone, 79 | "EbsOptimized" : options.ebsOptimized 80 | }); 81 | 82 | self.logger.notice("create(%s)", ops); 83 | 84 | self.api.runInstances(ops, next); 85 | 86 | }, 87 | 88 | function waitForInstances (result, next) { 89 | 90 | newInstanceIds = result.Instances.map(function (instance) { 91 | return instance.InstanceId; 92 | }); 93 | 94 | self.logger.notice("waitForAvailability(%s)", newInstanceIds.join(", ")); 95 | 96 | async.map(newInstanceIds, function (instanceId, next) { 97 | self.waitForOne({ _id: String(instanceId) }, next); 98 | }, next); 99 | }, 100 | 101 | function waitUntilState (instances, next) { 102 | async.map(instances, function (instance, next) { 103 | instance.wait({ state: "running" }, next); 104 | }, next); 105 | }, 106 | 107 | function complete (instances, next) { 108 | 109 | if (count === 1) { 110 | return next(null, instances[0]); 111 | } 112 | 113 | next(null, instances); 114 | } 115 | ], next); 116 | }, 117 | 118 | /** 119 | */ 120 | 121 | _load: function (options, next) { 122 | 123 | var search = {}, self = this; 124 | 125 | if (typeof options._id === "string") { 126 | search = { "InstanceIds": [options._id] }; 127 | } 128 | 129 | 130 | this.region.api.describeInstances(search, outcome.e(next).s(function (result) { 131 | 132 | var instances = flatten(result.Reservations.map(function (r) { 133 | return r.Instances; 134 | })); 135 | 136 | 137 | // don't filter if we're searching specifically for an item 138 | if (!options._id) { 139 | instances = instances.filter(function (instance) { 140 | return instance.State.Name !== "terminated"; 141 | }); 142 | } 143 | 144 | next(null, instances.map(function (instance) { 145 | 146 | return { 147 | // source : instance, 148 | _id : instance.InstanceId, 149 | state : instance.State.Name, 150 | type : instance.InstanceType, 151 | launchedAt : instance.LaunchTime, 152 | keyName : instance.KeyName, 153 | 154 | securityGroupIds : instance.SecurityGroups.map(function (securityGroup) { 155 | return securityGroup.GroupId; 156 | }), 157 | 158 | addresses : { 159 | privateDNS : instance.PrivateDnsName, 160 | publicDNS : instance.PublicDnsName, 161 | privateIp : instance.PrivateIpAddress, 162 | publicIp : instance.PublicIpAddress 163 | }, 164 | 165 | region : self.region.get("name"), 166 | tags : utils.mapTags(instance.Tags), 167 | imageId : instance.ImageId 168 | } 169 | })); 170 | })); 171 | } 172 | }); 173 | 174 | module.exports = InstanceCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/instances/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"), 4 | Tags = require("../tags"), 5 | utils = require("../../utils"), 6 | bindable = require("bindable"), 7 | async = require("async"); 8 | 9 | /* 10 | 11 | Server States: 12 | 13 | +--------+---------------+ 14 | | Code | State | 15 | +--------+---------------+ 16 | | 0 | pending | 17 | | 16 | running | 18 | | 32 | shutting-down | 19 | | 48 | terminated | 20 | | 64 | stopping | 21 | | 80 | stopped | 22 | +--------+--------------- 23 | 24 | */ 25 | 26 | function Instance () { 27 | BaseModel.apply(this, arguments); 28 | this.tags = new Tags(this); 29 | } 30 | 31 | 32 | BaseModel.extend(Instance, { 33 | 34 | /** 35 | */ 36 | 37 | name: "instance", 38 | 39 | 40 | /** 41 | */ 42 | 43 | createImage: function (options, next) { 44 | 45 | 46 | if (typeof options === "string") { 47 | options = { name: options }; 48 | } 49 | 50 | if (arguments.length === 1) { 51 | next = options; 52 | options = {}; 53 | } 54 | 55 | options = { 56 | "InstanceId": this.get("_id"), 57 | "Name": options.name || (this.get("_id") + " - " + Date.now()) 58 | }; 59 | 60 | this.logger.notice("createImage(%s)", options); 61 | 62 | var o = outcome.e(next), self = this; 63 | 64 | this.api.createImage(options, o.s(function (result) { 65 | self.region.images.waitForOne({ _id: result.ImageId }, o.s(function (image) { 66 | image.tags.update(self.get("tags"), function () { 67 | next(null, image); 68 | }); 69 | })); 70 | })) 71 | }, 72 | 73 | /** 74 | */ 75 | 76 | resize: function (type, next) { 77 | return this.update({ type: type }, next); 78 | }, 79 | 80 | /** 81 | */ 82 | 83 | getStatus: function (next) { 84 | 85 | var self = this; 86 | 87 | this.api.describeInstanceStatus({ 88 | InstanceIds: [this.get("_id")] 89 | }, outcome.e(next).s(function (result) { 90 | 91 | var status = new bindable.Object(result.InstanceStatuses.filter(function (status) { 92 | return status.InstanceId === self.get("_id"); 93 | }).map(function(status) { 94 | return status; 95 | }).pop()); 96 | 97 | return next(null, status); 98 | 99 | })) 100 | }, 101 | 102 | /** 103 | */ 104 | 105 | update: function (options, next) { 106 | 107 | // only for VPC instances 108 | /*if (options.securityGroupId) { 109 | options.securityGroupIds = [options.securityGroupId]; 110 | } 111 | 112 | if (options.securityGroup){ 113 | options.securityGroups = [options.securityGroup]; 114 | } 115 | 116 | if (options.securityGroups) { 117 | options.securityGroupIds = options.securityGroups.map(function (securityGroup) { 118 | return securityGroup.get("_id"); 119 | }); 120 | }*/ 121 | 122 | var ops = utils.cleanObject({ 123 | "InstanceId" : this.get("_id"), 124 | "InstanceType.Value" : options.type, 125 | "Kernel" : options.kernel, 126 | "Ramdisk" : options.user, 127 | "UserData" : options.userData, 128 | "DisableApiTermination" : options.disableApiTermination, 129 | "InstanceInitiatedShutdownBehavior" : options.instanceInitiatedShutdownBehavior, 130 | "RootDeviceName" : options.rootDeviceName, 131 | "BlockDeviceMapping" : options.blockDeviceMapping, 132 | "SourceDestCheck" : options.sourceDestCheck 133 | // "Groups" : options.securityGroupIds 134 | }); 135 | 136 | var state = this.get("state"), o = outcome.e(next), self = this; 137 | 138 | 139 | // cannot update active instance - need to shut it down first 140 | this.stop(o.s(function () { 141 | 142 | self.logger.notice("update(%s)", ops); 143 | 144 | self.api.modifyInstanceAttribute(ops, o.s(function () { 145 | 146 | self.reload(o.s(function () { 147 | 148 | if (state !== "running") { 149 | return next(null, self); 150 | } 151 | 152 | self.start(next); 153 | 154 | })); 155 | })); 156 | })); 157 | 158 | return this; 159 | }, 160 | 161 | /** 162 | */ 163 | 164 | clone: function (next) { 165 | var self = this; 166 | var tmpImage, retInstance; 167 | async.waterfall([ 168 | function createImage (next) { 169 | self.createImage(next); 170 | }, 171 | function createInstance (image, next) { 172 | tmpImage = image; 173 | image.createInstance({ 174 | type: self.get("type"), 175 | keyName: self.get("keyName"), 176 | securityGroupIds: self.get("securityGroupIds") 177 | }, next); 178 | }, 179 | function (instance, next) { 180 | retInstance = instance; 181 | tmpImage.destroy(next); 182 | } 183 | ], function (err) { 184 | if (err) return next(err); 185 | next(null, retInstance); 186 | }); 187 | }, 188 | 189 | /** 190 | */ 191 | 192 | getImage: function (next) { 193 | this.region.images.waitForOne({ _id: this.get("imageId") }, next); 194 | }, 195 | 196 | /** 197 | */ 198 | 199 | getAddress: function (next) { 200 | this.region.addresses.waitForOne({ instanceId: this.get("_id") }, next); 201 | }, 202 | 203 | /** 204 | */ 205 | 206 | getSecurityGroups: function (next) { 207 | this.region.securityGroups.wait({ _id: { $in: this.get("securityGroupIds") }}, next); 208 | }, 209 | 210 | /** 211 | */ 212 | 213 | getVolumes: function (next) { 214 | this.region.volumes.wait({ "attachments.instanceId": this.get("_id") }, next); 215 | }, 216 | 217 | /** 218 | */ 219 | 220 | getKeyPair: function (next) { 221 | this.region.keyPairs.waitForOne({ "name": this.get("keyName") }, next); 222 | }, 223 | 224 | /** 225 | */ 226 | 227 | restart: function (next) { 228 | var self = this; 229 | this.stop(function () { 230 | self.start(next); 231 | }); 232 | }, 233 | 234 | /** 235 | */ 236 | 237 | stop: function (next) { 238 | this.logger.notice("stop()"); 239 | this._runCommand("stopped", _.bind(this._stop, this, next), next); 240 | }, 241 | 242 | /** 243 | */ 244 | 245 | _stop: function (next) { 246 | var state = this.get("state"), 247 | self = this; 248 | 249 | if (/running/.test(state)) { 250 | this._callAndWaitUntilState("stopInstances", "stopped", next); 251 | } else if(/stopping|shutting-down/.test(state)) { 252 | this.wait({ state: /stopped|terminated/ }, function () { 253 | self.stop(next); 254 | }); 255 | } else if(/pending/.test(state)) { 256 | this.wait({ state: "running" }, function () { 257 | self.stop(next); 258 | }) 259 | } 260 | }, 261 | 262 | /** 263 | */ 264 | 265 | start: function (next) { 266 | this.logger.notice("start()"); 267 | this._runCommand("running", _.bind(this._start, this, next), next); 268 | }, 269 | 270 | /** 271 | */ 272 | 273 | _start: function (next) { 274 | 275 | var state = this.get("state"), self = this; 276 | 277 | if (/stopped/.test(state)) { 278 | this._callAndWaitUntilState("startInstances", "running", next); 279 | } else if (/shutting-down|stopping/.test(state)) { 280 | this.wait({ state: /stopped|terminated/ }, function () { 281 | self.start(next); 282 | }) 283 | } else if (/pending/.test(state)) { 284 | this.wait({ state: "running" }, next); 285 | } 286 | }, 287 | 288 | /** 289 | */ 290 | 291 | _destroy: function (next) { 292 | this._runCommand("terminated", _.bind(this._terminate, this, next), next); 293 | }, 294 | 295 | /** 296 | */ 297 | 298 | _terminate: function (next) { 299 | return this._callAndWaitUntilState("terminateInstances", "terminated", next); 300 | }, 301 | 302 | /** 303 | */ 304 | 305 | _runCommand: function (expectedState, runCommand, next) { 306 | var self = this; 307 | 308 | this.skip({ state: expectedState }, next, function () { 309 | 310 | var state = self.get("state"); 311 | 312 | 313 | if (/terminated/.test(state)) { 314 | next(comerr.notFound("The instance has been terminated.")); 315 | } else if (!/stopping|stopped|shutting-down|running|pending/.test(state)) { 316 | next(comerr.unknownError("An unrecognized instance state was returned.")); 317 | } else { 318 | 319 | runCommand(); 320 | } 321 | }); 322 | }, 323 | 324 | /** 325 | */ 326 | 327 | _callAndWaitUntilState: function (command, state, next) { 328 | var fn, self = this; 329 | 330 | if (typeof command !== "function") { 331 | fn = function (next) { 332 | self.api[command].call(self.api, { "InstanceIds": [self.get("_id")] }, outcome.e(next).s(function () { 333 | next(null, self); 334 | })); 335 | }; 336 | } else { 337 | fn = command; 338 | } 339 | 340 | fn(outcome.e(next).s(function () { 341 | self.wait({ state: state }, next); 342 | })); 343 | } 344 | }); 345 | 346 | module.exports = Instance; -------------------------------------------------------------------------------- /lib/ec2/regions/keyPairs/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | KeyPair = require("./model"), 3 | outcome = require("outcome"), 4 | utils = require("../../utils"); 5 | 6 | function KeyPairCollection (region) { 7 | BaseCollection.call(this, region); 8 | } 9 | 10 | BaseCollection.extend(KeyPairCollection, { 11 | 12 | /** 13 | */ 14 | 15 | name: "keyPairs", 16 | 17 | /** 18 | */ 19 | 20 | createModel: function (data) { 21 | return new KeyPair(data, this); 22 | }, 23 | 24 | /** 25 | */ 26 | 27 | create: function (optionsOrName, next) { 28 | 29 | var options, self = this, o = outcome.e(next); 30 | 31 | if (arguments.length === 1) { 32 | next = optionsOrName; 33 | optionsOrName = String(Date.now()); 34 | } 35 | 36 | if (typeof optionsOrName === "string") { 37 | options = { name: optionsOrName }; 38 | } else { 39 | options = optionsOrName; 40 | } 41 | 42 | 43 | 44 | var onKey = o.s(function (result) { 45 | self.waitForOne({ name: options.name }, o.s(function (keyPair) { 46 | keyPair.set("material", result.KeyMaterial); 47 | next(null, keyPair); 48 | })); 49 | }); 50 | 51 | if (options.material) { 52 | this.api.importKeyPair({ KeyName: options.name, PublicKeyMaterial: options.material }, onkey); 53 | } else { 54 | this.api.createKeyPair({ KeyName: options.name }, onKey); 55 | } 56 | }, 57 | 58 | /** 59 | */ 60 | 61 | _load: function (options, next) { 62 | 63 | var search = {}, self = this; 64 | 65 | if (typeof options._id === "string") { 66 | search.KeyNames = [options._id]; 67 | } 68 | 69 | this.api.describeKeyPairs(search, outcome.e(next).s(function (result) { 70 | var keyPairs = result.KeyPairs; 71 | next(null, keyPairs.map(function (keyPair) { 72 | return { 73 | _id : keyPair.KeyName, 74 | region : self.region.get("_id"), 75 | name : keyPair.KeyName, 76 | fingerprint : keyPair.KeyFingerprint 77 | } 78 | })); 79 | })); 80 | } 81 | 82 | }); 83 | 84 | 85 | module.exports = KeyPairCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/keyPairs/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"); 4 | 5 | /* 6 | 7 | Server States: 8 | 9 | +--------+---------------+ 10 | | Code | State | 11 | +--------+---------------+ 12 | | ? | pending | 13 | | ? | available | 14 | +--------+---------------+ 15 | 16 | */ 17 | 18 | function KeyPair () { 19 | BaseModel.apply(this, arguments); 20 | } 21 | 22 | 23 | BaseModel.extend(KeyPair, { 24 | name: "keyPair", 25 | _destroy: function (next) { 26 | this.api.deleteKeyPair({ KeyName: this.get("name") }, next); 27 | } 28 | }); 29 | 30 | module.exports = KeyPair; -------------------------------------------------------------------------------- /lib/ec2/regions/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | Instances = require("./instances"), 3 | KeyPairs = require("./keyPairs"), 4 | Addresses = require("./addresses"), 5 | Volumes = require("./volumes"), 6 | Snapshots = require("./snapshots"), 7 | SecurityGroups = require("./securityGroups") 8 | Images = require("./images"), 9 | Zones = require("./zones"), 10 | AWS = require("aws-sdk"); 11 | 12 | function Region () { 13 | 14 | BaseModel.apply(this, arguments); 15 | 16 | this.api = new AWS.EC2({ 17 | accessKeyId : this.collection.awsm.config.get("key"), 18 | secretAccessKey : this.collection.awsm.config.get("secret"), 19 | region : this.get("name"), 20 | maxRetries : 15 21 | }); 22 | 23 | this.logger = this.ec2.logger.child(this.get("_id")); 24 | 25 | this.instances = new Instances(this); 26 | this.addresses = new Addresses(this); 27 | this.keyPairs = new KeyPairs(this); 28 | this.zones = new Zones(this); 29 | this.volumes = new Volumes(this); 30 | this.snapshots = new Snapshots(this); 31 | this.securityGroups = new SecurityGroups(this); 32 | this.images = new Images(this, { "Owners": ["self"] }); 33 | this.allImages = new Images(this); 34 | } 35 | 36 | BaseModel.extend(Region, { 37 | 38 | }); 39 | 40 | module.exports = Region; -------------------------------------------------------------------------------- /lib/ec2/regions/securityGroups/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | SecurityGroup = require("./model"), 3 | outcome = require("outcome"), 4 | utils = require("../../utils"); 5 | 6 | function SecurityGroupCollection (region) { 7 | BaseCollection.call(this, region); 8 | } 9 | 10 | BaseCollection.extend(SecurityGroupCollection, { 11 | 12 | /** 13 | */ 14 | 15 | name: "securityGroups", 16 | 17 | /** 18 | */ 19 | 20 | createModel: function (data) { 21 | return new SecurityGroup(data, this); 22 | }, 23 | 24 | /** 25 | */ 26 | 27 | create: function (optionsOrName, next) { 28 | 29 | var options, self = this; 30 | 31 | if (typeof optionsOrName === "string") { 32 | options = { name: optionsOrName }; 33 | } else { 34 | options = optionsOrName; 35 | } 36 | 37 | if (!options.description) { 38 | options.description = "Security Group"; 39 | } 40 | 41 | this.api.createSecurityGroup({ 42 | GroupName: options.name, 43 | Description: options.description 44 | }, outcome.e(next).s(function (result) { 45 | self.waitForOne({ _id: result.GroupId }, next); 46 | })); 47 | }, 48 | 49 | /** 50 | */ 51 | 52 | _load: function (options, next) { 53 | 54 | var search = {}, self = this; 55 | 56 | if (typeof options._id === "string") { 57 | search.GroupIds = [options._id]; 58 | } 59 | 60 | this.api.describeSecurityGroups(search, outcome.e(next).s(function (result) { 61 | var securityGroups = result.SecurityGroups; 62 | 63 | next(null, securityGroups.map(function (securityGroup) { 64 | return { 65 | _id : securityGroup.GroupId, 66 | name : securityGroup.GroupName, 67 | region : self.region.get("_id"), 68 | description : securityGroup.Description, 69 | ownerId : securityGroup.OwnerId, 70 | tags : utils.mapTags(securityGroup.Tags), 71 | permissions : securityGroup.IpPermissions.map(function (permission) { 72 | return { 73 | ipRanges : permission.IpRanges, 74 | protocol : permission.IpProtocol, 75 | fromPort : permission.FromPort, 76 | toPort : permission.ToPort 77 | }; 78 | }) 79 | }; 80 | })); 81 | 82 | })); 83 | } 84 | 85 | }); 86 | 87 | module.exports = SecurityGroupCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/securityGroups/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"), 4 | Tags = require("../tags"); 5 | 6 | 7 | function SecurityGroup () { 8 | BaseModel.apply(this, arguments); 9 | this.tags = new Tags(this); 10 | } 11 | 12 | 13 | BaseModel.extend(SecurityGroup, { 14 | 15 | /** 16 | */ 17 | 18 | name: "securityGroup", 19 | 20 | /** 21 | */ 22 | 23 | authorize: function (optionsOrPort, next) { 24 | this._runCommand("authorizeSecurityGroupIngress", optionsOrPort, next); 25 | }, 26 | 27 | /** 28 | */ 29 | 30 | revoke: function (optionsOrPort, next) { 31 | this._runCommand("revokeSecurityGroupIngress", optionsOrPort, next); 32 | }, 33 | 34 | /** 35 | */ 36 | 37 | _runCommand: function (command, optionsOrPort, next) { 38 | 39 | var options = {}, query = { 40 | GroupId: this.get("_id"), 41 | IpPermissions: [] 42 | }, self = this; 43 | 44 | if (typeof optionsOrPort === "number") { 45 | options = { 46 | ports: [ 47 | { from: optionsOrPort, to: optionsOrPort } 48 | ] 49 | }; 50 | } else { 51 | options = optionsOrPort; 52 | } 53 | 54 | 55 | if (options.from) { 56 | options = { ports: [options] } 57 | } 58 | 59 | for (var i = options.ports.length; i--;) { 60 | var portInfo = options.ports[i]; 61 | 62 | if (!portInfo.ranges) { 63 | portInfo.ranges = ["0.0.0.0/0"]; 64 | } 65 | 66 | query.IpPermissions.push({ 67 | IpProtocol : portInfo.protocol || "tcp", 68 | FromPort : portInfo.from || portInfo.number, 69 | ToPort : portInfo.to || portInfo.number, 70 | IpRanges : portInfo.ranges.map(function (range) { 71 | return { 72 | CidrIp: range 73 | } 74 | }) 75 | }); 76 | } 77 | 78 | this.api[command].call(this.api, query, outcome.e(next).s(function () { 79 | self.reload(next); 80 | })); 81 | }, 82 | 83 | /** 84 | */ 85 | 86 | _destroy: function (next) { 87 | this.api.deleteSecurityGroup({ GroupName: this.get("name") }, next); 88 | } 89 | }); 90 | 91 | module.exports = SecurityGroup -------------------------------------------------------------------------------- /lib/ec2/regions/snapshots/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | Snapshot = require("./model"), 3 | outcome = require("outcome"), 4 | utils = require("../../utils"); 5 | 6 | function SnapshotCollection (region) { 7 | BaseCollection.call(this, region); 8 | } 9 | 10 | BaseCollection.extend(SnapshotCollection, { 11 | 12 | /** 13 | */ 14 | 15 | name: "snapshots", 16 | 17 | /** 18 | */ 19 | 20 | createModel: function (data) { 21 | return new Snapshot(data, this); 22 | }, 23 | 24 | /** 25 | */ 26 | 27 | create: function (volumeId, description, next) { 28 | 29 | if (arguments.length === 2) { 30 | next = description; 31 | description = undefined; 32 | } 33 | 34 | var o = outcome.e(next), self = this; 35 | 36 | this.api.createSnapshot(utils.cleanObject({ 37 | VolumeId: volumeId, 38 | Descrption: description 39 | }), o.s(function (result) { 40 | self.waitForOne({ _id: result.SnapshotId }, next); 41 | })) 42 | 43 | }, 44 | 45 | /** 46 | */ 47 | 48 | _load: function (options, next) { 49 | 50 | var search = { "OwnerIds": ["self"] }, self = this, o = outcome.e(next); 51 | 52 | if (typeof options._id === "string") { 53 | search.SnapshotIds = [options._id]; 54 | delete search.OwnerIds; 55 | } 56 | 57 | this.api.describeSnapshots(search, outcome.e(next).s(function(result) { 58 | 59 | var snapshots = result.Snapshots; 60 | 61 | 62 | next(null, snapshots.map(function (snapshot) { 63 | 64 | return { 65 | _id : snapshot.SnapshotId, 66 | volumeId : snapshot.VolumeId, 67 | startedAt : snapshot.StartTime, 68 | region : self.region.get("_id"), 69 | state : snapshot.State, 70 | tags : utils.mapTags(snapshot.Tags), 71 | progress : snapshot.Progress ? Number(snapshot.Progress.substr(0, snapshot.Progress.length - 1)) : 0, 72 | ownerId : snapshot.OwnerId, 73 | volumeSize : snapshot.VolumeSize, 74 | description : snapshot.Description, 75 | instanceId : snapshot.InstanceId, 76 | volumeId : snapshot.VolumeId, 77 | imageId : snapshot.ImageId 78 | }; 79 | })) 80 | })); 81 | } 82 | 83 | }); 84 | 85 | module.exports = SnapshotCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/snapshots/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"), 4 | Tags = require("../tags"); 5 | 6 | 7 | function Snapshot () { 8 | BaseModel.apply(this, arguments); 9 | this.tags = new Tags(this); 10 | } 11 | 12 | 13 | BaseModel.extend(Snapshot, { 14 | 15 | /** 16 | */ 17 | 18 | name: "snapshot", 19 | 20 | /** 21 | */ 22 | 23 | createVolume: function (options, next) { 24 | options.snapshotId = this.get("_id"); 25 | this.region.volumes.create(options, next); 26 | }, 27 | 28 | /** 29 | */ 30 | 31 | getVolume: function (next) { 32 | this.region.volumes.waitForOne({ _id: this.get("volumeId") }, next); 33 | }, 34 | 35 | /** 36 | */ 37 | 38 | _destroy: function (next) { 39 | this.api.deleteSnapshot({ 40 | SnapshotId: this.get("_id") 41 | }, next); 42 | } 43 | }); 44 | 45 | module.exports = Snapshot; -------------------------------------------------------------------------------- /lib/ec2/regions/tags/index.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"), 2 | hurryup = require("hurryup"), 3 | outcome = require("outcome"), 4 | utils = require("../../utils"); 5 | 6 | function Tags (target, region) { 7 | this._target = target; 8 | this.region = target.region; 9 | this.api = this.region.api; 10 | this.logger = target.logger.child("tags"); 11 | 12 | 13 | var self = this; 14 | 15 | target.tag = function () { 16 | var args = Array.prototype.slice.call(arguments, 0), 17 | next = args.pop(); 18 | self.update.apply(self, args.concat(function (err) { 19 | if (err) return next(err); 20 | next(null, self.target); 21 | })); 22 | } 23 | } 24 | 25 | protoclass(Tags, { 26 | 27 | /** 28 | */ 29 | 30 | update: function (nameOrTags, value, next) { 31 | 32 | var tags, createTags = {}, deleteTags = {}, self = this; 33 | 34 | 35 | if (arguments.length === 2) { 36 | next = value; 37 | value = undefined; 38 | tags = nameOrTags || {}; 39 | } else { 40 | tags = {}; 41 | tags[nameOrTags] = value; 42 | } 43 | 44 | if (Object.keys(tags).length === 0) { 45 | return next(); 46 | } 47 | 48 | for (var name in tags) { 49 | var tag = tags[name]; 50 | 51 | if (tag != null) { 52 | createTags[name] = String(tag) 53 | } 54 | 55 | deleteTags[name] = undefined; 56 | } 57 | 58 | 59 | // tagging doesn't always work on EC2 - depends on the state 60 | // or the instance / image 61 | function _tryTagging (next) { 62 | self._modifyTags("deleteTags", deleteTags, function () { 63 | self._modifyTags("createTags", createTags, function () { 64 | self._target.reload(function () { 65 | if (!self._target.hasAllProperties({ tags: tags })) { 66 | return next(new Error("tag changes haven't been made")); 67 | } 68 | 69 | next(null, self); 70 | }); 71 | }); 72 | }); 73 | } 74 | 75 | hurryup(_tryTagging, { timeout: 1000 * 60 * 3, retry: true, retryTimeout: 1000 }).call(self, next); 76 | }, 77 | 78 | /** 79 | */ 80 | 81 | _modifyTags: function (method, tags, next) { 82 | 83 | 84 | 85 | var query = { 86 | "Resources": [this._target.get("_id")], 87 | "Tags": [] 88 | }; 89 | 90 | for (var name in tags) { 91 | query.Tags.push(utils.cleanObject({ Key: name, Value: tags[name] })); 92 | } 93 | 94 | if (query.Tags.length === 0) { 95 | return next(); 96 | } 97 | 98 | this.logger.notice(method + "(%s)", query.Tags); 99 | 100 | this.api[method].call(this.api, query, next); 101 | } 102 | }); 103 | 104 | module.exports = Tags; -------------------------------------------------------------------------------- /lib/ec2/regions/volumes/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | Volume = require("./model"), 3 | outcome = require("outcome"), 4 | utils = require("../../utils"), 5 | Tags = require("../tags"); 6 | 7 | function VolumeCollection (region) { 8 | BaseCollection.call(this, region); 9 | this.tags = new Tags(this); 10 | } 11 | 12 | BaseCollection.extend(VolumeCollection, { 13 | 14 | /** 15 | */ 16 | 17 | name: "volumes", 18 | 19 | /** 20 | */ 21 | 22 | createModel: function (data) { 23 | return new Volume(data, this); 24 | }, 25 | 26 | /** 27 | */ 28 | 29 | create: function (options, next) { 30 | 31 | var ops = utils.cleanObject({ 32 | Size : options.size, 33 | SnapshotId : options.snapshotId, 34 | AvailabilityZone : options.zone, 35 | VolumeType : options.type, 36 | Iops : options.iops 37 | }); 38 | 39 | var o = outcome.e(next), self = this; 40 | 41 | this.api.createVolume(ops, o.s(function (result) { 42 | self.waitForOne({ _id: result.VolumeId, state: "available" }, next); 43 | })); 44 | }, 45 | 46 | /** 47 | */ 48 | 49 | _load: function (options, next) { 50 | 51 | var search = {}, o = outcome.e(next), self = this; 52 | 53 | if (typeof options._id === "string") { 54 | search.VolumeIds = [options._id]; 55 | } 56 | 57 | 58 | 59 | this.api.describeVolumes(search, o.s(function (result) { 60 | 61 | var volumes = result.Volumes; 62 | 63 | if (!options._id) { 64 | volumes = volumes.filter(function (volume) { 65 | return volume.State !== "deleting"; 66 | }) 67 | } 68 | 69 | 70 | next(null, volumes.map(function (volume) { 71 | 72 | return { 73 | source : volume, 74 | _id : volume.VolumeId, 75 | size : volume.Size, 76 | snapshotId : volume.SnapshotId, 77 | zone : volume.AvailabilityZone, 78 | createdAt : volume.createTime, 79 | region : self.region.get("_id"), 80 | type : volume.volumeType, 81 | state : volume.State, 82 | tags : utils.mapTags(volume.Tags), 83 | attachments : volume.Attachments.map(function (attachment) { 84 | return { 85 | instanceId : attachment.InstanceId, 86 | device : attachment.Device, 87 | status : attachment.Status, 88 | deleteOnTermination : attachment.DeleteOnTermination 89 | }; 90 | }) 91 | } 92 | })) 93 | })); 94 | } 95 | 96 | }); 97 | 98 | module.exports = VolumeCollection; -------------------------------------------------------------------------------- /lib/ec2/regions/volumes/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../base/model"), 2 | _ = require("underscore"), 3 | outcome = require("outcome"); 4 | 5 | 6 | function Volume () { 7 | BaseModel.apply(this, arguments); 8 | } 9 | 10 | 11 | BaseModel.extend(Volume, { 12 | 13 | /** 14 | */ 15 | 16 | name: "volume", 17 | 18 | /** 19 | */ 20 | 21 | attach: function (instanceId, device, next) { 22 | 23 | if (typeof instanceId === "object") { 24 | instanceId = instanceId.get("_id"); 25 | } 26 | 27 | if (arguments.length === 2) { 28 | next = device; 29 | device = "/dev/sdh"; // or xvdh 30 | } 31 | 32 | var self = this; 33 | 34 | var ops = { 35 | VolumeId : this.get("_id"), 36 | InstanceId : instanceId, 37 | Device : device 38 | }; 39 | 40 | this.logger.notice("attach(%s)", ops); 41 | 42 | this.api.attachVolume(ops, outcome.e(next).s(function () { 43 | self.region.instances.reload(function () { 44 | self.reload(next); 45 | }); 46 | })); 47 | }, 48 | 49 | /** 50 | */ 51 | 52 | getInstances: function (next) { 53 | 54 | var instanceIds = this.get("attachments").map(function (attachment) { 55 | return attachment.instanceId; 56 | }); 57 | 58 | this.region.instances.wait({ _id: {$all: instanceIds }}, next); 59 | }, 60 | 61 | /** 62 | */ 63 | 64 | detach: function (next) { 65 | var self = this; 66 | 67 | this.logger.notice("detach()"); 68 | 69 | this.api.detachVolume({ 70 | VolumeId: this.get("_id") 71 | }, outcome.e(next).s(function () { 72 | self.region.instances.reload(function () { 73 | self.reload(next); 74 | }); 75 | })); 76 | }, 77 | 78 | /** 79 | */ 80 | 81 | createSnapshot: function (description, next) { 82 | 83 | if (arguments.length === 1) { 84 | next = description; 85 | description = undefined; 86 | } 87 | 88 | this.region.snapshots.create(this.get("_id"), description, next); 89 | }, 90 | 91 | /** 92 | */ 93 | 94 | _destroy: function (next) { 95 | this.api.deleteVolume({ 96 | VolumeId: this.get("_id") 97 | }, next); 98 | } 99 | }); 100 | 101 | module.exports = Volume; -------------------------------------------------------------------------------- /lib/ec2/regions/zones/index.js: -------------------------------------------------------------------------------- 1 | var BaseCollection = require("../base/collection"), 2 | outcome = require("outcome"), 3 | utils = require("../../utils"); 4 | 5 | function ZoneCollection (region) { 6 | BaseCollection.call(this, region); 7 | } 8 | 9 | BaseCollection.extend(ZoneCollection, { 10 | 11 | /** 12 | */ 13 | 14 | _load: function (options, next) { 15 | this.api.describeAvailabilityZones(outcome.e(next).s(function (result) { 16 | next(null, result.AvailabilityZones.map(function (zone) { 17 | return { 18 | _id : zone.ZoneName, 19 | state : zone.State, 20 | name : zone.ZoneName, 21 | region : zone.RegionName 22 | } 23 | })) 24 | })); 25 | } 26 | }); 27 | 28 | module.exports = ZoneCollection; -------------------------------------------------------------------------------- /lib/ec2/utils/index.js: -------------------------------------------------------------------------------- 1 | var bindable = require("bindable"); 2 | 3 | module.exports = { 4 | 5 | /** 6 | */ 7 | 8 | mapTags: function (tagsAr) { 9 | var tags = {}; 10 | 11 | for (var i = tagsAr.length; i--;) { 12 | var tag = tagsAr[i]; 13 | tags[tag.Key] = tag.Value; 14 | } 15 | 16 | return tags; 17 | }, 18 | 19 | /** 20 | */ 21 | 22 | cleanObject: function (obj) { 23 | 24 | for (var key in obj) { 25 | if(obj[key] == null) { 26 | delete obj[key] 27 | } 28 | } 29 | 30 | // little bit of a hack - we want to get rid of dot syntax, so 31 | // use bindable.js to do it for us 32 | return new bindable.Object().setProperties(obj).context(); 33 | } 34 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"), 2 | bindable = require("bindable"), 3 | EC2 = require("./ec2"), 4 | Route53 = require("./route53"), 5 | Logger = require("./utils/logger"), 6 | Decorators = require("./decorators"), 7 | chain = require("./chain"); 8 | 9 | function Awsm (options) { 10 | 11 | this.config = new bindable.Object(options); 12 | this.logger = new Logger(undefined, this); 13 | this.ec2 = new EC2(this.config.get("ec2"), this); 14 | this.route53 = new Route53({}, this); 15 | 16 | 17 | this.chainer = chain; 18 | this.decorators = new Decorators(); 19 | } 20 | 21 | protoclass(Awsm, { 22 | 23 | /** 24 | */ 25 | 26 | chain: function () { 27 | return chain.wrap("awsm", this); 28 | }, 29 | 30 | /** 31 | * use a plugin 32 | */ 33 | 34 | use: function () { 35 | for (var i = arguments.length; i--;) { 36 | arguments[i](this); 37 | } 38 | return this; 39 | }, 40 | 41 | /** 42 | * additional plugins that might be applied 43 | * to a model such as an instance, image, security group, snapshot, volume, etc. 44 | */ 45 | 46 | decorator: function (decorator) { 47 | this.decorators.push(decorator); 48 | }, 49 | 50 | /** 51 | */ 52 | 53 | toString: function () { 54 | return this.logger.name; 55 | } 56 | }); 57 | 58 | module.exports = function (options) { 59 | return new Awsm(options); 60 | } 61 | 62 | module.exports.supportedEC2Regions = require("./ec2/regions").SUPPORTED_REGIONS; -------------------------------------------------------------------------------- /lib/route53/hostedZones/index.js: -------------------------------------------------------------------------------- 1 | var Collection = require("../../base/loadableCollection"), 2 | HostedZone = require("./model"), 3 | outcome = require("outcome"); 4 | 5 | function HostedZones (route53) { 6 | 7 | this.route53 = route53; 8 | this.api = route53.api; 9 | this.awsm = route53.awsm; 10 | this.logger = this.awsm.logger.child("hostedZones"); 11 | 12 | Collection.call(this, {}); 13 | } 14 | 15 | Collection.extend(HostedZones, { 16 | 17 | /** 18 | */ 19 | 20 | createModel: function (data) { 21 | return new HostedZone(data, this); 22 | }, 23 | 24 | /** 25 | */ 26 | 27 | _load: function (options, next) { 28 | 29 | var ops = {}; 30 | 31 | this.api.listHostedZones(ops, outcome.e(next).s(function (response) { 32 | next(null, response.HostedZones.map(function (hostedZone) { 33 | return { 34 | _id : hostedZone.Id, 35 | name : hostedZone.Name, 36 | config : hostedZone.Config 37 | } 38 | })); 39 | })); 40 | } 41 | }); 42 | 43 | module.exports = HostedZones; -------------------------------------------------------------------------------- /lib/route53/hostedZones/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../../base/loadableModel"), 2 | RecordSets = require("./recordSets"); 3 | 4 | 5 | function HostedZone (data, collection) { 6 | BaseModel.apply(this, arguments); 7 | this.api = collection.api; 8 | this.awsm = collection.awsm; 9 | this.logger = collection.logger.child(this.get("_id")); 10 | 11 | this.recordSets = new RecordSets(this); 12 | } 13 | 14 | BaseModel.extend(HostedZone, { 15 | 16 | /** 17 | */ 18 | 19 | _destroy: function (next) { 20 | 21 | }, 22 | 23 | /** 24 | */ 25 | 26 | _load: function (next) { 27 | 28 | } 29 | }) 30 | 31 | module.exports = HostedZone; -------------------------------------------------------------------------------- /lib/route53/hostedZones/recordSets/index.js: -------------------------------------------------------------------------------- 1 | var Collection = require("../../../base/loadableCollection"), 2 | RecordSet = require("./model"), 3 | outcome = require("outcome"), 4 | toarray = require("toarray"); 5 | 6 | function RecordSets (hostedZone) { 7 | 8 | this.hostedZone = hostedZone; 9 | this.api = hostedZone.api; 10 | this.awsm = hostedZone.awsm; 11 | this.logger = hostedZone.logger.child("recordSets"); 12 | 13 | Collection.call(this, {}); 14 | } 15 | 16 | Collection.extend(RecordSets, { 17 | 18 | /** 19 | */ 20 | 21 | createModel: function (data) { 22 | return new RecordSet(data, this); 23 | }, 24 | 25 | /** 26 | */ 27 | 28 | create: function (options, next) { 29 | 30 | var ops = { 31 | Name : options.name, 32 | Type : options.type, 33 | Weight : options.weight, 34 | Region : options.region, 35 | Faolover : options.failover, 36 | ttl : options.ttl, 37 | ResourceRecords : toarray(options.records).map(function (record) { 38 | return { Value: record } 39 | }) 40 | }; 41 | 42 | this.api.changeResourceRecordSets({ 43 | Action : "CREATE", 44 | ResourceRecordSet : ops 45 | }) 46 | }, 47 | 48 | /** 49 | */ 50 | 51 | _load: function (options, next) { 52 | 53 | var ops = { 54 | HostedZoneId: this.hostedZone.get("_id") 55 | }; 56 | 57 | this.api.listResourceRecordSets(ops, outcome.e(next).s(function (response) { 58 | next(null, response.ResourceRecordSets.map(function (recordSet) { 59 | 60 | return { 61 | _id : recordSet.Name + ":" + recordSet.Type, 62 | name : recordSet.Name, 63 | type : recordSet.Type, 64 | ttl : recordSet.TTL, 65 | weight : recordSet.Weight, 66 | region : recordSet.Region, 67 | Failover : recordSet.Failover, 68 | records : recordSet.ResourceRecords.map(function (record) { 69 | return record.Value 70 | }) 71 | } 72 | })); 73 | })); 74 | } 75 | }); 76 | 77 | module.exports = RecordSets; -------------------------------------------------------------------------------- /lib/route53/hostedZones/recordSets/model.js: -------------------------------------------------------------------------------- 1 | var BaseModel = require("../../../base/loadableModel"); 2 | 3 | 4 | function RecordSet (data, collection) { 5 | 6 | BaseModel.apply(this, arguments); 7 | 8 | this.api = collection.api; 9 | this.awsm = collection.awsm; 10 | this.logger = collection.logger.child(this.get("_id")); 11 | } 12 | 13 | BaseModel.extend(RecordSet, { 14 | 15 | }) 16 | 17 | module.exports = RecordSet; -------------------------------------------------------------------------------- /lib/route53/index.js: -------------------------------------------------------------------------------- 1 | var bindable = require("bindable"), 2 | protoclass = require("protoclass"), 3 | AWS = require("aws-sdk"), 4 | HostedZones = require("./hostedZones"); 5 | 6 | // http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/frames.html#!AWS/Route53.html 7 | 8 | function Route53 (options, awsm) { 9 | this.config = new bindable.Object(options); 10 | this.awsm = awsm; 11 | this.logger = awsm.logger.child("route53"); 12 | 13 | this.api = new AWS.Route53({ 14 | accessKeyId : awsm.config.get("key"), 15 | secretAccessKey : awsm.config.get("secret"), 16 | maxRetries : 15 17 | }); 18 | 19 | this.hostedZones = new HostedZones(this); 20 | } 21 | 22 | 23 | protoclass(Route53, { 24 | 25 | /** 26 | */ 27 | 28 | toString: function () { 29 | return this.logger.name; 30 | } 31 | }); 32 | 33 | module.exports = Route53; 34 | 35 | -------------------------------------------------------------------------------- /lib/utils/logger.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"); 2 | 3 | require("colors"); 4 | 5 | var logLevels = { 6 | "verbose" : ["verbose", "notice", "warn", "error"], 7 | "notice" : ["notice", "warn", "error"], 8 | "warn" : ["warn", "error"] 9 | } 10 | 11 | var logColors = { 12 | "verbose": "grey", 13 | // "notice": "green", 14 | "warn": "yellow", 15 | "error": "red" 16 | } 17 | 18 | 19 | 20 | function Logger (name, awsm) { 21 | 22 | this.name = name || ""; 23 | this.awsm = awsm; 24 | var levels = logLevels[awsm.config.get("log.level")] || logLevels.verbose; 25 | this.colors = awsm.config.get("log.colors"); 26 | 27 | var self = this; 28 | 29 | ["notice:log", "verbose:log", "warn", "error"].forEach(function (inf) { 30 | 31 | var parts = inf.split(":"), 32 | level = parts.shift(), 33 | method = parts.shift() || level; 34 | 35 | if (!~levels.indexOf(level)) { 36 | return self[level] = function () { }; 37 | } 38 | 39 | self[level] = function () { 40 | self._log(method, level, Array.prototype.slice.call(arguments, 0)); 41 | } 42 | }) 43 | } 44 | 45 | protoclass(Logger, { 46 | 47 | /** 48 | */ 49 | 50 | child: function (name) { 51 | if (!this.name) return new Logger(name, this.awsm); 52 | return new Logger(this.name + (name ? "." + name : ""), this.awsm); 53 | }, 54 | 55 | /** 56 | */ 57 | 58 | _log: function (method, level, args) { 59 | args[0] = this.name + "." + args[0]; 60 | 61 | if (this.colors && logColors[level]) { 62 | args[0] = args[0][logColors[level]]; 63 | } 64 | 65 | 66 | for (var i = args.length; i>=1; i--) { 67 | var v = args[i]; 68 | if (typeof v === "object") { 69 | args[i] = JSON.stringify(args[i], null, 2); 70 | } else if (typeof v === "string") { 71 | args[i] = '"' + v + '"'; 72 | } 73 | } 74 | 75 | console[method].apply(console, args); 76 | } 77 | }); 78 | 79 | module.exports = Logger; -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | ALL_TESTS = $(shell find ./test -name "*-test.js") 2 | 3 | test-node: 4 | ./node_modules/.bin/_mocha $(ALL_TESTS) --reporter list --timeout 9999999 --ignore-leaks --bail 5 | 6 | test-cov: 7 | CACHE=1 NODE_ENV=TEST_COV \ 8 | ./node_modules/.bin/istanbul cover \ 9 | ./node_modules/.bin/_mocha $(ALL_TESTS) --ignore-leaks 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "awsm", 3 | "version": "0.0.44", 4 | "description": "node-awsm =========", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "make test-node" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/crcn/node-awsm.git" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/crcn/node-awsm/issues" 17 | }, 18 | "dependencies": { 19 | "protoclass": "0.0.x", 20 | "bindable": "0.5.x", 21 | "toarray": "0.0.x", 22 | "outcome": "0.0.x", 23 | "sift": "0.0.x", 24 | "comerr": "0.0.x", 25 | "colors": "~0.6.2", 26 | "aws-sdk": "2.0.0-rc9", 27 | "hurryup": "0.0.x", 28 | "brasslet": "0.0.x", 29 | "async": "~0.2.10", 30 | "underscore": "~1.5.2", 31 | "memoizee": "~0.2.6", 32 | "traverse": "~0.6.6", 33 | "dsync": "0.0.2", 34 | "burrito": "~0.2.12", 35 | "flatten": "0.0.1" 36 | }, 37 | "devDependencies": { 38 | "expect.js": "~0.2.0", 39 | "mocha": "~1.17.1", 40 | "istanbul": "~0.2.4" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/flushAWSAccount.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var helpers = require("../test/helpers"), 4 | awsm = require("../"), 5 | async = require("async"); 6 | 7 | var aws = awsm(helpers.config); 8 | 9 | async.waterfall([ 10 | 11 | function removeInstances (next) { 12 | aws.ec2.instances.all(destroyAll(next, "instances")); 13 | }, 14 | 15 | function removeImages (next) { 16 | aws.ec2.images.all(destroyAll(next, "images")); 17 | }, 18 | 19 | function deallocateAddresses (next) { 20 | aws.ec2.addresses.all(destroyAll(next, "addresses")); 21 | }, 22 | 23 | function destroyKeyPairs (next) { 24 | aws.ec2.keyPairs.all(destroyAll(next, "key pairs")); 25 | }, 26 | 27 | function destroyAllSnapshots (next) { 28 | aws.ec2.snapshots.all(destroyAll(next, "snapshots")); 29 | }, 30 | 31 | function destroyAllVolumes (next) { 32 | aws.ec2.volumes.all(destroyAll(next, "volumes")); 33 | }, 34 | 35 | function destroyAllSecurityGroups (next) { 36 | aws.ec2.securityGroups.all(destroyAll(next, "security groups")); 37 | } 38 | 39 | ], function () { 40 | 41 | }); 42 | 43 | function destroyAll(next, label) { 44 | return function (err, models) { 45 | if (err) return next(err); 46 | console.log("destroying %s", label); 47 | async.eachLimit(models, 20, function (model, next) { 48 | model.destroy(function () { 49 | next(); 50 | }); 51 | }, next); 52 | } 53 | } -------------------------------------------------------------------------------- /test/ec2/addresses/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/addresses#", function () { 7 | 8 | var aws, region, instance, address; 9 | 10 | before(function (next) { 11 | aws = awsm(helpers.config); 12 | aws.ec2.regions.findOne({ name: "us-east-1" }, function (err, model) { 13 | region = model; 14 | next(); 15 | }); 16 | }); 17 | 18 | before(function (next) { 19 | region.instances.create({ imageId: helpers.images.ubuntu._id }, function (err, model) { 20 | instance = model; 21 | next(); 22 | }); 23 | }); 24 | 25 | after(function (next) { 26 | instance.destroy(next); 27 | }); 28 | 29 | it("can be allocated", function (next) { 30 | region.addresses.create(outcome.e(next).s(function (model) { 31 | address = model; 32 | next(); 33 | })); 34 | }); 35 | 36 | it("can be associated with an instance", function (next) { 37 | address.attach(instance, outcome.e(next).s(function (address) { 38 | instance.getAddress(function (err, address2) { 39 | expect(address.get("_id")).to.be(address2.get("_id")); 40 | address.getInstance(function (err, instance2) { 41 | expect(instance.get("_id")).to.be(instance2.get("_id")); 42 | expect(instance.get("addresses.publicIp")).to.equal(address.get("publicIp")); 43 | next(); 44 | }) 45 | }); 46 | })); 47 | }); 48 | 49 | it("can be detached from an instance", function (next) { 50 | address.detach(outcome.e(next).s(function () { 51 | address.getInstance(function (err, instance) { 52 | expect(!!instance).to.be(false); 53 | next(); 54 | }); 55 | })); 56 | }); 57 | 58 | it("can be destroyed", function (next) { 59 | address.destroy(next); 60 | }) 61 | }); -------------------------------------------------------------------------------- /test/ec2/core/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | expect = require("expect.js"); 4 | 5 | describe("ec2/regions#", function () { 6 | 7 | var aws; 8 | 9 | before(function () { 10 | aws = awsm(helpers.config); 11 | }) 12 | 13 | it("has ec2 instance", function () { 14 | expect(aws.ec2).not.to.be(undefined); 15 | }); 16 | }); -------------------------------------------------------------------------------- /test/ec2/images/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/images#", function () { 7 | 8 | var aws, region, instance, image, testImageName = "test-image" + Date.now(); 9 | 10 | before(function (next) { 11 | aws = awsm(helpers.config); 12 | aws.ec2.regions.findOne("us-east-1", outcome.e(next).s(function (model) { 13 | region = model; 14 | next(); 15 | })); 16 | }); 17 | 18 | // create the image from an instance 19 | before(function (next) { 20 | region.instances.create({ imageId: helpers.images.ubuntu._id, flavor: "t1.micro" }, outcome.e(next).s(function (model) { 21 | instance = model; 22 | instance.stop(function () { 23 | instance.createImage({ name: testImageName }, outcome.e(next).s(function (model) { 24 | image = model; 25 | next(); 26 | })); 27 | }); 28 | })); 29 | }); 30 | 31 | after(function (next) { 32 | instance.destroy(next); 33 | }); 34 | 35 | it("can list all available images", function (next) { 36 | region.images.all(function (err, images) { 37 | expect(images.length).to.greaterThan(0); 38 | next(); 39 | }) 40 | }); 41 | 42 | it("can create a tag", function (next) { 43 | image.tags.update({ test: "test-tag" }, outcome.e(next).s(function () { 44 | expect(image.get("tags.test")).to.be("test-tag"); 45 | next(); 46 | })); 47 | }); 48 | 49 | it("can remove a tag", function (next) { 50 | image.tags.update({ test: undefined }, outcome.e(next).s(function () { 51 | expect(image.get("tags.test")).to.be(undefined); 52 | image.tags.update({ test: "test-tag" }, next); 53 | })); 54 | }); 55 | 56 | it("can create an instance", function (next) { 57 | image.createInstance({}, outcome.e(next).s(function (instance) { 58 | instance.destroy(next); 59 | })); 60 | }); 61 | 62 | 63 | it("can migrate to another region with just a string", function (next) { 64 | image.migrate(["us-west-1"], outcome.e(next).s(function (images) { 65 | var copiedImage = images.shift(); 66 | expect(copiedImage.get("region")).to.be("us-west-1"); 67 | expect(copiedImage.get("tags.test")).to.be("test-tag"); 68 | next(); 69 | })); 70 | }); 71 | 72 | it("can migrate to another region with a region instance", function (next) { 73 | aws.ec2.regions.find({ name: {$ne: region.get("name") }}, outcome.e(next).s(function (regions) { 74 | var n = regions.length; 75 | expect(n).to.be(7); 76 | image.migrate(regions, outcome.e(next).s(function (images) { 77 | expect(images.length).to.be(n); 78 | next(); 79 | })); 80 | })); 81 | }); 82 | 83 | it("can be destroyed", function (next) { 84 | image.destroy(next); 85 | }); 86 | }); -------------------------------------------------------------------------------- /test/ec2/instances/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/instances#", function () { 7 | 8 | var aws, region, instance, securityGroup, keyPair; 9 | 10 | before(function (next) { 11 | aws = awsm(helpers.config); 12 | aws.ec2.regions.findOne("us-east-1", outcome.e(next).s(function (model) { 13 | region = model; 14 | next(); 15 | })); 16 | }); 17 | 18 | before(function (next) { 19 | region.securityGroups.create({ name: "test-sg-" + Date.now() }, outcome.e(next).s(function (model) { 20 | securityGroup = model; 21 | next(); 22 | })); 23 | }); 24 | 25 | before(function (next) { 26 | region.keyPairs.create("test-keypair", outcome.e(next).s(function (model) { 27 | keyPair = model; 28 | next(); 29 | })); 30 | }); 31 | 32 | after(function (next) { 33 | securityGroup.destroy(next); 34 | }); 35 | 36 | after(function (next) { 37 | keyPair.destroy(next); 38 | }); 39 | 40 | it("can create an instance with a security group", function (next) { 41 | region.instances.create({ type: "t1.micro", imageId: helpers.images.ubuntu._id, securityGroup: securityGroup }, outcome.e(next).s(function (model) { 42 | instance = model; 43 | console.log(JSON.stringify(instance.context(), null, 2)); 44 | expect(instance.get("type")).to.be("t1.micro"); 45 | next(); 46 | })); 47 | }); 48 | 49 | 50 | it("can create an instance with a keypair", function (next) { 51 | region.instances.create({ type: "t1.micro", imageId: helpers.images.ubuntu._id, keyPair: keyPair }, outcome.e(next).s(function (model) { 52 | expect(model.get("keyName")).to.be("test-keypair"); 53 | model.getKeyPair(outcome.e(next).s(function (keyPair) { 54 | expect(keyPair.get("name")).to.be("test-keypair"); 55 | model.destroy(next); 56 | })); 57 | })); 58 | }) 59 | 60 | 61 | it("can get the security groups of a given instance", function (next) { 62 | instance.getSecurityGroups(outcome.e(next).s(function (securityGroups) { 63 | expect(securityGroups.length).to.be(1); 64 | expect(securityGroups[0].get("_id")).to.be(securityGroup.get("_id")); 65 | next(); 66 | })); 67 | }); 68 | 69 | 70 | it("can be stopped", function (next) { 71 | instance.stop(next); 72 | }); 73 | 74 | it("can be started", function (next) { 75 | instance.start(next); 76 | }); 77 | 78 | it("can be restarted", function (next) { 79 | instance.restart(next); 80 | }); 81 | 82 | it("can create an image", function (next) { 83 | instance.stop(function () { 84 | instance.createImage(function (err, image) { 85 | image.destroy(next); 86 | }); 87 | }); 88 | }); 89 | 90 | it("can return the image of the instance", function (next) { 91 | instance.getImage(function (err, image) { 92 | expect(!!image).to.be(true); 93 | next(); 94 | }); 95 | }) 96 | 97 | it("can be destroyed", function (next) { 98 | instance.destroy(next); 99 | }); 100 | }); -------------------------------------------------------------------------------- /test/ec2/instances/securityGroup-test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crcn/node-awsm/7d6237aa4ab0fe96fb2b60e40f744242b7b2a7d6/test/ec2/instances/securityGroup-test.js -------------------------------------------------------------------------------- /test/ec2/instances/update-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/instances/update#", function () { 7 | 8 | var aws, region, instance; 9 | 10 | before(function (next) { 11 | aws = awsm(helpers.config); 12 | aws.ec2.regions.findOne("us-east-1", outcome.e(next).s(function (model) { 13 | region = model; 14 | next(); 15 | })); 16 | }); 17 | 18 | 19 | before(function (next) { 20 | region.instances.create({ type: "t1.micro", imageId: helpers.images.ubuntu._id }, outcome.e(next).s(function (model) { 21 | instance = model; 22 | next(); 23 | })); 24 | }); 25 | 26 | after(function (next) { 27 | instance.destroy(next); 28 | }); 29 | 30 | it("can resize an instance", function (next) { 31 | instance.resize("m3.medium", outcome.e(next).s(function () { 32 | expect(instance.get("type")).to.be("m3.medium"); 33 | expect(instance.get("state")).to.be("running"); 34 | instance.resize("t1.micro", next); 35 | })); 36 | }); 37 | 38 | }); -------------------------------------------------------------------------------- /test/ec2/instances/volumes-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/instances/volumes#", function () { 7 | 8 | var aws, region, instance, securityGroup; 9 | 10 | before(function (next) { 11 | aws = awsm(helpers.config); 12 | aws.ec2.regions.findOne("us-east-1", outcome.e(next).s(function (model) { 13 | region = model; 14 | next(); 15 | })); 16 | }); 17 | 18 | before(function (next) { 19 | region.instances.create({ type: "t1.micro", securityGroup: securityGroup, imageId: helpers.images.ubuntu._id }, outcome.e(next).s(function (model) { 20 | instance = model; 21 | next(); 22 | })); 23 | }); 24 | 25 | after(function (next) { 26 | instance.destroy(next); 27 | }); 28 | 29 | 30 | it("can return the list of volumes for an instance ", function (next) { 31 | instance.getVolumes(outcome.e(next).s(function (volumes) { 32 | expect(volumes.length).to.be(1); 33 | expect(volumes[0].get("attachments.0.instanceId")).to.be(instance.get("_id")); 34 | next(); 35 | })); 36 | }); 37 | }); -------------------------------------------------------------------------------- /test/ec2/keyPairs/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/keyPairs#", function () { 7 | 8 | var aws, region, instance, address; 9 | 10 | before(function (next) { 11 | aws = awsm(helpers.config); 12 | aws.ec2.regions.findOne({ name: "us-east-1" }, function (err, model) { 13 | region = model; 14 | next(); 15 | }); 16 | }); 17 | 18 | it("can create a keypair", function (next) { 19 | region.keyPairs.create({ name: "test-keypair" }, outcome.e(next).s(function (model) { 20 | keyPair = model; 21 | expect(keyPair.get("material")).not.to.be(undefined); 22 | next(); 23 | })) 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/ec2/regions/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/regions#", function () { 7 | 8 | var aws; 9 | 10 | before(function () { 11 | aws = awsm(helpers.config); 12 | }); 13 | 14 | it("can list all available regions", function (next) { 15 | aws.ec2.regions.all(outcome.e(next).s(function (regions) { 16 | expect(regions.length).to.be(8); 17 | next(); 18 | })); 19 | }); 20 | 21 | it("can filter all us regions", function (next) { 22 | aws.ec2.regions.find({ name: /us-*/ }, outcome.e(next).s(function (regions) { 23 | expect(regions.length).to.be(3); 24 | next(); 25 | })); 26 | }); 27 | }); -------------------------------------------------------------------------------- /test/ec2/securityGroups/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/securityGroups#", function () { 7 | 8 | var aws, region, volume, zone, securityGroup; 9 | 10 | before(function () { 11 | aws = awsm(helpers.config); 12 | }); 13 | 14 | before(function (next) { 15 | aws.ec2.regions.findOne({ name: "us-east-1" }, outcome.e(next).s(function (model) { 16 | region = model; 17 | next(); 18 | })); 19 | }); 20 | 21 | 22 | it("can be created", function (next) { 23 | region.securityGroups.create("test-group" + Date.now(), outcome.e(next).s(function (model) { 24 | securityGroup = model; 25 | next(); 26 | })) 27 | }); 28 | 29 | it("can authorize a port", function (next) { 30 | securityGroup.authorize(80, outcome.e(next).s(function () { 31 | expect(securityGroup.get("permissions.0.fromPort")).to.be(80); 32 | expect(securityGroup.get("permissions.0.toPort")).to.be(80); 33 | next(); 34 | })); 35 | }); 36 | 37 | it("can de-authorize a port", function (next) { 38 | securityGroup.revoke(80, outcome.e(next).s(function () { 39 | expect(securityGroup.get("permissions").length).to.be(0); 40 | next(); 41 | })); 42 | }); 43 | 44 | it("can authorize a port range", function (next) { 45 | securityGroup.authorize({ from: 91, to: 100 }, outcome.e(next).s(function () { 46 | expect(securityGroup.get("permissions.0.fromPort")).to.be(91); 47 | expect(securityGroup.get("permissions.0.toPort")).to.be(100); 48 | securityGroup.revoke({ from: 90, to: 100 }, next); 49 | })); 50 | }); 51 | 52 | it("can authorize an array of port ranges", function (next) { 53 | securityGroup.authorize({ ports: [{ from: 80, to: 90 }]}, outcome.e(next).s(function () { 54 | expect(securityGroup.get("permissions.0.fromPort")).to.be(80); 55 | expect(securityGroup.get("permissions.0.toPort")).to.be(90); 56 | next(); 57 | })); 58 | }); 59 | 60 | it("can be destroyed", function (next) { 61 | securityGroup.destroy(next); 62 | }); 63 | }); -------------------------------------------------------------------------------- /test/ec2/snapshots/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/snapshots#", function () { 7 | 8 | }); -------------------------------------------------------------------------------- /test/ec2/volumes/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/volumes#", function () { 7 | 8 | var aws, region, volume, zone, instance; 9 | 10 | before(function () { 11 | aws = awsm(helpers.config); 12 | }); 13 | 14 | before(function (next) { 15 | aws.ec2.regions.findOne({ name: "us-east-1" }, outcome.e(next).s(function (model) { 16 | region = model; 17 | next(); 18 | })); 19 | }); 20 | 21 | before(function (next) { 22 | region.zones.all(function (err, zones) { 23 | zone = zones.shift(); 24 | next(); 25 | }) 26 | }); 27 | 28 | 29 | before(function (next) { 30 | region.instances.create({ zone: zone, type: "t1.micro", imageId: helpers.images.ubuntu._id }, outcome.e(next).s(function (model) { 31 | instance = model; 32 | next(); 33 | })) 34 | }); 35 | 36 | it("can create a volume", function (next) { 37 | region.volumes.create({ 38 | size: 8, 39 | zone: zone.get("_id") 40 | }, outcome.e(next).s(function (model) { 41 | volume = model; 42 | next(); 43 | })); 44 | }); 45 | 46 | it("can be attached to an instance", function (next) { 47 | var o = outcome.e(next); 48 | volume.attach(instance, o.s(function () { 49 | instance.getVolumes(o.s(function (volumes) { 50 | expect(volumes.length).to.be(2); 51 | next(); 52 | })); 53 | })); 54 | }); 55 | 56 | it("can be detached from an instance", function (next) { 57 | volume.detach(function () { 58 | instance.getVolumes(outcome.e(next).s(function (volumes) { 59 | expect(volumes.length).to.be(1); 60 | next(); 61 | })); 62 | }); 63 | }); 64 | 65 | it("can create a snapshot", function (next) { 66 | volume.createSnapshot(outcome.e(next).s(function (snapshot) { 67 | snapshot.getVolume(function (err, volume2) { 68 | expect(volume.get("_id")).to.be(volume2.get("_id")); 69 | next(); 70 | }) 71 | })); 72 | }); 73 | 74 | it("can be destroyed", function (next) { 75 | volume.destroy(next); 76 | }) 77 | }); -------------------------------------------------------------------------------- /test/ec2/zones/basic-test.js: -------------------------------------------------------------------------------- 1 | var awsm = require("../../../"), 2 | helpers = require("../../helpers"), 3 | outcome = require("outcome"), 4 | expect = require("expect.js"); 5 | 6 | describe("ec2/zones#", function () { 7 | 8 | var aws, region; 9 | 10 | before(function () { 11 | aws = awsm(helpers.config); 12 | }); 13 | 14 | before(function (next) { 15 | aws.ec2.regions.findOne({ name: "us-east-1" }, outcome.e(next).s(function (model) { 16 | region = model; 17 | next(); 18 | })); 19 | }); 20 | 21 | it("can list all available zones in a given region", function (next) { 22 | region.zones.all(function (err, zones) { 23 | expect(zones.length).to.be(3); 24 | next(); 25 | }); 26 | }) 27 | }); -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | config: require("./config"), 3 | images: { 4 | ubuntu: { 5 | _id: "ami-a73264ce" 6 | } 7 | } 8 | }; --------------------------------------------------------------------------------