diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1a6bd4587 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +package-lock.json binary diff --git a/.gitignore b/.gitignore index 17168f4e1..318fba425 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,7 @@ azure_error coverage.html .coveralls.yml pkgcloud.lcov* +node-jscoverage +.nyc_output +*.tgz +*.tar.gz diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 000000000..77bab72f6 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,89 @@ +{ + "predef" : ["-context"], + "maxerr" : 50, // {int} Maximum error before stopping + + // Enforcing + "bitwise" : false, // true: Prohibit bitwise operators (&, |, ^, etc.) + "camelcase" : false, // true: Identifiers must be in camelCase + "curly" : true, // true: Require {} for every new block or scope + "eqeqeq" : false, // true: Require triple equals (===) for comparison + "forin" : false, // true: Require filtering for..in loops with obj.hasOwnProperty() + "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. + "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` + "indent" : false, // {int} Number of spaces to use for indentation + "latedef" : true, // true: Require variables/functions to be defined before being used + "newcap" : true, // true: Require capitalization of all constructor functions e.g. `new F()` + "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` + "noempty" : true, // true: Prohibit use of empty blocks + "nonbsp" : false, // true: Prohibit "non-breaking whitespace" characters. + "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) + "plusplus" : false, // true: Prohibit use of `++` & `--` + "quotmark" : "single", // Quotation mark consistency: + // false : do nothing (default) + // true : ensure whatever is used is consistent + // "single" : require single quotes + // "double" : require double quotes + "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) + "unused" : true, // true: Require all defined variables be used + "strict" : false, // true: Requires all functions run in ES5 Strict Mode + "maxparams" : false, // {int} Max number of formal params allowed per function + "maxdepth" : false, // {int} Max depth of nested blocks (within functions) + "maxstatements" : false, // {int} Max number statements per function + "maxcomplexity" : false, // {int} Max cyclomatic complexity per function + "maxlen" : false, // {int} Max number of characters per line + + // Relaxing + "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) + "boss" : true, // true: Tolerate assignments where comparisons would be expected + "debug" : true, // true: Allow debugger statements e.g. browser breakpoints. + "eqnull" : true, // true: Tolerate use of `== null` + "es5" : false, // true: Allow ES5 syntax (ex: getters and setters) + "esnext" : true, // true: Allow ES.next (ES6) syntax (ex: `const`) + "moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features) + // (ex: `for each`, multiple try/catch, function expression…) + "evil" : true, // true: Tolerate use of `eval` and `new Function()` + "expr" : true, // true: Tolerate `ExpressionStatement` as Programs + "funcscope" : true, // true: Tolerate defining variables inside control statements + "globalstrict" : true, // true: Allow global "use strict" (also enables 'strict') + "iterator" : true, // true: Tolerate using the `__iterator__` property + "lastsemic" : true, // true: Tolerate omitting a semicolon for the last statement of a 1-line block + "laxbreak" : true, // true: Tolerate possibly unsafe line breakings + "laxcomma" : true, // true: Tolerate comma-first style coding + "loopfunc" : true, // true: Tolerate functions being defined in loops + "multistr" : true, // true: Tolerate multi-line strings + "noyield" : true, // true: Tolerate generator functions with no yield statement in them. + "notypeof" : true, // true: Tolerate invalid typeof operator values + "proto" : true, // true: Tolerate using the `__proto__` property + "scripturl" : true, // true: Tolerate script-targeted URLs + "shadow" : true, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` + "sub" : true, // true: Tolerate using `[]` notation when it can still be expressed in dot notation + "supernew" : true, // true: Tolerate `new function () { ... };` and `new Object;` + "validthis" : true, // true: Tolerate using this in a non-constructor function + "-W086" : true, // true: Allow fall-through; see https://github.com/jshint/jshint/issues/18 + "-W008" : true, // true: Allow leading decimal point; see https://jslinterrors.com/a-leading-decimal-point-can-be-confused-with-a-dot-a + "-W041" : true, // true: Ignore strict comparison checking; see https://github.com/jshint/jshint/blob/a643f3fec0632249dcd78d0cf5f200d9166b587d/src/messages.js#L115 + "-W082" : true, // true: Allow function declarations inside blocks; see https://jslinterrors.com/function-statements-should-not-be-placed-in-blocks + + // Environments + "browser" : false, // Web Browser (window, document, etc) + "browserify" : false, // Browserify (node.js code in the browser) + "couch" : false, // CouchDB + "devel" : false, // Development/debugging (alert, confirm, etc) + "dojo" : false, // Dojo Toolkit + "jasmine" : false, // Jasmine + "jquery" : false, // jQuery + "mocha" : true, // Mocha + "mootools" : false, // MooTools + "node" : true, // Node.js + "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) + "prototypejs" : false, // Prototype and Scriptaculous + "qunit" : false, // QUnit + "rhino" : false, // Rhino + "shelljs" : false, // ShellJS + "worker" : false, // Web Workers + "wsh" : false, // Windows Scripting Host + "yui" : false, // Yahoo User Interface + + // Custom Globals + "globals" : {} // additional predefined global variables +} diff --git a/.npmignore b/.npmignore index e19255845..260ffcaef 100644 --- a/.npmignore +++ b/.npmignore @@ -1 +1,15 @@ -test/* \ No newline at end of file +.DS_Store +*.log +*.notes +.*.sw[op] +.project +.idea +azure_error +coverage.html +.coveralls.yml +pkgcloud.lcov* +node-jscoverage +.nyc_output +test/* +*.tgz +*.tar.gz diff --git a/.travis.yml b/.travis.yml index 340271c98..9c67e9deb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,10 @@ language: node_js node_js: - - "0.8" - - "0.10" - +- '8' +- '10' notifications: - email: - - travis@nodejitsu.com - irc: "irc.freenode.org#pkgcloud" - -before_script: - - git clone git://github.com/visionmedia/node-jscoverage.git - - pushd node-jscoverage - - ./configure - - make - - popd + irc: irc.freenode.org#pkgcloud + slack: + secure: RjlcoxHiKzep+CdLSC4UcL6Wsrv/4F3mzCwnDhIFsoH53XZ4t5oexLyaQxZFVq6KYes7nv2D/w2jUX7z7LBCZ6N0p8Sf/ao2s5O71ETDYDajtQjdEC1/w8PflC61b1EsVfE1tDbDtkFJtVXWKCBUAfbvrMYZwP0f8Ffhf3Bvg2I= +script: npm test +after_success: npm run coverage diff --git a/CHANGELOG.md b/CHANGELOG.md index 143370221..a47e3ac36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,116 @@ +## CHANGELOG + +## v2.2.0 + +- [#678] [@jcrugzz] support more aws auth scenarios via credentials object or sessionToken. + +## v2.1.1 + +- [#666], [@guischdi] Fix GCS upload. + +## v2.1.0 + +- Add content encoding option for s3 upload + +## v2.0.0 + +- [BREAKING] Drop all legacy providers. +- Update dependencies. +- Documentation fixes. + - Fix broken links to databases.md + - Replace Openstack with OpenStack + - Fix page title markdown formatting +- `this.protocol` from client instance is used as the URL protocol + as it should be. Fixes [#526]. + +## v1.7.0 + +* Amazon use native `aws-sdk` for `s3` uploads instead of `s3-upload-stream` module +* Update the `aws-sdk` to `2.382.0` +* Amazon storage client.upload - expose concurrency - `queueSize` and `partSize` configurability and add ability to abort an upload + +## v1.6.0 + +- Equivalent to `v1.5.0`. + +## v1.5.0 + +- Added 1&1 support with tests. +- Set Content-Type with Google storage. Fixes [#635]. +- Attempt to restrict to `node@6` and `node@8`. +- [fix] Patched RegEx DoS vuln & Remote Memory Exposure vuln. + This addresses two major security issues: + - https://nodesecurity.io/advisories/309 + - https://nodesecurity.io/advisories/535 +- Fix typo in OpenStack `createSecurityGroupRule`. Fixes [#556]. +- Fix typo in `createSecurityGroup`. Fixes [#454]. +- Fix breaking test due to expired auth token. + +## v1.4.0 + +- Pass in other aws options that allow an s3 like API to be configured correctly +- Fixing broken merge from replacing underscore with lodash + +## v1.3.0 +* OpenStack identity v3 (keystone) support, Issue [#367](//github.com/pkgcloud/pkgcloud/issues/367), [#477](//github.com/pkgcloud/pkgcloud/issues/477), PR [#461](//github.com/pkgcloud/pkgcloud/pull/461) +* OpenStack cancel client download, Issue [#379](//github.com/pkgcloud/pkgcloud/issues/379), PR [#416](//github.com/pkgcloud/pkgcloud/pull/416) +* OpenStack improved directory support for getFiles, PR [#390](//github.com/pkgcloud/pkgcloud/pull/390) +* Add support for prepending a custom user agent, Issue [#394](//github.com/pkgcloud/pkgcloud/issues/394), PR [#395](//github.com/pkgcloud/pkgcloud/pull/395) +* OpenStack fixed storage copy function, Issue [#396](//github.com/pkgcloud/pkgcloud/issues/396) , PR [#350](//github.com/pkgcloud/pkgcloud/pull/350) +* OpenStack allow non-strict SSL, PR [#397](//github.com/pkgcloud/pkgcloud/pull/397) +* Adding a refresh method on the stack model, Issue [#398](//github.com/pkgcloud/pkgcloud/issues/398), PR [#402](//github.com/pkgcloud/pkgcloud/pull/402) +* Google storage return metadata in success handler, Issue [#400](//github.com/pkgcloud/pkgcloud/issues/400), PR [#401](//github.com/pkgcloud/pkgcloud/pull/401) +* OpenStack added template outputs field to stack, PR [#403](//github.com/pkgcloud/pkgcloud/pull/403) +* Amazon fixes to AWS config, Issue [#406](//github.com/pkgcloud/pkgcloud/issues/406), PR [#407](//github.com/pkgcloud/pkgcloud/pull/xxx), [#409](//github.com/pkgcloud/pkgcloud/pull/407) +* Added support for nestedDepth option to stack getResources method, Issue [#410](//github.com/pkgcloud/pkgcloud/issues/410), PR [#411](//github.com/pkgcloud/pkgcloud/pull/411) +* Added support for networking security groups, security group rules, PR [#412](//github.com/pkgcloud/pkgcloud/pull/412) +* Allow passing options to rebuildServer, Issue [#414](//github.com/pkgcloud/pkgcloud/issues/414), PR [#415](//github.com/pkgcloud/pkgcloud/pull/415) +* Allow deleteStack to accept stack object or stack name, Issue [#418](//github.com/pkgcloud/pkgcloud/issues/418), PR [#420](//github.com/pkgcloud/pkgcloud/pull/420) +* Amazon createServer: use pluralized keys for SecurityGroups/SecurityGroupIds, Issue [#432](//github.com/pkgcloud/pkgcloud/issues/432), PR [#433](//github.com/pkgcloud/pkgcloud/pull/433) +* RackSpace, OpenStack add enableRootUser and listRootStatus, PR [#438](//github.com/pkgcloud/pkgcloud/pull/438) +* Amazon storage added cache control support to s3 buckets, PR [#447](//github.com/pkgcloud/pkgcloud/pull/447) +* OpenStack compute added deleteRule, PR [#456](//github.com/pkgcloud/pkgcloud/pull/456) +* DigitalOcean ported provider to APIv2, PR [#470](//github.com/pkgcloud/pkgcloud/pull/470) +* OpenStack handle request errors in client.upload flow, Issue [#481](//github.com/pkgcloud/pkgcloud/issues/481), PR [#484](//github.com/pkgcloud/pkgcloud/pull/484) +* OpenStack added adminPass attribute to createServer, PR [#486](//github.com/pkgcloud/pkgcloud/pull/486) +* Amazon added AWS specific options for cache control and file encryption, PR [#496](//github.com/pkgcloud/pkgcloud/pull/496) +* Misc doc fixes, PR [#417](//github.com/pkgcloud/pkgcloud/pull/417), [#426](//github.com/pkgcloud/pkgcloud/pull/426), [#429](//github.com/pkgcloud/pkgcloud/pull/429), [#442](//github.com/pkgcloud/pkgcloud/pull/442), [#444](//github.com/pkgcloud/pkgcloud/pull/444), [#449](//github.com/pkgcloud/pkgcloud/pull/449), [#498](//github.com/pkgcloud/pkgcloud/pull/498) + +## v1.2.0 +* Added Support for Openstack CDN (Poppy) + +## v1.1.0 +* Added support for Google Cloud Storage +* Added support for Rackspace Cloud Networks + +## v1.0.3 +* Adding support for Openstack Trove, and adding HP, rackspace providers + +## v1.0.2 +* Adding support for OpenStack Cinder + +## v1.0.1 +* Adding a rackspace orchestration provider + +## v1.0.0 +* Requires node 0.10, dropping support for node v0.8 +* Fundamentally changed the streaming file api for upload. No longer takes a callback. See #332 +* Significant cleanup across storage apis across providers +* Added `toJSON` on all models +* Changed underlying Amazon provider to use aws-sdk +* Added Openstack Heat provider +* updated all package dependencies, including removing `utile` +* static website support for Rackspace Cloud Files +* Added compute.keys support for HP Compute provider + +## v0.9.6 +* Fixed a long-standing bug in openstack.compute.getFlavor #292 + +## v0.9.5 +* Openstack Network service. +* Added support for HP Cloud provider. +* Added support for Rackspace Storage Temporary URLs + ## v0.9.4 * Added support for os-security-groups compute extension @@ -91,4 +204,3 @@ ## v0.7.2 * Added a pkgcloud User-Agent for outbound HTTP requests #134 * Added tests for core compute method signatures - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e7c1b09bf..724ed2a39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,7 +26,8 @@ The only issues we accept are bug reports or feature requests. Bugs must be isol - Please tag your commit depending what it does (`[misc]`, `[docs]`, `[database]`, `[compute]`) - Follow the style guide in code and docs. - Your pull request should pass the tests and the `travis-ci` build. This will be reviewed by the maintainer. - + - Before making the pull request, please run the unit tests using `npm test`. + - Before making the pull request, please run the lint tests using `npm run lint`. ## Coding standards: JS @@ -46,7 +47,7 @@ The only issues we accept are bug reports or feature requests. Bugs must be isol ## Adding tests to my pull requests -The tests are written using [mocha](http://visionmedia.github.io/mocha/) and given the nature of `pkgcloud` test against third-party APIs are very slow specially in case of IaaS providers, so, we encourage the use of [`hock`](https://github.com/mmalecki) for mocking the responses. +The tests are written using [mocha](http://mochajs.org/) and given the nature of `pkgcloud` test against third-party APIs are very slow specially in case of IaaS providers, so, we encourage the use of [`hock`](https://github.com/mmalecki/hock) for mocking the responses. Be familiar with the whole test suite and its helpers and other utilities on `test/` directory, read it to see examples of tests. @@ -57,7 +58,7 @@ var hock = require('hock'); // ... ``` -As `hock` has a similar API to [nock](https://github.com/flatiron/nock), you can easily record the API calls using `nock`. do this using the `recorder.rec()` method offer by `nock` just add this before the test declaration: +As `hock` has a similar API to [nock](https://github.com/pgte/nock), you can easily record the API calls using `nock`. do this using the `recorder.rec()` method offer by `nock` just add this before the test declaration: ``` js nock.recorder.rec(); diff --git a/LICENSE b/LICENSE index 4b467e5d7..58f564e66 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011 Nodejitsu Inc. +Copyright (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -16,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. \ No newline at end of file +THE SOFTWARE. diff --git a/Makefile b/Makefile deleted file mode 100644 index aac56eae4..000000000 --- a/Makefile +++ /dev/null @@ -1,34 +0,0 @@ -MOCHA_OPTS=-t 4000 test/*/*/*-test.js test/*/*/*/*-test.js -REPORTER = spec - -check: test - -test: test-unit - -test-unit: - @NODE_ENV=test MOCK=on ./node_modules/.bin/mocha \ - --reporter $(REPORTER) \ - $(MOCHA_OPTS) - -lib-cov: - jscoverage --encoding=UTF-8 lib lib-cov - -test-cov: lib-cov - mv lib lib-bak - mv lib-cov lib - $(MAKE) test REPORTER=html-cov > coverage.html - rm -rf lib - mv lib-bak lib - -test-coveralls: lib-cov - echo TRAVIS_JOB_ID $(TRAVIS_JOB_ID) - mv lib lib-bak - mv lib-cov lib - $(MAKE) test REPORTER=mocha-lcov-reporter > pkgcloud.lcov.raw - rm -rf lib - mv lib-bak lib - sed "s#SF:#SF:$(PWD)/lib/#g" pkgcloud.lcov.raw > pkgcloud.lcov - ./node_modules/coveralls/bin/coveralls.js < pkgcloud.lcov - rm pkgcloud.lcov pkgcloud.lcov.raw - -.PHONY: test diff --git a/README.md b/README.md index 3368d4837..3c9f961e1 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # pkgcloud [![Build Status](https://secure.travis-ci.org/pkgcloud/pkgcloud.png?branch=master)](http://travis-ci.org/pkgcloud/pkgcloud) [![NPM version](https://badge.fury.io/js/pkgcloud.png)](http://badge.fury.io/js/pkgcloud) +[![Join the chat at https://gitter.im/pkgcloud/pkgcloud](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/pkgcloud/pkgcloud?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + pkgcloud is a standard library for node.js that abstracts away differences among multiple cloud providers. * [Getting started](#getting-started) @@ -8,12 +10,15 @@ pkgcloud is a standard library for node.js that abstracts away differences among * [Supported APIs](#supported-apis) * [Compute](#compute) * [Storage](#storage) - * [Uploading Files](#uploading) - * [Downloading Files](#downloading) + * [Uploading Files](#upload-a-file) + * [Downloading Files](#download-a-file) * [Database](#databases) * [DNS](#dns----beta) *(beta)* * [Block Storage](#block-storage----beta) *(beta)* * [Load Balancers](#load-balancers----beta) *(beta)* +* [Orchestration](#orchestration----beta) *(beta)* +* [Network](#network----beta) *(beta)* +* [CDN](#cdn----beta) *(beta)* * _Fine Print_ * [Installation](#installation) * [Tests](#tests) @@ -31,7 +36,7 @@ You can install `pkgcloud` via `npm` or add to it to [dependencies](https://npmj npm install pkgcloud ``` -Currently there are six service types which are handled by pkgcloud: +Currently there are nine service types which are handled by pkgcloud: * [Compute](#compute) * [Storage](#storage) @@ -39,9 +44,27 @@ Currently there are six service types which are handled by pkgcloud: * [DNS](#dns----beta) *(beta)* * [Block Storage](#block-storage----beta) *(beta)* * [Load Balancers](#load-balancers----beta) *(beta)* +* [Network](#network----beta) *(beta)* +* [Orchestration](#orchestration----beta) *(beta)* +* [CDN](#cdn----beta) *(beta)* In our [Roadmap](#roadmap), we plan to add support for more services, such as Queueing, Monitoring, and more. Additionally, we plan to implement more providers for the *beta* services, thus moving them out of *beta*. +### User Agent + +By default, all pkgcloud HTTP requests will have a user agent with the library and version: `nodejs-pkgcloud/x.y.z` where `x.y.z` is the current version. + +You can get this from a client at any time by calling `client.getUserAgent();`. Some providers may have an additional suffix as a function of the underlying HTTP stacks. + +You can also set a custom User Agent prefix: + +```javascript +client.setCustomUserAgent('my-app/1.2.3'); + +// returns "my-app/1.2.3 nodejs-pkgcloud/1.1.0" +client.getUserAgent(); +``` + ### Basic APIs for pkgcloud @@ -49,13 +72,13 @@ Services provided by `pkgcloud` are exposed in two ways: * **By service type:** For example, if you wanted to create an API client to communicate with a compute service you could simply: -``` js +``` js var client = require('pkgcloud').compute.createClient({ // - // The name of the provider (e.g. "joyent") + // The name of the provider (e.g. "openstack") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -65,7 +88,7 @@ Services provided by `pkgcloud` are exposed in two ways: * **By provider name:** For example, if you knew the name of the provider you wished to communicate with you could do so directly: ``` js - var client = require('pkgcloud').providers.joyent.compute.createClient({ + var client = require('pkgcloud').providers.openstack.compute.createClient({ // // ... Provider specific credentials // @@ -96,26 +119,35 @@ If a service does not have at least two providers, it is considered a *beta* int * [Amazon](docs/providers/amazon.md#using-compute) * [Azure](docs/providers/azure.md#using-compute) * [DigitalOcean](docs/providers/digitalocean.md#using-compute) - * [Joyent](docs/providers/joyent.md#using-compute) + * [HP](docs/providers/hp/compute.md) * [Openstack](docs/providers/openstack/compute.md) * [Rackspace](docs/providers/rackspace/compute.md) * **[Storage](#storage)** * [Amazon](docs/providers/amazon.md#using-storage) * [Azure](docs/providers/azure.md#using-storage) + * [Google](docs/providers/google.md) + * [HP](docs/providers/hp/storage.md) * [Openstack](docs/providers/openstack/storage.md) * [Rackspace](docs/providers/rackspace/storage.md) * **[Database](#databases)** - * [IrisCouch](docs/providers/iriscouch.md) - * [MongoLab](docs/providers/mongolab.md) * [Rackspace](docs/providers/rackspace/database.md) - * [MongoHQ](docs/providers/mongohq.md) - * [RedisToGo](docs/providers/redistogo.md) * **[DNS](#dns----beta)** *(beta)* * [Rackspace](docs/providers/rackspace/dns.md) * **[Block Storage](#block-storage----beta)** *(beta)* * [Rackspace](docs/providers/rackspace/blockstorage.md) + * [Openstack](docs/providers/openstack/blockstorage.md) * **[Load Balancers](#load-balancers----beta)** *(beta)* * [Rackspace](docs/providers/rackspace/loadbalancer.md) +* **[Orchestration](#orchestration----beta)** *(beta)* + * [Openstack](docs/providers/openstack/orchestration.md) + * [Rackspace](docs/providers/rackspace/orchestration.md) +* **[Network](#network----beta)** *(beta)* + * [HP](docs/providers/hp/network.md) + * [Openstack](docs/providers/openstack/network.md) + * [Rackspace](docs/providers/rackspace/network.md) +* **[CDN](#cdn----beta)** *(beta)* + * [Openstack](docs/providers/openstack/cdn.md) + * [Rackspace](docs/providers/rackspace/cdn.md) ## Compute @@ -124,10 +156,10 @@ The `pkgcloud.compute` service is designed to make it easy to provision and work ``` js var client = require('pkgcloud').compute.createClient({ // - // The name of the provider (e.g. "joyent") + // The name of the provider (e.g. "openstack") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -139,7 +171,7 @@ Each compute provider takes different credentials to authenticate; these details * [Amazon](docs/providers/amazon.md#using-compute) * [Azure](docs/providers/azure.md#using-compute) * [DigitalOcean](docs/providers/digitalocean.md#using-compute) -* [Joyent](docs/providers/joyent.md#using-compute) +* [HP](docs/providers/hp/compute.md) * [Openstack](docs/providers/openstack/compute.md) * [Rackspace](docs/providers/rackspace/compute.md) @@ -171,10 +203,10 @@ To get started with a `pkgcloud.storage` client just create one: ``` js var client = require('pkgcloud').storage.createClient({ // - // The name of the provider (e.g. "joyent") + // The name of the provider (e.g. "openstack") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -185,6 +217,8 @@ Each storage provider takes different credentials to authenticate; these details * [Amazon](docs/providers/amazon.md#using-storage) * [Azure](docs/providers/azure.md#using-storage) +* [Google](docs/providers/google.md#using-storage) +* [HP](docs/providers/hp/storage.md) * [Openstack](docs/providers/openstack/storage.md) * [Rackspace](docs/providers/rackspace/storage.md) @@ -197,7 +231,7 @@ Each instance of `pkgcloud.storage.Client` returned from `pkgcloud.storage.creat * `client.getContainer(containerName, function (err, container) { })` ### File -* `client.upload(options, function (err) { })` +* `client.upload(options)` * `client.download(options, function (err) { })` * `client.getFiles(container, function (err, files) { })` * `client.getFile(container, file, function (err, server) { })` @@ -209,22 +243,33 @@ Both the `.upload(options)` and `.download(options)` have had **careful attentio ``` js var pkgcloud = require('pkgcloud'), fs = require('fs'); - + var client = pkgcloud.storage.createClient({ /* ... */ }); - - fs.createReadStream('a-file.txt').pipe(client.upload({ + + var readStream = fs.createReadStream('a-file.txt'); + var writeStream = client.upload({ container: 'a-container', remote: 'remote-file-name.txt' - })); + }); + + writeStream.on('error', function(err) { + // handle your error case + }); + + writeStream.on('success', function(file) { + // success, file will be a File model + }); + + readStream.pipe(writeStream); ``` ### Download a File ``` js var pkgcloud = require('pkgcloud'), fs = require('fs'); - + var client = pkgcloud.storage.createClient({ /* ... */ }); - + client.download({ container: 'a-container', remote: 'remote-file-name.txt' @@ -233,17 +278,17 @@ Both the `.upload(options)` and `.download(options)` have had **careful attentio ## Databases -The `pkgcloud.database` service is designed to consistently work with a variety of Database-as-a-Service (DBaaS) providers. +The `pkgcloud.database` service is designed to consistently work with a variety of Database-as-a-Service (DBaaS) providers. To get started with a `pkgcloud.storage` client just create one: ``` js var client = require('pkgcloud').database.createClient({ // - // The name of the provider (e.g. "joyent") + // The name of the provider (e.g. "openstack") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -252,14 +297,6 @@ To get started with a `pkgcloud.storage` client just create one: Each database provider takes different credentials to authenticate; these details about each specific provider can be found below: -* **CouchDB** - * [IrisCouch](docs/providers/iriscouch.md#couchdb) -* **MongoDB** - * [MongoLab](docs/providers/mongolab.md) - * [MongoHQ](docs/providers/mongohq.md) -* **Redis** - * [IrisCouch](docs/providers/iriscouch.md#redis) - * [RedisToGo](docs/providers/redistogo.md) * **MySQL** * [Rackspace](docs/providers/rackspace/databases.md) * **Azure Tables** @@ -285,7 +322,7 @@ To get started with a `pkgcloud.dns` client just create one: // The name of the provider (e.g. "rackspace") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -314,7 +351,7 @@ Each instance of `pkgcloud.dns.Client` returned from `pkgcloud.dns.createClient` ## Block Storage -- Beta -##### Note: Block Storage is considered Beta until there are multiple providers; presently only Rackspace are supported. +##### Note: Block Storage is considered Beta until there are multiple providers; presently only Openstack and Rackspace are supported. The `pkgcloud.blockstorage` service is designed to make it easy to create and manage block storage volumes and snapshots. @@ -326,7 +363,7 @@ To get started with a `pkgcloud.blockstorage` client just create one: // The name of the provider (e.g. "rackspace") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -336,6 +373,7 @@ To get started with a `pkgcloud.blockstorage` client just create one: #### Providers * [Rackspace](docs/providers/rackspace/blockstorage.md) +* [Openstack](docs/providers/openstack/blockstorage.md) Each instance of `pkgcloud.blockstorage.Client` returned from `pkgcloud.blockstorage.createClient` has a set of uniform APIs: @@ -367,7 +405,7 @@ To get started with a `pkgcloud.loadbalancer` client just create one: // The name of the provider (e.g. "rackspace") // provider: 'provider-name', - + // // ... Provider specific credentials // @@ -393,6 +431,157 @@ Each instance of `pkgcloud.loadbalancer.Client` returned from `pkgcloud.loadbala * `client.updateNode(loadBalancer, node, function (err) { })` * `client.removeNode(loadBalancer, node, function (err) { })` +## Network -- Beta + +##### Note: Network is considered Beta until there are multiple providers; presently only HP & Openstack providers are supported. + +The `pkgcloud.network` service is designed to make it easy to create and manage networks. + +To get started with a `pkgcloud.network` client just create one: + +``` js + var client = require('pkgcloud').network.createClient({ + // + // The name of the provider (e.g. "openstack") + // + provider: 'provider-name', + + // + // ... Provider specific credentials + // + }); +``` + +#### Providers + +* [HP](docs/providers/hp/network.md) +* [Openstack](docs/providers/openstack/network.md) +* [Rackspace](docs/providers/rackspace/network.md) + + +Each instance of `pkgcloud.network.Client` returned from `pkgcloud.network.createClient` has a set of uniform APIs: + +### Networks +* `client.getNetworks(options, function (err, networks) { })` +* `client.getNetwork(network, function (err, network) { })` +* `client.createNetwork(options, function (err, network) { })` +* `client.updateNetwork(network, function (err, network) { })` +* `client.deleteNetwork(network, function (err, networkId) { })` + +### Subnets +* `client.getSubnets(options, function (err, subnets) { })` +* `client.getSubnet(subnet, function (err, subnet) { })` +* `client.createSubnet(options, function (err, subnet) { })` +* `client.updateSubnet(subnet, function (err, subnet) { })` +* `client.deleteSubnet(subnet, function (err, subnetId) { })` + +### Ports +* `client.getPorts(options, function (err, ports) { })` +* `client.getPort(port, function (err, port) { })` +* `client.createPort(options, function (err, port) { })` +* `client.updatePort(port, function (err, port) { })` +* `client.deletePort(port, function (err, portId) { })` + +## Orchestration -- Beta + +##### Note: Orchestration is considered Beta until there are multiple providers; presently only Openstack are supported. + +The `pkgcloud.orchestration` service is designed to allow you to access Openstack Heat via node.js. You can manage stacks and resources from within any node.js application. + +To get started with a `pkgcloud.orchestration` client just create one: + +``` js + var client = require('pkgcloud').orchestration.createClient({ + // + // The name of the provider (e.g. "openstack") + // + provider: 'provider-name', + + // + // ... Provider specific credentials + // + }); +``` + +#### Providers + +* [Openstack](docs/providers/openstack/orchestration.md) +* [Rackspace](docs/providers/rackspace/orchestration.md) + +Each instance of `pkgcloud.orchestration.Client` returned from `pkgcloud.orchestration.createClient` has a set of uniform APIs: + +### Stack +* `client.getStack(stack, function (err, stack) { })` +* `client.getStacks(options, function (err, stacks) { })` +* `client.createStack(details, function (err, stack) { })` +* `client.previewStack(details, function (err, stack) { })` +* `client.adoptStack(details, function (err, stack) { })` +* `client.updateStack(stack, function (err, stack) { })` +* `client.deleteStack(stack, function (err) { })` +* `client.abandonStack(stack, function (err, abandonedStack) { })` +* `client.getTemplate(stack, function (err, template) { })` + +### Resources +* `client.getResource(stack, resource, function (err, resource) { })` +* `client.getResources(stack, function (err, resources) { })` +* `client.getResourceTypes(function (err, resourceTypes) { })` +* `client.getResourceSchema(resourceType, function (err, resourceSchema) { })` +* `client.getResourceTemplate(resourceType, function (err, resourceTemplate) { })` + +### Events +* `client.getEvent(stack, resource, eventId, function (err, event) { })` +* `client.getEvents(stack, function (err, events) { })` +* `client.getResourceEvents(stack, resource, function (err, events) { })` + +### Templates +* `client.validateTemplate(template, function (err, template) { })` + +## CDN -- Beta + +##### Note: CDN is considered Beta until there are multiple providers; presently only Openstack and Rackspace are supported. + +The `pkgcloud.cdn` service is designed to allow you to access Openstack Poppy via node.js. You can manage services and flavors from within any node.js application. + +To get started with a `pkgcloud.cdn` client just create one: + +``` js + var client = require('pkgcloud').cdn.createClient({ + // + // The name of the provider (e.g. "openstack") + // + provider: 'provider-name', + + // + // ... Provider specific credentials + // + }); +``` + +#### Providers + +* [Openstack](docs/providers/openstack/cdn.md) +* [Rackspace](docs/providers/rackspace/cdn.md) + +Each instance of `pkgcloud.cdn.Client` returned from `pkgcloud.cdn.createClient` has a set of uniform APIs: + +### Base +* `client.getHomeDocument(function (err, homeDocument) { })` +* `client.getPing(function (err) { })` + +### Service +* `client.getService(service, function (err, service) { })` +* `client.getServices(options, function (err, services) { })` +* `client.createService(details, function (err, service) { })` +* `client.updateService(service, function (err, service) { })` +* `client.deleteService(service, function (err) { })` + +### Service Assets +* `client.deleteServiceCachedAssets(service, assetUrl, function(err) { })` + +### Flavors +* `client.getFlavor(flavor, function (err, flavor) { })` +* `client.getFlavors(options, function (err, flavors) { })` + ## Installation ``` bash @@ -400,7 +589,12 @@ Each instance of `pkgcloud.loadbalancer.Client` returned from `pkgcloud.loadbala ``` ## Tests -For run the tests you will need `mocha@1.9.x` or higher, please install it and then run: +To run the tests you will need `mocha@1.9.x` or higher. You may install all +the requirements with: +``` bash + $ npm install +``` +Then run the tests: ``` bash $ npm test @@ -435,13 +629,13 @@ Also you can run the tests directly using `mocha` with `hock` enabled: ``` bash Linux/Mac - Mocha installed globally: $ MOCK=on mocha -R spec test/*/*/*-test.js test/*/*/*/*-test.js - + Linux/Mac - Mocha installed locally: $ MOCK=on node_modules/.bin/mocha -R spec test/*/*/*-test.js test/*/*/*/*-test.js Windows - Mocha installed globally: $ set MOCK=on&mocha -R spec test/*/*/*-test.js test/*/*/*/*-test.js - + Windows - Mocha installed locally: $ set MOCK=on&node_modules\.bin\mocha.cmd -R spec test/*/*/*-test.js test/*/*/*/*-test.js ``` @@ -450,16 +644,16 @@ Even better, you can run the tests for some specific provider: ``` bash Linux/Mac - Mocha installed globally: - $ MOCK=on mocha -R spec test/iriscouch/*/*-test.js + $ MOCK=on mocha -R spec test/openstack/*/*-test.js Linux/Mac - Mocha installed locally: - $ MOCK=on ./node_modules/.bin/mocha -R spec test/iriscouch/*/*-test.js + $ MOCK=on ./node_modules/.bin/mocha -R spec test/openstack/*/*-test.js Windows - Mocha installed globally: - $ set MOCK=on&mocha -R spec test/iriscouch/*/*-test.js - + $ set MOCK=on&mocha -R spec test/openstack/*/*-test.js + Windows - Mocha installed locally: - $ set MOCK=on&node_modules\.bin\mocha.cmd -R spec test/iriscouch/*/*-test.js + $ set MOCK=on&node_modules\.bin\mocha.cmd -R spec test/openstack/*/*-test.js ``` @@ -471,7 +665,7 @@ var client = pkgcloud.compute.createClient(options); client.on('log::*', function(message, object) { if (object) { - console.log(this.event.split('::')[1] + ' ' + message) + console.log(this.event.split('::')[1] + ' ' + message); console.dir(object); } else { @@ -484,34 +678,21 @@ client.on('log::*', function(message, object) { The valid log events raised are `log::debug`, `log::verbose`, `log::info`, `log::warn`, and `log::error`. There is also a [more detailed logging example using pkgcloud with Winston](docs/logging-with-winston.md). ## Code Coverage -You will need jscoverage installed in order to run code coverage. There seems to be many forks of the jscoverage project, but the recommended one is [node-jscoverage](https://github.com/visionmedia/node-jscoverage), because we use [node-coveralls](https://github.com/cainus/node-coveralls) to report coverage to http://coveralls.io. node-coveralls requires output from [mocha-lcov-reporter](https://github.com/StevenLooman/mocha-lcov-reporter), whose documentation mentions node-jscoverage. - -### Warning - -**Running coverage will mess with your lib folder. It will make a backup lib-bak before running and restore it if the coverage task runs successfully.** - -In order to simplify cleanup if something goes wrong, it is recommended to have all all new files added and all changes committed before running coverage, so you'll be able to restore with these commands if something goes wrong: - -``` bash -git clean -fd -git checkout lib -``` - -### Coverage Pre-requisites - -Please make sure jscoverage has been installed following the instructions at [node-jscoverage](https://github.com/visionmedia/node-jscoverage). - -### Local Coverage - -make test-cov ### Run Coverage locally and send to coveralls.io -Travis takes care of coveralls, so this shouldn't be necessary unless you're troubleshooting a problem with Travis/Coveralls. -You'll need to have access to the coveralls repo_token, which should only be visible to pkgcloud/pkgcloud admins. +Travis takes care of coveralls, so this shouldn't be necessary unless you're +troubleshooting a problem with Travis / Coveralls. You'll need to have access +to the coveralls `repo_token`, which should only be visible to +`pkgcloud/pkgcloud` admins. -1. Create a .coveralls.yml containing the repo_token from https://coveralls.io/r/pkgcloud/pkgcloud -2. Run make test-coveralls +1. Create a `.coveralls.yml` containing the `repo_token` from + https://coveralls.io/r/pkgcloud/pkgcloud +2. Run the following: +``` +npm test +npm run coverage +``` ## Contribute! @@ -519,15 +700,6 @@ We welcome contribution to `pkgcloud` by any and all individuals or organization We are pretty flexible about these guidelines, but the closer you follow them the more likely we are to merge your pull-request. - -## Roadmap - -1. Backport latest fixes from `node-cloudfiles` and `node-cloudservers` -2. Implement more providers for Block Storage, DNS, and Load Balancing -3. Add more services: Monitoring, Queueing, Autoscale. -4. Implement `fs` compatible file API. -5. Support additional service providers. - -#### Author: [Nodejitsu Inc.](http://nodejitsu.com) -#### Contributors: [Charlie Robbins](https://github.com/indexzero), [Nuno Job](https://github.com/dscape), [Daniel Aristizabal](https://github.com/cronopio), [Marak Squires](https://github.com/marak), [Dale Stammen](https://github.com/stammen), [Ken Perkins](https://github.com/kenperkins) +#### Author: [Charlie Robbins](https://github.com/indexzero) +#### Contributors: [Ross Kukulinski](https://github.com/rosskukulinski), [Jarrett Cruger](https://github.com/jcrugzz), [Ken Perkins](https://github.com/kenperkins) #### License: MIT diff --git a/docs/README.md b/docs/README.md index ab2fbbf20..092debdfb 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,23 +18,32 @@ If a service does not have at least two providers, it is considered a *beta* int * [Amazon](providers/amazon.md#using-compute) * [Azure](providers/azure.md#using-compute) * [DigitalOcean](providers/digitalocean.md#using-compute) - * [Joyent](providers/joyent.md#using-compute) + * [HP](providers/hp/compute.md) * [Openstack](providers/openstack/compute.md) * [Rackspace](providers/rackspace/compute.md) + * [1&1 Oneandone](providers/oneandone/compute.md) * **Storage** * [Amazon](providers/amazon.md#using-storage) * [Azure](providers/azure.md#using-storage) + * [HP](providers/hp/storage.md) * [Openstack](providers/openstack/storage.md) * [Rackspace](providers/rackspace/storage.md) * **Databases** - * [IrisCouch](providers/iriscouch.md) - * [MongoLab](providers/mongolab.md) - * [Rackspace](providers/rackspace/database.md) - * [MongoHQ](providers/mongohq.md) - * [RedisToGo](providers/redistogo.md) + * [HP](providers/hp/databases.md) + * [Openstack](providers/openstack/databases.md) + * [Rackspace](providers/rackspace/databases.md) * **DNS** *(beta)* * [Rackspace](providers/rackspace/dns.md) * **Block Storage** *(beta)* * [Rackspace](providers/rackspace/blockstorage.md) + * [Openstack](providers/openstack/blockstorage.md) + * [1&1 Oneandone](providers/oneandone/blockstorage.md) +* **Orchestration** *(beta)* + * [Rackspace](providers/rackspace/orchestration.md) + * [Openstack](providers/openstack/orchestration.md) * **Load Balancers** *(beta)* * [Rackspace](providers/rackspace/loadbalancer.md) + * [1&1 Oneandone](providers/oneandone/loadbalancer.md) +* **Networking** *(beta)* + * [Openstack](providers/openstack/network.md) + * [HP](providers/openstack/hp.md) diff --git a/docs/providers/amazon.md b/docs/providers/amazon.md index 7f5273352..e758d4e26 100644 --- a/docs/providers/amazon.md +++ b/docs/providers/amazon.md @@ -1,26 +1,54 @@ -# Using Amazon Web Services (aws) with `pkgcloud` +## Using the Amazon (AWS) provider in pkgcloud -* [Using Compute](#using-compute) -* [Using Storage](#using-storage) +The Amazon provider in pkgcloud supports the following services: - -## Using Compute +* **Compute** (EC2) +* **Storage** S3 (Simple Storage Service) -``` js - var amazon = pkgcloud.compute.createClient({ - provider: 'amazon', - key: 'asdfkjas;dkj43498aj3n', - keyId: '98kja34lkj' - }); +### Client Creation + +For all of the Amazon services, you create a client with the same options: + +```Javascript +var client = require('pkgcloud').compute.createClient({ + provider: 'amazon', + keyId: 'your-access-key-id', // access key id + key: 'your-secret-key-id', // secret key + region: 'us-west-2' // region +}); +``` + +```Javascript +var client = require('pkgcloud').storage.createClient({ + provider: 'amazon', + keyId: 'your-access-key-id', // access key id + key: 'your-secret-key-id', // secret key + region: 'us-west-2' // region +}); ``` +### File upload + +Whether s3 `multipart-upload` or `putObject` API is used depends on the `partSize` option value and the size of file being uploaded. +Single `putObject` request is made if an object being uploaded is not large enough. if the object size exceeds defined `partSize`, it uses `multipart-upload` API + + +```Javascript +var readableStream = fs.createReadStream('./path/to/file'); + +var writableStream = client.upload({ + queueSize: 1, // == default value + partSize: 5 * 1024 * 1024, // == default value of 5MB + container: 'web-static', + remote: 'image.jpg' +}); - -## Using Storage +//writableStream.managedUpload === https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3/ManagedUpload.html +// managedUpload object allows you to abort ongoing upload or track file upload progress. -``` js - var amazon = pkgcloud.storage.createClient({ - provider: 'amazon', // 'aws', 's3' - key: 'asdfkjas;dkj43498aj3n', - keyId: '98kja34lkj' - }); +readableStream.pipe(writableStream) +.on('success', function(file) { + console.log(file); +}).on('error', function(err) { + console.log(err); +}); ``` diff --git a/docs/providers/compute-commonality.md b/docs/providers/compute-commonality.md index 08820ff98..e0051ee12 100644 --- a/docs/providers/compute-commonality.md +++ b/docs/providers/compute-commonality.md @@ -7,8 +7,7 @@ The following table outlines the methods that are available on the different com API AWS Azure -Joyent -Openstack +OpenStack RAX DigitalOcean @@ -44,4 +43,4 @@ The following table outlines the methods that are available on the different com -*Note: There are a few I haven't listed yet that I know exist on Rackspace/Openstack, but I lack the awareness of the other providers.* +*Note: There are a few I haven't listed yet that I know exist on Rackspace/OpenStack, but I lack the awareness of the other providers.* diff --git a/docs/providers/digitalocean.md b/docs/providers/digitalocean.md index 6e5b913b2..33da938be 100644 --- a/docs/providers/digitalocean.md +++ b/docs/providers/digitalocean.md @@ -5,13 +5,12 @@ ## Using Compute -DigitalOcean requires a client ID and API key. +DigitalOcean requires an API token. ```js var pkgcloud = require('pkgcloud'); var digitalocean = pkgcloud.compute.createClient({ provider: 'digitalocean', - clientId: '', - apiKey: '' + token: '' }); ``` diff --git a/docs/providers/google.md b/docs/providers/google.md new file mode 100644 index 000000000..927702540 --- /dev/null +++ b/docs/providers/google.md @@ -0,0 +1,47 @@ +# Using Google Cloud with `pkgcloud` + +The Google Cloud provider in pkgcloud supports the following services: + +* **Storage** Google Cloud Storage + +Using the Google Cloud provider requires: + +1. A project id +2. A JSON key file + +Both are provided from the [Google Developers Console](https://console.developers.google.com/project). For detailed instructions, see this [Getting Started guide](https://github.com/GoogleCloudPlatform/gcloud-node/blob/v0.10.0/README.md#authorization). + +
+ +## Using Storage + +```Javascript +var client = require('pkgcloud').storage.createClient({ + provider: 'google', + keyFilename: '/path/to/a/keyfile.json', // path to a JSON key file + projectId: 'eco-channel-658' // project id +}); +``` + + +## Uploading a file +```Javascript +var readStream = fs.createReadStream(); + +var writeStream = client.upload({ + container: , + remote: , + contentType: 'application/pdf' // optional +}); + +writeStream.on('error', function (err) { + console.error(err); +}); + +writeStream.on('success', function (file) { + console.log("Success!"); +}); + +readStream.pipe(writeStream); +``` + diff --git a/docs/providers/hp/README.md b/docs/providers/hp/README.md new file mode 100644 index 000000000..b85a12513 --- /dev/null +++ b/docs/providers/hp/README.md @@ -0,0 +1,74 @@ +![HP Helion icon](http://www8.hp.com/hpnext/sites/default/files/content/documents/HP%20Helion%20Logo_Cloud_Martin%20Fink_New%20Style%20of%20IT_Hewlett-Packard.PNG) + +## Using the HP Helion Cloud provider in pkgcloud +The HP Helion Cloud provider in pkgcloud supports the following services: + +* [**Compute**](compute.md) (cloud compute) +* [**Databases**](databases.md) (databases) +* [**Network**](network.md) (network) +* [**Storage**](storage.md) (object storage) + +### Activating your HP Helion Cloud services +If this is your first time using HP Helion Cloud Services, please follow [this](https://community.hpcloud.com/article/hp-public-cloud-quick-start-guide) guide to get started. +### Getting Started with Compute + +We've provided a [simple compute example](getting-started-compute.md) where it creates a couple of compute instances. + +### Authentication +For all of the HP Cloud services, you create a client with the same options. + +For key-based auth: + +```Javascript +createClient({ + provider: 'hp', + tenantId: 'your-tenant-id', + username: 'your-access-key', + apiKey: 'your-secret-key', +}); +``` + +For password auth: + +```Javascript +createClient({ + provider: 'hp', + tenantId: 'your-tenant-id', + username: 'your-username', + password: 'your-password', +}); +``` + +### Getting your API key +You can provision API keys in your cloud management console. +Please see [here](https://community.hpcloud.com/article/managing-your-access-keys) for more instructions. + +### Authentication Endpoints and Regions +All of the HP `createClient` calls have a few options that can be provided: + +#### authUrl +`authUrl` specifies the authentication endpoint used to create a token for your HP client. +See here for more details : [Regions](http://docs.hpcloud.com/api/identity/#2.2RegionsandAvailabilityZones) +If you're targeting an HP Private Cloud instance, please contact your administrator for the authUrl. + +#### region +`region` specifies which region of a service to use. HP has services deployed in multiple regions. +See here for more details : [Regions](http://docs.hpcloud.com/api/identity/#2.2RegionsandAvailabilityZones) +If you're targeting an HP Private Cloud instance, please contact your administrator for region names. + +##### Authenticating against the US-West endpoint + +```Javascript + region: 'region-a.geo-1', + authUrl: 'https://region-a.geo-1.identity.hpcloudsvc.com:35357' +``` + +##### Authenticating against the US-East endpoint + +```Javascript + region: 'region-b.geo-1', + authUrl: 'https://region-b.geo-1.identity.hpcloudsvc.com:35357' +``` + +### Tokens and Expiration +When you make your first call to a HP provider, your client is authenticated transparent to your API call. HP will issue you a token, with an expiration. When that token expires, the client will automatically re-authenticate and retrieve a new token. The caller shouldn't have to worry about this happening. diff --git a/docs/providers/hp/compute.md b/docs/providers/hp/compute.md new file mode 100644 index 000000000..18982a3a2 --- /dev/null +++ b/docs/providers/hp/compute.md @@ -0,0 +1,144 @@ +![HP Helion icon](http://www8.hp.com/hpnext/sites/default/files/content/documents/HP%20Helion%20Logo_Cloud_Martin%20Fink_New%20Style%20of%20IT_Hewlett-Packard.PNG) + +## Using the HP Helion Cloud Compute provider + +Creating a client is straight-forward: + +``` js + var HPCompute = pkgcloud.compute.createClient({ + provider: 'hp', + username: 'your-user-name', + apiKey: 'your-api-key', + region: 'region of identity service', + authUrl: 'https://your-identity-service' + }); +``` + +[More options for creating clients](README.md) + +### API Methods + +**Servers** + +#### client.getServers(callback) +Lists all servers that are available to use on your HP Cloud account + +Callback returns `f(err, servers)` where `servers` is an `Array` + +#### client.createServer(options, callback) +Creates a server with the options specified + +Options are as follows: + +```js +{ + name: 'serverName', // required + flavor: 'flavor1', // required + image: 'image1', // required + personality: [] // optional +} +``` +Returns the server in the callback `f(err, server)` + +#### client.destroyServer(server, callback) +Destroys the specified server + +Takes server or serverId as an argument and returns the id of the destroyed server in the callback `f(err, serverId)` + +#### client.getServer(server, callback) +Gets specified server + +Takes server or serverId as an argument and returns the server in the callback +`f(err, server)` + +#### client.rebootServer(server, options, callback) +Reboots the specifed server with options + +Options include: + +```js +{ + type: 'HARD' // optional (defaults to 'SOFT') +} +``` +Returns callback with a confirmation + +#### client.getVersion(callback) + +Get the current version of the api returned in a callback `f(err, version)` + +#### client.getLimits(callback) + +Get the current API limits returned in a callback `f(err, limits)` + +**flavors** + +#### client.getFlavors(callback) + +Returns a list of all possible server flavors available in the callback `f(err, +flavors)` + +#### client.getFlavor(flavor, callback) +Returns the specified HP flavor of OpenStack Images by ID or flavor +object in the callback `f(err, flavor)` + +**images** + +#### client.getImages(callback) +Returns a list of the images available for your account + +`f(err, images)` + +#### client.getImage(image, callback) +Returns the image specified + +`f(err, image)` + +#### client.createImage(options, callback) +Creates an Image based on a server + +Options include: + +```js +{ + name: 'imageName', // required + server: 'serverId' // required +} +``` + +Returns the newly created image + +`f(err, image)` + +#### client.destroyImage(image, callback) +Destroys the specified image and returns a confirmation + +`f(err, {ok: imageId})` + +## Volume Attachments + +Attaching a volume to a compute instance requires using a HP compute client, as well as possessing a `volume` or `volumeId`. Detaching volumes behaves the same way. + +#### client.getVolumeAttachments(server, callback) + +Gets an array of volumeAttachments for the provided server. + +`f(err, volumeAttachments)` + +#### client.getVolumeAttachmentDetails(server, attachment, callback) + +Gets the details for a provided server and attachment. `attachment` may either be the `attachmentId` or an object with `attachmentId` as a property. + +`f(err, volumeAttachment)` + +#### client.attachVolume(server, volume, callback) + +Attaches the provided `volume` to the `server`. `volume` may either be the `volumeId` or an instance of `Volume`. + +`f(err, volumeAttachment)` + +#### client.detachVolume(server, attachment, callback) + +Detaches the provided `attachment` from the server. `attachment` may either be the `attachmentId` or an object with `attachmentId` as a property. If the `volume` is mounted this call will return an err. + +`f(err)` diff --git a/docs/providers/hp/databases.md b/docs/providers/hp/databases.md new file mode 100644 index 000000000..0779c47cb --- /dev/null +++ b/docs/providers/hp/databases.md @@ -0,0 +1,92 @@ +## Using the HP Helion Database provider + +Creating a client is straight-forward: + +``` js + var rackspace = pkgcloud.database.createClient({ + provider: 'hp', + username: 'your-user-name', + apiKey: 'your-api-key', + region: 'region of identity service', + authUrl: 'https://your-identity-service' + }); +``` + +[More options for creating clients](README.md) + +### Creating a MySQL Database + +The steps for provision a MySQL database from HP Helion cloud databases are: + +1. Choose a flavor (memory RAM size) +2. Create an instance of a database server. +3. When the instance is provisioned, create your database. + +Also you can manage users across your instances and each instance can handle several databases. + +``` js + client.getFlavors(function (err, flavors) { + // + // Look at the availables flavors for your instance + // + console.log(flavors); + + // + // Lets choose the ID 1 for 512MB flavor + // + client.getFlavor(1, function (err, flavor) { + // + // Create the instance for host the databases. + // + client.createInstance({ + name: 'test-instance', + flavor: flavor, + // + // Optional, you can choose the disk size for the instance + // (1 - 8) in GB. Default to 1 + // + size: 3 + // + // Optional, you can give an array of database names for initialize + // when the instace is ready + // + databases: ['first-database', 'second-database'] + }, function (err, instance) { + // + // At this point when the instance is ready we can manage the databases + // + client.createDatabase({ + name: 'test-database', + instance: instance + }, function (err, database) { + // + // Log the result + // + console.log(database); + }); + }); + }) + }); +``` + +### API Methods ### + +#### client.createUser(options, callback) + +Allows the creation of specific users to have access to any database you create. + +Accepts one user object as the `options` argument or an array of user objects. + +A user object is defined as follows: + +```js +{ + username: 'nodejitsu', // required + password: 'foobar', // required + databases: ['first-db, second-db'], // required (Can be either string or array) + instance: instance // required (instance or instanceId) +} +``` + +**note**: If creating multiple users, the instance provided must be the same for +both. diff --git a/docs/providers/hp/getting-started-compute.md b/docs/providers/hp/getting-started-compute.md new file mode 100644 index 000000000..d4e77f974 --- /dev/null +++ b/docs/providers/hp/getting-started-compute.md @@ -0,0 +1,98 @@ +![HP Helion icon](http://www8.hp.com/hpnext/sites/default/files/content/documents/HP%20Helion%20Logo_Cloud_Martin%20Fink_New%20Style%20of%20IT_Hewlett-Packard.PNG) +# Getting started with pkgcloud & HP Helion Cloud +The HP node.js SDK is available as part of `pkgcloud`, a multi-provider cloud provisioning package + +Pkgcloud currently supports HP Helion Cloud Compute and HP Helion Cloud Object Storage, and HP Helion Cloud Networking. + +To install `pkgcloud` from the command line: + +``` +npm install pkgcloud +``` + +Don't have `npm` or `node` yet? [Get it now](http://nodejs.org/download). + +## Using pkgcloud + +In this example, we're going to create a HP Helion Cloud Compute client, create two servers, and then output their details to the command line. + +*Note: We're going to use [lodash.js](https://lodash.com) for some convenience functions.* + +```Javascript +var pkgcloud = require('pkgcloud'), + _ = require('lodash'); + +// create our client with your openstack credentials +var client = pkgcloud.compute.createClient({ + provider: 'hp', + username: 'your-user-name', + apiKey: 'your-api-key', + region: 'region of identity service', + authUrl: 'https://your-identity-service' +}); + +// first we're going to get our flavors +client.getFlavors(function (err, flavors) { + if (err) { + console.dir(err); + return; + } + + // then get our base images + client.getImages(function (err, images) { + if (err) { + console.dir(err); + return; + } + + // Pick a medium instance flavor + // see here for more instance flavors: http://www.hpcloud.com/products-services/hp-cloud-compute-13_5 + var flavor = _.findWhere(flavors, { name: 'standard.medium' }); + + // Pick an image based on CentOS 6.3 + var image = _.findWhere(images, { name: 'CentOS 6.3 Server 64-bit 20130116' }); + + // Create our first server + client.createServer({ + name: 'server1', + image: image, + flavor: flavor + }, handleServerResponse); + + // Create our second server + client.createServer({ + name: 'server2', + image: image, + flavor: flavor + }, handleServerResponse); + }); +}); + +// This function will handle our server creation, +// as well as waiting for the server to come online after we've +// created it. +function handleServerResponse(err, server) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER CREATED: ' + server.name + ', waiting for active status'); + + // Wait for status: ACTIVE on our server, and then callback + server.setWait({ status: 'ACTIVE' }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER INFO'); + console.log(server.name); + console.log(server.status); + console.log(server.id); + + console.log('Make sure you DELETE server: ' + server.id + + ' in order to not accrue billing charges'); + }); +} +``` diff --git a/docs/providers/hp/network.md b/docs/providers/hp/network.md new file mode 100644 index 000000000..664c97bf5 --- /dev/null +++ b/docs/providers/hp/network.md @@ -0,0 +1,245 @@ +![HP Helion icon](http://www8.hp.com/hpnext/sites/default/files/content/documents/HP%20Helion%20Logo_Cloud_Martin%20Fink_New%20Style%20of%20IT_Hewlett-Packard.PNG) + +## Using the HP Cloud Network provider + +Creating a client is straight-forward: + +``` js + var hPNetwork = pkgcloud.network.createClient({ + provider: 'hp', + username: 'your-user-name', + apiKey: 'your-api-key', + region: 'region of identity service', + authUrl: 'https://your-identity-service' + }); +``` +### API Methods + +**Networks** + +#### client.getNetworks(callback) +Lists all networks that are available to use on your HP Cloud account + +Callback returns `f(err, networks)` where `networks` is an `Array` + +#### client.getNetwork(network, callback) +Gets specified network + +Takes network or networkId as an argument and returns the network in the callback +`f(err, network)` + +#### client.createNetwork(options, callback) +Creates a network with the options specified + +Options are as follows: + +```js +{ + name: 'networkName', // optional + adminStateUp : true, // optional + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, Admin only +} +``` +Returns the network in the callback `f(err, network)` + +#### client.updateNetwork(options, callback) +Updates a network with the options specified + +Options are as follows: + +```js +{ + id : 'networkId', // required + name: 'networkName', // optional + adminStateUp : true, // optional + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, Admin only +} +``` +Returns the network in the callback `f(err, network)` + +#### client.destroyNetwork(network, callback) +Destroys the specified network + +Takes network or networkId as an argument and returns the id of the destroyed network in the callback `f(err, networkId)` + +**Subnets** + +#### client.getSubnets(callback) +Lists all subnets that are available to use on your HP Cloud account + +Callback returns `f(err, subnets)` where `subnets` is an `Array` + +#### client.getSubnet(subnet, callback) +Gets specified subnet + +Takes subnet or subnetId as an argument and returns the subnet in the callback +`f(err, subnet)` + +#### client.createSubnet(options, callback) +Creates a subnet with the options specified + +Options are as follows: + +```js +{ + name: 'subnetName', // optional + networkId : 'networkId', // required, The ID of the attached network. + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + gatewayIp : 'gateway ip address', // optional,The gateway IP address. + enableDhcp : true // Set to true if DHCP is enabled and false if DHCP is disabled. +} +``` +Returns the subnet in the callback `f(err, subnet)` + +#### client.updateSubnet(options, callback) +Updates a subnet with the options specified + +Options are as follows: + +```js +{ + id : 'subnetId', // required + name: 'subnetName', // optional + networkId : 'networkId', // required, The ID of the attached network. + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + gatewayIp : 'gateway ip address', // optional,The gateway IP address. + enableDhcp : true // Set to true if DHCP is enabled and false if DHCP is disabled. +} +``` +Returns the subnet in the callback `f(err, subnet)` + +#### client.destroySubnet(subnet, callback) +Destroys the specified subnet + +Takes subnet or subnetId as an argument and returns the id of the destroyed subnet in the callback `f(err, subnetId)` + +**Ports** + +#### client.getPorts(callback) +Lists all ports that are available to use on your HP Cloud account + +Callback returns `f(err, ports)` where `ports` is an `Array` + +#### client.getPort(port, callback) +Gets specified port + +Takes port or portId as an argument and returns the port in the callback +`f(err, port)` + +#### client.createPort(options, callback) +Creates a port with the options specified + +Options are as follows: + +```js +{ + name: 'portName', // optional + adminStateUp : true, // optional, The administrative status of the router. Admin-only + networkId : 'networkId', // required, The ID of the attached network. + status : 'text status', // optional, The status of the port. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + macAddress: 'mac address' // optional + fixedIps : ['ip address1', 'ip address 2'], // optional. + securityGroups : ['security group1', 'security group2'] // optional, Specify one or more security group IDs. +} +``` +Returns the port in the callback `f(err, port)` + +#### client.updatePort(options, callback) +Updates a port with the options specified + +Options are as follows: + +```js +{ + id : 'portId', // required + name: 'portName', // optional + adminStateUp : true, // optional, The administrative status of the router. Admin-only + networkId : 'networkId', // required, The ID of the attached network. + status : 'text status', // optional, The status of the port. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + macAddress: 'mac address' // optional + fixedIps : ['ip address1', 'ip address 2'], // optional. + securityGroups : ['security group1', 'security group2'] // optional, Specify one or more security group IDs. +} +``` +Returns the port in the callback `f(err, port)` + +#### client.destroyPort(port, callback) +Destroys the specified port + +Takes port or portId as an argument and returns the id of the destroyed port in the callback `f(err, portId)` + +**Security Groups** + +#### client.getSecurityGroups(callback) +Lists all security groups that are available to use on your OpenStack account + +Callback returns `f(err, securityGroups)` where `securityGroups` is an `Array` + +#### client.getSecurityGroup(securityGroup, callback) +Gets specified security group + +Takes securityGroup or securityGroupId as an argument and returns the security group in the callback +`f(err, securityGroup)` + +#### client.createSecurityGroup(options, callback) +Creates a security group with the options specified + +Options are as follows: + +```js +{ + name: 'securityGroupName', // required, name of security group + description : 'security group description', // optional, description of security group + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group. Admin-only +} +``` +Returns the created security group in the callback `f(err, securityGroup)` + +#### client.destroySecurityGroup(securityGroup, callback) +Destroys the specified security group + +Takes securityGroup or securityGroupId as an argument and returns the id of the destroyed security group in the callback `f(err, securityGroupId)` + +**Security Group Rules** + +#### client.getSecurityGroupRules(callback) +Lists all security group rules that are available to use on your OpenStack account + +Callback returns `f(err, securityGroupRules)` where `securityGroupRules` is an `Array` + +#### client.getSecurityGroupRule(securityGroupRule, callback) +Gets specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the security group rule in the callback +`f(err, securityGroupRule)` + +#### client.createSecurityGroupRule(options, callback) +Creates a security group rule with the options specified + +Options are as follows: + +```js +{ + securityGroupId: 'securityGroupId', // required, The security group ID to associate with this security group rule. + direction: 'ingress|egress', // required, The direction in which the security group rule is applied. + ethertype: 'IPv4|IPv6', // optional, + portRangeMin: portNumber, // optional, The minimum port number in the range that is matched by the security group rule. + portRangeMax: portNumber, // optional, The maximum port number in the range that is matched by the security group rule. + protocol: 'tcp|udp|icmp', // optional, The protocol that is matched by the security group rule + remoteGroupId: 'remote group id', // optional, The remote group ID to be associated with this security group rule. You can specify either this or remoteIpPrefix. + remoteIpPrefix: 'remote IP prefix', // optional, The remote IP prefix to be associated with this security group rule. You can specify either this or remoteGroupId. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group rule. Admin-only +} +``` +Returns the created security group rule in the callback `f(err, securityGroupRule)` + +#### client.destroySecurityGroupRule(securityGroupRule, callback) +Destroys the specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the id of the destroyed security group rule in the callback `f(err, securityGroupRuleId)` diff --git a/docs/providers/hp/storage.md b/docs/providers/hp/storage.md new file mode 100644 index 000000000..be9208993 --- /dev/null +++ b/docs/providers/hp/storage.md @@ -0,0 +1,269 @@ +![HP Helion icon](http://www8.hp.com/hpnext/sites/default/files/content/documents/HP%20Helion%20Logo_Cloud_Martin%20Fink_New%20Style%20of%20IT_Hewlett-Packard.PNG) + +## Using the HP Object Storage provider + +* Container + * [Model](#container-model) + * [APIs](#container-apis) +* File + * [Model](#file-model) + * [APIs](#file-apis) + +Creating a client is straight-forward: + +``` js + var hpStorage = pkgcloud.storage.createClient({ + provider: 'hp', + username: 'your-user-name', + apiKey: 'your-api-key', + region: 'region of identity service', + authUrl: 'https://your-identity-service' + }); +``` + +Learn about [more options for creating clients](README.md) in the HP `storage` provider. + +### Container Model + +A Container for HP Object Storage has following properties: + +```Javascript +{ + name: 'my-container', + count: 1, // number of files in your container + bytes: 12345, // size of the container in bytes + metadata: { // key value pairs for the container + // ... + } +} +``` + +### File Model + +A File for HP Object Storage has the following properties: + +```Javascript +{ + name: 'my-file', + container: 'my-container', // may be an instance of container if provided + size: 12345, // size of the file in bytes + contentType: 'plain/text' // Mime type for the file + lastModified: Fri Dec 14 2012 10:16:50 GMT-0800 (PST), // Last modified date of the file + etag: '1234567890abcdef', // MD5 sum of the file + metadata: {} // optional object metadata +} +``` + +### Container APIs + +* [`client.getContainers(function(err, containers) { })`](#clientgetcontainersfunctionerr-containers--) +* [`client.getContainer(container, function(err, container) { })`](#clientgetcontainercontainer-functionerr-container--) +* [`client.createContainer(container, function(err, container) { })`](#clientcreatecontainercontainer-functionerr-container--) +* [`client.destroyContainer(container, function(err, result) { })`](#clientdestroycontainercontainer-functionerr-result--) +* [`client.updateContainerMetadata(container, function(err, container) { })`](#clientupdatecontainermetadatacontainer-functionerr-container--) +* [`client.removeContainerMetadata(container, metadataToRemove, function(err, container) { })`](#clientremovecontainermetadatacontainer-metadatatoremove-functionerr-container--) + +### Container API Details + +For all of the container methods, you can pass either an instance of [`container`](#container) or the container name as `container`. For example: + +```Javascript +client.getContainer('my-container', function(err, container) { ... }); +``` + +This call is functionally equivalent to: + +```Javascript +var myContainer = new Container({ name: 'my-container' }); + +client.getContainer(myContainer, function(err, container) { ... }); +``` + +#### client.getContainers(function(err, containers) { }) + +Retreives the containers for the current client instance as an array of [`container`](#container-model) + +#### client.getContainer(container, function(err, container) { }) + +Retrieves the specified [`container`](#container-model) from the current client instance. + +#### client.createContainer(container, function(err, container) { }) + +Creates a new [`container`](#container-model) with the name from argument `container`. You can optionally provide `metadata` on the request: + +```javascript +client.createContainer({ + name: 'my-container', + metadata: { + brand: 'bmw', + model: '335i' + year: 2009 + }}, function(err, container) { + // ... + }) +``` + +#### client.destroyContainer(container, function(err, result) { }) + +Removes the [`container`](#container-model) from the storage account. If there are any files within the `container`, they will be deleted before removing the `container` on the client. `result` will be `true` on success. + +#### client.updateContainerMetadata(container, function(err, container) { }) + +Updates the metadata on the provided [`container`](#container-model) . Currently, the `updateContainer` method only adds new metadata fields. If you need to remove specific metadata properties, you should call `client.removeContainerMetadata(...)`. + +```javascript +container.metadata.color = 'red'; +client.updateContainerMetadata(container, function(err, container) { + // ... +}) +``` + +#### client.removeContainerMetadata(container, metadataToRemove, function(err, container) { }) + +Removes the keys in the `metadataToRemove` object from the stored [`container`](#container-model) metadata. + +```Javascript +client.removeContainerMetadata(container, { year: false }, function(err, c) { + // ... +}); +``` + +### File APIs + +* [`client.upload(options)`](#clientuploadoptions) +* [`client.download(options, function(err, file) { })`](#clientdownloadoptions-functionerr-file--) +* [`client.getFile(container, file, function(err, file) { })`](#clientgetfilecontainer-file-functionerr-file--) +* [`client.getFiles(container, function(err, file) { })`](#clientgetfilescontainer-functionerr-file--) +* [`client.removeFile(container, file, function(err, result) { })`](#clientremovefilecontainer-file-functionerr-result--) +* [`client.updateFileMetadata(container, file, function(err, file) { })`](#clientupdatefilemetadatacontainer-file-functionerr-file--) + +### File API Details + +For all of the file methods, you can pass either an instance of [`container`](#container-model) or the container name as `container`. For example: + +```Javascript +client.getFile('my-container', 'my-file', function(err, file) { ... }); +``` + +This call is functionally equivalent to: + +```Javascript +var myContainer = new Container({ name: 'my-container' }); + +client.getFile(myContainer, 'my-file', function(err, file) { ... }); +``` + +#### client.upload(options) + +Returns a `writableStream`. Upload a new file to a [`container`](#container-model). `writableStream` will emit `success` on completion of the upload, and will emit `error` on any failure. Function for `success` is `function(file) { ... }` where `file` is a [`file`](#file-model) model + +To upload a file, you need to provide an `options` argument: + +```Javascript +var options = { + // required options + container: 'my-container', // this can be either the name or an instance of container + remote: 'my-file', // name of the new file + contentType: 'application/json', // optional mime type for the file, will attempt to auto-detect based on remote name + size: 1234 // size of the file +}; +``` + +```Javascript + var readStream = fs.createReadStream('a-file.txt'); + var writeStream = client.upload({ + container: 'a-container', + remote: 'remote-file-name.txt' + }); + + writeStream.on('error', function(err) { + // handle your error case + }); + + writeStream.on('success', function(file) { + // success, file will be a File model + }); + + readStream.pipe(writeStream); +``` + +#### client.download(options, function(err, file) { }) + +Returns a readable stream. Download a [`file`](#file-model) from a [`container`](#container-model). + +To download a file, you need to provide an `options` argument: + +```Javascript +var options = { + // required options + container: 'my-container', // this can be either the name or an instance of container + remote: 'my-file', // name of the new file + + // optional, either stream or local + stream: myStream, // any instance of a writeable stream + local: '/path/to/local/file' // the path to a local file to write to +}; +``` + +You need not provide either `stream` or `local`. `client.download` returns a readable stream, so you can simply pipe it into your writeable stream. For example: + +```Javascript +var fs = require('fs'), + pkgcloud = require('pkgcloud'); + +var client = pkgcloud.providers.hp.storage.createClient({ ... }); + +var myFile = fs.createWriteStream('/my/local/file'); + +client.download({ + container: 'my-container', + remote: 'my-file' +}, function(err, result) { + // handle the download result +})).pipe(myFile); +``` + +You could also download to a local file via the `local` property on `options`: + +```Javascript +var pkgcloud = require('pkgcloud'); + +var client = pkgcloud.providers.hp.storage.createClient({ ... }); + +client.download({ + container: 'my-container', + remote: 'my-file', + local: '/path/to/my/file' +}, function(err, result) { + // handle the download result +}); +``` + +This is functionally equivalent to piping from an `fs.createWriteStream`, but has a simplified calling convention. + +#### client.getFile(container, file, function(err, file) { }) + +Retrieves the specified [`file`](#file-model) details in the specified [`container`](#container-model) from the current client instance. + +#### client.getFiles(container, function(err, files) { }) + +Retreives an array of [`file`](#file-model) for the provided [`container`](#container-model). + +#### client.removeFile(container, file, function(err, result) { }) + +Removes the provided [`file`](#file-model) from the provided [`container`](#container-model). + +#### client.updateFileMetadata(container, file, function(err, file) { }) + +Updates the [`file`](#file-model) metadata in the the provided [`container`](#container-model). + +File metadata is completely replaced with each call to updateFileMetadata. This is different than container metadata. To delete a property, just remove it from the metadata attribute on the `File` and call `updateFileMetadata`. +```javascript +file.metadata = { + campaign = '2011 website' +}; + +client.updateFileMetadata(file.container, file, function(err, file) { + // ... +}); +``` diff --git a/docs/providers/iriscouch.md b/docs/providers/iriscouch.md deleted file mode 100644 index 8bafdec1e..000000000 --- a/docs/providers/iriscouch.md +++ /dev/null @@ -1,71 +0,0 @@ -# Using IrisCouch with `pkgcloud` - -**In order to use IrisCOuch you will need to have created a valid account.** IrisCouch actually exposes two database services: - -* [Using CouchDB](#couchdb) -* [Using Redis](#redis) -* [All API Methods](#all-api-methods) - - -## Using CouchDB - -``` js -var client = pkgcloud.database.createClient({ - provider: 'iriscouch', - username: 'bob', - password: '1234' -}); - -// -// Create a couch -// -client.create({ - subdomain: 'pkgcloud-nodejitsu-test-7', - first_name: 'pkgcloud', - last_name: 'pkgcloud', - email: 'info@nodejitsu.com' -}, function (err, result) { - // - // Check now exists @ http://pkgcloud-nodejitsu-test-7.iriscouch.com - // - console.log(err, result); -}); -``` - - -## Using Redis - -IrisCouch also supports provisioning redis databases. In this case just pass the option `type: 'redis'` to the `create()` method and put a `password` for the access. - -``` js -// -// Crate a redis database -// -client.create({ - subdomain: 'pkgcloud-nodejitsu-test-7', - first_name: 'pkgcloud', - last_name: 'pkgcloud', - email: 'info@nodejitsu.com', - // - // For redis instead of couch just put type to redis - // - type: 'redis', - // - // AND ADD A PASSWORD! (required) - // - password: 'mys3cur3p4ssw0rd' -}, function (err, result) { - // - // Check the connection, use result.host and result.password values - // redis-cli -h $RESULT.HOST -a $RESULT.PASSWORD - // - console.log('HOST to connect:', result.host); - console.log('KEY to use:', result.password); -}); -``` - -## All API methods - -The `client` instance returned by `pkgcloud.database.createClient` has the following methods for IrisCouch: - -* `client.create(options, callback)` \ No newline at end of file diff --git a/docs/providers/joyent.md b/docs/providers/joyent.md deleted file mode 100644 index 6cd855084..000000000 --- a/docs/providers/joyent.md +++ /dev/null @@ -1,33 +0,0 @@ -# Using Joyent with `pkgcloud` - -* [Using Compute](#using-compute) - - -## Using Compute - -Joyent requires a username / password or key / keyId combo. The key / keyId should be registered in Joyent servers; check `test/helpers/index.js` for details on key/keyId works. - -**key / keyId pair** -``` js - var pkgcloud = require('pkgcloud'), - path = require('path'), - fs = require('fs'); - - var joyent = pkgcloud.compute.createClient({ - provider: 'joyent', - account: 'nodejitsu' - keyId: '/nodejitsu1/keys/dscape', - key: fs.readFileSync(path.join(process.env.HOME, '.ssh/id_rsa'), 'ascii') - }); -``` - -**username / password** -``` js - var pkgcloud = require('pkgcloud'); - - var joyent = pkgcloud.compute.createClient({ - provider: 'joyent', - username: 'your-account', - password: 'your-password' - }); -``` \ No newline at end of file diff --git a/docs/providers/mongohq.md b/docs/providers/mongohq.md deleted file mode 100644 index 89a6ca8ed..000000000 --- a/docs/providers/mongohq.md +++ /dev/null @@ -1,39 +0,0 @@ -# Using MongoHQ with `pkgcloud` - -MongoHQ is available in `pkgcloud` as a `pkgcloud.databases` target. Here is an example of how to use it: - -``` js - var client = pkgcloud.database.createClient({ - provider: 'mongohq', - username: "bob", - password: "1234" - }); - - // - // Create a MongoDB - // - client.create({ - name: "mongo-instance", - plan: "free", - }, function (err, result) { - // - // Check the result - // - console.log(err, result); - - // - // Now delete that same MongoDB - // - client.remove(result.id, function (err, result) { - // - // Check the result - // - console.log(err, result); - }); - }); -``` - -The `client` instance returned by `pkgcloud.database.createClient` has the following methods for MongoHQ: - -* `client.create(options, callback)` -* `client.remove(id, callback)` \ No newline at end of file diff --git a/docs/providers/mongolab.md b/docs/providers/mongolab.md deleted file mode 100644 index 82037ffec..000000000 --- a/docs/providers/mongolab.md +++ /dev/null @@ -1,69 +0,0 @@ -# Using MongoLab with `pkgcloud` - -The MongoLab API has a slightly different approach for managing databases: they have implemented accounts, and each account can provision databases. **To create a database with MongoLab you will need first create an account and then use the created account as "owner" of the database.** - -``` js - // - // First lets set up the client - // - var client = pkgcloud.database.createClient({ - provider: 'mongolab', - username: 'bob', - password: '1234' - }); -``` - -``` js - // - // Now lets create an account - // name and email are required fields. - // - client.createAccount({ - name:'daniel', - email:'daniel@nodejitsu.com', - // - // If you want, you can set your own password - // (Password must contain at least one numeric character.) - // if not mongolab will create a password for you. - // - password:'mys3cur3p4ssw0rd' - }, function (err, user) { - // - // Now you can provision databases under this user account - // - console.log(user); - }); -``` - -``` js - // - // Now lets create a database - // name and owner are required fields - // - client.create({ - name:'myDatabase', - // - // You need to put the exact name account returned in the account creation. - // - owner: user.account.username - }, function (err, database) { - // - // That is all - // - console.log(database); - }); -``` - -The `client` instance returned by `pkgcloud.database.createClient` has the following methods for MongoLab: - -## Accounts -* `client.createAccount(options, callback)` -* `client.getAccounts(callback)` -* `client.getAccount(name, callback)` -* `client.deleteAccount(name, callback)` - -## Databases -* `client.create(options, callback)` -* `client.getDatabases(owner, callback)` -* `client.getDatabase(options, callback)` -* `client.remove(options, callback)` \ No newline at end of file diff --git a/examples/database/mongolab.js b/docs/providers/oneandone.md similarity index 100% rename from examples/database/mongolab.js rename to docs/providers/oneandone.md diff --git a/docs/providers/oneandone/README.md b/docs/providers/oneandone/README.md new file mode 100644 index 000000000..9aab30b30 --- /dev/null +++ b/docs/providers/oneandone/README.md @@ -0,0 +1,38 @@ +## Using the 1&1 provider in pkgcloud + +The 1&1 provider in pkgcloud supports the following services: + +* [**Compute**](compute.md) (Cloud Servers) +* [**Block Storage**](blockstorage.md) (Cloud Block Storage) *(beta)* +* [**Load Balancers**](loadbalancer.md) (Cloud Load Balancers) *(beta)* + +### Getting Started with Compute + +We've provided a [simple compute example](getting-started-compute.md) where it creates a couple of compute instances. + +### Authentication + +For all of the Rackspace services, you create a client with the same options: + +you can store the token in your Environment variables just like below. + +```Javascript +var client = require('pkgcloud').compute.createClient({ + provider: 'oneandone', + token: process.env.OAO_TOKEN +}); +``` + + +### Datacenters + +All of the 1&1 Oneandone `createClient` calls have a few options that can be provided: + +#### location + +`location` specifies which region of a service to use. Different services have different regions enabled. The current list of regions is: + +- `ES` (Spain) +- `US` (United States of America) +- `DE` (Germany) +- `GB` (United Kingdom of Great Britain and Northern Ireland) \ No newline at end of file diff --git a/docs/providers/oneandone/blockstorage.md b/docs/providers/oneandone/blockstorage.md new file mode 100644 index 000000000..cb52fc34e --- /dev/null +++ b/docs/providers/oneandone/blockstorage.md @@ -0,0 +1,59 @@ +## Using the 1&1 Block Storage provider + +#### BETA - This API may change as additional providers for block storage are added to pkgcloud + +Creating a block-storage client is straight-forward: + +``` js + var oneandone = pkgcloud.blockstorage.createClient({ + provider: 'oneandone', // required + token: 'apikey', // required + }); +``` + +[More options for creating clients](README.md) + +* Snapshot + * [Model](#snapshot-model) + * [APIs](#snapshot-apis) + + +### Snapshot Model + +A Snapshot for BlockStorage has the following properties: + +```Javascript +{ + id: '12345678-1111-2222-3333-123456789012', // id of the snapshot +} +``` + +### Snapshot APIs + +#### client.getSnapshots(options, callback) +Returns a list of the server's snapshots. + +Callback returns `f(err, snapshots)` where `snapshots` is an `Array`. `options` is an optional `boolean` which will return the full snapshot details if true. + +#### client.createSnapshot(details, callback) +Creates a snapshot with the details specified + +Options are as follows: + +```js +{ + server: '81504C620D98BCEBAA5202D145203B4B',//Server or Server id to create the snapshot from +} + +``` +Returns the new snapshot in the callback `f(err, snapshot)` + +#### client.deleteSnapshot(snapshot, callback) +Removes a snapshot + +Takes snapshot or snapshotId as an argument and returns an error if unsuccessful `f(err)` + +#### client.updateSnapshot(snapshot, callback) +Restores a snapshot into the server. + +Returns callback with a confirmation \ No newline at end of file diff --git a/docs/providers/oneandone/compute.md b/docs/providers/oneandone/compute.md new file mode 100644 index 000000000..9b1410fe6 --- /dev/null +++ b/docs/providers/oneandone/compute.md @@ -0,0 +1,102 @@ +## Using the 1&1 Compute provider + +As of the `v0.8` release of `pkgcloud`, the Compute provider uses Next Generation Cloud Servers, meaning you'll need to use a version <=0.7.x to use First Generation Cloud Servers. + +Creating a client is straight-forward: + +``` js + var oneandone = pkgcloud.compute.createClient({ + provider: 'oneandone', // required + token: 'your-api-key', // required + }); +``` + +[More options for creating clients](README.md) + +### API Methods + +**Servers** + +#### client.getServers(callback) +Lists all servers available to your account. + +Callback returns `f(err, servers)` where `servers` is an `Array` + +#### client.createServer(options, callback) +Creates a server with the options specified + +Options are as follows: + +```js +{ + name: 'server name', + flavor: 'falvor-id', + image: 'image or imageId', + location: 'datacenter id' +} + +``` +Returns the server in the callback `f(err, server)` + +#### client.destroyServer(server, callback) +Destroys the specified server + +Takes server or serverId as an argument and returns the id of the destroyed server in the callback `f(err, serverId)` + +#### client.getServer(server, callback) +Gets specified server + +Takes server or serverId as an argument and returns the server in the callback +`f(err, server)` + +#### client.rebootServer(server, options, callback) +Reboots the specifed server with options + +Returns callback with a confirmation + +#### client.getVersion(callback) + +Get the current version of the api returned in a callback `f(err, version)` + +**flavors** + +#### client.getFlavors(callback) + +Returns available flavours for fixed servers in the callback `f(err, +flavors)` + +#### client.getFlavor(flavor, callback) +Returns information about one flavour in the callback `f(err, flavor)` + +**images** + +#### client.getImages(callback) +Lists all images available to your account + +`f(err, images)` + +#### client.getImage(image, callback) +Information about specific appliance/image + +`f(err, image)` + +#### client.createImage(options, callback) +Adds a new image from a server + +Options include: + +```js +{ + name: 'image name', + server: 'server or server id' +} +``` + +Returns the newly created image + +`f(err, image)` + +#### client.destroyImage(image, callback) +Destroys the specified image and returns a confirmation + +`f(err, {ok: imageId})` \ No newline at end of file diff --git a/docs/providers/oneandone/getting-started-compute.md b/docs/providers/oneandone/getting-started-compute.md new file mode 100644 index 000000000..bb8afd079 --- /dev/null +++ b/docs/providers/oneandone/getting-started-compute.md @@ -0,0 +1,93 @@ +# Getting started with pkgcloud & Oneandone + +The onandone node.js SDK is available as part of `pkgcloud`, a multi-provider cloud provisioning package. + +To install `pkgcloud` from the command line: + +``` +npm install pkgcloud +``` + +Don't have `npm` or `node` yet? [Get it now](http://nodejs.org/download). + +## Using pkgcloud + +In this example, we're going to create a 1&1 compute client, create two servers, and then output their details to the command line. + +*Note: We're going to use [lodash.js](https://lodash.com) for some convenience functions.* + +```Javascript +var pkgcloud = require('pkgcloud'), + _ = require('lodash'); + +// create our client with your 1&1 token +var client = pkgcloud.providers.oneandone.compute.createClient({ + token: 'api-key' +}); + +// first we're going to get our flavors +client.getFlavors(function (err, flavors) { + if (err) { + console.dir(err); + return; + } + + // then get our base images + client.getImages(function (err, images) { + if (err) { + console.dir(err); + return; + } + + // Pick a 512MB instance flavor + var flavor = _.findWhere(flavors, { name: '512MB Standard Instance' }); + + // Pick an image based on Ubuntu 14.04 + var image = _.findWhere(images, { name: 'Ubuntu 14.04' }); + + // Create our first server + client.createServer({ + name: 'server1', + image: image, + flavor: flavor.id, + location:location.id + }, handleServerResponse); + + // Create our second server + client.createServer({ + name: 'server2', + image: image, + flavor: flavor.id, + location:location.id, + }, handleServerResponse); + }); +}); + +// This function will handle our server creation, +// as well as waiting for the server to come online after we've +// created it. +function handleServerResponse(err, server) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER CREATED: ' + server.name + ', waiting for active status'); + + // Wait for status: ACTIVE on our server, and then callback + server.setWait({ status: 'ACTIVE' }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER INFO'); + console.log(server.name); + console.log(server.status); + console.log(server.id); + + console.log('Make sure you DELETE server: ' + server.id + + ' in order to not accrue billing charges'); + }); +} +``` diff --git a/docs/providers/oneandone/loadbalancer.md b/docs/providers/oneandone/loadbalancer.md new file mode 100644 index 000000000..bddfa8c70 --- /dev/null +++ b/docs/providers/oneandone/loadbalancer.md @@ -0,0 +1,193 @@ +## Using the 1&1 Load Balancer provider + +#### BETA - This API may change as additional providers for load balancers are added to pkgcloud + +### Table of Contents + +* LoadBalancer + * [Model](#loadbalancer-model) + * [Managing Load Balancers](#loadbalancer-apis) + * [Nodes](#nodes) +* Node + * [Model](#node-model) + +### Getting Started + +Creating a loadbalancer client is straight-forward: + +``` js + var oneandone = pkgcloud.loadbalancer.createClient({ + provider: 'oneandone', // required + token: 'your-api-key' // required + }); +``` + +*[More options for creating clients](README.md)* + +Once you have a client, creating a load balancer is straight-forward. + +```Javascript +oneandone.createLoadBalancer({ + name: 'lb test', + healthCheckInterval: 40, + Persistence: true, + persistenceTime: 1200, + method: oneandone.LoadBalancerMethod.ROUND_ROBIN, + rules: [ + { + protocol: 'TCP', + port_balancer: 80, + port_server: 80, + source: '0.0.0.0' + } + ], + location: '4EFAD5836CE43ACA502FD5B99BEE44EF' + }, function(err, loadBalancer) { + // use your new loadBalancer here +}); +``` + +### LoadBalancer Model + +A LoadBalancer has following properties: + +```Javascript +{ + "id": "13C3F75BA55AF28B8B2B4E508786F48B", + "name": "My Load Balancer 1", + "ip": "70.35.192.35", + "healthCheckTest": "TCP", + "healthCheckInterval": 15, + "persistence": true, + "persistenceTime": 1200, + "datacenter": { + "id": "908DC2072407C94C8054610AD5A53B8C", + "country_code": "US", + "location": "United States of America" + }, + "rules": [ + { + "id": "E7CC65B301050BA1722F19EE0B08F1DB", + "protocol": "TCP", + "port_balancer": 90, + "port_server": 90, + "source": "0.0.0.0" + } + ], + "nodes": [{ + "id": "8808D9603ED0001D97F70854EDE3C195", + "ip": "212.227.202.122", + "server_name": "create-test-oao" +}] +} +``` + +**Proxy Methods** + +An instance of a `LoadBalancer` has a number of convenience proxy methods. For example: + +```Javascript +client.getNodes(loadBalancer, function(err, nodes) { ... }; + +// is equivalent to + +loadBalancer.getNodes(function(err, nodes) { ... }; +``` + +View the [complete list of LoadBalancer proxy methods](#loadbalancer-proxy-methods). + +### Node Model + +A Node for LoadBalancer has the following properties: + +```Javascript +{ + "id": "8808D9603ED0001D97F70854EDE3C195", + "ip": "212.227.202.122", + "server_name": "create-test-oao" +} +``` + +### LoadBalancer APIs + +#### client.getLoadBalancers(options, callback) +Lists all loadbalancers available to your account. + +Callback returns `f(err, loadbalancers)` where `loadbalancers` is an `Array`. `options` is an optional and unused argument at this time. + +#### client.getLoadBalancer(loadBalancer, callback) +Gets specified LoadBalancer. + +Takes `loadBalancer` or `loadBalancerId` as an argument and returns the `loadBalancer` in the callback +`f(err, loadBalancer)` + +#### client.createLoadBalancer(details, callback) + +The following JS object provides a brief overview of required and optional parameters for the `createLoadBalancer` `details` argument: + +```js +{ + name: 'lb test', + healthCheckInterval: 40, + Persistence: true, + persistenceTime: 1200, + method: oneandone.LoadBalancerMethod.ROUND_ROBIN, + rules: [ + { + protocol: 'TCP', + port_balancer: 80, + port_server: 80, + source: '0.0.0.0' + } + ], + location: '4EFAD5836CE43ACA502FD5B99BEE44EF' +} +``` + +Returns the new LoadBalancer in the callback `f(err, loadBalancer)` + +#### client.updateLoadBalancer(loadBalancer, callback) +Updates the `name`, `healthCheckInterval`, `healthCheckPath`, `Persistence`, `persistenceTime` and `method` properties of the provided `loadBalancer`. + +Returns callback with `f(err)`. + +#### client.deleteLoadBalancer(loadBalancer, callback) +Deletes the specified `loadBalancer`. + +Takes `loadBalancer` or `loadBalancerId` as an argument and returns an error if unsuccessful `f(err)` + +### Nodes + +A `Node` represnets serverIPs added to a load balancer. When you setup load balancers serverIPs are loaded as nodes. + +#### client.getNodes(loadBalancer, callback) + +Returns a list of the servers/IPs attached to a load balancer. + +Callback is `f(err, nodes)`. + +#### client.addNodes(loadBalancer, nodes, callback) + +Assigns servers/IPs to a load balancer. + +##### Node Details +```Javascript +{ + serverIps=[ip1,ip2], + loadbalancer = _loadBalancer +} +``` + +Callback is `f(err, nodes)`. + +#### client.removeNode(loadBalancer, node, callback) + +Remove a `node` from the provided `loadBalancer`. Takes `loadBalancer` or `loadBalancerId` as an argument. `node` should be either the `node` or `nodeId`. + +Callback is `f(err)`. + +### LoadBalancer Proxy Methods + +##### loadBalancer.getNodes(callback) +##### loadBalancer.addNodes(nodes, callback) +##### loadBalancer.removeNode(node, callback) \ No newline at end of file diff --git a/docs/providers/openstack/README.md b/docs/providers/openstack/README.md index f525d7627..8a21a7ceb 100644 --- a/docs/providers/openstack/README.md +++ b/docs/providers/openstack/README.md @@ -1,9 +1,14 @@ -## Using the Openstack provider in pkgcloud +## Using the OpenStack provider in pkgcloud The OpenStack provider in pkgcloud supports the following services: +* [**BlockStorage**](blockstorage.md) (Cinder) * [**Compute**](compute.md) (Nova) +* [**Databases**](databases.md) (databases) * [**Storage**](storage.md) (Swift) +* [**Network**](network.md) (Neutron) +* [**Orchestration**](orchestration.md) (Heat) +* [**CDN**](cdn.md) (Poppy) ### Getting Started with Compute @@ -11,20 +16,23 @@ We've provided a [simple compute example](getting-started-compute.md) where it c ### Authentication -For all of the Openstack services, you create a client with the same options: +For all of the OpenStack services, you create a client with the same options: -```Javascript -var client = require('pkgcloud').compute.createClient({ - provider: 'openstack', - username: 'your-user-name', - password: 'your-password', - authUrl: 'https://your-identity-service' -}); +```javascript + var openstack = pkgcloud.storage.createClient({ + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required + }); ``` +**Note:** *Due to variances between OpenStack deployments, you may or may not need a `region` option.* + + ### Authentication Endpoints and Regions -All of the Openstack `createClient` calls have a few options that can be provided: +All of the OpenStack `createClient` calls have a few options that can be provided: #### region @@ -44,4 +52,4 @@ var client = require('pkgcloud').compute.createClient({ #### Tokens and Expiration -When you make your first call to a Openstack provider, your client is authenticated transparent to your API call. Openstack will issue you a token, with an expiration. When that token expires, the client will automatically re-authenticate and retrieve a new token. The caller shouldn't have to worry about this happening. +When you make your first call to a OpenStack provider, your client is authenticated transparent to your API call. OpenStack will issue you a token, with an expiration. When that token expires, the client will automatically re-authenticate and retrieve a new token. The caller shouldn't have to worry about this happening. diff --git a/docs/providers/openstack/blockstorage.md b/docs/providers/openstack/blockstorage.md new file mode 100644 index 000000000..ee1b43966 --- /dev/null +++ b/docs/providers/openstack/blockstorage.md @@ -0,0 +1,167 @@ +## Using the OpenStack Block Storage provider + +#### BETA - This API may change as additional providers for block storage are added to pkgcloud + +Creating a block-storage client is straight-forward: + +``` js + var openstack = pkgcloud.blockstorage.createClient({ + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required + }); +``` +**Note:** *Due to variances between OpenStack deployments, you may or may not need a `region` option.* + +[More options for creating clients](README.md) + +Note: For **attaching volumes to compute instances**, please see the [compute volume attachments](compute.md#volume-attachments) documentation. + +* Volume + * [Model](#volume-model) + * [APIs](#volume-apis) +* Snapshot + * [Model](#snapshot-model) + * [APIs](#snapshot-apis) +* VolumeType + * [Model](#volumetype-model) + * [APIs](#volumetype-apis) + +### Volume Model + +A Volume for BlockStorage has following properties: + +```Javascript +{ + id: '12345678-1111-2222-3333-123456789012', // id of the volume + name: 'foo3', + description: 'my volume', + status: 'available', // status of the volume + size: 100, // in GB + volumeType: 'SATA', + attachments: [], // array of the attachments for this volume + snapshotId: null, // snapshotId, if any, for this volume + createdAt: '2013-07-26T15:54:04.000000' +} +``` + +### Snapshot Model + +A Snapshot for BlockStorage has the following properties: + +```Javascript +{ + id: '12345678-1111-2222-3333-123456789012', // id of the snapshot + name: 'foo3', + description: 'my snapshot', + status: 'available', // status of the snapshot + size: 100, // in GB + volumeId: '12345678-1111-2222-3333-123456789012', + createdAt: '2013-07-26T15:54:04.000000' +} +``` + +### VolumeType Model + +A VolumeType for BlockStorage has the following properties: + +```Javascript +{ + id: '12345678-1111-2222-3333-123456789012', // id of the snapshot + name: 'SSD', + extra_specs: {} // not used presently +} +``` + +### Volume APIs + +#### client.getVolumes(options, callback) +Lists all volumes that are available to use on your OpenStack account + +Callback returns `f(err, volumes)` where `volumes` is an `Array`. `options` is an optional `boolean` which will return the full volume details if true. + +#### client.getVolume(volume, callback) +Gets specified volume. + +Takes volume or volumeId as an argument and returns the volume in the callback +`f(err, volume)` + +#### client.createVolume(details, callback) +Creates a volume with the details specified + +Options are as follows: + +```js +{ + name: 'volumeName', // required + description: 'my volume', // required + size: 100, // 100-1000 gb + volumeType: 'SSD' // optional, defaults to spindles + snapshotId: '1234567890' // optional, the snapshotId to use when creating the volume +} +``` +Returns the new volume in the callback `f(err, volume)` + +#### client.deleteVolume(volume, callback) +Deletes the specified volume + +Takes volume or volumeId as an argument and returns an error if unsuccessful `f(err)` + +#### client.updateVolume(volume, callback) +Updates the name & description on the provided volume. Does not support resize. + +Returns callback with a confirmation + +### Snapshot APIs + +#### client.getSnapshots(options, callback) +Lists all snapshots that are available to use on your OpenStack account + +Callback returns `f(err, snapshots)` where `snapshots` is an `Array`. `options` is an optional `boolean` which will return the full snapshot details if true. + +#### client.getSnapshot(snapshot, callback) +Gets specified snapshot. + +Takes snapshot or snapshotId as an argument and returns the snapshot in the callback +`f(err, snapshot)` + +#### client.createSnapshot(details, callback) +Creates a snapshot with the details specified + +Options are as follows: + +```js +{ + name: 'volumeName', // required + description: 'my volume', // required + volumeId: 'asdf1234', // required, volume id of the new snapshot + force: true // optional, defaults to false. force creation of the snapshot +} +``` +Returns the new snapshot in the callback `f(err, snapshot)` + +#### client.deleteSnapshot(snapshot, callback) +Deletes the specified snapshot + +Takes snapshot or snapshotId as an argument and returns an error if unsuccessful `f(err)` + +#### client.updateSnapshot(snapshot, callback) +Updates the name & description on the provided snapshot. + +Returns callback with a confirmation + +### VolumeType APIs + +Volume types are used to define which kind of new volume to create. + +#### client.getVolumeTypes(callback) +Lists all volumeTypes that are available to use on your OpenStack account + +Callback returns `f(err, volumeTypes)` where `volumeTypes` is an `Array`. + +#### client.getVolumeType(volumeType, callback) +Gets specified volumeType. + +Takes volumeType or volumeTypeId as an argument and returns the volumeType in the callback +`f(err, volumeType)` diff --git a/docs/providers/openstack/cdn.md b/docs/providers/openstack/cdn.md new file mode 100644 index 000000000..1c8936c46 --- /dev/null +++ b/docs/providers/openstack/cdn.md @@ -0,0 +1,92 @@ +## Using the OpenStack CDN provider + +Creating a client is straight-forward: + +``` js + var openstack = pkgcloud.cdn.createClient({ + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required + }); +``` + +[More options for creating clients](README.md) + +### API Methods + +## Base + +#### `client.getHomeDocument(function (err, homeDocument) { })` +Retrieves the home document, which allows you to navigate the remainder of the +API. Callback is `f(err, homeDocument)` where `homeDocument` is an `Object`. + +#### `client.getPing(function (err) { })` +Pings the server for any errors. Callback is `f(err)`. + +## Services + +#### client.createService(options, callback) +Creates a service with the options specified. + +Options are as follows: + +```js +{ + name: 'my-service-name', // name of service, required + domains: [ ... ], // list of domains for service, required + origins: [ ... ], // list of origins for service, required + caching: [ ... ], // list of caching rules for service, optional + restrictions: [ ... ], // list of restrictions on where service can be accessed from, optional + flavorId: 'cdn' // ID of CDN flavor to use, required +} +``` +Callback is `f(err, service)`, where `service` is the created service. + +#### client.getServices([options], callback) + +Lists all created services. Callback is `f(err, services)` where `services` +is an `Array`. + +#### client.getService(service, callback) + +Retrieve the created service for the provided service or serviceName. Callback is `f(err, +service)`. + +#### client.updateService(service, callback) + +Update the provided service. + +The following values from the provided service are updatable. + +```js +{ + name: 'my-service-name', // name of service, required + domains: [ ... ], // list of domains for service, required + origins: [ ... ], // list of origins for service, required + flavorId: 'cdn' // ID of CDN flavor to use, required +} +``` + +#### client.deleteService(service, callback) + +Delete the created service. Callback is `f(err)`. + +## Service Assets + +#### client.deleteServiceCachedAssets(service, assetUrl, callback) + +Purge the service's cached asset (if `assetUrl` is specified) or all cached +assets (if `assetUrl` is not specified). Callback is `f(err)`. + +## Flavors + +#### client.getFlavors(options, callback) + +Lists all available CDN flavors. Callback is `f(err, flavors)` where +`flavors` is an Array. + +#### client.getFlavor(flavor, callback) + +Retrieve the CDN flavor for a provided flavor or flavorId. Callback is `f(err, +flavor)`. diff --git a/docs/providers/openstack/compute.md b/docs/providers/openstack/compute.md index 4e64e39a5..fc428c76d 100644 --- a/docs/providers/openstack/compute.md +++ b/docs/providers/openstack/compute.md @@ -1,16 +1,18 @@ -##Using the Openstack Compute provider +##Using the OpenStack Compute provider Creating a client is straight-forward: ``` js var openstack = pkgcloud.compute.createClient({ - provider: 'openstack', - username: 'your-user-name', - password: 'your-password', - authUrl: 'https://your-identity-service' + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required }); ``` +**Note:** *Due to variances between OpenStack deployments, you may or may not need a `region` option.* + [More options for creating clients](README.md) ### API Methods @@ -18,7 +20,7 @@ Creating a client is straight-forward: **Servers** #### client.getServers(callback) -Lists all servers that are available to use on your Openstack account +Lists all servers that are available to use on your OpenStack account Callback returns `f(err, servers)` where `servers` is an `Array` @@ -60,6 +62,27 @@ Options include: ``` Returns callback with a confirmation +#### client.rebuildServer(server, options, callback) +Rebuilds the specifed server with options + +Options include: + +```js +{ + image: '45a01744-2bcf-4a23-ae88-63317f768a2f', // required; image ID or instance of pkgcloud.core.compute.Image + accessIPv4: '123.45.67.89' // optional; IPv4 address of server + accessIPv6: 'f0::09', // optional; IPv6 address of server + adminPass: 'foobar', // optional; administrator password for the server + metadata: { group: 'webservers' }, // optional; metadata key/value pairs + personality: [ { path: '/etc/banner.txt', contents: 'ICAgICAgDQo' } ], // optional; personality files - path and contents + 'OS-DCF:diskConfig': 'AUTO' // optional; disk configuration value ("AUTO" | "MANUAL") +} +``` +Returns callback with a confirmation + +**Note about backwards compatiblity:** +For backwards compatibility, it is also possible to pass an image ID or instance of `pkgcloud.core.compute.Image` as the value of the `options` argument. + #### client.getVersion(callback) Get the current version of the api returned in a callback `f(err, version)` @@ -76,7 +99,7 @@ Returns a list of all possible server flavors available in the callback `f(err, flavors)` #### client.getFlavor(flavor, callback) -Returns the specified rackspace flavor of Openstack Images by ID or flavor +Returns the specified flavor of OpenStack Images by ID or flavor object in the callback `f(err, flavor)` **images** @@ -114,7 +137,7 @@ Destroys the specified image and returns a confirmation ## Volume Attachments -Attaching a volume to a compute instance requires using a rackspace compute client, as well as possessing a `volume` or `volumeId`. Detaching volumes behaves the same way. +Attaching a volume to a compute instance requires using an openstack compute client, as well as possessing a `volume` or `volumeId`. Detaching volumes behaves the same way. #### client.getVolumeAttachments(server, callback) @@ -138,4 +161,4 @@ Attaches the provided `volume` to the `server`. `volume` may either be the `volu Detaches the provided `attachment` from the server. `attachment` may either be the `attachmentId` or an object with `attachmentId` as a property. If the `volume` is mounted this call will return an err. -`f(err)` \ No newline at end of file +`f(err)` diff --git a/docs/providers/openstack/databases.md b/docs/providers/openstack/databases.md new file mode 100644 index 000000000..1c5cd768f --- /dev/null +++ b/docs/providers/openstack/databases.md @@ -0,0 +1,91 @@ +## Using the OpenStack Database provider + +Creating a client is straight-forward: + +``` js + var rackspace = pkgcloud.database.createClient({ + provider: 'openstack', + username: 'your-user-name', + password: 'your-password', + authUrl: 'https://your-identity-service' + }); +``` + +[More options for creating clients](README.md) + +### Creating a MySQL Database + +The steps for provision a MySQL database from OpenStack cloud databases are: + +1. Choose a flavor (memory RAM size) +2. Create an instance of a database server. +3. When the instance is provisioned, create your database. + +Also you can manage users across your instances and each instance can handle several databases. + +``` js + client.getFlavors(function (err, flavors) { + // + // Look at the availables flavors for your instance + // + console.log(flavors); + + // + // Lets choose the ID 1 for 512MB flavor + // + client.getFlavor(1, function (err, flavor) { + // + // Create the instance for host the databases. + // + client.createInstance({ + name: 'test-instance', + flavor: flavor, + // + // Optional, you can choose the disk size for the instance + // (1 - 8) in GB. Default to 1 + // + size: 3 + // + // Optional, you can give an array of database names for initialize + // when the instace is ready + // + databases: ['first-database', 'second-database'] + }, function (err, instance) { + // + // At this point when the instance is ready we can manage the databases + // + client.createDatabase({ + name: 'test-database', + instance: instance + }, function (err, database) { + // + // Log the result + // + console.log(database); + }); + }); + }) + }); +``` + +### API Methods ### + +#### client.createUser(options, callback) + +Allows the creation of specific users to have access to any database you create. + +Accepts one user object as the `options` argument or an array of user objects. + +A user object is defined as follows: + +```js +{ + username: 'nodejitsu', // required + password: 'foobar', // required + databases: ['first-db, second-db'], // required (Can be either string or array) + instance: instance // required (instance or instanceId) +} +``` + +**note**: If creating multiple users, the instance provided must be the same for +both. diff --git a/docs/providers/openstack/getting-started-compute.md b/docs/providers/openstack/getting-started-compute.md index d947ee178..bad2400f1 100644 --- a/docs/providers/openstack/getting-started-compute.md +++ b/docs/providers/openstack/getting-started-compute.md @@ -1,8 +1,8 @@ -# Getting started with pkgcloud & Openstack +# Getting started with pkgcloud & OpenStack -The Openstack node.js SDK is available as part of `pkgcloud`, a multi-provider cloud provisioning package +The OpenStack node.js SDK is available as part of `pkgcloud`, a multi-provider cloud provisioning package -Pkgcloud currently supports Openstack Nova (compute) and Openstack Swift (storage). +Pkgcloud currently supports OpenStack Nova (compute) and OpenStack Swift (storage). To install `pkgcloud` from the command line: @@ -14,19 +14,21 @@ Don't have `npm` or `node` yet? [Get it now](http://nodejs.org/download). ## Using pkgcloud -In this example, we're going to create a Openstack compute client, create two servers, and then output their details to the command line. +In this example, we're going to create a OpenStack compute client, create two servers, and then output their details to the command line. -*Note: We're going to use [underscore.js](http://underscorejs.org) for some convenience functions.* +*Note: We're going to use [lodash.js](https://lodash.com) for some convenience functions.* +*Note: For DevStack, change AuthUrl to http://:5000* ```Javascript var pkgcloud = require('pkgcloud'), - _ = require('underscore'); + _ = require('lodash'); // create our client with your openstack credentials var client = pkgcloud.compute.createClient({ provider: 'openstack', username: 'your-user-name', password: 'your-password', + region: 'RegionOne', //default for DevStack, might be different on other OpenStack distributions authUrl: 'https://your-identity-service' }); @@ -45,10 +47,10 @@ client.getFlavors(function (err, flavors) { } // Pick a 512MB instance flavor - var flavor = _.findWhere(flavors, { name: '512MB Standard Instance' }); + var flavor = _.findWhere(flavors, { name: 'm1.tiny' }); // Pick an image based on Ubuntu 12.04 - var image = _.findWhere(images, { name: 'Ubuntu 12.04 LTS (Precise Pangolin)' }); + var image = _.findWhere(images, { name: 'cirros-0.3.2-x86_64-uec' }); // Check if this version is correct // Create our first server client.createServer({ @@ -77,8 +79,8 @@ function handleServerResponse(err, server) { console.log('SERVER CREATED: ' + server.name + ', waiting for active status'); - // Wait for status: ACTIVE on our server, and then callback - server.setWait({ status: 'ACTIVE' }, 5000, function (err) { + // Wait for status: RUNNING on our server, and then callback + server.setWait({ status: server.STATUS.running }, 5000, function (err) { if (err) { console.dir(err); return; @@ -93,4 +95,4 @@ function handleServerResponse(err, server) { ' in order to not accrue billing charges'); }); } -``` \ No newline at end of file +``` diff --git a/docs/providers/openstack/network.md b/docs/providers/openstack/network.md new file mode 100644 index 000000000..3df2a0b58 --- /dev/null +++ b/docs/providers/openstack/network.md @@ -0,0 +1,315 @@ +##Using the OpenStack Network provider + +Creating a client is straight-forward: + +``` js + var openstack = pkgcloud.network.createClient({ + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required + }); +``` + +**Note:** *Due to variances between OpenStack deployments, you may or may not need a `region` option.* + +### API Methods + +**Networks** + +#### client.getNetworks(callback) +Lists all networks that are available to use on your OpenStack account + +Callback returns `f(err, networks)` where `networks` is an `Array` + +#### client.getNetwork(network, callback) +Gets specified network + +Takes network or networkId as an argument and returns the network in the callback +`f(err, network)` + +#### client.createNetwork(options, callback) +Creates a network with the options specified + +Options are as follows: + +```js +{ + name: 'networkName', // optional + adminStateUp : true, // optional + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, Admin only +} +``` +Returns the network in the callback `f(err, network)` + +#### client.updateNetwork(options, callback) +Updates a network with the options specified + +Options are as follows: + +```js +{ + id : 'networkId', // required + name: 'networkName', // optional + adminStateUp : true, // optional + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, Admin only +} +``` +Returns the network in the callback `f(err, network)` + +#### client.destroyNetwork(network, callback) +Destroys the specified network + +Takes network or networkId as an argument and returns the id of the destroyed network in the callback `f(err, networkId)` + +**Subnets** + +#### client.getSubnets(callback) +Lists all subnets that are available to use on your OpenStack account + +Callback returns `f(err, subnets)` where `subnets` is an `Array` + +#### client.getSubnet(subnet, callback) +Gets specified subnet + +Takes subnet or subnetId as an argument and returns the subnet in the callback +`f(err, subnet)` + +#### client.createSubnet(options, callback) +Creates a subnet with the options specified + +Options are as follows: + +```js +{ + name: 'subnetName', // optional + networkId : 'networkId', // required, The ID of the attached network. + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + gatewayIp : 'gateway ip address', // optional,The gateway IP address. + enableDhcp : true // Set to true if DHCP is enabled and false if DHCP is disabled. +} +``` +Returns the subnet in the callback `f(err, subnet)` + +#### client.updateSubnet(options, callback) +Updates a subnet with the options specified + +Options are as follows: + +```js +{ + id : 'subnetId', // required + name: 'subnetName', // optional + networkId : 'networkId', // required, The ID of the attached network. + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + gatewayIp : 'gateway ip address', // optional,The gateway IP address. + enableDhcp : true // Set to true if DHCP is enabled and false if DHCP is disabled. +} +``` +Returns the subnet in the callback `f(err, subnet)` + +#### client.destroySubnet(subnet, callback) +Destroys the specified subnet + +Takes subnet or subnetId as an argument and returns the id of the destroyed subnet in the callback `f(err, subnetId)` + +**Ports** + +#### client.getPorts(callback) +Lists all ports that are available to use on your OpenStack account + +Callback returns `f(err, ports)` where `ports` is an `Array` + +#### client.getPort(port, callback) +Gets specified port + +Takes port or portId as an argument and returns the port in the callback +`f(err, port)` + +#### client.createPort(options, callback) +Creates a port with the options specified + +Options are as follows: + +```js +{ + name: 'portName', // optional + adminStateUp : true, // optional, The administrative status of the router. Admin-only + networkId : 'networkId', // required, The ID of the attached network. + status : 'text status', // optional, The status of the port. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + macAddress: 'mac address' // optional + fixedIps : ['ip address1', 'ip address 2'], // optional. + securityGroups : ['security group1', 'security group2'] // optional, Specify one or more security group IDs. +} +``` +Returns the port in the callback `f(err, port)` + +#### client.updatePort(options, callback) +Updates a port with the options specified + +Options are as follows: + +```js +{ + id : 'portId', // required + name: 'portName', // optional + adminStateUp : true, // optional, The administrative status of the router. Admin-only + networkId : 'networkId', // required, The ID of the attached network. + status : 'text status', // optional, The status of the port. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + macAddress: 'mac address' // optional + fixedIps : ['ip address1', 'ip address 2'], // optional. + securityGroups : ['security group1', 'security group2'] // optional, Specify one or more security group IDs. +} +``` +Returns the port in the callback `f(err, port)` + +#### client.destroyPort(port, callback) +Destroys the specified port + +Takes port or portId as an argument and returns the id of the destroyed port in the callback `f(err, portId)` + +**Security Groups** + +#### client.getSecurityGroups(callback) +Lists all security groups that are available to use on your OpenStack account + +Callback returns `f(err, securityGroups)` where `securityGroups` is an `Array` + +#### client.getSecurityGroup(securityGroup, callback) +Gets specified security group + +Takes securityGroup or securityGroupId as an argument and returns the security group in the callback +`f(err, securityGroup)` + +#### client.createSecurityGroup(options, callback) +Creates a security group with the options specified + +Options are as follows: + +```js +{ + name: 'securityGroupName', // required, name of security group + description : 'security group description', // optional, description of security group + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group. Admin-only +} +``` +Returns the created security group in the callback `f(err, securityGroup)` + +#### client.destroySecurityGroup(securityGroup, callback) +Destroys the specified security group + +Takes securityGroup or securityGroupId as an argument and returns the id of the destroyed security group in the callback `f(err, securityGroupId)` + +**Security Group Rules** + +#### client.getSecurityGroupRules(callback) +Lists all security group rules that are available to use on your OpenStack account + +Callback returns `f(err, securityGroupRules)` where `securityGroupRules` is an `Array` + +#### client.getSecurityGroupRule(securityGroupRule, callback) +Gets specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the security group rule in the callback +`f(err, securityGroupRule)` + +#### client.createSecurityGroupRule(options, callback) +Creates a security group rule with the options specified + +Options are as follows: + +```js +{ + securityGroupId: 'securityGroupId', // required, The security group ID to associate with this security group rule. + direction: 'ingress|egress', // required, The direction in which the security group rule is applied. + ethertype: 'IPv4|IPv6', // optional, + portRangeMin: portNumber, // optional, The minimum port number in the range that is matched by the security group rule. + portRangeMax: portNumber, // optional, The maximum port number in the range that is matched by the security group rule. + protocol: 'tcp|udp|icmp', // optional, The protocol that is matched by the security group rule + remoteGroupId: 'remote group id', // optional, The remote group ID to be associated with this security group rule. You can specify either this or remoteIpPrefix. + remoteIpPrefix: 'remote IP prefix', // optional, The remote IP prefix to be associated with this security group rule. You can specify either this or remoteGroupId. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group rule. Admin-only +} +``` +Returns the created security group rule in the callback `f(err, securityGroupRule)` + +#### client.destroySecurityGroupRule(securityGroupRule, callback) +Destroys the specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the id of the destroyed security group rule in the callback `f(err, securityGroupRuleId)` + +**Security Groups** + +#### client.getSecurityGroups(callback) +Lists all security groups that are available to use on your OpenStack account + +Callback returns `f(err, securityGroups)` where `securityGroups` is an `Array` + +#### client.getSecurityGroup(securityGroup, callback) +Gets specified security group + +Takes securityGroup or securityGroupId as an argument and returns the security group in the callback +`f(err, securityGroup)` + +#### client.createSecurityGroup(options, callback) +Creates a security group with the options specified + +Options are as follows: + +```js +{ + name: 'securityGroupName', // required, name of security group + description : 'security group description', // optional, description of security group + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group. Admin-only +} +``` +Returns the created security group in the callback `f(err, securityGroup)` + +#### client.destroySecurityGroup(securityGroup, callback) +Destroys the specified security group + +Takes securityGroup or securityGroupId as an argument and returns the id of the destroyed security group in the callback `f(err, securityGroupId)` + +**Security Group Rules** + +#### client.getSecurityGroupRules(callback) +Lists all security group rules that are available to use on your OpenStack account + +Callback returns `f(err, securityGroupRules)` where `securityGroupRules` is an `Array` + +#### client.getSecurityGroupRule(securityGroupRule, callback) +Gets specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the security group rule in the callback +`f(err, securityGroupRule)` + +#### client.createSecurityGroupRule(options, callback) +Creates a security group rule with the options specified + +Options are as follows: + +```js +{ + securityGroupId: 'securityGroupId', // required, The security group ID to associate with this security group rule. + direction: 'ingress|egress', // required, The direction in which the security group rule is applied. + ethertype: 'IPv4|IPv6', // optional, + portRangeMin: portNumber, // optional, The minimum port number in the range that is matched by the security group rule. + portRangeMax: portNumber, // optional, The maximum port number in the range that is matched by the security group rule. + protocol: 'tcp|udp|icmp', // optional, The protocol that is matched by the security group rule + remoteGroupId: 'remote group id', // optional, The remote group ID to be associated with this security group rule. You can specify either this or remoteIpPrefix. + remoteIpPrefix: 'remote IP prefix', // optional, The remote IP prefix to be associated with this security group rule. You can specify either this or remoteGroupId. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group rule. Admin-only +} +``` +Returns the created security group rule in the callback `f(err, securityGroupRule)` + +#### client.destroySecurityGroupRule(securityGroupRule, callback) +Destroys the specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the id of the destroyed security group rule in the callback `f(err, securityGroupRuleId)` diff --git a/docs/providers/openstack/orchestration.md b/docs/providers/openstack/orchestration.md new file mode 100644 index 000000000..1d3f2de1a --- /dev/null +++ b/docs/providers/openstack/orchestration.md @@ -0,0 +1,158 @@ +##Using the OpenStack Orchestration provider + +Creating a client is straight-forward: + +``` js + var openstack = pkgcloud.orchestration.createClient({ + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required + }); +``` + +**Note:** *Due to variances between OpenStack deployments, you may or may not need a `region` option.* + +[More options for creating clients](README.md) + +### API Methods + +## Stacks + +#### client.getStacks([options], callback) +Lists all stacks that are available to use on your OpenStack account + +Callback returns `f(err, stacks)` where `stacks` is an `Array` + +#### client.createStack(options, callback) +Creates a stack with the options specified. + +Options are as follows: + +```js +{ + name: 'my-stack-name', // required + timeout: 30, // timeout, in minutes, required + templateUrl: 'http://path.to.some.openstack.heat.template', // required, unless you pass template directly + template: { ... }, // optional, unless you don't provide templateUrl + parameters: { ... }, // optional parameters for the stack + environment: { ... }, // optional environment values for the stack + files: { ... }, // optional files for the stack +} +``` +Returns the stack in the callback `f(err, stack)` + +#### client.getStack(stack, callback) +Retrieves the provided stack or stackId from the service. Callback has the signature `f(err, stack)`. + +Result stack-object includes following fields +```js +{ + id: , + name: , + status: , + description: , + templateDescription: , + statusReason: , + owner: , + disableRollback: , + parameters: , + capabilities: , + notificationTopics: , + timeout: , + createdAt: , + updatedAt: , + outputs: // Outputs field has value only if outputs are defined in template and the stack has been instantiated. +} +``` + +#### client.previewStack(details, callback) +Identical to the `client.createStack()` call, except it only previews the creation, instead of actually provisioning +the stack. + +Returns the previewed stack in the callback `f(err, stack)` + +#### client.adoptStack(details, callback) +Identical to the `client.createStack()` call, except it requires passing `details.stackData` which is the `abandonedStack` +value returned from `client.abandonStack()`. + +Returns the created stack in the callback `f(err, stack)` + +#### client.updateStack(stack, callback) + +Update the provided stack. + +The following values from the provided stack are updatable. + +```js +{ + name: 'my-stack-name', // required + timeout: 30, // timeout, in minutes, required + templateUrl: 'http://path.to.some.openstack.heat.template', // required, unless you pass template directly + template: { ... }, // optional, unless you don't provide templateUrl + parameters: { ... }, // optional parameters for the stack + environment: { ... }, // optional environment values for the stack + files: { ... }, // optional files for the stack +} +``` + +#### client.deleteStack(stack, callback) + +Delete the created stack, and delete the resources. Callback is `f(err)`. + +#### client.abandonStack(stack, callback) + +Delete the created stack, but leave the resources running. Will callback with `f(err, abandonedStack)` where the +`abandonedStack` would be passed in as an option to `client.createStack()`. + +#### client.getTemplate(stack, callback) + +Get the template for a provided stack. Will callback with `f(err, template)`. + +## Resources + +#### client.getResource(stack, resource, callback) + +Get the resource for a provided stack and resource or resourceName in the callback `f(err, +resource)` + +#### client.getResources(stack, [options], callback) +Get the resources for a provided stack. Callback is `f(err, resources)`. + +Options are as follows: +```js +{ + nestedDepth: 3 // include resources from nested stacks up to nestedDepth levels of recursion +} +``` + +#### client.getResourceTypes(callback) +Get a list of valid resource types. Callback is `f(err, resourceTypes)`. + +#### client.getResourceSchema(resourceType, callback) +Get the schema for a provided resourceType. Callback is `f(err, resourceSchema)`. + +#### client.getResourceTemplate(resourceType, callback) +Get the template for a provided resourceType. Callback is `f(err, resourceTemplate)`. + +## Events + +#### client.getEvent(stack, resource, eventId, callback) +Get the event for a provided stack, resource and eventId. + +`f(err, event)` + +#### client.getEvents(stack, callback) +Get all of the events for a provided stack + +`f(err, events)` + +#### client.getResourceEvents(stack, resource, callback) +Get all of the events for a stack and resource. + +`f(err, events)` + +## Templates + +#### client.validateTemplate(template, callback) +Validates a provided template, with a callback of `f(err, template)`. diff --git a/docs/providers/openstack/storage.md b/docs/providers/openstack/storage.md index 0d07c9b49..14c322a6b 100644 --- a/docs/providers/openstack/storage.md +++ b/docs/providers/openstack/storage.md @@ -1,4 +1,4 @@ -## Using the Openstack Storage provider +## Using the OpenStack Storage provider * Container * [Model](#container-model) @@ -10,19 +10,21 @@ Creating a client is straight-forward: ``` js - var rackspace = pkgcloud.storage.createClient({ - provider: 'openstack', - username: 'your-user-name', - password: 'your-password', - authUrl: 'https://your-identity-service' + var openstack = pkgcloud.storage.createClient({ + provider: 'openstack', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required }); ``` -Learn about [more options for creating clients](README.md) in the Openstack `storage` provider. +**Note:** *Due to variances between OpenStack deployments, you may or may not need a `region` option.* + +Learn about [more options for creating clients](README.md) in the OpenStack `storage` provider. ### Container Model -A Container for Openstack has following properties: +A Container for OpenStack has following properties: ```Javascript { @@ -37,7 +39,7 @@ A Container for Openstack has following properties: ### File Model -A File for Openstack has the following properties: +A File for OpenStack has the following properties: ```Javascript { @@ -127,7 +129,7 @@ client.removeContainerMetadata(container, { year: false }, function(err, c) { ### File APIs -* [`client.upload(options, function(err, result) { })`](#clientuploadoptions-functionerr-result--) +* [`client.upload(options)`](#clientuploadoptions) * [`client.download(options, function(err, file) { })`](#clientdownloadoptions-functionerr-file--) * [`client.getFile(container, file, function(err, file) { })`](#clientgetfilecontainer-file-functionerr-file--) * [`client.getFiles(container, function(err, file) { })`](#clientgetfilescontainer-functionerr-file--) @@ -150,9 +152,8 @@ var myContainer = new Container({ name: 'my-container' }); client.getFile(myContainer, 'my-file', function(err, file) { ... }); ``` -#### client.upload(options, function(err, result) { }) - -Returns a writeable stream. Upload a new file to a [`container`](#container-model). `result` will be `true` on success. +#### client.upload(options) +Returns a `writableStream`. Upload a new file to a [`container`](#container-model). `writableStream` will emit `success` on completion of the upload, and will emit `error` on any failure. Function for `success` is `function(file) { ... }` where `file` is a [`file`](#file-model) model To upload a file, you need to provide an `options` argument: @@ -161,57 +162,29 @@ var options = { // required options container: 'my-container', // this can be either the name or an instance of container remote: 'my-file', // name of the new file - - // optional, either stream or local - stream: myStream, // any instance of a readable stream - local: '/path/to/local/file' // a path to any local file - - // Other optional values - metadata: { // provide any number of property/values for metadata - campaign: '2012 magazine' - }, - headers: { // optionally provide raw headers to send to cloud files - 'content-type': 'application/json' - } + contentType: 'application/json', // optional mime type for the file, will attempt to auto-detect based on remote name + size: 1234 // size of the file }; ``` -You need not provide either `stream` or `local`. `client.upload` returns a writeable stream, so you can simply pipe directly into it from your stream. For example: - ```Javascript -var fs = require('fs'), - pkgcloud = require('pkgcloud'); - -var client = pkgcloud.providers.rackspace.storage.createClient({ ... }); - -var myFile = fs.createReadStream('/my/local/file'); - -myFile.pipe(client.upload({ - container: 'my-container', - remote: 'my-file' -}, function(err, result) { - // handle the upload result -})); -``` - -You could also upload a local file via the `local` property on `options`: + var readStream = fs.createReadStream('a-file.txt'); + var writeStream = client.upload({ + container: 'a-container', + remote: 'remote-file-name.txt' + }); -```Javascript -var pkgcloud = require('pkgcloud'); + writeStream.on('error', function(err) { + // handle your error case + }); -var client = pkgcloud.providers.rackspace.storage.createClient({ ... }); + writeStream.on('success', function(file) { + // success, file will be a File model + }); -client.upload({ - container: 'my-container', - remote: 'my-file', - local: '/path/to/my/file' -}, function(err, result) { - // handle the upload result -}); + readStream.pipe(writeStream); ``` -This is functionally equivalent to piping from an `fs.createReadStream`, but has a simplified calling convention. - #### client.download(options, function(err, file) { }) Returns a readable stream. Download a [`file`](#file-model) from a [`container`](#container-model). @@ -236,7 +209,7 @@ You need not provide either `stream` or `local`. `client.download` returns a rea var fs = require('fs'), pkgcloud = require('pkgcloud'); -var client = pkgcloud.providers.rackspace.storage.createClient({ ... }); +var client = pkgcloud.providers.openstack.storage.createClient({ ... }); var myFile = fs.createWriteStream('/my/local/file'); @@ -253,7 +226,7 @@ You could also download to a local file via the `local` property on `options`: ```Javascript var pkgcloud = require('pkgcloud'); -var client = pkgcloud.providers.rackspace.storage.createClient({ ... }); +var client = pkgcloud.providers.openstack.storage.createClient({ ... }); client.download({ container: 'my-container', diff --git a/docs/providers/rackspace/README.md b/docs/providers/rackspace/README.md index f47d8adf4..432771682 100644 --- a/docs/providers/rackspace/README.md +++ b/docs/providers/rackspace/README.md @@ -7,7 +7,10 @@ The Rackspace provider in pkgcloud supports the following services: * [**Databases**](databases.md) (Cloud Databases) * [**DNS**](dns.md) (Cloud DNS) *(beta)* * [**Block Storage**](blockstorage.md) (Cloud Block Storage) *(beta)* +* [**Orchestration**](orchestration.md) (Cloud Orchestration) *(beta)* * [**Load Balancers**](loadbalancer.md) (Cloud Load Balancers) *(beta)* +* [**Network**](network.md) (Cloud Networks) *(beta)* +* [**CDN**](cdn.md) (Rackspace CDN) *(beta)* ### Getting Started with Compute @@ -31,33 +34,40 @@ In addition to your `apiKey`, you could alternately provide your `password` as a All of the Rackspace `createClient` calls have a few options that can be provided: -#### authUrl +#### region -`authUrl` specifies the authentication endpoint used to create a token for your Rackspace client. By default, this is set to the Global endpoint: https://identity.api.rackspacecloud.com. +`region` specifies which region of a service to use. Different services have different regions enabled, and DNS doesn't require a region at all. The current list of regions is: -##### Authenticating against the London endpoint +- `DFW` (Dallas, Texas) +- `ORD` (Chicago, Illinois) +- `IAD` (Washington, DC) +- `LON` (London, UK) +- `SYD` (Sydney, Austrailia) +- `HKG` (Hong Kong, China) + +##### Specifying a custom region ```Javascript var client = require('pkgcloud').compute.createClient({ provider: 'rackspace', username: 'your-user-name', apiKey: 'your-api-key', - authUrl: 'https://lon.identity.api.rackspacecloud.com' + region: 'ORD' }); ``` -#### region +#### authUrl -`region` specifies which region of a service to use. For example, when you authenticate with the global endpoint for compute, you have the option of either `DFW`, `ORD`, or `SYD`. The default region is `DFW`. Previous pkgcloud versions did not let you specify which region you used, so all calls were against `DFW`. +`authUrl` specifies the authentication endpoint used to create a token for your Rackspace client. By default, this is set to the Global endpoint: https://identity.api.rackspacecloud.com. -##### Specifying a custom region +##### Authenticating against the London endpoint ```Javascript var client = require('pkgcloud').compute.createClient({ provider: 'rackspace', username: 'your-user-name', apiKey: 'your-api-key', - region: 'ORD' + authUrl: 'https://lon.identity.api.rackspacecloud.com' }); ``` diff --git a/docs/providers/rackspace/blockstorage.md b/docs/providers/rackspace/blockstorage.md index cb59ef682..32ff08808 100644 --- a/docs/providers/rackspace/blockstorage.md +++ b/docs/providers/rackspace/blockstorage.md @@ -1,4 +1,4 @@ -##Using the Rackspace Block Storage provider +## Using the Rackspace Block Storage provider #### BETA - This API may change as additional providers for block storage are added to pkgcloud @@ -6,9 +6,12 @@ Creating a block-storage client is straight-forward: ``` js var rackspace = pkgcloud.blockstorage.createClient({ - provider: 'rackspace', - username: 'your-user-name', - apiKey: 'your-api-key' + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine }); ``` diff --git a/docs/providers/rackspace/cdn.md b/docs/providers/rackspace/cdn.md new file mode 100644 index 000000000..9596a4b86 --- /dev/null +++ b/docs/providers/rackspace/cdn.md @@ -0,0 +1,92 @@ +## Using the Rackspace CDN provider + +Creating a client is straight-forward: + +``` js + var rackspace = pkgcloud.cdn.createClient({ + provider: 'rackspace', // required + username: 'your-user-name', // required + password: 'your-password', // required + authUrl: 'your identity service url' // required + }); +``` + +[More options for creating clients](README.md) + +### API Methods + +## Base + +#### `client.getHomeDocument(function (err, homeDocument) { })` +Retrieves the home document, which allows you to navigate the remainder of the +API. Callback is `f(err, homeDocument)` where `homeDocument` is an `Object`. + +#### `client.getPing(function (err) { })` +Pings the server for any errors. Callback is `f(err)`. + +## Services + +#### client.createService(options, callback) +Creates a service with the options specified. + +Options are as follows: + +```js +{ + name: 'my-service-name', // name of service, required + domains: [ ... ], // list of domains for service, required + origins: [ ... ], // list of origins for service, required + caching: [ ... ], // list of caching rules for service, optional + restrictions: [ ... ], // list of restrictions on where service can be accessed from, optional + flavorId: 'cdn' // ID of CDN flavor to use, required +} +``` +Callback is `f(err, service)`, where `service` is the created service. + +#### client.getServices([options], callback) + +Lists all created services. Callback is `f(err, services)` where `services` +is an `Array`. + +#### client.getService(service, callback) + +Retrieve the created service for the provided service or serviceName. Callback is `f(err, +service)`. + +#### client.updateService(service, callback) + +Update the provided service. + +The following values from the provided service are updatable. + +```js +{ + name: 'my-service-name', // name of service, required + domains: [ ... ], // list of domains for service, required + origins: [ ... ], // list of origins for service, required + flavorId: 'cdn' // ID of CDN flavor to use, required +} +``` + +#### client.deleteService(service, callback) + +Delete the created service. Callback is `f(err)`. + +## Service Assets + +#### client.deleteServiceCachedAssets(service, assetUrl, callback) + +Purge the service's cached asset (if `assetUrl` is specified) or all cached +assets (if `assetUrl` is not specified). Callback is `f(err)`. + +## Flavors + +#### client.getFlavors(options, callback) + +Lists all available CDN flavors. Callback is `f(err, flavors)` where +`flavors` is an Array. + +#### client.getFlavor(flavor, callback) + +Retrieve the CDN flavor for a provided flavor or flavorId. Callback is `f(err, +flavor)`. diff --git a/docs/providers/rackspace/compute.md b/docs/providers/rackspace/compute.md index 25bedbe4c..0a3ae0934 100644 --- a/docs/providers/rackspace/compute.md +++ b/docs/providers/rackspace/compute.md @@ -1,4 +1,4 @@ -##Using the Rackspace Compute provider +## Using the Rackspace Compute provider As of the `v0.8` release of `pkgcloud`, the Compute provider uses Next Generation Cloud Servers, meaning you'll need to use a version <=0.7.x to use First Generation Cloud Servers. @@ -6,9 +6,12 @@ Creating a client is straight-forward: ``` js var rackspace = pkgcloud.compute.createClient({ - provider: 'rackspace', - username: 'your-user-name', - apiKey: 'your-api-key' + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine }); ``` @@ -61,6 +64,27 @@ Options include: ``` Returns callback with a confirmation +#### client.rebuildServer(server, options, callback) +Rebuilds the specifed server with options + +Options include: + +```js +{ + image: '45a01744-2bcf-4a23-ae88-63317f768a2f', // required; image ID or instance of pkgcloud.core.compute.Image + accessIPv4: '123.45.67.89' // optional; IPv4 address of server + accessIPv6: 'f0::09', // optional; IPv6 address of server + adminPass: 'foobar', // optional; administrator password for the server + metadata: { group: 'webservers' }, // optional; metadata key/value pairs + personality: [ { path: '/etc/banner.txt', contents: 'ICAgICAgDQo' } ], // optional; personality files - path and contents + 'OS-DCF:diskConfig': 'AUTO' // optional; disk configuration value ("AUTO" | "MANUAL") +} +``` +Returns callback with a confirmation + +**Note about backwards compatiblity:** +For backwards compatibility, it is also possible to pass an image ID or instance of `pkgcloud.core.compute.Image` as the value of the `options` argument. + #### client.getVersion(callback) Get the current version of the api returned in a callback `f(err, version)` @@ -139,4 +163,4 @@ Attaches the provided `volume` to the `server`. `volume` may either be the `volu Detaches the provided `attachment` from the server. `attachment` may either be the `attachmentId` or an object with `attachmentId` as a property. If the `volume` is mounted this call will return an err. -`f(err)` \ No newline at end of file +`f(err)` diff --git a/docs/providers/rackspace/databases.md b/docs/providers/rackspace/databases.md index 6ad70fff9..abe0c35a2 100644 --- a/docs/providers/rackspace/databases.md +++ b/docs/providers/rackspace/databases.md @@ -1,12 +1,15 @@ -##Using the Rackspace Database provider +## Using the Rackspace Database provider Creating a client is straight-forward: ``` js var rackspace = pkgcloud.database.createClient({ - provider: 'rackspace', - username: 'your-user-name', - apiKey: 'your-api-key' + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine }); ``` @@ -87,4 +90,32 @@ A user object is defined as follows: ``` **note**: If creating multiple users, the instance provided must be the same for -both. \ No newline at end of file +both. + +#### client.listRootStatus(instance, callback) + +Checks if the root user is enabled for the passed instance. + +```js +client.listRootStatus(instance, function (err, result) { + // handle err + console.log(result); // => { rootEnabled: true } +}); +``` + +#### client.enableRootUser(instance, callback) + +Enables the root user for the passed instance. + +```js +client.enableRootUser(instance, function (err, result) { + // handle err + console.log(result); + // => { + // => user: { + // => name: 'root', + // => password: '' + // => } + // => } +}); +``` diff --git a/docs/providers/rackspace/dns.md b/docs/providers/rackspace/dns.md index 58d6349cc..255be6f2b 100644 --- a/docs/providers/rackspace/dns.md +++ b/docs/providers/rackspace/dns.md @@ -10,10 +10,13 @@ Creating a client is straight-forward: ``` js + // Rackspace Cloud DNS is a global service, so no region is required + var rackspace = pkgcloud.dns.createClient({ - provider: 'rackspace', - username: 'your-user-name', - apiKey: 'your-api-key' + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine }); ``` diff --git a/docs/providers/rackspace/getting-started-compute.md b/docs/providers/rackspace/getting-started-compute.md index 9ec9832a7..4102d43e6 100644 --- a/docs/providers/rackspace/getting-started-compute.md +++ b/docs/providers/rackspace/getting-started-compute.md @@ -16,16 +16,17 @@ Don't have `npm` or `node` yet? [Get it now](http://nodejs.org/download). In this example, we're going to create a Rackspace compute client, create two servers, and then output their details to the command line. -*Note: We're going to use [underscore.js](http://underscorejs.org) for some convenience functions.* +*Note: We're going to use [lodash.js](https://lodash.com) for some convenience functions.* ```Javascript var pkgcloud = require('pkgcloud'), - _ = require('underscore'); + _ = require('lodash'); // create our client with your rackspace credentials var client = pkgcloud.providers.rackspace.compute.createClient({ username: 'your-username', - apiKey: 'your-api-key' + apiKey: 'your-api-key', + region: 'your-region' // see http://www.rackspace.com/knowledge_center/article/about-regions }); // first we're going to get our flavors @@ -91,4 +92,4 @@ function handleServerResponse(err, server) { ' in order to not accrue billing charges'); }); } -``` \ No newline at end of file +``` diff --git a/docs/providers/rackspace/loadbalancer.md b/docs/providers/rackspace/loadbalancer.md index 20ccf5cc0..b02a18c1b 100644 --- a/docs/providers/rackspace/loadbalancer.md +++ b/docs/providers/rackspace/loadbalancer.md @@ -1,4 +1,4 @@ -##Using the Rackspace Load Balancer provider +## Using the Rackspace Load Balancer provider #### BETA - This API may change as additional providers for load balancers are added to pkgcloud @@ -28,9 +28,12 @@ Creating a loadbalancer client is straight-forward: ``` js var rackspace = pkgcloud.loadbalancer.createClient({ - provider: 'rackspace', - username: 'your-user-name', - apiKey: 'your-api-key' + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine }); ``` diff --git a/docs/providers/rackspace/network.md b/docs/providers/rackspace/network.md new file mode 100644 index 000000000..104d1cb6c --- /dev/null +++ b/docs/providers/rackspace/network.md @@ -0,0 +1,247 @@ +## Using the Rackspace Network provider + +Creating a client is straight-forward: + +``` js + var rackspace = pkgcloud.network.createClient({ + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine + }); +``` + +[More options for creating clients](README.md) + +### API Methods + +## Networks + +#### client.getNetworks(callback) +Lists all networks that are available to use on your HP Cloud account + +Callback returns `f(err, networks)` where `networks` is an `Array` + +#### client.getNetwork(network, callback) +Gets specified network + +Takes network or networkId as an argument and returns the network in the callback +`f(err, network)` + +#### client.createNetwork(options, callback) +Creates a network with the options specified + +Options are as follows: + +```js +{ + name: 'networkName', // optional + adminStateUp : true, // optional + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, Admin only +} +``` +Returns the network in the callback `f(err, network)` + +#### client.updateNetwork(options, callback) +Updates a network with the options specified + +Options are as follows: + +```js +{ + id : 'networkId', // required + name: 'networkName', // optional + adminStateUp : true, // optional + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, Admin only +} +``` +Returns the network in the callback `f(err, network)` + +#### client.destroyNetwork(network, callback) +Destroys the specified network + +Takes network or networkId as an argument and returns the id of the destroyed network in the callback `f(err, networkId)` + +## Subnets + +#### client.getSubnets(callback) +Lists all subnets that are available to use on your HP Cloud account + +Callback returns `f(err, subnets)` where `subnets` is an `Array` + +#### client.getSubnet(subnet, callback) +Gets specified subnet + +Takes subnet or subnetId as an argument and returns the subnet in the callback +`f(err, subnet)` + +#### client.createSubnet(options, callback) +Creates a subnet with the options specified + +Options are as follows: + +```js +{ + name: 'subnetName', // optional + networkId : 'networkId', // required, The ID of the attached network. + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + gatewayIp : 'gateway ip address', // optional,The gateway IP address. + enableDhcp : true // Set to true if DHCP is enabled and false if DHCP is disabled. +} +``` +Returns the subnet in the callback `f(err, subnet)` + +#### client.updateSubnet(options, callback) +Updates a subnet with the options specified + +Options are as follows: + +```js +{ + id : 'subnetId', // required + name: 'subnetName', // optional + networkId : 'networkId', // required, The ID of the attached network. + shared : true, // optional, Admin only + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + gatewayIp : 'gateway ip address', // optional,The gateway IP address. + enableDhcp : true // Set to true if DHCP is enabled and false if DHCP is disabled. +} +``` +Returns the subnet in the callback `f(err, subnet)` + +#### client.destroySubnet(subnet, callback) +Destroys the specified subnet + +Takes subnet or subnetId as an argument and returns the id of the destroyed subnet in the callback `f(err, subnetId)` + +## Ports + +#### client.getPorts(callback) +Lists all ports that are available to use on your HP Cloud account + +Callback returns `f(err, ports)` where `ports` is an `Array` + +#### client.getPort(port, callback) +Gets specified port + +Takes port or portId as an argument and returns the port in the callback +`f(err, port)` + +#### client.createPort(options, callback) +Creates a port with the options specified + +Options are as follows: + +```js +{ + name: 'portName', // optional + adminStateUp : true, // optional, The administrative status of the router. Admin-only + networkId : 'networkId', // required, The ID of the attached network. + status : 'text status', // optional, The status of the port. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + macAddress: 'mac address' // optional + fixedIps : ['ip address1', 'ip address 2'], // optional. + securityGroups : ['security group1', 'security group2'] // optional, Specify one or more security group IDs. +} +``` +Returns the port in the callback `f(err, port)` + +#### client.updatePort(options, callback) +Updates a port with the options specified + +Options are as follows: + +```js +{ + id : 'portId', // required + name: 'portName', // optional + adminStateUp : true, // optional, The administrative status of the router. Admin-only + networkId : 'networkId', // required, The ID of the attached network. + status : 'text status', // optional, The status of the port. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the network. Admin-only + macAddress: 'mac address' // optional + fixedIps : ['ip address1', 'ip address 2'], // optional. + securityGroups : ['security group1', 'security group2'] // optional, Specify one or more security group IDs. +} +``` +Returns the port in the callback `f(err, port)` + +#### client.destroyPort(port, callback) +Destroys the specified port + +Takes port or portId as an argument and returns the id of the destroyed port in the callback `f(err, portId)` + +**Security Groups** + +#### client.getSecurityGroups(callback) +Lists all security groups that are available to use on your OpenStack account + +Callback returns `f(err, securityGroups)` where `securityGroups` is an `Array` + +#### client.getSecurityGroup(securityGroup, callback) +Gets specified security group + +Takes securityGroup or securityGroupId as an argument and returns the security group in the callback +`f(err, securityGroup)` + +#### client.createSecurityGroup(options, callback) +Creates a security group with the options specified + +Options are as follows: + +```js +{ + name: 'securityGroupName', // required, name of security group + description : 'security group description', // optional, description of security group + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group. Admin-only +} +``` +Returns the created security group in the callback `f(err, securityGroup)` + +#### client.destroySecurityGroup(securityGroup, callback) +Destroys the specified security group + +Takes securityGroup or securityGroupId as an argument and returns the id of the destroyed security group in the callback `f(err, securityGroupId)` + +**Security Group Rules** + +#### client.getSecurityGroupRules(callback) +Lists all security group rules that are available to use on your OpenStack account + +Callback returns `f(err, securityGroupRules)` where `securityGroupRules` is an `Array` + +#### client.getSecurityGroupRule(securityGroupRule, callback) +Gets specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the security group rule in the callback +`f(err, securityGroupRule)` + +#### client.createSecurityGroupRule(options, callback) +Creates a security group rule with the options specified + +Options are as follows: + +```js +{ + securityGroupId: 'securityGroupId', // required, The security group ID to associate with this security group rule. + direction: 'ingress|egress', // required, The direction in which the security group rule is applied. + ethertype: 'IPv4|IPv6', // optional, + portRangeMin: portNumber, // optional, The minimum port number in the range that is matched by the security group rule. + portRangeMax: portNumber, // optional, The maximum port number in the range that is matched by the security group rule. + protocol: 'tcp|udp|icmp', // optional, The protocol that is matched by the security group rule + remoteGroupId: 'remote group id', // optional, The remote group ID to be associated with this security group rule. You can specify either this or remoteIpPrefix. + remoteIpPrefix: 'remote IP prefix', // optional, The remote IP prefix to be associated with this security group rule. You can specify either this or remoteGroupId. + tenantId : 'tenantId' // optional, The ID of the tenant who owns the security group rule. Admin-only +} +``` +Returns the created security group rule in the callback `f(err, securityGroupRule)` + +#### client.destroySecurityGroupRule(securityGroupRule, callback) +Destroys the specified security group rule + +Takes securityGroupRule or securityGroupRuleId as an argument and returns the id of the destroyed security group rule in the callback `f(err, securityGroupRuleId)` diff --git a/docs/providers/rackspace/orchestration.md b/docs/providers/rackspace/orchestration.md new file mode 100644 index 000000000..9bb9c37b1 --- /dev/null +++ b/docs/providers/rackspace/orchestration.md @@ -0,0 +1,137 @@ +## Using the Rackspace Orchestration provider + +Creating a client is straight-forward: + +``` js + var rackspace = pkgcloud.orchestration.createClient({ + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine + }); +``` + +[More options for creating clients](README.md) + +### API Methods + +## Stacks + +#### client.getStacks([options], callback) +Lists all stacks that are available to use on your Rackspace account + +Callback returns `f(err, stacks)` where `stacks` is an `Array` + +#### client.createStack(options, callback) +Creates a stack with the options specified. + +Options are as follows: + +```js +{ + name: 'my-stack-name', // required + timeout: 30, // timeout, in minutes, required + templateUrl: 'http://path.to.some.openstack.heat.template', // required, unless you pass template directly + template: { ... }, // optional, unless you don't provide templateUrl + parameters: { ... }, // optional parameters for the stack + environment: { ... }, // optional environment values for the stack + files: { ... }, // optional files for the stack +} +``` +Returns the stack in the callback `f(err, stack)` + +#### client.getStack(stack, callback) +Retrieves the provided stack or stackId from the service. Callback has the signature `f(err, stack)`. + +#### client.previewStack(details, callback) +Identical to the `client.createStack()` call, except it only previews the creation, instead of actually provisioning +the stack. + +Returns the previewed stack in the callback `f(err, stack)` + +#### client.adoptStack(details, callback) +Identical to the `client.createStack()` call, except it requires passing `details.stackData` which is the `abandonedStack` +value returned from `client.abandonStack()`. + +Returns the created stack in the callback `f(err, stack)` + +#### client.updateStack(stack, callback) + +Update the provided stack. + +The following values from the provided stack are updatable. + +```js +{ + name: 'my-stack-name', // required + timeout: 30, // timeout, in minutes, required + templateUrl: 'http://path.to.some.openstack.heat.template', // required, unless you pass template directly + template: { ... }, // optional, unless you don't provide templateUrl + parameters: { ... }, // optional parameters for the stack + environment: { ... }, // optional environment values for the stack + files: { ... }, // optional files for the stack +} +``` + +#### client.deleteStack(stack, callback) + +Delete the created stack, and delete the resources. Callback is `f(err)`. + +#### client.abandonStack(stack, callback) + +Delete the created stack, but leave the resources running. Will callback with `f(err, abandonedStack)` where the +`abandonedStack` would be passed in as an option to `client.createStack()`. + +#### client.getTemplate(stack, callback) + +Get the template for a provided stack. Will callback with `f(err, template)`. + +## Resources + +#### client.getResource(stack, resource, callback) + +Get the resource for a provided stack and resource or resourceName in the callback `f(err, +resource)` + +#### client.getResources(stack, [options], callback) +Get the resources for a provided stack. Callback is `f(err, resources)`. + +Options are as follows: +```js +{ + nestedDepth: 3 // include resources from nested stacks up to nestedDepth levels of recursion +} +``` + +#### client.getResourceTypes(callback) +Get a list of valid resource types. Callback is `f(err, resourceTypes)`. + +#### client.getResourceSchema(resourceType, callback) +Get the schema for a provided resourceType. Callback is `f(err, resourceSchema)`. + +#### client.getResourceTemplate(resourceType, callback) +Get the template for a provided resourceType. Callback is `f(err, resourceTemplate)`. + +## Events + +#### client.getEvent(stack, resource, eventId, callback) +Get the event for a provided stack, resource and eventId. + +`f(err, event)` + +#### client.getEvents(stack, callback) +Get all of the events for a provided stack + +`f(err, events)` + +#### client.getResourceEvents(stack, resource, callback) +Get all of the events for a stack and resource. + +`f(err, events)` + +## Templates + +#### client.validateTemplate(template, callback) +Validates a provided template, with a callback of `f(err, template)`. \ No newline at end of file diff --git a/docs/providers/rackspace/storage.md b/docs/providers/rackspace/storage.md index 00c076454..fc5b935c9 100644 --- a/docs/providers/rackspace/storage.md +++ b/docs/providers/rackspace/storage.md @@ -11,14 +11,16 @@ Creating a client is straight-forward: ``` js var rackspace = pkgcloud.storage.createClient({ - provider: 'rackspace', - username: 'your-rax-user-name', - apiKey: 'your-rax-api-key', - region: 'IAD' + provider: 'rackspace', // required + username: 'your-user-name', // required + apiKey: 'your-api-key', // required + region: 'IAD', // required, regions can be found at + // http://www.rackspace.com/knowledge_center/article/about-regions + useInternal: false // optional, use to talk to serviceNet from a Rackspace machine }); ``` -Learn about [more options for creating clients](README.md) in the Openstack `storage` provider. `region` parameter can be any [Rackspace Region](http://www.rackspace.com/about/datacenters/). +Learn about [more options for creating clients](README.md) in the OpenStack `storage` provider. `region` parameter can be any [Rackspace Region](http://www.rackspace.com/about/datacenters/). ### Container Model @@ -127,7 +129,7 @@ client.removeContainerMetadata(container, { year: false }, function(err, c) { ### File APIs -* [`client.upload(options, function(err, result) { })`](#clientuploadoptions-functionerr-result--) +* [`client.upload(options)`](#clientuploadoptions) * [`client.download(options, function(err, file) { })`](#clientdownloadoptions-functionerr-file--) * [`client.getFile(container, file, function(err, file) { })`](#clientgetfilecontainer-file-functionerr-file--) * [`client.getFiles(container, [options], function(err, file) { })`](#clientgetfilescontainer-options-functionerr-files--) @@ -150,9 +152,8 @@ var myContainer = new Container({ name: 'my-container' }); client.getFile(myContainer, 'my-file', function(err, file) { ... }); ``` -#### client.upload(options, function(err, result) { }) - -Returns a writeable stream. Upload a new file to a [`container`](#container-model). `result` will be `true` on success. +#### client.upload(options) +Returns a `writableStream`. Upload a new file to a [`container`](#container-model). `writableStream` will emit `success` on completion of the upload, and will emit `error` on any failure. Function for `success` is `function(file) { ... }` where `file` is a [`file`](#file-model) model To upload a file, you need to provide an `options` argument: @@ -161,57 +162,29 @@ var options = { // required options container: 'my-container', // this can be either the name or an instance of container remote: 'my-file', // name of the new file - - // optional, either stream or local - stream: myStream, // any instance of a readable stream - local: '/path/to/local/file' // a path to any local file - - // Other optional values - metadata: { // provide any number of property/values for metadata - campaign: '2012 magazine' - }, - headers: { // optionally provide raw headers to send to cloud files - 'content-type': 'application/json' - } + contentType: 'application/json', // optional mime type for the file, will attempt to auto-detect based on remote name + size: 1234 // size of the file }; ``` -You need not provide either `stream` or `local`. `client.upload` returns a writeable stream, so you can simply pipe directly into it from your stream. For example: - ```Javascript -var fs = require('fs'), - pkgcloud = require('pkgcloud'); - -var client = pkgcloud.providers.rackspace.storage.createClient({ ... }); - -var myFile = fs.createReadStream('/my/local/file'); - -myFile.pipe(client.upload({ - container: 'my-container', - remote: 'my-file' -}, function(err, result) { - // handle the upload result -})); -``` - -You could also upload a local file via the `local` property on `options`: + var readStream = fs.createReadStream('a-file.txt'); + var writeStream = client.upload({ + container: 'a-container', + remote: 'remote-file-name.txt' + }); -```Javascript -var pkgcloud = require('pkgcloud'); + writeStream.on('error', function(err) { + // handle your error case + }); -var client = pkgcloud.providers.rackspace.storage.createClient({ ... }); + writeStream.on('success', function(file) { + // success, file will be a File model + }); -client.upload({ - container: 'my-container', - remote: 'my-file', - local: '/path/to/my/file' -}, function(err, result) { - // handle the upload result -}); + readStream.pipe(writeStream); ``` -This is functionally equivalent to piping from an `fs.createReadStream`, but has a simplified calling convention. - #### client.download(options, function(err, file) { }) Returns a readable stream. Download a [`file`](#file-model) from a [`container`](#container-model). diff --git a/docs/providers/redistogo.md b/docs/providers/redistogo.md deleted file mode 100644 index c6e1737a1..000000000 --- a/docs/providers/redistogo.md +++ /dev/null @@ -1,43 +0,0 @@ -# Using RedisToGo with `pkgcloud` - -``` js - var client = pkgcloud.database.createClient({ - provider: 'redistogo', - username: "bob", - password: "1234" - }); - - // - // Create a redis - // - client.create({ - plan: "nano", - }, function (err, result) { - // - // Log the result - // - console.log(err, result); - - // - // Get the same redis we just created - // - client.get(result.id, function (err, result) { - // - // Show the details of the database we just created - // - console.log(err, result); - client.remove(result.id, function (err, result) { - // - // Ensure it was removed correctly. - // - console.log(err, result); - }); - }); - }); -``` - -The `client` instance returned by `pkgcloud.database.createClient` has the following methods for RedisToGo: - -* `client.create(options, callback)` -* `client.remove(id, callback)` -* `client.get(id, callback)` \ No newline at end of file diff --git a/docs/vocabulary.md b/docs/vocabulary.md index f6c2d6a33..286440fba 100644 --- a/docs/vocabulary.md +++ b/docs/vocabulary.md @@ -8,11 +8,11 @@ When considering all IaaS providers as a whole, their vocabulary is somewhat dis pkgcloud OpenStack - Joyent Amazon Azure Rackspace DigitalOcean + HP Server @@ -22,6 +22,7 @@ When considering all IaaS providers as a whole, their vocabulary is somewhat dis Virtual Machine Server Droplet + Server Image @@ -31,6 +32,7 @@ When considering all IaaS providers as a whole, their vocabulary is somewhat dis Image Image Image + Image Flavor @@ -40,6 +42,7 @@ When considering all IaaS providers as a whole, their vocabulary is somewhat dis RoleSize Flavor Size + Flavor diff --git a/examples/blockstorage/oneandone.js b/examples/blockstorage/oneandone.js new file mode 100644 index 000000000..6952da3bd --- /dev/null +++ b/examples/blockstorage/oneandone.js @@ -0,0 +1,67 @@ +/** + * Created by Ali Bazlamit on 9/6/2017. + */ +var pkgcloud = require('pkgcloud'), + _ = require('lodash'); + +(function() { + + var config = { + token: process.env.OAO_TOKEN, + }; + + // create our client with your 1&1 credentials + var computeClient = pkgcloud.providers.oneandone.compute.createClient(config); + var blockStorageClient = pkgcloud.providers.oneandone.blockstorage.createClient(config); + + // first we're going to get our flavors + computeClient.getFlavors(function (err, flavors) { + if (err) { + console.dir(err); + return; + } + + // then get our base images + computeClient.getImages(function (err, images) { + if (err) { + console.dir(err); + return; + } + + // Pick a medium instance flavor + var flavor = _.findWhere(flavors, { name: 'M' }); + + // Pick an image based on Ubuntu 14.04 + var image = _.findWhere(images, { name: 'centos6-32std' }); + + // Create our first server + computeClient.createServer({ + name: 'server1', + image: image, + flavor: flavor.id + }, function(err, server) { + if (err) { + console.error(err); + return; + } + + // Wait for our server to start up + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + + // create a block storage snapshot + blockStorageClient.createSnapshot(server, function(err, snapshot) { + if (err) { + console.error(err); + return; + } + console.log(snapshot); + }); + }); + }); + }); + }); +})(); diff --git a/examples/blockstorage/rackspace.js b/examples/blockstorage/rackspace.js index 7ba6d4b07..7d8752159 100644 --- a/examples/blockstorage/rackspace.js +++ b/examples/blockstorage/rackspace.js @@ -1,5 +1,5 @@ var pkgcloud = require('pkgcloud'), - _ = require('underscore'); + _ = require('lodash'); (function() { diff --git a/examples/cdn/rackspace.js b/examples/cdn/rackspace.js new file mode 100644 index 000000000..771e97c17 --- /dev/null +++ b/examples/cdn/rackspace.js @@ -0,0 +1,95 @@ +var pkgcloud = require('../../lib/pkgcloud'); + +var rackspace = pkgcloud.cdn.createClient({ + provider: 'rackspace', + username: 'rackspace_id', + apiKey: '1234567890poiiuytrrewq' +}); + +// Basic flavor and service operations. Please note that due to the asynchronous nature of Javascript programming, +// the code sample below will cause unexpected results if run as-a-whole and are meant for documentation +// and illustration purposes. + +// 1 -- to list all available CDN flavors +rackspace.getFlavors(function (err, flavors) { + if (err) { + console.dir(err); + return; + } + flavors.forEach(function (flavor) { + console.log(flavor.id); + }); +}); + +// 2 -- to create a service +rackspace.createService({ + name: 'sample-service-test', + domains: [ + { + domain: 'www.acme.com' + }, + { + domain: 'acme.com' + } + ], + origins: [ + { + origin: '12.34.56.78' + } + ], + flavorId: 'cdn' +}, function (err, service) { + if (err) { + console.dir(err); + return; + } + + console.log(service.id); + console.log(service.name); + +}); + +// 2 -- to list our services +rackspace.getServices(function (err, services) { + if (err) { + console.dir(err); + return; + } + + services.forEach(function(service) { + console.log(service.id); + console.log(service.name); + }); + +}); + +// 3 -- to get our service and update it +rackspace.getService(function (err, service) { + if (err) { + console.dir(err); + return; + } + + service.origins[0].origin = '88.88.88.88'; + + rackspace.updateService(service, function (err, service) { + if (err) { + console.dir(err); + return; + } + + console.log(service.origins[0].origin); + + }); + +}); + +// 4 -- to delete our service +rackspace.deleteService('abcdef01-2345-6789-abcd-ef0123456789', function (err) { + if (err) { + console.dir(err); + return; + } + + console.log('Service deletion request was successful.'); +}); diff --git a/examples/compute/amazon.js b/examples/compute/amazon.js index f0c19f743..a1bc22674 100644 --- a/examples/compute/amazon.js +++ b/examples/compute/amazon.js @@ -5,3 +5,13 @@ var client = pkgcloud.compute.createClient({ accessKey: 'asdfkjas;dkj43498aj3n', accessKeyId: '98kja34lkj' }); + +client.getServers(function (err, servers) { + if (err) { + console.error(err); + } + + servers.forEach(function (server) { + console.log(server.toJSON()); + }); +}); diff --git a/examples/compute/azure.js b/examples/compute/azure.js index d523babba..2acd9b62c 100644 --- a/examples/compute/azure.js +++ b/examples/compute/azure.js @@ -8,9 +8,9 @@ var pkgcloud = require('../../lib/pkgcloud'), // options = { provider: 'azure', - "storageAccount": "test-storage-account", - "storageAccessKey": "test-storage-access-key", - "subscriptionId": "azure-account-subscription-id", + storageAccount: 'test-storage-account', + storageAccessKey: 'test-storage-access-key', + subscriptionId: 'azure-account-subscription-id', key: fs.readFileSync('path to your account management key file', 'ascii'), cert: fs.readFileSync('path to your account management certificate pem file', 'ascii') }; @@ -39,15 +39,15 @@ options = { // Azure ports (endpoints) ports: [ { - name : "foo", // name of port - protocol : "tcp", // tcp or udp - port: "12333", // external port number - localPort: "12333" // internal port number + name : 'foo', // name of port + protocol : 'tcp', // tcp or udp + port: '12333', // external port number + localPort: '12333' // internal port number } ] }; -console.log("creating server..."); +console.log('creating server...'); client.createServer(options, function (err, server) { if (err) { @@ -55,7 +55,7 @@ client.createServer(options, function (err, server) { } else { // Wait for the server to reach the RUNNING state. // This may take several minutes. - console.log("waiting for server RUNNING state..."); + console.log('waiting for server RUNNING state...'); server.setWait({ status: server.STATUS.running }, 10000, function (err, server) { if (err) { console.log(err); diff --git a/examples/compute/digitalocean.js b/examples/compute/digitalocean.js new file mode 100644 index 000000000..de0209e60 --- /dev/null +++ b/examples/compute/digitalocean.js @@ -0,0 +1,54 @@ +var pkgcloud = require('../../lib/pkgcloud'), + client, + options; + +// +// Create a pkgcloud compute instance +// +options = { + provider: 'digitalocean', + token: 'digitalocean-api-token' +}; +client = pkgcloud.compute.createClient(options); + +// +// List DigitalOcean Droplets. +// +client.getServers(function (err, servers) { + if (err) { + console.error(err); + } + + servers.forEach(function (server) { + console.log(server.name, server.id, server.status); + }); +}); + + +// +// Create a Droplet and wait until finished. +// +options = { + name: 'pkgcloud-test', + flavor: '512mb', + image: 'ubuntu-14-04-x64', + ipv6: true, + private_networking: true, + backups: true +}; + +client.createServer(options, function (err, server) { + if (err) { + console.log(err); + } else { + // Wait for the server to reach the RUNNING state. + console.log('waiting for server RUNNING state...'); + server.setWait({ status: server.STATUS.running }, 10000, function (err, server) { + if (err) { + console.log(err); + } else { + console.dir(server); + } + }); + } +}); \ No newline at end of file diff --git a/examples/compute/joyent.js b/examples/compute/joyent.js deleted file mode 100644 index 102608754..000000000 --- a/examples/compute/joyent.js +++ /dev/null @@ -1,23 +0,0 @@ -var pkgcloud = require('../../lib/pkgcloud'); - -// -// Joyent requires a username / password or key / keyId combo. -// key/keyId should be registered in Joyent servers. -// check `test/helpers/index.js` for details on key/keyId works. -// -var client = pkgcloud.compute.createClient({ - provider: 'joyent', - account: 'nodejitsu', - keyId: '/nodejitsu1/keys/dscape', - key: fs.readFileSync(path.join(process.env.HOME, '.ssh/id_rsa'), 'ascii') -}); - -// -// Alternatively create a client with a username / password pair -// -var otherClient = pkgcloud.compute.createClient({ - provider: 'joyent', - username: 'your-account', - password: 'your-password' -}); - diff --git a/examples/compute/oneandone.js b/examples/compute/oneandone.js new file mode 100644 index 000000000..1df0ae89d --- /dev/null +++ b/examples/compute/oneandone.js @@ -0,0 +1,74 @@ +/** + * Created by Ali Bazlamit on 9/6/2017. + */ +var pkgcloud = require('pkgcloud'), + _ = require('lodash'); + +// create our client with your 1&1 token +var client = pkgcloud.providers.oneandone.compute.createClient({ + token: process.env.OAO_TOKEN, +}); + +// This function will handle our server creation, +// as well as waiting for the server to come online after we've +// created it. +function handleServerResponse(err, server) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER CREATED: ' + server.name + ', waiting for active status'); + + // Wait for status: ACTIVE on our server, and then callback + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER INFO'); + console.log(server.name); + console.log(server.status); + console.log(server.id); + + console.log('Make sure you DELETE server: ' + server.id + + ' in order to not accrue billing charges'); + }); +} + +// first we're going to get our flavors +client.getFlavors(function (err, flavors) { + if (err) { + console.dir(err); + return; + } + + // then get our base images + client.getImages(function (err, images) { + if (err) { + console.dir(err); + return; + } + + // Pick a medium instance flavor + var flavor = _.findWhere(flavors, { name: 'M' }); + + // Pick an image based on Ubuntu 14.04 + var image = _.findWhere(images, { name: 'centos6-32std' }); + + // Create our first server + client.createServer({ + name: 'server1', + image: image, + flavor: flavor.id + }, handleServerResponse); + + // Create our second server + client.createServer({ + name: 'server2', + image: image, + flavor: flavor.id + }, handleServerResponse); + }); +}); diff --git a/examples/compute/rackspace.js b/examples/compute/rackspace.js index dd5e89cb5..127c5039e 100644 --- a/examples/compute/rackspace.js +++ b/examples/compute/rackspace.js @@ -1,12 +1,41 @@ var pkgcloud = require('pkgcloud'), - _ = require('underscore'); + _ = require('lodash'); // create our client with your rackspace credentials var client = pkgcloud.providers.rackspace.compute.createClient({ username: 'your-username', - apiKey: 'your-api-key' + apiKey: 'your-api-key', + region: 'DFW' }); +// This function will handle our server creation, +// as well as waiting for the server to come online after we've +// created it. +function handleServerResponse(err, server) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER CREATED: ' + server.name + ', waiting for active status'); + + // Wait for status: ACTIVE on our server, and then callback + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + + console.log('SERVER INFO'); + console.log(server.name); + console.log(server.status); + console.log(server.id); + + console.log('Make sure you DELETE server: ' + server.id + + ' in order to not accrue billing charges'); + }); +} + // first we're going to get our flavors client.getFlavors(function (err, flavors) { if (err) { @@ -42,31 +71,3 @@ client.getFlavors(function (err, flavors) { }, handleServerResponse); }); }); - -// This function will handle our server creation, -// as well as waiting for the server to come online after we've -// created it. -function handleServerResponse(err, server) { - if (err) { - console.dir(err); - return; - } - - console.log('SERVER CREATED: ' + server.name + ', waiting for active status'); - - // Wait for status: ACTIVE on our server, and then callback - server.setWait({ status: server.STATUS.running }, 5000, function (err) { - if (err) { - console.dir(err); - return; - } - - console.log('SERVER INFO'); - console.log(server.name); - console.log(server.status); - console.log(server.id); - - console.log('Make sure you DELETE server: ' + server.id + - ' in order to not accrue billing charges'); - }); -} \ No newline at end of file diff --git a/examples/database/azure.js b/examples/database/azure.js index 8cbb26593..b60f7917a 100644 --- a/examples/database/azure.js +++ b/examples/database/azure.js @@ -2,15 +2,15 @@ var pkgcloud = require('../../lib/pkgcloud'); var client = pkgcloud.database.createClient({ provider: 'azure', - storageAccount: "storage-account-name", // Name of your Azure storage account - storageAccessKey: "storage-account-access-key" // Access key for storage account + storageAccount: 'storage-account-name', // Name of your Azure storage account + storageAccessKey: 'storage-account-access-key' // Access key for storage account }); // // Create an Azure Table // client.create({ - name: "testing123" + name: 'testing123' }, function (err, result) { // // Check the result diff --git a/examples/database/iriscouch.js b/examples/database/iriscouch.js deleted file mode 100644 index 3297e100e..000000000 --- a/examples/database/iriscouch.js +++ /dev/null @@ -1,47 +0,0 @@ -var pkgcloud = require('../../lib/pkgcloud'); - -var client = pkgcloud.database.createClient({ - provider: 'iriscouch', - username: "bob", - password: "1234" -}); - -// -// Create a couch database -// -client.create({ - subdomain: "pkgcloud-nodejitsu-test-5", - first_name: "pkgcloud", - last_name: "pkgcloud", - email: "info@nodejitsu.com" -}, function (err, result) { - // - // Check now exists @ http://pkgcloud-nodejitsu-test-5.iriscouch.com - // - console.log(err, result); -}); - -// -// Crate a redis database -// -client.create({ - subdomain: "pkgcloud-nodejitsu-test-6", - first_name: "pkgcloud", - last_name: "pkgcloud", - email: "info@nodejitsu.com", - // - // For redis instead of couch just put type to redis - // - type: "redis", - // - // AND ADD A PASSWORD! (required) - // - password: "mys3cur3p4ssw0rd" -}, function (err, result) { - // - // Check the connection, use result.host and result.password values - // redis-cli -h $RESULT.HOST -a $RESULT.PASSWORD - // - console.log('HOST to connect:', result.host); - console.log('KEY to use:', result.password); -}); \ No newline at end of file diff --git a/examples/database/mongohq.js b/examples/database/mongohq.js deleted file mode 100644 index e19c69015..000000000 --- a/examples/database/mongohq.js +++ /dev/null @@ -1,30 +0,0 @@ -var pkgcloud = require('../../lib/pkgcloud'); - -var client = pkgcloud.database.createClient({ - provider: 'mongohq', - username: "bob", - password: "1234" -}); - -// -// Create a MongoDB -// -client.create({ - name: "mongo-instance", - plan: "free", -}, function (err, result) { - // - // Check the result - // - console.log(err, result); - - // - // Now delete that same MongoDB - // - client.remove(result.id, function (err, result) { - // - // Check the result - // - console.log(err, result); - }); -}); \ No newline at end of file diff --git a/examples/database/rackspace.js b/examples/database/rackspace.js index 10ca0843c..08fbc4848 100644 --- a/examples/database/rackspace.js +++ b/examples/database/rackspace.js @@ -46,5 +46,5 @@ client.getFlavors(function (err, flavors) { console.log(database); }); }); - }) -}); \ No newline at end of file + }); +}); diff --git a/examples/database/redistogo.js b/examples/database/redistogo.js deleted file mode 100644 index 73aa041ed..000000000 --- a/examples/database/redistogo.js +++ /dev/null @@ -1,35 +0,0 @@ -var pkgcloud = require('../../lib/pkgcloud'); - -var client = pkgcloud.database.createClient({ - provider: 'redistogo', - username: "bob", - password: "1234" -}); - -// -// Create a redis -// -client.create({ - plan: "nano", -}, function (err, result) { - // - // Log the result - // - console.log(err, result); - - // - // Get the same redis we just created - // - client.get(result.id, function (err, result) { - // - // Show the details of the database we just created - // - console.log(err, result); - client.remove(result.id, function (err, result) { - // - // Ensure it was removed correctly. - // - console.log(err, result); - }); - }); -}); \ No newline at end of file diff --git a/examples/dns/rackspace.js b/examples/dns/rackspace.js index 2a9a402b9..7e66ab18b 100644 --- a/examples/dns/rackspace.js +++ b/examples/dns/rackspace.js @@ -1,7 +1,6 @@ -var pkgcloud = require('../../lib/pkgcloud'), - _ = require('underscore'); +var pkgcloud = require('../../lib/pkgcloud'); -var rackspace = pkgcloud.dns.createClient({ +var client = pkgcloud.dns.createClient({ provider: 'rackspace', username: 'rax-user-id', apiKey: '1234567890asdbchehe' @@ -12,13 +11,13 @@ var rackspace = pkgcloud.dns.createClient({ // and illustration purposes. // 1 - Get all DNS "Zones" associates with your account -rackspace.getZones(function (err, zones) { +client.getZones(function (err, zones) { if (err) { console.dir(err); return; } - _.each(zones, function (zone) { + zones.forEach(function (zone) { console.log(zone.id + ' ' + zone.name); }); @@ -34,7 +33,7 @@ var details = { comment: 'I pity .foo' }; -rackspace.createZone(details, function (err, zone) { +client.createZone(details, function (err, zone) { if (err) { console.dir(err); return; @@ -46,7 +45,7 @@ rackspace.createZone(details, function (err, zone) { // 3 - Get the "Zone" we just created and get its records -rackspace.getZones({ name: 'example.org' }, function (err, zones) { +client.getZones({ name: 'example.org' }, function (err, zones) { if (err) { console.dir(err); return; @@ -54,13 +53,13 @@ rackspace.getZones({ name: 'example.org' }, function (err, zones) { if (zones.length) { console.log('We have parent Zone'); - rackspace.getRecords(zones[0], function (err, records) { + client.getRecords(zones[0], function (err, records) { if (err) { console.dir(err); return; } - _.each(records, function (record){ + records.forEach(function (record){ console.log(record.toJSON()); }); }); @@ -74,7 +73,7 @@ var _rec = { data: '127.0.0.1' }; -rackspace.getZones({ name: 'example.org' }, function (err, zones) { +client.getZones({ name: 'example.org' }, function (err, zones) { if (err) { console.dir(err); return; @@ -82,7 +81,7 @@ rackspace.getZones({ name: 'example.org' }, function (err, zones) { if (zones.length) { console.log('We have parent Zone'); - rackspace.createRecord(zones[0], _rec, function (err, rec) { + client.createRecord(zones[0], _rec, function (err, rec) { if (err) { console.dir(err); return; @@ -96,7 +95,7 @@ rackspace.getZones({ name: 'example.org' }, function (err, zones) { }); // 5 - Now let's remove the "Zone" and all of its children records. -rackspace.getZones({ name: 'example.org' }, function (err, zones) { +client.getZones({ name: 'example.org' }, function (err, zones) { if (err) { console.dir(err); return; @@ -104,7 +103,7 @@ rackspace.getZones({ name: 'example.org' }, function (err, zones) { if (zones.length) { console.log('We have parent Zone'); - rackspace.deleteZone(zones[0], function (err) { + client.deleteZone(zones[0], function (err) { if (err) { console.dir(err); return; diff --git a/examples/loadbalancer/oneandone.js b/examples/loadbalancer/oneandone.js new file mode 100644 index 000000000..58503652a --- /dev/null +++ b/examples/loadbalancer/oneandone.js @@ -0,0 +1,85 @@ +/** + * Created by Ali Bazlamit on 9/6/2017. + */ +var pkgcloud = require('pkgcloud'), + _ = require('lodash'); + +(function () { + + var config = { + token: process.env.OAO_TOKEN, + }; + + // create our client with your 1&1 credentials + var computeClient = pkgcloud.providers.oneandone.compute.createClient(config); + var loadBalancerClient = pkgcloud.providers.oneandone.loadbalancer.createClient(config); + + // first we're going to get our flavors + computeClient.getFlavors(function (err, flavors) { + if (err) { + console.dir(err); + return; + } + + // then get our base images + computeClient.getImages(function (err, images) { + if (err) { + console.dir(err); + return; + } + + // Pick a 512MB instance flavor + var flavor = _.findWhere(flavors, { name: 'M' }); + + // Pick an image based on Ubuntu 14.04 + var image = _.findWhere(images, { name: 'centos6-32std' }); + + // Create our first server + computeClient.createServer({ + name: 'server11', + image: image, + flavor: flavor.id, + location: '4EFAD5836CE43ACA502FD5B99BEE44EF' + }, function (err, server) { + if (err) { + console.error(err); + return; + } + + // Wait for our server to start up + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + + // create a block storage snapshot + var options = { + name: 'lb test', + healthCheckInterval: 40, + Persistence: true, + persistenceTime: 1200, + method: 'ROUND_ROBIN', + rules: [ + { + protocol: 'TCP', + port_balancer: 80, + port_server: 80, + source: '0.0.0.0' + } + ], + location: '4EFAD5836CE43ACA502FD5B99BEE44EF' + }; + + loadBalancerClient.createLoadBalancer(options, function (err, loadbalancer) { + if (err) { + console.error(err); + return; + } + console.log(loadbalancer); + }); + }); + }); + }); + }); +})(); diff --git a/examples/storage/amazon.js b/examples/storage/amazon.js index 42fc98ebd..3dd2d643c 100644 --- a/examples/storage/amazon.js +++ b/examples/storage/amazon.js @@ -5,3 +5,13 @@ var client = pkgcloud.storage.createClient({ accessKey: 'asdfkjas;dkj43498aj3n', accessKeyId: '98kja34lkj' }); + +client.getContainers(function (err, containers) { + if (err) { + console.error(err); + } + + containers.forEach(function (container) { + console.log(container.toJSON()); + }); +}); diff --git a/examples/storage/azure.js b/examples/storage/azure.js index 3cf9bf908..ef6462fdc 100644 --- a/examples/storage/azure.js +++ b/examples/storage/azure.js @@ -1,7 +1,17 @@ var pkgcloud = require('../../lib/pkgcloud'); -var azure = pkgcloud.storage.createClient({ +var client = pkgcloud.storage.createClient({ provider: 'azure', - storageAccount: "test-storage-account", // Name of your storage account - storageAccessKey: "test-storage-access-key" // Access key for storage account + storageAccount: 'test-storage-account', // Name of your storage account + storageAccessKey: 'test-storage-access-key' // Access key for storage account }); + +client.getContainers(function (err, containers) { + if (err) { + console.error(err); + } + + containers.forEach(function (container) { + console.log(container.toJSON()); + }); +}); \ No newline at end of file diff --git a/examples/storage/rackspace.js b/examples/storage/rackspace.js index 1b26a2ea3..304b2e470 100644 --- a/examples/storage/rackspace.js +++ b/examples/storage/rackspace.js @@ -1,8 +1,7 @@ var fs = require('fs'), - pkgcloud = require('../../lib/pkgcloud'), - _ = require('underscore'); + pkgcloud = require('../../lib/pkgcloud'); -var rackspace = pkgcloud.storage.createClient({ +var client = pkgcloud.storage.createClient({ provider: 'rackspace', username: 'rackspace_id', apiKey: '1234567890poiiuytrrewq', @@ -14,7 +13,7 @@ var rackspace = pkgcloud.storage.createClient({ // and illustration purposes. // 1 -- to create a container -rackspace.createContainer({ +client.createContainer({ name: 'sample-container-test', metadata: { callme: 'maybe' @@ -31,20 +30,20 @@ rackspace.createContainer({ }); // 2 -- to list our containers -rackspace.getContainers(function (err, containers) { +client.getContainers(function (err, containers) { if (err) { console.dir(err); return; } - _.each(containers, function(container) { + containers.forEach(function(container) { console.log(container.name); }); }); // 3 -- to create a container and upload a file to it -rackspace.createContainer({ +client.createContainer({ name: 'sample-container', metadata: { callme: 'maybe' @@ -57,22 +56,24 @@ rackspace.createContainer({ var myPicture = fs.createReadStream('/path/to/some/file/picture.jpg'); - myPicture.pipe(rackspace.upload({ - container: container.name, - remote: 'profile-picture.jpg' - }, - function (err, result) { - if (err) { - console.dir(err); - return; - } - - console.log(result); - })); + var upload = client.upload({ + container: container.name, + remote: 'profile-picture.jpg' + }); + + upload.on('error', function(err) { + console.error(err); + }); + + upload.on('success', function(file) { + console.log(file.toJSON()); + }); + + myPicture.pipe(upload); }); // 4 -- setup container as CDN -rackspace.getContainer('container-name', function (err, container) { +client.getContainer('container-name', function (err, container) { if(err){ console.log('There was an error retrieving container:\n'); console.dir(err); @@ -86,23 +87,25 @@ rackspace.getContainer('container-name', function (err, container) { return; } console.log('Successfully set bucket as CDN bucket'); + console.log(cont); }); }); // 5 -- to get a container, empty it, then finally destroying it -rackspace.getContainer('sample-container', function (err, container) { +client.getContainer('sample-container', function (err, container) { if (err) { console.dir(err); return; } // destroying a container automatically calls the remove file API to empty before delete - rackspace.destroyContainer(container, function (err, result) { + client.destroyContainer(container, function (err, result) { if (err) { console.dir(err); return; } - console.log('Container ' + container.name + ' was successfully destroyed.') + console.log('Container ' + container.name + ' was successfully destroyed.'); + console.log(result); }); -}); \ No newline at end of file +}); diff --git a/lib/pkgcloud.js b/lib/pkgcloud.js index 8921d9617..ae6152235 100644 --- a/lib/pkgcloud.js +++ b/lib/pkgcloud.js @@ -1,7 +1,7 @@ /* * pkgcloud.js: Top-level include for the pkgcloud module * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ @@ -9,7 +9,7 @@ var path = require('path'); var pkgcloud = exports; -require('pkginfo')(module, 'version'); +pkgcloud.version = require('../package.json').version; var components = [ './pkgcloud/core/base', @@ -22,14 +22,11 @@ var providers = [ 'amazon', 'azure', 'digitalocean', - 'iriscouch', - 'joyent', - 'mongohq', - 'mongolab', + 'google', + 'oneandone', 'openstack', 'rackspace', - 'redistogo', - 'telefonica' + 'hp' ]; var services = [ @@ -39,6 +36,8 @@ var services = [ 'database', 'dns', 'loadbalancer', + 'orchestration', + 'network', 'storage' ]; diff --git a/lib/pkgcloud/amazon/client.js b/lib/pkgcloud/amazon/client.js index e39fa754c..e6786ea8f 100644 --- a/lib/pkgcloud/amazon/client.js +++ b/lib/pkgcloud/amazon/client.js @@ -1,39 +1,68 @@ /* * client.js: Base client from which all AWS clients inherit from * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - request = require('request'), +var util = require('util'), + AWS = require('aws-sdk'), base = require('../core/base'); +var userAgent = AWS.util.userAgent(); var Client = exports.Client = function (options) { + var self = this; + base.Client.call(this, options); options = options || {}; // Allow overriding serversUrl in child classes this.provider = 'amazon'; + this.endpoint = options.endpoint; this.securityGroup = options.securityGroup; this.securityGroupId = options.securityGroupId; - this.version = options.version || '2012-04-01'; + this.version = options.version || '2014-06-15'; this.protocol = options.protocol || 'https://'; - this.serversUrl = options.serversUrl - || this.serversUrl - || 'ec2.amazonaws.com'; // support either key/accessKey syntax this.config.key = this.config.key || options.accessKey; this.config.keyId = this.config.keyId || options.accessKeyId; + this._awsConfig = { + accessKeyId: this.config.keyId, + secretAccessKey: this.config.key, + region: options.region, + s3ForcePathStyle: options.forcePathBucket, + sessionToken: options.sessionToken, + credentials: options.credentials + }; + + // TODO think about a proxy option for pkgcloud + // enable forwarding to mock test server + if (options.serversUrl) { + this._awsConfig.httpOptions = { + proxy: this.protocol + options.serversUrl + }; + } + + if (options.endpoint) { + this._awsConfig.endpoint = new AWS.Endpoint(options.endpoint); + } + + this.userAgent = util.format('%s %s', self.getUserAgent(), userAgent); + + // Setup a custom user agent for pkgcloud + AWS.util.userAgent = function () { + return self.userAgent; + }; + if (!this.before) { this.before = []; } }; -utile.inherits(Client, base.Client); +util.inherits(Client, base.Client); Client.prototype._toArray = function toArray(obj) { if (typeof obj === 'undefined') { diff --git a/lib/pkgcloud/amazon/compute/client/flavors.js b/lib/pkgcloud/amazon/compute/client/flavors.js index 9975eb7ce..270b25a64 100644 --- a/lib/pkgcloud/amazon/compute/client/flavors.js +++ b/lib/pkgcloud/amazon/compute/client/flavors.js @@ -1,7 +1,7 @@ /* * flavors.js: Implementation of AWS Flavors Client. * - * (C) 2012, Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/amazon/compute/client/groups.js b/lib/pkgcloud/amazon/compute/client/groups.js index 6c44c04f7..091e7f8f3 100644 --- a/lib/pkgcloud/amazon/compute/client/groups.js +++ b/lib/pkgcloud/amazon/compute/client/groups.js @@ -4,7 +4,7 @@ */ var errs = require('errs'), - utile = require('utile'); + _ = require('lodash'); // // ### function listGroups (options, callback) @@ -22,10 +22,23 @@ exports.listGroups = function (options, callback) { var self = this; options = options || {}; - return this._query('DescribeSecurityGroups', options, function (err, body, res) { - return err - ? callback(err) - : callback(err, self._toArray(body.securityGroupInfo.item)); + var requestOpts = {}; + + if (options.groupNames) { + requestOpts.GroupNames = options.groupNames; + } + + if (options.groupIds) { + requestOpts.GroupIds = options.groupIds; + } + + self.ec2.describeSecurityGroups(requestOpts, function(err, data) { + if (err) { + callback(err); + return; + } + + callback(err, data.SecurityGroups); }); }; @@ -37,12 +50,15 @@ exports.listGroups = function (options, callback) { // Gets the details of the EC2 SecurityGroup with the specified `name`. // exports.getGroup = function (name, callback) { - return this.listGroups({ - 'GroupName.1': name - }, function (err, body) { - return err - ? callback(err) - : callback(null, body[0]); + this.listGroups({ groupNames: [ name ] }, function(err, groups) { + if (err) { + callback(err); + } else if (groups && groups[0]) { + callback(err, groups[0]); + } + else { + callback(new Error('Group not found')); + } }); }; @@ -63,18 +79,16 @@ exports.addGroup = function (options, callback) { ); } - return this._query( - 'CreateSecurityGroup', - { - GroupName: options.name, - GroupDescription: options.description - }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - } - ); + var requestOpts = { + GroupName: options.name, + Description: options.description + }; + + this.ec2.createSecurityGroup(requestOpts, function(err, data) { + return err + ? callback(err) + : callback(null, _.extend(requestOpts, { GroupId: data.GroupId })); + }); }; // @@ -85,15 +99,11 @@ exports.addGroup = function (options, callback) { // Destroys EC2 SecurityGroup with the specified `name`. // exports.destroyGroup = function (name, callback) { - return this._query( - 'DeleteSecurityGroup', - { GroupName: name }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - } - ); + this.ec2.deleteSecurityGroup({ GroupName: name }, function(err) { + return err + ? callback(err) + : callback(null, true); + }); }; // @@ -117,18 +127,14 @@ exports.addRules = function (options, callback) { } // Simply append the group name to the rules - override if existing - var rules = options.rules; - rules.GroupName = options.name; - - return this._query( - 'AuthorizeSecurityGroupIngress', - rules , - function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - } - ); + this.ec2.authorizeSecurityGroupIngress({ + GroupName: options.name, + IpPermissions: options.rules + }, function(err) { + return err + ? callback(err) + : callback(null, true); + }); }; // @@ -152,16 +158,12 @@ exports.delRules = function (options, callback) { } // Simply append the group name to the rules - override if existing - var rules = options.rules; - rules.GroupName = options.name; - - return this._query( - 'RevokeSecurityGroupIngress', - rules , - function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - } - ); + this.ec2.revokeSecurityGroupIngress({ + GroupName: options.name, + IpPermissions: options.rules + }, function (err) { + return err + ? callback(err) + : callback(null, true); + }); }; diff --git a/lib/pkgcloud/amazon/compute/client/images.js b/lib/pkgcloud/amazon/compute/client/images.js index 2f4f2415f..a92dadb66 100644 --- a/lib/pkgcloud/amazon/compute/client/images.js +++ b/lib/pkgcloud/amazon/compute/client/images.js @@ -1,12 +1,11 @@ /* * images.js: Implementation of AWS Images Client. * - * (C) 2012, Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var pkgcloud = require('../../../../../lib/pkgcloud'), base = require('../../../core/compute'), - errs = require('errs'), compute = pkgcloud.providers.amazon.compute; // @@ -19,27 +18,39 @@ var pkgcloud = require('../../../../../lib/pkgcloud'), exports.getImages = function getImages(options, callback) { if (!callback && typeof options === 'function') { callback = options; - options = null; + options = {}; } - var query = { 'Owner.0': 'self' }, + var requestOpts = { }, self = this; - if (options && options.owners) { - // - // TODO: Support more than just owners - // - for (var i = 0; i < options.owners.length; i++) { - query['Owner.' + (i + 1)] = options.owners[i]; - } + if (options.owners && options.owners instanceof Array) { + requestOpts.Owners = options.owners; + } + else if (options.owners && typeof options.owners === 'string') { + requestOpts.Owners = [ options.owners ]; } - return this._query('DescribeImages', query, function (err, body, res) { - return err - ? callback(err) - : callback(null, self._toArray(body.imagesSet.item).map(function (image) { - return new compute.Image(self, image); - }), res); + if (options.images && options.images instanceof Array) { + requestOpts.ImageIds = options.images; + } + else if (options.images && typeof options.images === 'string') { + requestOpts.ImagesIds = [ options.images ]; + } + + if (!requestOpts.ImageIds && !requestOpts.Owners) { + requestOpts.Owners = [ 'self' ]; + } + + self.ec2.describeImages(requestOpts, function(err, data) { + if (err) { + callback(err); + return; + } + + callback(err, data.Images.map(function(image) { + return new compute.Image(self, image); + })); }); }; @@ -52,21 +63,22 @@ exports.getImages = function getImages(options, callback) { // object. // exports.getImage = function getImage(image, callback) { - var self = this, - imageId = image instanceof base.Image ? image.id : image, - query = { 'ImageId.1': imageId }; + var self = this; - return this._query('DescribeImages', query, function (err, body, res) { + self.getImages({ + images: [ image ] + }, function(err, images) { if (err) { - return callback(err); + callback(err); + return; + } + + if (images && images[0]) { + callback(err, images[0]); + return; } - var image = self._toArray(body.imagesSet.item).map(function (image) { - return image ? new compute.Image(self, image) : null; - })[0]; - return !image - ? callback(new Error('Image not found')) - : callback(null, image, res); + callback(new Error('Image not found')); }); }; @@ -83,18 +95,25 @@ exports.getImage = function getImage(image, callback) { exports.createImage = function createImage(options, callback) { options || (options = {}); - if (!options.name) throw new TypeError('`name` is a required option'); - if (!options.server) throw new TypeError('`server` is a required option'); + if (!options.name) { + throw new TypeError('`name` is a required option'); + } + + if (!options.server) { + throw new TypeError('`server` is a required option'); + } var self = this, serverId = options.server instanceof base.Server - ? options.server.id : options.server, - query = { InstanceId: serverId, Name: options.name }; + ? options.server.id : options.server; - return this._query('CreateImage', query, function (err, body, res) { + this.ec2.createImage({ + InstanceId: serverId, + Name: options.name + }, function(err, data) { return err ? callback(err) - : self.getImage(res.imageId, callback); + : self.getImage(data.ImageId, callback); }); }; @@ -114,19 +133,25 @@ exports.destroyImage = function destroyImage(image, callback) { return callback(err); } - if (!image.blockDeviceMapping.item || - !image.blockDeviceMapping.item.ebs.snapshotId) { + if (!image.blockDeviceMappings || !image.blockDeviceMappings[0] || + !image.blockDeviceMappings[0].Ebs || !image.blockDeviceMappings[0].Ebs.SnapshotId) { return callback(new TypeError('Image is not EBS backed')); } - var query = { - snapshotId: image.blockDeviceMapping.item.ebs.snapshotId - }; - - self._query('DeleteSnapshot', query, function (err, body, res) { - return err - ? callback(err) - : callback(null, { ok: query.snapshotId }, res); - }); + self.ec2.deregisterImage({ ImageId: image instanceof base.Image ? image.id : image }, + function(err) { + if (err) { + callback(err); + return; + } + + self.ec2.deleteSnapshot({ + SnapshotId: image.blockDeviceMappings[0].Ebs.SnapshotId + }, function(err) { + return err + ? callback(err) + : callback(null, { ok: image.blockDeviceMappings[0].Ebs.SnapshotId }); + }); + }); }); }; diff --git a/lib/pkgcloud/amazon/compute/client/index.js b/lib/pkgcloud/amazon/compute/client/index.js index 9063483dc..6f183ca7b 100644 --- a/lib/pkgcloud/amazon/compute/client/index.js +++ b/lib/pkgcloud/amazon/compute/client/index.js @@ -1,57 +1,27 @@ /* * index.js: Compute client for AWS CloudAPI * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var qs = require('querystring'), - utile = require('utile'), - urlJoin = require('url-join'), - xml2js = require('xml2js'), - auth = require('../../../common/auth'), - amazon = require('../../client'); +var AWS = require('aws-sdk'), + util = require('util'), + amazon = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { amazon.Client.call(this, options); this.securityGroup = options.securityGroup; - utile.mixin(this, require('./flavors')); - utile.mixin(this, require('./images')); - utile.mixin(this, require('./servers')); - utile.mixin(this, require('./keys')); - utile.mixin(this, require('./groups')); + _.extend(this, require('./flavors')); + _.extend(this, require('./images')); + _.extend(this, require('./servers')); + _.extend(this, require('./keys')); + _.extend(this, require('./groups')); - this.before.push(auth.amazon.bodySignature); + this.ec2 = new AWS.EC2(this._awsConfig); }; -utile.inherits(Client, amazon.Client); - -Client.prototype._query = function query(action, query, callback) { - return this._request({ - method: 'POST', - headers: { }, - body: utile.mixin({ Action: action }, query) - }, function (err, body, res) { - if (err) { - return callback(err); - } - var parser = new xml2js.Parser(); - - parser.parseString(body, function (err, data) { - return err - ? callback(err) - : callback(err, data, res); - }); - }); -}; - -Client.prototype._getUrl = function (options) { - options = options || {}; - - return urlJoin(this.protocol + this.serversUrl, - (typeof options === 'string' - ? options - : options.path)); -}; +util.inherits(Client, amazon.Client); diff --git a/lib/pkgcloud/amazon/compute/client/keys.js b/lib/pkgcloud/amazon/compute/client/keys.js index a8e9b9936..45dc6d53e 100644 --- a/lib/pkgcloud/amazon/compute/client/keys.js +++ b/lib/pkgcloud/amazon/compute/client/keys.js @@ -1,12 +1,11 @@ /* * keys.js: Implementation of AWS SSH keys Client. * - * (C) 2012, Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var errs = require('errs'), - utile = require('utile'); +var errs = require('errs'); // // ### function listKeys (options, callback) @@ -21,13 +20,19 @@ exports.listKeys = function (options, callback) { options = {}; } - var self = this; + var self = this, + requestOpts = {}; + options = options || {}; - return this._query('DescribeKeyPairs', options, function (err, body, res) { + if (options.keyNames) { + requestOpts.KeyNames = options.keyNames; + } + + self.ec2.describeKeyPairs(requestOpts, function(err, data) { return err ? callback(err) - : callback(null, self._toArray(body.keySet.item)); + : callback(err, data.KeyPairs); }); }; @@ -40,7 +45,7 @@ exports.listKeys = function (options, callback) { // exports.getKey = function (name, callback) { return this.listKeys({ - 'KeyName.1': name + keyNames: [ name ] }, function (err, body) { return err ? callback(err) @@ -65,18 +70,14 @@ exports.addKey = function (options, callback) { ); } - return this._query( - 'ImportKeyPair', - { - KeyName: options.name, - PublicKeyMaterial: utile.base64.encode(options.key) - }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - } - ); + this.ec2.importKeyPair({ + KeyName: options.name, + PublicKeyMaterial: new Buffer(options.key).toString('base64') + }, function (err) { + return err + ? callback(err) + : callback(null, true); + }); }; // @@ -87,13 +88,11 @@ exports.addKey = function (options, callback) { // Destroys EC2 Key Pair with the specified `name`. // exports.destroyKey = function (name, callback) { - return this._query( - 'DeleteKeyPair', - { KeyName: name }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - } - ); -}; \ No newline at end of file + this.ec2.deleteKeyPair({ + KeyName: name + }, function (err) { + return err + ? callback(err) + : callback(null, true); + }); +}; diff --git a/lib/pkgcloud/amazon/compute/client/servers.js b/lib/pkgcloud/amazon/compute/client/servers.js index 66da39eb2..d2c070aee 100644 --- a/lib/pkgcloud/amazon/compute/client/servers.js +++ b/lib/pkgcloud/amazon/compute/client/servers.js @@ -1,11 +1,10 @@ /* * servers.js: Instance methods for working with servers from AWS Cloud * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var async = require('async'), - request = require('request'), base = require('../../../core/compute'), pkgcloud = require('../../../../../lib/pkgcloud'), errs = require('errs'), @@ -32,7 +31,7 @@ exports.getVersion = function getVersion(callback) { // exports.getLimits = function getLimits(callback) { return errs.handle( - errs.create({ message: "AWS's API is not rate limited" }), + errs.create({ message: 'AWS\'s API is not rate limited' }), callback ); }; @@ -47,31 +46,29 @@ exports.getLimits = function getLimits(callback) { exports._getDetails = function getDetails(details, callback) { var self = this; - self._query( - 'DescribeInstanceAttribute', { - InstanceId: details.instanceId, - Attribute: 'userData' - }, - function (err, body, res) { - if (err) { - // disregard the errors, if any - return callback(null, details); - } + self.ec2.describeInstanceAttribute({ + InstanceId: details.instanceId || details.InstanceId, + Attribute: 'userData' + }, function(err, data) { + if (err) { + // disregard the errors, if any + return callback(null, details); + } - var meta = new Buffer( - body.userData.value || '', - 'base64' - ).toString(); + var meta = new Buffer( + data.UserData.Value || '', + 'base64' + ).toString(); - try { - meta = JSON.parse(meta); - } catch (e) { - meta = {}; - } + try { + meta = JSON.parse(meta); + } catch (e) { + meta = {}; + } - details.name = meta.name; - callback(null, details); - }); + details.name = meta.name; + callback(null, details); + }); }; // @@ -81,34 +78,37 @@ exports._getDetails = function getDetails(details, callback) { // // Lists all servers available to your account. // -exports.getServers = function getServers(callback) { +exports.getServers = function getServers(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + var self = this; - return self._query('DescribeInstances', {}, function (err, body, res) { + + self.ec2.describeInstances(options, function(err, data) { if (err) { - return callback(err); + callback(err); + return; } var servers = []; - if (!body || !body.reservationSet || !body.reservationSet.item) { - return callback(null, []); - } - - self._toArray(body.reservationSet.item).forEach(function (reservation) { - self._toArray(reservation.instancesSet.item).forEach(function (instance) { + data.Reservations.forEach(function(reservation) { + reservation.Instances.forEach(function(instance) { servers.push(instance); }); }); - async.map( - servers, + // TODO investigate performance when getServers returns 100s of servers + async.map(servers, self._getDetails.bind(self), function finish(err, servers) { return err ? callback(err) : callback(null, servers.map(function (server) { - return new compute.Server(self, server); - }), res); + return new compute.Server(self, server); + })); } ); }); @@ -134,14 +134,14 @@ exports.createServer = function createServer(options, callback) { options = options || {}; // no args var self = this, - meta = { name: options.name || '' }, - createOptions = { - UserData: new Buffer(JSON.stringify(meta)).toString('base64'), - MinCount: 1, - MaxCount: 1 - }, - securityGroup, - securityGroupId; + meta = { name: options.name || '' }, + createOptions = { + UserData: new Buffer(JSON.stringify(meta)).toString('base64'), + MinCount: 1, + MaxCount: 1 + }, + securityGroup, + securityGroupId; if (!options.image) { return errs.handle( @@ -154,12 +154,12 @@ exports.createServer = function createServer(options, callback) { securityGroup = this.securityGroup || options['SecurityGroup']; if (securityGroup) { - createOptions['SecurityGroup'] = securityGroup; + createOptions['SecurityGroups'] = [securityGroup]; } securityGroupId = this.securityGroupId || options['SecurityGroupId']; if (securityGroupId) { - createOptions['SecurityGroupId'] = securityGroupId; + createOptions['SecurityGroupIds'] = [securityGroupId]; } createOptions.ImageId = options.image instanceof base.Image @@ -181,23 +181,19 @@ exports.createServer = function createServer(options, callback) { || options['Placement.AvailabilityZone']; } - return this._query( - 'RunInstances', - createOptions, - function (err, body, res) { - var server; - if (err) { - return callback(err); - } + self.ec2.runInstances(createOptions, function(err, data) { + var server; + if (err) { + return callback(err); + } - self._toArray(body.instancesSet.item).forEach(function (instance) { - instance.meta = meta; - server = new compute.Server(self, instance); - }); + data.Instances.forEach(function (instance) { + instance.meta = meta; + server = new compute.Server(self, instance); + }); - callback(null, server, res); - } - ); + callback(null, server); + }); }; // @@ -208,17 +204,16 @@ exports.createServer = function createServer(options, callback) { // Destroy a server in AWS. // exports.destroyServer = function destroyServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; - - return this._query( - 'TerminateInstances', - { InstanceId: serverId }, - function (err, body, res) { - return err - ? callback && callback(err) - : callback && callback(null, { ok: serverId }, res); - } - ); + var self = this, + serverId = server instanceof base.Server ? server.id : server; + + self.ec2.terminateInstances({ + InstanceIds: [ serverId ] + }, function(err) { + return err + ? callback && callback(err) + : callback && callback(null, { ok: serverId }); + }); }; // @@ -232,26 +227,21 @@ exports.getServer = function getServer(server, callback) { var self = this, serverId = server instanceof base.Server ? server.id : server; - return this._query( - 'DescribeInstances', - { - 'InstanceId.1' : serverId, - 'Filter.1.Name': 'instance-state-code', - 'Filter.1.Value.1': 0, // pending - 'Filter.1.Value.2': 16, // running - 'Filter.1.Value.3': 32, // shutting down - 'Filter.1.Value.4': 64, // stopping - 'Filter.1.Value.5': 80 // stopped - }, - function (err, body, res) { + self.ec2.describeInstances({ + InstanceIds: [ serverId ], + Filters: [ + { Name: 'instance-state-code', + Values : [ '0', '16', '32', '64', '80' ] } + ] + }, function (err, data) { var server; if (err) { return callback(err); } - self._toArray(body.reservationSet.item).forEach(function (reservation) { - self._toArray(reservation.instancesSet.item).forEach(function (instance) { + data.Reservations.forEach(function(reservation) { + reservation.Instances.forEach(function (instance) { server = instance; }); }); @@ -261,7 +251,10 @@ exports.getServer = function getServer(server, callback) { } self._getDetails(server, function (err, server) { - if (err) return callback(err); + if (err) { + return callback(err); + } + callback(null, new compute.Server(self, server)); }); } @@ -277,17 +270,16 @@ exports.getServer = function getServer(server, callback) { // Reboots a server // exports.rebootServer = function rebootServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; - - return this._query( - 'RebootInstances', - { InstanceId: serverId }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, { ok: serverId }, res); - } - ); + var self = this, + serverId = server instanceof base.Server ? server.id : server; + + self.ec2.rebootInstances({ + InstanceIds: [ serverId ] + }, function (err) { + return err + ? callback && callback(err) + : callback && callback(null, { ok: serverId }); + }); }; // diff --git a/lib/pkgcloud/amazon/compute/flavor.js b/lib/pkgcloud/amazon/compute/flavor.js index 894477ca9..50325b37f 100644 --- a/lib/pkgcloud/amazon/compute/flavor.js +++ b/lib/pkgcloud/amazon/compute/flavor.js @@ -1,42 +1,78 @@ /* * flavor.js: AWS Cloud Package * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - base = require('../../core/compute/flavor'); +var util = require('util'), + base = require('../../core/compute/flavor'), + _ = require('lodash'); var Flavor = exports.Flavor = function Flavor(client, details) { base.Flavor.call(this, client, details); }; -utile.inherits(Flavor, base.Flavor); +util.inherits(Flavor, base.Flavor); Flavor.options = { - 'm1.small': { ram: 1.7 * 1024, disk: 160 }, - 'm1.medium': { ram: 3.75 * 1024, disk: 410 }, - 'm1.large': { ram: 7.5 * 1024, disk: 850 }, - 'm1.xlarge': { ram: 15 * 1024, disk: 1690 }, - 'c1.medium': { ram: 1.7 * 1024, disk: 350 }, - 'c1.xlarge': { ram: 7 * 1024, disk: 1690 }, - 'm2.xlarge': { ram: 17.1 * 1024, disk: 420 }, - 'm2.2xlarge': { ram: 34.2 * 1024, disk: 850 }, - 'm2.4xlarge': { ram: 68.4 * 1024, disk: 1690 }, - 'cc1.4xlarge': { ram: 23 * 1024, disk: 1690 }, - 'cg1.4xlarge': { ram: 22 * 1024, disk: 1690 }, - 'cc2.8xlarge': { ram: 60.5 * 1024, disk: 3370 }, - 't1.micro': { ram: 613, disk: 0 } + + // Previous Generation Instance Types + 'm1.small': { id: 'm1.small', ram: 1.7 * 1024, disk: 160 }, + 'm1.medium': { id: 'm1.medium', ram: 3.75 * 1024, disk: 410 }, + 'm1.large': { id: 'm1.large', ram: 7.5 * 1024, disk: 2 * 420 }, + 'm1.xlarge': { id: 'm1.xlarge', ram: 15 * 1024, disk: 4 * 420 }, + 'c1.medium': { id: 'c1.medium', ram: 1.7 * 1024, disk: 350 }, + 'c1.xlarge': { id: 'c1.xlarge', ram: 7 * 1024, disk: 4 * 420 }, + 'cc2.8xlarge': { id: 'cc2.8xlarge', ram: 60.5 * 1024, disk: 4 * 840 }, + 'm2.xlarge': { id: 'm2.xlarge', ram: 17.1 * 1024, disk: 420 }, + 'm2.2xlarge': { id: 'm2.2xlarge', ram: 34.2 * 1024, disk: 850 }, + 'm2.4xlarge': { id: 'm2.4xlarge', ram: 68.4 * 1024, disk: 2 * 840 }, + 'cr1.8xlarge': { id: 'cr1.8xlarge', ram: 244 * 1024, disk: 2 * 120 }, + 'hi1.4xlarge': { id: 'hi1.4xlarge', ram: 60.5 * 1024, disk: 2 * 1024 }, + 'cg1.4xlarge': { id: 'cg1.4xlarge', ram: 22.5 * 1024, disk: 2 * 840 }, + 't1.micro': { id: 't1.micro', ram: 613, disk: 0}, + + // Current Generation Instance Types + 't2.micro': { id: 't2.micro', ram: 1024, disk: 0 }, + 't2.small': { id: 't2.small', ram: 2 * 1024, disk: 0 }, + 't2.medium': { id: 't2.medium', ram: 4 * 1024, disk: 0 }, + 'm3.medium': { id: 'm3.medium', ram: 3.75 * 1024, disk: 4 }, + 'm3.large': { id: 'm3.large', ram: 7.5 * 1024, disk: 32 }, + 'm3.xlarge': { id: 'm3.xlarge', ram: 15 * 1024, disk: 2 * 40 }, + 'm3.2xlarge': { id: 'm3.2xlarge', ram: 30 * 1024, disk: 2 * 80 }, + 'c3.large': { id: 'c3.large', ram: 3.75 * 1024, disk: 2 * 16 }, + 'c3.xlarge': { id: 'c3.xlarge', ram: 7.5 * 1024, disk: 2 * 40 }, + 'c3.2xlarge': { id: 'c3.2xlarge', ram: 15 * 1024, disk: 2 * 80 }, + 'c3.4xlarge': { id: 'c3.4xlarge', ram: 30 * 1024, disk: 2 * 160 }, + 'c3.8xlarge': { id: 'c3.8xlarge', ram: 60 * 1024, disk: 2 * 320 }, + 'g2.2xlarge': { id: 'g2.2xlarge', ram: 15 * 1024, disk: 60 }, + 'r3.large': { id: 'r3.large', ram: 15.25 * 1024, disk: 32 }, + 'r3.xlarge': { id: 'r3.xlarge', ram: 30.5 * 1024, disk: 80 }, + 'r3.2xlarge': { id: 'r3.2xlarge', ram: 61 * 1024, disk: 160 }, + 'r3.4xlarge': { id: 'r3.4xlarge', ram: 122 * 1024, disk: 320 }, + 'r3.8xlarge': { id: 'r3.8xlarge', ram: 244 * 1024, disk: 2 * 320 }, + 'i2.xlarge': { id: 'i2.xlarge', ram: 30.5 * 1024, disk: 800 }, + 'i2.2xlarge': { id: 'i2.2xlarge', ram: 61 * 1024, disk: 2 * 800 }, + 'i2.4xlarge': { id: 'i2.4xlarge', ram: 122 * 1024, disk: 4 * 800 }, + 'i2.8xlarge': { id: 'i2.8xlarge', ram: 244 * 1024, disk: 8 * 800 }, + 'hs1.8xlarge': { id: 'hs1.8xlarge', ram: 117 * 1024, disk: 24 * 2000 } + }; Flavor.prototype._setProperties = function (details) { var id = details.name || 'm1.small'; - if (!Flavor.options[id]) throw new TypeError('No such AWS Flavor: ' + id); + if (!Flavor.options[id]) { + throw new TypeError('No such AWS Flavor: ' + id); + } this.id = id; this.name = id; this.ram = Flavor.options[id].ram; this.disk = Flavor.options[id].disk; }; + +Flavor.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'ram', 'disk' ]); +}; \ No newline at end of file diff --git a/lib/pkgcloud/amazon/compute/image.js b/lib/pkgcloud/amazon/compute/image.js index 5648bbafc..2af52b309 100644 --- a/lib/pkgcloud/amazon/compute/image.js +++ b/lib/pkgcloud/amazon/compute/image.js @@ -1,22 +1,28 @@ /* * image.js: AWS Cloud * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - base = require('../../core/compute/image'); +var util = require('util'), + base = require('../../core/compute/image'), + _ = require('lodash'); var Image = exports.Image = function Image(client, details) { base.Image.call(this, client, details); }; -utile.inherits(Image, base.Image); +util.inherits(Image, base.Image); Image.prototype._setProperties = function (details) { - this.id = details.imageId; - this.name = details.name || details.imageLocation.split('/')[1]; + this.id = details.imageId || details.ImageId; + this.name = details.Name || details.ImageLocation.split('/')[1]; this.created = new Date(0); + this.blockDeviceMappings = details.BlockDeviceMappings; this.details = this.amazon = details; }; + +Image.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'created', 'blockDeviceMappings']); +}; diff --git a/lib/pkgcloud/amazon/compute/index.js b/lib/pkgcloud/amazon/compute/index.js index 1fe951fee..e2d4b17a9 100644 --- a/lib/pkgcloud/amazon/compute/index.js +++ b/lib/pkgcloud/amazon/compute/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the AWS compute module * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/amazon/compute/server.js b/lib/pkgcloud/amazon/compute/server.js index 818a6c608..e0cd396fc 100644 --- a/lib/pkgcloud/amazon/compute/server.js +++ b/lib/pkgcloud/amazon/compute/server.js @@ -1,25 +1,26 @@ /* * server.js: AWS Server * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - base = require('../../core/compute/server'); +var util = require('util'), + base = require('../../core/compute/server'), + _ = require('lodash'); var Server = exports.Server = function Server(client, details) { base.Server.call(this, client, details); }; -utile.inherits(Server, base.Server); +util.inherits(Server, base.Server); Server.prototype._setProperties = function (details) { - this.id = details.instanceId; + this.id = details.InstanceId || details.instanceId; this.name = details.name || (details.meta || {}).name; - if (details.instanceState) { - switch (details.instanceState.name.toUpperCase()) { + if (details.State) { + switch (details.State.Name.toUpperCase()) { case 'PENDING': this.status = this.STATUS.provisioning; break; @@ -41,13 +42,13 @@ Server.prototype._setProperties = function (details) { var addresses = { private: [], public: [] }; - ['ipAddress', 'dnsName'].forEach(function (prop) { + ['PublicIpAddress', 'PublicDnsName'].forEach(function (prop) { if (typeof details[prop] === 'string') { addresses.public.push(details[prop]); } }); - ['privateIpAddress', 'privateDnsName'].forEach(function (prop) { + ['PrivateIpAddress', 'PrivateDnsName'].forEach(function (prop) { if (typeof details[prop] === 'string') { addresses.private.push(details[prop]); } @@ -56,9 +57,13 @@ Server.prototype._setProperties = function (details) { // // AWS specific // - this.imageId = details.imageId; - this.addresses = details.addresses = addresses; - this.launchTime = details.launchTime; - this.type = details.instanceType; + this.imageId = details.ImageId; + this.addresses = details.Addresses = addresses; + this.launchTime = details.LaunchTime; + this.flavorId = details.InstanceType; this.original = this.amazon = details; }; + +Server.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'status', 'image', 'addresses', 'launchTime', 'flavor' ]); +}; diff --git a/lib/pkgcloud/amazon/index.js b/lib/pkgcloud/amazon/index.js index b1b009ab8..ed8534b40 100644 --- a/lib/pkgcloud/amazon/index.js +++ b/lib/pkgcloud/amazon/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the AWS module. * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/amazon/storage/client/containers.js b/lib/pkgcloud/amazon/storage/client/containers.js index 0bac80377..da71aefd2 100644 --- a/lib/pkgcloud/amazon/storage/client/containers.js +++ b/lib/pkgcloud/amazon/storage/client/containers.js @@ -1,13 +1,11 @@ /* * containers.js: Instance methods for working with containers from AWS S3 * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var async = require('async'), - request = require('request'), - base = require('../../../core/storage'), pkgcloud = require('../../../../../lib/pkgcloud'), storage = pkgcloud.providers.amazon.storage; @@ -19,20 +17,19 @@ var async = require('async'), exports.getContainers = function (callback) { var self = this; - this._xmlRequest({ - path: '/' - }, function (err, body) { + self.s3.listBuckets(function(err, data) { if (err) { - return callback(err); + callback(err); + return; } - var containers = self._toArray(body.Buckets.Bucket); + var containers = data.Buckets; - containers = containers.map(function (container) { + containers = containers.map(function(container) { return new (storage.Container)(self, container); }); - callback(null, containers); + callback(err, containers); }); }; @@ -47,12 +44,12 @@ exports.getContainer = function (container, callback) { var containerName = container instanceof storage.Container ? container.name : container, self = this; - this._xmlRequest({ - container: containerName - }, function (err, body) { + self.s3.listObjects({ + Bucket: containerName + }, function(err, data) { return err ? callback(err) - : callback(null, new (storage.Container)(self, body)); + : callback(null, new (storage.Container)(self, data)); }); }; @@ -64,13 +61,12 @@ exports.getContainer = function (container, callback) { // with this instance. // exports.createContainer = function (options, callback) { - var containerName = options instanceof base.Container ? options.name : options, + var containerName = options instanceof storage.Container ? options.name : options, self = this; - this._xmlRequest({ - method: 'PUT', - container: containerName - }, function (err) { + self.s3.createBucket({ + Bucket: containerName + }, function(err) { return err ? callback(err) : callback(null, new (storage.Container)(self, options)); @@ -84,38 +80,61 @@ exports.createContainer = function (options, callback) { // Destroys the specified `container` and all files in it. // exports.destroyContainer = function (container, callback) { - var containerName = container instanceof base.Container ? container.name : container, + var containerName = container instanceof storage.Container ? container.name : container, self = this; - this.getFiles(containerName, false, function (err, files) { + function getPagedFiles(containerName, marker, callback) { + var options = {}; + + if (marker) { + options.marker = marker; + } + + self.getFiles(containerName, marker, callback); + } + + function deleteContainer(err) { if (err) { return callback(err); } - function deleteContainer(err) { - if (err) { - return callback(err); + self.s3.deleteBucket({ + Bucket: containerName + }, function (err) { + return err + ? callback(err) + : callback(null, true); } + ); + } + + function destroyFile(file, next) { + file.remove(next); + } + + function deleteFiles(files, next) { + async.forEachLimit(files, 10, destroyFile, next); + } + + function handleResponse(err, files, meta) { + if (err) { + return callback(err); + } - self._xmlRequest({ - method: 'DELETE', - container: containerName - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, res.statusCode == 204); + if (meta.isTruncated) { + deleteFiles(files, function (err) { + if (err) { + callback(err); + return; } - ); - } - function destroyFile(file, next) { - file.remove(next); + getPagedFiles(containerName, files[files.length - 1].name, handleResponse); + }); + return; } - if (files.length === 0) { - return deleteContainer(); - } + deleteFiles(files, deleteContainer); + } - async.forEach(files, destroyFile, deleteContainer); - }); + getPagedFiles(containerName, handleResponse); }; diff --git a/lib/pkgcloud/amazon/storage/client/files.js b/lib/pkgcloud/amazon/storage/client/files.js index 9ed678faf..7065e71a0 100644 --- a/lib/pkgcloud/amazon/storage/client/files.js +++ b/lib/pkgcloud/amazon/storage/client/files.js @@ -1,21 +1,15 @@ /* * files.js: Instance methods for working with files from AWS S3 * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var fs = require('fs'), - filed = require('filed'), - mime = require('mime'), - request = require('request'), - utile = require('utile'), - qs = require('querystring'), - base = require('../../../core/storage'), +var base = require('../../../core/storage'), pkgcloud = require('../../../../../lib/pkgcloud'), - storage = pkgcloud.providers.amazon.storage; - -const MAX_UPLOAD = 5 * 1024 * 1024 * 1024; + through = require('through2'), + storage = pkgcloud.providers.amazon.storage, + _ = require('lodash'); // // ### function removeFile (container, file, callback) @@ -25,6 +19,8 @@ const MAX_UPLOAD = 5 * 1024 * 1024 * 1024; // Destroys the `file` in the specified `container`. // exports.removeFile = function (container, file, callback) { + var self = this; + if (container instanceof storage.Container) { container = container.name; } @@ -33,320 +29,110 @@ exports.removeFile = function (container, file, callback) { file = file.name; } - this._request({ - method: 'DELETE', - container: container, - path: file - }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, res.statusCode == 204); + self.s3.deleteObject({ + Bucket: container, + Key: file + }, function(err, data) { + return err + ? callback(err) + : callback(null, !!data.DeleteMarker); }); }; -exports.upload = function (options, callback) { - if (typeof options === 'function' && !callback) { - callback = options; - options = {}; - } +exports.upload = function (options) { + var self = this; - // - // Optional helper function passed to `this._request` - // in the case when no callback is passed to `.upload(options)`. - // - function onUpload(err, body, res) { - return err - ? callback(err) - : callback(null, res.statusCode == 200, res); + // check for deprecated calling with a callback + if (typeof arguments[arguments.length - 1] === 'function') { + self.emit('log::warn', 'storage.upload no longer supports calling with a callback'); } - var container = options.container, - success = callback ? onUpload : null, - apiStream, - inputStream; - - if (container instanceof storage.Container) { - container = container.name; - } + var s3Options = { + Bucket: options.container instanceof base.Container ? options.container.name : options.container, + Key: options.remote instanceof base.File ? options.remote.name : options.remote + }; - options.headers = options.headers || {}; + var s3Settings = { + queueSize: options.queueSize || 1, + partSize: options.partSize || 5 * 1024 * 1024 + }; - if (options.local) { - inputStream = filed(options.local); - options.headers['content-length'] = fs.statSync(options.local).size; - } - else if (options.stream) { - inputStream = options.stream; + if (options.cacheControl) { + s3Options.CacheControl = options.cacheControl; } - if (options.headers && !options.headers['content-type'] && options.remote) { - options.headers['content-type'] = mime.lookup(options.remote); + if (options.contentType) { + s3Options.ContentType = options.contentType; } - - // If the content-length is greater than 5gb, we need to force the - // multi-part path by nuking the content-length header - if (options.headers['content-length'] !== undefined && - parseInt(options.headers['content-length'], 10) > MAX_UPLOAD) { - delete options.headers['content-length']; + + if (options.contentEncoding) { + s3Options.ContentEncoding = options.contentEncoding; } - if (options.headers['content-length'] !== undefined) { - // Regular upload - apiStream = this._request({ - method: 'PUT', - upload: true, - container: container, - path: options.remote, - headers: options.headers || {} - }, success); - } else { - // Multi-part, 5mb chunk upload - apiStream = this.multipartUpload(options, success); + // use ACL until a more obvious permission generalization is available + if (options.acl) { + s3Options.ACL = options.acl; } - if (inputStream) { - inputStream.pipe(apiStream); + // add AWS specific options + if (options.cacheControl) { + s3Options.CacheControl = options.cacheControl; } - return apiStream; -}; - -exports.multipartUpload = function (options, callback) { - var self = this, - container = options.container, - chunk = 5 * 1024 * 1024, - chunksStarted = 0, - chunksFinished = [], - stream = new storage.ChunkedStream(chunk), - ended = false; - - if (container instanceof storage.Container) { - container = container.name; + if (options.ServerSideEncryption) { + s3Options.ServerSideEncryption = options.ServerSideEncryption; } - // We're doing a lot of parallel stuff there, - // but callback should be called only once - function handleResponse(err, body, res) { - if (handleResponse.called) return; - handleResponse.called = true; - callback && callback(err, body, res); - } + // we need a writable stream because aws-sdk listens for an error event on writable + // stream and redirects it to the provided callback - without the writable stream + // the error would be emitted twice on the returned proxyStream + var writableStream = through(); + // we need a proxy stream so we can always return a file model + // via the 'success' event + var proxyStream = through(); - handleResponse.called = false; - - // Wait for first data event, probably file is less than 5 mbs and - // we don't need that multipart thing at all - var first = null, - fastCase = true; - - stream.once('data', function (data) { - // Good case - all data fits in one chunk - if (fastCase) { - if (data.length < chunk && !first) { - first = data; - return; - } else { - fastCase = false; - - // Emit accumulated chunk - self.emit('data', first); - first = null; - } - } + s3Options.Body = writableStream; - stream.pause(); - self._xmlRequest({ - method: 'POST', - container: container, - // Don't use the qs option here because there's no way to turn an object - // parsed with qs into just ?uploads. i.e. { uploads: null } turns into - // '?uploads=' which breaks signing - path: options.remote + '?uploads' - }, function (err, body, res) { - if (err) { - return handleResponse(err); - } - - if (res.statusCode !== 200) return handleResponse(res.statusCode, res); - - // Upload rest - function onChunk(chunk) { - stream.pause(); - uploadChunk(body.UploadId, chunk, function (err, chunk) { - if (err) return handleResponse(err); - - finish(chunk); - stream.resume(); - }); - } - stream.on('data', onChunk); - - // Upload existing chunk - onChunk(data); - } - ); - }); + var managedUpload = self.s3.upload(s3Options, s3Settings); - stream.on('end', function () { - if (fastCase) { - // Upload with default method once again - var rstream = self.upload(utile.mixin({}, options, { - stream: null, - headers: utile.mixin({}, options.headers, { - 'content-length': first.length - }) - }), handleResponse); - rstream.end(first); - return; - } + proxyStream.managedUpload = managedUpload; - ended = true; - finish(); - }); - - function uploadChunk(uploadId, data, uploadCallback) { - // Ignore empty chunks - if (data.length === 0) return; - - var id = ++chunksStarted, - chunk = { - uploadId: uploadId, - id: id, - etag: null - }; - - var stream = self._request({ - method: 'PUT', - upload: true, - path: options.remote, - container: container, - qs: { - partNumber: id, - uploadId: uploadId - }, - headers: utile.mixin({}, options.headers, { - 'content-length': data.length - }) - }, function (err, body, res) { - if (err) { return uploadCallback(err); } - if (res.statusCode != 200) return uploadCallback(res.statusCode); - - chunk.etag = res.headers.etag; - uploadCallback(null, chunk); - }); - stream.write(data); - stream.end(); - } - - function finish(chunk) { - if (chunk) chunksFinished.push(chunk); - - // We must send request only if: - // - stream was ended - // - we was doing multipart request - // - all chunks were uploaded - if (!ended || - chunksFinished.length === 0 || - chunksFinished.length !== chunksStarted) { - return; + managedUpload.send(function(err, data) { + if (err) { + return proxyStream.emit('error', err); } + return proxyStream.emit('success', new storage.File(self, data)); + }); - // Sort chunks in ascending order - chunksFinished.sort(function (a, b) { - return a.id > b.id ? 1 : a.id < b.id ? -1 : 0; - }); - - var body = '' + - chunksFinished.map(function (chunk) { - return '' + - '' + chunk.id + '' + - '' + chunk.etag + '' + - ''; - }).join('') + - ''; - - // Send "Complete Multipart Upload" request - self._request({ - method: 'POST', - container: container, - path: options.remote, - qs: { - uploadId: chunksFinished[0].uploadId - }, - headers: { - 'Content-Length': Buffer.byteLength(body) - }, - body: body - }, function (err, body, res) { - handleResponse(err, res.statusCode == 200); - }); - } + proxyStream.pipe(writableStream); - return stream; + return proxyStream; }; -exports.download = function (options, callback) { - var self = this, - success = callback ? onDownload : null, - container = options.container, - inputStream, - apiStream; - - // - // Optional helper function passed to `this._request` - // in the case when no callback is passed to `.download(options)`. - // - function onDownload(err, body, res) { - return err - ? callback(err) - : callback(null, new (storage.File)(self, utile.mixin(res.headers, { - container: container, - name: options.remote - }))); - } +exports.download = function (options) { + var self = this; - if (container instanceof storage.Container) { - container = container.name; - } - - if (options.local) { - inputStream = filed(options.local); - } - else if (options.stream) { - inputStream = options.stream; - } + return self.s3.getObject({ + Bucket: options.container instanceof base.Container ? options.container.name : options.container, + Key: options.remote instanceof base.File ? options.remote.name : options.remote + }).createReadStream(); - apiStream = this._request({ - path: options.remote, - container: container, - download: true - }, success); - - if (inputStream) { - apiStream.pipe(inputStream); - } - - return apiStream; }; exports.getFile = function (container, file, callback) { var containerName = container instanceof base.Container ? container.name : container, self = this; - this._request( - { - method: 'HEAD', - container: containerName, - path: file - }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, new storage.File(self, utile.mixin(res.headers, { - container: container, - name: file - }))); + self.s3.headObject({ + Bucket: containerName, + Key: file + }, function(err, data) { + return err + ? callback(err) + : callback(null, new storage.File(self, _.extend(data, { + container: container, + name: file + }))); }); }; @@ -356,22 +142,39 @@ exports.getFiles = function (container, options, callback) { if (typeof options === 'function') { callback = options; - options = null; + options = {}; + } + else if (!options) { + options = {}; } - this._xmlRequest( - { - container: containerName, - qs: options - }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, self._toArray(body.Contents).map(function (file) { - file.container = container; - return new storage.File(self, file); - })); - } - ); + var s3Options = { + Bucket: containerName + }; + + if (options.marker) { + s3Options.Marker = options.marker; + } + + if (options.prefix) { + s3Options.Prefix = options.prefix; + } + + if (options.maxKeys) { + s3Options.MaxKeys = options.maxKeys; + } + + self.s3.listObjects(s3Options, function(err, data) { + return err + ? callback(err) + : callback(null, self._toArray(data.Contents).map(function (file) { + file.container = container; + return new storage.File(self, file); + }), { + isTruncated: data.IsTruncated, + marker: data.Marker, + nextMarker: data.NextMarker + }); + }); }; diff --git a/lib/pkgcloud/amazon/storage/client/index.js b/lib/pkgcloud/amazon/storage/client/index.js index 5c47db546..f8d7a6e94 100644 --- a/lib/pkgcloud/amazon/storage/client/index.js +++ b/lib/pkgcloud/amazon/storage/client/index.js @@ -1,59 +1,22 @@ /* * client.js: Storage client for AWS S3 * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - urlJoin = require('url-join'), - xml2js = require('xml2js'), - auth = require('../../../common/auth'), - amazon = require('../../client'); +var util = require('util'), + AWS = require('aws-sdk'), + amazon = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { - this.serversUrl = 's3.amazonaws.com'; - amazon.Client.call(this, options); - utile.mixin(this, require('./containers')); - utile.mixin(this, require('./files')); + _.extend(this, require('./containers')); + _.extend(this, require('./files')); - this.before.push(auth.amazon.headersSignature); + this.s3 = new AWS.S3(this._awsConfig); }; -utile.inherits(Client, amazon.Client); - -Client.prototype._xmlRequest = function query(options, callback) { - - if (typeof options === 'function') { - callback = options; - options = {}; - } - - return this._request(options, function (err, body, res) { - - if (err) { - return callback(err); - } - var parser = new xml2js.Parser(); - - parser.parseString(body || '', function (err, data) { - return err - ? callback(err) - : callback(null, data, res); - }); - }); -}; - -Client.prototype._getUrl = function (options) { - options = options || {}; - - if (typeof options === 'string') { - return urlJoin('https://' + this.serversUrl, options); - } - - return urlJoin('https://' + - (options.container ? options.container + '.' : '') + - this.serversUrl, options.path); -}; +util.inherits(Client, amazon.Client); diff --git a/lib/pkgcloud/amazon/storage/container.js b/lib/pkgcloud/amazon/storage/container.js index ec01c99c3..1a2524e1a 100644 --- a/lib/pkgcloud/amazon/storage/container.js +++ b/lib/pkgcloud/amazon/storage/container.js @@ -1,19 +1,20 @@ /* * container.js: AWS S3 Bucket * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), storage = require('../storage'), - base = require('../../core/storage/container'); + base = require('../../core/storage/container'), + _ = require('lodash'); var Container = exports.Container = function Container(client, details) { base.Container.call(this, client, details); }; -utile.inherits(Container, base.Container); +util.inherits(Container, base.Container); Container.prototype._setProperties = function (details) { var self = this; @@ -33,9 +34,14 @@ Container.prototype._setProperties = function (details) { this.isTruncated = details.IsTruncated === 'true'; if (details.Contents) { - this.client._toArray(details.Contents).forEach(function (file) { + details.Contents.forEach(function (file) { file.container = self; self.files.push(new storage.File(self.client, file)); }); } }; + +Container.prototype.toJSON = function () { + return _.pick(this, ['name', 'maxKeys', 'isTruncated' ]); +}; + diff --git a/lib/pkgcloud/amazon/storage/file.js b/lib/pkgcloud/amazon/storage/file.js index d7ae0ceae..cf414df4b 100644 --- a/lib/pkgcloud/amazon/storage/file.js +++ b/lib/pkgcloud/amazon/storage/file.js @@ -1,26 +1,34 @@ /* * container.js: AWS S3 File * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - base = require('../../core/storage/file'); +var util = require('util'), + base = require('../../core/storage/file'), + _ = require('lodash'); var File = exports.File = function File(client, details) { base.File.call(this, client, details); }; -utile.inherits(File, base.File); +util.inherits(File, base.File); File.prototype._setProperties = function (details) { - var self = this; - this.name = details.name || details.Key; - this.size = +(details.Size || details['content-length']) || 0; - this.container = details.container; + this.etag = details.ETag || details.etag || null; + this.lastModified = details.LastModified || details.lastModified || null; + this.size = +(details.Size || details['content-length'] || details.ContentLength) || 0; + this.container = details.container || details.Bucket; + this.location = details.location || details.Location; + // amazon appears to send the etag double enquoted + this.etag = this.etag ? this.etag.replace(/"/g,'') : this.etag; // AWS Specific - this.storageClass = this.StorageClass; + this.storageClass = details.StorageClass || this.StorageClass; +}; + +File.prototype.toJSON = function () { + return _.pick(this, ['name', 'etag', 'size', 'storageClass', 'lastModified', 'container', 'location' ]); }; diff --git a/lib/pkgcloud/amazon/storage/index.js b/lib/pkgcloud/amazon/storage/index.js index 7d0432889..a7e4e5193 100644 --- a/lib/pkgcloud/amazon/storage/index.js +++ b/lib/pkgcloud/amazon/storage/index.js @@ -1,14 +1,13 @@ /* * index.js: Top-level include for the AWS S3 module * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ exports.Client = require('./client').Client; exports.Container = require('./container').Container; exports.File = require('./file').File; -exports.ChunkedStream = require('./utils').ChunkedStream; exports.createClient = function (options) { return new exports.Client(options); diff --git a/lib/pkgcloud/amazon/storage/utils.js b/lib/pkgcloud/amazon/storage/utils.js deleted file mode 100644 index 5418ab998..000000000 --- a/lib/pkgcloud/amazon/storage/utils.js +++ /dev/null @@ -1,99 +0,0 @@ -var util = require('util'), - Stream = require('stream').Stream; - -var ChunkedStream = exports.ChunkedStream = function ChunkedStream(chunk) { - Stream.call(this); - - this.writable = true; - this.readable = true; - - this.ended = false; - this.paused = false; - this.size = 0; - this.chunk = chunk; - this.buffer = []; - this.chunkBuffer = []; -}; - -util.inherits(ChunkedStream, Stream); - -ChunkedStream.prototype.write = function write(data, encoding) { - if (!Buffer.isBuffer(data)) { - data = new Buffer(data, encoding); - } - - this.buffer.push(data); - this.size += data.length; - - // Split data in chunks - while (this.size >= this.chunk) { - var total = 0, - parts = []; - - this.buffer = this.buffer.filter(function (part) { - if (total >= this.chunk) return true; - - parts.push(part); - total += part.length; - return false; - }, this); - - // Last chunk is bigger than we need - if (total > this.chunk) { - var last = parts[parts.length - 1], - splitPos = last.length - total + this.chunk, - head = last.slice(0, splitPos), - tail = last.slice(splitPos); - - parts[parts.length - 1] = head; - - // Return tail back to main buffer - this.buffer.unshift(tail); - } - - this.emitChunk(Buffer.concat(parts, this.chunk)); - this.size -= this.chunk; - } - - if (this.paused) return false; -}; - -ChunkedStream.prototype.end = function end() { - if (this.ended) return; - - // Emit all left data - var self = this; - this.ended = true; - this.emitChunk(Buffer.concat(this.buffer, this.size)); - this.buffer = []; - this.size = 0; - - this.emit('end'); -}; -ChunkedStream.prototype.close = ChunkedStream.prototype.end; - -ChunkedStream.prototype.emitChunk = function emitChunk(chunk) { - if (this.paused) { - this.chunkBuffer.push(chunk); - return; - } - this.emit('data', chunk); -}; - -ChunkedStream.prototype.pause = function pause() { - if (this.paused) return; - this.paused = true; -}; - -ChunkedStream.prototype.resume = function resume() { - if (!this.paused) return; - this.paused = false; - - // Emit all accumulated data - this.chunkBuffer.forEach(function (chunk) { - this.emit('data', chunk); - }, this); - this.chunkBuffer = []; - - this.emit('drain'); -}; diff --git a/lib/pkgcloud/azure/client.js b/lib/pkgcloud/azure/client.js index 3ab008686..9ef30a2e2 100644 --- a/lib/pkgcloud/azure/client.js +++ b/lib/pkgcloud/azure/client.js @@ -5,8 +5,7 @@ * */ -var utile = require('utile'), - request = require('request'), +var util = require('util'), base = require('../core/base'); var Client = exports.Client = function (options) { @@ -23,7 +22,7 @@ var Client = exports.Client = function (options) { } }; -utile.inherits(Client, base.Client); +util.inherits(Client, base.Client); Client.prototype._toArray = function toArray(obj) { if (typeof obj === 'undefined') { diff --git a/lib/pkgcloud/azure/compute/client/images.js b/lib/pkgcloud/azure/compute/client/images.js index 50976b49f..b6c37354e 100644 --- a/lib/pkgcloud/azure/compute/client/images.js +++ b/lib/pkgcloud/azure/compute/client/images.js @@ -6,7 +6,6 @@ */ var pkgcloud = require('../../../../../lib/pkgcloud'), base = require('../../../core/compute'), - errs = require('errs'), compute = pkgcloud.providers.azure.compute, azureApi = require('../../utils/azureApi'); @@ -29,7 +28,7 @@ exports.getImages = function getImages(options, callback) { return this.get(path, function (err, body, res) { return err ? callback(err) - : callback(null, self._toArray(body.OSImage).map(function (image) { + : callback(null, self._toArray(body.Images.OSImage).map(function (image) { return new compute.Image(self, image); }), res); }); @@ -78,8 +77,13 @@ exports.getImage = function getImage(image, callback) { exports.createImage = function createImage(options, callback) { options || (options = {}); - if (!options.name) throw new TypeError('`name` is a required option'); - if (!options.server) throw new TypeError('`server` is a required option'); + if (!options.name) { + throw new TypeError('`name` is a required option'); + } + + if (!options.server) { + throw new TypeError('`server` is a required option'); + } var self = this, serverId = options.server instanceof base.Server diff --git a/lib/pkgcloud/azure/compute/client/index.js b/lib/pkgcloud/azure/compute/client/index.js index 6253ccffe..20b9d0ed4 100644 --- a/lib/pkgcloud/azure/compute/client/index.js +++ b/lib/pkgcloud/azure/compute/client/index.js @@ -5,22 +5,22 @@ * */ -var qs = require('querystring'), - utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), https = require('https'), auth = require('../../../common/auth'), azureApi = require('../../utils/azureApi.js'), xml2JSON = require('../../utils/xml2json.js').xml2JSON, - azure = require('../../client'); + azure = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { azure.Client.call(this, options); - utile.mixin(this, require('./flavors')); - utile.mixin(this, require('./images')); - utile.mixin(this, require('./servers')); - utile.mixin(this, require('./keys')); + _.extend(this, require('./flavors')); + _.extend(this, require('./images')); + _.extend(this, require('./servers')); + _.extend(this, require('./keys')); this.serversUrl = options.serversUrl || azureApi.MANAGEMENT_ENDPOINT; this.version = azureApi.MANAGEMENT_API_VERSION; @@ -47,13 +47,13 @@ var Client = exports.Client = function (options) { } }; -utile.inherits(Client, azure.Client); +util.inherits(Client, azure.Client); Client.prototype._query = function query(action, query, callback) { return this._request({ method: 'POST', headers: { }, - body: utile.mixin({ Action: action }, query) + body: _.extend({ Action: action }, query) }, function (err, body, res) { if (err) { return callback(err); } xml2JSON(body, function (err, data) { diff --git a/lib/pkgcloud/azure/compute/client/keys.js b/lib/pkgcloud/azure/compute/client/keys.js index ef2597d84..500008670 100644 --- a/lib/pkgcloud/azure/compute/client/keys.js +++ b/lib/pkgcloud/azure/compute/client/keys.js @@ -5,8 +5,7 @@ * */ -var errs = require('errs'), - utile = require('utile'); +var errs = require('errs'); // // ### function listKeys (options, callback) @@ -24,7 +23,7 @@ exports.listKeys = function (options, callback) { var self = this; options = options || {}; - return this._query('DescribeKeyPairs', options, function (err, body, res) { + return this._query('DescribeKeyPairs', options, function (err, body) { return err ? callback(err) : callback(null, self._toArray(body.keySet.item)); @@ -69,9 +68,9 @@ exports.addKey = function (options, callback) { 'ImportKeyPair', { KeyName: options.name, - PublicKeyMaterial: utile.base64.encode(options.key) + PublicKeyMaterial: new Buffer(options.key).toString('base64') }, - function (err, body, res) { + function (err) { return err ? callback(err) : callback(null, true); @@ -90,10 +89,10 @@ exports.destroyKey = function (name, callback) { return this._query( 'DeleteKeyPair', { KeyName: name }, - function (err, body, res) { + function (err) { return err ? callback(err) : callback(null, true); } ); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/azure/compute/client/servers.js b/lib/pkgcloud/azure/compute/client/servers.js index 5bf226f59..4d47ca16c 100644 --- a/lib/pkgcloud/azure/compute/client/servers.js +++ b/lib/pkgcloud/azure/compute/client/servers.js @@ -4,8 +4,7 @@ * (C) Microsoft Open Technologies, Inc. * */ -var async = require('async'), - base = require('../../../core/compute'), +var base = require('../../../core/compute'), pkgcloud = require('../../../../../lib/pkgcloud'), errs = require('errs'), azureApi = require('../../utils/azureApi'), @@ -29,7 +28,7 @@ exports.getVersion = function getVersion(callback) { // exports.getLimits = function getLimits(callback) { return errs.handle( - errs.create({ message: "Azure's API is not rate limited" }), + errs.create({ message: 'Azure\'s API is not rate limited' }), callback ); }; @@ -42,8 +41,7 @@ exports.getLimits = function getLimits(callback) { // Lists all servers available to your account. // exports.getServers = function getServers(callback) { - var self = this, - servers = []; + var self = this; azureApi.getServers(this, function (err, results) { if (err) { @@ -123,7 +121,7 @@ exports.createServer = function createServer(options, callback) { exports.destroyServer = function destroyServer(server, callback) { var serverId = server instanceof base.Server ? server.id : server; - azureApi.destroyServer(this, serverId, function (err, res) { + azureApi.destroyServer(this, serverId, function (err) { if (callback) { return !err ? callback && callback(null, { ok: serverId }) @@ -142,7 +140,7 @@ exports.destroyServer = function destroyServer(server, callback) { exports.stopServer = function stopServer(server, callback) { var serverId = server instanceof base.Server ? server.id : server; - azureApi.stopServer(this, serverId, function (err, res) { + azureApi.stopServer(this, serverId, function (err) { return !err ? callback(null, { ok: serverId }) : callback(err); @@ -174,7 +172,7 @@ exports.createHostedService = function createHostedService(serviceName, callback exports.rebootServer = function rebootServer(server, callback) { var serverId = server instanceof base.Server ? server.id : server; - azureApi.rebootServer(this, serverId, function (err, res) { + azureApi.rebootServer(this, serverId, function (err) { return !err ? callback(null, { ok: serverId }) : callback(err); diff --git a/lib/pkgcloud/azure/compute/flavor.js b/lib/pkgcloud/azure/compute/flavor.js index 1b17b8b60..ee2f5f66d 100644 --- a/lib/pkgcloud/azure/compute/flavor.js +++ b/lib/pkgcloud/azure/compute/flavor.js @@ -5,14 +5,14 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/compute/flavor'); var Flavor = exports.Flavor = function Flavor(client, details) { base.Flavor.call(this, client, details); }; -utile.inherits(Flavor, base.Flavor); +util.inherits(Flavor, base.Flavor); Flavor.options = { 'ExtraSmall': { ram: 0.768 * 1024, disk: 20 }, @@ -25,7 +25,9 @@ Flavor.options = { Flavor.prototype._setProperties = function (details) { var id = details.name || details.id || 'ExtraSmall'; - if (!Flavor.options[id]) throw new TypeError('No such Azure Flavor: ' + id); + if (!Flavor.options[id]) { + throw new TypeError('No such Azure Flavor: ' + id); + } this.id = id; this.name = id; diff --git a/lib/pkgcloud/azure/compute/image.js b/lib/pkgcloud/azure/compute/image.js index 38bb6b1f4..06a1682d7 100644 --- a/lib/pkgcloud/azure/compute/image.js +++ b/lib/pkgcloud/azure/compute/image.js @@ -5,14 +5,14 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/compute/image'); var Image = exports.Image = function Image(client, details) { base.Image.call(this, client, details); }; -utile.inherits(Image, base.Image); +util.inherits(Image, base.Image); Image.prototype._setProperties = function (details) { this.id = details.Name; diff --git a/lib/pkgcloud/azure/compute/server.js b/lib/pkgcloud/azure/compute/server.js index 3118db6c5..af78a4ed9 100644 --- a/lib/pkgcloud/azure/compute/server.js +++ b/lib/pkgcloud/azure/compute/server.js @@ -5,7 +5,7 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/compute/server'); var Server = exports.Server = function Server(client, details) { @@ -13,20 +13,20 @@ var Server = exports.Server = function Server(client, details) { this.requestPending = false; }; -utile.inherits(Server, base.Server); +util.inherits(Server, base.Server); Server.prototype._setProperties = function (details) { var roleInstance = null; details = details || {}; - this.id = details.Name || ""; - this.name = details.Name || ""; + this.id = details.Name || ''; + this.name = details.Name || ''; if (details.RoleInstanceList && details.RoleInstanceList.RoleInstance) { roleInstance = details.RoleInstanceList.RoleInstance; } - //console.log("Status: " + details.Status + ' RoleInstanceList: ' + roleInstance ? roleInstance.InstanceStatus : 'UNKNOWN'); + //console.log('Status: ' + details.Status + ' RoleInstanceList: ' + roleInstance ? roleInstance.InstanceStatus : 'UNKNOWN'); // azure can return an inconsistent RoleInstance status (not in azure rest api docs) so we check everything. // an azure vm has a complicated state machine. We need to check the status of both the deployment and the role. @@ -101,7 +101,6 @@ Server.prototype._setProperties = function (details) { // TODO: Need to clean up once I understand what is private ip? if (roleInstance) { - var ip = roleInstance.IpAddress; addresses.public.push(roleInstance.InstanceEndpoints.InstanceEndpoint.Vip); addresses.private.push(roleInstance.IpAddress); } else { diff --git a/lib/pkgcloud/azure/compute/templates/templates.js b/lib/pkgcloud/azure/compute/templates/templates.js index a2401d1fa..60a5336be 100644 --- a/lib/pkgcloud/azure/compute/templates/templates.js +++ b/lib/pkgcloud/azure/compute/templates/templates.js @@ -7,7 +7,7 @@ var fs = require('fs'); var PATH = require('path'); -var _ = require('underscore'); +var _ = require('lodash'); exports.loadSync = function (name) { var path = PATH.join(__dirname, name); @@ -15,7 +15,8 @@ exports.loadSync = function (name) { }; exports.compileSync = function (template, params) { - return _.template(template, params); + var compiled = _.template(template); + return compiled(params); }; exports.load = function (name, callback) { @@ -28,8 +29,7 @@ exports.load = function (name, callback) { exports.compile = function (name, params, callback) { var path = PATH.join(__dirname, name); fs.readFile(path, 'utf8', function (err, data) { - callback(err, _.template(data, params)); + var compiled = _.template(data); + callback(err, compiled(params)); }); }; - - diff --git a/lib/pkgcloud/azure/database/client/databases.js b/lib/pkgcloud/azure/database/client/databases.js index 2c1700c0a..9a974e174 100644 --- a/lib/pkgcloud/azure/database/client/databases.js +++ b/lib/pkgcloud/azure/database/client/databases.js @@ -10,8 +10,19 @@ var errs = require('errs'), templates = require('../../utils/templates'), PATH = require('path'), xml2js = require('xml2js'), - _ = require('underscore'), - url = require('url'); + _ = require('lodash'); + +// Encode a uri according to Azure Table rules +// ### @options {uri} The uri to encode +// ### @return {String} The encoded uri. +var encodeTableUriComponent = function (uri) { + return encodeURIComponent(uri) + .replace(/!/g, '%21') + .replace(/'/g, '%27') + .replace(/\(/g, '%28') + .replace(/\)/g, '%29') + .replace(/\*/g, '%2A'); +}; // Create a new Azure Table Database // Need name of table to create @@ -48,17 +59,18 @@ exports.create = function (options, callback) { }, function (template, next) { // compile template with params - body = _.template(template, params); + var compiled = _.template(template); + body = compiled(params); headers['content-length'] = body.length; self._request({ method: 'POST', path: 'Tables', body: body, headers: headers - }, function (err, body, res) { + }, function (err, body) { if (err) { return next(err); } - var parser = new xml2js.Parser(); + var parser = new xml2js.Parser({explicitArray : false}); parser.parseString(body || '', function (err, data) { return !err ? next(null, data) @@ -68,7 +80,7 @@ exports.create = function (options, callback) { }], function (err, result) { return !err - ? callback(null, self.formatResponse(result)) + ? callback(null, self.formatResponse(result.entry)) : callback(err); } ); @@ -83,15 +95,15 @@ exports.list = function (callback) { this._xmlRequest({ method: 'GET', path: 'Tables' - }, function (err, body, res) { + }, function (err, body) { if (err) { return callback(err); } - if (body && body.entry) { - if (Array.isArray(body.entry)) { - body.entry.forEach(function (table) { + if (body && body.feed && body.feed.entry) { + if (Array.isArray(body.feed.entry)) { + body.feed.entry.forEach(function (table) { tables.push(self.formatResponse(table)); }); } else { - tables.push(self.formatResponse(body.entry)); + tables.push(self.formatResponse(body.feed.entry)); } } callback(null, tables); @@ -110,7 +122,7 @@ exports.remove = function (id, callback) { }), Array.prototype.slice.call(arguments).pop()); } - path = encodeTableUriComponent("Tables('" + id + "')"); + path = encodeTableUriComponent('Tables(\'' + id + '\')'); this._xmlRequest({ method: 'DELETE', path: path @@ -120,15 +132,3 @@ exports.remove = function (id, callback) { : callback(null, res.statusCode === 204); }); }; - -// Encode a uri according to Azure Table rules -// ### @options {uri} The uri to encode -// ### @return {String} The encoded uri. -var encodeTableUriComponent = function (uri) { - return encodeURIComponent(uri) - .replace(/!/g, '%21') - .replace(/'/g, '%27') - .replace(/\(/g, '%28') - .replace(/\)/g, '%29') - .replace(/\*/g, '%2A'); -}; diff --git a/lib/pkgcloud/azure/database/client/index.js b/lib/pkgcloud/azure/database/client/index.js index 7a6820c82..bb7ff2445 100644 --- a/lib/pkgcloud/azure/database/client/index.js +++ b/lib/pkgcloud/azure/database/client/index.js @@ -5,13 +5,13 @@ * */ -var utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), auth = require('../../../common/auth'), azureApi = require('../../utils/azureApi.js'), xml2JSON = require('../../utils/xml2json.js').xml2JSON, - azure = require('../../client'); - + azure = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { azure.Client.call(this, options); @@ -24,10 +24,10 @@ var Client = exports.Client = function (options) { this.azureKeys.storageAccessKey = this.config.storageAccessKey; this.before.push(auth.azure.tablesSignature); - utile.mixin(this, require('./databases')); + _.extend(this, require('./databases')); }; -utile.inherits(Client, azure.Client); +util.inherits(Client, azure.Client); // // Gets the version of the Azure Tables API we are running against diff --git a/lib/pkgcloud/azure/storage/client/containers.js b/lib/pkgcloud/azure/storage/client/containers.js index c505407fc..57773727a 100644 --- a/lib/pkgcloud/azure/storage/client/containers.js +++ b/lib/pkgcloud/azure/storage/client/containers.js @@ -5,9 +5,7 @@ * */ -var async = require('async'), - request = require('request'), - base = require('../../../core/storage'), +var base = require('../../../core/storage'), pkgcloud = require('../../../../../lib/pkgcloud'), storage = pkgcloud.providers.azure.storage; @@ -21,13 +19,14 @@ exports.getContainers = function (callback) { this._xmlRequest({ method: 'GET', + path: '/', qs: { comp: 'list' } }, function (err, body) { if (err) { return callback(err); } - var containers = self._toArray(body.Containers.Container); + var containers = self._toArray(body.EnumerationResults.Containers.Container); containers = containers.map(function (container) { return new (storage.Container)(self, container); @@ -46,8 +45,7 @@ exports.getContainers = function (callback) { // exports.getContainer = function (container, callback) { var containerName = container instanceof storage.Container ? container.name : container, - self = this, - options; + self = this; this._xmlRequest({ method: 'GET', @@ -55,7 +53,7 @@ exports.getContainer = function (container, callback) { qs: { restype: 'container' } - }, function (err, body, res) { + }, function (err, body) { return err ? callback(err) : callback(null, new (storage.Container)(self, body)); @@ -85,7 +83,7 @@ exports.createContainer = function (options, callback) { qs: { restype: 'container' } - }, function (err, body, res) { + }, function (err) { return err ? callback(err) : callback(null, new (storage.Container)(self, options)); @@ -104,8 +102,7 @@ exports.createContainer = function (options, callback) { // of the same name during this cleanup period, your call returns an error immediately. // exports.destroyContainer = function (container, callback) { - var containerName = container instanceof base.Container ? container.name : container, - self = this; + var containerName = container instanceof base.Container ? container.name : container; this._xmlRequest({ method: 'DELETE', diff --git a/lib/pkgcloud/azure/storage/client/files.js b/lib/pkgcloud/azure/storage/client/files.js index a985218b9..8a356388c 100644 --- a/lib/pkgcloud/azure/storage/client/files.js +++ b/lib/pkgcloud/azure/storage/client/files.js @@ -5,17 +5,15 @@ * */ -var fs = require('fs'), - filed = require('filed'), +var filed = require('filed-mimefix'), + through = require('through2'), mime = require('mime'), - request = require('request'), - utile = require('utile'), urlJoin = require('url-join'), - qs = require('querystring'), base = require('../../../core/storage'), AzureConstants = require('../../utils/constants'), pkgcloud = require('../../../../../lib/pkgcloud'), - storage = pkgcloud.providers.azure.storage; + storage = pkgcloud.providers.azure.storage, + _ = require('lodash'); // // ### function removeFile (container, file, callback) @@ -43,189 +41,241 @@ exports.removeFile = function (container, file, callback) { }); }; -exports.upload = function (options, callback) { - if (typeof options === 'function' && !callback) { - callback = options; - options = {}; - } +exports.upload = function (options) { + var self = this; - // - // Optional helper function passed to `this._request` - // in the case when no callback is passed to `.upload(options)`. - // - function onUpload(err, body, res) { - return err - ? callback(err) - : callback(null, res.statusCode === 200 || res.statusCode === 201, res); + // check for deprecated calling with a callback + if (typeof arguments[arguments.length - 1] === 'function') { + self.emit('log::warn', 'storage.upload no longer supports calling with a callback'); } - var container = options.container, - success = callback ? onUpload : null, - path, - rstream, - lstream; - - if (container instanceof storage.Container) { - container = container.name; + // if we don't have a size, we need to immediately jump into multipart upload + if (!options.size) { + return self.multipartUpload(options); } - options.headers = options.headers || {}; + var writableStream, + proxyStream = through(), + uploadOptions = { + method: 'PUT', + upload: true, + headers: options.headers || {} + }; - if (options.local) { - lstream = filed(options.local); - options.headers['content-length'] = fs.statSync(options.local).size; + if (options.container instanceof storage.Container) { + uploadOptions.path = urlJoin(options.container.name, options.remote); } - else if (options.stream) { - lstream = options.stream; + else { + uploadOptions.path = urlJoin(options.container, options.remote); } - if (options.headers && !options.headers['content-type'] && options.remote) { - options.headers['content-type'] = mime.lookup(options.remote); + if (options.contentType) { + uploadOptions.headers['content-type'] = options.contentType; + } + else { + uploadOptions.headers['content-type'] = mime.getType(options.remote); } - options.headers['x-ms-blob-type'] = AzureConstants.BlobConstants.BlobTypes.BLOCK; + uploadOptions.headers['content-length'] = options.size; + uploadOptions.headers['x-ms-blob-type'] = AzureConstants.BlobConstants.BlobTypes.BLOCK; - path = urlJoin(container, options.remote); + writableStream = self._request(uploadOptions); - if (options.azureBlockId) { - options.qs = { - comp: 'block', - blockid: options.azureBlockId + writableStream.on('complete', function(response) { + var err = self._parseError(response); + + if (err) { + proxyStream.emit('error', err); + return; } - } - if (options.headers['content-length'] !== undefined) { - // Regular upload - rstream = this._request({ - method: 'PUT', - upload: true, - path: path, - headers: options.headers || {}, - qs: options.qs - }, success); - } else { - // Multi-part, 5mb chunk upload - rstream = this.multipartUpload(options, success); - } + // load the file metadata from the cloud, so we can return a proper model + self.getFile(options.container, options.remote, function (err, file) { + if (err) { + proxyStream.emit('error', err); + return; + } - if (lstream) lstream.pipe(rstream); + proxyStream.emit('success', file); + }); + }); - return rstream; + writableStream.on('error', function(err) { + proxyStream.emit('err', err); + }); + + writableStream.on('data', function (chunk) { + proxyStream.emit('data', chunk); + }); + + // we need a proxy stream so we can always return a file model + // via the 'success' event + proxyStream.pipe(writableStream); + + return proxyStream; }; var getBlockId = function (a, b) { - return "block" + ((1e15 + a + "").slice(-b)); + return 'block' + ((1e15 + a + '').slice(-b)); }; -exports.multipartUpload = function (options, callback) { +exports.multipartUpload = function (options) { var self = this, - container = options.container, - chunk = AzureConstants.BlobConstants.DEFAULT_WRITE_BLOCK_SIZE_IN_BYTES, - numBlocks = 0, - chunksFinished = [], - stream = new storage.ChunkedStream(chunk), + numberOfBlocks = 0, + chunkedStream = new storage.ChunkedStream(AzureConstants.BlobConstants.DEFAULT_WRITE_BLOCK_SIZE_IN_BYTES), + proxyStream = through(), ended = false; - if (container instanceof storage.Container) { - container = container.name; - } + // for each chunk of data, we need to upload a block + chunkedStream.on('data', function (data) { + chunkedStream.pause(); - stream.on('data', function (data) { - stream.pause(); - options.azureBlockId = getBlockId(numBlocks++, 15); - options.headers['content-length'] = data.length; - - var next = function (err, body, res) { - // TODO What to do if err here? - // TODO MUST FIX - if (!ended) { - stream.resume(); - } else { - self.sendBlockList(options, numBlocks, callback); - } + var blockOptions = { + container: options.container, + remote: options.remote, + headers: options.headers || {}, + size: data.length, + blockId: getBlockId(numberOfBlocks++, 15) }; - var rstream = self.upload(options, next); - rstream.write(data); - rstream.end(); + var writableStream = self.uploadBlock(blockOptions); + + writableStream.on('error', function(err) { + chunkedStream.emit('error', err); + }); + + // when the block has been uploaded, resume if more data + writableStream.on('complete', function(response) { + var err = self._parseError(response); + + if (err) { + chunkedStream.emit('error', err); + ended = true; + } + else if (!ended) { + chunkedStream.resume(); + } + else { + // when we're done with data, we need to upload the block list + self.sendBlockList(_.extend({ + numberOfBlocks: numberOfBlocks + } ,_.pick(options, ['remote', 'container', 'headers', 'contentType'])), function(err) { + if (err) { + proxyStream.emit('error', err); + return; + } + + // finally, fetch the file, and emit success on the original stream + self.getFile(options.container, options.remote, function(err, file) { + if (err) { + proxyStream.emit('error', err); + return; + } + + proxyStream.emit('success', file); + }); + }); + } + }); + + // write the chunk to the block stream, and end it. + writableStream.write(data); + writableStream.end(); }); - stream.on('end', function (data) { + chunkedStream.on('end', function () { ended = true; }); - return stream; + chunkedStream.on('error', function(err) { + proxyStream.emit('error', err); + }); + + proxyStream.pipe(chunkedStream); + + return proxyStream; }; -exports.sendBlockList = function (options, numBlocks, callback) { - var container = options.container, - body, - path, - qs; +exports.sendBlockList = function (options, callback) { + var putOptions = { + method: 'PUT', + headers: options.headers || {}, + qs: { + comp: 'blocklist' + } + }; - if (container instanceof storage.Container) { - container = container.name; + if (options.container instanceof storage.Container) { + putOptions.path = urlJoin(options.container.name, options.remote); + } + else { + putOptions.path = urlJoin(options.container, options.remote); } - - options.headers = options.headers || {}; // remove x-ms-blob-type header or request will fail - if (options.headers['x-ms-blob-type']) { - delete options.headers['x-ms-blob-type']; + if (putOptions.headers['x-ms-blob-type']) { + delete putOptions.headers['x-ms-blob-type']; } - if (options.headers['content-type']) { - options.headers['x-ms-blob-content-type'] = options.headers['content-type']; + if (options.contentType) { + putOptions.headers['x-ms-blob-content-type'] = options.contentType; } else if (options.remote) { - options.headers['x-ms-blob-content-type'] = mime.lookup(options.remote); + putOptions.headers['x-ms-blob-content-type'] = mime.getType(options.remote); } - path = urlJoin(container, options.remote); - qs = { - comp: 'blocklist' - }; - - body = ''; - body += ''; - for (var i = 0; i < numBlocks; i++) { - body += '' + encodeURIComponent(getBlockId(i, 15)) + ''; + putOptions.body = ''; + putOptions.body += ''; + for (var i = 0; i < options.numberOfBlocks; i++) { + putOptions.body += '' + encodeURIComponent(getBlockId(i, 15)) + ''; } - body += ''; + putOptions.body += ''; - options.headers['content-length'] = body.length; + putOptions.headers['content-length'] = putOptions.body.length; - this._request({ - method: 'PUT', - path: path, - body: body, - headers: options.headers, - qs: qs - }, function (err, body, res) { + this._request(putOptions, function (err, body, res) { return err ? callback && callback(err) : callback && callback(null, res.statusCode === 201, res); }); }; +exports.uploadBlock = function(options) { + var uploadOptions = { + method: 'PUT', + upload: true, + headers: { + 'content-length': options.size + }, + qs: { + comp: 'block', + blockid: options.blockId + } + }; + + if (options.container instanceof storage.Container) { + uploadOptions.path = urlJoin(options.container.name, options.remote); + } + else { + uploadOptions.path = urlJoin(options.container, options.remote); + } + + return this._request(uploadOptions); +}; + exports.download = function (options, callback) { var self = this, - success = callback ? onDownload : null, container = options.container, lstream, rstream; - // - // Optional helper function passed to `this._request` - // in the case when no callback is passed to `.download(options)`. - // - function onDownload(err, body, res) { + var success = !callback ? null : function (err, body, res) { return err ? callback(err) - : callback(null, new (storage.File)(self, utile.mixin(res.headers, { - container: container, + : callback(null, new (storage.File)(self, _.extend(res.headers, { + container: options.container, name: options.remote }))); - } + }; if (container instanceof storage.Container) { container = container.name; @@ -260,17 +310,25 @@ exports.getFile = function (container, file, callback) { }, function (err, body, res) { return err ? callback(err) - : callback(null, new storage.File(self, utile.mixin(res.headers, { + : callback(null, new storage.File(self, _.extend(res.headers, { container: container, name: file }))); }); }; -exports.getFiles = function (container, download, callback) { +exports.getFiles = function (container, options, callback) { var containerName = container instanceof base.Container ? container.name : container, self = this; + if (typeof options === 'function') { + callback = options; + options = {}; + } + else if (!options) { + options = {}; + } + this._xmlRequest({ method: 'GET', path: containerName, @@ -278,7 +336,7 @@ exports.getFiles = function (container, download, callback) { restype: 'container', comp: 'list' } - }, function (err, body, res) { + }, function (err, body) { if (err) { return callback(err); } diff --git a/lib/pkgcloud/azure/storage/client/index.js b/lib/pkgcloud/azure/storage/client/index.js index 1f6eac737..b6cf02b98 100644 --- a/lib/pkgcloud/azure/storage/client/index.js +++ b/lib/pkgcloud/azure/storage/client/index.js @@ -5,20 +5,21 @@ * */ -var utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), auth = require('../../../common/auth'), azureApi = require('../../utils/azureApi.js'), xml2JSON = require('../../utils/xml2json.js').xml2JSON, - azure = require('../../client'); + azure = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { this.serversUrl = options.serversUrl || azureApi.STORAGE_ENDPOINT; azure.Client.call(this, options); - utile.mixin(this, require('./containers')); - utile.mixin(this, require('./files')); + _.extend(this, require('./containers')); + _.extend(this, require('./files')); // add the auth keys for request authorization this.azureKeys = {}; @@ -28,7 +29,7 @@ var Client = exports.Client = function (options) { this.before.push(auth.azure.storageSignature); }; -utile.inherits(Client, azure.Client); +util.inherits(Client, azure.Client); Client.prototype._xmlRequest = function query(options, callback) { return this._request(options, function (err, body, res) { @@ -57,6 +58,6 @@ Client.prototype._getUrl = function (options) { } - return urlJoin('http://' + this.azureKeys.storageAccount + '.' + this.serversUrl + '/', + return urlJoin(this.protocol + this.azureKeys.storageAccount + '.' + this.serversUrl + '/', fragment); }; diff --git a/lib/pkgcloud/azure/storage/container.js b/lib/pkgcloud/azure/storage/container.js index f3c2d90bb..f64fb3b9e 100644 --- a/lib/pkgcloud/azure/storage/container.js +++ b/lib/pkgcloud/azure/storage/container.js @@ -5,19 +5,17 @@ * */ -var utile = require('utile'), - storage = require('../storage'), +var util = require('util'), + _ = require('lodash'), base = require('../../core/storage/container'); var Container = exports.Container = function Container(client, details) { base.Container.call(this, client, details); }; -utile.inherits(Container, base.Container); +util.inherits(Container, base.Container); Container.prototype._setProperties = function (details) { - var self = this; - if (typeof details === 'string') { this.name = details; return; @@ -31,3 +29,7 @@ Container.prototype._setProperties = function (details) { this.original = this.azure = details; }; + +Container.prototype.toJSON = function () { + return _.pick(this, ['name']); +}; diff --git a/lib/pkgcloud/azure/storage/file.js b/lib/pkgcloud/azure/storage/file.js index cbfb3de30..ffc467f7c 100644 --- a/lib/pkgcloud/azure/storage/file.js +++ b/lib/pkgcloud/azure/storage/file.js @@ -5,14 +5,15 @@ * */ -var utile = require('utile'), +var util = require('util'), + _ = require('lodash'), base = require('../../core/storage/file'); var File = exports.File = function File(client, details) { base.File.call(this, client, details); }; -utile.inherits(File, base.File); +util.inherits(File, base.File); File.prototype._setProperties = function (details) { this.container = details.container; @@ -26,3 +27,7 @@ File.prototype._setProperties = function (details) { this.size = (details['content-length']) ? parseInt(details['content-length'], 10) : 0; } }; + +File.prototype.toJSON = function () { + return _.pick(this, ['name', 'size', 'container' ]); +}; \ No newline at end of file diff --git a/lib/pkgcloud/azure/storage/utils.js b/lib/pkgcloud/azure/storage/utils.js index c10963d33..9a600cf71 100644 --- a/lib/pkgcloud/azure/storage/utils.js +++ b/lib/pkgcloud/azure/storage/utils.js @@ -30,7 +30,9 @@ ChunkedStream.prototype.write = function write(data, encoding) { parts = []; this.buffer = this.buffer.filter(function (part) { - if (total >= this.chunk) return true; + if (total >= this.chunk) { + return true; + } parts.push(part); total += part.length; @@ -54,14 +56,17 @@ ChunkedStream.prototype.write = function write(data, encoding) { this.size -= this.chunk; } - if (this.paused) return false; + if (this.paused) { + return false; + } }; ChunkedStream.prototype.end = function end() { - if (this.ended) return; + if (this.ended) { + return; + } // Emit all left data - var self = this; this.ended = true; this.emitChunk(Buffer.concat(this.buffer, this.size)); this.buffer = []; @@ -80,12 +85,18 @@ ChunkedStream.prototype.emitChunk = function emitChunk(chunk) { }; ChunkedStream.prototype.pause = function pause() { - if (this.paused) return; + if (this.paused) { + return; + } + this.paused = true; }; ChunkedStream.prototype.resume = function resume() { - if (!this.paused) return; + if (!this.paused) { + return; + } + this.paused = false; // Emit all accumulated data diff --git a/lib/pkgcloud/azure/utils/azureApi.js b/lib/pkgcloud/azure/utils/azureApi.js index af2e4da7a..435686133 100644 --- a/lib/pkgcloud/azure/utils/azureApi.js +++ b/lib/pkgcloud/azure/utils/azureApi.js @@ -16,20 +16,29 @@ var HeaderConstants = require('./constants').HeaderConstants; var async = require('async'); var templates = require('../compute/templates/templates'); -var _ = require('underscore'); +var _ = require('lodash'); var errs = require('errs'); var URL = require('url'); var cert = require('../utils/cert'); var pkgcloud = require('../../../../../pkgcloud'); -var MANAGEMENT_API_VERSION = exports.MANAGEMENT_API_VERSION = '2012-03-01'; -var MANAGEMENT_ENDPOINT = exports.MANAGEMENT_ENDPOINT = 'management.core.windows.net'; +exports.MANAGEMENT_API_VERSION = '2012-03-01'; +exports.MANAGEMENT_ENDPOINT = 'management.core.windows.net'; var STORAGE_ENDPOINT = exports.STORAGE_ENDPOINT = 'blob.core.windows.net'; -var STORAGE_API_VERSION = exports.STORAGE_API_VERSION = HeaderConstants.TARGET_STORAGE_VERSION; -var TABLES_ENDPOINT = exports.TABLES_ENDPOINT = 'table.core.windows.net'; -var TABLES_API_VERSION = exports.TABLES_API_VERSION = '2012-02-12'; +exports.STORAGE_API_VERSION = HeaderConstants.TARGET_STORAGE_VERSION; +exports.TABLES_ENDPOINT = 'table.core.windows.net'; +exports.TABLES_API_VERSION = '2012-02-12'; var MINIMUM_POLL_INTERVAL = exports.MINIMUM_POLL_INTERVAL = 3000; +// Declaring variables for helper functions defined later +var createVM, createLinuxVM, createWindowsVM, validateCreateOptions, getServer, + getServers, rebootServer, stopServer, deleteHostedService, destroyServer, createImage, + captureServer, deleteImage, destroyImage, getMediaLinkUrl, createEndpoints, + makeTemplateRequest, createHostedService, addCertificate, getHostedServices, + deleteServer, getOSImage, deleteOSDisk, deleteOSBlob, getServersFromServices, + getServersFromService, isVM, getHostedServiceProperties, pollRequestStatus, + getStorageInfoFromUri; + /** * createServer() * @@ -55,7 +64,7 @@ var MINIMUM_POLL_INTERVAL = exports.MINIMUM_POLL_INTERVAL = 3000; * its status is RUNNING. This entire process may take several minutes. */ -var createServer = exports.createServer = function (client, options, callback) { +exports.createServer = function (client, options, callback) { var vmOptions = {}, ssh; @@ -131,123 +140,10 @@ var createServer = exports.createServer = function (client, options, callback) { ); }; -var createVM = function (client, options, vmOptions, callback) { - // check OS type of image to determine if we are creating a linux or windows VM - switch (vmOptions.image.OS.toLowerCase()) { - case 'linux': - createLinuxVM(client, options, vmOptions, callback); - break; - case 'windows': - createWindowsVM(client, options, vmOptions, callback); - break; - default: - callback(errs.create({message: 'Unknown Image OS: ' + vmOptions.image.OS})); - break; - } -}; - -var getMediaLinkUrl = function (storageAccount, fileName) { - return 'http://' + storageAccount + '.' + STORAGE_ENDPOINT + '/vhd/' + fileName; -}; - -var createEndpoints = function (ports) { - var endPoints = '', - template = templates.loadSync('endpoint.xml'); - - (ports || []).forEach(function (port) { - endPoints += templates.compileSync(template, port); - }); - return endPoints; -}; - -var createLinuxVM = function (client, options, vmOptions, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; - var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); - var label = new Buffer(options.name).toString('base64'); - - var configParams = { - NAME: options.name, - LABEL_BASE64: label, - USERNAME: options.username, - PASSWORD: options.password, - SSH_CERTIFICATE_FINGERPRINT: vmOptions.sshCertInfo.fingerprint, - PORT: options.ssh.port || '22', - LOCAL_PORT: options.ssh.localPort || '22', - ROLESIZE: options.flavor, - ENDPOINTS: createEndpoints(options.ports), - OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, - OS_IMAGE_MEDIALINK: mediaLink - }; - - makeTemplateRequest(client, path, 'linuxDeployment.xml', configParams, callback); -}; - -var createWindowsVM = function (client, options, vmOptions, callback) { - var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; - var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); - var label = new Buffer(options.name).toString('base64'); - - var configParams = { - NAME: options.name, - COMPUTER_NAME: options.computerName || options.name.slice(0, 15), - LABEL_BASE64: label, - PASSWORD: options.password, - ROLESIZE: options.flavor, - ENDPOINTS: createEndpoints(options.ports), - OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, - OS_IMAGE_MEDIALINK: mediaLink - }; - - makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); -}; - -var captureServer = function (client, serverName, targetImageName, callback) { - // /services/hostedservices//deployments//roleinstances//operations - var path = client.subscriptionId + '/services/hostedservices/' + - serverName + '/deployments/' + - serverName + '/roleInstances/' + - serverName + '/Operations'; - - var configParams = { - NAME: targetImageName - }; - - makeTemplateRequest(client, path, 'captureRole.xml', configParams, callback); -}; - -var deleteImage = function (client, image, callback) { - // https://management.core.windows.net//services/images/ - var path = client.subscriptionId + '/services/images/' + image.Name; - - var configParams = { - LABEL: image.LABEL - }; - - makeTemplateRequest(client, path, 'deleteImage.xml', configParams, callback); -}; - -var validateCreateOptions = function (options, config, callback) { - if (typeof options === 'function') { - options = {}; - } - options = options || {}; // no args - - // check required options values - ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function (member) { - if (!options[member]) { - errs.handle( - errs.create({ message: 'options.' + member + ' is a required argument.' }), - callback - ); - } - }); - callback(); -}; - /** * getServer */ -var getServer = exports.getServer = function (client, serverName, callback) { +getServer = exports.getServer = function (client, serverName, callback) { getServersFromService(client, serverName, function (err, servers) { return !err ? callback(err, servers[0] ? servers[0] : null) @@ -255,7 +151,7 @@ var getServer = exports.getServer = function (client, serverName, callback) { }); }; -var getServers = exports.getServers = function (client, callback) { +getServers = exports.getServers = function (client, callback) { // async execute the following tasks one by one and bail if there is an error async.waterfall([ function (next) { @@ -272,7 +168,7 @@ var getServers = exports.getServers = function (client, callback) { ); }; -var makeTemplateRequest = function (client, path, templateName, params, callback) { +makeTemplateRequest = function (client, path, templateName, params, callback) { var headers = {}, body; @@ -283,7 +179,8 @@ var makeTemplateRequest = function (client, path, templateName, params, callback }, function (template, next) { // compile template with params - body = _.template(template, params); + var compiled = _.template(template); + body = compiled(params); headers['content-length'] = body.length; headers['content-type'] = 'application/xml'; headers['accept'] = 'application/xml'; @@ -300,13 +197,13 @@ var makeTemplateRequest = function (client, path, templateName, params, callback pollRequestStatus(client, res.headers['x-ms-request-id'], MINIMUM_POLL_INTERVAL, next); }); }], - function (err, result) { + function (err) { callback(err); } ); }; -var createHostedService = exports.createHostedService = function (client, options, callback) { +createHostedService = exports.createHostedService = function (client, options, callback) { var path = client.subscriptionId + '/services/hostedservices'; var params = { NAME: options.name, @@ -323,7 +220,7 @@ var createHostedService = exports.createHostedService = function (client, option * POST https://management.core.windows.net//services/hostedservices//deployments//roleinstances//operations * A successful operation returns status code 201 (Created). Need to poll for success? */ -var rebootServer = exports.rebootServer = function (client, serviceName, callback) { +rebootServer = exports.rebootServer = function (client, serviceName, callback) { var path = client.subscriptionId + '/services/hostedservices/' + serviceName + '/deployments/' + serviceName + '/roleInstances/' + @@ -338,7 +235,7 @@ var rebootServer = exports.rebootServer = function (client, serviceName, callbac * POST https://management.core.windows.net//services/hostedservices//deployments//roleinstances//operations * A successful operation returns status code 201 (Created). Need to poll for success? */ -var stopServer = exports.stopServer = function (client, serviceName, callback) { +stopServer = exports.stopServer = function (client, serviceName, callback) { var path = client.subscriptionId + '/services/hostedservices/' + serviceName + '/deployments/' + serviceName + '/roleInstances/' + @@ -347,7 +244,7 @@ var stopServer = exports.stopServer = function (client, serviceName, callback) { makeTemplateRequest(client, path, 'shutdownRole.xml', {}, callback); }; -var addCertificate = function (client, serviceName, cert, password, callback) { +addCertificate = function (client, serviceName, cert, password, callback) { var path = client.subscriptionId + '/services/hostedservices/' + serviceName + '/certificates'; @@ -359,7 +256,7 @@ var addCertificate = function (client, serviceName, cert, password, callback) { makeTemplateRequest(client, path, 'addCertificate.xml', params, callback); }; -var deleteHostedService = exports.deleteHostedService = function (client, serviceName, callback) { +deleteHostedService = exports.deleteHostedService = function (client, serviceName, callback) { // DELETE https://management.core.windows.net//services/hostedservices/ var path = client.subscriptionId + '/services/hostedservices/' + serviceName; @@ -375,22 +272,22 @@ var deleteHostedService = exports.deleteHostedService = function (client, servic }); }; -var getHostedServices = exports.getHostedServices = function (client, callback) { +getHostedServices = exports.getHostedServices = function (client, callback) { var path = client.subscriptionId + '/services/hostedservices', services = []; - client.get(path, function (err, body, res) { + client.get(path, function (err, body) { if (err) { return callback(err); } - if (body.HostedService) { + if (body && body.HostedServices && body.HostedServices.HostedService) { // need to check if azure returned an array or single object - if (Array.isArray(body.HostedService)) { - body.HostedService.forEach(function (service) { + if (Array.isArray(body.HostedServices.HostedService)) { + body.HostedServices.HostedService.forEach(function (service) { services.push(service); }); } else { - services.push(body.HostedService); + services.push(body.HostedServices.HostedService); } } @@ -406,7 +303,7 @@ var getHostedServices = exports.getHostedServices = function (client, callback) * To determine the status code for the operation once it is complete, call Get Operation Status. * Because Delete Deployment is an asynchronous operation, it always returns status code 202 (Accept). */ -var destroyServer = exports.destroyServer = function (client, serverName, callback) { +destroyServer = exports.destroyServer = function (client, serverName, callback) { var server = null; // async execute the following tasks one by one and bail if there is an error @@ -432,13 +329,13 @@ var destroyServer = exports.destroyServer = function (client, serverName, callba function (next) { deleteHostedService(client, serverName, next); }], - function (err, result) { + function (err) { callback(err, true); } ); }; -var deleteServer = function (client, serverName, callback) { +deleteServer = function (client, serverName, callback) { var path = client.subscriptionId + '/services/hostedservices/' + serverName; path += '/deployments/' + serverName; @@ -454,7 +351,7 @@ var deleteServer = function (client, serverName, callback) { }); }; -var getOSImage = exports.getOSImage = function (client, imageName, callback) { +getOSImage = exports.getOSImage = function (client, imageName, callback) { var path = '/' + client.subscriptionId + '/services/images/' + imageName; var onError = function (err) { @@ -466,14 +363,14 @@ var getOSImage = exports.getOSImage = function (client, imageName, callback) { } }; - client.get(path, function (err, body, res) { + client.get(path, function (err, body) { return err ? onError(err) : callback(null, body); }); }; -var deleteOSDisk = function (client, server, callback) { +deleteOSDisk = function (client, server, callback) { var diskName = null, path; @@ -490,7 +387,6 @@ var deleteOSDisk = function (client, server, callback) { // https://management.core.windows.net//services/disks/ path = client.subscriptionId + '/services/disks/' + diskName; - client._request({ method: 'DELETE', path: path @@ -503,7 +399,7 @@ var deleteOSDisk = function (client, server, callback) { }); }; -var deleteOSBlob = function (client, server, callback) { +deleteOSBlob = function (client, server, callback) { var blob = null; if (server && server.RoleList && server.RoleList.Role) { @@ -522,7 +418,7 @@ var deleteOSBlob = function (client, server, callback) { callback(err); } else { var storage = pkgcloud.storage.createClient(client.config); - storage.removeFile(info.container, info.file, function (err, result) { + storage.removeFile(info.container, info.file, function (err) { callback(err); }); } @@ -533,7 +429,7 @@ var deleteOSBlob = function (client, server, callback) { * getServersFromServices * Retrieves all servers (VMs) from the list of services */ -var getServersFromServices = function (client, services, callback) { +getServersFromServices = function (client, services, callback) { var task = function (service, next) { getServersFromService(client, service.ServiceName, function (err, servers) { next(err, servers); @@ -550,16 +446,16 @@ var getServersFromServices = function (client, services, callback) { * getServersFromServices * Retrieves all servers (VMs) from a Hosted Service */ -var getServersFromService = function (client, serviceName, callback) { +getServersFromService = function (client, serviceName, callback) { var servers = []; getHostedServiceProperties(client, serviceName, function (err, result) { if (err) { return callback(err); } - if (result && result.Deployments && result.Deployments.Deployment) { - if (isVM(result.Deployments.Deployment)) { - servers.push(result.Deployments.Deployment); + if (result && result.HostedService && result.HostedService.Deployments && result.HostedService.Deployments.Deployment) { + if (isVM(result.HostedService.Deployments.Deployment)) { + servers.push(result.HostedService.Deployments.Deployment); } } @@ -567,7 +463,7 @@ var getServersFromService = function (client, serviceName, callback) { }); }; -var isVM = function (deployment) { +isVM = function (deployment) { if (deployment.RoleList && deployment.RoleList.Role) { if (deployment.RoleList.Role.RoleType === 'PersistentVMRole') { return true; @@ -582,7 +478,7 @@ var isVM = function (deployment) { GET https://management.core.windows.net//services/hostedservices/?embed-detail=true A successful operation returns status code 200 (OK). */ -var getHostedServiceProperties = function (client, serviceName, callback) { +getHostedServiceProperties = function (client, serviceName, callback) { var path = client.subscriptionId + '/services/hostedservices/' + serviceName + '?embed-detail=true'; var onError = function (err) { @@ -591,7 +487,7 @@ var getHostedServiceProperties = function (client, serviceName, callback) { : callback(err); }; - client.get(path, function (err, body, res) { + client.get(path, function (err, body) { return err ? onError(err) : callback(null, body); @@ -604,14 +500,14 @@ var getHostedServiceProperties = function (client, serviceName, callback) { * GET https://management.core.windows.net//operations/ */ -var pollRequestStatus = function (client, requestId, interval, callback) { +pollRequestStatus = function (client, requestId, interval, callback) { var checkStatus = function () { var path = client.subscriptionId + '/operations/' + requestId; - client.get(path, function (err, body, res) { + client.get(path, function (err, body) { if (err) { return callback(err); } - switch (body.Status) { + switch (body.Operation.Status) { case 'InProgress': setTimeout(checkStatus, interval); break; @@ -628,7 +524,7 @@ var pollRequestStatus = function (client, requestId, interval, callback) { checkStatus(); }; -var getStorageInfoFromUri = exports.getStorageInfoFromUri = function (uri, callback) { +getStorageInfoFromUri = exports.getStorageInfoFromUri = function (uri, callback) { var u, tokens, path, info = {}; @@ -658,7 +554,7 @@ var getStorageInfoFromUri = exports.getStorageInfoFromUri = function (uri, callb * 2. stop server if it is running * 3. capture server image */ -var createImage = exports.createImage = function (client, serverName, targetImageName, callback) { +createImage = exports.createImage = function (client, serverName, targetImageName, callback) { async.waterfall([ function (next) { // stop the server @@ -674,12 +570,23 @@ var createImage = exports.createImage = function (client, serverName, targetImag ); }; +deleteImage = function (client, image, callback) { + // https://management.core.windows.net//services/images/ + var path = client.subscriptionId + '/services/images/' + image.Name; + + var configParams = { + LABEL: image.LABEL + }; + + makeTemplateRequest(client, path, 'deleteImage.xml', configParams, callback); +}; + /** * destroyImage() * 1. get the requested image * 2. delete the image using its label */ -var destroyImage = exports.destroyImage = function (client, imageName, callback) { +destroyImage = exports.destroyImage = function (client, imageName, callback) { async.waterfall([ function (next) { // stop the server @@ -694,6 +601,108 @@ var destroyImage = exports.destroyImage = function (client, imageName, callback) ); }; +createVM = function (client, options, vmOptions, callback) { + // check OS type of image to determine if we are creating a linux or windows VM + switch (vmOptions.image.OSImage.OS.toLowerCase()) { + case 'linux': + createLinuxVM(client, options, vmOptions, callback); + break; + case 'windows': + createWindowsVM(client, options, vmOptions, callback); + break; + default: + callback(errs.create({message: 'Unknown Image OS: ' + vmOptions.image.OS})); + break; + } +}; + +getMediaLinkUrl = function (storageAccount, fileName) { + return 'http://' + storageAccount + '.' + STORAGE_ENDPOINT + '/vhd/' + fileName; +}; + +createEndpoints = function (ports) { + var endPoints = '', + template = templates.loadSync('endpoint.xml'); + + (ports || []).forEach(function (port) { + endPoints += templates.compileSync(template, port); + }); + return endPoints; +}; + +createLinuxVM = function (client, options, vmOptions, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; + var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); + var label = new Buffer(options.name).toString('base64'); + + var configParams = { + NAME: options.name, + LABEL_BASE64: label, + USERNAME: options.username, + PASSWORD: options.password, + SSH_CERTIFICATE_FINGERPRINT: vmOptions.sshCertInfo.fingerprint, + PORT: options.ssh.port || '22', + LOCAL_PORT: options.ssh.localPort || '22', + ROLESIZE: options.flavor, + ENDPOINTS: createEndpoints(options.ports), + OS_SOURCE_IMAGE_NAME: vmOptions.image.OSImage.Name, + OS_IMAGE_MEDIALINK: mediaLink + }; + + makeTemplateRequest(client, path, 'linuxDeployment.xml', configParams, callback); +}; + +createWindowsVM = function (client, options, vmOptions, callback) { + var path = client.subscriptionId + '/services/hostedservices/' + options.name + '/deployments'; + var mediaLink = getMediaLinkUrl(client.config.storageAccount, options.name + '.vhd'); + var label = new Buffer(options.name).toString('base64'); + + var configParams = { + NAME: options.name, + COMPUTER_NAME: options.computerName || options.name.slice(0, 15), + LABEL_BASE64: label, + PASSWORD: options.password, + ROLESIZE: options.flavor, + ENDPOINTS: createEndpoints(options.ports), + OS_SOURCE_IMAGE_NAME: vmOptions.image.Name, + OS_IMAGE_MEDIALINK: mediaLink + }; + + makeTemplateRequest(client, path, 'windowsDeployment.xml', configParams, callback); +}; + +captureServer = function (client, serverName, targetImageName, callback) { + // /services/hostedservices//deployments//roleinstances//operations + var path = client.subscriptionId + '/services/hostedservices/' + + serverName + '/deployments/' + + serverName + '/roleInstances/' + + serverName + '/Operations'; + + var configParams = { + NAME: targetImageName + }; + + makeTemplateRequest(client, path, 'captureRole.xml', configParams, callback); +}; + +validateCreateOptions = function (options, config, callback) { + if (typeof options === 'function') { + options = {}; + } + options = options || {}; // no args + + // check required options values + ['flavor', 'image', 'name', 'username', 'password', 'location'].forEach(function (member) { + if (!options[member]) { + errs.handle( + errs.create({ message: 'options.' + member + ' is a required argument.' }), + callback + ); + } + }); + callback(); +}; + exports._updateMinimumPollInterval = function(interval) { MINIMUM_POLL_INTERVAL = interval; -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/azure/utils/cert.js b/lib/pkgcloud/azure/utils/cert.js index 903053d46..4979aab87 100644 --- a/lib/pkgcloud/azure/utils/cert.js +++ b/lib/pkgcloud/azure/utils/cert.js @@ -85,7 +85,7 @@ var getFingerPrint = function (pem) { return sha1.digest('hex').toUpperCase(); }; -var getAzureCertInfo = exports.getAzureCertInfo = function (cert) { +exports.getAzureCertInfo = function (cert) { return { cert: cert, fingerprint: getFingerPrint(cert.toString()) diff --git a/lib/pkgcloud/azure/utils/hmacsha256sign.js b/lib/pkgcloud/azure/utils/hmacsha256sign.js index ae86f09bc..b7d1b3878 100644 --- a/lib/pkgcloud/azure/utils/hmacsha256sign.js +++ b/lib/pkgcloud/azure/utils/hmacsha256sign.js @@ -16,9 +16,6 @@ // Module dependencies. var crypto = require('crypto'); -// Expose 'HmacSHA256Sign'. -exports = module.exports = HmacSha256Sign; - /** * Creates a new HmacSHA256Sign object. * @@ -41,3 +38,6 @@ HmacSha256Sign.prototype.sign = function (stringToSign) { return crypto.createHmac('sha256', this._decodedAccessKey).update(stringToSign).digest('base64'); }; + +// Expose 'HmacSHA256Sign'. +exports = module.exports = HmacSha256Sign; diff --git a/lib/pkgcloud/azure/utils/sharedkey.js b/lib/pkgcloud/azure/utils/sharedkey.js index 7b75b59f6..0f795d37f 100644 --- a/lib/pkgcloud/azure/utils/sharedkey.js +++ b/lib/pkgcloud/azure/utils/sharedkey.js @@ -20,11 +20,6 @@ // Module dependencies. var HeaderConstants = require('./constants').HeaderConstants; var HmacSha256Sign = require('./hmacsha256sign'); -var URL = require('url'); -var azureApi = require('./azureApi'), - -// Expose 'SharedKey'. -exports = module.exports = SharedKey; /** * Creates a new SharedKey object. @@ -47,11 +42,9 @@ var getvalueToAppend = function (value) { * Signs a request with the Authentication header. * * @param {req} The request request object. - * @param {options} The request options to be signed. - * @param {function (error)} callback The callback function. * @return {undefined} */ -SharedKey.prototype.signRequest = function (req, options) { +SharedKey.prototype.signRequest = function (req) { var httpVerb = req.method || 'GET'; @@ -153,9 +146,12 @@ SharedKey.prototype._getCanonicalizedHeaders = function (req) { canonicalizedHeadersArray.sort(); for (var headerName in canonicalizedHeadersArray) { - canonicalizedHeaders += canonicalizedHeadersArray[headerName].toLowerCase() + ":" + req.headers[canonicalizedHeadersArray[headerName]] + '\n'; + canonicalizedHeaders += canonicalizedHeadersArray[headerName].toLowerCase() + ':' + req.headers[canonicalizedHeadersArray[headerName]] + '\n'; } } return canonicalizedHeaders; -}; \ No newline at end of file +}; + +// Expose 'SharedKey'. +exports = module.exports = SharedKey; diff --git a/lib/pkgcloud/azure/utils/sharedkeytable.js b/lib/pkgcloud/azure/utils/sharedkeytable.js index cee68144f..a1b822ba5 100644 --- a/lib/pkgcloud/azure/utils/sharedkeytable.js +++ b/lib/pkgcloud/azure/utils/sharedkeytable.js @@ -19,9 +19,6 @@ var HmacSha256Sign = require('./hmacsha256sign'), azureApi = require('../utils/azureApi'), URL = require('url'); -// Expose 'SharedKeyTable'. -exports = module.exports = SharedKeyTable; - /** * Creates a new SharedKeyTable object. * @@ -101,4 +98,7 @@ SharedKeyTable.prototype._getCanonicalizedResource = function (req) { } } return canonicalizedResource; -}; \ No newline at end of file +}; + +// Expose 'SharedKeyTable'. +exports = module.exports = SharedKeyTable; diff --git a/lib/pkgcloud/azure/utils/templates.js b/lib/pkgcloud/azure/utils/templates.js index 697a773b5..3b433a536 100644 --- a/lib/pkgcloud/azure/utils/templates.js +++ b/lib/pkgcloud/azure/utils/templates.js @@ -6,7 +6,7 @@ */ var fs = require('fs'); -var _ = require('underscore'); +var _ = require('lodash'); exports.load = function (path, callback) { fs.readFile(path, 'utf8', function (err, data) { @@ -19,9 +19,8 @@ exports.compile = function (path, params, callback) { if (err) { callback(err); } else { - callback(null, _.template(data, params)); + var compiled = _.template(data); + callback(null, compiled(params)); } }); }; - - diff --git a/lib/pkgcloud/azure/utils/xml2json.js b/lib/pkgcloud/azure/utils/xml2json.js index 9c28b0068..3d05030f9 100644 --- a/lib/pkgcloud/azure/utils/xml2json.js +++ b/lib/pkgcloud/azure/utils/xml2json.js @@ -25,7 +25,7 @@ exports.xml2JSON = function (xml, callback) { xml = xml.slice(index); } - var parser = new xml2js.Parser({normalize: false, trim: false}); + var parser = new xml2js.Parser({normalize: false, trim: false, explicitArray : false}); parser.parseString(xml, function (err, data) { if (err) { callback(err); diff --git a/lib/pkgcloud/common/auth.js b/lib/pkgcloud/common/auth.js index 3a0b10034..01a1b808b 100644 --- a/lib/pkgcloud/common/auth.js +++ b/lib/pkgcloud/common/auth.js @@ -1,14 +1,12 @@ /* * auth.js: Utilities for authenticating with multiple cloud providers * - * (C) 2011-2012 Nodejitsu Inc. + * (C) 2011-2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var httpSignature = require('./http-signature'), - awsSignature = require('./aws-signature'), - azureSignature = require('./azure-signature'), - utile = require('utile'); + azureSignature = require('./azure-signature'); var auth = exports; @@ -19,14 +17,16 @@ auth.basic = function basicAuth(req) { req.headers = req.headers || {}; req.headers.authorization = [ 'Basic', - utile.base64.encode(credentials) + new Buffer(credentials).toString('base64') ].join(' '); }; // Add Account number for requests to rackspace API auth.accountId = function (req) { req.headers = req.headers || {}; - req.headers['x-auth-project-id'] = this.config.accountNumber; + if (this.config.accountNumber) { + req.headers['x-auth-project-id'] = this.config.accountNumber; + } }; function signatureGenerator(sign) { @@ -47,10 +47,6 @@ function azureSignatureGenerator(sign) { } auth.httpSignature = signatureGenerator(httpSignature.sign); -auth.amazon = { - bodySignature: signatureGenerator(awsSignature.signBody), - headersSignature: signatureGenerator(awsSignature.signHeaders) -}; auth.azure = { managementSignature: azureSignatureGenerator(azureSignature.managementSignature), diff --git a/lib/pkgcloud/common/aws-signature.js b/lib/pkgcloud/common/aws-signature.js deleted file mode 100644 index 3b01faca3..000000000 --- a/lib/pkgcloud/common/aws-signature.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * aws-signature.js: Implmentation of authentication for Amazon AWS APIs. - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var url = require('url'), - qs = require('querystring'), - crypto = require('crypto'); - -exports.signBody = function signBody(req, options) { - if (!options) options = {}; - - if (typeof options.key !== 'string') { - throw new TypeError('`key` is a required argument for aws-signature'); - } - - if (typeof options.keyId !== 'string') { - throw new TypeError('`keyId` is a required argument for aws-signature'); - } - - var signatureString = [ - req.method, '\n', - this.serversUrl, '\n', - '/', '\n' - ], - query = req.body; - - query.AWSAccessKeyId = options.keyId; - query.SignatureMethod = 'HmacSHA256'; - query.SignatureVersion = 2; - query.Version = this.version; - query.Timestamp = new Date(+new Date + 36e5 * 30).toISOString(); - - Object.keys(query).sort().forEach(function (key, i) { - if (i !== 0) signatureString.push('&'); - signatureString.push(encodeURIComponent(key), '=', encodeURIComponent(query[key])); - }); - - var toSign = signatureString.join(''); - - // Crappy code, but AWS seems to need it - toSign = toSign.replace(/!/g, '%21'); - toSign = toSign.replace(/'/g, '%27'); - toSign = toSign.replace(/\*/g, '%2A'); - toSign = toSign.replace(/\(/g, '%28'); - toSign = toSign.replace(/\)/g, '%29'); - - query.Signature = crypto.createHmac( - 'sha256', - options.key - ).update(toSign).digest('base64'); - - if (req.qs) { - req.qs.Action = query.Action; - } - else { - req.qs = { - Action: query.Action - }; - } - - delete query.Action; - - req.body = Object.keys(query).sort().map(function (key) { - return encodeURIComponent(key) + '=' + encodeURIComponent(query[key]); - }).join('&'); - - req.headers['Content-Type'] = 'application/x-www-form-urlencoded'; - req.headers['Content-Length'] = Buffer.byteLength(req.body); -}; - -exports.signHeaders = function signHeaders(req, options) { - if (!options) options = {}; - - if (typeof options.key !== 'string') { - throw new TypeError('`key` is a required argument for aws-signature'); - } - - if (typeof options.keyId !== 'string') { - throw new TypeError('`keyId` is a required argument for aws-signature'); - } - - req.headers = req.headers || {}; - - // Lower-case keys in headers hashmap - var headers = {}; - Object.keys(req.headers).forEach(function (key) { - headers[key.toLowerCase()] = req.headers[key]; - }); - - var now = new Date(), - signatureString = [ - req.method || 'GET', '\n', - headers['content-md5'] || '', '\n', - headers['content-type'] || '', '\n', - now.toUTCString(), '\n' - ]; - - // Push amz headers to signature string - Object.keys(headers).forEach(function (key) { - if (/^x-amz/.test(key)) { - signatureString.push(key, ':', headers[key], '\n'); - } - }); - - if (req.signingUrl) { - signatureString.push(req.signingUrl); - } - else { - signatureString.push(req.path); - } - - var signature = crypto.createHmac( - 'sha1', - options.key - ).update(signatureString.join('')).digest('base64'); - - req.headers.Date = now.toUTCString(); - req.headers.Authorization = 'AWS ' + options.keyId + ':' + signature; -}; diff --git a/lib/pkgcloud/common/azure-signature.js b/lib/pkgcloud/common/azure-signature.js index d28239651..64f6c83bd 100644 --- a/lib/pkgcloud/common/azure-signature.js +++ b/lib/pkgcloud/common/azure-signature.js @@ -1,20 +1,14 @@ /* * azure-signature.js: Implementation of authentication for Azure APIs. * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var url = require('url'), - qs = require('querystring'), - https = require('https'), - azureApi = require('../azure/utils/azureApi'), +var azureApi = require('../azure/utils/azureApi'), SharedKey = require('../azure/utils/sharedkey'), SharedTableKey = require('../azure/utils/sharedkeytable'); -var MANAGEMENT_API_VERSION = azureApi.MANAGEMENT_API_VERSION; -var STORAGE_API_VERSION = azureApi.STORAGE_API_VERSION; - exports.managementSignature = function managementSignature(req, options) { req.headers = req.headers || {}; diff --git a/lib/pkgcloud/common/http-signature.js b/lib/pkgcloud/common/http-signature.js index 46ee1fb10..10d91ab7f 100644 --- a/lib/pkgcloud/common/http-signature.js +++ b/lib/pkgcloud/common/http-signature.js @@ -4,13 +4,11 @@ * Copyright (C) 2011 Joyent, Inc. All rights reserved. * MIT License * - * Modified by Nodejitsu, under MIT + * Modified under MIT * */ -var assert = require('assert'), - crypto = require('crypto'), - http = require('http'); +var crypto = require('crypto'); // // ## Globals @@ -25,8 +23,6 @@ var Algorithms = { 'hmac-sha512': true }; -var Authorization = 'Signature keyId="%s",algorithm="%s",headers="%s" %s'; - // // ## Specific Errors // @@ -127,4 +123,4 @@ module.exports = { return req; } -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/common/index.js b/lib/pkgcloud/common/index.js index cf5fe070c..b9753790b 100644 --- a/lib/pkgcloud/common/index.js +++ b/lib/pkgcloud/common/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the pkgcloud common module. * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/common/status.js b/lib/pkgcloud/common/status.js index 231afaa75..b4cab0dc8 100644 --- a/lib/pkgcloud/common/status.js +++ b/lib/pkgcloud/common/status.js @@ -1,7 +1,7 @@ /* * status.js: Standardized statuses for different services * - * (C) 2011-2012 Nodejitsu Inc. + * (C) 2011-2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/core/base/client.js b/lib/pkgcloud/core/base/client.js index 490380a14..34a4a5431 100644 --- a/lib/pkgcloud/core/base/client.js +++ b/lib/pkgcloud/core/base/client.js @@ -1,16 +1,14 @@ /* * client.js: Base client from which all pkgcloud clients inherit from * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var fs = require('fs'), - events = require('eventemitter2'), +var events = require('eventemitter2'), request = require('request'), - utile = require('utile'), + util = require('util'), qs = require('qs'), - common = require('../../common'), pkgcloud = require('../../../pkgcloud'), errs = require('errs'); @@ -27,7 +25,35 @@ var Client = exports.Client = function (options) { this.config = options || {}; }; -utile.inherits(Client, events.EventEmitter2); +util.inherits(Client, events.EventEmitter2); + +/** + * Client.setCustomUserAgent + * + * @description allows the caller to specify a custom prefix for the HTTP UserAgent + * for all queries generated during the lifetime of the client. + * + * Valid user agents should come in the form of app-name/version, for example: + * + * client.setCustomUserAgent("my-app/1.2.3"); + * + * @param {String} userAgent the new userAgent to be prefixed + */ +Client.prototype.setCustomUserAgent = function (userAgent) { + this._customUserAgent = userAgent; +}; + +/** + * Client.getUserAgent + * + * @description gets the full UserAgent for the current client + * + * @returns {string} + */ +Client.prototype.getUserAgent = function() { + return util.format('%snodejs-pkgcloud/%s', this._customUserAgent ? + this._customUserAgent + ' ' : '', pkgcloud.version); +}; /** * Client._request @@ -42,7 +68,6 @@ utile.inherits(Client, events.EventEmitter2); */ Client.prototype._request = function (options, callback) { var self = this; - var requestOptions = {}; requestOptions.method = options.method || 'GET'; @@ -106,7 +131,7 @@ Client.prototype._request = function (options, callback) { delete opts.signingUrl; // Set our User Agent - opts.headers['User-Agent'] = utile.format('nodejs-pkgcloud/%s', pkgcloud.version); + opts.headers['User-Agent'] = self.getUserAgent(); // If we are missing callback if (!callback) { @@ -151,30 +176,9 @@ Client.prototype._defaultRequestHandler = function (callback) { return callback(err); } - var statusCode = res.statusCode.toString(), - err2; - - if (Object.keys(self.failCodes).indexOf(statusCode) !== -1) { - // - // TODO: Support more than JSON errors here - // - err2 = { - provider: self.provider, - failCode: self.failCodes[statusCode], - statusCode: res.statusCode, - message: self.provider + ' Error (' + - statusCode + '): ' + self.failCodes[statusCode], - href: res.request.uri.href, - method: res.request.method, - headers: res.headers - }; - - try { - err2.result = typeof body === 'string' ? JSON.parse(body) : body; - } catch (e) { - err2.result = { err: body }; - } + var err2 = self._parseError(res, body); + if (err2) { self.emit('log::error', 'Error during provider response', err2); return callback(errs.create(err2)); } @@ -187,5 +191,37 @@ Client.prototype._defaultRequestHandler = function (callback) { }); callback(err, body, res); + }; +}; + +Client.prototype._parseError = function(response, body) { + var self = this, + statusCode = response.statusCode.toString(), + err; + + if (Object.keys(self.failCodes).indexOf(statusCode) !== -1) { + // + // TODO: Support more than JSON errors here + // + err = { + provider: self.provider, + failCode: self.failCodes[statusCode], + statusCode: response.statusCode, + message: self.provider + ' Error (' + + statusCode + '): ' + self.failCodes[statusCode], + href: response.request.uri.href, + method: response.request.method, + headers: response.headers + }; + + if (body) { + try { + err.result = typeof body === 'string' ? JSON.parse(body) : body; + } catch (e) { + err.result = { err: body }; + } + } } + + return err; }; diff --git a/lib/pkgcloud/core/base/index.js b/lib/pkgcloud/core/base/index.js index b73e15336..2d71cde3f 100644 --- a/lib/pkgcloud/core/base/index.js +++ b/lib/pkgcloud/core/base/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for pkgcloud `base` module from which all pkgcloud objects inherit. * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/core/base/model.js b/lib/pkgcloud/core/base/model.js index 2b1f5eed1..2c00d102d 100644 --- a/lib/pkgcloud/core/base/model.js +++ b/lib/pkgcloud/core/base/model.js @@ -1,12 +1,12 @@ /* * model.js: Base model from which all pkgcloud models inherit from * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var events = require('eventemitter2'), - utile = require('utile'); + util = require('util'); var Model = exports.Model = function (client, details) { events.EventEmitter2.call(this, { delimiter: '::', wildcard: true }); @@ -17,7 +17,7 @@ var Model = exports.Model = function (client, details) { } }; -utile.inherits(Model, events.EventEmitter2); +util.inherits(Model, events.EventEmitter2); // ### function setWait (attributes, interval, callback) // diff --git a/lib/pkgcloud/core/compute/flavor.js b/lib/pkgcloud/core/compute/flavor.js index 42b33d4cf..44abcc62b 100644 --- a/lib/pkgcloud/core/compute/flavor.js +++ b/lib/pkgcloud/core/compute/flavor.js @@ -1,18 +1,18 @@ /* * flavor.js: Base flavor from which all pkgcloud flavors inherit from * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var Flavor = exports.Flavor = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(Flavor, model.Model); +util.inherits(Flavor, model.Model); Flavor.prototype.refresh = function (callback) { return this.client.getFlavor(this, callback); diff --git a/lib/pkgcloud/core/compute/image.js b/lib/pkgcloud/core/compute/image.js index 7fff63adb..f468ad901 100644 --- a/lib/pkgcloud/core/compute/image.js +++ b/lib/pkgcloud/core/compute/image.js @@ -1,18 +1,18 @@ /* * image.js: Base image from which all pkgcloud images inherit from * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var Image = exports.Image = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(Image, model.Model); +util.inherits(Image, model.Model); Image.prototype.refresh = function (callback) { return this.client.getImage(this, callback); diff --git a/lib/pkgcloud/core/compute/index.js b/lib/pkgcloud/core/compute/index.js index cbececd00..d9c09456e 100644 --- a/lib/pkgcloud/core/compute/index.js +++ b/lib/pkgcloud/core/compute/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include from which all pkgcloud compute models inherit. * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ @@ -57,7 +57,6 @@ exports.serverIp = function (server, options) { isPrivate = options.isPrivate || exports.isPrivate, interfaces, addresses, - networks, pub; if (server.ips) { diff --git a/lib/pkgcloud/core/compute/server.js b/lib/pkgcloud/core/compute/server.js index 64d789bac..4bef9e01c 100644 --- a/lib/pkgcloud/core/compute/server.js +++ b/lib/pkgcloud/core/compute/server.js @@ -1,11 +1,11 @@ /* * server.js: Base server from which all pkgcloud servers inherit from * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'), computeStatus = require('../../common/status').compute; @@ -13,12 +13,15 @@ var Server = exports.Server = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(Server, model.Model); +util.inherits(Server, model.Model); Server.prototype.refresh = function (callback) { var self = this; return self.client.getServer(this, function (err, server) { - if (!err) self._setProperties(server.original); + if (!err) { + self._setProperties(server.original); + } + return callback.apply(this, arguments); }); }; diff --git a/lib/pkgcloud/core/dns/record.js b/lib/pkgcloud/core/dns/record.js index c7d760f4f..fec81e9cd 100644 --- a/lib/pkgcloud/core/dns/record.js +++ b/lib/pkgcloud/core/dns/record.js @@ -7,14 +7,14 @@ * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var Record = exports.Record = function (zone, details) { model.Model.call(this, zone.client, details); }; -utile.inherits(Record, model.Model); +util.inherits(Record, model.Model); Record.prototype.create = function(callback) { return this.zone.createRecord(this, callback); diff --git a/lib/pkgcloud/core/dns/zone.js b/lib/pkgcloud/core/dns/zone.js index 6f1bbd252..a334cf899 100644 --- a/lib/pkgcloud/core/dns/zone.js +++ b/lib/pkgcloud/core/dns/zone.js @@ -7,14 +7,14 @@ * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var Zone = exports.Zone = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(Zone, model.Model); +util.inherits(Zone, model.Model); Zone.prototype.create = function (callback) { return this.client.createZone(this, callback); diff --git a/lib/pkgcloud/core/loadbalancer/loadbalancer.js b/lib/pkgcloud/core/loadbalancer/loadbalancer.js index f2fda0017..5e4ebf45e 100644 --- a/lib/pkgcloud/core/loadbalancer/loadbalancer.js +++ b/lib/pkgcloud/core/loadbalancer/loadbalancer.js @@ -7,14 +7,14 @@ * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var LoadBalancer = exports.LoadBalancer = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(LoadBalancer, model.Model); +util.inherits(LoadBalancer, model.Model); LoadBalancer.prototype.create = function (callback) { return this.client.createLoadBalancer(this, callback); diff --git a/lib/pkgcloud/core/loadbalancer/node.js b/lib/pkgcloud/core/loadbalancer/node.js index c9ce602ae..606ff409a 100644 --- a/lib/pkgcloud/core/loadbalancer/node.js +++ b/lib/pkgcloud/core/loadbalancer/node.js @@ -7,11 +7,11 @@ * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var Node = exports.Node = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(Node, model.Model); +util.inherits(Node, model.Model); diff --git a/lib/pkgcloud/core/network/network.js b/lib/pkgcloud/core/network/network.js new file mode 100644 index 000000000..d51abacc6 --- /dev/null +++ b/lib/pkgcloud/core/network/network.js @@ -0,0 +1,31 @@ +/* + * network.js: Base network from which all pkgcloud networks inherit. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + model = require('../base/model'); + +var Network = exports.Network = function (client, details) { + model.Model.call(this, client, details); +}; + +util.inherits(Network, model.Model); + +Network.prototype.create = function (callback) { + this.client.createNetwork(this.name, callback); +}; + +Network.prototype.refresh = function (callback) { + this.client.getNetwork(this.id, callback); +}; + +Network.prototype.update = function (callback) { + this.client.updateNetwork(this, callback); +}; + +Network.prototype.destroy = function (callback) { + this.client.destroyNetwork(this.id, callback); +}; diff --git a/lib/pkgcloud/core/network/port.js b/lib/pkgcloud/core/network/port.js new file mode 100644 index 000000000..d918a1020 --- /dev/null +++ b/lib/pkgcloud/core/network/port.js @@ -0,0 +1,31 @@ +/* + * port.js: Base network from which all pkgcloud ports inherit. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + model = require('../base/model'); + +var Port = exports.Port = function (client, details) { + model.Model.call(this, client, details); +}; + +util.inherits(Port, model.Model); + +Port.prototype.create = function (callback) { + this.client.createPort(this.name, callback); +}; + +Port.prototype.refresh = function (callback) { + this.client.getPort(this.id, callback); +}; + +Port.prototype.update = function (callback) { + this.client.updatePort(this, callback); +}; + +Port.prototype.destroy = function (callback) { + this.client.destroyPort(this.id, callback); +}; diff --git a/lib/pkgcloud/core/network/securityGroup.js b/lib/pkgcloud/core/network/securityGroup.js new file mode 100644 index 000000000..18eeef7fd --- /dev/null +++ b/lib/pkgcloud/core/network/securityGroup.js @@ -0,0 +1,28 @@ +/* + * securityGroup.js: Base securityGroup from which all pkgcloud securityGroup inherit. + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var util = require('util'), + Model = require('../base/model').Model; + +var SecurityGroup = exports.SecurityGroup = function (client, details) { + Model.call(this, client, details); +}; + +util.inherits(SecurityGroup, Model); + +SecurityGroup.prototype.create = function (callback) { + this.client.createSecurityGroup(this, callback); +}; + +SecurityGroup.prototype.refresh = function (callback) { + this.client.getSecurityGroup(this.id, callback); +}; + +SecurityGroup.prototype.destroy = function (callback) { + this.client.destroySecurityGroup(this.id, callback); +}; diff --git a/lib/pkgcloud/core/network/securityGroupRule.js b/lib/pkgcloud/core/network/securityGroupRule.js new file mode 100644 index 000000000..abacd2c4b --- /dev/null +++ b/lib/pkgcloud/core/network/securityGroupRule.js @@ -0,0 +1,28 @@ +/* + * securityGroupRule.js: Base securityGroupRule from which all pkgcloud securityGroupRule inherit. + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var util = require('util'), + Model = require('../base/model').Model; + +var SecurityGroupRule = exports.SecurityGroupRule = function (client, details) { + Model.call(this, client, details); +}; + +util.inherits(SecurityGroupRule, Model); + +SecurityGroupRule.prototype.create = function (callback) { + this.client.createSecurityGroupRule(this, callback); +}; + +SecurityGroupRule.prototype.refresh = function (callback) { + this.client.getSecurityGroupRule(this.id, callback); +}; + +SecurityGroupRule.prototype.destroy = function (callback) { + this.client.destroySecurityGroupRule(this.id, callback); +}; diff --git a/lib/pkgcloud/core/network/subnet.js b/lib/pkgcloud/core/network/subnet.js new file mode 100644 index 000000000..34c2568b7 --- /dev/null +++ b/lib/pkgcloud/core/network/subnet.js @@ -0,0 +1,31 @@ +/* + * subnet.js: Base subnet from which all pkgcloud subnet inherit. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + model = require('../base/model'); + +var Subnet = exports.Subnet = function (client, details) { + model.Model.call(this, client, details); +}; + +util.inherits(Subnet, model.Model); + +Subnet.prototype.create = function (callback) { + this.client.createSubnet(this.name, callback); +}; + +Subnet.prototype.refresh = function (callback) { + this.client.getSubnet(this.id, callback); +}; + +Subnet.prototype.update = function (callback) { + this.client.updateSubnet(this, callback); +}; + +Subnet.prototype.destroy = function (callback) { + this.client.destroySubnet(this.id, callback); +}; diff --git a/lib/pkgcloud/core/storage/container.js b/lib/pkgcloud/core/storage/container.js index 8e1c008a7..2c5ab3be0 100644 --- a/lib/pkgcloud/core/storage/container.js +++ b/lib/pkgcloud/core/storage/container.js @@ -1,20 +1,20 @@ /* * container.js: Base container from which all pkgcloud containers inherit from * - * (C) 2010 Nodejitsu Inc. + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), model = require('../base/model'); var Container = exports.Container = function (client, details) { - model.Model.call(this, client, details); - this.files = []; + + model.Model.call(this, client, details); }; -utile.inherits(Container, model.Model); +util.inherits(Container, model.Model); Container.prototype.create = function (callback) { this.client.createContainer(this.name, callback); @@ -54,4 +54,4 @@ Container.prototype.getFiles = function (download, callback) { Container.prototype.removeFile = function (file, callback) { this.client.removeFile(this.name, file, callback); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/core/storage/file.js b/lib/pkgcloud/core/storage/file.js index e7981d892..d98d21d9d 100644 --- a/lib/pkgcloud/core/storage/file.js +++ b/lib/pkgcloud/core/storage/file.js @@ -1,12 +1,11 @@ /* * file.js: Base container from which all pkgcloud files inherit from * - * (C) 2010 Nodejitsu Inc. + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var fs = require('fs'), - utile = require('utile'), +var util = require('util'), model = require('../base/model'), storage = require('../storage'); @@ -14,7 +13,7 @@ var File = exports.File = function (client, details) { model.Model.call(this, client, details); }; -utile.inherits(File, model.Model); +util.inherits(File, model.Model); File.prototype.remove = function (callback) { this.client.removeFile(this.containerName, this.name, callback); diff --git a/lib/pkgcloud/core/storage/index.js b/lib/pkgcloud/core/storage/index.js index 8a9cc34f2..09f922080 100644 --- a/lib/pkgcloud/core/storage/index.js +++ b/lib/pkgcloud/core/storage/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include from which all pkgcloud storage models inherit. * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/digitalocean/client.js b/lib/pkgcloud/digitalocean/client.js index c261ff5cc..55ff0cce3 100644 --- a/lib/pkgcloud/digitalocean/client.js +++ b/lib/pkgcloud/digitalocean/client.js @@ -1,13 +1,11 @@ /* * client.js: Base client from which all Joyent clients inherit from * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - fs = require('fs'), - auth = require('../common/auth'), +var util = require('util'), base = require('../core/base'); // @@ -18,8 +16,8 @@ var utile = require('utile'), // #### @throws {TypeError} On bad input // var Client = exports.Client = function (opts) { - if (!opts || !opts.clientId || !opts.apiKey) { - throw new TypeError('clientId and apiKey are required'); + if (!opts || !opts.token) { + throw new TypeError('token is required'); } base.Client.call(this, opts); @@ -27,7 +25,7 @@ var Client = exports.Client = function (opts) { this.provider = 'digitalocean'; this.protocol = opts.protocol || 'https://'; this.serversUrl = opts.serversUrl; - + if (!this.before) { this.before = []; } @@ -41,14 +39,14 @@ var Client = exports.Client = function (opts) { }); this.before.push(function setAuth(req) { - req.qs = req.qs || {}; - - req.qs.client_id = opts.clientId; - req.qs.api_key = opts.apiKey; + req.headers = req.headers || {}; + req.headers.authorization = [ + 'Bearer', opts.token + ].join(' '); }); }; -utile.inherits(Client, base.Client); +util.inherits(Client, base.Client); Client.prototype.failCodes = { 400: 'Bad Request', diff --git a/lib/pkgcloud/digitalocean/compute/client/flavors.js b/lib/pkgcloud/digitalocean/compute/client/flavors.js index 015a67dde..d481fac61 100644 --- a/lib/pkgcloud/digitalocean/compute/client/flavors.js +++ b/lib/pkgcloud/digitalocean/compute/client/flavors.js @@ -1,7 +1,7 @@ /* * flavors.js: Implementation of DigitalOcean Flavors Client. * - * (C) 2012, Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ @@ -19,15 +19,15 @@ var pkgcloud = require('../../../../../lib/pkgcloud'), exports.getFlavors = function getFlavors(callback) { var self = this; return this._request({ - path: '/sizes' + path: '/v2/sizes' }, function (err, body, res) { if (err || !body.sizes) { return callback(err || new Error('No flavors provided.')); } - + callback(null, body.sizes.map(function (result) { return new compute.Flavor(self, result); - }), res); + }), res); }); }; @@ -45,8 +45,8 @@ exports.getFlavor = function getFlavor(flavor, callback) { self = this; return this._request({ - path: '/sizes' - }, function (err, body, res) { + path: '/v2/sizes' + }, function (err, body) { if (err || !body.sizes) { return callback(err || new Error('No flavors found.')); } @@ -54,9 +54,10 @@ exports.getFlavor = function getFlavor(flavor, callback) { var flavor = body.sizes.filter(function (flavor) { return flavor.id == flavorId; })[0]; - + return !flavor ? callback(new Error('No flavor found with id: ' + flavorId)) - : callback(null, new Flavor(self, flavor)); + : callback(null, new compute.Flavor(self, flavor)); }); -}; \ No newline at end of file +}; + diff --git a/lib/pkgcloud/digitalocean/compute/client/images.js b/lib/pkgcloud/digitalocean/compute/client/images.js index 9a0bce4fc..b7618a088 100644 --- a/lib/pkgcloud/digitalocean/compute/client/images.js +++ b/lib/pkgcloud/digitalocean/compute/client/images.js @@ -1,25 +1,41 @@ /* * images.js: Implementation of DigitalOcean Images Client. * - * (C) 2012, Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var pkgcloud = require('../../../../../lib/pkgcloud'), base = require('../../../core/compute'), errs = require('errs'), + urlJoin = require('url-join'), compute = pkgcloud.providers.digitalocean.compute; // // ### function getImages (callback) +// #### @options {Object} Options when getting images +// #### @options.per_page {number} **Optional** Number of images to list +// #### @options.page {number} **Optional** Page number of images to return // #### @callback {function} f(err, images). `images` is an array that // represents the images that are available to your account // // Lists all images available to your account. // -exports.getImages = function getImages(callback) { - var self = this; +exports.getImages = function getImages(options, callback) { + if (!callback && typeof options === 'function') { + callback = options; + options = {}; + } + + var self = this, + per_page = options.per_page || 200, + page = options.page || 1; + return this._request({ - path: '/images' + path: '/v2/images', + qs: { + per_page: per_page, + page: page + } }, function (err, body, res) { if (err || !body.images) { return callback(err || new Error('No images found.')); @@ -41,10 +57,10 @@ exports.getImages = function getImages(callback) { // exports.getImage = function getImage(image, callback) { var imageId = image instanceof base.Image ? image.id : image, - self = this; + self = this; return this._request({ - path: '/images/' + imageId + path: urlJoin('/v2/images/', imageId) }, function (err, body, res) { return err ? callback(err) @@ -78,14 +94,14 @@ exports.createImage = function createImage(options, callback) { // Destroys an image in DigitalOcean // exports.destroyImage = function destroyImage(image, callback) { - var imageId = image instanceof base.Image ? image.id : image, - self = this; + var imageId = image instanceof base.Image ? image.id : image; return this._request({ - path: '/images/' + imageId + '/destroy' + path: urlJoin('/images/', imageId , '/destroy'), + method: 'DELETE' }, function (err, body, res) { return err ? callback(err) : callback(null, { ok: imageId }, res); }); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/digitalocean/compute/client/index.js b/lib/pkgcloud/digitalocean/compute/client/index.js index 9fd493816..f74726c6b 100644 --- a/lib/pkgcloud/digitalocean/compute/client/index.js +++ b/lib/pkgcloud/digitalocean/compute/client/index.js @@ -1,24 +1,25 @@ /* * index.js: Compute client for DigitalOcean API * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), - digitalocean = require('../../client'); + digitalocean = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { digitalocean.Client.call(this, options); - utile.mixin(this, require('./flavors')); - utile.mixin(this, require('./images')); - utile.mixin(this, require('./servers')); - utile.mixin(this, require('./keys')); + _.extend(this, require('./flavors')); + _.extend(this, require('./images')); + _.extend(this, require('./servers')); + _.extend(this, require('./keys')); }; -utile.inherits(Client, digitalocean.Client); +util.inherits(Client, digitalocean.Client); Client.prototype._getUrl = function (options) { options = options || {}; diff --git a/lib/pkgcloud/digitalocean/compute/client/keys.js b/lib/pkgcloud/digitalocean/compute/client/keys.js index 4e5cc493c..4bf6d5df5 100644 --- a/lib/pkgcloud/digitalocean/compute/client/keys.js +++ b/lib/pkgcloud/digitalocean/compute/client/keys.js @@ -1,12 +1,12 @@ /* * keys.js: Implementation of DigitalOcean SSH keys Client. * - * (C) 2012, Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var errs = require('errs'), - utile = require('utile'); + urlJoin = require('url-join'); // // ### function listKeys (callback) @@ -16,8 +16,8 @@ var errs = require('errs'), // exports.listKeys = function (callback) { return this._request({ - path: '/ssh_keys' - }, function (err, body, res) { + path: '/v2/account/keys' + }, function (err, body) { return err ? callback(err) : callback(null, body.ssh_keys); @@ -25,16 +25,16 @@ exports.listKeys = function (callback) { }; // -// ### function getKey (name, callback) -// #### @name {string} Name of the DigitalOcean SSH key to get +// ### function getKey (id, callback) +// #### @id {int} Name of the DigitalOcean SSH key to get // #### @callback {function} Continuation to respond to when complete. // -// Gets the details of the DigitalOcean SSH Key with the specified `name`. +// Gets the details of the DigitalOcean SSH Key with the specified `id`. // -exports.getKey = function (name, callback) { +exports.getKey = function (id, callback) { return this._request({ - path: '/ssh_keys/' + name - }, function (err, body, res) { + path: urlJoin('/v2/account/keys', id) + }, function (err, body) { return err ? callback(err) : callback(null, body.ssh_key); @@ -59,12 +59,13 @@ exports.addKey = function (options, callback) { } return this._request({ - path: '/ssh_keys/new', - qs: { + path: '/v2/account/keys', + method: 'POST', + body: { name: options.name, ssh_pub_key: options.key } - }, function (err, body, res) { + }, function (err) { return err ? callback(err) : callback(null, true); @@ -73,17 +74,18 @@ exports.addKey = function (options, callback) { // // ### function getKey (name, callback) -// #### @name {string} Name of the DigitalOcean SSH key to destroy +// #### @id {int} Name of the DigitalOcean SSH key to destroy // #### @callback {function} Continuation to respond to when complete. // -// Destroys DigitalOcean SSH Key with the specified `name`. +// Destroys DigitalOcean SSH Key with the specified `id`. // -exports.destroyKey = function (name, callback) { +exports.destroyKey = function (id, callback) { return this._request({ - path: '/ssh_keys/' + name + '/destroy', - }, function (err, body, res) { + path: urlJoin('/v2/account/keys', id), + method: 'DELETE', + }, function (err) { return err ? callback(err) : callback(null, true); }); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/digitalocean/compute/client/servers.js b/lib/pkgcloud/digitalocean/compute/client/servers.js index 235b788b5..db4a14ce6 100644 --- a/lib/pkgcloud/digitalocean/compute/client/servers.js +++ b/lib/pkgcloud/digitalocean/compute/client/servers.js @@ -1,15 +1,19 @@ /* * servers.js: Instance methods for working with servers from DigitalOcean * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var request = require('request'), - base = require('../../../core/compute'), +var base = require('../../../core/compute'), pkgcloud = require('../../../../../lib/pkgcloud'), errs = require('errs'), + urlJoin = require('url-join'), compute = pkgcloud.providers.digitalocean.compute; +function _getServerId(server) { + return (server instanceof base.Server ? server.id : server) + ''; +} + // // ### function getVersion (callback) // #### @callback {function} f(err, version). @@ -18,7 +22,7 @@ var request = require('request'), // exports.getVersion = function getVersion(callback) { return errs.handle( - errs.create({ message: "DigitalOcean's API does not support versioning" }), + errs.create({ message: 'DigitalOcean\'s API does not support versioning' }), callback ); }; @@ -31,7 +35,7 @@ exports.getVersion = function getVersion(callback) { // exports.getLimits = function getLimits(callback) { return errs.handle( - errs.create({ message: "DigitalOcean's API is not rate limited" }), + errs.create({ message: 'DigitalOcean\'s API is not rate limited' }), callback ); }; @@ -39,8 +43,8 @@ exports.getLimits = function getLimits(callback) { // // ### function getServers (callback) // #### @options {Object} Options when getting servers -// #### @options.offset {number} Number of servers to skip when listing -// #### @options.limit {number} Number of servers to return +// #### @options.per_page {number} **Optional** Number of servers to skip when listing +// #### @options.page {number} **Optional** Number of servers to return // #### @callback {function} f(err, servers). `servers` is an array that // represents the servers that are available to your account // @@ -49,14 +53,20 @@ exports.getLimits = function getLimits(callback) { exports.getServers = function getServers(options, callback) { if (!callback && typeof options === 'function') { callback = options; - options = null; + options = {}; } - var self = this; + var self = this, + per_page = options.per_page || 200, + page = options.page || 1; + return this._request( { - path: '/droplets', - qs: options + path: '/v2/droplets', + qs: { + per_page: per_page, + page: page + } }, function (err, body, res) { if (err) { @@ -73,11 +83,15 @@ exports.getServers = function getServers(options, callback) { // // ### function createServer (options, callback) // #### @opts {Object} **Optional** options -// #### @name {String} **Optional** a name for your server +// #### @name {String} a name for your server // #### @flavor {String|Flavor} **Optional** flavor to use for this image // #### @image {String|Image} **Optional** the image to use // #### @required {Boolean} **Optional** Validate if flavor, name, // and image are present +// #### @ipv6 {Boolean} **Optional** Enable IPv6 +// #### @private_networking {Boolean} **Optional** Enable private networking +// #### @backups {Boolean} **Optional** Enable backups +// #### @user_data {String} **Optional** Provide cloud-init user-data // #### @* {*} **Optional** Anything platform specific // #### @callback {Function} f(err, server). // @@ -95,8 +109,9 @@ exports.createServer = function createServer(options, callback) { var self = this, createOptions = { - path: '/droplets/new', - qs: {} + method: 'POST', + path: '/v2/droplets', + body: {} }; ['flavor', 'image', 'name'].forEach(function (member) { @@ -108,13 +123,13 @@ exports.createServer = function createServer(options, callback) { } }); - createOptions.qs.name = options.name; - createOptions.qs.region_id = options.region || options.region_id || 1; - createOptions.qs.size_id = options.flavor instanceof base.Flavor + createOptions.body.name = options.name; + createOptions.body.region = options.region || options.region_id || 'nyc3'; + createOptions.body.size = options.flavor instanceof base.Flavor ? options.flavor.id : options.flavor; - createOptions.qs.image_id = options.image instanceof base.Image + createOptions.body.image = options.image instanceof base.Image ? options.image.id : options.image; @@ -123,14 +138,34 @@ exports.createServer = function createServer(options, callback) { // which can be a single string or an Array. // if (options.keyname) { - createOptions.qs.ssh_key_ids = options.keyname; + createOptions.body.ssh_keys = options.keyname; } else if (options.keynames) { - createOptions.qs.ssh_key_ids = Array.isArray(options.keynames) + createOptions.body.ssh_keys = Array.isArray(options.keynames) ? options.keynames.join(',') : options.keynames; } + + // + // DigitalOcean specific options. + // + if (options.ipv6) { + createOptions.body.ipv6 = options.ipv6; + } + + if (options.private_networking) { + createOptions.body.private_networking = options.private_networking; + } + + if (options.backups) { + createOptions.body.backups = options.backups; + } + + if (options.user_data) { + createOptions.body.user_data = options.user_data; + } + return this._request(createOptions, function (err, body, res) { return err ? callback(err) @@ -150,8 +185,7 @@ exports.createServer = function createServer(options, callback) { // Destroy a server in DigitalOcean. // exports.destroyServer = function destroyServer(server, options, callback) { - var serverId = server instanceof base.Server ? server.id : server, - self = this; + var serverId = _getServerId(server); if (typeof options === 'function') { callback = options; @@ -159,12 +193,8 @@ exports.destroyServer = function destroyServer(server, options, callback) { } this._request({ - path: '/droplets/' + serverId + '/destroy', - qs: { - scrub_data: (typeof options.scrubData === 'boolean') - ? (options.scrubData ? '1' : '0') - : '1' - } + method: 'DELETE', + path: urlJoin('/v2/droplets/', serverId) }, function (err, body, res) { return err ? callback(err) : callback(null, { ok: serverId }, res); }); @@ -178,11 +208,11 @@ exports.destroyServer = function destroyServer(server, options, callback) { // Gets a server in DigitalOcean. // exports.getServer = function getServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server, + var serverId = _getServerId(server), self = this; return this._request({ - path: '/droplets/' + serverId + path: urlJoin('/v2/droplets/', serverId) }, function (err, body, res) { return !err ? callback(null, new compute.Server(self, body.droplet), res) @@ -201,7 +231,9 @@ exports.getServer = function getServer(server, callback) { exports.rebootServer = function rebootServer(server, callback) { var serverId = server instanceof base.Server ? server.id : server; return this._request({ - path: '/droplets/' + serverId + '/reboot' + method: 'POST', + path: urlJoin('/v2/droplets/', serverId, '/actions'), + body: { type: 'reboot' }, }, function (err, body, res) { return err ? callback(err) @@ -220,8 +252,12 @@ exports.rebootServer = function rebootServer(server, callback) { exports.renameServer = function renameServer(server, name, callback) { var serverId = server instanceof base.Server ? server.id : server; return this._request({ - path: '/droplets/' + serverId + '/rename', - qs: { name: name } + method: 'POST', + path: urlJoin('/v2/droplets/', serverId, '/actions'), + body: { + type: 'rename', + name: name + }, }, function (err, body, res) { return err ? callback(err) diff --git a/lib/pkgcloud/digitalocean/compute/flavor.js b/lib/pkgcloud/digitalocean/compute/flavor.js index f585d3976..bce1c5426 100644 --- a/lib/pkgcloud/digitalocean/compute/flavor.js +++ b/lib/pkgcloud/digitalocean/compute/flavor.js @@ -1,30 +1,30 @@ /* * flavor.js: DigitalOcean Server "Size" * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/compute/flavor'); var Flavor = exports.Flavor = function Flavor(client, details) { base.Flavor.call(this, client, details); }; -utile.inherits(Flavor, base.Flavor); +util.inherits(Flavor, base.Flavor); Flavor.prototype._setProperties = function (details) { - this.id = details.id; - this.name = details.name; + this.id = details.slug; + this.name = details.slug; this.ram = details.memory; this.disk = details.disk; - + // // DigitalOcean specific // - this.cpu = details.cpu; - this.costPerHour = details.cost_per_hour; - this.costPerMonth = details.cost_per_month; + this.cpu = details.vcpus; + this.costPerHour = details.price_hourly; + this.costPerMonth = details.price_monthly; this.original = this.digitalocean = details; }; \ No newline at end of file diff --git a/lib/pkgcloud/digitalocean/compute/image.js b/lib/pkgcloud/digitalocean/compute/image.js index 57c7429c7..be1b1530f 100644 --- a/lib/pkgcloud/digitalocean/compute/image.js +++ b/lib/pkgcloud/digitalocean/compute/image.js @@ -1,26 +1,29 @@ /* * image.js: DigitalOcean Image * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/compute/image'); var Image = exports.Image = function Image(client, details) { base.Image.call(this, client, details); }; -utile.inherits(Image, base.Image); +util.inherits(Image, base.Image); Image.prototype._setProperties = function (details) { this.id = details.id; this.name = details.name; + this.created = details.created_at; // // DigitalOcean specific // this.distribution = details.distribution; + this.public = details.public; + this.slug = details.slug; this.original = this.digitalocean = details; }; \ No newline at end of file diff --git a/lib/pkgcloud/digitalocean/compute/index.js b/lib/pkgcloud/digitalocean/compute/index.js index 36815810c..7878ea5ea 100644 --- a/lib/pkgcloud/digitalocean/compute/index.js +++ b/lib/pkgcloud/digitalocean/compute/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the DigitalOcean compute module * - * (C) 2012 Nodejitsu Inc. + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/digitalocean/compute/server.js b/lib/pkgcloud/digitalocean/compute/server.js index c7fc2c9a3..0d7c72122 100644 --- a/lib/pkgcloud/digitalocean/compute/server.js +++ b/lib/pkgcloud/digitalocean/compute/server.js @@ -1,33 +1,51 @@ /* * server.js: DigitalOcean Server * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - compute = require('../../core/compute'), +var util = require('util'), base = require('../../core/compute/server'); var Server = exports.Server = function Server(client, details) { base.Server.call(this, client, details); }; -utile.inherits(Server, base.Server); +util.inherits(Server, base.Server); Server.prototype._setProperties = function (details) { + var self = this; + + function getAddresses(networks) { + networks.forEach(function (network) { + self.addresses[network.type].push(network.ip_address); + }); + } + this.id = details.id; this.name = details.name; - this.imageId = details.image_id; - this.flavorId = details.size_id; + this.imageId = details.image.id; + this.flavorId = details.size_slug; this.addresses = { - public: [details.ip_address], + public: [], private: [] }; + if (details.networks.v4) { + getAddresses(details.networks.v4); + } + + if (details.networks.v6) { + getAddresses(details.networks.v6); + } + switch (details.status && details.status.toUpperCase()) { case 'ACTIVE': - this.status = "RUNNING"; + this.status = 'RUNNING'; + break; + case 'OFF': + this.status = this.STATUS.stopped; break; case 'NEW': default: @@ -38,5 +56,7 @@ Server.prototype._setProperties = function (details) { // DigitalOcean specific // this.region = details.region_id; + this.created = details.created_at; this.original = this.digitalocean = details; }; + diff --git a/lib/pkgcloud/digitalocean/index.js b/lib/pkgcloud/digitalocean/index.js index a29a0083d..b54da38e5 100644 --- a/lib/pkgcloud/digitalocean/index.js +++ b/lib/pkgcloud/digitalocean/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the DigitalOcean module. * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/google/client.js b/lib/pkgcloud/google/client.js new file mode 100644 index 000000000..364f5a541 --- /dev/null +++ b/lib/pkgcloud/google/client.js @@ -0,0 +1,21 @@ +/* + * client.js: Base client from which all Google Cloud Storage clients inherit from + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var util = require('util'), + base = require('../core/base'); + +var Client = exports.Client = function (options) { + base.Client.call(this, options); + + options = options || {}; + + this.provider = 'google'; + this.config.keyFilename = this.config.keyFilename || options.keyFilename; + this.config.projectId = this.config.projectId || options.projectId; +}; + +util.inherits(Client, base.Client); diff --git a/lib/pkgcloud/google/index.js b/lib/pkgcloud/google/index.js new file mode 100644 index 000000000..c0ab50782 --- /dev/null +++ b/lib/pkgcloud/google/index.js @@ -0,0 +1,8 @@ +/* + * index.js: Top-level include for the Google Cloud Storage module. + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +exports.storage = require('./storage'); diff --git a/lib/pkgcloud/google/storage/client/containers.js b/lib/pkgcloud/google/storage/client/containers.js new file mode 100644 index 000000000..67bd4b8ee --- /dev/null +++ b/lib/pkgcloud/google/storage/client/containers.js @@ -0,0 +1,121 @@ +/* + * containers.js: Instance methods for working with containers from Google Cloud Storage + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var async = require('async'), + pkgcloud = require('../../../../../lib/pkgcloud'), + storage = pkgcloud.providers.google.storage; + +/** + * Get all Google Cloud Storage containers for this instance. + * + * @param {function} callback - Continuation to respond to when complete. + */ +exports.getContainers = function (callback) { + var self = this, + containers = []; + + function handleResponse(err, buckets, nextQuery) { + if (err) { + callback(err); + return; + } + + buckets.forEach(function(container) { + containers.push(new (storage.Container)(self, container)); + }); + + if (nextQuery) { + self.storage.getBuckets(nextQuery, handleResponse); + return; + } + + callback(err, containers); + } + + self.storage.getBuckets(handleResponse); +}; + +/** + * Responds with the Google Cloud Storage bucket for the specified container. + * + * @param {string|storage.Container} container - The container to return. + * @param {function} callback - Continuation to respond to when complete. + */ +exports.getContainer = function (container, callback) { + var self = this, + bucket = this._getBucket(container); + + bucket.getMetadata(function(err) { + return err + ? callback(err) + : callback(null, new (storage.Container)(self, bucket)); + }); +}; + +/** + * Creates the specified `container` in the Google Cloud Storage account + * associated with this instance. + * + * @param {string|storage.Container} container - The container to create. + * @param {function} callback - Continuation to respond to when complete. + */ +exports.createContainer = function (options, callback) { + var self = this, + bucketName = this._getBucket(options).name; + + self.storage.createBucket(bucketName, function(err, bucket) { + return err + ? callback(err) + : callback(null, new (storage.Container)(self, bucket)); + }); +}; + +/** + * Destroys the specified container and all files in it. + * + * @param {string|storage.Container} container - The container to destroy. + * @param {function} callback - Continuation to respond to when complete. + */ +exports.destroyContainer = function (container, callback) { + var bucket = this._getBucket(container); + + function deleteContainer() { + bucket.delete(function(err) { + return err + ? callback(err) + : callback(null, true); + }); + } + + function destroyFile(file, next) { + file.delete(next); + } + + function deleteFiles(files, next) { + async.forEachLimit(files, 10, destroyFile, next); + } + + function handleResponse(err, files, nextQuery) { + if (err) { + return callback(err); + } + + if (files.length > 0) { + deleteFiles(files, function() { + if (nextQuery) { + bucket.getFiles(nextQuery, handleResponse); + } else { + deleteContainer(); + } + }); + } else { + deleteContainer(); + } + } + + bucket.getFiles(handleResponse); +}; diff --git a/lib/pkgcloud/google/storage/client/files.js b/lib/pkgcloud/google/storage/client/files.js new file mode 100644 index 000000000..b41567c1e --- /dev/null +++ b/lib/pkgcloud/google/storage/client/files.js @@ -0,0 +1,117 @@ +/* + * files.js: Instance methods for working with files from Google Cloud Storage + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var pkgcloud = require('../../../../../lib/pkgcloud'), + storage = pkgcloud.providers.google.storage, + _ = require('lodash'); + +/** + * Destroy a file in the specified container. + * + * @param {string} container - Name of the container to destroy the file in. + * @param {string} file - Name of the file to destroy. + * @param {function} callback - Continuation to respond to when complete. + */ +exports.removeFile = function (container, file, callback) { + var bucket = this._getBucket(container), + file = this._getFile(bucket, file); + + file.delete(function(err) { + return err + ? callback(err) + : callback(null, true); + }); +}; + +/** + * Upload a file to the specified bucket. + * + * @param {object} options - Configuration object. + * @param {object} options.container - Container object for the file. + * @param {object|string} options.file - The file to upload content to. + * @return {stream} + */ +exports.upload = function (options) { + var self = this, + bucket = this._getBucket(options), + file = this._getFile(bucket, options), + metadata = { contentType: options.contentType }; + + // check for deprecated calling with a callback + if (typeof arguments[arguments.length - 1] === 'function') { + self.emit('log::warn', 'storage.upload no longer supports calling with a callback'); + } + + // GCS implicitly detects mime-type if none was implicitly given via options + var writableStream = file.createWriteStream({ metadata }); + + // return a file model via the 'success' event + writableStream.on('finish', function() { + writableStream.emit('success', new storage.File(self, file)); + }); + + return writableStream; +}; + +/** + * Download a file from the specified bucket. + * + * @param {object} options - Configuration object. + * @param {object} options.container - Container object for the file. + * @param {object|string} options.file - The file to upload content to. + * @return {stream} + */ +exports.download = function (options) { + var bucket = this._getBucket(options), + file = this._getFile(bucket, options); + + return file.createReadStream(options); +}; + +exports.getFile = function (container, file, callback) { + var self = this, + bucket = this._getBucket(container), + file = this._getFile(bucket, file); + + file.getMetadata(function(err, data) { + return err + ? callback(err) + : callback(null, new storage.File(self, _.extend({ metadata: data }, { + container: container + }))); + }); +}; + +/** + * Get all of the files from a gcloud bucket. + * + * @param {object} container - Container object for the file. + * @param {object=|function} options - Options or callback. + * @param {string} options.maxResults - Maximum amount of results to fetch. + * @param {function} callback - Continuation to respond to when complete. + */ +exports.getFiles = function (container, options, callback) { + var self = this, + bucket = this._getBucket(container); + + if (typeof options === 'function') { + callback = options; + options = {}; + } + else if (!options) { + options = {}; + } + + bucket.getFiles(options, function(err, files, nextQuery) { + return err + ? callback(err) + : callback(null, files.map(function (file) { + file.container = container; + return new storage.File(self, file); + }), nextQuery); + }); +}; diff --git a/lib/pkgcloud/google/storage/client/index.js b/lib/pkgcloud/google/storage/client/index.js new file mode 100644 index 000000000..03b36f5af --- /dev/null +++ b/lib/pkgcloud/google/storage/client/index.js @@ -0,0 +1,56 @@ +/* + * client.js: Storage client for Google Cloud Storage + * + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +const util = require('util'); +const google = require('../../client'); +const _ = require('lodash'); +const pkgcloud = require('../../../../../lib/pkgcloud'); +const { Storage } = require('@google-cloud/storage'); + +var Client = exports.Client = function (options) { + google.Client.call(this, options); + + _.extend(this, require('./containers')); + _.extend(this, require('./files')); + + this.storage = new Storage(options); +}; + +util.inherits(Client, google.Client); + +/** + * Return a gcloud Bucket instance after detecting its name from a variety of + * parameter types. + * + * @param {object|string} container - A descriptor for a gcloud Bucket. + * @return {gcloud:bucket} + */ +Client.prototype._getBucket = function (container) { + container = container.container || container; + + var storage = pkgcloud.providers.google.storage, + containerName = container instanceof storage.Container ? container.name : container; + + return this.storage.bucket(containerName || container); +}; + +/** + * Return a gcloud File instance after detecting its name from a variety of + * parameter types. + * + * @param {gcloud:bucket} bucket - A gcloud Bucket instance, which contains the file. + * @param {object|string} file - A descriptor for a gcloud File. + * @return {gcloud:file} + */ +Client.prototype._getFile = function (bucket, file) { + file = file.file || file.remote || file; + + var storage = pkgcloud.providers.google.storage, + fileName = file instanceof storage.File ? file.name : file; + + return bucket.file(fileName || file); +}; diff --git a/lib/pkgcloud/google/storage/container.js b/lib/pkgcloud/google/storage/container.js new file mode 100644 index 000000000..ea609f227 --- /dev/null +++ b/lib/pkgcloud/google/storage/container.js @@ -0,0 +1,26 @@ +/* + * container.js: Google Cloud Storage Bucket + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var util = require('util'), + base = require('../../core/storage/container'), + _ = require('lodash'); + +var Container = exports.Container = function Container(client, details) { + base.Container.call(this, client, details); +}; + +util.inherits(Container, base.Container); + +Container.prototype._setProperties = function (bucket) { + this.name = bucket.name; + this.metadata = bucket.metadata; + _.extend(this, bucket.metadata); +}; + +Container.prototype.toJSON = function () { + return this.metadata; +}; diff --git a/lib/pkgcloud/google/storage/file.js b/lib/pkgcloud/google/storage/file.js new file mode 100644 index 000000000..bbad7f10c --- /dev/null +++ b/lib/pkgcloud/google/storage/file.js @@ -0,0 +1,30 @@ +/* + * container.js: Google Cloud Storage File + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var util = require('util'), + base = require('../../core/storage/file'), + _ = require('lodash'); + +var File = exports.File = function File(client, details) { + base.File.call(this, client, details); +}; + +util.inherits(File, base.File); + +File.prototype._setProperties = function (file) { + this.name = file.name; + this.metadata = file.metadata; + _.extend(this, file.metadata); + + if (this.size) { + this.size = parseInt(this.size, 10); + } +}; + +File.prototype.toJSON = function () { + return this.metadata; +}; \ No newline at end of file diff --git a/lib/pkgcloud/google/storage/index.js b/lib/pkgcloud/google/storage/index.js new file mode 100644 index 000000000..c87d08a46 --- /dev/null +++ b/lib/pkgcloud/google/storage/index.js @@ -0,0 +1,14 @@ +/* + * index.js: Top-level include for the Google Cloud Storage module + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +exports.Client = require('./client').Client; +exports.Container = require('./container').Container; +exports.File = require('./file').File; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/hp/client.js b/lib/pkgcloud/hp/client.js new file mode 100644 index 000000000..88e4fe822 --- /dev/null +++ b/lib/pkgcloud/hp/client.js @@ -0,0 +1,37 @@ +/* + * client.js: Base client from which all HP clients inherit from + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + identity = require('./identity'), + base = require('../openstack/client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + options = options || {}; + + if (!options.authUrl){ + throw new Error('authUrl is invalid'); + } + + options.identity = identity.Identity; + + if (typeof options.useServiceCatalog === 'undefined') { + options.useServiceCatalog = true; + } + + base.Client.call(this, options); + + this.provider = 'hp'; +}; + +util.inherits(Client, base.Client); + +Client.prototype._getIdentityOptions = function() { + return _.extend({ + apiKey: this.config.apiKey + }, Client.super_.prototype._getIdentityOptions.call(this)); +}; diff --git a/lib/pkgcloud/hp/compute/client/index.js b/lib/pkgcloud/hp/compute/client/index.js new file mode 100644 index 000000000..ad2484097 --- /dev/null +++ b/lib/pkgcloud/hp/compute/client/index.js @@ -0,0 +1,29 @@ +/* + * client.js: Compute client for HP Cloudservers + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +var util = require('util'), + hp = require('../../client'), + ComputeClient = require('../../../openstack/compute/computeClient').ComputeClient, + _ = require('lodash'); + +var Client = exports.Client = function (options) { + hp.Client.call(this, options); + _.extend(this, require('../../../openstack/compute/client/flavors')); + _.extend(this, require('../../../openstack/compute/client/images')); + _.extend(this, require('../../../openstack/compute/client/servers')); + _.extend(this, require('../../../openstack/compute/client/extensions/keys')); + _.extend(this, require('../../../openstack/compute/client/extensions/floating-ips')); + _.extend(this, require('../../../openstack/compute/client/extensions/keys')); + _.extend(this, require('../../../openstack/compute/client/extensions/security-groups')); + _.extend(this, require('../../../openstack/compute/client/extensions/servers')); + + this.serviceType = 'compute'; +}; + +util.inherits(Client, hp.Client); +_.extend(Client.prototype, ComputeClient.prototype); diff --git a/lib/pkgcloud/hp/compute/index.js b/lib/pkgcloud/hp/compute/index.js new file mode 100644 index 000000000..0eef07471 --- /dev/null +++ b/lib/pkgcloud/hp/compute/index.js @@ -0,0 +1,16 @@ + /* + * index.js: Top-level include for the Rackspace storage module + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +exports.Client = require('./client').Client; +exports.Flavor = require('../../openstack/compute/flavor').Flavor; +exports.Image = require('../../openstack/compute/image').Image; +exports.Server = require('../../openstack/compute/server').Server; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/hp/database/client/index.js b/lib/pkgcloud/hp/database/client/index.js new file mode 100644 index 000000000..0cefae2a0 --- /dev/null +++ b/lib/pkgcloud/hp/database/client/index.js @@ -0,0 +1,61 @@ +/* + * client.js: Database client for HP Trove Databases + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + urlJoin = require('url-join'), + hp = require('../../client'), + auth = require('../../../common/auth.js'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + hp.Client.call(this, options); + + this.before.push(auth.accountId); + + _.extend(this, require('../../../openstack/database/client/flavors')); + _.extend(this, require('../../../openstack/database/client/instances')); + _.extend(this, require('../../../openstack/database/client/databases')); + _.extend(this, require('../../../openstack/database/client/users')); + + this.serviceType = 'database'; +}; + +util.inherits(Client, hp.Client); + +Client.prototype._getUrl = function (options) { + options = options || {}; + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); + +}; + +// +// Gets the version of the OpenStack Compute API we are running against +// Parameters: callback +// +Client.prototype.getVersion = function getVersion(callback) { + var self = this; + + this.auth(function (err) { + if (err) { + return callback(err); + } + + self._request({ + uri: self._getUrl('/').replace('/v1.0/' + self._identity.token.tenant.id + '/', '') + }, function (err, body) { + if (err) { + return callback(err); + } + return callback(null, + ((typeof body === 'object') ? body.versions : JSON.parse(body).versions)); + }); + }); +}; diff --git a/lib/pkgcloud/hp/database/index.js b/lib/pkgcloud/hp/database/index.js new file mode 100644 index 000000000..36f968345 --- /dev/null +++ b/lib/pkgcloud/hp/database/index.js @@ -0,0 +1,16 @@ +/* + * index.js: Top-level include for the HP database module + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +exports.Client = require('./client').Client; +exports.Flavor = require('../../openstack/database/flavor').Flavor; +exports.Instance = require('../../openstack/database/instance').Instance; +exports.Database = require('../../openstack/database/database').Database; +exports.User = require('../../openstack/database/user').User; + +exports.createClient = function createClient(options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/hp/identity/hpIdentity.js b/lib/pkgcloud/hp/identity/hpIdentity.js new file mode 100644 index 000000000..89d340011 --- /dev/null +++ b/lib/pkgcloud/hp/identity/hpIdentity.js @@ -0,0 +1,50 @@ +/* + * hpIdentity.js: hpIdentity model + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +var identity = require('../../openstack/context'), + events = require('eventemitter2'), + Identity = identity.Identity, + util = require('util'); + +var HPIdentity = exports.Identity = function (options) { + this.options = options; + this.name = 'HPIdentity'; + + this.basePath = options.basePath || '/v2.0/tokens'; + this.useServiceCatalog = (typeof options.useServiceCatalog === 'boolean') + ? options.useServiceCatalog + : true; + + events.EventEmitter2.call(this, { delimiter: '::', wildcard: true }); +}; + +util.inherits(HPIdentity, events.EventEmitter2); +util.inherits(HPIdentity, Identity); + +HPIdentity.prototype._buildAuthenticationPayload = function () { + var self = this; + + HPIdentity.super_.prototype._buildAuthenticationPayload.call(this); + + this.emit('log::trace', 'Building HP Identity Auth Payload'); + + if (!self._authenticationPayload) { + // setup our inputs for authorization + // access key & secret key + if (self.options.apiKey && self.options.username) { + self._authenticationPayload = { + auth: { + 'apiAccessKeyCredentials': { + 'accessKey': self.options.username, + 'secretKey': self.options.apiKey + } + } + }; + } + } +}; diff --git a/lib/pkgcloud/hp/identity/index.js b/lib/pkgcloud/hp/identity/index.js new file mode 100644 index 000000000..54c592b69 --- /dev/null +++ b/lib/pkgcloud/hp/identity/index.js @@ -0,0 +1,9 @@ +/* + * index.js: Identity models + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +module.exports = require('./hpIdentity'); diff --git a/lib/pkgcloud/hp/index.js b/lib/pkgcloud/hp/index.js new file mode 100644 index 000000000..f725ea2df --- /dev/null +++ b/lib/pkgcloud/hp/index.js @@ -0,0 +1,12 @@ +/* + * index.js: Top-level include for the HP module. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +exports.storage = require('./storage'); +exports.compute = require('./compute'); +exports.network = require('./network'); +exports.database = require('./database'); diff --git a/lib/pkgcloud/hp/network/client/index.js b/lib/pkgcloud/hp/network/client/index.js new file mode 100644 index 000000000..a48370179 --- /dev/null +++ b/lib/pkgcloud/hp/network/client/index.js @@ -0,0 +1,51 @@ +/* + * client.js: Client for HP networking + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + urlJoin = require('url-join'), + hp = require('../../client'), + NetworkClient = require('../../../openstack/network/networkClient').NetworkClient, + _ = require('lodash'); + +var Client = exports.Client = function (options) { + hp.Client.call(this, options); + + this.models = { + Network: require('../../../openstack/network/network').Network, + Subnet: require('../../../openstack/network/subnet').Subnet, + Port: require('../../../openstack/network/port').Port, + SecurityGroup: require('../../../openstack/network/securityGroup').SecurityGroup, + SecurityGroupRule: require('../../../openstack/network/securityGroupRule').SecurityGroupRule + }; + + _.extend(this, require('../../../openstack/network/client/networks')); + _.extend(this, require('../../../openstack/network/client/subnets')); + _.extend(this, require('../../../openstack/network/client/ports')); + _.extend(this, require('../../../openstack/network/client/securityGroups')); + _.extend(this, require('../../../openstack/network/client/securityGroupRules')); + + this.serviceType = 'network'; +}; + +util.inherits(Client, hp.Client); +_.extend(Client.prototype, NetworkClient.prototype); + +/** + * client._getUrl + * + * @description get the url for the current networking service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function(options) { + if (options.path) { + options.path = urlJoin('v2.0', options.path); + } + return NetworkClient.prototype._getUrl.call(this, options); +}; diff --git a/lib/pkgcloud/hp/network/index.js b/lib/pkgcloud/hp/network/index.js new file mode 100644 index 000000000..b11932fee --- /dev/null +++ b/lib/pkgcloud/hp/network/index.js @@ -0,0 +1,17 @@ +/* + * index.js: Top-level include for the HP networking client. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +exports.Client = require('./client').Client; +exports.Network = require('../../openstack/network/network').Network; +exports.Subnet = require('../../openstack/network/subnet').Subnet; +exports.Port = require('../../openstack/network/port').Port; +exports.SecurityGroup = require('../../openstack/network/securityGroup').SecurityGroup; +exports.SecurityGroupRule = require('../../openstack/network/securityGroupRule').SecurityGroupRule; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/hp/storage/client/index.js b/lib/pkgcloud/hp/storage/client/index.js new file mode 100644 index 000000000..3826d3975 --- /dev/null +++ b/lib/pkgcloud/hp/storage/client/index.js @@ -0,0 +1,29 @@ +/* + * index.js: Storage client for HP Cloudservers + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +var util = require('util'), + hp = require('../../client'), + StorageClient = require('../../../openstack/storage/storageClient').StorageClient, + _ = require('lodash'); + +var Client = exports.Client = function (options) { + hp.Client.call(this, options); + + this.models = { + Container: require('../../../openstack/storage/container').Container, + File: require('../../../openstack/storage/file').File + }; + + _.extend(this, require('../../../openstack/storage/client/containers')); + _.extend(this, require('../../../openstack/storage/client/files')); + + this.serviceType = 'object-store'; +}; + +util.inherits(Client, hp.Client); +_.extend(Client.prototype, StorageClient.prototype); diff --git a/lib/pkgcloud/hp/storage/index.js b/lib/pkgcloud/hp/storage/index.js new file mode 100644 index 000000000..524da3a56 --- /dev/null +++ b/lib/pkgcloud/hp/storage/index.js @@ -0,0 +1,15 @@ + /* + * index.js: Top-level include for the HP storage module + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * Phani Raj + * + */ + +exports.Client = require('./client').Client; +exports.Container = require('../../openstack/storage/container').Container; +exports.File = require('../../openstack/storage/file').File; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/iriscouch/database/client/index.js b/lib/pkgcloud/iriscouch/database/client/index.js deleted file mode 100644 index be65f4707..000000000 --- a/lib/pkgcloud/iriscouch/database/client/index.js +++ /dev/null @@ -1,191 +0,0 @@ -/* - * client.js: Database client for Iriscouch Cloud Databases - * - * (C) 2011 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - request = require('request'), - pkgcloud = require('../../../../pkgcloud'), - errs = require('errs'); - -var Client = exports.Client = function (options) { - this.username = options.username; - this.password = options.password; - - this.protocol = options.protocol || 'https://'; - this.databaseUrl = options.databaseUrl || 'hosting.iriscouch.com/hosting_public'; -}; - -Client.prototype.create = function (attrs, callback) { - // Check for options. - if (!attrs || typeof attrs === 'function') { - return errs.handle(errs.create({ - message: 'Options required for create a database.' - }), Array.prototype.slice.call(arguments).pop()); - } - // Check for obligatory fields - if (!attrs['first_name'] || !attrs['last_name']) { - return errs.handle(errs.create({ - message: 'Options. first_name and last_name are required arguments' - }), Array.prototype.slice.call(arguments).pop()); - } - - if (!attrs['subdomain'] || !attrs['email']) { - return errs.handle(errs.create({ - message: 'Options. subdomain and email are required arguments' - }), Array.prototype.slice.call(arguments).pop()); - } - - // If is a redis provisioning request so we have to define a password - if (attrs['type'] && attrs['type'] === 'redis' && !attrs['password']) { - return errs.handle(errs.create({ - message: 'Options. password for redis is a required argument' - }), Array.prototype.slice.call(arguments).pop()); - } - - var self = this, - couch = { - // The ID needs the prefix of the type of database - _id: ((attrs['type'] && - attrs['type'] === 'redis') ? "Redis/" : "Server/") + attrs.subdomain, - partner: this.username, - creation: { - "first_name": attrs.first_name, - "last_name": attrs.last_name, - "email": attrs.email, - "subdomain": attrs.subdomain - } - }; - - // When redis so we have to add the password - if (attrs['type'] && attrs['type'] === 'redis') { - couch.creation.password = attrs['password']; - } - - var options = { - uri : this._getUrl(), - method : 'POST', - body : JSON.stringify(couch), - followRedirect: false, - headers: { - 'Content-Type': 'application/json', - 'Authorization': "Basic " + utile.base64.encode(this.username + ':' + this.password), - 'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version) - } - }; - - request(options, function (err, response, body) { - if (err) { - return callback(err); - } - - if (typeof body === 'string') { - try { body = JSON.parse(body) } - catch (ex) { } - } - - if (response.statusCode === 201) { - if (body.ok === true) { - // - // For Redis we dont have any polling method yet, so just trust on iriscouch for provisioning correctly - // - if (attrs['type'] && attrs['type'] === 'redis') { - var subdomain = body.id.split('/').pop(); - callback(err, { - id: subdomain, - port: 6379, - host: subdomain + '.redis.irstack.com', - uri: 'redis://' + subdomain + '.redis.irstack.com/', - username: '', - password: subdomain + '.redis.irstack.com:' + attrs['password'] - }); - } else { - // - // Remark: Begin polling iriscouch to determine when the couch database is ready. - // - self._checkCouch(attrs.subdomain, function (err, response) { - response.subdomain = attrs.subdomain; - var database = self.formatResponse(response); - callback(err, database); - }); - } - } - else { - callback("There was an issue creating the couch", { "created": false }); - } - } - else if (response.statusCode === 403 || response.statusCode === 401 || response.statusCode === 302) { - callback("incorrect partner name or password.", { "created": false }); - } - else if (response.statusCode === 409) { - callback("subdomain is already taken.", { "created": false }); - } - else { - callback("unknown error", { "created": false }); - } - }); -}; - -Client.prototype.formatResponse = function (response) { - var database = { - id: response.subdomain, - port: 6984, - host: response.subdomain + '.iriscouch.com', - uri: 'https://' + response.subdomain + '.iriscouch.com:6984/', - username: '', - password: '' - }; - return database; -}; - -Client.prototype._getUrl = function () { - return this.protocol + this.databaseUrl; -}; - -Client.prototype._checkCouch = function (couchName, callback) { - // - // Remark: Poll the couch with a GET every interval to determine if couch is up yet - // We perform a poll since there is no real database available notification event from couchone - // - - var interval = 4000, - maxAttempts = 20, - count = 0, - options = { - uri : this._getCouchPollingUrl(couchName), - method : 'GET', - followRedirect: false, - headers: { - 'Content-Type': 'application/json', - 'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version) - } - }, - - t = function () { - count = count + 1; - if (count > maxAttempts) { - return callback("Max Attempts hit", { "created": false }); - } - request(options, function (err, response, body) { - if (err) { - return callback(err, { "created": false }); - } - if (response.statusCode === 200) { - return callback(null, { "created": true }); - } - setTimeout(t, interval); - }); - }; - t(); -}; - -Client.prototype.remove = function (id, callback) { - callback("Destroy method not available for iriscouch."); -}; - -// This function gets overriden in tests to trap the polling request -Client.prototype._getCouchPollingUrl = function(couchName) { - return 'http://' + couchName + '.iriscouch.com/'; -}; diff --git a/lib/pkgcloud/iriscouch/database/index.js b/lib/pkgcloud/iriscouch/database/index.js deleted file mode 100644 index c34a775e7..000000000 --- a/lib/pkgcloud/iriscouch/database/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * index.js: Top-level include for the Iriscouch database module - * - * (C) 2011 Nodejitsu Inc. - * - */ - -exports.Client = require('./client').Client; - -exports.createClient = function createClient(options) { - return new exports.Client(options); -}; diff --git a/lib/pkgcloud/iriscouch/index.js b/lib/pkgcloud/iriscouch/index.js deleted file mode 100644 index b0c730580..000000000 --- a/lib/pkgcloud/iriscouch/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * index.js: Top-level include for the iriscouch module. - * - * (C) 2011 Nodejitsu Inc. - * - */ - -exports.database = require('./database'); diff --git a/lib/pkgcloud/joyent/client.js b/lib/pkgcloud/joyent/client.js deleted file mode 100644 index fd4eeeb91..000000000 --- a/lib/pkgcloud/joyent/client.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * client.js: Base client from which all Joyent clients inherit from - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - fs = require('fs'), - auth = require('../common/auth'), - base = require('../core/base'); - -// -// ### constructor (options) -// #### @opts {Object} an object literal with options -// #### @serversUrl {String} **Optional** CloudAPI Endpoint -// #### @apiVersion {String} **Optional** CloudAPI API Version -// #### @account {String} **Optional** CloudAPI Account to connect to -// #### @username {String} **Optional** Login name -// #### @password {String} **Optional** Password that goes with username -// #### @keyId {String} **Optional** SSH KeyId to sign in to cloudapi -// #### @key {String} **Optional** SSH key (PEM) that goes -// #### @identity {String} **Optional** File path of the private key -// with `keyId`. -// #### @throws {TypeError} On bad input -// -// Creates a new Joyent CloudAPI Client. Even though all @opts are optional -// you must either provide username/password or keyId/key -// -var Client = exports.Client = function (opts) { - if (!opts) { - throw new TypeError('options required'); - } - - if (opts.identity) { - opts.key = fs.readFileSync(opts.identity, 'ascii'); - } - - if (!(opts.username && opts.password) && - !(opts.keyId && opts.key)) { - throw new TypeError('Either username/password or keyId/key are required'); - } - - // default values - opts.account = opts.account || opts.username || 'my'; - opts.apiVersion = opts.apiVersion || '~6.5'; - - // if a person gives a key id by name that doesn't work in joyent - // keys are fully qualified. so we check for `/` in the keyId, if it's not - // there we need to do something about it - if (opts.keyId && opts.keyId.indexOf('/') === -1) { - // this will fail if account was also not properly set and account is - // set to `my`. - opts.keyId = '/' + opts.account + '/keys/' + opts.keyId; - } - - base.Client.call(this, opts); - - this.provider = 'joyent'; - this.account = opts.account; - this.serversUrl = opts.serversUrl - || process.env.SDC_CLI_URL - || 'us-sw-1.api.joyentcloud.com'; - this.protocol = opts.protocol || 'https://'; - - if (!this.before) { this.before = []; } - - if (opts.key && opts.keyId) { - this.before.push(auth.httpSignature); - } else { - this.before.push(auth.basic); - } - - this.before.push(function setReqHeaders(req) { - req.json = true; - if (typeof req.headers["X-Api-Version"] === 'undefined') { - req.headers["x-api-version"] = opts.apiVersion; - req.headers.Accept = 'application/json'; - req.headers['content-type'] = 'application/json'; - } - }); - - this.before.push(function setContentTypeAndReqJson(req) { - if (typeof req.body !== 'undefined') { - req.json = req.body; - delete req.body; - } - }); -}; - -utile.inherits(Client, base.Client); - -Client.prototype.failCodes = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 409: 'Conflict', - 413: 'Request Entity Too Large', - 415: 'Unsupported Media Type', - 420: 'Slow Down', - 449: 'Retry With', - 500: 'Internal Error', - 503: 'Service Unavailable' -}; - -Client.prototype.successCodes = { - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-authoritative information', - 204: 'No content' -}; diff --git a/lib/pkgcloud/joyent/compute/client/flavors.js b/lib/pkgcloud/joyent/compute/client/flavors.js deleted file mode 100644 index 31c4be0d1..000000000 --- a/lib/pkgcloud/joyent/compute/client/flavors.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * flavors.js: Implementation of Joyent Flavors Client. - * - * (C) 2012, Nodejitsu Inc. - * - */ - -var pkgcloud = require('../../../../../lib/pkgcloud'), - base = require('../../../core/compute'), - compute = pkgcloud.providers.joyent.compute; - -// -// ### function getFlavors (callback) -// #### @callback {function} f(err, flavors). `flavors` is an array that -// represents the flavors that are available to your account -// -// Lists all flavors available to your account. -// -exports.getFlavors = function getFlavors(callback) { - var self = this; - return this._request({ - path: this.account + '/packages' - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, body.map(function (result) { - return new compute.Flavor(self, result); - }), res); - }); -}; - -// -// ### function getFlavor (flavor, callback) -// #### @image {Flavor|String} Flavor ID or an Flavor -// #### @callback {function} f(err, flavor). `flavor` is an object that -// represents the flavor that was retrieved. -// -// Gets a specified flavor of Joyent DataSets using the provided details -// object. -// -exports.getFlavor = function getFlavor(flavor, callback) { - var self = this, - flavorId = flavor instanceof base.Flavor ? flavor.id : flavor; - - // joyent decided to add spaces to their identifiers - flavorId = encodeURIComponent(flavorId); - - return this._request({ - path: this.account + '/packages/' + flavorId - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, new compute.Flavor(self, body), res); - }); -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/compute/client/images.js b/lib/pkgcloud/joyent/compute/client/images.js deleted file mode 100644 index bf28da777..000000000 --- a/lib/pkgcloud/joyent/compute/client/images.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * images.js: Implementation of Joyent Images Client. - * - * (C) 2012, Nodejitsu Inc. - * - */ -var pkgcloud = require('../../../../../lib/pkgcloud'), - base = require('../../../core/compute'), - errs = require('errs'), - compute = pkgcloud.providers.joyent.compute; - -// -// ### function getImages (callback) -// #### @callback {function} f(err, images). `images` is an array that -// represents the images that are available to your account -// -// Lists all images available to your account. -// -exports.getImages = function getImages(callback) { - var self = this; - return this._request({ - path: this.account + '/datasets' - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, body.map(function (result) { - return new compute.Image(self, result); - }), res); - }); -}; - -// ### function getImage (image, callback) -// #### @image {Image|String} Image id or an Image -// #### @callback {function} f(err, image). `image` is an object that -// represents the image that was retrieved. -// -// Gets a specified image of Joyent DataSets using the provided details -// object. -// -exports.getImage = function getImage(image, callback) { - var self = this, - imageId = image instanceof base.Image ? image.id : image; - - // joyent decided to add spaces to their identifiers - imageId = encodeURIComponent(imageId); - - return this._request({ - path: this.account + '/datasets/' + imageId - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, new compute.Image(self, body), res); - }); -}; - -// -// ### function createImage(options, callback) -// #### @id {Object} an object literal with options -// #### @name {String} String name of the image -// #### @server {Boolean} the server to use -// #### @callback {function} f(err, image). `image` is an object that -// represents the image that was created. -// -// Creates an image in Joyent based on a server -// -exports.createImage = function createImage(options, callback) { - return errs.handle( - errs.create({ message: 'Not supported by joyent' }), - callback - ); -}; - -// -// ### function destroyImage(image, callback) -// #### @image {Image|String} Image id or an Image -// #### @callback {function} f(err, image). `image` is an object that -// represents the image that was deleted. -// -// Destroys an image in Joyent -// -exports.destroyImage = function destroyImage(image, callback) { - return errs.handle( - errs.create({ message: 'Not supported by joyent' }), - callback - ); -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/compute/client/index.js b/lib/pkgcloud/joyent/compute/client/index.js deleted file mode 100644 index 33bf1b75e..000000000 --- a/lib/pkgcloud/joyent/compute/client/index.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * index.js: Compute client for Joyent CloudAPI - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - urlJoin = require('url-join'), - joyent = require('../../client'); - -var Client = exports.Client = function (options) { - joyent.Client.call(this, options); - - utile.mixin(this, require('./flavors')); - utile.mixin(this, require('./images')); - utile.mixin(this, require('./servers')); - utile.mixin(this, require('./keys')); -}; - -utile.inherits(Client, joyent.Client); - -Client.prototype._getUrl = function (options) { - options = options || {}; - - var root = this.serversUrl - ? this.protocol + this.serversUrl - : this.protocol + 'us-sw-1.api.joyentcloud.com'; - - return urlJoin(root, typeof options === 'string' - ? options - : options.path); -}; diff --git a/lib/pkgcloud/joyent/compute/client/keys.js b/lib/pkgcloud/joyent/compute/client/keys.js deleted file mode 100644 index 563f4a0f2..000000000 --- a/lib/pkgcloud/joyent/compute/client/keys.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * keys.js: Implementation of Joyent SSH keys Client. - * - * (C) 2012, Nodejitsu Inc. - * - */ - -var errs = require('errs'), - utile = require('utile'); - -// -// ### function listKeys (callback) -// #### @callback {function} Continuation to respond to when complete. -// -// Lists all Joyent SSH Keys matching the specified `options`. -// -exports.listKeys = function (callback) { - return this._request({ - path: this.account + '/keys' - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, body); - }); -}; - -// -// ### function getKey (name, callback) -// #### @name {string} Name of the Joyent SSH key to get -// #### @callback {function} Continuation to respond to when complete. -// -// Gets the details of the Joyent SSH Key with the specified `name`. -// -exports.getKey = function (name, callback) { - return this._request({ - path: this.account + '/keys/' + name - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, body); - }); -}; - -// -// ### function addKey (options, callback) -// #### @options {Object} SSH Public Key details -// #### @name {string} String name of the key -// #### @key {string} SSH Public Key -// #### @callback {function} Continuation to respond to when complete. -// -// Adds a Joyent SSH Key with the specified `options`. -// -exports.addKey = function (options, callback) { - if (!options || !options.key || !options.name) { - return errs.handle( - errs.create({ message: '`key` and `name` are required options.' }), - callback - ); - } - - return this._request({ - method: 'POST', - path: this.account + '/keys', - body: options - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - }); -}; - -// -// ### function getKey (name, callback) -// #### @name {string} Name of the Joyent SSH key to destroy -// #### @callback {function} Continuation to respond to when complete. -// -// Destroys Joyent SSH Key with the specified `name`. -// -exports.destroyKey = function (name, callback) { - return this._request({ - method: 'DELETE', - path: this.account + '/keys/' + name - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - }); -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/compute/client/servers.js b/lib/pkgcloud/joyent/compute/client/servers.js deleted file mode 100644 index 3ce979dd0..000000000 --- a/lib/pkgcloud/joyent/compute/client/servers.js +++ /dev/null @@ -1,264 +0,0 @@ -/* - * servers.js: Instance methods for working with servers from Joyent Cloud - * - * (C) 2012 Nodejitsu Inc. - * - */ -var request = require('request'), - base = require('../../../core/compute'), - pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - compute = pkgcloud.providers.joyent.compute; - -// -// ### function getVersion (callback) -// #### @callback {function} f(err, version). -// -// Gets the current API version -// -exports.getVersion = function getVersion(callback) { - return this._request({ - path: this.account + '/datacenters' - }, function (err, response, res) { - return err - ? callback(err) - : callback(null, res.headers['x-api-version'], res); - }); -}; - -// -// ### function getLimits (callback) -// #### @callback {function} f(err, version). -// -// Gets the current API limits -// -exports.getLimits = function getLimits(callback) { - return errs.handle( - errs.create({message: "Joyent's API is not rate limited"}), callback); -}; - -// -// ### function getServers (callback) -// #### @options {Object} Options when getting servers -// #### @options.offset {number} Number of servers to skip when listing -// #### @options.limit {number} Number of servers to return -// #### @callback {function} f(err, servers). `servers` is an array that -// represents the servers that are available to your account -// -// Lists all servers available to your account. -// -exports.getServers = function getServers(options, callback) { - if (!callback && typeof options === 'function') { - callback = options; - options = null; - } - - var self = this; - return this._request( - { - path: this.account + '/machines', - qs: options - }, - function (err, body, res) { - return err - ? callback(err) - : callback(null, body.map(function (result) { - return new compute.Server(self, result); - }), res); - } - ); -}; - -// -// ### function createServer (options, callback) -// #### @opts {Object} **Optional** options -// #### @name {String} **Optional** a name for your server -// #### @flavor {String|Favor} **Optional** flavor to use for this image -// #### @image {String|Image} **Optional** the image to use -// #### @required {Boolean} **Optional** Validate if flavor, name, -// and image are present -// #### @* {*} **Optional** Anything platform specific -// #### @callback {Function} f(err, server). -// -// Creates a server with the specified options. The flavor -// properties of the options can be instances of Flavor -// OR ids to those entities in Joyent. -// -exports.createServer = function createServer(options, callback) { - if (typeof options === 'function') { - callback = options; - options = {}; - } - - options = options || {}; // no args - var self = this, - createOptions = { - method: 'POST', - path: this.account + '/machines', - body: options - }; - - ['flavor', 'image', 'name'].forEach(function (member) { - if (options.required) { // marked as required? - if (!options[member]) { - return errs.handle( - errs.create({ message: 'options.' + member + ' is a required argument.' }), - callback - ); - } - } - }); - if (options.flavor) { - createOptions.body["package"] = options.flavor instanceof base.Flavor - ? options.flavor.id - : options.flavor; - - delete options.flavor; - } - - if (options.image) { - createOptions.body.dataset = options.image instanceof base.Image - ? options.image.id - : options.image; - - delete options.image; - } - - return this._request(createOptions, function (err, body, res) { - return err - ? callback(err) - : callback(null, new compute.Server(self, body), res); - }); -}; - -// -// ### function destroyServer(server, callback) -// #### @server {Server|String} Server id or a server -// #### @callback {Function} f(err, serverId). -// -// Destroy a server in Joyent. -// -exports.destroyServer = function destroyServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; - var self = this; - - var stopOptions = { - method: 'POST', - path: this.account + '/machines/' + serverId, - body: { - action: 'stop' - } - }; - - return this._request(stopOptions, function (err, body, res) { - if (err) { return callback && callback(err); } - if (res.statusCode === 202) { - var checks = 10; - var done = false; - function check() { - if (done) return; - checks--; - if (checks <= 0) return; - if (checks === 0) { - done = true; - finish(new Error('Machine unresponsive to STOP')); - return; - } - var checkOptions = { - method: 'GET', - path: self.account + '/machines/' + serverId - }; - - self._request(checkOptions, function (err, body, res) { - if (err) { return callback && callback(err) } - if (body && body.state === 'stopped') { - done = true; - finish(); - return; - } - }); - - setTimeout(check, 5000); - } - - check(); - return; - } - else { - finish(); - } - - function finish() { - var destroyOptions = { - method: 'DELETE', - path: self.account + '/machines/' + serverId - }; - - self._request(destroyOptions, function (err, body, res) { - return err - ? callback && callback(err) - : callback && callback(err, {ok: serverId}, res); - }); - } - }); -}; - -// -// ### function getServer(server, callback) -// #### @server {Server|String} Server id or a server -// #### @callback {Function} f(err, serverId). -// -// Gets a server in Joyent. -// -exports.getServer = function getServer(server, callback) { - var self = this, - serverId = server instanceof base.Server ? server.id : server; - - return this._request({ - path: this.account + '/machines/' + serverId - }, function (err, body, res) { - return err - ? callback(err) - : callback(null, new compute.Server(self, body), res); - }); -}; - - -// -// ### function rebootServer (server, options, callback) -// #### @server {Server|String} The server to reboot -// #### @callback {Function} f(err, server). -// -// Reboots a server -// -exports.rebootServer = function rebootServer(server, callback) { - var serverId = server instanceof base.Server ? server.id : server; - var createOptions = { - method: 'POST', - path: this.account + '/machines/' + serverId, - qs: { - action: 'reboot' - } - }; - - return this._request(createOptions, function (err, body, res) { - return err - ? callback(err) - : callback(null, { ok: serverId }, res); - }); -}; - -// -// ### function renameServer(server, name, callback) -// #### @server {Server|String} Server id or a server -// #### @name {String} New name to apply to the server -// #### @callback {Function} f(err, server). -// -// Renames a server -// -exports.renameServer = function renameServer(server, name, callback) { - return errs.handle( - errs.create({ message: 'Not supported by Joyent.' }), - callback - ); -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/compute/flavor.js b/lib/pkgcloud/joyent/compute/flavor.js deleted file mode 100644 index 3b5167e3a..000000000 --- a/lib/pkgcloud/joyent/compute/flavor.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * flavor.js: Joyent Cloud Package - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - base = require('../../core/compute/flavor'); - -var Flavor = exports.Flavor = function Flavor(client, details) { - base.Flavor.call(this, client, details); -}; - -utile.inherits(Flavor, base.Flavor); - -Flavor.prototype._setProperties = function (details) { - this.id = details.name; - this.name = details.name; - this.ram = details.memory; - this.disk = details.disk; - - // - // Joyent specific - // - this.swap = details.swap; - this["default"] = details["default"]; - this.original = this.joyent = details; -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/compute/image.js b/lib/pkgcloud/joyent/compute/image.js deleted file mode 100644 index 47e68e3c2..000000000 --- a/lib/pkgcloud/joyent/compute/image.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * image.js: Joyent Cloud DataSet - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - base = require('../../core/compute/image'); - -var Image = exports.Image = function Image(client, details) { - base.Image.call(this, client, details); -}; - -utile.inherits(Image, base.Image); - -Image.prototype._setProperties = function (details) { - this.id = details.urn; - this.name = details.name; - this.created = details.created; - - // - // Joyent specific - // - this.urn = details.urn; - this.joyentId = details.id; - this.os = details.os; - this.type = details.type; - this.description = details.description; - this["default"] = details["default"]; - this.version = details.version; - this.requirements = details.requirements; - this.original = this.rackspace = details; -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/compute/server.js b/lib/pkgcloud/joyent/compute/server.js deleted file mode 100644 index a45cb2283..000000000 --- a/lib/pkgcloud/joyent/compute/server.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * server.js: Joyent Cloud Machine - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - compute = require('../../core/compute'), - base = require('../../core/compute/server'), - computeStatus = require('../../common/status').compute; - -var Server = exports.Server = function Server(client, details) { - base.Server.call(this, client, details); -}; - -utile.inherits(Server, base.Server); - -Server.prototype._setProperties = function (details) { - this.id = details.id; - this.name = details.name; - - if (details.state) { - switch (details.state.toUpperCase()) { - case 'PROVISIONING': - this.status = this.STATUS.provisioning; - break; - case 'RUNNING': - this.status = this.STATUS.running; - break; - case 'STOPPING': - case 'STOPPED': - this.status = this.STATUS.stopped; - break; - default: - this.status = this.STATUS.unknown; - break; - } - } - - var addresses = details.ips.reduce(function (all, addr) { - if (compute.isPrivate(addr)) { - all['private'].push(addr); - } - else { - all['public'].push(addr); - } - - return all; - }, { 'private': [], 'public': [] }); - - // - // Joyent specific - // - this.ips = details.ips; - this.imageId = details.dataset; - this.addresses = details.addresses = addresses; - this.created = details.created; - this.updated = details.updated; - this.type = details.type; - this.ram = details.memory; - this.disk = details.disk; - this.metadata = details.metadata; - this.original = this.joyent = details; - this.adminPass = details.metadata && details.metadata.credentials && - details.metadata.credentials.admin; - -}; \ No newline at end of file diff --git a/lib/pkgcloud/joyent/index.js b/lib/pkgcloud/joyent/index.js deleted file mode 100644 index a6bed7e82..000000000 --- a/lib/pkgcloud/joyent/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * index.js: Top-level include for the Joyent module. - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.compute = require('./compute'); diff --git a/lib/pkgcloud/mongohq/database/client/databases.js b/lib/pkgcloud/mongohq/database/client/databases.js deleted file mode 100644 index 02254345b..000000000 --- a/lib/pkgcloud/mongohq/database/client/databases.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * database.js: Database methods for working with databases from MongoHQ - * - * (C) 2012 Nodejitsu Inc. - * - */ - - var errs = require('errs'), - url = require('url'); - -// Function formatResponse -// This function parse the response from the provider and return an object -// with the correct keys and values. -// ### @response {Object} The body response from the provider api -function formatResponse(response) { - var info, user, dbname, database, auth; - info = url.parse(response.config.MONGOHQ_URL); - auth = encodeURIComponent(info.auth); - user = auth.replace(/%3A/i, ':').split(':'); - dbname = info.pathname.replace('/', ''), - database = { - id: response.id, - port: Number(info.port), - host: info.hostname, - uri: 'mongodb://' + info.auth + '@' + info.host, - username: decodeURIComponent(user[0]), - password: decodeURIComponent(user[1]), - dbname: dbname - }; - return database; -} - -// Create a new Database at mongohq -// Need Name and select a plan. -// ### @options {Object} pair of name an plan values. -// ##### @options['name'] {String} Name of the new database.(required) -// ##### @options['plan'] {String} Name of the plan selected for database.(required) -exports.create = function create(options, callback) { - // Check for options - if (!options || typeof options === 'function') { - return errs.handle(errs.create({ - message: 'Options required for create a database.' - }), Array.prototype.slice.call(arguments).pop()); - } - // Check for name - if (!options['name']) { - return errs.handle(errs.create({ - message: 'options. Name are required arguments' - }), Array.prototype.slice.call(arguments).pop()); - } - - // Check for plan - if (!options['plan']) { - options['plan'] = 'free'; - } - - var createOptions = { - path : 'resources', - method : 'POST', - body : 'app_id=' + options.name + '&plan=' + options.plan - }; - - this._request(createOptions, function (err, b, response) { - if (err) { - return callback(err); - } - var body; - if (typeof b !== 'object') { - try { - body = JSON.parse(b); - } catch (e) { - return errs.handle(errs.create({ - messages: 'Bad response from server.' - }), callback); - } - } else { body = b; } - return callback(null, formatResponse(body)); - }); -}; - -// -// Removes one mongo instance by id -// ### @id {String} ID of the instance to remove. -exports.remove = function remove(id, callback) { - // Check for id - if (!id || typeof id === 'function') { - return errs.handle(errs.create({ - message: 'ID is a required argument' - }), Array.prototype.slice.call(arguments).pop()); - } - - var deleteOptions = { - path : 'resources/' + id, - method : 'DELETE' - }; - - this._request(deleteOptions, function (err, body, response) { - return err - ? callback(err) - : callback(null, 'deleted'); - }); -}; \ No newline at end of file diff --git a/lib/pkgcloud/mongohq/database/client/index.js b/lib/pkgcloud/mongohq/database/client/index.js deleted file mode 100644 index b70d44f3c..000000000 --- a/lib/pkgcloud/mongohq/database/client/index.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * index.js: Database client for MongoHQ Cloud Databases - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - urlJoin = require('url-join'), - base = require('../../../core/base'), - auth = require('../../../common/auth'), - url = require('url'), - request = require('request'), - errs = require('errs'); - -var Client = exports.Client = function (options) { - base.Client.call(this, options); - - if (!this.before) { - this.before = []; - } - - this.protocol = options.protocol || 'https://'; - this.databaseUrl = options.databaseUrl || 'providers.mongohq.com'; - - this.before.push(auth.basic); - - utile.mixin(this, require('./databases')); -}; - -utile.inherits(Client, base.Client); - -Client.prototype._getUrl = function (options) { - options = options || {}; - - return urlJoin([this.protocol + this.databaseUrl, 'provider'].join('/'), - typeof options === 'string' - ? options - : options.path); -}; - -Client.prototype.failCodes = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Resize not allowed', - 404: 'Item or Account not found', - 409: 'Build in progress', - 413: 'Over Limit', - 415: 'Bad Media Type', - 500: 'Fault', - 503: 'Service Unavailable' -}; - -Client.prototype.successCodes = { - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-authoritative information', - 204: 'No content' -}; \ No newline at end of file diff --git a/lib/pkgcloud/mongohq/database/index.js b/lib/pkgcloud/mongohq/database/index.js deleted file mode 100644 index f5d03971d..000000000 --- a/lib/pkgcloud/mongohq/database/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * index.js: Top-level include for the MongoHQ database module - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.Client = require('./client').Client; - -exports.createClient = function createClient(options) { - return new exports.Client(options); -}; diff --git a/lib/pkgcloud/mongohq/index.js b/lib/pkgcloud/mongohq/index.js deleted file mode 100644 index 63b085d8b..000000000 --- a/lib/pkgcloud/mongohq/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * index.js: Top-level include for the mongohq module. - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.database = require('./database'); diff --git a/lib/pkgcloud/mongolab/database/client/accounts.js b/lib/pkgcloud/mongolab/database/client/accounts.js deleted file mode 100644 index 922088d34..000000000 --- a/lib/pkgcloud/mongolab/database/client/accounts.js +++ /dev/null @@ -1,117 +0,0 @@ -/* - * accounts.js: Accounts methods for working with databases from MongoLab - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'); - -// Create Account -// ### @options {Object} Set of options can be -// #### options['name'] {string} Name of account (required) -// #### options['email'] {string} Email of the owner of the account (required) -// #### options['password'] {string} Password for the account (Optional), If not specify so mongolab will generate one. -// ### @callback {Function} Continuation to respond to when complete. -exports.createAccount = function createAccount(options, callback) { - // Check for options - if (typeof options === 'function') { - return errs.handle(errs.create({ - message: 'Options required for create an account.' - }), options); - } - - if (!options['name']) { - return errs.handle(errs.create({ - message: 'options. Name is a required argument' - }), callback); - } - - if (!options['email']) { - return errs.handle(errs.create({ - message: 'options. Email is a required argument' - }), callback); - } - - // Add support for the displayName input for mongolab - // https://objectlabs.jira.com/wiki/display/partners/MongoLab+Partner+Integration+API#MongoLabPartnerIntegrationAPI-Createaccount.1 - - var adminUser = { email: options['email'] }; - - if (options['password']) { - if (/[+\d]/g.test(options['password'])) { - adminUser['password'] = options['password']; - } else { - return errs.handle(errs.create({ - message: 'options. Password must contain at least one numeric character.' - }), callback); - } - } - - var createOptions = { - method: 'POST', - path: 'accounts', - body: { - name: [this.config.username, options['name']].join('_'), - adminUser: adminUser - } - }; - - this._request(createOptions, function (err, body, response) { - return err - ? callback(err) - : callback(null, { account: body.adminUser }); - }); -}; - -// Delete Account -// ### @name {String} Name of the account to be deleted -// ### @callback {Function} Continuation to respond to when complete. -exports.deleteAccount = function deleteAccount(name, callback) { - // Check for options - if (typeof name === 'function') { - return errs.handle(errs.create({ - message: 'Name required for delete an account.' - }), name); - } - - var deleteOptions = { - method: 'DELETE', - path: 'accounts/' + name - }; - - this._request(deleteOptions, function (err, body, response) { - return err - ? callback(err) - : callback(null); - }); -}; - -// List all accounts -// ### @callback {Function} Continuation to respond to when complete. -exports.getAccounts = function getAccounts(callback) { - this._request({ path: 'accounts' }, function (err, body, response) { - return err - ? callback(err) - : callback(null, body.map(function (account) { - return account.adminUser; - })); - }); -}; - -// View an account -exports.getAccount = function getAccount(name, callback) { - // Check for options - if (typeof name === 'function') { - return errs.handle(errs.create({ - message: 'Name required for view an account.' - }), name); - } - - this._request({ path: 'accounts/' + name }, function (err, body, response) { - return err - ? callback(err) - : callback(null, body.adminUser); - }); -}; \ No newline at end of file diff --git a/lib/pkgcloud/mongolab/database/client/databases.js b/lib/pkgcloud/mongolab/database/client/databases.js deleted file mode 100644 index 1ab883ce0..000000000 --- a/lib/pkgcloud/mongolab/database/client/databases.js +++ /dev/null @@ -1,192 +0,0 @@ -/* - * database.js: Database methods for working with databases from MongoLab - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - qs = require('querystring'), - url = require('url'); - - -// Function formatResponse -// This function parse the response from the provider and return an object -// with the correct keys and values. -// ### @response {Object} The body response from the provider api -function formatResponse(response) { - var info, user, dbname, auth; - info = url.parse(response.uri); - auth = encodeURIComponent(info.auth); - user = auth.replace(/%3A/i, ':').split(':'); - dbname = response.name; - - var database = { - id: dbname, - host: info.hostname, - port: Number(info.port), - uri: 'mongodb://' + info.auth + '@' + info.host, - username: decodeURIComponent(user[0]), - password: decodeURIComponent(user[1]), - dbname: dbname - }; - return database; -} - -// Create Database -// ### @options {Object} Set of options can be -// #### options['name'] {String} Name of database (required) -// #### options['owner'] {String} Name of the user owner the database (required) -// #### options['plan'] {String} Name of plan according to the MongoLab plans (Default: 'free') -// ### @callback {Function} Continuation to respond to when complete. -exports.create = function create(options, callback) { - // Check for options - if (typeof options === 'function') { - return errs.handle(errs.create({ - message: 'Options required for create a database.' - }), options); - } - - if (!options['name']) { - return errs.handle(errs.create({ - message: 'options. Name is a required argument' - }), callback); - } - - if (!options['owner']) { - return errs.handle(errs.create({ - message: 'options. Owner is a required argument' - }), callback); - } - - if (!options['plan']) { - options['plan'] = 'free'; - } - - // We have to setup the correct prefix for database name - // for the moment we use the 'owner' field because we expect the correct prefix there. - var databaseName = [options['owner'], options['name']].join('_'); - - // Setup the account name according mongolab API. - // @todo We need a helper function for add the prefix if its necesary - //var account = [this.config.username, options['owner']].join('_'); - // at the moment we need provide the username with the prefix (partner name) - var account = options['owner']; - - var createOptions = { - method: 'POST', - path: 'accounts/' + account + '/databases', - body: { - name: databaseName, - plan: options['plan'], - username: options['owner'], - // In future we will have to change this for support multiples clouds and user-selected cloud. - cloud: this.config.cloud - } - }; - - this._request(createOptions, function (err, body, response) { - return err - ? callback(err) - : callback(null, formatResponse(body)); - }); -}; - -// Lists all databases by user account -// ### @owner {String} Username for list their databases -// ### @callback {Function} Continuation to respond to when complete. -exports.getDatabases = function getDatabases(owner, callback) { - // Check for options - if (typeof owner === 'function') { - return errs.handle(errs.create({ - message: 'Name required for delete an account.' - }), owner); - } - - this._request({ path: 'accounts/' + owner + '/databases' }, function (err, body, response) { - return err - ? callback(err) - : callback(null, body); - }); -}; - -// View one database with details -// NOT USE THIS METHOD YET -// The principal idea of this method is for view details like username and -// password and the hostname and port, but for now MongoLab just answer with the name. -// The behavior I describe its according the parters documentation. -// https://objectlabs.jira.com/wiki/display/partners/MongoLab+Partner+Integration+API#MongoLabPartnerIntegrationAPI-Viewdatabase -// ### @options {Object} Set of options can be -// #### options['name'] {String} Name of the database to view (required) -// #### options['owner'] {String} Username of the database owner (required) -// ### @callback {Function} Continuation to respond to when complete. -exports.getDatabase = function getDatabase(options, callback) { - // Check for options - if (typeof options === 'function') { - return errs.handle(errs.create({ - message: 'Options required for view a database.' - }), options); - } - - // Check for name - if (!options['name']) { - return errs.handle(errs.create({ - message: 'options. Name is a required argument.' - }), callback); - } - - // Check for owner - if (!options['owner']) { - return errs.handle(errs.create({ - message: 'options. Username of owner is a required argument.' - }), callback); - } - - var path = ['accounts', options['owner'], 'databases', options['name']].join('/'); - - this._request({ path: path }, function (err, body, response) { - return err - ? callback(err) - : callback(null, body); - }); -}; - -// Delete a database -// ### @options {Object} Set of options can be -// #### options['name'] {String} Name of the database to view (required) -// #### options['owner'] {String} Username of the database owner (required) -// ### @callback {Function} Continuation to respond to when complete. -exports.remove = function remove(options, callback) { - // Check for options - if (typeof options === 'function') { - return errs.handle(errs.create({ - message: 'Options required for delete a database.' - }), options); - } - - // Check for name - if (!options['name']) { - return errs.handle(errs.create({ - message: 'options. Name is a required argument.' - }), callback); - } - - // Check for owner - if (!options['owner']) { - return errs.handle(errs.create({ - message: 'options. Username of owner is a required argument.' - }), callback); - } - - var deleteOptions = { - method: 'DELETE', - path: ['accounts', options['owner'], 'databases', options['name']].join('/') - }; - - this._request(deleteOptions, function (err, body, response) { - return err - ? callback(err) - : callback(null); - }); -}; diff --git a/lib/pkgcloud/mongolab/database/client/index.js b/lib/pkgcloud/mongolab/database/client/index.js deleted file mode 100644 index a00104e76..000000000 --- a/lib/pkgcloud/mongolab/database/client/index.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * index.js: Database client for MongoLab databases - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - urlJoin = require('url-join'), - base = require('../../../core/base'), - auth = require('../../../common/auth'); - -var Client = exports.Client = function (options) { - base.Client.call(this, options); - - if (!this.before) { - this.before = []; - } - - this.protocol = options.protocol || 'https://'; - this.databaseUrl = options.databaseUrl || 'api.mongolab.com'; - - this.before.push(auth.basic); - - this.before.push(function (req) { - req.json = true; - if (typeof req.body !== 'undefined') { - req.headers['Content-Type'] = 'application/json'; - req.body = JSON.stringify(req.body); - } - }); - - utile.mixin(this, require('./databases')); - utile.mixin(this, require('./accounts')); -}; - -utile.inherits(Client, base.Client); - -Client.prototype._getUrl = function (options) { - options = options || {}; - - var root = [this.protocol + this.databaseUrl, - 'api', '1', 'partners', (this.config.username) - ? this.config.username - : ''].join('/'); - - return urlJoin(root, typeof options === 'string' - ? options - : options.path); -}; - -Client.prototype.failCodes = { - 400: 'Bad Request', - 401: 'Unauthorized', - 403: 'Resize not allowed', - 404: 'Item or Account not found', - 409: 'Build in progress', - 413: 'Over Limit', - 415: 'Bad Media Type', - 500: 'Fault', - 503: 'Service Unavailable' -}; - -Client.prototype.successCodes = { - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-authoritative information', - 204: 'No content' -}; \ No newline at end of file diff --git a/lib/pkgcloud/mongolab/database/index.js b/lib/pkgcloud/mongolab/database/index.js deleted file mode 100644 index 6ef8ab5b8..000000000 --- a/lib/pkgcloud/mongolab/database/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * index.js: Top-level include for the MongoLab database module - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.Client = require('./client').Client; - -exports.createClient = function createClient(options) { - return new exports.Client(options); -}; diff --git a/lib/pkgcloud/mongolab/index.js b/lib/pkgcloud/mongolab/index.js deleted file mode 100644 index 1ee6d28cf..000000000 --- a/lib/pkgcloud/mongolab/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * index.js: Top-level include for the MongoLab module. - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.database = require('./database'); diff --git a/lib/pkgcloud/oneandone/blockstorage/client/index.js b/lib/pkgcloud/oneandone/blockstorage/client/index.js new file mode 100644 index 000000000..be47a99f0 --- /dev/null +++ b/lib/pkgcloud/oneandone/blockstorage/client/index.js @@ -0,0 +1,15 @@ +/** + * Created by Ali Bazlamit on 8/28/2017. + */ + +var util = require('util'), + oneandone = require('../../client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + oneandone.Client.call(this, options); + + _.extend(this, require('./snapshots')); +}; + +util.inherits(Client, oneandone.Client); diff --git a/lib/pkgcloud/oneandone/blockstorage/client/snapshots.js b/lib/pkgcloud/oneandone/blockstorage/client/snapshots.js new file mode 100644 index 000000000..a365aeeac --- /dev/null +++ b/lib/pkgcloud/oneandone/blockstorage/client/snapshots.js @@ -0,0 +1,122 @@ +/** + * Created by Ali Bazlamit on 8/28/2017. + */ + +var Snapshot = require('../snapshot').Snapshot, + oneandone = require('liboneandone'), + Server = require('../../compute/server').Server; +/** + * client.getSnapshots + * + * @description Returns a list of the server's snapshots. + * + * @param {function} callback + * @returns {*} + */ +exports.getSnapshots = function (server, callback) { + var self = this, + serverId = server instanceof Server ? server.id : server; + oneandone.listSnapshots(serverId, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 200) { + callback(JSON.parse(body)); + return; + } + var object = JSON.parse(body); + var result = []; + if (object instanceof Array) { + result = object; + } else { + result.push(object); + } + callback(null, result.map(function (data) { + return new Snapshot(self, data); + })); + }); +}; + +/** + * client.createSnapshot + * + * @description Creates a new snapshot of the server. + * + * @param {string} server Server or Server id to create the snapshot from + * @param {function} callback + * @returns {*} + */ +exports.createSnapshot = function (server, callback) { + var serverId = server instanceof Server ? server.id : server; + + oneandone.createSnapshot(serverId, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var snapshot = JSON.parse(body); + callback(null, snapshot.snapshot); + }); +}; + +/** + * client.updateSnapshot + * + * @description Restores a snapshot into the server. + * + * @param {string} options.server Server or Server id to restore the snapshot into + * @param {string} options.snapshot snapshot or snapshot id to restore into the server provided + * @param {function} callback + * @returns {*} + */ +exports.updateSnapshot = function (options, callback) { + var self = this; + var serverId = options.server instanceof Server ? options.server.id : options.server, + snapshotId = options.snapshot instanceof Object ? options.snapshot.id : options.snapshot; + + oneandone.restoreSnapshot(serverId, snapshotId, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var snp = JSON.parse(body); + callback(null, new Snapshot(self, snp)); + }); +}; + +/** + * client.deleteSnapshot + * + * @description Removes a snapshot + * @param {string} options.server Server or Server id to restore the snapshot into + * @param {string} options.snapshot snapshot or snapshot id to restore into the server provided + * @param {function} callback + * @returns {*} + */ +exports.deleteSnapshot = function (options, callback) { + var self = this; + var serverId = options.server instanceof Server ? options.server.id : options.server, + snapshotId = options.snapshot instanceof Object ? options.snapshot.id : options.snapshot; + + oneandone.deleteSnapshot(serverId, snapshotId, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var snp = JSON.parse(body); + callback(null, new Snapshot(self, snp)); + }); +}; \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/blockstorage/index.js b/lib/pkgcloud/oneandone/blockstorage/index.js new file mode 100644 index 000000000..c149170bd --- /dev/null +++ b/lib/pkgcloud/oneandone/blockstorage/index.js @@ -0,0 +1,9 @@ +/** + * Created by Ali Bazlamit on 8/28/2017. + */ +exports.Client = require('./client').Client; +exports.Snapshot = require('./snapshot').Snapshot; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/oneandone/blockstorage/snapshot.js b/lib/pkgcloud/oneandone/blockstorage/snapshot.js new file mode 100644 index 000000000..452a6052c --- /dev/null +++ b/lib/pkgcloud/oneandone/blockstorage/snapshot.js @@ -0,0 +1,26 @@ +/** + * Created by Ali Bazlamit on 8/28/2017. + */ + +var util = require('util'), + base = require('../../core/base'), + _ = require('lodash'); + +var Snapshot = exports.Snapshot = function Snapshot(client, details) { + base.Model.call(this, client, details); +}; + +util.inherits(Snapshot, base.Model); + +Snapshot.prototype._setProperties = function (details) { + this.id = details.id; +}; + +Snapshot.prototype.toJSON = function () { + return _.pick(this, ['id', 'name']); +}; + + + + + diff --git a/lib/pkgcloud/oneandone/client.js b/lib/pkgcloud/oneandone/client.js new file mode 100644 index 000000000..2de22b9a6 --- /dev/null +++ b/lib/pkgcloud/oneandone/client.js @@ -0,0 +1,45 @@ +/* + * client.js: Base client + * (C) Created by Ali Bazlamit on 8/10/2017. + * + */ + +var util = require('util'), + OAO = require('liboneandone'), + url = 'cloudpanel-api.1and1.com/v1', + base = require('../core/base'); + + +var Client = exports.Client = function (options) { + if (!options || !options.token) { + throw new TypeError('token is required'); + } + base.Client.call(this, options); + + options = options || {}; + this.provider = 'oneandone'; + this.protocol = options.protocol || 'https://'; + this.serversUrl = options.serversUrl ? options.serversUrl : url; + OAO.oneandoneauth(options.token); + OAO.setendpoint(this.protocol + this.serversUrl); +}; + +util.inherits(Client, base.Client); + + +Client.prototype.failCodes = { + 400: 'Bad Request', + 401: 'Unauthorized', + 403: 'Forbidden', + 404: 'Item not found', + 500: 'Fault', + 503: 'Service Unavailable' +}; + +Client.prototype.successCodes = { + 200: 'OK', + 201: 'Created', + 202: 'Accepted', + 203: 'Non-authoritative information', + 204: 'No content' +}; \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/compute/client/flavors.js b/lib/pkgcloud/oneandone/compute/client/flavors.js new file mode 100644 index 000000000..944f46730 --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/client/flavors.js @@ -0,0 +1,59 @@ +/** + * Created by Ali Bazlamit on 8/19/2017. + */ + +var pkgcloud = require('../../../../../lib/pkgcloud'), + base = require('../../../core/compute'), + oneandone = require('liboneandone'), + compute = pkgcloud.providers.oneandone.compute; + +// +// ### function getFlavors(size, callback) +// #### @size {size|String} flavor name or part of it S,M,L,XL +// #### @callback {function} f(err, flavors). `flavors` +// +// Returns available flavours for fixed servers. +// +exports.getFlavors = function getFlavors(callback) { + var self = this; + + oneandone.listHardwareFlavours(function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 200) { + callback(JSON.parse(body)); + return; + } + var flavors = JSON.parse(body); + callback(error, flavors.map(function (flavor) { + return new compute.Flavor(self, flavor); + })); + }); +}; + +// +// ### function getFlavor (flavor, callback) +// #### @flavor {Flavor|String} Flavor ID or an Flavor +// #### @callback {function} f(err, flavor). `flavor` is an object that +// represents the flavor that was retrieved. +// +// Returns information about one flavour +// +exports.getFlavor = function getFlavor(flavor, callback) { + var flavorId = flavor instanceof base.Flavor ? flavor.id : flavor; + var self = this; + oneandone.getHardwareFlavour(flavorId, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 200) { + callback(JSON.parse(body)); + return; + } + var flavor = JSON.parse(body); + callback(null, new compute.Flavor(self, flavor)); + }); +}; \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/compute/client/images.js b/lib/pkgcloud/oneandone/compute/client/images.js new file mode 100644 index 000000000..284abf0e4 --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/client/images.js @@ -0,0 +1,135 @@ +/* + * (C) Created by Ali Bazlamit on 8/19/2017. + * + */ +var pkgcloud = require('../../../../../lib/pkgcloud'), + base = require('../../../core/compute'), + oneandone = require('liboneandone'), + compute = pkgcloud.providers.oneandone.compute; +// +// ### function getImages (callback) +// #### @callback {function} f(err, images). `images` is an array that +// represents the images that are available to your account +// +// Lists all images available to your account. +// +exports.getImages = function getImages(options, callback) { + var self = this; + if (typeof options === 'function') { + callback = options; + options = {}; + } + var images = []; + oneandone.listServerAppliances(function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 200) { + callback(JSON.parse(body)); + return; + } + images = JSON.parse(body); + callback(error, images.map(function (image) { + return new compute.Image(self, image); + })); + }); +}; + +// ### function getImage (image, callback) +// #### @image {Image|String} Image id or an Image +// #### @callback {function} f(err, image). `image` is an object that +// represents the image that was retrieved. +// +// Information about specific appliance +// + +exports.getImage = function getImage(image, callback) { + var self = this; + var imageId = image instanceof base.Image + ? image.id + : image; + + oneandone.getServerAppliance(imageId, function (error, response, body) { + if (error) { + return callback(error); + } + var img = JSON.parse(body); + callback(null, new compute.Image(self, img)); + }); +}; + +// +// ### function createImage(options, callback) +// #### @id {Object} an object literal with options +// #### @name {String} String name of the image +// #### @server {Server} the server to create an image from +// #### @callback {function} f(err, image). `image` is an object that +// represents the image that was created. +// +// Adds a new image from a server +// + +exports.createImage = function createImage(options, callback) { + var self = this; + options || (options = {}); + + var serverId = options.server instanceof base.Server + ? options.server.id + : options.server; + + if (!options.name) { + throw new TypeError('`name` is a required option'); + } + + if (!options.server) { + throw new TypeError('`server` is a required option'); + } + + var imageData = { + 'server_id': serverId, + 'name': options.name, + 'frequency': oneandone.ImageFrequency.ONCE, + 'source': 'server', + 'num_images': 1, + 'datacenter_id': options.server.datacenter ? options.server.datacenter.id : null + }; + + oneandone.createImage(imageData, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var _image = JSON.parse(body); + var image = new compute.Image(self, _image); + callback(null, image); + + }); +}; + +// +// ### function destroyImage(image, callback) +// #### @image {Image|String} Image id or an Image +// #### @callback {function} f(err, image). `image` is an object that +// represents the image that was deleted. +// +// Destroys an image +// + +exports.destroyImage = function destroyImage(image, callback) { + var imageId = image instanceof base.Image + ? image.id + : image; + oneandone.deleteImage(imageId, function (error, response, body) { + if (error) { + return callback(error); + } + callback(null, body); + }); +}; + + diff --git a/lib/pkgcloud/oneandone/compute/client/index.js b/lib/pkgcloud/oneandone/compute/client/index.js new file mode 100644 index 000000000..33d202b33 --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/client/index.js @@ -0,0 +1,17 @@ +/** + * Created by Ali Bazlamit on 8/10/2017. + */ + +var util = require('util'), + oneandone = require('../../client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + oneandone.Client.call(this, options); + + _.extend(this, require('./servers')); + _.extend(this, require('./images')); + _.extend(this, require('./flavors')); +}; + +util.inherits(Client, oneandone.Client); \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/compute/client/servers.js b/lib/pkgcloud/oneandone/compute/client/servers.js new file mode 100644 index 000000000..47d00e020 --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/client/servers.js @@ -0,0 +1,190 @@ +/** + * Created by Ali Bazlamit on 8/10/2017. + */ + +var base = require('../../../core/compute'), + pkgcloud = require('../../../../../lib/pkgcloud'), + errs = require('errs'), + oneandone = require('liboneandone'), + compute = pkgcloud.providers.oneandone.compute; + +// +// ### function getVersion (callback) +// #### @callback {function} f(err, version). +// +// Gets the current API version +// +exports.getVersion = function getVersion(callback) { + callback(null, '1.7'); +}; + +// +// ### function getServers (callback) +// #### @callback {function} f(err, servers). `servers` is an array that +// represents the servers that are available to your account +// +// Lists all servers available to your account. +// +exports.getServers = function getServers(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var self = this; + + oneandone.listServers(function (error, response, results) { + if (error) { + callback(error); + return; + } + callback(null, JSON.parse(results).map(function (server) { + return new compute.Server(self, server); + })); + }); +}; + +// +// ### function createServer (options, callback) +// #### @opts {Object} **Optional** options +// #### @name {String} **Optional** the name of server +// #### @image {String|Image} the image ID to use +// #### @flavor {String|Flavor} **Optional** flavor to use for this image +// #### @callback {Function} f(err, server). +// +// Creates a server with the specified options. The flavor +// id of the options can be ids of Hardware Flavors +// +exports.createServer = function createServer(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + var self = this, + ImageId, + FlavorId, + hardware = {}; + options = options || {}; // no args + if (!options.image) { + return errs.handle( + errs.create({ + message: 'options.image is a required argument.' + }), + callback + ); + } + ImageId = options.image instanceof base.Image + ? options.image.id + : options.image; + + if (options.flavor) { + FlavorId = options.flavor instanceof base.Flavor + ? options.flavor.id + : options.flavor; + hardware = { + 'fixed_instance_size_id': FlavorId + }; + } else { + return errs.handle( + errs.create({ + message: 'options.flavor: is required' + }), + callback + ); + } + + var serverData = { + 'name': options.name, + 'hardware': hardware, + 'appliance_id': ImageId, + 'datacenter_id': options.location + }; + + oneandone.createServer(serverData, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var _server = JSON.parse(body); + var server = new compute.Server(self, _server); + callback(null, server); + }); +}; + +// +// ### function destroyServer(server, callback) +// #### @server {Server|String} Server id or a server +// #### @callback {Function} f(err, serverId). +// +// Destroy a server in OAO. +// +exports.destroyServer = function destroyServer(server, callback) { + var serverId = server instanceof base.Server ? server.id : server; + oneandone.deleteServer(serverId, false, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + callback(null, JSON.parse(body)); + }); +}; + +// +// ### function getServer(server, callback) +// #### @server {Server|String} Server id or a server +// #### @callback {Function} f(err, serverId). +// +// Gets a server in OAO. +// +exports.getServer = function getServer(server, callback) { + var self = this, + serverId = server instanceof base.Server ? server.id : server; + + oneandone.getServer(serverId, function (error, response, body) { + if (error) { + return callback(error); + } + var srv = JSON.parse(body); + callback(null, new compute.Server(self, srv)); + } + ); +}; + +// +// ### function rebootServer (server, options, callback) +// #### @server {Server|String} The server to reboot +// #### @callback {Function} f(err, server). +// +// Reboots a server +// +exports.rebootServer = function rebootServer(server, callback) { + var self = this, + serverId = server instanceof base.Server ? server.id : server; + + var updateData = { + 'action': oneandone.ServerUpdateAction.REBOOT, + 'method': oneandone.ServerUpdateMethod.SOFTWARE + + }; + + oneandone.updateServerStatus(serverId, updateData, function (error, response, body) { + if (error) { + return callback(error); + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var _server = JSON.parse(body); + var server = new compute.Server(self, _server); + callback(null, server); + }); +}; \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/compute/flavor.js b/lib/pkgcloud/oneandone/compute/flavor.js new file mode 100644 index 000000000..3d7ac2496 --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/flavor.js @@ -0,0 +1,22 @@ +/** + * Created by Ali Bazlamit on 8/21/2017. + */ + +var util = require('util'), + base = require('../../core/compute/flavor'); + +var Flavor = exports.Flavor = function Flavor(client, details) { + base.Flavor.call(this, client, details); +}; + +util.inherits(Flavor, base.Flavor); + +Flavor.prototype._setProperties = function (details) { + var id = details.id; + + this.id = id; + this.name = details.name; + this.ram = details.hardware.ram; + this.disk = details.hardware.hdds[0] ? details.hardware.hdds[0].size : 0; + this.cores = details.hardware.vcore; +}; diff --git a/lib/pkgcloud/oneandone/compute/image.js b/lib/pkgcloud/oneandone/compute/image.js new file mode 100644 index 000000000..96b112986 --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/image.js @@ -0,0 +1,26 @@ +/* + * image.js: OAO + * + * (C) Created by Ali Bazlamit on 8/19/2017. + * + */ + +var util = require('util'), + base = require('../../core/compute/image'), + _ = require('lodash'); + +var Image = exports.Image = function Image(client, details) { + base.Image.call(this, client, details); +}; + +util.inherits(Image, base.Image); + +Image.prototype._setProperties = function (details) { + this.id = details.id; + this.name = details.name; + this.server_id = details.server_id; +}; + +Image.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'server_id']); +}; diff --git a/lib/pkgcloud/joyent/compute/index.js b/lib/pkgcloud/oneandone/compute/index.js similarity index 61% rename from lib/pkgcloud/joyent/compute/index.js rename to lib/pkgcloud/oneandone/compute/index.js index 7ad4ff846..5307e0de8 100644 --- a/lib/pkgcloud/joyent/compute/index.js +++ b/lib/pkgcloud/oneandone/compute/index.js @@ -1,15 +1,11 @@ -/* - * index.js: Top-level include for the Joyent compute module - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.Client = require('./client').Client; -exports.Flavor = require('./flavor').Flavor; -exports.Image = require('./image').Image; -exports.Server = require('./server').Server; - -exports.createClient = function (options) { - return new exports.Client(options); -}; +/** + * Created by Ali Bazlamit on 8/14/2017. + */ +exports.Client = require('./client').Client; +exports.Server = require('./server').Server; +exports.Image = require('./image').Image; +exports.Flavor = require('./flavor').Flavor; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/oneandone/compute/server.js b/lib/pkgcloud/oneandone/compute/server.js new file mode 100644 index 000000000..e1337f35e --- /dev/null +++ b/lib/pkgcloud/oneandone/compute/server.js @@ -0,0 +1,45 @@ +/** + * server.js: 1&1 Server + * + * (C) Created by Ali Bazlamit on 8/10/2017. + * + */ + +var util = require('util'), + _ = require('lodash'), + base = require('../../core/compute/server'); + +var Server = exports.Server = function Server(client, details) { + base.Server.call(this, client, details); +}; + +util.inherits(Server, base.Server); + +Server.prototype._setProperties = function (details) { + + this.id = details.id; + this.name = details.name; + this.image = details.image; + this.imageId = details.image ? details.image.id : details.imageId; + this.ips = details.ips; + if (details.datacenter) { + this.datacenter = details.datacenter; + } + + switch (details.status && details.status.state) { + case 'POWERED_ON': + this.status = 'RUNNING'; + break; + case 'POWERED_OFF': + this.status = this.STATUS.stopped; + break; + case 'NEW': + default: + this.status = 'PROVISIONING'; + } + this.original = this.oneandone = details; +}; + +Server.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'image', 'datacenter']); +}; \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/index.js b/lib/pkgcloud/oneandone/index.js new file mode 100644 index 000000000..001d01d00 --- /dev/null +++ b/lib/pkgcloud/oneandone/index.js @@ -0,0 +1,9 @@ +/** + * + * (C) Created by Ali Bazlamit on 8/10/2017. + * + */ + +exports.compute = require('./compute'); +exports.blockstorage = require('./blockstorage'); +exports.loadbalancer = require('./loadbalancer'); \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/loadbalancer/client/index.js b/lib/pkgcloud/oneandone/loadbalancer/client/index.js new file mode 100644 index 000000000..d1aec485f --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/client/index.js @@ -0,0 +1,17 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ + +var util = require('util'), + oneandone = require('../../client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + oneandone.Client.call(this, options); + + _.extend(this, require('./loadbalancers')); + _.extend(this, require('./nodes')); + +}; + +util.inherits(Client, oneandone.Client); \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/loadbalancer/client/loadbalancers.js b/lib/pkgcloud/oneandone/loadbalancer/client/loadbalancers.js new file mode 100644 index 000000000..a129485d3 --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/client/loadbalancers.js @@ -0,0 +1,170 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ +var oneandone = require('liboneandone'), + LoadBalancer = require('../loadbalancer').LoadBalancer; + +// +// ### function getLoadBalancers (callback) +// #### @callback {function} f(err, loadbalancers). `loadbalancers` is an array that +// represents the loadbalancers that are available to your account +// +// Lists all loadbalancers available to your account. +// +exports.getLoadBalancers = function getLoadBalancers(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var self = this; + + oneandone.listLoadBalancers(function (error, response, body) { + if (error) { + callback(error); + return; + } + callback(null, JSON.parse(body).map(function (loadbalancer) { + return new LoadBalancer(self, loadbalancer); + })); + }); +}; + +// +// ### function createLoadbalancer (options, callback) +// #### @opts {Object} options +// #### @name {String} Load balancer name +// #### @healthCheckInterval {int} Health check period in seconds +// #### @healthCheckPath {String} **Optional** flavor to use for this image +// #### @healthCheckParser {String} **Optional** flavor to use for this image +// #### @persistence {boolean} Persistence +// #### @persistenceTime {int} Persistence time in seconds. Required if persistence is enabled. +// #### @method {String} 'Balancing procedure','enum': ['ROUND_ROBIN', 'LEAST_CONNECTIONS']. +// #### @datacenterId {String} **Optional** ID of the datacenter where the load balancer will be created +// #### @rules {Array|Rule} **Optional** flavor to use for this image +// #### @callback {Function} f(err, loadbalancer). +// +// Creates a new load balancer. +// +exports.createLoadBalancer = function createLoadBalancer(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + var self = this; + + var balancerData = { + 'name': options.name, + 'description': options.name, + 'health_check_test': oneandone.HealthCheckTestTypes.TCP, + 'health_check_interval': options.healthCheckInterval, + 'health_check_path': options.healthCheckPath, + 'health_check_parser': options.healthCheckParser, + 'persistence': options.Persistence, + 'persistence_time': options.persistenceTime, + 'method': options.method, + 'datacenter_id': options.location, + 'rules': options.rules + }; + + oneandone.createLoadBalancer(balancerData, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var _lb = JSON.parse(body); + var lb = new LoadBalancer(self, _lb); + callback(null, lb); + }); +}; + +// +// ### function deleteLoadBalancer(loadbalancer, callback) +// #### @loadbalancer {LoadBalancer|String} LoadBalancer id or a LoadBalancer +// #### @callback {Function} f(err, lbId). +// +// Destroy a LoadBalancer in OAO. +// +exports.deleteLoadBalancer = function deleteLoadBalancer(loadbalancer, callback) { + var lbId = loadbalancer instanceof LoadBalancer ? loadbalancer.id : loadbalancer; + oneandone.deleteLoadBalancer(lbId, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + callback(null, JSON.parse(body)); + }); +}; + +// +// ### function getLoadBalancer(loadbalancer, callback) +// #### @loadbalancer {LoadBalancer|String} LoadBalancer id or a loadbalancer +// #### @callback {Function} f(err, lbId). +// +// Gets a loadbalancer in OAO. +// +exports.getLoadBalancer = function getLoadBalancer(loadbalancer, callback) { + var self = this, + lbId = loadbalancer instanceof LoadBalancer ? loadbalancer.id : loadbalancer; + + oneandone.getLoadBalancer(lbId, function (error, response, body) { + if (error) { + return callback(error); + } + var _lb = JSON.parse(body); + callback(null, new LoadBalancer(self, _lb)); + } + ); +}; + +/** + * client.updateLoadBalancer + // #### @opts {Object} options + // #### @name {String} Load balancer name + // #### @healthCheckInterval {int} Health check period in seconds + // #### @healthCheckPath {String} **Optional** flavor to use for this image + // #### @healthCheckParser {String} **Optional** flavor to use for this image + // #### @persistence {boolean} Persistence + // #### @persistenceTime {int} Persistence time in seconds. Required if persistence is enabled. + // #### @method {String} 'Balancing procedure','enum': ['ROUND_ROBIN', 'LEAST_CONNECTIONS']. + * @param {function} callback + * @returns {*} + */ +exports.updateLoadBalancer = function updateLoadBalancer(options, callback) { + var self = this, + lbId = options.loadbalancer instanceof LoadBalancer ? options.loadbalancer.id : options.loadbalancer; + + var updateData = { + 'name': options.name, + 'health_check_test': oneandone.HealthCheckTestTypes.TCP, + 'health_check_interval': options.healthCheckInterval, + 'health_check_path': options.healthCheckPath, + 'health_check_parser': options.healthCheckParser, + 'persistence': options.Persistence, + 'persistence_time': options.persistenceTime, + 'method': options.method, + 'rules': options.rules + }; + oneandone.updateLoadBalancer(lbId, updateData, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var lb = JSON.parse(body); + callback(null, new LoadBalancer(self, lb)); + }); +}; + + diff --git a/lib/pkgcloud/oneandone/loadbalancer/client/nodes.js b/lib/pkgcloud/oneandone/loadbalancer/client/nodes.js new file mode 100644 index 000000000..41b27d1ca --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/client/nodes.js @@ -0,0 +1,90 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ +var oneandone = require('liboneandone'), + LoadBalancer = require('../loadbalancer').LoadBalancer, + Node = require('../node').Node; + +// +// ### function getNodes (callback) +// #### @callback {function} f(err, nodes). +// Returns a list of the servers/IPs attached to a load balancer. +// +exports.getNodes = function getNodes(loadbalancer, callback) { + var self = this, + lbId = loadbalancer instanceof LoadBalancer ? loadbalancer.id : loadbalancer; + + oneandone.listLoadBalancerServerIps(lbId, function (error, response, body) { + if (error) { + callback(error); + return; + } + callback(null, JSON.parse(body).map(function (node) { + return new Node(self, node); + })); + }); +}; + +// +// ### function createNode (serverIps, callback) +// #### @opts {Object} options +// #### @loadbalancer {Loadbalancer} Load balancer name +// #### @serverIps {Array} +// +// #### @callback {Function} f(err, node). +// +// Assigns servers/IPs to a load balancer. +// +exports.addNodes = function addNodes(options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + var self = this; + var lbId = options.loadbalancer instanceof LoadBalancer ? options.loadbalancer.id : options.loadbalancer; + + var assignData = { + 'server_ips': options.serverIps + }; + + oneandone.assignServerIpToLoadBalancer(lbId, assignData, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var _lb = JSON.parse(body); + var lb = new LoadBalancer(self, _lb); + callback(null, lb); + }); +}; + +// +// ### function deleteNode(options, callback) +// #### @opts {Object} options +// #### @loadbalancer {Loadbalancer} Load balancer name +// #### @serverIp {String} +// #### @callback {Function} f(err, lbId). +// +// Destroy a Node in OAO. +// +exports.removeNode = function removeNode(options, callback) { + var self = this, + lbId = options.loadbalancer instanceof LoadBalancer ? options.loadbalancer.id : options.loadbalancer; + oneandone.unassignServerIpFromLoadBalancer(lbId, options.serverIp, function (error, response, body) { + if (error) { + callback(error); + return; + } + if (response.statusCode != 202) { + callback(JSON.parse(body)); + return; + } + var _lb = JSON.parse(body); + var lb = new LoadBalancer(self, _lb); + callback(null, lb); + }); +}; \ No newline at end of file diff --git a/lib/pkgcloud/oneandone/loadbalancer/index.js b/lib/pkgcloud/oneandone/loadbalancer/index.js new file mode 100644 index 000000000..0e3ae9482 --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/index.js @@ -0,0 +1,11 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ + +exports.Client = require('./client').Client; +exports.LoadBalancer = require('./loadbalancer').LoadBalancer; +exports.Node = require('./node').Node; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/oneandone/loadbalancer/loadbalancer.js b/lib/pkgcloud/oneandone/loadbalancer/loadbalancer.js new file mode 100644 index 000000000..0fc4284b3 --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/loadbalancer.js @@ -0,0 +1,56 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ + +var util = require('util'), + base = require('../../core/loadbalancer/loadbalancer'); + +var LoadBalancer = exports.LoadBalancer = function LoadBalancer(client, details) { + base.LoadBalancer.call(this, client, details); +}; + +util.inherits(LoadBalancer, base.LoadBalancer); + +LoadBalancer.prototype._setProperties = function (details) { + var id = details.id; + + this.id = id; + this.name = details.name; + this.ip = details.ip; + this.healthCheckTest = details.health_check_test; + this.healthCheckInterval = details.health_check_interval; + this.persistence = details.persistence; + this.persistenceTime = details.persistence_time; + this.datacenter = details.datacenter; + this.rules = details.rules; + this.nodes = details.server_ips; + +}; + +/// Nodes + +LoadBalancer.prototype.refresh = function(callback) { + var self = this; + return self.client.getLoadBalancer(this, function (err, server) { + if (err) { + callback(err); + return; + } + + self._setProperties(server); + callback(err, self); + }); +}; + +//NODES +LoadBalancer.prototype.getNodes = function(callback) { + this.client.getNodes(this, callback); +}; + +LoadBalancer.prototype.addNodes = function(nodes, callback) { + this.client.addNodes(this, nodes, callback); +}; + +LoadBalancer.prototype.removeNode = function (node, callback) { + this.client.removeNode(this, node, callback); +}; diff --git a/lib/pkgcloud/oneandone/loadbalancer/node.js b/lib/pkgcloud/oneandone/loadbalancer/node.js new file mode 100644 index 000000000..153ba4e96 --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/node.js @@ -0,0 +1,20 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ + +var util = require('util'), + base = require('../../core/loadbalancer/node'); + +var Node = exports.Node = function Node(client, details) { + base.Node.call(this, client, details); +}; + +util.inherits(Node, base.Node); + +Node.prototype._setProperties = function (details) { + var self = this; + + self.id = details.id; + self.ip = details.ip; + self.server_name = details.server_name; +}; diff --git a/lib/pkgcloud/oneandone/loadbalancer/rule.js b/lib/pkgcloud/oneandone/loadbalancer/rule.js new file mode 100644 index 000000000..84459025e --- /dev/null +++ b/lib/pkgcloud/oneandone/loadbalancer/rule.js @@ -0,0 +1,25 @@ +/** + * Created by Ali Bazlamit on 8/31/2017. + */ +var _ = require('lodash'); +var Rule = function (details) { + if (!details) { + throw new Error('Rule must be constructed with at-least basic details.'); + } + + this._setProperties(details); +}; + +Rule.prototype._setProperties = function (details) { + this.id = details.id; + this.protocol = details.protocol; + this.portBalancer = details.port_balancer; + this.portServer = details.port_server; + this.source = details.source; +}; + +exports.Rule = Rule; + +Rule.prototype.toJSON = function () { + return _.pick(this, ['id', 'protocol', 'port_balancer', 'port_server','source']); +}; \ No newline at end of file diff --git a/lib/pkgcloud/openstack/blockstorage/client/index.js b/lib/pkgcloud/openstack/blockstorage/client/index.js new file mode 100644 index 000000000..99e5ca205 --- /dev/null +++ b/lib/pkgcloud/openstack/blockstorage/client/index.js @@ -0,0 +1,35 @@ +/* + * index.js: Openstack cinder (blockstorage) client + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + * + */ + +var util = require('util'), + urlJoin = require('url-join'), + openstack = require('../../client'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + openstack.Client.call(this, options); + + _.extend(this, require('./volumetypes')); + _.extend(this, require('./snapshots')); + _.extend(this, require('./volumes')); + + this.serviceType = 'volume'; +}; + +util.inherits(Client, openstack.Client); + +Client.prototype._getUrl = function (options) { + options = options || {}; + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); + +}; diff --git a/lib/pkgcloud/rackspace/blockstorage/client/snapshots.js b/lib/pkgcloud/openstack/blockstorage/client/snapshots.js similarity index 93% rename from lib/pkgcloud/rackspace/blockstorage/client/snapshots.js rename to lib/pkgcloud/openstack/blockstorage/client/snapshots.js index 57751e3dd..fc88fb007 100644 --- a/lib/pkgcloud/rackspace/blockstorage/client/snapshots.js +++ b/lib/pkgcloud/openstack/blockstorage/client/snapshots.js @@ -1,14 +1,13 @@ /* - * snapshots.js: Instance methods for working with SnapShots from OpenStack Block Storage + * snapshots.js: Instance methods for working with SnapShots from Openstack Cinder (Block Storage) * - * (C) 2013 Rackspace + * (C) 2014 Rackspace * Ken Perkins * MIT LICENSE * * */ -var errs = require('errs'), - Snapshot = require('../snapshot').Snapshot, +var Snapshot = require('../snapshot').Snapshot, urlJoin = require('url-join'); var _urlPrefix = 'snapshots'; @@ -96,8 +95,7 @@ exports.createSnapshot = function(details, callback) { } }; - self._request(createOptions, function(err, body, res) { - console.dir(body); + self._request(createOptions, function(err, body) { return err ? callback(err) : callback(null, new Snapshot(self, body.snapshot)); @@ -128,7 +126,7 @@ exports.updateSnapshot = function (snapshot, callback) { } }; - self._request(updateOptions, function (err, body, res) { + self._request(updateOptions, function (err, body) { return err ? callback(err) : callback(null, new Snapshot(self, body.snapshot)); @@ -155,4 +153,4 @@ exports.deleteSnapshot = function (snapshot, callback) { ? callback(err) : callback(null, true); }); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/blockstorage/client/volumes.js b/lib/pkgcloud/openstack/blockstorage/client/volumes.js similarity index 95% rename from lib/pkgcloud/rackspace/blockstorage/client/volumes.js rename to lib/pkgcloud/openstack/blockstorage/client/volumes.js index 41ff07390..ed4d5b547 100644 --- a/lib/pkgcloud/rackspace/blockstorage/client/volumes.js +++ b/lib/pkgcloud/openstack/blockstorage/client/volumes.js @@ -1,14 +1,13 @@ /* - * volumes.js: Instance methods for working with Volumes from CloudBlockStorage + * volumes.js: Instance methods for working with Volumes from Openstack Cinder (Block Storage) * - * (C) 2013 Rackspace + * (C) 2014 Rackspace * Ken Perkins * MIT LICENSE * * */ -var errs = require('errs'), - Volume = require('../volume').Volume, +var Volume = require('../volume').Volume, VolumeType = require('../volumetype').VolumeType, urlJoin = require('url-join'); @@ -166,4 +165,4 @@ exports.deleteVolume = function (volume, callback) { ? callback(err) : callback(); }); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/blockstorage/client/volumetypes.js b/lib/pkgcloud/openstack/blockstorage/client/volumetypes.js similarity index 90% rename from lib/pkgcloud/rackspace/blockstorage/client/volumetypes.js rename to lib/pkgcloud/openstack/blockstorage/client/volumetypes.js index bf64aa9d9..e345b053d 100644 --- a/lib/pkgcloud/rackspace/blockstorage/client/volumetypes.js +++ b/lib/pkgcloud/openstack/blockstorage/client/volumetypes.js @@ -1,14 +1,13 @@ /* - * volumetypes.js: Instance methods for working with VolumeTypes from CloudBlockStorage + * volumetypes.js: Instance methods for working with VolumeTypes from Openstack Cinder (Block Storage) * - * (C) 2013 Rackspace + * (C) 2014 Rackspace * Ken Perkins * MIT LICENSE * * */ -var errs = require('errs'), - VolumeType = require('../volumetype').VolumeType, +var VolumeType = require('../volumetype').VolumeType, urlJoin = require('url-join'); var _urlPrefix = 'types'; @@ -54,4 +53,4 @@ exports.getVolumeType = function (volumeType, callback) { ? callback(err) : callback(null, new VolumeType(self, body['volume_type'])); }); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/openstack/blockstorage/index.js b/lib/pkgcloud/openstack/blockstorage/index.js new file mode 100644 index 000000000..e80ae01ff --- /dev/null +++ b/lib/pkgcloud/openstack/blockstorage/index.js @@ -0,0 +1,17 @@ +/* + * index.js: Top-level include for the Openstack Block Storage module + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + * + */ + +exports.Client = require('./client').Client; +exports.Volume = require('./volume').Volume; +exports.VolumeType = require('./volumetype').VolumeType; +exports.Snapshot = require('./snapshot').Snapshot; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/rackspace/blockstorage/snapshot.js b/lib/pkgcloud/openstack/blockstorage/snapshot.js similarity index 81% rename from lib/pkgcloud/rackspace/blockstorage/snapshot.js rename to lib/pkgcloud/openstack/blockstorage/snapshot.js index db3181cb1..487a076a9 100644 --- a/lib/pkgcloud/rackspace/blockstorage/snapshot.js +++ b/lib/pkgcloud/openstack/blockstorage/snapshot.js @@ -1,21 +1,21 @@ /* - * volume.js: OpenStack BlockStorage snapshot + * volume.js: OpenStack Block Storage snapshot * - * (C) 2013 Rackspace + * (C) 2014 Rackspace * Ken Perkins * MIT LICENSE * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/base'), - _ = require('underscore'); + _ = require('lodash'); var Snapshot = exports.Snapshot = function Snapshot(client, details) { base.Model.call(this, client, details); }; -utile.inherits(Snapshot, base.Model); +util.inherits(Snapshot, base.Model); Snapshot.prototype._setProperties = function (details) { this.id = details.id; diff --git a/lib/pkgcloud/rackspace/blockstorage/volume.js b/lib/pkgcloud/openstack/blockstorage/volume.js similarity index 84% rename from lib/pkgcloud/rackspace/blockstorage/volume.js rename to lib/pkgcloud/openstack/blockstorage/volume.js index bf669299d..cb42d530a 100644 --- a/lib/pkgcloud/rackspace/blockstorage/volume.js +++ b/lib/pkgcloud/openstack/blockstorage/volume.js @@ -1,21 +1,21 @@ /* - * volume.js: OpenStack BlockStorage volume + * volume.js: OpenStack Block Storage volume * - * (C) 2013 Rackspace + * (C) 2014 Rackspace * Ken Perkins * MIT LICENSE * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/base'), - _ = require('underscore'); + _ = require('lodash'); var Volume = exports.Volume = function Volume(client, details) { base.Model.call(this, client, details); }; -utile.inherits(Volume, base.Model); +util.inherits(Volume, base.Model); Volume.prototype._setProperties = function (details) { this.id = details.id; diff --git a/lib/pkgcloud/rackspace/blockstorage/volumetype.js b/lib/pkgcloud/openstack/blockstorage/volumetype.js similarity index 80% rename from lib/pkgcloud/rackspace/blockstorage/volumetype.js rename to lib/pkgcloud/openstack/blockstorage/volumetype.js index 0a226c329..e0703b07e 100644 --- a/lib/pkgcloud/rackspace/blockstorage/volumetype.js +++ b/lib/pkgcloud/openstack/blockstorage/volumetype.js @@ -1,21 +1,21 @@ /* * volumetype.js: OpenStack Block Storage volume type * - * (C) 2013 Rackspace + * (C) 2014 Rackspace * Ken Perkins * MIT LICENSE * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/base'), - _ = require('underscore'); + _ = require('lodash'); var VolumeType = exports.VolumeType = function VolumeType(client, details) { base.Model.call(this, client, details); }; -utile.inherits(VolumeType, base.Model); +util.inherits(VolumeType, base.Model); VolumeType.prototype._setProperties = function (details) { this.id = details.id; diff --git a/lib/pkgcloud/openstack/cdn/client/base.js b/lib/pkgcloud/openstack/cdn/client/base.js new file mode 100644 index 000000000..fb8f9536e --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/client/base.js @@ -0,0 +1,47 @@ +/* + * flavors.js: Instance methods for working with base resources from Openstack CDN + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +/** + * client.getHomeDocument + * + * @description gets the home document for the CDN service + * + * @param callback + * @return {*} + */ +exports.getHomeDocument = function(callback) { + var requestOptions = { + path: '/' + }; + + return this._request(requestOptions, function (err, body) { + if (err) { + return callback(err); + } + + callback(err, body); + }); +}; + +/** + * client.getPing + * + * @description gets the server ping response (status response) + * + * @param callback + * @return {*} + */ +exports.getPing = function(callback) { + var requestOptions = { + path: '/ping' + }; + + return this._request(requestOptions, function (err) { + return callback(err); + }); +}; diff --git a/lib/pkgcloud/openstack/cdn/client/flavors.js b/lib/pkgcloud/openstack/cdn/client/flavors.js new file mode 100644 index 000000000..50f155c6f --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/client/flavors.js @@ -0,0 +1,75 @@ +/* + * flavors.js: Instance methods for working with flavors from Openstack CDN + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var pkgcloud = require('../../../../../lib/pkgcloud'), + urlJoin = require('url-join'), + cdn = pkgcloud.providers.openstack.cdn; + +var _urlPrefix = '/flavors'; + +/** + * client.getFlavor + * + * @description Gets a flavor from the account + * + * @param {String|object} flavor The flavor or flavorId to fetch + * @param {Function} callback + * @returns {request|*} + */ +exports.getFlavor = function (flavor, callback) { + var self = this, + path = flavor instanceof cdn.Flavor + ? urlJoin(_urlPrefix, flavor.id) + : urlJoin(_urlPrefix, flavor); + + return this._request({ + path: path + }, function (err, body) { + if (err) { + return callback(err); + } + if (!body) { + callback(new Error('Unexpected empty response')); + } + else { + callback(err, new cdn.Flavor(self, body)); + } + }); +}; + +/** + * client.getFlavors + * + * @description get the list of flavors for the current account + * + * @param {object|Function} [options] A set of options for the getFlavors call + * @param {function} callback f(err, flavors) where flavors is an array of Flavor + * @returns {*} + */ +exports.getFlavors = function getFlavors(options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var requestOptions = { + path: _urlPrefix + }; + + return this._request(requestOptions, function (err, body) { + if (err) { + return callback(err); + } + + callback(err, body.flavors.map(function(flavor) { + return new cdn.Flavor(self, flavor); + })); + }); +}; diff --git a/lib/pkgcloud/openstack/cdn/client/index.js b/lib/pkgcloud/openstack/cdn/client/index.js new file mode 100644 index 000000000..a308707bd --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/client/index.js @@ -0,0 +1,47 @@ +/* + * index.js: CDN client for OpenStack + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var util = require('util'), + openstack = require('../../client'), + urlJoin = require('url-join'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + openstack.Client.call(this, options); + + _.extend(this, require('./base')); + _.extend(this, require('./services')); + _.extend(this, require('./flavors')); + + this.serviceType = 'cdn'; + +}; + +util.inherits(Client, openstack.Client); + +/** + * client._getUrl + * + * @description get the url for the current CDN service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function (options) { + options = options || {}; + + if (!this._serviceUrl) { + throw new Error('Service url not found'); + } + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); +}; diff --git a/lib/pkgcloud/openstack/cdn/client/services.js b/lib/pkgcloud/openstack/cdn/client/services.js new file mode 100644 index 000000000..a64c868ed --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/client/services.js @@ -0,0 +1,301 @@ +/* + * services.js: Instance methods for working with services from Openstack CDN + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var pkgcloud = require('../../../../../lib/pkgcloud'), + errs = require('errs'), + url = require('url'), + urlJoin = require('url-join'), + util = require('util'), + _ = require('lodash'), + cdn = pkgcloud.providers.openstack.cdn, + jsonpatch = require('fast-json-patch'); + +// Declaring variables for helper functions defined later +var _getService, _validateProperties; + +var _urlPrefix = '/services'; + +/** + * client.getService + * + * @description Gets a service from the account + * + * @param {String|object} service The service or serviceId to fetch + * @param {Function} callback + * @returns {request|*} + */ +exports.getService = function (service, callback) { + return _getService(this, service, callback); +}; + +/** + * client.getServices + * + * @description get the list of services for the current account + * + * @param {object|Function} [options] A set of options for the getServices call + * @param {function} callback f(err, services) where services is an array of Service + * @returns {*} + */ +exports.getServices = function getServices(options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var requestOptions = { + path: _urlPrefix + }; + + requestOptions.qs = _.pick(options, + 'limit', + 'marker'); + + return this._request(requestOptions, function (err, body) { + if (err) { + return callback(err); + } + + callback(err, body.services.map(function(service) { + return new cdn.Service(self, service); + })); + }); +}; + +/** + * client.createService + * + * @description Creates a service with the specified options. + + * @param {object} details the details to create this service + * @param {String} details.name the name of the new service + * @param {Array} details.domains list of domains for the new service + * @param {Object} details.domains[n] information about a domain + * @param {String} details.domains[n].domain the domain name + * @param {String} [details.domains[n].protocol] the protocol used to access the domain; default = http + * @param {Array} details.origins list of origin servers for the new service + * @param {Object} details.origins[n] information about an origin server + * @param {String} details.origins[n].origin origin server address + * @param {Number} [details.origins[n].port] origin server port; default = 80 + * @param {Boolean} [details.origins[n].ssl] whether origin server uses SSL; default = false + * @param {Array} [details.origins[n].rules] list of rules defining the conditions when this origin should be accessed + * @param {Object} [details.origins[n].rules[n]] information about an access rule + * @param {String} [details.origins[n].rules[n].name] the name of the rule + * @param {String} [details.origins[n].rules[n].request_url] the request URL this rule should match (regex supported) + * @param {Array} [details.caching] list of TTL rules for assets of this service + * @param {Object} [details.caching[n]] information about a TTL rule + * @param {String} [details.caching[n].name] the name of the TTL rule + * @param {Number} [details.caching[n].ttl] the TTL value, in seconds? + * @param {Array} [details.caching[n].rules] list of rules that determine if this TTL should be applied to an asset + * @param {Object} [details.caching[n].rules[n]] information about a TTL rule + * @param {String} [details.caching[n].rules[n].name] the name of the TTL rule + * @param {String} [details.caching[n].rules[n].request_url] the request URL this rule should match (regex supported) + * @param {Array} [details.restrictions] list of restrictions on who can access new service + * @param {Object} [details.restrictions[n]] information about an access restriction + * @param {String} [details.restrictions[n].name] the name of the restriction + * @param {Array} [details.restrictions[n].rules] list of restrition rules + * @param {Object} [details.restrictions[n].rules[n]] information about a restriction rule + * @param {String} [details.restrictions[n].rules[n].name] the name of the restriction rule + * @param {String} [details.restrictions[n].rules[n].referrer] the domain from which the new service can be accessed + * @param {String} details.flavorId the ID of the flavor to use for this service + * @param callback + * @returns {request|null} + */ +exports.createService = function (details, callback) { + if (typeof details === 'function') { + callback = details; + details = {}; + } + + details = details || {}; + + if (!_validateProperties(['name', 'domains', 'origins', 'flavorId'], details, + 'options.%s is a required argument.', callback)) { + return; + } + + var self = this, + createOptions = { + method: 'POST', + path: _urlPrefix, + body: { + name: details.name, + domains: details.domains, + origins: details.origins, + flavor_id: details.flavorId + } + }; + + if (details.caching) { + createOptions.body.caching = details.caching; + } + + if (details.restrictions) { + createOptions.body.restrictions = details.restrictions; + } + + return self._request(createOptions, function (err, body, res) { + if (err) { + return callback(err); + } + + return _getService(self, res.headers.location, callback); + }); +}; + +/** + * client.updateService + * + * @description Update a service + * + * @param {String|object} service The service or serviceId to update + * @param {Function} callback + * @returns {request|*} + */ +exports.updateService = function (service, callback) { + var self = this; + + if (!(service instanceof cdn.Service)) { + return callback(new Error('you must provide a service to update')); + } + + // Get a pristine copy of service resource from the server + return _getService(self, service, function(err, pristineService) { + if (err) { + return callback(err); + } + + // Compare passed-in service with pristine copy to generate + // JSON Patch representation + var patch = jsonpatch.compare(pristineService, service); + return self._request({ + path: urlJoin(_urlPrefix, service.id), + body: patch, + method: 'PATCH' + }, function (err) { + if (err) { + return callback(err); + } + + callback(err, service); + }); + }); +}; + +/** + * client.deleteService + * + * @description Delete a service from the account + * + * @param {String|object} service The service or serviceId to delete + * @param {Function} callback + * @returns {request|*} + */ +exports.deleteService = function (service, callback) { + var path = service instanceof cdn.Service + ? urlJoin(_urlPrefix, service.id) + : urlJoin(_urlPrefix, service); + + return this._request({ + path: path, + method: 'DELETE' + }, function (err) { + return callback(err); + }); +}; + +/** + * client.deleteServiceCachedAssets + * + * @description Delete cached assets of a service + * + * @param {String|object} service The service or serviceId whose cached assets to delete + * @param {String|null} assetUrl The URL of the asset to delete; default = delete all assets + * @param {Function} callback + * @returns {request|*} + */ +exports.deleteServiceCachedAssets = function (service, assetUrl, callback) { + var path = service instanceof cdn.Service + ? urlJoin(_urlPrefix, service.id) + : urlJoin(_urlPrefix, service); + + if (!callback) { + callback = assetUrl; + assetUrl = null; + } + + return this._request({ + path: urlJoin(path, 'assets'), + qs: assetUrl ? { url: assetUrl } : { all: true }, + method: 'DELETE' + }, function (err) { + return callback(err); + }); +}; + +/** + * _getService + * + * @description Gets a service from the account + * + * @param {String|object} service The service or serviceId or serviceUrl to fetch + * @param {Function} callback + * @returns {request|*} + */ +_getService = function (self, service, callback) { + var requestOptions = {}; + + // Determine if service is an object, a URL or a string (serviceId) + if (service instanceof cdn.Service) { + requestOptions.path = urlJoin(_urlPrefix, service.id); + } else { + if (!!url.parse(service).protocol) { + requestOptions.uri = service; + } else { + requestOptions.path = urlJoin(_urlPrefix, service); + } + } + + return self._request(requestOptions, function (err, body) { + if (err) { + return callback(err); + } + if (!body) { + callback(new Error('Unexpected empty response')); + } + else { + callback(err, new cdn.Service(self, body)); + } + }); +}; + +/** + * _validateProperties + * + * @description local helper function for validating arguments + * + * @param {Array} required The list of required properties + * @param {object} options The options object to validate + * @param {String} formatString String formatter for the error message + * @param {Function} callback + * @returns {boolean} + */ +_validateProperties = function (required, options, formatString, callback) { + return !required.some(function (item) { + if (typeof(options[item]) === 'undefined') { + errs.handle( + errs.create({ message: util.format(formatString, item) }), + callback + ); + return true; + } + return false; + }); +}; diff --git a/lib/pkgcloud/openstack/cdn/flavor.js b/lib/pkgcloud/openstack/cdn/flavor.js new file mode 100644 index 000000000..49699fb16 --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/flavor.js @@ -0,0 +1,27 @@ +/* + * flavor.js: OpenStack CDN Flavor + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + * + */ + +var util = require('util'), + base = require('../../core/base'), + _ = require('lodash'); + +var Flavor = exports.Flavor = function Flavor(client, details) { + base.Model.call(this, client, details); +}; + +util.inherits(Flavor, base.Model); + +Flavor.prototype._setProperties = function (details) { + this.id = details.id || details['id']; + this.providers = details.providers || details['providers']; +}; + +Flavor.prototype.toJSON = function () { + return _.pick(this, ['id', 'providers']); +}; diff --git a/lib/pkgcloud/openstack/cdn/index.js b/lib/pkgcloud/openstack/cdn/index.js new file mode 100644 index 000000000..69b49ac76 --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/index.js @@ -0,0 +1,15 @@ +/* + * index.js: Top-level include for the OpenStack CDN module + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +exports.Client = require('./client').Client; +exports.Service = require('./service').Service; +exports.Flavor = require('./flavor').Flavor; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/openstack/cdn/service.js b/lib/pkgcloud/openstack/cdn/service.js new file mode 100644 index 000000000..5896536dc --- /dev/null +++ b/lib/pkgcloud/openstack/cdn/service.js @@ -0,0 +1,36 @@ +/* + * service.js: OpenStack CDN Service + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + * + */ + +var util = require('util'), + base = require('../../core/base'), + _ = require('lodash'); + +var Service = exports.Service = function Service(client, details) { + base.Model.call(this, client, details); +}; + +util.inherits(Service, base.Model); + +Service.prototype._setProperties = function (details) { + this.id = details.id || details['id']; + this.name = details.name || details['name']; + this.domains = details.domains || details['domains']; + this.origins = details.origins || details['origins']; + this.caching = details.caching || details['caching']; + this.restrictions = details.restrictions || details['restrictions']; + this.flavorId = details.flavorId || details['flavor_id']; + this.status = details.status || details['status']; + this.links = details.links; + this.errors = details.errors; +}; + +Service.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'domains', 'origins', 'caching', + 'restrictions', 'flavorId', 'status', 'links', 'errors']); +}; diff --git a/lib/pkgcloud/openstack/client.js b/lib/pkgcloud/openstack/client.js index aa91fea63..2f0259318 100644 --- a/lib/pkgcloud/openstack/client.js +++ b/lib/pkgcloud/openstack/client.js @@ -1,13 +1,13 @@ /* * client.js: Base client from which all OpenStack clients inherit from * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * (C) 2015 IBM Corp. * */ -var utile = require('utile'), - request = require('request'), - through = require('through'), +var util = require('util'), + through = require('through2'), base = require('../core/base'), errs = require('errs'), context = require('./context'); @@ -35,7 +35,9 @@ var Client = exports.Client = function (options) { this.authUrl = options.authUrl || 'auth.api.trystack.org'; this.provider = 'openstack'; this.region = options.region; - this.tenantId = options.tenantId; + this.tenantId = options.tenantId; + this.version = options.version || 'v2.0'; + this.keystoneAuthVersion = options.keystoneAuthVersion || 'v2.0'; if (!/^http[s]?\:\/\//.test(this.authUrl)) { this.authUrl = 'http://' + this.authUrl; @@ -51,6 +53,10 @@ var Client = exports.Client = function (options) { }); this.before.push(function (req) { + if (req.headers['Content-Type'] && req.headers['Content-Type'] !== 'application/json') { + req.json = false; + return; + } req.json = true; if (typeof req.body !== 'undefined') { req.headers['Content-Type'] = 'application/json'; @@ -66,25 +72,51 @@ var Client = exports.Client = function (options) { this._serviceUrl = null; }; -utile.inherits(Client, base.Client); +util.inherits(Client, base.Client); Client.prototype._getIdentityOptions = function() { var options = { url: this.authUrl, + version: this.version, username: this.config.username, - password: this.config.password + password: this.config.password, + keystoneAuthVersion: this.keystoneAuthVersion }; + options.strictSSL = typeof this.config.strictSSL === 'boolean' + ? this.config.strictSSL : true; + + if (this.config.domainId) { + options.domainId = this.config.domainId; + } else if (this.config.domainName) { + options.domainName = this.config.domainName; + } + + if (this.config.projectDomainName) { + options.projectDomainName = this.config.projectDomainName; + } else if (this.config.projectDomainId) { + options.projectDomainId = this.config.projectDomainId; + } + if (this.config.tenantId) { options.tenantId = this.config.tenantId; } else if (this.config.tenantName) { options.tenantName = this.config.tenantName; } + if (typeof this.config.useServiceCatalog === 'boolean') { options.useServiceCatalog = this.config.useServiceCatalog; } + if (this.config.basePath) { + options.basePath = this.config.basePath; + } + + if (this.config.headers) { + options.token = this.config.headers.authorization; + } + return options; }; @@ -170,33 +202,64 @@ Client.prototype.auth = function (callback) { Client.prototype._request = function (options, callback) { var self = this; - if (!self._isAuthorized()) { self.emit('log::trace', 'Not-Authenticated, inlining Auth...'); - var buf = through().pause(); - + var proxyStream = through(); + proxyStream.pause(); self.auth(function (err) { - if (err) { self.emit('log::error', 'Error with inline authentication', err); - return errs.handle(err, callback); + if (callback) { + return errs.handle(err, callback); + } + + return errs.handle(err, function (err) { + if (err) { + proxyStream.emit('error', err); + } + }); } self.emit('log::trace', 'Creating Authenticated Proxy Request'); var apiStream = Client.super_.prototype._request.call(self, options, callback); + proxyStream.on('abort', function () { + apiStream.abort(); + }); + + proxyStream.abort = function () { + apiStream.abort(); + }; + if (options.upload) { - buf.pipe(apiStream); + + // needed for event propagation during proxied auth for streams + apiStream.on('error', function (err) { + proxyStream.emit('error', err); + }); + + apiStream.on('complete', function (response) { + proxyStream.emit('complete', response); + }); + + proxyStream.pipe(apiStream); } else if (options.download) { - apiStream.pipe(buf); + apiStream.on('error', function (err) { + proxyStream.emit('error', err); + }); + + apiStream.on('response', function (response) { + proxyStream.emit('response', response); + }); + apiStream.pipe(proxyStream); } - buf.resume(); + proxyStream.resume(); }); - return buf; + return proxyStream; } else { self.emit('log::trace', 'Creating Authenticated Request'); diff --git a/lib/pkgcloud/openstack/compute/client/extensions/floating-ips.js b/lib/pkgcloud/openstack/compute/client/extensions/floating-ips.js index ff44c9bb5..54bb1668e 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/floating-ips.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/floating-ips.js @@ -7,8 +7,7 @@ * */ -var Server = require('../../server').Server, - urlJoin = require('url-join'); +var urlJoin = require('url-join'); var _extension = 'os-floating-ips'; @@ -115,7 +114,7 @@ exports.deallocateFloatingIp = function (floatingIp, callback) { * @returns {*} */ exports.addFloatingIp = function (server, floatingIp, callback) { - var floatingIpAddress = (typeof floatingIp === 'object') ? floatingIp.ip : floatingIp + var floatingIpAddress = (typeof floatingIp === 'object') ? floatingIp.ip : floatingIp; return this._doServerAction(server, { addFloatingIp: { @@ -137,7 +136,7 @@ exports.addFloatingIp = function (server, floatingIp, callback) { * @returns {*} */ exports.removeFloatingIp = function (server, floatingIp, callback) { - var floatingIpAddress = (typeof floatingIp === 'object') ? floatingIp.ip : floatingIp + var floatingIpAddress = (typeof floatingIp === 'object') ? floatingIp.ip : floatingIp; return this._doServerAction(server, { removeFloatingIp: { diff --git a/lib/pkgcloud/openstack/compute/client/extensions/index.js b/lib/pkgcloud/openstack/compute/client/extensions/index.js index 1fb53a6f6..70464d1b6 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/index.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/index.js @@ -8,7 +8,7 @@ * (updated by Alvaro M. Reol) */ -var utile = require('utile'); +var _ = require('lodash'); var extensions = { getExtensions: function(callback) { @@ -22,12 +22,12 @@ var extensions = { } }; -utile.mixin(extensions, require('./floating-ips')); -utile.mixin(extensions, require('./keys')); -utile.mixin(extensions, require('./networks')); -utile.mixin(extensions, require('./security-groups')); -utile.mixin(extensions, require('./security-group-rules')); -utile.mixin(extensions, require('./servers')); -utile.mixin(extensions, require('./volume-attachments')); +_.extend(extensions, require('./floating-ips')); +_.extend(extensions, require('./keys')); +_.extend(extensions, require('./networks')); +_.extend(extensions, require('./security-groups')); +_.extend(extensions, require('./security-group-rules')); +_.extend(extensions, require('./servers')); +_.extend(extensions, require('./volume-attachments')); module.exports = extensions; diff --git a/lib/pkgcloud/openstack/compute/client/extensions/keys.js b/lib/pkgcloud/openstack/compute/client/extensions/keys.js index 9bbcf37ce..befaa39fc 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/keys.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/keys.js @@ -1,7 +1,7 @@ /* * keys.js Implementation of OpenStack KeyPair API * - * (C) 2013, Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/openstack/compute/client/extensions/networks-base.js b/lib/pkgcloud/openstack/compute/client/extensions/networks-base.js index 393e21244..4012a65e1 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/networks-base.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/networks-base.js @@ -9,7 +9,7 @@ */ var urlJoin = require('url-join'), - _ = require('underscore'); + _ = require('lodash'); exports.createNetworkExtension = function(prefix) { return { @@ -236,7 +236,7 @@ exports.createNetworkExtension = function(prefix) { return callback(err); }); } - } + }; }; diff --git a/lib/pkgcloud/openstack/compute/client/extensions/networks.js b/lib/pkgcloud/openstack/compute/client/extensions/networks.js index 862648437..1d2f3deb8 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/networks.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/networks.js @@ -8,7 +8,6 @@ * */ -var urlJoin = require('url-join'), - networks = require('./networks-base'); +var networks = require('./networks-base'); module.exports = networks.createNetworkExtension('os-networks'); diff --git a/lib/pkgcloud/openstack/compute/client/extensions/security-group-rules.js b/lib/pkgcloud/openstack/compute/client/extensions/security-group-rules.js index 291946bd8..b879ed6c2 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/security-group-rules.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/security-group-rules.js @@ -5,10 +5,8 @@ * */ -var urlJoin = require('url-join'), - _ = require('underscore'), - async = require('async'); - +var async = require('async'); +var urlJoin = require('url-join'); var _extension = 'os-security-group-rules'; /** @@ -80,3 +78,24 @@ exports.addRules = function(rules, callback) { callback(null, created); }); }; + +/** + * client.deleteRule + * + * @description Remove a rule from a security group + * + * @param {string} ruleId the id of the rule to be deleted + * @param {Function} callback + * @returns {request|*} + */ +exports.deleteRule = function deleteRule(ruleId, callback) { + + return this._request({ + method: 'DELETE', + path: urlJoin(_extension, ruleId) + }, function (err) { + return err + ? callback(err) + : callback(err, { ok: ruleId}); + }); +}; diff --git a/lib/pkgcloud/openstack/compute/client/extensions/servers.js b/lib/pkgcloud/openstack/compute/client/extensions/servers.js index cc099330b..b0fc93c01 100644 --- a/lib/pkgcloud/openstack/compute/client/extensions/servers.js +++ b/lib/pkgcloud/openstack/compute/client/extensions/servers.js @@ -7,11 +7,6 @@ * */ -var Server = require('../../server').Server, - urlJoin = require('url-join'); - -var _extension = 'servers'; - /** * client.startServer * diff --git a/lib/pkgcloud/openstack/compute/client/flavors.js b/lib/pkgcloud/openstack/compute/client/flavors.js index a876be1fc..7fc133870 100644 --- a/lib/pkgcloud/openstack/compute/client/flavors.js +++ b/lib/pkgcloud/openstack/compute/client/flavors.js @@ -1,7 +1,7 @@ /* * flavors.js: Implementation of OpenStack Flavors Client. * - * (C) 2013, Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var pkgcloud = require('../../../../../lib/pkgcloud'), @@ -58,7 +58,7 @@ exports.getFlavor = function getFlavor(flavor, callback) { if (err) { return callback(err); } - if (!body || !body.flavors) { + if (!body || !body.flavor) { return callback(new Error('Unexpected empty response')); } else { diff --git a/lib/pkgcloud/openstack/compute/client/images.js b/lib/pkgcloud/openstack/compute/client/images.js index 6ece45f1e..6132c7131 100644 --- a/lib/pkgcloud/openstack/compute/client/images.js +++ b/lib/pkgcloud/openstack/compute/client/images.js @@ -1,7 +1,7 @@ /* * images.js: Implementation of OpenStack Images Client. * - * (C) 2013, Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var pkgcloud = require('../../../../../lib/pkgcloud'), @@ -142,4 +142,32 @@ exports.destroyImage = function destroyImage(image, callback) { ? callback(err) : callback(null, { ok: imageId }); }); -}; \ No newline at end of file +}; + +/** + * client.updateImageMeta + * + * @description updates image metadata + * + * @param {String|object} image the image or imageId to get + * @param {object} metadata the metadata object to store + * @param callback + * @returns {*} + */ +exports.updateImageMeta = function updateImageMeta(image, metadata, callback) { + var imageId = image instanceof compute.Image ? image.id : image, + specs = { + metadata : metadata + }; + + return this._request({ + path: urlJoin(_urlPrefix, imageId, 'metadata' ), + method: 'POST', + body: specs + }, + function (err, body) { + return err + ? callback(err) + : callback(null, body); + }); +}; diff --git a/lib/pkgcloud/openstack/compute/client/index.js b/lib/pkgcloud/openstack/compute/client/index.js index a47eda70f..9cc502d77 100644 --- a/lib/pkgcloud/openstack/compute/client/index.js +++ b/lib/pkgcloud/openstack/compute/client/index.js @@ -1,25 +1,25 @@ /* * index.js: Compute client for OpenStack * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), openstack = require('../../client'), ComputeClient = require('../computeClient').ComputeClient, - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { openstack.Client.call(this, options); - utile.mixin(this, require('./flavors')); - utile.mixin(this, require('./images')); - utile.mixin(this, require('./servers')); - utile.mixin(this, require('./extensions')); + _.extend(this, require('./flavors')); + _.extend(this, require('./images')); + _.extend(this, require('./servers')); + _.extend(this, require('./extensions')); this.serviceType = 'compute'; }; -utile.inherits(Client, openstack.Client); +util.inherits(Client, openstack.Client); _.extend(Client.prototype, ComputeClient.prototype); diff --git a/lib/pkgcloud/openstack/compute/client/servers.js b/lib/pkgcloud/openstack/compute/client/servers.js index 3c839d26f..b5199f5e2 100644 --- a/lib/pkgcloud/openstack/compute/client/servers.js +++ b/lib/pkgcloud/openstack/compute/client/servers.js @@ -1,21 +1,44 @@ /* * servers.js: Instance methods for working with servers from OpenStack Cloud * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var request = require('request'), - base = require('../../../core/compute'), +var base = require('../../../core/compute'), pkgcloud = require('../../../../../lib/pkgcloud'), errs = require('errs'), urlJoin = require('url-join'), util = require('util'), - _ = require('underscore'), + _ = require('lodash'), Server = require('../server').Server, compute = pkgcloud.providers.openstack.compute; var _urlPrefix = '/servers'; +/** + * validateProperties + * + * @description local helper function for validating arguments + * + * @param {Array} required The list of required properties + * @param {object} options The options object to validate + * @param {String} formatString String formatter for the error message + * @param {Function} callback + * @returns {boolean} + */ +function validateProperties(required, options, formatString, callback) { + return !required.some(function (item) { + if (typeof(options[item]) === 'undefined') { + errs.handle( + errs.create({ message: util.format(formatString, item) }), + callback + ); + return true; + } + return false; + }); +} + /** * client._doServerAction * @@ -101,6 +124,7 @@ exports.getServers = function getServers(options, callback) { * @param {Object} [details.keyname] optional keyname configuration * @param {Object} [details.personality] optional personality configuration * @param {Object} [details.metadata] optional metadata configuration + * @param {Object} [details.adminPass] optional password configuration * @param callback * @returns {request|*} */ @@ -146,10 +170,19 @@ exports.createServer = function createServer(details, callback) { createOptions.body.server.key_name = details.keyname; } + if (details.adminPass) { + createOptions.body.server.adminPass = details.adminPass; + } + if (details.securityGroups) { createOptions.body.server.security_groups = details.securityGroups; } + if (details.cloudConfig) { + createOptions.body.server.user_data = details.cloudConfig; + createOptions.body.server.config_drive = true; + } + return this._request(createOptions, function (err, body) { if (err) { return callback(err); @@ -247,18 +280,42 @@ exports.rebootServer = function rebootServer(server, options, callback) { * * @description Rebuild the provider server * - * @param {String|object} server The server or serverId to rebuild - * @param {String|object} image The image or imageId to use in the rebuild + * @param {String|object} server The server or serverId to rebuild + * @param {String|object} options The image or imageId to use in the rebuild (for backwards compatibility) + * @param {String|object} options.image The image or imageId to use in the rebuild + * @param {String} options.accessIPv4 The IP version 4 address of the server. + * @param {String} options.accessIPv6 The IP version 6 address of the server. + * @param {String} options.adminPass The administrator password for the server. + * @param {object} options.metadata Metadata key/value pairs. + * @param {array} options.personality Personality files - path and contents + * @param {String} options.personality[n].path Personality file path + * @param {String} options.personality[n].contents Personality file contents + * @param {String} options['OS-DCF:diskConfig'] The disk configuration value ("AUTO" | "MANUAL"). * @param {Function} callback * @returns {*} */ -exports.rebuildServer = function rebootServer(server, image, callback) { - var imageId = image instanceof base.Image ? image.id : image; +exports.rebuildServer = function rebootServer(server, options, callback) { + var rebuildBody = {}; + if (options instanceof base.Image) { + // Image object was passed in as options (backwards compatible) + rebuildBody.imageRef = options.id; + } else if (typeof options === 'object') { + // Several options were passed in as options + rebuildBody = _.pick(options, [ + 'accessIPv4', + 'accessIPv6', + 'adminPass', + 'metadata', + 'personality' + ]); + rebuildBody.imageRef = options.image instanceof base.Image ? options.image.id : options.image; + } else { + // Image ID was passed in as options (backwards compatible) + rebuildBody.imageRef = options; + } this._doServerAction.call(this, server, { - rebuild: { - imageRef: imageId - } + rebuild: rebuildBody }, callback); }; @@ -362,27 +419,3 @@ exports.getServerAddresses = function (server, type, callback) { : callback(null, body.addresses || body); }); }; - -/** - * validateProperties - * - * @description local helper function for validating arguments - * - * @param {Array} required The list of required properties - * @param {object} options The options object to validate - * @param {String} formatString String formatter for the error message - * @param {Function} callback - * @returns {boolean} - */ -function validateProperties(required, options, formatString, callback) { - return !required.some(function (item) { - if (typeof(options[item]) === 'undefined') { - errs.handle( - errs.create({ message: util.format(formatString, item) }), - callback - ); - return true; - } - return false; - }); -} diff --git a/lib/pkgcloud/openstack/compute/flavor.js b/lib/pkgcloud/openstack/compute/flavor.js index d6679d0bc..c7f9d9f12 100644 --- a/lib/pkgcloud/openstack/compute/flavor.js +++ b/lib/pkgcloud/openstack/compute/flavor.js @@ -1,18 +1,19 @@ /* * flavor.js: OpenStack Cloud flavor * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - base = require('../../core/compute/flavor'); +var util = require('util'), + base = require('../../core/compute/flavor'), + _ = require('lodash'); var Flavor = exports.Flavor = function Flavor(client, details) { base.Flavor.call(this, client, details); }; -utile.inherits(Flavor, base.Flavor); +util.inherits(Flavor, base.Flavor); Flavor.prototype._setProperties = function (details) { this.id = details.id; @@ -22,3 +23,7 @@ Flavor.prototype._setProperties = function (details) { this.vcpus = details.vcpus; this.swap = details.swap; }; + +Flavor.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'ram', 'disk', 'vcpus', 'swap' ]); +}; \ No newline at end of file diff --git a/lib/pkgcloud/openstack/compute/image.js b/lib/pkgcloud/openstack/compute/image.js index fd093ffdc..1c0ca0872 100644 --- a/lib/pkgcloud/openstack/compute/image.js +++ b/lib/pkgcloud/openstack/compute/image.js @@ -1,18 +1,19 @@ /* * image.js: OpenStack Cloud image * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), - base = require('../../core/compute/image'); +var util = require('util'), + base = require('../../core/compute/image'), + _ = require('lodash'); var Image = exports.Image = function Image(client, details) { base.Image.call(this, client, details); }; -utile.inherits(Image, base.Image); +util.inherits(Image, base.Image); Image.prototype._setProperties = function (details) { this.id = details.id; @@ -25,4 +26,8 @@ Image.prototype._setProperties = function (details) { this.updated = details.updated; this.status = details.status; this.progress = details.progress; +}; + +Image.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'status', 'progress', 'created', 'updated']); }; \ No newline at end of file diff --git a/lib/pkgcloud/openstack/compute/index.js b/lib/pkgcloud/openstack/compute/index.js index de5f78fb9..05e66dbb5 100644 --- a/lib/pkgcloud/openstack/compute/index.js +++ b/lib/pkgcloud/openstack/compute/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the OpenStack compute module * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/openstack/compute/server.js b/lib/pkgcloud/openstack/compute/server.js index 79b22fe5d..9c240186b 100644 --- a/lib/pkgcloud/openstack/compute/server.js +++ b/lib/pkgcloud/openstack/compute/server.js @@ -1,20 +1,20 @@ /* * server.js: OpenStack Cloud server * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), compute = require('../../core/compute'), base = require('../../core/compute/server'), - _ = require('underscore'); + _ = require('lodash'); var Server = exports.Server = function Server(client, details) { base.Server.call(this, client, details); }; -utile.inherits(Server, base.Server); +util.inherits(Server, base.Server); Server.prototype._setProperties = function (details) { var self = this; @@ -94,13 +94,13 @@ Server.prototype._setProperties = function (details) { } // Try to set the flavorId using a flavor object - if (typeof this.flavorId === "undefined" && + if (typeof this.flavorId === 'undefined' && details.flavor && details.flavor.id) { this.flavorId = details.flavor.id; } // Try to set the imageId using an image object - if (typeof this.imageId === "undefined" && + if (typeof this.imageId === 'undefined' && details.image && details.image.id) { this.imageId = details.image.id; } @@ -135,9 +135,9 @@ Server.prototype.getAddresses = function (type, callback) { }; Server.prototype.toJSON = function() { - return _.pick(this, ['id', 'name', 'status', 'hostId', 'addresses', - 'links', 'key_name', 'image', 'flavor', 'user_id', 'tenant_id', 'progress', + return _.pick(this, ['id', 'name', 'status', 'hostId', 'adminPass', 'addresses', + 'links', 'key_name', 'imageId', 'flavorId', 'user_id', 'tenant_id', 'progress', 'OS-EXT-STS:task_state', 'OS-EXT-STS:vm_state', 'OS-EXT-STS:power_state', 'OS-DCF:diskConfig', 'accessIPv4', 'accessIPv6', 'config_drive', 'metadata', 'created', 'updated']); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/openstack/context/identity.js b/lib/pkgcloud/openstack/context/identity.js index c30728da4..489d41266 100644 --- a/lib/pkgcloud/openstack/context/identity.js +++ b/lib/pkgcloud/openstack/context/identity.js @@ -2,22 +2,18 @@ * identity.js: Identity for openstack authentication * * (C) 2013 Rackspace, Ken Perkins + * (C) 2015 IBM Corp. * MIT LICENSE * */ -var _ = require('underscore'), +var _ = require('lodash'), events = require('eventemitter2'), - fs = require('fs'), request = require('request'), ServiceCatalog = require('./serviceCatalog').ServiceCatalog, - svcCat = require('./serviceCatalog'), - url = require('url'), - utile = require('utile'), urlJoin = require('url-join'), util = require('util'), - pkgcloud = require('../../../pkgcloud'), - errs = require('errs'); + pkgcloud = require('../../../pkgcloud'); // TODO refactor failCodes, getError into global handlers var failCodes = { @@ -33,6 +29,38 @@ var failCodes = { 503: 'Service Unavailable' }; +function getError(err, res, body) { + if (err) { + return err; + } + + var statusCode = res.statusCode.toString(), + err2; + + if (Object.keys(failCodes).indexOf(statusCode) !== -1) { + // + // TODO: Support more than JSON errors here + // + err2 = { + failCode: failCodes[statusCode], + statusCode: res.statusCode, + message: 'Error (' + + statusCode + '): ' + failCodes[statusCode], + href: res.request.uri.href, + method: res.request.method, + headers: res.headers + }; + + try { + err2.result = typeof body === 'string' ? JSON.parse(body) : body; + } catch (e) { + err2.result = { err: body }; + } + + return err2; + } +} + /** * Identity object * @@ -48,6 +76,7 @@ var Identity = exports.Identity = function (options) { self.options = options || {}; self.name = 'OpenstackIdentity'; + self.basePath = options.basePath || (options.keystoneAuthVersion === 'v3' ? '/v3/auth/tokens' : '/v2.0/tokens'); self.useServiceCatalog = (typeof options.useServiceCatalog === 'boolean') ? options.useServiceCatalog : true; @@ -59,7 +88,7 @@ var Identity = exports.Identity = function (options) { }); }; -utile.inherits(Identity, events.EventEmitter2); +util.inherits(Identity, events.EventEmitter2); /** * Identity.authorize @@ -76,17 +105,30 @@ Identity.prototype.authorize = function (options, callback) { callback = options; options = {}; } - var authenticationOptions = { - uri: urlJoin(options.url || self.options.url, '/v2.0/tokens'), + uri: urlJoin(options.url || self.options.url, self.basePath), method: 'POST', + strictSSL: options.strictSSL || self.options.strictSSL, headers: { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version), 'Content-Type': 'application/json', 'Accept': 'application/json' } }; + if (self.options.headers) { + for (var header in self.options.headers) { + if (self.options.headers.hasOwnProperty(header)) { + authenticationOptions.headers[header] = self.options.headers[header]; + } + } + } + if (self.options.version === 1 || self.options.version === '/v1.0') { + authenticationOptions.uri = urlJoin(options.url || self.options.url, '/auth/v1.0'); + authenticationOptions.method = 'GET'; + authenticationOptions.headers['X-Auth-User'] = self.options.username; + authenticationOptions.headers['X-Auth-Key'] = self.options.password; + } self._buildAuthenticationPayload(); // we can't be called without a payload @@ -108,44 +150,11 @@ Identity.prototype.authorize = function (options, callback) { self.emit('log::trace', 'Sending client authorization request', authenticationOptions); - // Don't keep a copy of the credentials in memory - delete self._authenticationPayload; - - request(authenticationOptions, function (err, response, body) { - // check for a network error, or a handled error - var err2 = getError(err, response, body); - - if (err2) { - return callback(err2); - } - - self.emit('log::trace', 'Provider Authentication Response', { - href: response.request.uri.href, - method: response.request.method, - headers: response.headers, - statusCode: response.statusCode - }); - - // If we don't have a tenantId in the response (meaning no service catalog) - // go ahead and make a 1-off request to get a tenant and then reauthorize - if (!body.access.token.tenant) { - getTenantId(urlJoin(options.url || self.options.url, '/v2.0/tenants'), body.access.token.id); - } - else { - try { - self._parseIdentityResponse(body); - callback(); - } - catch (e) { - callback(e); - } - } - }); - function getTenantId(endpoint, token) { var tenantOptions = { uri: endpoint, json: true, + strictSSL: options.strictSSL || self.options.strictSSL, headers: { 'X-Auth-Token': token, 'Content-Type': 'application/json', @@ -174,6 +183,48 @@ Identity.prototype.authorize = function (options, callback) { self.authorize(options, callback); }); } + + // Don't keep a copy of the credentials in memory + delete self._authenticationPayload; + request(authenticationOptions, function (err, response, body) { + // check for a network error, or a handled error + var err2 = getError(err, response, body); + + if (err2) { + return callback(err2); + } + + self.emit('log::trace', 'Provider Authentication Response', { + href: response.request.uri.href, + method: response.request.method, + headers: response.headers, + statusCode: response.statusCode + }); + + // If we've been asked to do v1 auth, check the response headers + // otherwise, check the body + try { + if (self.options.version === 1 || self.options.version === '/v1.0') { + self._storageUrl = response.headers['x-storage-url']; + self.token = { + id: response.headers['x-auth-token'] + }; + callback(); + } + // If we don't have a tenantId in the response (meaning no service catalog) + // go ahead and make a 1-off request to get a tenant and then reauthorize + else if (self.options.keystoneAuthVersion !== 'v3' && !body.access.token.tenant) { + getTenantId(urlJoin(options.url || self.options.url, '/v2.0/tenants'), body.access.token.id); + } + else { + self._parseIdentityResponse(body, response.headers); + callback(); + } + } + catch (e) { + callback(e); + } + }); }; /** @@ -188,27 +239,101 @@ Identity.prototype._buildAuthenticationPayload = function () { var self = this; self.emit('log::trace', 'Building Openstack Identity Auth Payload'); - - // setup our inputs for authorization - if (self.options.password && self.options.username) { - self._authenticationPayload = { - auth: { - passwordCredentials: { - username: self.options.username, - password: self.options.password + if (self.options.keystoneAuthVersion === 'v3') { + if (self.options.password) { + self._authenticationPayload = { + auth: { + identity : { + methods : ['password'], + password : { + user: { + password: self.options.password + } + } + } } + }; + + //first add user name or id to user field + if (self.options.username) { + self._authenticationPayload.auth.identity.password.user.name = self.options.username; + } else if (self.options.userid) { + self._authenticationPayload.auth.identity.password.user.id = self.options.userid; } - }; - } - // Token and tenant are also valid inputs - else if (self.options.token && (self.options.tenantId || self.options.tenantName)) { - self._authenticationPayload = { - auth: { - token: { - id: self.options.token + //check if authenticating against user domain + if (self.options.domainId) { + self._authenticationPayload.auth.identity.password.user.domain = {id:self.options.domainId}; + } else if (self.options.domainName) { + self._authenticationPayload.auth.identity.password.user.domain = {name:self.options.domainName}; + } + //check if we're getting a scoped token against a project and/or domain + if (self.options.tenantId || self.options.tenantName || self.options.projectDomainName || self.options.projectDomainId) { + self._authenticationPayload.auth.scope = {}; + var scopedProject = true; + if (self.options.tenantId) { + self._authenticationPayload.auth.scope.project = {id:self.options.tenantId}; + } else if (self.options.tenantName) { + self._authenticationPayload.auth.scope.project = {name:self.options.tenantName}; + } else { + scopedProject = false; + } + if (!scopedProject) { + if (self.options.projectDomainId) { + self._authenticationPayload.auth.scope.domain = {id:self.options.projectDomainId}; + } else if (self.options.projectDomainName) { + self._authenticationPayload.auth.scope.domain = {name:self.options.projectDomainName}; + } + } else { + if (self.options.projectDomainId) { + self._authenticationPayload.auth.scope.project.domain = {id:self.options.projectDomainId}; + } else if(self.options.projectDomainName) { + self._authenticationPayload.auth.scope.project.domain = {name:self.options.projectDomainName}; + } } } - }; + } + // Token and tenant are also valid inputs + else if (self.options.token && (self.options.tenantId || self.options.tenantName)) { + self._authenticationPayload = { + auth: { + identity : { + methods : ['token'], + token: { + id: self.options.token + } + } + } + }; + } + } else { + // setup our inputs for authorization + if (self.options.password && self.options.username) { + self._authenticationPayload = { + auth: { + passwordCredentials: { + username: self.options.username, + password: self.options.password + } + } + }; + } + // Token and tenant are also valid inputs + else if (self.options.token && (self.options.tenantId || self.options.tenantName)) { + self._authenticationPayload = { + auth: { + token: { + id: self.options.token + } + } + }; + } + // Are we filtering down by a tenant? + if (self._authenticationPayload && self.options.tenantId) { + self._authenticationPayload.auth.tenantId = self.options.tenantId; + } + else if (self._authenticationPayload && self.options.tenantName) { + self._authenticationPayload.auth.tenantName = self.options.tenantName; + } } }; @@ -221,67 +346,51 @@ Identity.prototype._buildAuthenticationPayload = function () { * @param {object} data the raw response from the identity call * @private */ -Identity.prototype._parseIdentityResponse = function (data) { +Identity.prototype._parseIdentityResponse = function (data, headers) { var self = this; if (!data) { throw new Error('missing required arguments!'); } - - if (data.access.token) { - self.token = data.access.token; - self.token.expires = new Date(self.token.expires); - } + if (self.options.keystoneAuthVersion === 'v3') { + self.token = { + id: headers['x-subject-token'], + expires: new Date(data.token.expires_at), + issued_at: new Date(data.token.issued_at), + tenant: {id: data.token.project.id, name: data.token.project.name} + }; + self.user = data.token.user; + if (self.useServiceCatalog) { + self.serviceCatalog = new ServiceCatalog(data.token.catalog); + } + } else { + if (data.access.token) { + self.token = data.access.token; + self.token.expires = new Date(self.token.expires); + } - if (self.useServiceCatalog && data.access.serviceCatalog) { - self.serviceCatalog = new ServiceCatalog(data.access.serviceCatalog); - } + if (self.useServiceCatalog && data.access.serviceCatalog) { + self.serviceCatalog = new ServiceCatalog(data.access.serviceCatalog); + } - self.user = data.access.user; + self.user = data.access.user; + } self.raw = data; - }; Identity.prototype.getServiceEndpointUrl = function (options) { if (this.useServiceCatalog) { return this.serviceCatalog.getServiceEndpointUrl(options); } + // This is a hack to enable support for v1 swift clients + // TODO move all auth for v1 swift out of identity, as it's not related to identity at all + else if (this.options.version === 1 || this.options.version === '/v1.0') { + return this._storageUrl; + } else { return this.options.url; } }; -function getError(err, res, body) { - if (err) { - return err; - } - - var statusCode = res.statusCode.toString(), - err2; - - if (Object.keys(failCodes).indexOf(statusCode) !== -1) { - // - // TODO: Support more than JSON errors here - // - err2 = { - failCode: failCodes[statusCode], - statusCode: res.statusCode, - message: 'Error (' + - statusCode + '): ' + failCodes[statusCode], - href: res.request.uri.href, - method: res.request.method, - headers: res.headers - }; - - try { - err2.result = typeof body === 'string' ? JSON.parse(body) : body; - } catch (e) { - err2.result = { err: body }; - } - - return err2; - } - return; -} diff --git a/lib/pkgcloud/openstack/context/index.js b/lib/pkgcloud/openstack/context/index.js index a4b7aefac..aa36ee91d 100644 --- a/lib/pkgcloud/openstack/context/index.js +++ b/lib/pkgcloud/openstack/context/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the OpenStack identity module * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/openstack/context/service.js b/lib/pkgcloud/openstack/context/service.js index 6cf6e1b3d..0c32f2afd 100644 --- a/lib/pkgcloud/openstack/context/service.js +++ b/lib/pkgcloud/openstack/context/service.js @@ -2,11 +2,23 @@ * service.js: Service model * * (C) 2013 Rackspace, Ken Perkins + * (C) 2015 IBM Corp. * MIT LICENSE * */ -var _ = require('underscore'); +var _ = require('lodash'); + +function matchRegion(a, b) { + if (!a && !b) { + return true; + } + else if ((!a && b) || (a && !b)) { + return false; + } + + return a.toLowerCase() === b.toLowerCase(); +} /** * Service class @@ -56,16 +68,60 @@ Service.prototype.getEndpointUrl = function (options) { if (options.serviceType.toLowerCase() !== this.type.toLowerCase()) { return ''; } + //since we don't know if this is a v2 or v3 catalog, check if the "interface" field is set on an endpoint, if so, we're v3 + if (self.endpoints && self.endpoints.length > 0 && self.endpoints[0]['interface'] !== undefined) { + self.v3 = true; + } - options = options || {}; + /** + * getUrl + * + * @description utility function for getEndpointUrl + * @param {object} endpoint the endpoint to use + * @param {string} [endpoint.internalURL] the internal URL of the endpoint + * @param {string} [endpoint.publicURL] the public URL of the endpoint + * + * @returns {String} the uri of the endpoint + */ + function getUrl(endpoint) { + var useInternal = typeof options.useInternal === 'boolean' ? + options.useInternal : false; + + return useInternal && endpoint.internalURL + ? endpoint.internalURL + : ((typeof options.useAdmin === 'boolean' && options.useAdmin && endpoint.adminURL) ? + endpoint.adminURL : endpoint.publicURL); + } + + /** + * endpointMatchDesiredInterface + * + * @description determine if the endpoint interface matches the desired interface + * @param {object} endpoint + * @returns {Boolean} + */ + function endpointMatchDesiredInterface(endpoint) { + var interfaceToUse = 'public'; + if (options.useInternal === true) { + interfaceToUse = 'internal'; + } else if (options.useAdmin === true) { + interfaceToUse = 'admin'; + } + return endpoint['interface'] === interfaceToUse; + } if (options.region) { _.each(self.endpoints, function (endpoint) { if (!endpoint.region || !matchRegion(endpoint.region, options.region)) { return; } - - url = getUrl(endpoint); + if (self.v3 === true) { + if (endpointMatchDesiredInterface(endpoint)) { + url = endpoint.url; + } + } else { + url = getUrl(endpoint); + } }); } else { @@ -82,26 +138,6 @@ Service.prototype.getEndpointUrl = function (options) { }); } - /** - * getUrl - * - * @description utility function for getEndpointUrl - * @param {object} endpoint the endpoint to use - * @param {string} [endpoint.internalURL] the internal URL of the endpoint - * @param {string} [endpoint.publicURL] the public URL of the endpoint - * - * @returns {String} the uri of the endpoint - */ - function getUrl(endpoint) { - var useInternal = typeof options.useInternal === 'boolean' ? - options.useInternal : false; - - return useInternal && endpoint.internalURL - ? endpoint.internalURL - : ((typeof options.useAdmin === 'boolean' && options.useAdmin && endpoint.adminURL) ? - endpoint.adminURL : endpoint.publicURL); - } - if (!url) { throw new Error('Unable to identify endpoint url'); } @@ -109,15 +145,4 @@ Service.prototype.getEndpointUrl = function (options) { return url; }; -exports.Service = Service; - -function matchRegion(a, b) { - if (!a && !b) { - return true; - } - else if ((!a && b) || (a && !b)) { - return false; - } - - return a.toLowerCase() === b.toLowerCase(); -} +exports.Service = Service; \ No newline at end of file diff --git a/lib/pkgcloud/openstack/context/serviceCatalog.js b/lib/pkgcloud/openstack/context/serviceCatalog.js index 91b3f41cc..f435ebdad 100644 --- a/lib/pkgcloud/openstack/context/serviceCatalog.js +++ b/lib/pkgcloud/openstack/context/serviceCatalog.js @@ -6,10 +6,8 @@ * */ -var service = require('./service'), - Service = require('./service').Service, - async = require('async'), - _ = require('underscore'); +var Service = require('./service').Service, + _ = require('lodash'); /** * ServiceCatalog class diff --git a/lib/pkgcloud/rackspace/database/client/databases.js b/lib/pkgcloud/openstack/database/client/databases.js similarity index 97% rename from lib/pkgcloud/rackspace/database/client/databases.js rename to lib/pkgcloud/openstack/database/client/databases.js index 0fd0027a0..f70ed0822 100644 --- a/lib/pkgcloud/rackspace/database/client/databases.js +++ b/lib/pkgcloud/openstack/database/client/databases.js @@ -1,7 +1,7 @@ /* * database.js: Database methods for working with database within instances from Rackspace Cloud * - * (C) 2012 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ @@ -20,8 +20,6 @@ var pkgcloud = require('../../../../../lib/pkgcloud'), // #### options['collate'] {string} Should be a valid Collate for mysql. Default to 'utf8_general_ci' // For more info about character_set and collate for mysql see http://dev.mysql.com/doc/refman/5.6/en/charset-mysql.html exports.createDatabase = exports.create = function createDatabase(options, callback) { - var self = this; - // Check for options if (!options || typeof options === 'function') { return errs.handle(errs.create({ @@ -108,7 +106,7 @@ exports.getDatabases = function getDatabases(options, callback) { requestOptions.qs = completeUrl; requestOptions.path = 'instances/' + instanceId + '/databases'; - this._request(requestOptions, function (err, body, response) { + this._request(requestOptions, function (err, body) { if (err) { return callback(err); } diff --git a/lib/pkgcloud/rackspace/database/client/flavors.js b/lib/pkgcloud/openstack/database/client/flavors.js similarity index 91% rename from lib/pkgcloud/rackspace/database/client/flavors.js rename to lib/pkgcloud/openstack/database/client/flavors.js index 0e03f91a0..45c4164e7 100644 --- a/lib/pkgcloud/rackspace/database/client/flavors.js +++ b/lib/pkgcloud/openstack/database/client/flavors.js @@ -1,7 +1,7 @@ /* - * flavors.js: Implementation of Rackspace Flavors Client. + * flavors.js: Implementation of Openstack Trove Flavors Client. * - * (C) 2011 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ diff --git a/lib/pkgcloud/openstack/database/client/index.js b/lib/pkgcloud/openstack/database/client/index.js new file mode 100644 index 000000000..2646173e2 --- /dev/null +++ b/lib/pkgcloud/openstack/database/client/index.js @@ -0,0 +1,61 @@ +/* + * client.js: Database client for Openstack Trove Databases + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + urlJoin = require('url-join'), + rackspace = require('../../client'), + auth = require('../../../common/auth.js'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + rackspace.Client.call(this, options); + + this.before.push(auth.accountId); + + _.extend(this, require('./flavors')); + _.extend(this, require('./instances')); + _.extend(this, require('./databases')); + _.extend(this, require('./users')); + + this.serviceType = 'database'; +}; + +util.inherits(Client, rackspace.Client); + +Client.prototype._getUrl = function (options) { + options = options || {}; + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); + +}; + +// +// Gets the version of the OpenStack Compute API we are running against +// Parameters: callback +// +Client.prototype.getVersion = function getVersion(callback) { + var self = this; + + this.auth(function (err) { + if (err) { + return callback(err); + } + + self._request({ + uri: self._getUrl('/').replace('/v1.0/' + self._identity.token.tenant.id + '/', '') + }, function (err, body) { + if (err) { + return callback(err); + } + return callback(null, + ((typeof body === 'object') ? body.versions : JSON.parse(body).versions)); + }); + }); +}; diff --git a/lib/pkgcloud/rackspace/database/client/instances.js b/lib/pkgcloud/openstack/database/client/instances.js similarity index 80% rename from lib/pkgcloud/rackspace/database/client/instances.js rename to lib/pkgcloud/openstack/database/client/instances.js index d132118d5..928659791 100644 --- a/lib/pkgcloud/rackspace/database/client/instances.js +++ b/lib/pkgcloud/openstack/database/client/instances.js @@ -1,7 +1,7 @@ /* - * instances.js: Instance methods for working with database instances from Rackspace Cloud + * instances.js: Instance methods for working with database instances from Openstack Trove * - * (C) 2011 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ @@ -45,14 +45,14 @@ exports.createInstance = function createInstance(options, callback) { // If the 'databases' are specified we create a template for each database name. if (options && options['databases'] && - typeof options['databases'] === 'array' && + Array.isArray(options['databases']) && options['databases'].length > 0) { options['databases'].forEach(function (item, idx) { if (typeof item === 'string') { // This template is according to the defaults of rackspace. options['databases'][idx] = { name: item, - character_set: "utf8", + character_set: 'utf8', collate: 'utf8_general_ci' }; } @@ -89,7 +89,7 @@ exports.createInstance = function createInstance(options, callback) { } }; - this._request(createOptions, function (err, body, response) { + this._request(createOptions, function (err, body) { return err ? callback(err) : callback(null, new Instance(self, body.instance)); @@ -124,7 +124,7 @@ exports.getInstances = function getInstances(options, callback) { requestOptions.qs = completeUrl; requestOptions.path = 'instances'; - this._request(requestOptions, function (err, body, res) { + this._request(requestOptions, function (err, body) { if (err) { return callback(err); } @@ -177,7 +177,7 @@ exports.getInstance = function getInstance(instance, callback) { var instanceId = instance instanceof Instance ? instance.id : instance; this._request({ path: 'instances/' + instanceId - }, function (err, body, response) { + }, function (err, body) { return err ? callback(err) : callback(null, new Instance(self, body.instance)); @@ -319,4 +319,71 @@ exports.setVolumeSize = function setVolumeSize(instance, newSize, callback) { message: 'Bad response from resize action.' }), callback); }); -}; \ No newline at end of file +}; + +// Enable the root user for the given database instance. +// ### @instance {string | Object} The ID of the instance or a instance or Instance class (required) +// ### @callback {Function} Function to continue, the call is cb(error, res) +exports.enableRootUser = function enableRootUser(instance, callback) { + // Check for instance + if (typeof instance === 'function' || typeof instance === 'undefined') { + return errs.handle(errs.create({ + message: 'An instance is required.' + }), Array.prototype.slice.call(arguments).pop()); + } + + var instanceId = instance instanceof Instance ? instance.id : instance; + + var options = { + method: 'POST', + path: 'instances/' + instanceId + '/root' + }; + + this._request(options, function (err, body, response) { + if (err) { + return callback(err); + } + + if (response.statusCode === 200) { + return callback(null, body); + } + + return errs.handle(errs.create({ + message: 'Bad response from enabling root user' + }), callback); + }); +}; + +// List the root status for the database instance. +// `res` will be an object like `{ rootEnabled: true }` +// ### @instance {string | Object} The ID of the instance or a instance or Instance class (required) +// ### @callback {Function} Function to continue, the call is cb(error, res) +exports.listRootStatus = function listRootStatus(instance, callback) { + // Check for instance + if (typeof instance === 'function' || typeof instance === 'undefined') { + return errs.handle(errs.create({ + message: 'An instance is required.' + }), Array.prototype.slice.call(arguments).pop()); + } + + var instanceId = instance instanceof Instance ? instance.id : instance; + + var options = { + method: 'GET', + path: 'instances/' + instanceId + '/root' + }; + + this._request(options, function (err, body, response) { + if (err) { + return callback(err); + } + + if (response.statusCode === 200) { + return callback(null, body); + } + + return errs.handle(errs.create({ + message: 'Bad response from listing root-enabled status' + }), callback); + }); +}; diff --git a/lib/pkgcloud/rackspace/database/client/users.js b/lib/pkgcloud/openstack/database/client/users.js similarity index 91% rename from lib/pkgcloud/rackspace/database/client/users.js rename to lib/pkgcloud/openstack/database/client/users.js index c292f58e8..536d338cd 100644 --- a/lib/pkgcloud/rackspace/database/client/users.js +++ b/lib/pkgcloud/openstack/database/client/users.js @@ -1,7 +1,7 @@ /* - * users.js: Client methods for working with users on database within instances from Rackspace Cloud + * users.js: Client methods for working with users on database within instances from Openstack Trove. * - * (C) 2012 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ @@ -10,7 +10,6 @@ var pkgcloud = require('../../../../../lib/pkgcloud'), Instance = pkgcloud.providers.rackspace.database.Instance, User = pkgcloud.providers.rackspace.database.User, errs = require('errs'), - async = require('async'), qs = require('querystring'); // Create a User(s) for a Database. @@ -28,21 +27,20 @@ exports.createUser = function createUser(options, callback) { instanceId, count = 0; - // Check for options - if (!options || typeof options === 'function') { - return errs.handle(errs.create({ - message: 'Options required for create an instance.' - }), options); - } + function makeRequest() { + var createOptions = { + method: 'POST', + path: 'instances/' + instanceId + '/users', + body: { + users: users + } + }; - // Not as clean as I'd like but async didn't seem to work properly. - if (options instanceof Array) { - for (var i = 0; i < options.length; i++) { - assessOptions(options[i]); - } - } - else { - assessOptions(options); + self._request(createOptions, function (err, body, response) { + return err + ? callback(err) + : callback(null, response); + }); } function assessOptions(opts) { @@ -51,7 +49,8 @@ exports.createUser = function createUser(options, callback) { // Check for required options. ['username', 'password', 'database', 'instance'].forEach(function (required) { - if (!opts[required]) { + // API allows for individual or multiple databases + if (!opts[required] && ( required === 'database' && !opts['databases'])) { if (calledBack) { return; } errs.handle(errs.create({ message: 'Options. ' + required + ' is a required argument' @@ -86,21 +85,29 @@ exports.createUser = function createUser(options, callback) { if (opts && opts['databases'] && opts['databases'] instanceof Array && opts['databases'].length > 0) { - opts['databases'].forEach(function (item, idx) { + opts['databases'].forEach(function (item) { if (typeof item === 'string') { - databases.push(item); + databases.push({'name' : item}); } else if (item instanceof Database) { - databases.push(item.name); + databases.push({'name' : item.name}); } }); } + if (opts && opts['database'] && typeof opts['database'] === 'string') { + databases.push({ 'name' : opts['database']}); + } + + if (opts && opts['database'] && opts['database'] instanceof Database) { + databases.push({ 'name' : opts['database'].name}); + } + if (opts && opts['databases'] && typeof opts['databases'] === 'string') { - databases.push(opts['databases']); + databases.push({ 'name' : opts['databases']}); } if (opts && opts['databases'] && opts['databases'] instanceof Database) { - databases.push(opts['databases'].name); + databases.push({ 'name' : opts['databases'].name}); } // Check for invalid characters and permitted length of database databases.forEach(function (db) { @@ -135,20 +142,21 @@ exports.createUser = function createUser(options, callback) { } } - function makeRequest() { - var createOptions = { - method: 'POST', - path: 'instances/' + instanceId + '/users', - body: { - users: users - } - }; + // Check for options + if (!options || typeof options === 'function') { + return errs.handle(errs.create({ + message: 'Options required for create an instance.' + }), options); + } - self._request(createOptions, function (err, body, response) { - return err - ? callback(err) - : callback(null, response); - }); + // Not as clean as I'd like but async didn't seem to work properly. + if (options instanceof Array) { + for (var i = 0; i < options.length; i++) { + assessOptions(options[i]); + } + } + else { + assessOptions(options); } }; @@ -189,13 +197,12 @@ exports.getUsers = function getUsers(options, callback) { requestOptions.qs = completeUrl; requestOptions.path = 'instances/' + instanceId + '/users'; - this._request(requestOptions, function (err, body, res) { + this._request(requestOptions, function (err, body) { if (err) { return callback(err); } var marker = null; - if (body.links && body.links.length > 0) { marker = qs.parse(body.links[0].href.split('?').pop()).marker; } diff --git a/lib/pkgcloud/rackspace/database/database.js b/lib/pkgcloud/openstack/database/database.js similarity index 59% rename from lib/pkgcloud/rackspace/database/database.js rename to lib/pkgcloud/openstack/database/database.js index 855b6da85..0ef39e94b 100644 --- a/lib/pkgcloud/rackspace/database/database.js +++ b/lib/pkgcloud/openstack/database/database.js @@ -1,18 +1,18 @@ /* - * databases.js: Rackspace Cloud Database within a Instance + * databases.js: Openstack Trove Database within a Instance * - * (C) 2011 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ -var utile = require('utile'), +var util = require('util'), model = require('../../core/base/model'); var Database = exports.Database = function Database(client, details) { model.Model.call(this, client, details); }; -utile.inherits(Database, model.Model); +util.inherits(Database, model.Model); Database.prototype.refresh = function (callback) { this.client.getDatabase(this, callback); @@ -22,6 +22,11 @@ Database.prototype._setProperties = function (details) { // @todo Check for characters that CANNOT be used in the Database Name // @todo There is a length restrictions for database name. 64 this.name = details.name; - if (details.characterSet) this.characterSet = details.characterSet; - if (details.collation) this.collation = details.collation; -}; \ No newline at end of file + if (details.characterSet) { + this.characterSet = details.characterSet; + } + + if (details.collation) { + this.collation = details.collation; + } +}; diff --git a/lib/pkgcloud/rackspace/database/flavor.js b/lib/pkgcloud/openstack/database/flavor.js similarity index 73% rename from lib/pkgcloud/rackspace/database/flavor.js rename to lib/pkgcloud/openstack/database/flavor.js index 981f3d1e2..cdfa41e0a 100644 --- a/lib/pkgcloud/rackspace/database/flavor.js +++ b/lib/pkgcloud/openstack/database/flavor.js @@ -1,18 +1,18 @@ /* - * flavor.js: Rackspace Cloud Databases flavor + * flavor.js: Openstack Trove Databases flavor * - * (C) 2011 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ -var utile = require('utile'), +var util = require('util'), base = require('../../openstack/compute/flavor'); var Flavor = exports.Flavor = function Flavor(client, details) { base.Flavor.call(this, client, details); }; -utile.inherits(Flavor, base.Flavor); +util.inherits(Flavor, base.Flavor); Flavor.prototype._setProperties = function (details) { var selfLink = details.links.filter(function (link) { diff --git a/lib/pkgcloud/openstack/database/index.js b/lib/pkgcloud/openstack/database/index.js new file mode 100644 index 000000000..a46b22c79 --- /dev/null +++ b/lib/pkgcloud/openstack/database/index.js @@ -0,0 +1,16 @@ +/* + * index.js: Top-level include for the Openstack Trove module + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +exports.Client = require('./client').Client; +exports.Flavor = require('./flavor').Flavor; +exports.Instance = require('./instance').Instance; +exports.Database = require('./database').Database; +exports.User = require('./user').User; + +exports.createClient = function createClient(options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/rackspace/database/instance.js b/lib/pkgcloud/openstack/database/instance.js similarity index 90% rename from lib/pkgcloud/rackspace/database/instance.js rename to lib/pkgcloud/openstack/database/instance.js index 1d4a039fc..ac09d8397 100644 --- a/lib/pkgcloud/rackspace/database/instance.js +++ b/lib/pkgcloud/openstack/database/instance.js @@ -1,11 +1,11 @@ /* - * instances.js: Rackspace Cloud Database Instance + * instances.js: Openstack Trove Database Instance * - * (C) 2011 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ -var utile = require('utile'), +var util = require('util'), model = require('../../core/base/model'), computeStatus = require('../../common/status').compute; @@ -13,7 +13,7 @@ var Instance = exports.Instance = function Instance(client, details) { model.Model.call(this, client, details); }; -utile.inherits(Instance, model.Model); +util.inherits(Instance, model.Model); Instance.prototype.refresh = function (callback) { this.client.getInstance(this, callback); @@ -62,4 +62,4 @@ Instance.prototype._setProperties = function (details) { this.hostname = details.hostname || this.hostname; this.volume = details.volume || this.volume; this.original = this.rackspace = details; -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/database/user.js b/lib/pkgcloud/openstack/database/user.js similarity index 69% rename from lib/pkgcloud/rackspace/database/user.js rename to lib/pkgcloud/openstack/database/user.js index 677548640..ed35ef82b 100644 --- a/lib/pkgcloud/rackspace/database/user.js +++ b/lib/pkgcloud/openstack/database/user.js @@ -1,18 +1,18 @@ /* - * user.js: Rackspace Cloud Database User + * user.js: Openstack Trove Database User * - * (C) 2011 Nodejitsu Inc. + * (C) 2014 Hewlett-Packard Development Company, L.P. * */ -var utile = require('utile'), +var util = require('util'), model = require('../../core/base/model'); var User = exports.User = function User(client, details) { model.Model.call(this, client, details); }; -utile.inherits(User, model.Model); +util.inherits(User, model.Model); User.prototype.refresh = function (callback) { this.client.getUser(this, callback); @@ -21,4 +21,4 @@ User.prototype.refresh = function (callback) { User.prototype._setProperties = function (details) { this.name = details.name; this.password = details.password; -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/openstack/identity/client/index.js b/lib/pkgcloud/openstack/identity/client/index.js index c26bd21c1..e74c259d6 100644 --- a/lib/pkgcloud/openstack/identity/client/index.js +++ b/lib/pkgcloud/openstack/identity/client/index.js @@ -6,10 +6,9 @@ * */ -var utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), - openstack = require('../../client'), - _ = require('underscore'); + openstack = require('../../client'); var Client = exports.Client = function (options) { openstack.Client.call(this, options); @@ -17,7 +16,7 @@ var Client = exports.Client = function (options) { this.serviceType = null; }; -utile.inherits(Client, openstack.Client); +util.inherits(Client, openstack.Client); /** * Client._getUrl @@ -67,7 +66,7 @@ Client.prototype.validateToken = function (token, belongsTo, callback) { }; } - this._request(options, function (err, body, res) { + this._request(options, function (err, body) { return err ? callback(err) : callback(err, body); @@ -94,10 +93,10 @@ Client.prototype.getTenantInfo = function (tenantId, callback) { path: urlJoin('/v2.0/tenants', tenantId ? tenantId : '') }; - this._request(options, function (err, body, res) { + this._request(options, function (err, body) { return err ? callback(err) : callback(err, body); }); -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/openstack/identity/index.js b/lib/pkgcloud/openstack/identity/index.js index 9081e1127..72822bf84 100644 --- a/lib/pkgcloud/openstack/identity/index.js +++ b/lib/pkgcloud/openstack/identity/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the OpenStack identity module * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/openstack/index.js b/lib/pkgcloud/openstack/index.js index eb1d6b4bf..ffec3b774 100644 --- a/lib/pkgcloud/openstack/index.js +++ b/lib/pkgcloud/openstack/index.js @@ -1,10 +1,15 @@ /* * index.js: Top-level include for the OpenStack module. * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ +exports.blockstorage = require('./blockstorage'); exports.compute = require('./compute'); +exports.identity = require('./identity'); +exports.orchestration = require('./orchestration'); +exports.network = require('./network'); exports.storage = require('./storage'); -exports.identity = require('./identity'); \ No newline at end of file +exports.database = require('./database'); +exports.cdn = require('./cdn'); diff --git a/lib/pkgcloud/openstack/network/client/index.js b/lib/pkgcloud/openstack/network/client/index.js new file mode 100644 index 000000000..2d00bce1a --- /dev/null +++ b/lib/pkgcloud/openstack/network/client/index.js @@ -0,0 +1,51 @@ +/* + * client.js: Client for Openstack networking + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + urlJoin = require('url-join'), + openstack = require('../../client'), + NetworkClient = require('../networkClient').NetworkClient, + _ = require('lodash'); + +var Client = exports.Client = function (options) { + openstack.Client.call(this, options); + + this.models = { + Network: require('../network').Network, + Subnet: require('../subnet').Subnet, + Port: require('../port').Port, + SecurityGroup: require('../securityGroup').SecurityGroup, + SecurityGroupRule: require('../securityGroupRule').SecurityGroupRule + }; + + _.extend(this, require('./networks')); + _.extend(this, require('./subnets')); + _.extend(this, require('./ports')); + _.extend(this, require('./securityGroups')); + _.extend(this, require('./securityGroupRules')); + + this.serviceType = 'network'; +}; + +util.inherits(Client, openstack.Client); +_.extend(Client.prototype, NetworkClient.prototype); + +/** + * client._getUrl + * + * @description get the url for the current networking service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function(options) { + if (options.path) { + options.path = urlJoin('v2.0', options.path); + } + return NetworkClient.prototype._getUrl.call(this, options); +}; diff --git a/lib/pkgcloud/openstack/network/client/networks.js b/lib/pkgcloud/openstack/network/client/networks.js new file mode 100644 index 000000000..a417ec949 --- /dev/null +++ b/lib/pkgcloud/openstack/network/client/networks.js @@ -0,0 +1,172 @@ +/* + * networks.js: Instance methods for working with networks + * for Openstack networking + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var urlJoin = require('url-join'); + +var networksResourcePath = '/networks'; + +// Declaring variables for helper functions defined later +var _convertNetworkToWireFormat; + +/** + * client.getNetworks + * + * @description get the list of networks for an account + * + * @param {object|Function} options + * @param {Function} callback + */ +exports.getNetworks = function (options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var getNetworkOpts = { + path: networksResourcePath + }; + + this._request(getNetworkOpts, function (err, body) { + if (err) { + return callback(err); + } + else if (!body || !body.networks || !(body.networks instanceof Array)) { + return new Error('Malformed API Response'); + } + + return callback(err, body.networks.map(function (network) { + return new self.models.Network(self, network); + })); + }); +}; + +/** + * client.getNetwork + * + * @description get the details for a specific network + * + * @param {String|object} network the network or networkId + * @param callback + */ +exports.getNetwork = function (network, callback) { + var self = this, + networkId = network instanceof this.models.Network ? network.id : network; + self.emit('log::trace', 'Getting details for network', networkId); + this._request({ + path: urlJoin(networksResourcePath, networkId), + method: 'GET' + }, function (err, body) { + if (err) { + return callback(err); + } + + if (!body ||!body.network) { + return new Error('Malformed API Response'); + } + + callback(err, new self.models.Network(self, body.network)); + }); +}; + +/** + * client.createNetwork + * + * @description create a new network + * + * @param {object} options + * @param {String} options.name the name of the new network + * @param callback + */ +exports.createNetwork = function (options, callback) { + var self = this, + network = typeof options === 'object' ? options : { 'name' : options}; + + network = _convertNetworkToWireFormat(network); + + var createNetworkOpts = { + method: 'POST', + path: networksResourcePath, + body: { 'network' : network} + }; + + self.emit('log::trace', 'Creating network', network); + this._request(createNetworkOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.Network(self, body.network)); + }); +}; + +/** + * client.updateNetwork + * + * @description update an existing network + * + * @param {object} options + * @param callback + */ +exports.updateNetwork = function (network, callback) { + var self = this, + networkId = network.id, + networkToUpdate = _convertNetworkToWireFormat(network); + + var updateNetworkOpts = { + method: 'PUT', + path: urlJoin(networksResourcePath, networkId), + contentType: 'application/json', + body: { 'network' : networkToUpdate} + }; + + self.emit('log::trace', 'Updating network', networkId); + this._request(updateNetworkOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.Network(self, body.network)); + }); +}; + +/** + * client.destroyNetwork + * + * @description Delete a specific network + * + * @param {String|object} network the network or network ID + * @param callback + */ +exports.destroyNetwork = function (network, callback) { + var self = this, + networkId = network instanceof this.models.Network ? network.id : network; + self.emit('log::trace', 'Deleting network', networkId); + this._request({ + path: urlJoin(networksResourcePath,networkId), + method: 'DELETE' + }, function (err) { + if (err) { + return callback(err); + } + callback(err, networkId); + }); +}; + +/** + * _convertNetworkToWireFormat + * + * @description convert Network instance into its wire representation. + * + * @param {object} details the Network instance. + */ +_convertNetworkToWireFormat = function (details){ + var wireFormat = {}; + wireFormat.admin_state_up = details.admin_state_up || details.adminStateUp; + wireFormat.name = details.name; + wireFormat.shared = details.shared; + wireFormat.tenant_id = details.tenantId || details.tenant_id; + return wireFormat; +}; diff --git a/lib/pkgcloud/openstack/network/client/ports.js b/lib/pkgcloud/openstack/network/client/ports.js new file mode 100644 index 000000000..2a9242665 --- /dev/null +++ b/lib/pkgcloud/openstack/network/client/ports.js @@ -0,0 +1,176 @@ +/* + * ports.js: Instance methods for working with ports + * for Openstack porting + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var urlJoin = require('url-join'); + +var portsResourcePath = '/ports'; + +// Declaring variables for helper functions defined later +var _convertPortToWireFormat; + +/** + * client.getPorts + * + * @description get the list of ports for an account + * + * @param {object|Function} options + * @param {Function} callback + */ +exports.getPorts = function (options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var getPortOpts = { + path: portsResourcePath + }; + + this._request(getPortOpts, function (err, body) { + if (err) { + return callback(err); + } + else if (!body || !body.ports || !(body.ports instanceof Array)) { + return new Error('Malformed API Response'); + } + + return callback(err, body.ports.map(function (port) { + return new self.models.Port(self, port); + })); + }); +}; + +/** + * client.getPort + * + * @description get the details for a specific port + * + * @param {String|object} port the port or portId + * @param callback + */ +exports.getPort = function (port, callback) { + var self = this, + portId = port instanceof this.models.Port ? port.id : port; + self.emit('log::trace', 'Getting details for port', portId); + this._request({ + path: urlJoin(portsResourcePath, portId), + method: 'GET' + }, function (err, body) { + if (err) { + return callback(err); + } + + if (!body ||!body.port) { + return new Error('Malformed API Response'); + } + + callback(err, new self.models.Port(self, body.port)); + }); +}; + +/** + * client.createPort + * + * @description create a new port + * + * @param {object} options + * @param {String} options.name the name of the new port + * @param callback + */ +exports.createPort = function (options, callback) { + var self = this, + port = typeof options === 'object' ? options : { 'name' : options}; + + port = _convertPortToWireFormat(port); + + var createPortOpts = { + method: 'POST', + path: portsResourcePath, + body: { 'port' : port} + }; + + self.emit('log::trace', 'Creating port', port); + this._request(createPortOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.Port(self, body.port)); + }); +}; + +/** + * client.updatePort + * + * @description update an existing port + * + * @param {object} options + * @param callback + */ +exports.updatePort = function (port, callback) { + var self = this, + portId = port.id; + + port = _convertPortToWireFormat(port); + var updatePortOpts = { + method: 'PUT', + path: urlJoin(portsResourcePath, portId), + contentType: 'application/json', + body: { 'port' : port} + }; + + self.emit('log::trace', 'Updating port', port); + this._request(updatePortOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.Port(self, body.port)); + }); +}; + +/** + * client.destroyPort + * + * @description Delete a specific port + * + * @param {String|object} port the port or port ID + * @param callback + */ +exports.destroyPort = function (port, callback) { + var self = this, + portId = port instanceof this.models.Port ? port.id : port; + self.emit('log::trace', 'Deleting port', portId); + this._request({ + path: urlJoin(portsResourcePath,portId), + method: 'DELETE' + }, function (err) { + if (err) { + return callback(err); + } + callback(err, portId); + }); +}; + +/** + * _convertPortToWireFormat + * + * @description convert Port instance into its wire representation. + * + * @param {object} details the Port instance. + */ +_convertPortToWireFormat = function (details){ + var wireFormat = {}; + wireFormat.status = details.status; + wireFormat.name = details.name; + wireFormat.admin_state_up = details.admin_state_up || details.adminStateUp; + wireFormat.tenant_id = details.tenant_id || details.tenantId; + wireFormat.mac_address = details.mac_address || details.macAddress; + wireFormat.fixed_ips = details.fixed_ips || details.fixedIps; + wireFormat.security_groups = details.security_groups || details.securityGroups; + wireFormat.network_id = details.network_id || details.networkId; + return wireFormat; +}; diff --git a/lib/pkgcloud/openstack/network/client/securityGroupRules.js b/lib/pkgcloud/openstack/network/client/securityGroupRules.js new file mode 100644 index 000000000..dbd6a135f --- /dev/null +++ b/lib/pkgcloud/openstack/network/client/securityGroupRules.js @@ -0,0 +1,156 @@ +/* + * securityGroupRules.js: Instance methods for working with security group rules + * for Openstack Networking + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var urlJoin = require('url-join'); + +var securityGroupRulesResourcePath = '/security-group-rules'; + +// Declaring variables for helper functions defined later +var _convertSecurityGroupRuleToWireFormat; + +/** + * client.getSecurityGroupRules + * + * @description get the list of security group rules for an account + * + * @param {object|Function} options + * @param {Function} callback + */ +exports.getSecurityGroupRules = function (options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var getSecurityGroupRuleOpts = { + path: securityGroupRulesResourcePath + }; + + this._request(getSecurityGroupRuleOpts, function (err, body) { + if (err) { + return callback(err); + } + else if (!body || !body.security_group_rules || !(body.security_group_rules instanceof Array)) { + return new Error('Malformed API Response'); + } + + return callback(err, body.security_group_rules.map(function (securityGroupRule) { + return new self.models.SecurityGroupRule(self, securityGroupRule); + })); + }); +}; + +/** + * client.getSecurityGroupRule + * + * @description get the details for a specific securityGroupRule + * + * @param {String|object} securityGroupRule the securityGroupRule or securityGroupRuleId + * @param callback + */ +exports.getSecurityGroupRule = function (securityGroupRule, callback) { + var self = this, + securityGroupRuleId = securityGroupRule instanceof this.models.SecurityGroupRule ? securityGroupRule.id : securityGroupRule; + self.emit('log::trace', 'Getting details for security group rule', securityGroupRuleId); + this._request({ + path: urlJoin(securityGroupRulesResourcePath, securityGroupRuleId), + method: 'GET' + }, function (err, body) { + if (err) { + return callback(err); + } + + if (!body ||!body.security_group_rule) { + return new Error('Malformed API Response'); + } + + callback(err, new self.models.SecurityGroupRule(self, body.security_group_rule)); + }); +}; + +/** + * client.createSecurityGroupRule + * + * @description create a new securityGroupRule + * + * @param object securityGroupRule + * @param string securityGroupRule.securityGroupId The security group ID to associate with this security group rule. + * @param string securityGroupRule.direction The direction ("ingress" or "egress") in which the security group rule is applied. + * @param {string} securityGroupRule.ethertype "IPv4" or "IPv6". + * @param {number} securityGroupRule.portRangeMin The minimum port number in the range that is matched by the security group rule. + * @param {number} securityGroupRule.portRangeMax The maximum port number in the range that is matched by the security group rule. + * @param {string} securityGroupRule.protocol The protocol ("tcp", "udp", or "icmp") that is matched by the security group rule. + * @param {string} securityGroupRule.remoteGroupId The remote group ID to be associated with this security group rule. You can specify either this or remoteIpPrefix. + * @param {string} securityGroupRule.remoteIpPrefix The remote IP prefix to be associated with this security group rule. You can specify either this or remoteGroupId. + * + * @param callback + */ +exports.createSecurityGroupRule = function (securityGroupRule, callback) { + var self = this; + + securityGroupRule = _convertSecurityGroupRuleToWireFormat(securityGroupRule); + + var createSecurityGroupRuleOpts = { + method: 'POST', + path: securityGroupRulesResourcePath, + body: { 'security_group_rule': securityGroupRule } + }; + + self.emit('log::trace', 'Creating security group rule', securityGroupRule); + this._request(createSecurityGroupRuleOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.SecurityGroupRule(self, body.security_group_rule)); + }); +}; + +/** + * client.destroySecurityGroupRule + * + * @description Delete a specific securityGroupRule + * + * @param {String|object} securityGroupRule the securityGroupRule or securityGroupRule ID + * @param callback + */ +exports.destroySecurityGroupRule = function (securityGroupRule, callback) { + var self = this, + securityGroupRuleId = securityGroupRule instanceof this.models.SecurityGroupRule ? securityGroupRule.id : securityGroupRule; + self.emit('log::trace', 'Deleting security group rule', securityGroupRuleId); + this._request({ + path: urlJoin(securityGroupRulesResourcePath,securityGroupRuleId), + method: 'DELETE' + }, function (err) { + if (err) { + return callback(err); + } + callback(err, securityGroupRuleId); + }); +}; + +/** + * _convertSecurityGroupRuleToWireFormat + * + * @description convert SecurityGroupRule instance into its wire representation. + * + * @param {object} details the SecurityGroupRule instance. + */ +_convertSecurityGroupRuleToWireFormat = function (details){ + var wireFormat = {}; + wireFormat.direction = details.direction; + wireFormat.ethertype = details.ethertype; + wireFormat.security_group_id = details.security_group_id || details.securityGroupId; + wireFormat.port_range_min = details.port_range_min || details.portRangeMin; + wireFormat.port_range_max = details.port_range_max || details.portRangeMax; + wireFormat.protocol = details.protocol; + wireFormat.remote_group_id = details.remote_group_id || details.remoteGroupId; + wireFormat.remote_ip_prefix = details.remote_ip_prefix || details.remoteIpPrefix; + return wireFormat; +}; diff --git a/lib/pkgcloud/openstack/network/client/securityGroups.js b/lib/pkgcloud/openstack/network/client/securityGroups.js new file mode 100644 index 000000000..3beca6471 --- /dev/null +++ b/lib/pkgcloud/openstack/network/client/securityGroups.js @@ -0,0 +1,145 @@ +/* + * securityGroups.js: Instance methods for working with security groups + * for Openstack Networking + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var urlJoin = require('url-join'); + +var securityGroupsResourcePath = '/security-groups'; + +// Declaring variables for helper functions defined later +var _convertSecurityGroupToWireFormat; + +/** + * client.getSecurityGroups + * + * @description get the list of security groups for an account + * + * @param {object|Function} options + * @param {Function} callback + */ +exports.getSecurityGroups = function (options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var getSecurityGroupOpts = { + path: securityGroupsResourcePath + }; + + this._request(getSecurityGroupOpts, function (err, body) { + if (err) { + return callback(err); + } + else if (!body || !body.security_groups || !(body.security_groups instanceof Array)) { + return new Error('Malformed API Response'); + } + + return callback(err, body.security_groups.map(function (securityGroup) { + return new self.models.SecurityGroup(self, securityGroup); + })); + }); +}; + +/** + * client.getSecurityGroup + * + * @description get the details for a specific securityGroup + * + * @param {String|object} securityGroup the securityGroup or securityGroupId + * @param callback + */ +exports.getSecurityGroup = function (securityGroup, callback) { + var self = this, + securityGroupId = securityGroup instanceof this.models.SecurityGroup ? securityGroup.id : securityGroup; + self.emit('log::trace', 'Getting details for security group', securityGroupId); + this._request({ + path: urlJoin(securityGroupsResourcePath, securityGroupId), + method: 'GET' + }, function (err, body) { + if (err) { + return callback(err); + } + + if (!body ||!body.security_group) { + return new Error('Malformed API Response'); + } + + callback(err, new self.models.SecurityGroup(self, body.security_group)); + }); +}; + +/** + * client.createSecurityGroup + * + * @description create a new security group + * + * @param object securityGroup + * @param string securityGroup.name Name of security group. + * @param {string} securityGroup.description Description of security group. + * @param {string} securityGroup.tenantId The ID of the tenant who owns the security group. Only administrative users can specify a tenant ID other than their own. + * @param callback + */ +exports.createSecurityGroup = function (securityGroup, callback) { + var self = this; + + securityGroup = _convertSecurityGroupToWireFormat(securityGroup); + + var createSecurityGroupOpts = { + method: 'POST', + path: securityGroupsResourcePath, + body: { 'security_group': securityGroup } + }; + + self.emit('log::trace', 'Creating security group', securityGroup); + this._request(createSecurityGroupOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.SecurityGroup(self, body.security_group)); + }); +}; + +/** + * client.destroySecurityGroup + * + * @description Delete a specific securityGroup + * + * @param {String|object} securityGroup the securityGroup or securityGroup ID + * @param callback + */ +exports.destroySecurityGroup = function (securityGroup, callback) { + var self = this, + securityGroupId = securityGroup instanceof this.models.SecurityGroup ? securityGroup.id : securityGroup; + self.emit('log::trace', 'Deleting security group', securityGroupId); + this._request({ + path: urlJoin(securityGroupsResourcePath,securityGroupId), + method: 'DELETE' + }, function (err) { + if (err) { + return callback(err); + } + callback(err, securityGroupId); + }); +}; + +/** + * _convertSecurityGroupToWireFormat + * + * @description convert SecurityGroup instance into its wire representation. + * + * @param {object} details the SecurityGroup instance. + */ +_convertSecurityGroupToWireFormat = function (details){ + var wireFormat = {}; + wireFormat.name = details.name; + wireFormat.description = details.description; + wireFormat.tenant_id = details.tenant_id || details.tenantId; + return wireFormat; +}; diff --git a/lib/pkgcloud/openstack/network/client/subnets.js b/lib/pkgcloud/openstack/network/client/subnets.js new file mode 100644 index 000000000..1559aa796 --- /dev/null +++ b/lib/pkgcloud/openstack/network/client/subnets.js @@ -0,0 +1,181 @@ +/* + * networks.js: Instance methods for working with networks + * for Openstack networking + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var urlJoin = require('url-join'); + +var subnetsResourcePath = '/subnets'; + +// Declaring variables for helper functions defined later +var _convertSubnetToWireFormat; + +/** + * client.getSubnets + * + * @description get the list of networks for an account + * + * @param {object|Function} options + * @param {Number} [options.limit] the number of records to return + * @param {String} [options.marker] Marker value. Operation returns object names that are greater than this value. + * @param {String} [options.end_marker] Operation returns object names that are less than this value. + * @param {Function} callback + */ +exports.getSubnets = function (options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var getSubnetOpts = { + path: subnetsResourcePath, + }; + + this._request(getSubnetOpts, function (err, body) { + if (err) { + return callback(err); + } + else if (!body ||!body.subnets || !(body.subnets instanceof Array)) { + return new Error('Malformed API Response'); + } + + return callback(err, body.subnets.map(function (subnet) { + return new self.models.Subnet(self, subnet); + })); + }); +}; + +/** + * client.getSubnet + * + * @description get the details for a specific network + * + * @param {String|object} subnet the subnet or subnetId + * @param callback + */ +exports.getSubnet = function (subnet, callback) { + var self = this, + subnetId = subnet instanceof this.models.Subnet ? subnet.id : subnet; + self.emit('log::trace', 'Getting details for subnet', subnetId); + this._request({ + path: urlJoin(subnetsResourcePath, subnetId), + method: 'GET' + }, function (err, body) { + if (err) { + return callback(err); + } + + if (!body || !body.subnet) { + return new Error('Malformed API Response'); + } + + callback(err, new self.models.Subnet(self, body.subnet)); + }); +}; + +/** + * client.createSubnet + * + * @description create a new subnet + * + * @param {object} options + * @param {String} options.name the name of the new subnet + * @param callback + */ +exports.createSubnet = function (options, callback) { + var self = this, + subnet = typeof options === 'object' ? options : { 'name' : options}; + + subnet = _convertSubnetToWireFormat(subnet); + + var createSubnetOpts = { + method: 'POST', + path: subnetsResourcePath, + body: { 'subnet' : subnet} + }; + + self.emit('log::trace', 'Creating subnet', subnet); + this._request(createSubnetOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.Subnet(self, body.subnet)); + }); +}; + +/** + * client.updateSubnet + * + * @description update an existing subnet + * + * @param {object} options + * @param callback + */ +exports.updateSubnet = function (subnet, callback) { + var self = this, + subnetId = subnet.id, + subnetToUpdate = _convertSubnetToWireFormat(subnet); + + var updateSubnetOpts = { + method: 'PUT', + path: urlJoin(subnetsResourcePath, subnetId), + contentType: 'application/json', + body: { 'subnet' : subnetToUpdate} + }; + + self.emit('log::trace', 'Updating subnet', subnetId); + this._request(updateSubnetOpts, function (err,body) { + return err + ? callback(err) + : callback(err, new self.models.Subnet(self, body.subnet)); + }); +}; + +/** + * client.destroySubnet + * + * @description Delete a specific network + * + * @param {String|object} subnet the subnet or subnet ID + * @param callback + */ +exports.destroySubnet = function (subnet, callback) { + var self = this, + subnetId = subnet instanceof this.models.Subnet ? subnet.id : subnet; + self.emit('log::trace', 'Deleting subnet', subnetId); + this._request({ + path: urlJoin(subnetsResourcePath,subnetId), + method: 'DELETE' + }, function (err) { + if (err) { + return callback(err); + } + callback(err, subnetId); + }); +}; + +/** + * _convertSubnetToWireFormat + * + * @description convert Subnet instance into its wire representation. + * + * @param {object} details the Subnet instance. + */ +_convertSubnetToWireFormat = function (details){ + + var wireFormat = {}; + wireFormat.name = details.name; + wireFormat.network_id = details.networkId || details.network_id; + wireFormat.tenant_id = details.tenantId || details.tenant_id; + wireFormat.allocation_pools = details.allocationPools || details.allocation_pools; + wireFormat.gateway_ip = details.gatewayIp || details.gateway_ip; + wireFormat.ip_version = details.ipVersion || details.ip_version; + wireFormat.cidr = details.cidr; + wireFormat.enable_dhcp = details.enable_dhcp == null ? details.enableDhcp : details.enable_dhcp; + + return wireFormat; +}; diff --git a/lib/pkgcloud/openstack/network/index.js b/lib/pkgcloud/openstack/network/index.js new file mode 100644 index 000000000..4199b7447 --- /dev/null +++ b/lib/pkgcloud/openstack/network/index.js @@ -0,0 +1,17 @@ +/* + * index.js: Top-level include for the Openstack networking client. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +exports.Client = require('./client').Client; +exports.Network = require('./network').Network; +exports.Subnet = require('./subnet').Subnet; +exports.Port = require('./port').Port; +exports.SecurityGroup = require('./securityGroup').SecurityGroup; +exports.SecurityGroupRule = require('./securityGroup').SecurityGroupRule; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/openstack/network/network.js b/lib/pkgcloud/openstack/network/network.js new file mode 100644 index 000000000..69aa46a70 --- /dev/null +++ b/lib/pkgcloud/openstack/network/network.js @@ -0,0 +1,31 @@ +/* + * network.js: Openstack Network object. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + base = require('../../core/network/network'), + _ = require('lodash'); + +var Network = exports.Network = function Network(client, details) { + base.Network.call(this, client, details); +}; + +util.inherits(Network, base.Network); + +Network.prototype._setProperties = function (details) { + this.name = details.name || this.name; + this.status = details.status || this.status; + this.adminStateUp = details.admin_state_up || this.adminStateUp; + this.id = details.id || this.id; + this.shared = details.shared || this.shared || 0; + this.tenantId = details.tenant_id || this.tenantId; + this.subnets = details.subnets || this.subnets; +}; + +Network.prototype.toJSON = function () { + return _.pick(this, ['name', 'id', 'adminStateUp', 'status', 'shared', + 'tenantId', 'subnets']); +}; diff --git a/lib/pkgcloud/openstack/network/networkClient.js b/lib/pkgcloud/openstack/network/networkClient.js new file mode 100644 index 000000000..d98763a4f --- /dev/null +++ b/lib/pkgcloud/openstack/network/networkClient.js @@ -0,0 +1,48 @@ +/* + * networkingClient.js: A base NetworkClient for Openstack networking clients + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var urlJoin = require('url-join'); + +var Client = exports.NetworkClient = function () { + this.serviceType = 'network'; +}; + +/** + * client._getUrl + * + * @description get the url for the current network service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function (options) { + options = options || {}; + var fragment = ''; + + if (options.network) { + if (options.method === 'GET') { + fragment = encodeURIComponent(options.network); + } + } + + if (options.path) { + fragment = urlJoin(fragment, options.path.split('/').map(encodeURIComponent).join('/')); + } + + var serviceUrl = options.serviceType ? this._identity.getServiceEndpointUrl({ + serviceType: options.serviceType, + region: this.region + }) : this._serviceUrl; + + if (fragment === '' || fragment === '/') { + return serviceUrl; + } + + return urlJoin(serviceUrl, fragment); + +}; diff --git a/lib/pkgcloud/openstack/network/port.js b/lib/pkgcloud/openstack/network/port.js new file mode 100644 index 000000000..1b5a3bcf8 --- /dev/null +++ b/lib/pkgcloud/openstack/network/port.js @@ -0,0 +1,39 @@ +/* + * network.js: Openstack Port object. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + base = require('../../core/network/port'), + _ = require('lodash'); + +var Port = exports.Port = function Port(client, details) { + base.Port.call(this, client, details); +}; + +util.inherits(Port, base.Port); + +Port.prototype._setProperties = function (details) { + + this.status = details.status || this.status; + this.name = details.name || this.name; + this.allowedAddressPairs = details.allowed_address_pairs || this.allowedAddressPairs; + this.adminStateUp = details.admin_state_up || this.adminStateUp; + this.networkId = details.network_id || this.networkId; + this.tenantId = details.tenant_id || this.tenantId; + this.extraDhcpOpts = details.extra_dhcp_opts || this.extraDhcpOpts; + this.deviceOwner = details.device_owner || this.deviceOwner; + this.macAddress = details.mac_address || this.macAddress; + this.fixedIps = details.fixed_ips || this.fixedIps; + this.id = details.id || this.id; + this.securityGroups = details.security_groups || this.securityGroups; + this.deviceId = details.device_id || this.deviceId; +}; + +Port.prototype.toJSON = function () { + return _.pick(this, ['status', 'name', 'allowedAddressPairs', 'adminStateUp', + 'networkId', 'tenantId', 'extraDhcpOpts', 'deviceOwner', + 'macAddress', 'fixedIps', 'id', 'securityGroups', 'deviceId']); +}; diff --git a/lib/pkgcloud/openstack/network/securityGroup.js b/lib/pkgcloud/openstack/network/securityGroup.js new file mode 100644 index 000000000..cb68fa4dd --- /dev/null +++ b/lib/pkgcloud/openstack/network/securityGroup.js @@ -0,0 +1,30 @@ +/* + * network.js: Openstack Security Group object. + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var util = require('util'), + base = require('../../core/network/securityGroup'), + _ = require('lodash'); + +var SecurityGroup = exports.SecurityGroup = function SecurityGroup(client, details) { + base.SecurityGroup.call(this, client, details); +}; + +util.inherits(SecurityGroup, base.SecurityGroup); + +SecurityGroup.prototype._setProperties = function (details) { + + this.id = details.id || this.id; + this.name = details.name || this.name; + this.description = details.description || this.id; + this.tenantId = details.tenant_id || this.tenantId; + this.securityGroupRules = details.security_group_rules || this.securityGroupRules; +}; + +SecurityGroup.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'description', 'securityGroupRules', 'tenantId']); +}; diff --git a/lib/pkgcloud/openstack/network/securityGroupRule.js b/lib/pkgcloud/openstack/network/securityGroupRule.js new file mode 100644 index 000000000..f80580c35 --- /dev/null +++ b/lib/pkgcloud/openstack/network/securityGroupRule.js @@ -0,0 +1,36 @@ +/* + * network.js: Openstack Security Group Rule object. + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var util = require('util'), + base = require('../../core/network/securityGroupRule'), + _ = require('lodash'); + +var SecurityGroupRule = exports.SecurityGroupRule = function SecurityGroupRule(client, details) { + base.SecurityGroupRule.call(this, client, details); +}; + +util.inherits(SecurityGroupRule, base.SecurityGroupRule); + +SecurityGroupRule.prototype._setProperties = function (details) { + this.id = details.id || this.id; + this.direction = details.direction || this.direction; + this.ethertype = details.ethertype || this.id; + this.securityGroupId = details.security_group_id || this.securityGroupId; + this.portRangeMin = details.port_range_min || this.portRangeMin; + this.portRangeMax = details.port_range_max || this.portRangeMax; + this.protocol = details.protocol || this.protocol; + this.remoteGroupId = details.remote_group_id || this.remoteGroupId; + this.remoteIpPrefix = details.remote_ip_prefix || this.remoteIpPrefix; + this.tenantId = details.tenant_id || this.tenantId; +}; + +SecurityGroupRule.prototype.toJSON = function () { + return _.pick(this, ['id', 'direction', 'ethertype', 'securityGroupId', + 'portRangeMin', 'portRangeMax', 'protocol', + 'remoteGroupId', 'remoteIpPrefix', 'tenantId']); +}; diff --git a/lib/pkgcloud/openstack/network/subnet.js b/lib/pkgcloud/openstack/network/subnet.js new file mode 100644 index 000000000..0853a234c --- /dev/null +++ b/lib/pkgcloud/openstack/network/subnet.js @@ -0,0 +1,35 @@ +/* + * network.js: Openstack Subnet object. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var util = require('util'), + base = require('../../core/network/subnet'), + _ = require('lodash'); + +var Subnet = exports.Subnet = function Subnet(client, details) { + base.Subnet.call(this, client, details); +}; + +util.inherits(Subnet, base.Subnet); + +Subnet.prototype._setProperties = function (details) { + this.name = details.name || this.name; + this.enableDhcp = details.enable_dhcp || this.enableDhcp; + this.networkId = details.network_id || this.networkId; + this.id = details.id || this.id; + this.ipVersion = details.ip_version || this.ipVersion; + this.tenantId = details.tenant_id || this.tenantId; + this.gatewayIp = details.gateway_ip || this.gatewayIp; + this.cidr = details.cidr || this.cidr; + this.dnsNameServers = details.dns_nameservers || this.dnsNameServers; + this.hostRoutes = details.host_routes || this.hostRoutes; + this.allocationPools = details.allocation_pools || this.allocationPools; +}; + +Subnet.prototype.toJSON = function () { + return _.pick(this, ['name', 'id', 'networkId', 'ipVersion', + 'tenantId', 'gatewayIp', 'dnsNameServers', 'allocationPools', 'hostRoutes']); +}; diff --git a/lib/pkgcloud/openstack/orchestration/client/events.js b/lib/pkgcloud/openstack/orchestration/client/events.js new file mode 100644 index 000000000..8b0a7c862 --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/client/events.js @@ -0,0 +1,139 @@ +/* + * events.js: Instance methods for working with stack events from OpenStack Orchestration + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ +var pkgcloud = require('../../../../../lib/pkgcloud'), + urlJoin = require('url-join'), + orchestration = pkgcloud.providers.openstack.orchestration; + +var _urlPrefix = '/stacks'; + +/** + * client.getEvent + * + * @description get the event for a provided stack, resource, and eventName + * + * @param {object|string} stack stack or stackName + * @param {object|string} resource resource or resourceName + * @param {string} eventId eventId to query for + * @param {function} callback f(err, event) + * @returns {*} + */ +exports.getEvent = function (stack, resource, eventId, callback) { + var self = this; + + function getEvent(stack) { + return self._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id, 'resources', + resource instanceof orchestration.Resource ? resource.name : resource, 'events', eventId) + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body.event); + + }); + } + + if (stack instanceof orchestration.Stack) { + getEvent(stack); + } + else if (typeof stack === 'string') { + self.getStack(stack, function (err, stack) { + if (err) { + callback(err); + return; + } + + getEvent(stack); + }); + } + +}; + +/** + * client.getEvents + * + * @description get the list of stack events for a provided stack + * + * @param {object|string} stack stack or stackName to find events for + * @param {function} callback f(err, events) where stacks is an array of Event + * @returns {*} + */ +exports.getEvents = function(stack, callback) { + var self = this; + + function getEvents(stack) { + return self._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id, 'events') + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body.events); + + }); + } + + if (stack instanceof orchestration.Stack) { + getEvents(stack); + } + else if (typeof stack === 'string') { + self.getStack(stack, function (err, stack) { + if (err) { + callback(err); + return; + } + + getEvents(stack); + }); + } + +}; + +/** + * client.getResourceEvents + * + * @description get the list of stack events for a provided stack and resource + * + * @param {object|string} stack stack or stackName to find events for + * @param {object|string} resource resource or resourceName to find events for + * @param {function} callback f(err, events) where stacks is an array of Event + * @returns {*} + */ +exports.getResourceEvents = function (stack, resource, callback) { + var self = this; + + function getEvents(stack) { + return self._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id, 'resources', + resource instanceof orchestration.Resource ? resource.name : resource, 'events') + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body.events); + + }); + } + + if (stack instanceof orchestration.Stack) { + getEvents(stack); + } + else if (typeof stack === 'string') { + self.getStack(stack, function (err, stack) { + if (err) { + callback(err); + return; + } + + getEvents(stack); + }); + } +}; diff --git a/lib/pkgcloud/openstack/orchestration/client/index.js b/lib/pkgcloud/openstack/orchestration/client/index.js new file mode 100644 index 000000000..a844fb8ae --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/client/index.js @@ -0,0 +1,68 @@ +/* + * index.js: Orchestration client for OpenStack + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ + +var util = require('util'), + openstack = require('../../client'), + urlJoin = require('url-join'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + openstack.Client.call(this, options); + + _.extend(this, require('./stacks')); + _.extend(this, require('./templates')); + _.extend(this, require('./events')); + _.extend(this, require('./resources')); + + this.serviceType = 'orchestration'; + +}; + +util.inherits(Client, openstack.Client); + +/** + * client._getUrl + * + * @description get the url for the current compute service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function (options) { + options = options || {}; + + if (!this._serviceUrl) { + throw new Error('Service url not found'); + } + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); +}; + +/** + * client.buildInfo + * + * @description gets the build information for the orchestration service + * + * @param callback + * @returns {*} + */ +Client.prototype.buildInfo = function(callback) { + return this._request({ + path: '/build_info' + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body); + }); +}; diff --git a/lib/pkgcloud/openstack/orchestration/client/resources.js b/lib/pkgcloud/openstack/orchestration/client/resources.js new file mode 100644 index 000000000..cb9e9486f --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/client/resources.js @@ -0,0 +1,176 @@ +/* + * resources.js: Instance methods for working with stack resources from OpenStack Orchestration + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ +var pkgcloud = require('../../../../../lib/pkgcloud'), + urlJoin = require('url-join'), + _ = require('lodash'), + orchestration = pkgcloud.providers.openstack.orchestration; + +var _urlPrefix = '/resource_types'; + +/** + * client.getResource + * + * @description get the resource data for a provided resource and stack + * + * @param {object|string} stack stack or stackName to find resources for + * @param {object|string} resource resource or resourceName to get + * @param {function} callback f(err, resource) + * @returns {*} + */ +exports.getResource = function (stack, resource, callback) { + var self = this; + + function getResource(stack) { + return self._request({ + path: urlJoin('/stacks', stack.name, stack.id, 'resources', + resource instanceof orchestration.Resource ? resource.name : resource) + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, new orchestration.Resource(self, _.extend({ stack: stack }, body.resource))); + }); + } + + if (stack instanceof orchestration.Stack) { + getResource(stack); + } + else if (typeof stack === 'string') { + self.getStack(stack, function (err, stack) { + if (err) { + callback(err); + return; + } + + getResource(stack); + }); + } +}; + +/** + * client.getResources + * + * @description get the list of stack resources for a provided stack + * + * @param {object|string} stack stack or stackName to find resources for + * @param {function} callback f(err, resources) where stacks is an array of Resource + * @returns {*} + */ +exports.getResources = function (stack, options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + function getResources(stack) { + var requestOptions = { + path: urlJoin('/stacks', stack.name, stack.id, 'resources'), + qs: [] + }; + + if (options.nestedDepth) { + requestOptions.qs['nested_depth'] = options.nestedDepth; + } + + return self._request(requestOptions, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body.resources.map(function(resource) { + return new orchestration.Resource(self, _.extend({ stack: stack }, resource)); + })); + }); + } + + if (stack instanceof orchestration.Stack) { + getResources(stack); + } + else if (typeof stack === 'string') { + self.getStack(stack, function (err, stack) { + if (err) { + callback(err); + return; + } + + getResources(stack); + }); + } + +}; + +/** + * client.getResourceTypes + * + * @description get the list of resource types for the account + * + * @param {function} callback f(err, resourceTypes) where resourceTypes is an array of types + * @returns {*} + */ +exports.getResourceTypes = function (callback) { + var self = this; + + return self._request({ + path: _urlPrefix + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body.resource_types); + }); +}; + +/** + * client.getResourceSchema + * + * @description get the schema for a provided resource type + * + * @param {string} resourceType the resourceType to get the schema for + * @param {function} callback f(err, schema) + * @returns {*} + */ +exports.getResourceSchema = function (resourceType, callback) { + var self = this; + + return self._request({ + path: urlJoin(_urlPrefix, resourceType) + }, function (err, schema) { + if (err) { + return callback(err); + } + + callback(null, schema); + }); +}; + +/** + * client.getResourceTemplate + * + * @description get the template for a provided resource type + * + * @param {string} resourceType the resourceType to get the template for + * @param {function} callback f(err, template) + * @returns {*} + */ +exports.getResourceTemplate = function (resourceType, callback) { + var self = this; + + return self._request({ + path: urlJoin(_urlPrefix, resourceType, 'template') + }, function (err, template) { + if (err) { + return callback(err); + } + + callback(null, template); + }); +}; diff --git a/lib/pkgcloud/openstack/orchestration/client/stacks.js b/lib/pkgcloud/openstack/orchestration/client/stacks.js new file mode 100644 index 000000000..4023f9a62 --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/client/stacks.js @@ -0,0 +1,369 @@ +/* + * stacks.js: Instance methods for working with stacks from OpenStack Orchestration + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ + +var pkgcloud = require('../../../../../lib/pkgcloud'), + errs = require('errs'), + urlJoin = require('url-join'), + util = require('util'), + _ = require('lodash'), + orchestration = pkgcloud.providers.openstack.orchestration; + +var _urlPrefix = '/stacks'; + +/** + * validateProperties + * + * @description local helper function for validating arguments + * + * @param {Array} required The list of required properties + * @param {object} options The options object to validate + * @param {String} formatString String formatter for the error message + * @param {Function} callback + * @returns {boolean} + */ +function validateProperties(required, options, formatString, callback) { + return !required.some(function (item) { + if (typeof(options[item]) === 'undefined') { + errs.handle( + errs.create({ message: util.format(formatString, item) }), + callback + ); + return true; + } + return false; + }); +} + +/** + * client.getStack + * + * @description Gets a stack from the account + * + * @param {String|object} stack The stack or stackName to fetch + * @param {Function} callback + * @returns {request|*} + */ +exports.getStack = function (stack, callback) { + var self = this, + path = stack instanceof orchestration.Stack + ? urlJoin(_urlPrefix, stack.name, stack.id) + : urlJoin(_urlPrefix, stack); + + return this._request({ + path: path + }, function (err, body) { + if (err) { + return callback(err); + } + if (!body.stack) { + return new Error('Unexpected empty response'); + } + else { + callback(null, new orchestration.Stack(self, body.stack)); + } + }); +}; + +/** + * client.getStacks + * + * @description get the list of stacks for the current account + * + * @param {object|Function} [options] A set of options for the getStacks call + * @param {function} callback f(err, stacks) where stacks is an array of Stack + * @returns {*} + */ +exports.getStacks = function getStacks(options, callback) { + var self = this; + + if (typeof options === 'function') { + callback = options; + options = {}; + } + + var requestOptions = { + path: _urlPrefix + }; + + requestOptions.qs = _.pick(options, + 'status', + 'name', + 'limit', + 'marker', + 'sort'); + + if (options.sortDir) { + requestOptions.qs['sort_dir'] = options.sortDir; + } + + if (options.sortKeys) { + requestOptions.qs['sort_keys'] = options.sortKeys; + } + + return this._request(requestOptions, function (err, body) { + if (err) { + callback(err); + return; + } + + callback(err, body.stacks.map(function(stack) { + return new orchestration.Stack(self, stack); + })); + }); +}; + +/** + * client.createStack + * + * @description Creates a stack with the specified options. + + * @param {object} details the details to create this stack + * @param {String} details.name the name of the new stack + * @param {Number} details.timeout timeout in minutes for stack creation + * @param {String} [details.environment] the environment for the stack + * @param {Object} [details.template] template for the stack, required unless templateUrl is provided + * @param {String} [details.templateUrl] url for the template, required if no template + * @param {Object} [details.parameters] optional parameters configuration + * @param {Object} [details.files] optional files configuration + * @param callback + * @returns {request|null} + */ +exports.createStack = function (details, callback) { + if (typeof details === 'function') { + callback = details; + details = {}; + } + + details = details || {}; + + if (!validateProperties(['name', 'timeout'], details, + 'options.%s is a required argument.', callback)) { + return; + } + + if (!details.templateUrl && !details.template) { + callback(new Error('one of template or templateUrl are required')); + return; + } + + var self = this, + createOptions = { + method: 'POST', + path: details.preview ? urlJoin(_urlPrefix, 'preview'): _urlPrefix, + body: { + stack_name: details.name, + // environment is required from the API, but may be empty + environment: details.environment ? JSON.stringify(details.environment) : JSON.stringify({}), + timeout_mins: typeof details.timeout === 'number' ? details.timeout : parseInt(details.timeout) + } + }; + + if (details.template) { + createOptions.body.template = details.template; + } + else if (details.templateUrl) { + createOptions.body['template_url'] = details.templateUrl; + } + + if (details.parameters) { + createOptions.body.parameters = details.parameters; + } + + if (details.files) { + createOptions.body.files = details.files; + } + + // Adopt Stack options + if (details.stackData) { + createOptions.body['adopt_stack_data'] = JSON.stringify(details.stackData); + + if (typeof details.disableRollback === 'boolean') { + createOptions.body['disable_rollback'] = details.disableRollback; + } + + // if we're adopting a stack, copy the parameters (if any) from the stackData to + // the request payload + if (details.stackData.parameters) { + createOptions.body.parameters = details.stackData.parameters; + } + } + + return self._request(createOptions, function (err, body) { + if (err) { + return callback(err); + } + + if (!body || !body.stack) { + return new Error('Stack not passed back from OpenStack.'); + } + + // since createStack returns an href to the stack, lets go fetch it + self.getStack(body.stack.id, callback); + }); +}; + +/** + * client.previewStack + * + * @description Preview a stack creation with the specified options. + + * @param {object} details the details to create this stack + * @param {String} details.name the name of the new stack + * @param {String} details.environment the environment for the stack + * @param {Number} details.timeout timeout in minutes for stack creation + * @param {Object} [details.template] template for the stack, required unless templateUrl is provided + * @param {String} [details.templateUrl] url for the template, required if no template + * @param {Object} [details.parameters] optional parameters configuration + * @param {Object} [details.files] optional files configuration + * @param callback + * @returns {request|*} + */ +exports.previewStack = function(details, callback) { + return this.createStack(_.extend(details, { preview: true }), callback); +}; + +/** + * client.adoptStack + * + * @description adopt a stack from previously abandoned resources + + * @param {object} details the details to create this stack + * @param {String} details.name the name of the new stack + * @param {String} details.environment the environment for the stack + * @param {Number} details.timeout timeout in minutes for stack creation + * @param {Object} details.stackData Object with stack data to adopt + * @param {Boolean} details.disableRollback Controls whetehr a failure during stack creation causes deletion + * @param {Object} [details.template] template for the stack, required unless templateUrl is provided + * @param {String} [details.templateUrl] url for the template, required if no template + * @param {Object} [details.parameters] optional parameters configuration + * @param {Object} [details.files] optional files configuration + * @param callback + * @returns {request|*} + */ +exports.adoptStack = function (details, callback) { + return this.createStack(details, callback); +}; + +/** + * client.updateStack + * + * @description Update a stack + * + * @param {String|object} stack The stack or stackName to update + * @param {Function} callback + * @returns {request|*} + */ +exports.updateStack = function (stack, callback) { + if (!(stack instanceof orchestration.Stack)) { + callback(new Error('you must provide a stack to update')); + return; + } + + return this._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id), + body: { + template_url: stack.templateUrl, + template: stack.template, + files: stack.files, + environment: stack.environment, + parameters: stack.parameters, + timeout_mins: stack.timeout + }, + method: 'PUT' + }, function (err) { + if (err) { + return callback(err); + } + + callback(err, stack); + }); +}; + +/** + * client.deleteStack + * + * @description Delete a stack from the account + * + * @param {String|object} stack The stack or stackName to delete + * @param {Function} callback + * @returns {request|*} + */ +exports.deleteStack = function (stack, callback) { + var self = this; + + var deleteStack = function(stack) { + return self._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id), + method: 'DELETE' + }, function(err) { + if (err) { + return callback(err); + } + callback(err, true); + }); + }; + + if (typeof stack === 'string') { + self.getStack(stack, function(err, stack) { + if (err) { + return callback(err); + } + return deleteStack(stack); + }); + } else if (stack instanceof orchestration.Stack) { + return deleteStack(stack); + } else { + return callback(new Error('stack must be a string or stack instance')); + } +}; + +/** + * client.abandonStack + * + * @description Delete a stack, but preserve the created resources + * + * @param {String|object} stack The stack or stackName to abandon + * @param {Function} callback + * @returns {request|*} + */ +exports.abandonStack = function (stack, callback) { + var self = this; + + function abandon(stack) { + return self._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id, 'abandon'), + method: 'DELETE' + }, function (err, abandonStackData) { + if (err) { + return callback(err); + } + + callback(err, abandonStackData); + }); + } + + if (typeof stack === 'string') { + self.getStack(stack, function(err, stack) { + if (err) { + callback(err); + return; + } + + abandon(stack); + }); + + return; + } + else if (!(stack instanceof orchestration.stack)) { + callback(new Error('stack must be a string or stack instance')); + return; + } + + abandon(stack); + +}; diff --git a/lib/pkgcloud/openstack/orchestration/client/templates.js b/lib/pkgcloud/openstack/orchestration/client/templates.js new file mode 100644 index 000000000..c4482234b --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/client/templates.js @@ -0,0 +1,96 @@ +/* + * templates.js: Instance methods for working with templates from OpenStack Orchestration + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ +var pkgcloud = require('../../../../../lib/pkgcloud'), + urlJoin = require('url-join'), + orchestration = pkgcloud.providers.openstack.orchestration; + +var _urlPrefix = '/stacks'; + +/** + * client.getTemplate + * + * @description get the template for a provided stack + * + * @param {object|string} stack the stackName or stack for the template query + * @param {function} callback f(err, template) + * @returns {*} + */ +exports.getTemplate = function (stack, callback) { + var self = this; + + function getTemplate(stack) { + return self._request({ + path: urlJoin(_urlPrefix, stack.name, stack.id, 'template') + }, function (err, body) { + if (err) { + return callback(err); + } + if (!body) { + return new Error('Unexpected empty response'); + } + else { + callback(null, body); + } + }); + } + + if (stack instanceof orchestration.Stack) { + getTemplate(stack); + } + else if (typeof stack === 'string') { + self.getStack(stack, function(err, stack) { + if (err) { + callback(err); + return; + } + + getTemplate(stack); + }); + } + +}; + +/** + * client.validateTemplate + * + * @description validate a provided template + * + * @param {object|string} template the template or templateUrl to validate + * @param {function} callback f(err, template) + * @returns {*} + */ +exports.validateTemplate = function (template, callback) { + var self = this, + requestOpts = { + path: urlJoin('/validate'), + method: 'POST', + body: {} + }; + + if (typeof template === 'string') { + requestOpts.body.template_url = template; + } + else if (typeof template === 'object') { + requestOpts.body.template = template; + } + else { + callback(new Error('please provide either a template object, or a templateUrl')); + } + + return self._request(requestOpts, function (err, body) { + if (err) { + return callback(err); + } + if (!body) { + return new Error('Unexpected empty response'); + } + else { + callback(null, body); + } + }); +}; diff --git a/lib/pkgcloud/openstack/orchestration/index.js b/lib/pkgcloud/openstack/orchestration/index.js new file mode 100644 index 000000000..56642940b --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/index.js @@ -0,0 +1,15 @@ +/* + * index.js: Top-level include for the OpenStack orchestration module + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ + +exports.Client = require('./client').Client; +exports.Stack = require('./stack').Stack; +exports.Resource = require('./resource').Resource; + +exports.createClient = function (options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/openstack/orchestration/resource.js b/lib/pkgcloud/openstack/orchestration/resource.js new file mode 100644 index 000000000..1fc5a7ad9 --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/resource.js @@ -0,0 +1,48 @@ +/* + * volume.js: OpenStack Orchestration Stack + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + * + */ + +var util = require('util'), + base = require('../../core/base'), + Stack = require('./stack').Stack, + _ = require('lodash'); + +var Resource = exports.Resource = function Stack(client, details) { + base.Model.call(this, client, details); +}; + +util.inherits(Resource, base.Model); + +Resource.prototype._setProperties = function (details) { + this.name = details.name || details['resource_name']; + this.status = details.status || details['resource_status']; + this.stack = details.stack; + this.statusReason = details.statusReason || details['resource_status_reason']; + this.type = details.type || details['resource_type']; + this.logicalResourceId = details.logicalResourceId || details['logical_resource_id']; + this.physicalResourceId = details.physicalResourceId || details['physical_resource_id']; + this.requiredBy = details.requiredBy || details['required_by']; + this.updatedAt = details.updatedAt || details['updated_time']; + this.links = details.links; +}; + +Resource.prototype.toJSON = function () { + + var data = _.pick(this, ['name', 'status', 'statusReason', 'type', 'logicalResourceId', 'physicalResourceId', + 'requiredBy', 'updatedAt', 'links']); + + this.stack instanceof Stack ? data.stack = this.stack.toJSON() : {}; + + return data; + +}; + + + + + diff --git a/lib/pkgcloud/openstack/orchestration/stack.js b/lib/pkgcloud/openstack/orchestration/stack.js new file mode 100644 index 000000000..f9c9b894a --- /dev/null +++ b/lib/pkgcloud/openstack/orchestration/stack.js @@ -0,0 +1,56 @@ +/* + * volume.js: OpenStack Orchestration Stack + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + * + */ + +var util = require('util'), + base = require('../../core/base'), + _ = require('lodash'); + +var Stack = exports.Stack = function Stack(client, details) { + base.Model.call(this, client, details); +}; + +util.inherits(Stack, base.Model); + +Stack.prototype.refresh = function (callback) { + var self = this; + return self.client.getStack(this, function (err, stack) { + if (!err) { + self._setProperties(stack.original); + } + + return callback.apply(this, arguments); + }); +}; + +Stack.prototype._setProperties = function (details) { + this.id = details.id; + this.name = details.name || details['stack_name']; + this.status = details.status || details['stack_status']; + this.description = details.description; + this.templateDescription = details.templateDescription || details['template_description']; + this.statusReason = details.statusReason || details['stack_status_reason']; + this.owner = details.owner || details['stack_owner']; + this.disableRollback = details.disableRollback || details['disable_rollback']; + this.parameters = details.parameters; + this.capabilities = details.capabilities; + this.notificationTopics = details.notificationTopics || details['notification_topics']; + this.timeout = details.timeout || details['timeout_mins']; + + this.createdAt = details['creation_time']; + this.updatedAt = details['updated_time']; + + this.outputs = details['outputs']; + + this.original = this.openstack = details; +}; + +Stack.prototype.toJSON = function () { + return _.pick(this, ['id', 'name', 'status', 'description', 'templateDescription', 'statusReason', 'owner', + 'disableRollback', 'parameters', 'capabilities', 'notificationTopics', 'timeout', 'updatedAt', 'createdAt', 'outputs' ]); +}; diff --git a/lib/pkgcloud/openstack/storage/client/account.js b/lib/pkgcloud/openstack/storage/client/account.js new file mode 100644 index 000000000..f10f51359 --- /dev/null +++ b/lib/pkgcloud/openstack/storage/client/account.js @@ -0,0 +1,101 @@ +var crypto = require('crypto'); + +/** + * client.getTemporaryUrlKey + * + * @description get the previously set temporaryUrl key on the current account + * + * @param callback + */ +exports.getTemporaryUrlKey = function (callback) { + var self = this; + + this._request({ + method: 'HEAD' + }, function (err, body, res){ + if(err) { + return callback(err); + } + + var temporaryUrlKey = res.headers[self.ACCOUNT_META_PREFIX + 'temp-url-key']; + callback(null, temporaryUrlKey); + }); + +}; + +/** + * client.setTemporaryUrlKey + * + * @description set a temporaryUrl key on the current account + * + * @param {String} key the secret key to be used in hmac signing temporary urls + * @param callback + */ +exports.setTemporaryUrlKey = function (key, callback) { + if(!key){ + return process.nextTick(function(){ + callback(new Error('A temporary URL key must be provided')); + }); + } + + this._request({ + method: 'POST', + headers: { + 'x-account-meta-temp-url-key': key + } + }, function(err){ + callback(err); + }); +}; + +/** + * client.generateTempUrl + * + * @description create a temporary url for GET/PUT to a cloud files container + * + * @param {String|object} container the container or container name for the url + * @param {String|object} file the file or fileName for the url + * @param {String} method either GET or PUT + * @param {Number} time expiry for the url in seconds (from now) + * @param {String} key the secret key to be used for signing the url + * @param callback + */ +exports.generateTempUrl = function(container, file, method, time, key, callback) { + var containerName = container instanceof this.models.Container ? container.name : container, + fileName = file instanceof this.models.File ? file.name : file, + time = typeof time === 'number' ? time : parseInt(time), + self = this, + split = '/v1'; + + function createUrl() { + // construct our hmac signature + var expiry = parseInt(new Date().getTime() / 1000) + time, + url = self._getUrl({ + container: containerName, + path: fileName + }), + hmac_body = method.toUpperCase() + '\n' + expiry + '\n' + split + url.split(split)[1]; + + var hash = crypto.createHmac('sha1', key).update(hmac_body).digest('hex'); + + callback(null, url + '?temp_url_sig=' + hash + '&temp_url_expires=' + expiry); + } + + // We have to be authed to make sure we have the service catalog + // this is required to validate the service url + + if (!this._isAuthorized()) { + this.auth(function(err) { + if (err) { + callback(err); + return; + } + + createUrl(); + }); + + return; + } + + createUrl(); +}; diff --git a/lib/pkgcloud/openstack/storage/client/containers.js b/lib/pkgcloud/openstack/storage/client/containers.js index 53325b27f..646107a13 100644 --- a/lib/pkgcloud/openstack/storage/client/containers.js +++ b/lib/pkgcloud/openstack/storage/client/containers.js @@ -8,10 +8,7 @@ */ var async = require('async'), - request = require('request'), - base = require('../../../core/storage'), - pkgcloud = require('../../../../pkgcloud'), - _ = require('underscore'); + _ = require('lodash'); /** * client.getContainers @@ -44,7 +41,7 @@ exports.getContainers = function (options, callback) { return callback(err); } else if (!body || !(body instanceof Array)) { - return new Error('Malformed API Response') + return new Error('Malformed API Response'); } return callback(null, body.map(function (container) { @@ -190,34 +187,53 @@ exports.destroyContainer = function (container, callback) { var containerName = container instanceof this.models.Container ? container.name : container, self = this; - this.getFiles(container, function (err, files) { + // first fetch the container, to get the count of objects + this.getContainer(container, function(err, container) { if (err) { - return callback(err); + callback(err); + return; } - function deleteContainer(err) { + // we need to call get files, but with a limit of the current count of the container + // with a modest offset incase of any cache staleness + self.getFiles(container, { limit: container.count + 1000 }, function (err, files) { if (err) { return callback(err); } - self._request({ - method: 'DELETE', - container: containerName - }, function(err) { - return err - ? callback(err) - : callback(null, true); - }); - } + function deleteContainer(err) { + if (err) { + return callback(err); + } + + self._request({ + method: 'DELETE', + container: containerName + }, function (err) { + // if the file we're deleting returns a 404 error, ignore it + if (err && err.statusCode === 404) { + callback(null, false); + return; + } + // non-404 case, propagate it + else if (err) { + callback(err); + return; + } + + callback(null, true); + }); + } - function destroyFile(file, next) { - file.remove(next); - } + function destroyFile(file, next) { + file.remove(next); + } - if (files.length === 0) { - return deleteContainer(); - } + if (files.length === 0) { + return deleteContainer(); + } - async.forEach(files, destroyFile, deleteContainer); + async.forEachLimit(files, 15, destroyFile, deleteContainer); + }); }); }; diff --git a/lib/pkgcloud/openstack/storage/client/files.js b/lib/pkgcloud/openstack/storage/client/files.js index 397a73661..c29387137 100644 --- a/lib/pkgcloud/openstack/storage/client/files.js +++ b/lib/pkgcloud/openstack/storage/client/files.js @@ -6,15 +6,12 @@ * */ -var fs = require('fs'), - filed = require('filed'), +var filed = require('filed-mimefix'), mime = require('mime'), - request = require('request'), - utile = require('utile'), base = require('../../../core/storage'), - pkgcloud = require('../../../../pkgcloud'), - _ = require('underscore'), - storage = pkgcloud.providers.openstack.storage; + through = require('through2'), + _ = require('lodash'), + urlJoin = require('url-join'); /** * client.removeFile @@ -41,6 +38,36 @@ exports.removeFile = function (container, file, callback) { ); }; +/** + * client.bulkDelete + * + * @description remove a list of files from a container + * + * @param {String|object} container the container or containerName + * @param {array} files the files or fileNames to delete + * @param callback + */ +exports.bulkDelete = function(container, files, callback) { + var self = this, + containerName = container instanceof this.models.Container ? container.name : container; + this._request({ + method: 'DELETE', + body: files.map(function(file) { + return urlJoin(containerName, (file instanceof self.models.File ? file.name : file)); + }).join('\r\n'), + headers: { + 'Content-Type': 'text/plain' + }, + qs: { + 'bulk-delete': true + } + }, function(err, results) { + return err + ? callback(err) + : callback(null, results); + }); +}; + /** * client.upload * @@ -59,81 +86,75 @@ exports.removeFile = function (container, file, callback) { * @param callback * @returns {request|*} */ -exports.upload = function (options, callback) { - if (typeof options === 'function' && !callback) { - callback = options; - options = {}; - } +exports.upload = function (options) { + var self = this; - // - // Optional helper function passed to `this._request` - // in the case when no callback is passed to `.upload(options)`. - // - function onUpload(err, body, res) { - return err - ? callback(err) - : callback(null, true, res); + // check for deprecated calling with a callback + if (typeof arguments[arguments.length - 1] === 'function') { + self.emit('log::warn', 'storage.upload no longer supports calling with a callback'); } var container = options.container, - success = callback ? onUpload : null, - self = this, - apiStream, - inputStream, - uploadOptions; - - if (container instanceof this.models.Container) { - container = container.name; + writableStream, + proxyStream = through(), + uploadOptions = { + method: 'PUT', + upload: true, + container: container, + path: options.remote, + headers: options.headers || {} + }; + + if (options.container instanceof this.models.Container) { + uploadOptions.container = options.container.name; } - uploadOptions = { - method: 'PUT', - upload: true, - container: container, - path: options.remote, - headers: options.headers || {} - }; - - if (options.local) { - inputStream = filed(options.local); - uploadOptions.headers['content-length'] = fs.statSync(options.local).size; + if (options.contentType) { + uploadOptions.headers['content-type'] = options.contentType; } - else if (options.stream) { - inputStream = options.stream; + else { + uploadOptions.headers['content-type'] = mime.getType(options.remote); } - if (!uploadOptions.headers['content-type']) { - uploadOptions.headers['content-type'] = mime.lookup(options.remote); + if (options.metadata) { + uploadOptions.headers = _.extend(uploadOptions.headers, + self.serializeMetadata(self.OBJECT_META_PREFIX, options.metadata)); } - // If the inputStream is a request stream, headers are automatically - // copied from the source stream to the destination stream. - // As CloudFiles uses ETag to compute an md5 hash, this leads to a case - // where a remote resource with an ETag, piped via request to CloudFiles with - // pkgcloud, would result in a 422 "Unable to Process" error. - // - // As a result, we explicitly only opt in 'Content-Type' and 'Content-Length' - // if there is a response handler on the inputStream - if (inputStream) { - inputStream.on('response', function(response) { - response.headers = { - 'content-type': response.headers['content-type'], - 'content-length': response.headers['content-length'] + writableStream = this._request(uploadOptions); + + writableStream.on('complete', function(response) { + var err = self._parseError(response); + + if (err) { + proxyStream.emit('error', err); + return; + } + + // load the file metadata from the cloud, so we can return a proper model + self.getFile(uploadOptions.container, options.remote, function (err, file) { + if (err) { + proxyStream.emit('error', err); + return; } + + proxyStream.emit('success', file); }); - } + }); - if (options.metadata) { - uploadOptions.headers = _.extend(uploadOptions.headers, - self.serializeMetadata(self.OBJECT_META_PREFIX, options.metadata)); - } + writableStream.on('error', function (err) { + proxyStream.emit('error', err); + }); - apiStream = this._request(uploadOptions, success); - if (inputStream) { - inputStream.pipe(apiStream); - } + writableStream.on('data', function (chunk) { + proxyStream.emit('data', chunk); + }); - return apiStream; + // we need a proxy stream so we can always return a file model + // via the 'success' event + proxyStream.pipe(writableStream); + + return proxyStream; }; /** @@ -154,23 +175,18 @@ exports.upload = function (options, callback) { */ exports.download = function (options, callback) { var self = this, - success = callback ? onDownload : null, container = options.container, inputStream, apiStream; - // - // Optional helper function passed to `this._request` - // in the case when no callback is passed to `.download(options)`. - // - function onDownload(err, body, res) { + var success = !callback ? null : function (err, body, res) { return err ? callback(err) - : callback(null, new self.models.File(self, utile.mixin(res.headers, { - container: container, + : callback(null, new self.models.File(self, _.extend(res.headers, { + container: options.container, name: options.remote }))); - } + }; if (container instanceof self.models.Container) { container = container.name; @@ -186,7 +202,8 @@ exports.download = function (options, callback) { apiStream = this._request({ container: container, path: options.remote, - download: true + download: true, + headers: options.headers }, success); if (inputStream) { @@ -219,7 +236,7 @@ exports.getFile = function (container, file, callback) { }, function (err, body, res) { return err ? callback(err) - : callback(null, new self.models.File(self, utile.mixin(res.headers, { + : callback(null, new self.models.File(self, _.extend(res.headers, { container: container, name: file }))); @@ -245,6 +262,9 @@ exports.getFiles = function (container, options, callback) { callback = options; options = {}; } + else if (!options) { + options = {}; + } // If limit is not specified, or it is <=10k, just make a single request if (!options.limit || options.limit <= 10000) { @@ -289,18 +309,30 @@ exports._getFiles = function (container, options, callback) { self = this; var getFilesOpts = { - path: containerName, - qs: { - format: 'json' + path: containerName, + qs: _.extend({ + format: 'json' + }, _.pick(options, ['limit', 'marker', 'prefix', 'path', 'delimiter'])) + }; + + if (options.endMarker) { + getFilesOpts.qs.end_marker = options.endMarker; } - }; - if (options.limit) { - getFilesOpts.qs.limit = options.limit; + if (options.end_marker) { + getFilesOpts.qs.end_marker = options.end_marker; + } + + if (options.prefix) { + getFilesOpts.qs.prefix = options.prefix; + } + + if (options.path) { + getFilesOpts.qs.path = options.path; } - if (options.marker) { - getFilesOpts.qs.marker = options.marker; + if (options.delimiter) { + getFilesOpts.qs.delimiter = options.delimiter; } this._request(getFilesOpts, function (err, body) { @@ -309,7 +341,7 @@ exports._getFiles = function (container, options, callback) { return callback(err); } else if (!body || !(body instanceof Array)) { - return new Error('Malformed API Response') + return new Error('Malformed API Response'); } return callback(null, body.map(function (file) { @@ -323,7 +355,7 @@ exports._getFiles = function (container, options, callback) { * client.updateFileMetadata * * @description Updates the specified `file` with the provided metadata `headers` - * in the Rackspace Cloud Files account associated with this instance. + * in the Openstack account associated with this instance. * * @param {String|object} container the container or containerName * @param {String|object} file the file or fileName to update @@ -333,7 +365,7 @@ exports.updateFileMetadata = function (container, file, callback) { var self = this, containerName = container instanceof self.models.Container ? container.name : container; - if (!file instanceof base.File) { + if (!(file instanceof base.File)) { throw new Error('Must update an existing file instance'); } @@ -351,4 +383,41 @@ exports.updateFileMetadata = function (container, file, callback) { }); }; +/** + * client.copy + * + * @description copies a file to another location in the same or different container. + * + * @param {object} options + * @param {String|object} options.sourceContainer the source container + * @param {String|object} options.destinationContainer the destination container + * @param {String|object} options.sourceFile the file to copy + * @param {String|object} [options.destinationFile] the destination to copy to + * @param {String|object} [options.headers] headers to send with the request + * @param callback + */ +exports.copy = function (options, callback) { + var self = this, + containerName = options.sourceContainer instanceof self.models.Container ? options.sourceContainer.name : options.sourceContainer, + destContainerName = options.destinationContainer instanceof self.models.Container ? options.destinationContainer.name : options.destinationContainer, + destinationFile = options.destinationFile || options.sourceFile; + + var copyOptions = { + method: 'COPY', + uri: options.sourceFile instanceof self.models.File ? options.sourceFile.fullPath : this._getUrl({ + container: containerName, + path: options.sourceFile + }), + headers: _.extend(options.headers || {}, { + destination: urlJoin('/', destContainerName, + destinationFile instanceof self.models.File ? destinationFile.name : destinationFile) + }) + }; + + this._request(copyOptions, function (err) { + return err + ? callback(err) + : callback(null, true); + }); +}; diff --git a/lib/pkgcloud/openstack/storage/client/index.js b/lib/pkgcloud/openstack/storage/client/index.js index 216e78266..b219a23e6 100644 --- a/lib/pkgcloud/openstack/storage/client/index.js +++ b/lib/pkgcloud/openstack/storage/client/index.js @@ -6,13 +6,18 @@ * */ -var utile = require('utile'), - urlJoin = require('url-join'), +var util = require('util'), openstack = require('../../client'), StorageClient = require('../storageClient').StorageClient, - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { + // explicitly prevent service catalog usage if using version 1.0 for swift + // this must happen before we call into the base openstack client. + if (options.version === 1 || options.version === '/v1.0') { + options.useServiceCatalog = false; + } + openstack.Client.call(this, options); this.models = { @@ -20,11 +25,12 @@ var Client = exports.Client = function (options) { File: require('../file').File }; - utile.mixin(this, require('./containers')); - utile.mixin(this, require('./files')); + _.extend(this, require('./containers')); + _.extend(this, require('./files')); + _.extend(this, require('./account')); this.serviceType = 'object-store'; }; -utile.inherits(Client, openstack.Client); +util.inherits(Client, openstack.Client); _.extend(Client.prototype, StorageClient.prototype); diff --git a/lib/pkgcloud/openstack/storage/container.js b/lib/pkgcloud/openstack/storage/container.js index f996a9cbc..75fc9033a 100644 --- a/lib/pkgcloud/openstack/storage/container.js +++ b/lib/pkgcloud/openstack/storage/container.js @@ -6,15 +6,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/storage/container'), - _ = require('underscore'); + _ = require('lodash'); var Container = exports.Container = function Container(client, details) { base.Container.call(this, client, details); }; -utile.inherits(Container, base.Container); +util.inherits(Container, base.Container); Container.prototype.updateMetadata = function (callback) { this.client.updateContainerMetadata(this.container, callback); @@ -28,7 +28,7 @@ Container.prototype._setProperties = function (details) { this.name = details.name || this.name; this.ttl = details.ttl || this.ttl; this.logRetention = details.logRetention || this.logRetention; - this.count = details.count || this.count || 0 + this.count = details.count || this.count || 0; this.bytes = details.bytes || this.bytes || 0; this.metadata = details.metadata || this.metadata || {}; }; diff --git a/lib/pkgcloud/openstack/storage/file.js b/lib/pkgcloud/openstack/storage/file.js index 5b164cd0b..bf5162dc8 100644 --- a/lib/pkgcloud/openstack/storage/file.js +++ b/lib/pkgcloud/openstack/storage/file.js @@ -6,35 +6,22 @@ * */ -var utile = require('utile'), +var util = require('util'), + _ = require('lodash'), base = require('../../core/storage/file'); var File = exports.File = function File(client, details) { base.File.call(this, client, details); }; -utile.inherits(File, base.File); +util.inherits(File, base.File); File.prototype.updateMetadata = function (callback) { this.client.updateFileMetadata(this.container, this, callback); }; -// Remark: This method is untested -File.prototype.copy = function (container, destination, callback) { - var copyOptions = { - method: 'PUT', - uri: this.fullPath, - headers: { - 'X-COPY-DESTINATION': [container, destination].join('/'), - 'CONTENT-LENGTH': this.bytes - } - }; - - this.client._request(copyOptions, function (err, body, res) { - return err - ? callback(err) - : callback(null, true); - }); +File.prototype.copy = function (options, callback) { + this.client.copy(options, callback); }; File.prototype._setProperties = function (details) { @@ -42,9 +29,14 @@ File.prototype._setProperties = function (details) { this.metadata = {}; this.container = details.container || null; - this.name = details.name || null; + this.name = details.name || details.subdir || null; this.etag = details.etag || details.hash || null; - this.contentType = details['content-type'] || details['content_type'] || null; + + if (details.subdir) { + this.contentType = 'application/directory'; + } else { + this.contentType = details['content-type'] || details['content_type'] || null; + } this.lastModified = details['last-modified'] ? new Date(details['last-modified']) @@ -66,3 +58,7 @@ File.prototype._setProperties = function (details) { }); }; +File.prototype.toJSON = function () { + return _.pick(this, ['name', 'etag', 'size', 'storageClass', 'lastModified', 'container', 'location' ]); +}; + diff --git a/lib/pkgcloud/openstack/storage/storageClient.js b/lib/pkgcloud/openstack/storage/storageClient.js index 22373fea1..1420910f9 100644 --- a/lib/pkgcloud/openstack/storage/storageClient.js +++ b/lib/pkgcloud/openstack/storage/storageClient.js @@ -9,16 +9,17 @@ */ var urlJoin = require('url-join'), - _ = require('underscore'); + _ = require('lodash'); const CONTAINER_META_PREFIX = 'x-container-meta-'; const CONTAINER_REMOVE_META_PREFIX = 'x-remove-container-meta-'; const OBJECT_META_PREFIX = 'x-object-meta-'; const OBJECT_REMOVE_META_PREFIX = 'x-object-remove-meta-'; +const ACCOUNT_META_PREFIX = 'x-account-meta-'; var Client = exports.StorageClient = function () { this.serviceType = 'object-store'; -} +}; /** * client._getUrl @@ -87,9 +88,11 @@ Client.prototype.deserializeMetadata = function (prefix, metadata) { return deserializedMetadata; }; + + Client.prototype.CONTAINER_META_PREFIX = CONTAINER_META_PREFIX; Client.prototype.CONTAINER_REMOVE_META_PREFIX = CONTAINER_REMOVE_META_PREFIX; Client.prototype.OBJECT_META_PREFIX = OBJECT_META_PREFIX; Client.prototype.OBJECT_REMOVE_META_PREFIX = OBJECT_REMOVE_META_PREFIX; - +Client.prototype.ACCOUNT_META_PREFIX = ACCOUNT_META_PREFIX; diff --git a/lib/pkgcloud/rackspace/blockstorage/client/index.js b/lib/pkgcloud/rackspace/blockstorage/client/index.js index 25cd95d90..75e836176 100644 --- a/lib/pkgcloud/rackspace/blockstorage/client/index.js +++ b/lib/pkgcloud/rackspace/blockstorage/client/index.js @@ -7,21 +7,22 @@ * */ -var utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), - rackspace = require('../../client'); + rackspace = require('../../client'), + _ = require('lodash'); var Client = exports.Client = function (options) { rackspace.Client.call(this, options); - utile.mixin(this, require('./volumetypes')); - utile.mixin(this, require('./snapshots')); - utile.mixin(this, require('./volumes')); + _.extend(this, require('../../../openstack/blockstorage/client/volumetypes')); + _.extend(this, require('../../../openstack/blockstorage/client/snapshots')); + _.extend(this, require('../../../openstack/blockstorage/client/volumes')); this.serviceType = 'volume'; }; -utile.inherits(Client, rackspace.Client); +util.inherits(Client, rackspace.Client); Client.prototype._getUrl = function (options) { options = options || {}; diff --git a/lib/pkgcloud/rackspace/blockstorage/index.js b/lib/pkgcloud/rackspace/blockstorage/index.js index c48916fee..9c8abcd17 100644 --- a/lib/pkgcloud/rackspace/blockstorage/index.js +++ b/lib/pkgcloud/rackspace/blockstorage/index.js @@ -8,9 +8,9 @@ */ exports.Client = require('./client').Client; -exports.Volume = require('./volume').Volume; -exports.VolumeType = require('./volumetype').VolumeType; -exports.Snapshot = require('./snapshot').Snapshot; +exports.Volume = require('../../openstack/blockstorage/volume').Volume; +exports.VolumeType = require('../../openstack/blockstorage/volumetype').VolumeType; +exports.Snapshot = require('../../openstack/blockstorage/snapshot').Snapshot; exports.createClient = function (options) { return new exports.Client(options); diff --git a/lib/pkgcloud/rackspace/cdn/client/index.js b/lib/pkgcloud/rackspace/cdn/client/index.js new file mode 100644 index 000000000..b96452754 --- /dev/null +++ b/lib/pkgcloud/rackspace/cdn/client/index.js @@ -0,0 +1,51 @@ +/* + * client.js: client for Rackspace CDN + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT License + */ + +var util = require('util'), + rackspace = require('../../client'), + urlJoin = require('url-join'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + rackspace.Client.call(this, options); + + this.models = { + Service: require('../../../openstack/cdn/service').Service, + Flavor: require('../../../openstack/cdn/flavor').Flavor + }; + + _.extend(this, require('../../../openstack/cdn/client/base')); + _.extend(this, require('../../../openstack/cdn/client/services')); + _.extend(this, require('../../../openstack/cdn/client/flavors')); + + this.serviceType = 'rax:cdn'; +}; + +util.inherits(Client, rackspace.Client); + +/** + * client._getUrl + * + * @description get the url for the current compute service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function (options) { + options = options || {}; + + if (!this._serviceUrl) { + throw new Error('Service url not found'); + } + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); +}; diff --git a/lib/pkgcloud/rackspace/cdn/index.js b/lib/pkgcloud/rackspace/cdn/index.js new file mode 100644 index 000000000..1a5f56266 --- /dev/null +++ b/lib/pkgcloud/rackspace/cdn/index.js @@ -0,0 +1,15 @@ +/* + * index.js: Top-level include for the Rackspace CDN module. + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +exports.Client = require('./client').Client; +exports.Service = require('../../openstack/cdn/service').Service; +exports.Flavor = require('../../openstack/cdn/flavor').Flavor; + +exports.createClient = function(options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/rackspace/client.js b/lib/pkgcloud/rackspace/client.js index d7a07067b..6a5800d5f 100644 --- a/lib/pkgcloud/rackspace/client.js +++ b/lib/pkgcloud/rackspace/client.js @@ -1,14 +1,14 @@ /* * client.js: Base client from which all Rackspace clients inherit from * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), identity = require('./identity'), base = require('../openstack/client'), - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { options = options || {}; @@ -25,7 +25,7 @@ var Client = exports.Client = function (options) { this.provider = 'rackspace'; }; -utile.inherits(Client, base.Client); +util.inherits(Client, base.Client); Client.prototype._getIdentityOptions = function() { return _.extend({ diff --git a/lib/pkgcloud/rackspace/compute/client/extensions/virtual-interfacesv2.js b/lib/pkgcloud/rackspace/compute/client/extensions/virtual-interfacesv2.js index 7121b424c..99935c9ed 100644 --- a/lib/pkgcloud/rackspace/compute/client/extensions/virtual-interfacesv2.js +++ b/lib/pkgcloud/rackspace/compute/client/extensions/virtual-interfacesv2.js @@ -9,8 +9,7 @@ */ var Server = require('../../server').Server, - urlJoin = require('url-join'), - _ = require('underscore'); + urlJoin = require('url-join'); var _servers = 'servers', _extension = 'os-virtual-interfacesv2'; diff --git a/lib/pkgcloud/rackspace/compute/client/index.js b/lib/pkgcloud/rackspace/compute/client/index.js index 04ff529d5..67ba4bc92 100644 --- a/lib/pkgcloud/rackspace/compute/client/index.js +++ b/lib/pkgcloud/rackspace/compute/client/index.js @@ -1,29 +1,29 @@ /* * client.js: Compute client for Rackspace Cloudservers * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), rackspace = require('../../client'), ComputeClient = require('../../../openstack/compute/computeClient').ComputeClient, - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { rackspace.Client.call(this, options); - utile.mixin(this, require('../../../openstack/compute/client/flavors')); - utile.mixin(this, require('../../../openstack/compute/client/images')); - utile.mixin(this, require('../../../openstack/compute/client/servers')); - utile.mixin(this, require('../../../openstack/compute/client/extensions')); + _.extend(this, require('../../../openstack/compute/client/flavors')); + _.extend(this, require('../../../openstack/compute/client/images')); + _.extend(this, require('../../../openstack/compute/client/servers')); + _.extend(this, require('../../../openstack/compute/client/extensions')); // rackspace specific extensions - utile.mixin(this, require('./extensions/networksv2')); - utile.mixin(this, require('./extensions/virtual-interfacesv2')); + _.extend(this, require('./extensions/networksv2')); + _.extend(this, require('./extensions/virtual-interfacesv2')); this.serviceType = 'compute'; }; -utile.inherits(Client, rackspace.Client); +util.inherits(Client, rackspace.Client); _.extend(Client.prototype, ComputeClient.prototype); diff --git a/lib/pkgcloud/rackspace/compute/index.js b/lib/pkgcloud/rackspace/compute/index.js index 0fd03d94d..516538712 100644 --- a/lib/pkgcloud/rackspace/compute/index.js +++ b/lib/pkgcloud/rackspace/compute/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the Rackspace compute module * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ diff --git a/lib/pkgcloud/rackspace/compute/server.js b/lib/pkgcloud/rackspace/compute/server.js index cd7125cc4..e3a95ce40 100644 --- a/lib/pkgcloud/rackspace/compute/server.js +++ b/lib/pkgcloud/rackspace/compute/server.js @@ -7,11 +7,11 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../openstack/compute/server'); var Server = exports.Server = function Server(client, details) { base.Server.call(this, client, details); }; -utile.inherits(Server, base.Server); +util.inherits(Server, base.Server); diff --git a/lib/pkgcloud/rackspace/database/client/index.js b/lib/pkgcloud/rackspace/database/client/index.js index a19f535c8..8dffca1cc 100644 --- a/lib/pkgcloud/rackspace/database/client/index.js +++ b/lib/pkgcloud/rackspace/database/client/index.js @@ -1,31 +1,30 @@ /* * client.js: Database client for Rackspace Cloud Databases * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), urlJoin = require('url-join'), - request = require('request'), rackspace = require('../../client'), auth = require('../../../common/auth.js'), - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { rackspace.Client.call(this, options); this.before.push(auth.accountId); - utile.mixin(this, require('./flavors')); - utile.mixin(this, require('./instances')); - utile.mixin(this, require('./databases')); - utile.mixin(this, require('./users')); + _.extend(this, require('../../../openstack/database/client/flavors')); + _.extend(this, require('../../../openstack/database/client/instances')); + _.extend(this, require('../../../openstack/database/client/databases')); + _.extend(this, require('../../../openstack/database/client/users')); this.serviceType = 'rax:database'; }; -utile.inherits(Client, rackspace.Client); +util.inherits(Client, rackspace.Client); Client.prototype._getUrl = function (options) { options = options || {}; diff --git a/lib/pkgcloud/rackspace/database/index.js b/lib/pkgcloud/rackspace/database/index.js index 995030f48..df1a07b33 100644 --- a/lib/pkgcloud/rackspace/database/index.js +++ b/lib/pkgcloud/rackspace/database/index.js @@ -1,15 +1,15 @@ /* * index.js: Top-level include for the Rackspace database module * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ exports.Client = require('./client').Client; -exports.Flavor = require('./flavor').Flavor; -exports.Instance = require('./instance').Instance; -exports.Database = require('./database').Database; -exports.User = require('./user').User; +exports.Flavor = require('../../openstack/database/flavor').Flavor; +exports.Instance = require('../../openstack/database/instance').Instance; +exports.Database = require('../../openstack/database/database').Database; +exports.User = require('../../openstack/database/user').User; exports.createClient = function createClient(options) { return new exports.Client(options); diff --git a/lib/pkgcloud/rackspace/dns/client/index.js b/lib/pkgcloud/rackspace/dns/client/index.js index 853912973..b9fcf09fa 100644 --- a/lib/pkgcloud/rackspace/dns/client/index.js +++ b/lib/pkgcloud/rackspace/dns/client/index.js @@ -7,22 +7,22 @@ * */ -var utile = require('utile'), +var util = require('util'), rackspace = require('../../client'), urlJoin = require('url-join'), Status = require('../status').Status, - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { rackspace.Client.call(this, options); - utile.mixin(this, require('./records.js')); - utile.mixin(this, require('./zones.js')); + _.extend(this, require('./records.js')); + _.extend(this, require('./zones.js')); this.serviceType = 'rax:dns'; }; -utile.inherits(Client, rackspace.Client); +util.inherits(Client, rackspace.Client); Client.prototype._getUrl = function (options) { options = options || {}; diff --git a/lib/pkgcloud/rackspace/dns/client/records.js b/lib/pkgcloud/rackspace/dns/client/records.js index 2ac118179..bd421e3c9 100644 --- a/lib/pkgcloud/rackspace/dns/client/records.js +++ b/lib/pkgcloud/rackspace/dns/client/records.js @@ -7,11 +7,9 @@ * */ -var base = require('../../../core/dns'), - urlJoin = require('url-join'), +var urlJoin = require('url-join'), pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - _ = require('underscore'), + _ = require('lodash'), dns = pkgcloud.providers.rackspace.dns; var _urlPrefix = 'domains', @@ -66,7 +64,7 @@ module.exports = { path: urlJoin(_urlPrefix, zoneId, _recordFragment, recordId) }; - self._request(requestOptions, function (err, body, res) { + self._request(requestOptions, function (err, body) { return err ? callback(err) : callback(err, new dns.Record(self, body)); @@ -255,8 +253,8 @@ module.exports = { method: 'DELETE' }; - self._asyncRequest(requestOptions, function (err, result) { + self._asyncRequest(requestOptions, function (err) { return callback(err); }); } -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/dns/client/zones.js b/lib/pkgcloud/rackspace/dns/client/zones.js index 1017542d3..8d0f2cae3 100644 --- a/lib/pkgcloud/rackspace/dns/client/zones.js +++ b/lib/pkgcloud/rackspace/dns/client/zones.js @@ -7,11 +7,9 @@ * */ -var base = require('../../../core/dns'), - urlJoin = require('url-join'), +var urlJoin = require('url-join'), pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - _ = require('underscore'), + _ = require('lodash'), dns = pkgcloud.providers.rackspace.dns; var _urlPrefix = 'domains'; @@ -108,8 +106,9 @@ module.exports = { var listOfZones = []; _.each(zones, function (zone) { ['name', 'email'].forEach(function (required) { - if (!zone[required]) throw new Error('details.' + - required + ' is a required argument.'); + if (!zone[required]) { + throw new Error('details.' + required + ' is a required argument.'); + } }); var newZone = { @@ -160,8 +159,9 @@ module.exports = { var self = this; ['contentType', 'contents'].forEach(function (required) { - if (!details[required]) throw new Error('details.' + - required + ' is a required argument.'); + if (!details[required]) { + throw new Error('details.' + required + ' is a required argument.'); + } }); if (details.contentType !== 'BIND_9') { @@ -346,10 +346,10 @@ module.exports = { if (options.since) { requestOptions.qs = { since: options.since.toString() - } + }; } - self._request(requestOptions, function (err, body, res) { + self._request(requestOptions, function (err, body) { return err ? callback(err) : callback(err, body); @@ -438,4 +438,4 @@ module.exports = { }), res); }); } -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/dns/record.js b/lib/pkgcloud/rackspace/dns/record.js index 8e87943b7..b1921d60a 100644 --- a/lib/pkgcloud/rackspace/dns/record.js +++ b/lib/pkgcloud/rackspace/dns/record.js @@ -7,15 +7,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/dns/record'), - _ = require('underscore'); + _ = require('lodash'); var Record = exports.Record = function Record(zone, details) { base.Record.call(this, zone, details); }; -utile.inherits(Record, base.Record); +util.inherits(Record, base.Record); Record.prototype._setProperties = function (details) { var self = this; diff --git a/lib/pkgcloud/rackspace/dns/status.js b/lib/pkgcloud/rackspace/dns/status.js index 6e734cdf3..d0ee9956c 100644 --- a/lib/pkgcloud/rackspace/dns/status.js +++ b/lib/pkgcloud/rackspace/dns/status.js @@ -7,14 +7,14 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/base/model'); var Status = exports.Status = function Status(client, details) { base.Model.call(this, client, details); }; -utile.inherits(Status, base.Model); +util.inherits(Status, base.Model); /** * @name Status.getDetails @@ -31,7 +31,7 @@ Status.prototype.getDetails = function (callback) { qs: { showDetails: true } }; - self.client._request(requestOptions, function (err, body, res) { + self.client._request(requestOptions, function (err, body) { if (err) { return callback(err); } diff --git a/lib/pkgcloud/rackspace/dns/zone.js b/lib/pkgcloud/rackspace/dns/zone.js index d39e58172..64490fc2e 100644 --- a/lib/pkgcloud/rackspace/dns/zone.js +++ b/lib/pkgcloud/rackspace/dns/zone.js @@ -7,15 +7,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/dns/zone'), - _ = require('underscore'); + _ = require('lodash'); var Zone = exports.Zone = function Zone(client, details) { base.Zone.call(this, client, details); }; -utile.inherits(Zone, base.Zone); +util.inherits(Zone, base.Zone); Zone.prototype._setProperties = function (details) { var self = this; diff --git a/lib/pkgcloud/rackspace/identity/rackspaceIdentity.js b/lib/pkgcloud/rackspace/identity/rackspaceIdentity.js index eae184477..7909b8fe1 100644 --- a/lib/pkgcloud/rackspace/identity/rackspaceIdentity.js +++ b/lib/pkgcloud/rackspace/identity/rackspaceIdentity.js @@ -7,16 +7,16 @@ * */ -var _ = require('underscore'), - identity = require('../../openstack/context'), +var identity = require('../../openstack/context'), events = require('eventemitter2'), Identity = identity.Identity, util = require('util'); -exports.Identity = RackspaceIdentity = function (options) { +var RackspaceIdentity = exports.Identity = function (options) { this.options = options; this.name = 'RackspaceIdentity'; + this.basePath = options.basePath || '/v2.0/tokens'; this.useServiceCatalog = (typeof options.useServiceCatalog === 'boolean') ? options.useServiceCatalog : true; diff --git a/lib/pkgcloud/rackspace/index.js b/lib/pkgcloud/rackspace/index.js index 370ba7065..95ba375ee 100644 --- a/lib/pkgcloud/rackspace/index.js +++ b/lib/pkgcloud/rackspace/index.js @@ -1,7 +1,7 @@ /* * index.js: Top-level include for the Rackspace module. * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ @@ -10,4 +10,7 @@ exports.compute = require('./compute'); exports.database = require('./database'); exports.dns = require('./dns'); exports.loadbalancer = require('./loadbalancer'); +exports.orchestration = require('./orchestration'); exports.storage = require('./storage'); +exports.network = require('./network'); +exports.cdn = require('./cdn'); diff --git a/lib/pkgcloud/rackspace/loadbalancer/client/index.js b/lib/pkgcloud/rackspace/loadbalancer/client/index.js index 41e2caf41..15f3dffb1 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/client/index.js +++ b/lib/pkgcloud/rackspace/loadbalancer/client/index.js @@ -7,21 +7,21 @@ * */ -var utile = require('utile'), +var util = require('util'), rackspace = require('../../client'), urlJoin = require('url-join'), - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { rackspace.Client.call(this, options); - utile.mixin(this, require('./nodes.js')); - utile.mixin(this, require('./loadbalancers.js')); + _.extend(this, require('./nodes.js')); + _.extend(this, require('./loadbalancers.js')); this.serviceType = 'rax:load-balancer'; }; -utile.inherits(Client, rackspace.Client); +util.inherits(Client, rackspace.Client); Client.prototype._getUrl = function (options) { options = options || {}; diff --git a/lib/pkgcloud/rackspace/loadbalancer/client/loadbalancers.js b/lib/pkgcloud/rackspace/loadbalancer/client/loadbalancers.js index 6f621ad5e..b876f9d0e 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/client/loadbalancers.js +++ b/lib/pkgcloud/rackspace/loadbalancer/client/loadbalancers.js @@ -7,15 +7,69 @@ * */ -var base = require('../../../core/dns'), - urlJoin = require('url-join'), +var urlJoin = require('url-join'), pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - _ = require('underscore'), + _ = require('lodash'), lb = pkgcloud.providers.rackspace.loadbalancer; var _urlPrefix = 'loadbalancers'; +// Private function for validation of createLoadBalancer Inputs +var validateLbInputs = function (inputs) { + + var errors = { + requiredParametersMissing: [], + invalidInputs: [] + }, response; + + if (!inputs.name) { + errors.requiredParametersMissing.push('name'); + } + + if (!inputs.nodes) { + errors.requiredParametersMissing.push('nodes'); + } + + if (!inputs.protocol) { + errors.requiredParametersMissing.push('protocol'); + } + + if (!inputs.port) { + errors.requiredParametersMissing.push('port'); + } + + if (!inputs.virtualIps) { + errors.requiredParametersMissing.push('virtualIps'); + } + + if (inputs.name && inputs.name.length > 128) { + errors.invalidInputs.push('name exceeds maximum 128 length'); + } + + if (!inputs.protocol || + typeof(inputs.protocol) !== 'string' || !lb.Protocols[inputs.protocol]) { + errors.invalidInputs.push('please specify a valid protocol'); + } + + // TODO Node validation + + if (errors.requiredParametersMissing.length) { + response ? response.requiredParametersMissing = errors.requiredParametersMissing : + response = { + requiredParametersMissing: errors.requiredParametersMissing + }; + } + + if (errors.invalidInputs.length) { + response ? response.invalidInputs = errors.invalidInputs : + response = { + invalidInputs: errors.invalidInputs + }; + } + + return response; +}; + module.exports = { /** @@ -37,7 +91,7 @@ module.exports = { options = {}; } - self._request(requestOptions, function (err, body, res) { + self._request(requestOptions, function (err, body) { if (err) { return callback(err); } @@ -70,7 +124,7 @@ module.exports = { self._request({ path: urlJoin(_urlPrefix, loadBalancerId) - }, function (err, body, res) { + }, function (err, body) { if (err) { return callback(err); } @@ -333,7 +387,7 @@ module.exports = { self._request({ path: urlJoin(_urlPrefix, loadBalancerId, 'ssltermination'), method: 'DELETE' - }, function (err, body, res) { + }, function (err) { callback(err); }); }, @@ -355,7 +409,7 @@ module.exports = { self._request({ path: urlJoin(_urlPrefix, loadBalancerId, 'accesslist') - }, function (err, body, res) { + }, function (err, body) { return callback(err, body.accessList); }); }, @@ -448,7 +502,7 @@ module.exports = { self._request({ path: urlJoin(_urlPrefix, loadBalancerId, 'accesslist', '?id=' + list.join('&id=')), method: 'DELETE' - }, function (err, body, res) { + }, function (err) { return callback(err); }); }, @@ -552,7 +606,7 @@ module.exports = { type: details.type, delay: details.delay, timeout: details.timeout - } + }; } else if (details.type === 'HTTP' || details.type === 'HTTPS') { requestOptions.body = { @@ -563,7 +617,7 @@ module.exports = { bodyRegex: details.bodyRegex, path: details.path, statusRegex: details.statusRegex - } + }; if (details.hostHeader) { requestOptions.body.hostHeader = details.hostHeader; @@ -1124,59 +1178,3 @@ module.exports = { }); } }; - -// Private function for validation of createLoadBalancer Inputs -var validateLbInputs = function (inputs) { - - var errors = { - requiredParametersMissing: [], - invalidInputs: [] - }, response; - - if (!inputs.name) { - errors.requiredParametersMissing.push('name'); - } - - if (!inputs.nodes) { - errors.requiredParametersMissing.push('nodes'); - } - - if (!inputs.protocol) { - errors.requiredParametersMissing.push('protocol'); - } - - if (!inputs.port) { - errors.requiredParametersMissing.push('port'); - } - - if (!inputs.virtualIps) { - errors.requiredParametersMissing.push('virtualIps'); - } - - if (inputs.name && inputs.name.length > 128) { - errors.invalidInputs.push('name exceeds maximum 128 length'); - } - - if (!inputs.protocol || - typeof(inputs.protocol) !== 'string' || !lb.Protocols[inputs.protocol]) { - errors.invalidInputs.push('please specify a valid protocol'); - } - - // TODO Node validation - - if (errors.requiredParametersMissing.length) { - response ? response.requiredParametersMissing = errors.requiredParametersMissing : - response = { - requiredParametersMissing: errors.requiredParametersMissing - }; - } - - if (errors.invalidInputs.length) { - response ? response.invalidInputs = errors.invalidInputs : - response = { - invalidInputs: errors.invalidInputs - }; - } - - return response; -}; diff --git a/lib/pkgcloud/rackspace/loadbalancer/client/nodes.js b/lib/pkgcloud/rackspace/loadbalancer/client/nodes.js index 2c5754bec..9aa6df900 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/client/nodes.js +++ b/lib/pkgcloud/rackspace/loadbalancer/client/nodes.js @@ -7,11 +7,9 @@ * */ -var base = require('../../../core/dns'), - urlJoin = require('url-join'), +var urlJoin = require('url-join'), pkgcloud = require('../../../../../lib/pkgcloud'), - errs = require('errs'), - _ = require('underscore'), + _ = require('lodash'), lb = pkgcloud.providers.rackspace.loadbalancer; var _urlPrefix = 'loadbalancers'; @@ -122,7 +120,7 @@ module.exports = { loadBalancer instanceof lb.LoadBalancer ? loadBalancer.id : loadBalancer; if (!(node instanceof lb.Node) && (typeof node !== 'object')) { - throw new Error('node is require argument and must be an object'); + throw new Error('node is a required argument and must be an object'); } self._request({ @@ -214,4 +212,4 @@ module.exports = { : callback(err, body.nodeServiceEvents); }); } -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/loadbalancer/loadbalancer.js b/lib/pkgcloud/rackspace/loadbalancer/loadbalancer.js index 59e6d99c7..da6c2d3b8 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/loadbalancer.js +++ b/lib/pkgcloud/rackspace/loadbalancer/loadbalancer.js @@ -7,15 +7,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/loadbalancer/loadbalancer'), - _ = require('underscore'); + _ = require('lodash'); var LoadBalancer = exports.LoadBalancer = function LoadBalancer(client, details) { base.LoadBalancer.call(this, client, details); }; -utile.inherits(LoadBalancer, base.LoadBalancer); +util.inherits(LoadBalancer, base.LoadBalancer); LoadBalancer.prototype._setProperties = function (details) { var self = this; diff --git a/lib/pkgcloud/rackspace/loadbalancer/node.js b/lib/pkgcloud/rackspace/loadbalancer/node.js index 27e4976e7..be19b0757 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/node.js +++ b/lib/pkgcloud/rackspace/loadbalancer/node.js @@ -7,15 +7,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../core/loadbalancer/node'), - _ = require('underscore'); + _ = require('lodash'); var Node = exports.Node = function Node(client, details) { base.Node.call(this, client, details); }; -utile.inherits(Node, base.Node); +util.inherits(Node, base.Node); Node.prototype._setProperties = function (details) { var self = this; diff --git a/lib/pkgcloud/rackspace/loadbalancer/protocols.js b/lib/pkgcloud/rackspace/loadbalancer/protocols.js index 732b92141..24d274310 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/protocols.js +++ b/lib/pkgcloud/rackspace/loadbalancer/protocols.js @@ -11,75 +11,75 @@ exports.Protocols = { DNS_TCP: { - "name": "DNS_TCP", - "port": 53 + name: 'DNS_TCP', + port: 53 }, DNS_UDP: { - "name": "DNS_UDP", - "port": 53 + name: 'DNS_UDP', + port: 53 }, FTP: { - "name": "FTP", - "port": 21 + name: 'FTP', + port: 21 }, HTTP: { - "name": "HTTP", - "port": 80 + name: 'HTTP', + port: 80 }, HTTPS: { - "name": "HTTPS", - "port": 443 + name: 'HTTPS', + port: 443 }, IMAPS: { - "name": "IMAPS", - "port": 993 + name: 'IMAPS', + port: 993 }, IMAPv4: { - "name": "IMAPv4", - "port": 143 + name: 'IMAPv4', + port: 143 }, LDAP: { - "name": "LDAP", - "port": 389 + name: 'LDAP', + port: 389 }, LDAPS: { - "name": "LDAPS", - "port": 636 + name: 'LDAPS', + port: 636 }, MYSQL: { - "name": "MYSQL", - "port": 3306 + name: 'MYSQL', + port: 3306 }, POP3: { - "name": "POP3", - "port": 110 + name: 'POP3', + port: 110 }, POP3S: { - "name": "POP3S", - "port": 995 + name: 'POP3S', + port: 995 }, SMTP: { - "name": "SMTP", - "port": 25 + name: 'SMTP', + port: 25 }, TCP: { - "name": "TCP", - "port": 0 + name: 'TCP', + port: 0 }, TCP_CLIENT_FIRST: { - "name": "TCP_CLIENT_FIRST", - "port": 0 + name: 'TCP_CLIENT_FIRST', + port: 0 }, UDP: { - "name": "UDP", - "port": 0 + name: 'UDP', + port: 0 }, UDP_STREAM: { - "name": "UDP_STREAM", - "port": 0 + name: 'UDP_STREAM', + port: 0 }, SFTP: { - "name": "SFTP", - "port": 22 + name: 'SFTP', + port: 22 } }; \ No newline at end of file diff --git a/lib/pkgcloud/rackspace/loadbalancer/virtualip.js b/lib/pkgcloud/rackspace/loadbalancer/virtualip.js index f9c29b6b4..4c208c485 100644 --- a/lib/pkgcloud/rackspace/loadbalancer/virtualip.js +++ b/lib/pkgcloud/rackspace/loadbalancer/virtualip.js @@ -9,7 +9,7 @@ var VirtualIp = function (details) { if (!details) { - throw new Error('VirtualIp must be constructed with at-least basic details.') + throw new Error('VirtualIp must be constructed with at-least basic details.'); } this._setProperties(details); @@ -37,4 +37,4 @@ exports.VirtualIp = VirtualIp; exports.VirtualIpTypes = { PUBLIC: 'PUBLIC', SERVICENET: 'SERVICENET' -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/network/client/index.js b/lib/pkgcloud/rackspace/network/client/index.js new file mode 100644 index 000000000..b24d94dcf --- /dev/null +++ b/lib/pkgcloud/rackspace/network/client/index.js @@ -0,0 +1,35 @@ +/* + * client.js: client for Rackspace Networking + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT License + */ + +var util = require('util'), + rackspace = require('../../client'), + NetworkClient = require('../../../openstack/network/networkClient').NetworkClient, + _ = require('lodash'); + +var Client = exports.Client = function (options) { + rackspace.Client.call(this, options); + + this.models = { + Network: require('../../../openstack/network/network').Network, + Subnet: require('../../../openstack/network/subnet').Subnet, + Port: require('../../../openstack/network/port').Port, + SecurityGroup: require('../../../openstack/network/securityGroup').SecurityGroup, + SecurityGroupRule: require('../../../openstack/network/securityGroupRule').SecurityGroupRule + }; + + _.extend(this, require('../../../openstack/network/client/networks')); + _.extend(this, require('../../../openstack/network/client/subnets')); + _.extend(this, require('../../../openstack/network/client/ports')); + _.extend(this, require('../../../openstack/network/client/securityGroups')); + _.extend(this, require('../../../openstack/network/client/securityGroupRules')); + + this.serviceType = 'network'; +}; + +util.inherits(Client, rackspace.Client); +_.extend(Client.prototype, NetworkClient.prototype); diff --git a/lib/pkgcloud/rackspace/network/index.js b/lib/pkgcloud/rackspace/network/index.js new file mode 100644 index 000000000..6b3d249f9 --- /dev/null +++ b/lib/pkgcloud/rackspace/network/index.js @@ -0,0 +1,17 @@ +/* + * index.js: Top-level include for the Rackspace Networking module. + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +exports.Client = require('./client').Client; +exports.Network = require('../../openstack/network/network').Network; +exports.Subnet = require('../../openstack/network/subnet').Subnet; +exports.Port = require('../../openstack/network/port').Port; +exports.SecurityGroup = require('../../openstack/network/securityGroup').SecurityGroup; + +exports.createClient = function(options) { + return new exports.Client(options); +}; diff --git a/lib/pkgcloud/rackspace/orchestration/client/index.js b/lib/pkgcloud/rackspace/orchestration/client/index.js new file mode 100644 index 000000000..dddf74c50 --- /dev/null +++ b/lib/pkgcloud/rackspace/orchestration/client/index.js @@ -0,0 +1,67 @@ +/* + * client.js: client for Rackspace Orchestration + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ + +var util = require('util'), + rackspace = require('../../client'), + urlJoin = require('url-join'), + _ = require('lodash'); + +var Client = exports.Client = function (options) { + rackspace.Client.call(this, options); + + _.extend(this, require('../../../openstack/orchestration/client/events')); + _.extend(this, require('../../../openstack/orchestration/client/resources')); + _.extend(this, require('../../../openstack/orchestration/client/stacks')); + _.extend(this, require('../../../openstack/orchestration/client/templates')); + + this.serviceType = 'orchestration'; +}; + +util.inherits(Client, rackspace.Client); + +/** + * client._getUrl + * + * @description get the url for the current compute service + * + * @param options + * @returns {exports|*} + * @private + */ +Client.prototype._getUrl = function (options) { + options = options || {}; + + if (!this._serviceUrl) { + throw new Error('Service url not found'); + } + + return urlJoin(this._serviceUrl, + typeof options === 'string' + ? options + : options.path); +}; + +/** + * client.buildInfo + * + * @description gets the build information for the orchestration service + * + * @param callback + * @returns {*} + */ +Client.prototype.buildInfo = function (callback) { + return this._request({ + path: '/build_info' + }, function (err, body) { + if (err) { + return callback(err); + } + + callback(null, body); + }); +}; diff --git a/lib/pkgcloud/rackspace/orchestration/index.js b/lib/pkgcloud/rackspace/orchestration/index.js new file mode 100644 index 000000000..79ecf358b --- /dev/null +++ b/lib/pkgcloud/rackspace/orchestration/index.js @@ -0,0 +1,16 @@ + /* + * index.js: Top-level include for the Rackspace orchestration module + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + * + */ + +exports.Client = require('./client').Client; +exports.Stack = require('../../openstack/orchestration/stack').Stack; +exports.Resource = require('../../openstack/orchestration/resource').Resource; + +exports.createClient = function (options) { + return new exports.Client(options); +}; \ No newline at end of file diff --git a/lib/pkgcloud/rackspace/storage/client/archive.js b/lib/pkgcloud/rackspace/storage/client/archive.js index 8db42c4da..2caa9e24a 100644 --- a/lib/pkgcloud/rackspace/storage/client/archive.js +++ b/lib/pkgcloud/rackspace/storage/client/archive.js @@ -7,7 +7,7 @@ */ var fs = require('fs'), - filed = require('filed'); + filed = require('filed-mimefix'); /** * Client.extract diff --git a/lib/pkgcloud/rackspace/storage/client/cdn-containers.js b/lib/pkgcloud/rackspace/storage/client/cdn-containers.js index de99d991c..c3074d098 100644 --- a/lib/pkgcloud/rackspace/storage/client/cdn-containers.js +++ b/lib/pkgcloud/rackspace/storage/client/cdn-containers.js @@ -1,16 +1,13 @@ /* * cdn-containers.js: Instance methods for working with containers from Rackspace Cloudfiles * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var async = require('async'), crypto = require('crypto'), - request = require('request'), - base = require('../../../openstack'), - pkgcloud = require('../../../../pkgcloud'), - _ = require('underscore'); + _ = require('lodash'); /** * client.getFiles @@ -42,7 +39,7 @@ exports.getContainers = function (options, callback) { return callback(err); } else if (!body || !(body instanceof Array)) { - return new Error('Malformed API Response') + return new Error('Malformed API Response'); } if (!options.loadCDNAttributes) { @@ -62,7 +59,7 @@ exports.getContainers = function (options, callback) { return next(err); } next(); - }) + }); }, function (err) { callback(err, containers); }); @@ -287,7 +284,7 @@ exports._getCdnContainerDetails = function(container, callback) { container: containerName, serviceType: this.cdnServiceType }, function (err, body, res) { - if (err && !(err.statusCode === 404)) { + if (err && !(err.statusCode === 403 || err.statusCode === 404)) { return callback(err); } else if (err) { @@ -314,21 +311,101 @@ exports._getCdnContainerDetails = function(container, callback) { }); }; -exports.setTempUrlMetadataKey = function(key, callback) { +/** + * client.setStaticWebsite + * + * @description set the static website index page on a storage container + * + * @param {String|object} container the container or containerName + * @param {object} options an object with options + * @param {String} options.indexFile configure the static website index file for this container + * @param {String} options.errorFile configure the static website error file for this container + * @param callback + */ +exports.setStaticWebsite = function (container, options, callback) { + var indexFile = options.indexFile, errorFile = options.errorFile; + + if (typeof options === 'function') { + callback = options; + indexFile = 'index.html'; + errorFile = 'error.html'; + } + + container.metadata['web-index'] = indexFile; + container.metadata['web-error'] = errorFile; + + this.updateContainerMetadata(container, callback); +}; + +/** + * client.removeStaticWebsite + * + * @description remove the static website index/error page on a storage container + * + * @param {String|object} container the container or containerName + * @param callback + */ +exports.removeStaticWebsite = function (container, callback) { + var indexFile = container.metadata['web-index'], errorFile = container.metadata['web-error'], + metadata = { + 'web-index': indexFile, + 'web-error': errorFile + }; + + this.removeContainerMetadata(container, metadata, callback); +}; + +/** + * client.setTemporaryUrlKey + * + * @description set a temporaryUrl key on the current account + * + * @param {String} key the secret key to be used in hmac signing temporary urls + * @param callback + */ +exports.setTemporaryUrlKey = function(key, callback) { this._request({ method: 'POST', headers: { 'X-Account-Meta-Temp-Url-Key': key } }, function (err) { - callback(err) + callback(err); }); }; +/** + * client.generateTempUrl + * + * @description create a temporary url for GET/PUT to a cloud files container + * + * @param {String|object} container the container or container name for the url + * @param {String|object} file the file or fileName for the url + * @param {String} method either GET or PUT + * @param {Number} time expiry for the url in seconds (from now) + * @param {String} key the secret key to be used for signing the url + * @param callback + */ exports.generateTempUrl = function(container, file, method, time, key, callback) { - var self = this, split = '/v1'; + var containerName = container instanceof this.models.Container ? container.name : container, + fileName = file instanceof this.models.File ? file.name : file, + time = typeof time === 'number' ? time : parseInt(time), + self = this, + split = '/v1'; - time = parseInt(time); + function createUrl() { + // construct our hmac signature + var expiry = parseInt(new Date().getTime() / 1000) + time, + url = self._getUrl({ + container: containerName, + path: fileName + }), + hmac_body = method.toUpperCase() + '\n' + expiry + '\n' + split + url.split(split)[1]; + + var hash = crypto.createHmac('sha1', key).update(hmac_body).digest('hex'); + + callback(null, url + '?temp_url_sig=' + hash + '&temp_url_expires=' + expiry); + } // We have to be authed to make sure we have the service catalog // this is required to validate the service url @@ -348,17 +425,4 @@ exports.generateTempUrl = function(container, file, method, time, key, callback) createUrl(); - function createUrl() { - // construct our hmac signature - var expiry = parseInt(new Date().getTime() / 1000) + time, - url = self._getUrl({ - container: container, - path: file - }), - hmac_body = method + '\n' + expiry + '\n' + split + url.split(split)[1]; - - var hash = crypto.createHmac('sha1', key).update(hmac_body).digest('hex'); - - callback(null, url + "?temp_url_sig=" + hash + "&temp_url_expires=" + expiry); - } -}; \ No newline at end of file +}; diff --git a/lib/pkgcloud/rackspace/storage/client/files.js b/lib/pkgcloud/rackspace/storage/client/files.js index 955ad0ff3..795f1e765 100644 --- a/lib/pkgcloud/rackspace/storage/client/files.js +++ b/lib/pkgcloud/rackspace/storage/client/files.js @@ -6,13 +6,6 @@ * */ -var fs = require('fs'), - request = require('request'), - utile = require('utile'), - base = require('../../../core/storage'), - pkgcloud = require('../../../../pkgcloud'), - _ = require('underscore'); - // // ### function purgeFileFromCdn (container, file, emails, callback) // #### @container {string} Name of the container to destroy the file in diff --git a/lib/pkgcloud/rackspace/storage/client/index.js b/lib/pkgcloud/rackspace/storage/client/index.js index 24ad889d7..476419eab 100644 --- a/lib/pkgcloud/rackspace/storage/client/index.js +++ b/lib/pkgcloud/rackspace/storage/client/index.js @@ -1,14 +1,14 @@ /* * client.js: Compute client for Rackspace Cloudservers * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var utile = require('utile'), +var util = require('util'), rackspace = require('../../client'), StorageClient = require('../../../openstack/storage/storageClient').StorageClient, - _ = require('underscore'); + _ = require('lodash'); var Client = exports.Client = function (options) { rackspace.Client.call(this, options); @@ -18,16 +18,16 @@ var Client = exports.Client = function (options) { File: require('../file').File }; - utile.mixin(this, require('../../../openstack/storage/client/containers')); - utile.mixin(this, require('../../../openstack/storage/client/files')); - utile.mixin(this, require('./archive')); - utile.mixin(this, require('./cdn-containers')); - utile.mixin(this, require('./files')); + _.extend(this, require('../../../openstack/storage/client/containers')); + _.extend(this, require('../../../openstack/storage/client/files')); + _.extend(this, require('./archive')); + _.extend(this, require('./cdn-containers')); + _.extend(this, require('./files')); this.serviceType = 'object-store'; this.cdnServiceType = 'rax:object-cdn'; }; -utile.inherits(Client, rackspace.Client); +util.inherits(Client, rackspace.Client); _.extend(Client.prototype, StorageClient.prototype); diff --git a/lib/pkgcloud/rackspace/storage/container.js b/lib/pkgcloud/rackspace/storage/container.js index ef3986ad9..182f3a901 100644 --- a/lib/pkgcloud/rackspace/storage/container.js +++ b/lib/pkgcloud/rackspace/storage/container.js @@ -6,15 +6,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../openstack/storage/container'), - _ = require('underscore'); + _ = require('lodash'); var Container = exports.Container = function Container(client, details) { base.Container.call(this, client, details); }; -utile.inherits(Container, base.Container); +util.inherits(Container, base.Container); Container.prototype.refreshCdnDetails = function (callback) { var self = this; @@ -42,6 +42,14 @@ Container.prototype.updateCdn = function (options, callback) { this.client.updateCdnContainer(this, options, callback); }; +Container.prototype.setStaticWebsite = function (options, callback) { + this.client.setStaticWebsite(this, options, callback); +}; + +Container.prototype.removeStaticWebsite = function (callback) { + this.client.removeStaticWebsite(this, callback); +}; + Container.prototype._setProperties = function (details) { this.cdnEnabled = details.cdnEnabled || this.cdnEnabled || false; this.cdnUri = details.cdnUri || this.cdnUri; diff --git a/lib/pkgcloud/rackspace/storage/file.js b/lib/pkgcloud/rackspace/storage/file.js index 5e72adc97..f12ce2c8e 100644 --- a/lib/pkgcloud/rackspace/storage/file.js +++ b/lib/pkgcloud/rackspace/storage/file.js @@ -7,15 +7,15 @@ * */ -var utile = require('utile'), +var util = require('util'), base = require('../../openstack/storage/file'), - _ = require('underscore'); + _ = require('lodash'); var File = exports.File = function File(client, details) { base.File.call(this, client, details); }; -utile.inherits(File, base.File); +util.inherits(File, base.File); File.prototype.purgeFromCdn = function (emails, callback) { this.client.purgeFileFromCdn(this.container, this, emails, callback); diff --git a/lib/pkgcloud/redistogo/database/client/index.js b/lib/pkgcloud/redistogo/database/client/index.js deleted file mode 100644 index 0fecca24d..000000000 --- a/lib/pkgcloud/redistogo/database/client/index.js +++ /dev/null @@ -1,146 +0,0 @@ -/* - * client.js: Database client for RedisToGo Cloud Databases - * - * (C) 2011 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - request = require('request'), - pkgcloud = require('../../../../pkgcloud'), - errs = require('errs'); - -var Client = exports.Client = function (options) { - this.username = options.username; - this.password = options.password; - this._url = options.url || "https://redistogo.com"; -}; - -Client.prototype._getUrl = function () { - return this._url; -}; - -// -// Wrapper for all http requests with RedisToGo -// - -Client.prototype._request = function (options, callback) { - var self = this; - - options.headers['User-Agent'] = utile.format('nodejs-pkgcloud/%s', pkgcloud.version); - - request(options, function (err, response, body) { - if (err) { - return callback(err); - } - if (response.statusCode == 401 || response.statusCode == 403) { - return callback("Unauthorized"); - } - if (options.method !== "DELETE") { - var database; - if (typeof body !== 'object') { - try { - database = JSON.parse(body); - } catch (e) { - return callback("Bad response from server.", body); - } - } else { database = body; } - database = self.formatResponse(database); - return callback(null, database); - } else { - return callback(null, 'deleted'); - } - }); -}; - -// Create a new database at redistogo -// Need a correct plan -// ### @attrs {Object} Map of options -// ##### @attrs['plan'] Plan for the database.(required) -Client.prototype.create = function (attrs, callback) { - // Check for options. - if (!attrs || typeof attrs === 'function') { - return errs.handle(errs.create({ - message: 'Options required for create a database.' - }), Array.prototype.slice.call(arguments).pop()); - } - // Check for plan. - if (!attrs['plan']) { - attrs['plan'] = 'nano'; - } - // - // TODO: Add validation for options.plan types - // - var options = { - uri : this._getUrl() + '/instances.json', - method : 'POST', - body : 'instance%5Bplan%5D=' + attrs.plan, - headers: { - 'Authorization': "Basic " + utile.base64.encode(this.username + ':' + this.password) - } - }; - this._request(options, callback); -}; - -// Get information about specific database -// Need the database ID -// ### @id {String} ID of the database.(required) -Client.prototype.get = function (id, callback) { - // Check for id - if (!id || typeof id === 'function') { - return errs.handle(errs.create({ - message: 'ID is a required argument' - }), Array.prototype.slice.call(arguments).pop()); - } - var options, - path = '/instances', - self = this; - if (id !== null) { - path = path + '/' + id; - } - options = { - uri : this._getUrl() + path + '.json', - method : 'GET', - headers: { - 'Authorization': "Basic " + utile.base64.encode(this.username + ':' + this.password) - } - }; - this._request(options, callback); -}; - -// Removes one Redis instance by id -// Need the database ID -// ### @id {String} ID of the database.(required) -Client.prototype.remove = function (id, callback) { - // Check for id - if (!id || typeof id === 'function') { - return errs.handle(errs.create({ - message: 'ID is a required argument' - }), Array.prototype.slice.call(arguments).pop()); - } - var options, - path = '/instances/' + id, - self = this; - options = { - uri : this._getUrl() + path + '.json', - method : 'DELETE', - headers: { - 'Authorization': "Basic " + utile.base64.encode(this.username + ':' + this.password), - 'Content-Length': 0 - } - }; - this._request(options, callback); -}; - -Client.prototype.formatResponse = function (response) { - var database = { - id: response.id, - port: response.port, - host: response.label.split('-')[0] + '.redistogo.com', - uri: 'redis://nodejitsu:' + response.password + '@' + response.label.split('-')[0] + '.redistogo.com:' + response.port, - username: 'nodejitsu', - password: response.password, - metadata: response - }; - return database; -}; \ No newline at end of file diff --git a/lib/pkgcloud/redistogo/database/index.js b/lib/pkgcloud/redistogo/database/index.js deleted file mode 100644 index 213bca089..000000000 --- a/lib/pkgcloud/redistogo/database/index.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - * index.js: Top-level include for the RedisToGo database module - * - * (C) 2011 Nodejitsu Inc. - * - */ - -exports.Client = require('./client').Client; - -exports.createClient = function createClient(options) { - return new exports.Client(options); -}; diff --git a/lib/pkgcloud/redistogo/index.js b/lib/pkgcloud/redistogo/index.js deleted file mode 100644 index 7fbc67c26..000000000 --- a/lib/pkgcloud/redistogo/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * index.js: Top-level include for the RedisToGo module. - * - * (C) 2011 Nodejitsu Inc. - * - */ - -exports.database = require('./database'); diff --git a/lib/pkgcloud/telefonica/compute/client.js b/lib/pkgcloud/telefonica/compute/client.js deleted file mode 100644 index b157390f6..000000000 --- a/lib/pkgcloud/telefonica/compute/client.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * index.js: Compute client for Telefonica InstantServers CloudAPI - * - * (C) 2012 Nodejitsu Inc. - * - */ - -var utile = require('utile'), - urlJoin = require('url-join'), - joyent = require('../../joyent/compute'); - -var Client = exports.Client = function (options) { - joyent.Client.call(this, options); - - this.serversUrl = options.serversUrl - || process.env.SDC_CLI_URL - || 'api-eu-lon-1.instantservers.telefonica.com'; -}; - -utile.inherits(Client, joyent.Client); - -Client.prototype._getUrl = function (options) { - options = options || {}; - - return urlJoin(this.serversUrl - ? 'https://' + this.serversUrl - : 'https://api-eu-lon-1.instantservers.telefonica.com', - (typeof options === 'string' ? - options : options.path)); -}; diff --git a/lib/pkgcloud/telefonica/compute/index.js b/lib/pkgcloud/telefonica/compute/index.js deleted file mode 100644 index 1e48156f6..000000000 --- a/lib/pkgcloud/telefonica/compute/index.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * index.js: Top-level include for the Telefonica compute module - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.Client = require('./client').Client; -exports.Flavor = require('../../joyent/compute/flavor').Flavor; -exports.Image = require('../../joyent/compute/image').Image; -exports.Server = require('../../joyent/compute/server').Server; - -exports.createClient = function (options) { - return new exports.Client(options); -}; diff --git a/lib/pkgcloud/telefonica/index.js b/lib/pkgcloud/telefonica/index.js deleted file mode 100644 index 3c0872103..000000000 --- a/lib/pkgcloud/telefonica/index.js +++ /dev/null @@ -1,8 +0,0 @@ -/* - * index.js: Top-level include for the Telefonica module. - * - * (C) 2012 Nodejitsu Inc. - * - */ - -exports.compute = require('./compute'); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..e6d06e53f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3731 @@ +{ + "name": "pkgcloud", + "version": "2.2.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", + "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/generator": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.5.0.tgz", + "integrity": "sha512-1TTVrt7J9rcG5PMjvO7VEG3FrEoEJNHxumRq66GemPmzboLWtIjjcJgk8rokuAS7IiRSpgVSu5Vb9lc99iJkOA==", + "dev": true, + "requires": { + "@babel/types": "^7.5.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-function-name": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", + "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.0.0", + "@babel/template": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", + "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/highlight": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz", + "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.5.0.tgz", + "integrity": "sha512-I5nW8AhGpOXGCCNYGc+p7ExQIBxRFnS2fd/d862bNOKvmoEPjYPcfIjsfdy0ujagYOIYPczKgD9l3FsgTkAzKA==", + "dev": true + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.5.0.tgz", + "integrity": "sha512-SnA9aLbyOCcnnbQEGwdfBggnc142h/rbqqsXcaATj2hZcegCl903pUD/lfpsNBlBSuWow/YDfRyJuWi2EPR5cg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.5.0", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.5.0", + "@babel/types": "^7.5.0", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@babel/types": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.5.0.tgz", + "integrity": "sha512-UFpDVqRABKsW01bvw7/wSUe56uy6RXM5+VJibVVAybDGxEW25jdwiFJEf7ASvSaC7sN7rbE/l3cLp2izav+CtQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "@google-cloud/common": { + "version": "0.32.1", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", + "integrity": "sha512-bLdPzFvvBMtVkwsoBtygE9oUm3yrNmPa71gvOgucYI/GqvNP2tb6RYsDHPq98kvignhcgHGDI5wyNgxaCo8bKQ==", + "requires": { + "@google-cloud/projectify": "^0.3.3", + "@google-cloud/promisify": "^0.4.0", + "@types/request": "^2.48.1", + "arrify": "^2.0.0", + "duplexify": "^3.6.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^3.1.1", + "pify": "^4.0.1", + "retry-request": "^4.0.0", + "teeny-request": "^3.11.3" + }, + "dependencies": { + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + } + } + }, + "@google-cloud/paginator": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-0.2.0.tgz", + "integrity": "sha512-2ZSARojHDhkLvQ+CS32K+iUhBsWg3AEw+uxtqblA7xoCABDyhpj99FPp35xy6A+XlzMhOSrHHaxFE+t6ZTQq0w==", + "requires": { + "arrify": "^1.0.1", + "extend": "^3.0.1", + "split-array-stream": "^2.0.0", + "stream-events": "^1.0.4" + } + }, + "@google-cloud/projectify": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-0.3.3.tgz", + "integrity": "sha512-7522YHQ4IhaafgSunsFF15nG0TGVmxgXidy9cITMe+256RgqfcrfWphiMufW+Ou4kqagW/u3yxwbzVEW3dk2Uw==" + }, + "@google-cloud/promisify": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.4.0.tgz", + "integrity": "sha512-4yAHDC52TEMCNcMzVC8WlqnKKKq+Ssi2lXoUg9zWWkZ6U6tq9ZBRYLHHCRdfU+EU9YJsVmivwGcKYCjRGjnf4Q==" + }, + "@google-cloud/storage": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-2.5.0.tgz", + "integrity": "sha512-q1mwB6RUebIahbA3eriRs8DbG2Ij81Ynb9k8hMqTPkmbd8/S6Z0d6hVvfPmnyvX9Ej13IcmEYIbymuq/RBLghA==", + "requires": { + "@google-cloud/common": "^0.32.0", + "@google-cloud/paginator": "^0.2.0", + "@google-cloud/promisify": "^0.4.0", + "arrify": "^1.0.0", + "async": "^2.0.1", + "compressible": "^2.0.12", + "concat-stream": "^2.0.0", + "date-and-time": "^0.6.3", + "duplexify": "^3.5.0", + "extend": "^3.0.0", + "gcs-resumable-upload": "^1.0.0", + "hash-stream-validation": "^0.2.1", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "pumpify": "^1.5.1", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "teeny-request": "^3.11.3", + "through2": "^3.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/form-data": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", + "integrity": "sha512-JAMFhOaHIciYVh8fb5/83nmuO/AHwmto+Hq7a9y8FzLDcC1KCU344XDOMEmahnrTFlHjgh4L0WJFczNIX2GxnQ==", + "requires": { + "@types/node": "*" + } + }, + "@types/node": { + "version": "12.0.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.12.tgz", + "integrity": "sha512-Uy0PN4R5vgBUXFoJrKryf5aTk3kJ8Rv3PdlHjl6UaX+Cqp1QE0yPQ68MPXGrZOfG7gZVNDIJZYyot0B9ubXUrQ==" + }, + "@types/request": { + "version": "2.48.1", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.1.tgz", + "integrity": "sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg==", + "requires": { + "@types/caseless": "*", + "@types/form-data": "*", + "@types/node": "*", + "@types/tough-cookie": "*" + } + }, + "@types/tough-cookie": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", + "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, + "agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "ajv": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.1.tgz", + "integrity": "sha512-w1YQaVGNC6t2UCPjEawK/vo/dG8OOrVtUmhBT1uJJYxbl5kU2Tj3v6LGqBcsysN1yhuCStJCCA3GqdvKY8sqXQ==", + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", + "requires": { + "lodash": "^4.17.11" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sdk": { + "version": "2.488.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.488.0.tgz", + "integrity": "sha512-9AP48tyF1E5+x1CKeiRlj0Sv1YF7KI0BdSW9JP8x3ClhPWNUHjDYNH2OwsALuG1BloeY2ZigYqfI2fB7g3rNHQ==", + "requires": { + "buffer": "4.9.1", + "events": "1.1.1", + "ieee754": "1.1.8", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + } + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "base64-js": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", + "integrity": "sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bignumber.js": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-7.2.1.tgz", + "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cli": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cli/-/cli-1.0.1.tgz", + "integrity": "sha1-IoF1NPJL+klQw01TLUjsvGIbjBQ=", + "dev": true, + "requires": { + "exit": "0.1.2", + "glob": "^7.1.1" + }, + "dependencies": { + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "cliui": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", + "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=" + }, + "compressible": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.17.tgz", + "integrity": "sha512-BGHeLCK1GV7j1bSmQQAi26X+GgWcTjLr/0tzSvMCl3LH1w1IJ4PFSPoV5316b30cneTziC+B1a+3OjoSUcQYmw==", + "requires": { + "mime-db": ">= 1.40.0 < 2" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", + "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "configstore": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-4.0.0.tgz", + "integrity": "sha512-CmquAXFBocrzaSM8mtGPMM/HiWmyIpr4CcJl/rgY2uCObZ/S7cKU0silxslqJejl+t/T9HS8E0PUNQD81JGUEQ==", + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-browserify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", + "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", + "dev": true, + "requires": { + "date-now": "^0.1.4" + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "coveralls": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.4.tgz", + "integrity": "sha512-eyqUWA/7RT0JagiL0tThVhjbIjoiEUyWCjtUJoOPcWoeofP5WK/jb2OJYoBFrR6DvplR+AxOyuBqk4JHkk5ykA==", + "dev": true, + "requires": { + "growl": "~> 1.10.0", + "js-yaml": "^3.11.0", + "lcov-parse": "^0.0.10", + "log-driver": "^1.2.7", + "minimist": "^1.2.0", + "request": "^2.86.0" + }, + "dependencies": { + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-and-time": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.6.3.tgz", + "integrity": "sha512-lcWy3AXDRJOD7MplwZMmNSRM//kZtJaLz4n6D1P5z9wEmZGBKhJRBIr1Xs9KNQJmdXPblvgffynYji4iylUTcA==" + }, + "date-now": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", + "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=" + }, + "dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dev": true, + "requires": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + } + } + }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", + "dev": true + }, + "domhandler": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz", + "integrity": "sha1-LeWaCCLVAn+r/28DLCsloqir5zg=", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", + "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", + "requires": { + "is-obj": "^1.0.0" + } + }, + "duplexify": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", + "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", + "requires": { + "once": "^1.4.0" + } + }, + "ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" + }, + "entities": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", + "dev": true + }, + "errs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/errs/-/errs-0.3.2.tgz", + "integrity": "sha1-eYCZstvTfKK8dJ5TinwTB9C1BJk=" + }, + "es-abstract": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", + "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.0", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "is-callable": "^1.1.4", + "is-regex": "^1.0.4", + "object-keys": "^1.0.12" + } + }, + "es-to-primitive": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", + "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esutils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "eventemitter2": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-5.0.1.tgz", + "integrity": "sha1-YZegldX7a1folC9v1+qtY6CclFI=" + }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + }, + "fast-json-patch": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-2.1.0.tgz", + "integrity": "sha512-PipOsAKamRw7+CXtKiieehyjUeDVPJ5J7b2kdJYerEf6TSUQoD2ijpVyZ88KQm5YXziff4h762bz3+vzf56khg==", + "requires": { + "deep-equal": "^1.0.1" + } + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + }, + "fast-text-encoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", + "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" + }, + "filed-mimefix": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/filed-mimefix/-/filed-mimefix-0.1.3.tgz", + "integrity": "sha1-Cwtn0HWmP8dPJv3znH+dQxSWe7U=", + "requires": { + "mime": "^1.4.0" + }, + "dependencies": { + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + } + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gaxios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-1.8.4.tgz", + "integrity": "sha512-BoENMnu1Gav18HcpV9IleMPZ9exM+AvUjrAOV4Mzs/vfz2Lu/ABv451iEXByKiMPn2M140uul1txXCg83sAENw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-1.0.0.tgz", + "integrity": "sha512-Q6HrgfrCQeEircnNP3rCcEgiDv7eF9+1B+1MMgpE190+/+0mjQR8PxeOaRgxZWmdDAF9EIryHB9g1moPiw1SbQ==", + "requires": { + "gaxios": "^1.0.2", + "json-bigint": "^0.3.0" + } + }, + "gcs-resumable-upload": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-1.1.0.tgz", + "integrity": "sha512-uBz7uHqp44xjSDzG3kLbOYZDjxxR/UAGbB47A0cC907W6yd2LkcyFDTHg+bjivkHMwiJlKv4guVWcjPCk2zScg==", + "requires": { + "abort-controller": "^2.0.2", + "configstore": "^4.0.0", + "gaxios": "^1.5.0", + "google-auth-library": "^3.0.0", + "pumpify": "^1.5.1", + "stream-events": "^1.0.4" + }, + "dependencies": { + "abort-controller": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-2.0.3.tgz", + "integrity": "sha512-EPSq5wr2aFyAZ1PejJB32IX9Qd4Nwus+adnp7STYFM5/23nLPBazqZ1oor6ZqbH+4otaaGXTlC8RN5hq3C8w9Q==", + "requires": { + "event-target-shim": "^5.0.0" + } + } + } + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + }, + "dependencies": { + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "requires": { + "inherits": "2", + "minimatch": "0.3" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "google-auth-library": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", + "integrity": "sha512-cDQMzTotwyWMrg5jRO7q0A4TL/3GWBgO7I7q5xGKNiiFf9SmGY/OJ1YsLMgI2MVHHsEGyrqYnbnmV1AE+Z6DnQ==", + "requires": { + "base64-js": "^1.3.0", + "fast-text-encoding": "^1.0.0", + "gaxios": "^1.2.1", + "gcp-metadata": "^1.0.0", + "gtoken": "^2.3.2", + "https-proxy-agent": "^2.2.1", + "jws": "^3.1.5", + "lru-cache": "^5.0.0", + "semver": "^5.5.0" + } + }, + "google-p12-pem": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-1.0.4.tgz", + "integrity": "sha512-SwLAUJqUfTB2iS+wFfSS/G9p7bt4eWcc2LyfvmUXe7cWp6p3mpxDo6LLI29MXdU6wvPcQ/up298X7GMC5ylAlA==", + "requires": { + "node-forge": "^0.8.0", + "pify": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.0.tgz", + "integrity": "sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==" + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=" + }, + "gtoken": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", + "integrity": "sha512-EaB49bu/TCoNeQjhCYKI/CurooBKkGxIqFHsWABW0b25fobBYVTMe84A8EBVVZhl8emiUdNypil9huMOTmyAnw==", + "requires": { + "gaxios": "^1.0.4", + "google-p12-pem": "^1.0.0", + "jws": "^3.1.5", + "mime": "^2.2.0", + "pify": "^4.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", + "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "dev": true + }, + "hash-stream-validation": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.1.tgz", + "integrity": "sha1-7Mm5l7IYvluzEphii7gHhptz3NE=", + "requires": { + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "hock": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/hock/-/hock-1.3.3.tgz", + "integrity": "sha512-bEX7KH/KSv2Q5zA+o1EdyeH52+gD2cfpYyAsHMEwjb9txXWttityKVf7cG0y3UVA4D8bxKDzH8LVXCQIr9rClg==", + "dev": true, + "requires": { + "deep-equal": "0.2.1", + "url-equal": "0.1.2-1" + }, + "dependencies": { + "deep-equal": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.1.tgz", + "integrity": "sha1-+tenkyJMvww8d4b5LveA5PyMyHg=", + "dev": true + } + } + }, + "htmlparser2": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", + "dev": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", + "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "requires": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + } + }, + "ieee754": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.8.tgz", + "integrity": "sha1-vjPUCsEO8ZJnAfbwii2G+/0a0+Q=" + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "invert-kv": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true + }, + "ip": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", + "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" + }, + "is-buffer": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", + "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", + "dev": true + }, + "is-callable": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", + "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, + "is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" + }, + "is-symbol": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", + "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "dev": true, + "requires": { + "has-symbols": "^1.0.0" + } + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", + "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "dev": true + } + } + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=" + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=" + } + } + }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "jshint": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/jshint/-/jshint-2.10.2.tgz", + "integrity": "sha512-e7KZgCSXMJxznE/4WULzybCMNXNAd/bf5TSrvVEq78Q/K8ZwFpmBqQeDtNiHc3l49nV4E/+YeHU/JZjSUIrLAA==", + "dev": true, + "requires": { + "cli": "~1.0.0", + "console-browserify": "1.1.x", + "exit": "0.1.x", + "htmlparser2": "3.8.x", + "lodash": "~4.17.11", + "minimatch": "~3.0.2", + "shelljs": "0.3.x", + "strip-json-comments": "1.0.x" + }, + "dependencies": { + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + } + } + }, + "json-bigint": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", + "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "requires": { + "bignumber.js": "^7.0.0" + } + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "requires": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "lcid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", + "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "lcov-parse": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", + "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "dev": true + }, + "liboneandone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/liboneandone/-/liboneandone-1.2.0.tgz", + "integrity": "sha512-EB6Ak9qw+U4HAOnKqPtatxQ9pLclvtsBsggrvOuD4zclJ5xOeEASojsLKEC3O8KJ1Q4obE2JHhOeDuqWXvkoUQ==", + "requires": { + "mocha": "^2.5.3", + "request": "^2.74.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "requires": { + "ms": "0.7.1" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=" + } + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", + "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + } + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", + "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^2.0.0", + "p-is-promise": "^2.0.0" + } + }, + "mime": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", + "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==" + }, + "mime-db": { + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" + }, + "mime-types": { + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "requires": { + "mime-db": "1.40.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" + } + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz", + "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.2.2", + "yargs-parser": "13.0.0", + "yargs-unparser": "1.5.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, + "node-forge": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "nyc": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-13.3.0.tgz", + "integrity": "sha512-P+FwIuro2aFG6B0Esd9ZDWUd51uZrAEoGutqZxzrVmYl3qSfkLgcQpBPBjtDFsUQLFY1dvTQJPOyeqr8S9GF8w==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^3.0.1", + "convert-source-map": "^1.6.0", + "find-cache-dir": "^2.0.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.3", + "istanbul-lib-hook": "^2.0.3", + "istanbul-lib-instrument": "^3.1.0", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.2", + "istanbul-reports": "^2.1.1", + "make-dir": "^1.3.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.1.0", + "uuid": "^3.3.2", + "yargs": "^12.0.5", + "yargs-parser": "^11.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "async": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "lodash": "^4.17.11" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "caching-transform": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^1.3.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.3.0" + } + }, + "camelcase": { + "version": "5.0.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "commander": { + "version": "2.17.1", + "bundled": true, + "dev": true, + "optional": true + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-error": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "execa": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "6.0.5", + "bundled": true, + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "find-cache-dir": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^1.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "async": "^2.5.0", + "optimist": "^0.6.1", + "source-map": "^0.6.1", + "uglify-js": "^3.1.4" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "hasha": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-stream": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invert-kv": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.3", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "supports-color": "^6.0.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.3", + "make-dir": "^1.3.0", + "rimraf": "^2.6.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "^4.1.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "lcid": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "bundled": true, + "dev": true + }, + "lodash.flattendeep": { + "version": "4.4.0", + "bundled": true, + "dev": true + }, + "lru-cache": { + "version": "4.1.5", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "map-age-cleaner": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "requires": { + "p-defer": "^1.0.0" + } + }, + "mem": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "map-age-cleaner": "^0.1.1", + "mimic-fn": "^1.0.0", + "p-is-promise": "^2.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.10", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + } + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "normalize-package-data": { + "version": "2.5.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-is-promise": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "read-pkg": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + }, + "release-zalgo": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve": { + "version": "1.10.0", + "bundled": true, + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.3", + "bundled": true, + "dev": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "arrify": "^1.0.1", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^1.0.1" + } + }, + "uglify-js": { + "version": "3.4.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "2.4.2", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "12.0.5", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os-locale": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", + "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, + "requires": { + "execa": "^1.0.0", + "lcid": "^2.0.0", + "mem": "^4.0.0" + } + }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true + }, + "p-limit": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", + "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "psl": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.2.0.tgz", + "integrity": "sha512-GEn74ZffufCmkDDLNcl3uuyF/aSD6exEyh1v/ZSdAomB82t6G9hzJVRx0jBmLDW+VfZqks3aScmMw9DszwUalA==" + }, + "pump": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", + "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", + "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + } + }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "readable-stream": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "request": { + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "retry-request": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-4.1.1.tgz", + "integrity": "sha512-BINDzVtLI2BDukjWmjAIRZ0oglnCAkpP2vQjM3jdLhmT62h0xnQgciPwBRDAvHqpkPT2Wo1XuUyLyn6nbGrZQQ==", + "requires": { + "debug": "^4.1.1", + "through2": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, + "shelljs": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", + "dev": true + }, + "should": { + "version": "13.2.3", + "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", + "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, + "requires": { + "should-equal": "^2.0.0", + "should-format": "^3.0.3", + "should-type": "^1.4.0", + "should-type-adaptors": "^1.0.1", + "should-util": "^1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "^1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-type-adaptors": "^1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", + "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, + "requires": { + "should-type": "^1.3.0", + "should-util": "^1.0.0" + } + }, + "should-util": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha1-EMCI2LWOsHazIpu1oE4jLOEmQi0=" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "split-array-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/split-array-stream/-/split-array-stream-2.0.0.tgz", + "integrity": "sha512-hmMswlVY91WvGMxs0k8MRgq8zb2mSen4FmDNc5AFiTWtrBpdZN6nwD6kROVe4vNL+ywrvbCKsWVCnEd4riELIg==", + "requires": { + "is-stream-ended": "^0.1.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "requires": { + "stubs": "^3.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", + "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, + "strip-json-comments": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz", + "integrity": "sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E=", + "dev": true + }, + "stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=" + }, + "teeny-request": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", + "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==", + "requires": { + "https-proxy-agent": "^2.2.1", + "node-fetch": "^2.2.0", + "uuid": "^3.3.2" + } + }, + "through2": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.1.tgz", + "integrity": "sha512-M96dvTalPT3YbYLaKaCuwu+j06D/8Jfib0o/PxbVt6Amhv3dUAtW6rTV1jPgJSBG83I/e04Y6xkVdVhSRhi0ww==", + "requires": { + "readable-stream": "2 || 3" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=" + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + }, + "dependencies": { + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + } + } + }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", + "dev": true + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "url-equal": { + "version": "0.1.2-1", + "resolved": "https://registry.npmjs.org/url-equal/-/url-equal-0.1.2-1.tgz", + "integrity": "sha1-IjeVIL/gfSa1kIAEkIruEuhNBf8=", + "dev": true, + "requires": { + "deep-equal": "~1.0.1" + } + }, + "url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "xtend": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + }, + "yargs": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", + "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "os-locale": "^3.1.0", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", + "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", + "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.11", + "yargs": "^12.0.5" + }, + "dependencies": { + "get-caller-file": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, + "yargs": { + "version": "12.0.5", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", + "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^1.0.1", + "os-locale": "^3.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1 || ^4.0.0", + "yargs-parser": "^11.1.1" + } + }, + "yargs-parser": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", + "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + } + } +} diff --git a/package.json b/package.json index 28c331d57..85af72f3f 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,70 @@ { - "name": "pkgcloud", - "description": "An infrastructure-as-a-service agnostic cloud library for node.js", - "version": "0.9.4", - "author": "Nodejitsu Inc ", + "name": "pkgcloud", + "description": "A provider agnostic cloud library for Node.js", + "version": "2.2.0", + "author": "Charlie Robbins ", "contributors": [ - { "name": "Charlie Robbins", "email": "charlie@nodejitsu.com" }, - { "name": "Nuno Job", "email": "nuno@nodejitsu.com" }, - { "name": "Daniel Aristizabal", "email": "daniel@nodejitsu.com" }, - { "name": "Ken Perkins", "email": "ken.perkins@rackspace.com" } + "Ross Kukulinski ", + "Jarrett Cruger ", + "Ken Perkins " ], "repository": { "type": "git", - "url" : "http://github.com/pkgcloud/pkgcloud.git" + "url": "http://github.com/pkgcloud/pkgcloud.git" }, "keywords": [ - "cloud", "cloud computing", - "api", "rackspace", - "joyent", "aws", - "amazon", "azure", - "iaas", "servers", - "compute", "storage", - "databases", "client", - "mongolab", "iriscouch", - "mongohq", "openstack", - "redistogo" + "cloud", + "cloud computing", + "api", + "rackspace", + "aws", + "amazon", + "azure", + "google", + "iaas", + "servers", + "compute", + "storage", + "client", + "openstack", + "hpcloud", + "hp", + "helion" ], "dependencies": { - "async": "0.1.x", - "errs": "0.2.x", - "eventemitter2": "0.4.x", - "filed": "0.0.7", - "ip": "0.0.x", - "xml2js": "0.1.x", - "mime": "1.2.x", - "through": "~2.3", - "pkginfo": "0.2.x", - "qs": "0.6.x", - "request": "2.22.x", - "underscore": "1.4.x", - "url-join": "0.0.x", - "utile": "0.x.x" + "@google-cloud/storage": "^2.4.3", + "async": "^2.6.1", + "aws-sdk": "^2.382.0", + "errs": "^0.3.2", + "eventemitter2": "^5.0.1", + "fast-json-patch": "^2.1.0", + "filed-mimefix": "^0.1.3", + "ip": "^1.1.5", + "liboneandone": "^1.2.0", + "lodash": "^4.17.10", + "mime": "^2.4.1", + "qs": "^6.5.2", + "request": "^2.88.0", + "through2": "^3.0.1", + "url-join": "^4.0.0", + "xml2js": "^0.4.19" }, "devDependencies": { - "hock" : "0.2.x", - "mocha": "1.9.x", - "should": "1.2.x", - "mocha-lcov-reporter": "0.0.1", - "coveralls": "2.x.x" + "coveralls": "^3.0.2", + "hock": "^1.2.0", + "jshint": "^2.9.6", + "mocha": "^6.0.2", + "nyc": "^13.3.0", + "should": "^13.2.3" }, - "main": "./lib/pkgcloud", + "main": "./lib/pkgcloud", "scripts": { - "test": "make test", - "test-cov": "make test-cov", - "test-coveralls": "make test-coveralls" + "test": "MOCK=on nyc mocha -t 4000 test/*/*/*-test.js", + "coverage": "nyc report --reporter=text-lcov | coveralls", + "lint": "jshint --exclude-path .gitignore .", + "posttest": "npm run lint" }, - "engines": { "node": "0.8.x || 0.10.x" } + "engines": { + "node": ">= 8.0.0" + } } diff --git a/test/amazon/compute/client/groups-test.js b/test/amazon/compute/client/groups-test.js index dd51e2457..f4c775415 100644 --- a/test/amazon/compute/client/groups-test.js +++ b/test/amazon/compute/client/groups-test.js @@ -1,11 +1,12 @@ var helpers = require('../../../helpers'), + http = require('http'), should = require('should'), hock = require('hock'), mock = !!process.env.MOCK; -describe('pkgcloud/amazon/groups', function () { +describe.skip('pkgcloud/amazon/groups', function () { - var client, server; + var client, server, hockInstance; before(function (done) { client = helpers.createClient('amazon', 'compute'); @@ -14,24 +15,25 @@ describe('pkgcloud/amazon/groups', function () { return done(); } - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + hockInstance = hock.createHock(); + hockInstance.filteringRequestBody(helpers.authFilter); - server = hockClient.filteringRequestBody(helpers.authFilter); + // setup a filtering path for aws + hockInstance.filteringPathRegEx(/https:\/\/ec2\.us-west-2\.amazonaws\.com([?\w\-\.\_0-9\/]*)/g, '$1'); - done(); - }); + server = http.createServer(hockInstance.handler); + server.listen(12345, done); }); it('add SecurityGroup should succeed', function(done) { if (mock) { - server - .post('/?Action=CreateSecurityGroup', { + hockInstance + .post('/', { + Action: 'CreateSecurityGroup', GroupDescription: 'unit test', GroupName: 'unit test' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/add-group.xml'); } @@ -40,8 +42,9 @@ describe('pkgcloud/amazon/groups', function () { description: 'unit test' }, function(err, data) { should.not.exist(err); - data.should.equal(true); - server && server.done(); + data.GroupId.should.equal('sg-a6e01ccd'); + + hockInstance && hockInstance.done(); done(); }); }); @@ -49,17 +52,18 @@ describe('pkgcloud/amazon/groups', function () { it('destroy SecurityGroup should succeed', function (done) { if (mock) { - server - .post('/?Action=DeleteSecurityGroup', { + hockInstance + .post('/', { + Action: 'DeleteSecurityGroup', GroupName: 'unit test' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/destroy-group.xml'); } client.destroyGroup('unit test', function (err, data) { should.not.exist(err); data.should.equal(true); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -67,15 +71,15 @@ describe('pkgcloud/amazon/groups', function () { it('list SecurityGroups should succeed', function (done) { if (mock) { - server - .post('/?Action=DescribeSecurityGroups', {}) + hockInstance + .post('/', { Action: 'DescribeSecurityGroups'}, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/list-groups.xml'); } client.listGroups(function (err, data) { should.not.exist(err); - data.should.be.instanceOf(Array); - server && server.done(); + data.should.be.an.Array; + hockInstance && hockInstance.done(); done(); }); }); @@ -83,17 +87,18 @@ describe('pkgcloud/amazon/groups', function () { it('get SecurityGroup should succeed', function (done) { if (mock) { - server - .post('/?Action=DescribeSecurityGroups', { + hockInstance + .post('/', { + Action: 'DescribeSecurityGroups', 'GroupName.1': 'unit test' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/list-group.xml'); } client.getGroup('unit test', function (err, data) { should.not.exist(err); - // TODO - server && server.done(); + should.exist(data); + hockInstance && hockInstance.done(); done(); }); }); @@ -101,29 +106,33 @@ describe('pkgcloud/amazon/groups', function () { it('add Rules should succeed', function(done) { if (mock) { - server - .post('/?Action=AuthorizeSecurityGroupIngress', { + hockInstance + .post('/', { + Action: 'AuthorizeSecurityGroupIngress', GroupName: 'unit test', 'IpPermissions.1.FromPort': '0', 'IpPermissions.1.Groups.1.GroupName': 'unit test', 'IpPermissions.1.IpProtocol': 'tcp', 'IpPermissions.1.ToPort': '65535' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/add-rules.xml'); } client.addRules({ name: 'unit test', - rules: { - 'IpPermissions.1.IpProtocol': 'tcp', - 'IpPermissions.1.Groups.1.GroupName': 'unit test', - 'IpPermissions.1.FromPort': 0, - 'IpPermissions.1.ToPort': 65535 - } + rules: [ + { IpProtocol: 'tcp', + FromPort: 0, + UserIdGroupPairs: [ { + GroupName: 'unit test' + } ], + ToPort: 65535 + } + ] }, function(err, data) { should.not.exist(err); data.should.equal(true); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -131,29 +140,35 @@ describe('pkgcloud/amazon/groups', function () { it('delete Rules should succeed', function (done) { if (mock) { - server - .post('/?Action=RevokeSecurityGroupIngress', { + hockInstance + .post('/', { + Action: 'RevokeSecurityGroupIngress', GroupName: 'unit test', 'IpPermissions.1.FromPort': '0', 'IpPermissions.1.Groups.1.GroupName': 'unit test', 'IpPermissions.1.IpProtocol': 'tcp', 'IpPermissions.1.ToPort': '65535' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/destroy-rules.xml'); } client.delRules({ name: 'unit test', - rules: { - 'IpPermissions.1.IpProtocol': 'tcp', - 'IpPermissions.1.Groups.1.GroupName': 'unit test', - 'IpPermissions.1.FromPort': 0, - 'IpPermissions.1.ToPort': 65535 - } + rules: + [ + { IpProtocol: 'tcp', + FromPort: 0, + UserIdGroupPairs: [ + { + GroupName: 'unit test' + } + ], + ToPort: 65535 + } ] }, function (err, data) { should.not.exist(err); data.should.equal(true); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); diff --git a/test/amazon/compute/client/keys-test.js b/test/amazon/compute/client/keys-test.js index a22c5aff6..b809e8ffc 100644 --- a/test/amazon/compute/client/keys-test.js +++ b/test/amazon/compute/client/keys-test.js @@ -1,11 +1,12 @@ var helpers = require('../../../helpers'), + http = require('http'), should = require('should'), hock = require('hock'), mock = !!process.env.MOCK; -describe('pkgcloud/amazon/keys', function () { +describe.skip('pkgcloud/amazon/keys', function () { - var client, server; + var client, server, hockInstance; before(function (done) { client = helpers.createClient('amazon', 'compute'); @@ -14,24 +15,25 @@ describe('pkgcloud/amazon/keys', function () { return done(); } - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + hockInstance = hock.createHock(); + hockInstance.filteringRequestBody(helpers.authFilter); - server = hockClient.filteringRequestBody(helpers.authFilter); + // setup a filtering path for aws + hockInstance.filteringPathRegEx(/https:\/\/ec2\.us-west-2\.amazonaws\.com([?\w\-\.\_0-9\/]*)/g, '$1'); - done(); - }); + server = http.createServer(hockInstance.handler); + server.listen(12345, done); }); it('add KeyPair should succeed', function(done) { if (mock) { - server - .post('/?Action=ImportKeyPair', { + hockInstance + .post('/', { + Action: 'ImportKeyPair', KeyName: 'unittest', - PublicKeyMaterial: 'c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFDblhidGZGTTNrNExFb3hMaENGQ3lucnBibmtPYWphQ2xFUVVzdWRaazBTVWxVenl0Y2laRjArN25VaDg1VDZjZWMyNjdnazZ4ZTBZWEJqalhWc2xqcGtBVnIyc21ycFRwc2FJWk1qdXdPNlZHNFdYMG54NFhJaG1lTy9WcmdvYzY5Q0liTFJqNnkySlI1UTlaaHVqZVZJK1FZVkg3RnZ0OTZMZjh5SkN6YzRQdDZIVCswU2pudnlqSVZRTkcrWFVuS21GMWNVTGZiWTZOK2JwbUhJQWpxNW1mLzR4T2lKeHFUa0N0NmhoNGk4aE4vOHJmMzUwL0dDUE1GYTA0Umh2Si9hQVRWMmhxLzR4UXZVUXhzdzVsWnUzM3dZMENiQXI1Z3Z2bHZQd1grV0pFQjQ3RU9adEwrdm1nZVdieGJETGNFNUVaSnIxejJIV2ZSQkIweC9uQng=' - }) + PublicKeyMaterial: 'YzNOb0xYSnpZU0JCUVVGQlFqTk9lbUZETVhsak1rVkJRVUZCUkVGUlFVSkJRVUZDUVZGRGJsaGlkR1pHVFROck5FeEZiM2hNYUVOR1EzbHVjbkJpYm10UFlXcGhRMnhGVVZWemRXUmFhekJUVld4VmVubDBZMmxhUmpBck4yNVZhRGcxVkRaalpXTXlOamRuYXpaNFpUQlpXRUpxYWxoV2MyeHFjR3RCVm5JeWMyMXljRlJ3YzJGSldrMXFkWGRQTmxaSE5GZFlNRzU0TkZoSmFHMWxUeTlXY21kdll6WTVRMGxpVEZKcU5ua3lTbEkxVVRsYWFIVnFaVlpKSzFGWlZrZzNSblowT1RaTVpqaDVTa042WXpSUWREWklWQ3N3VTJwdWRubHFTVlpSVGtjcldGVnVTMjFHTVdOVlRHWmlXVFpPSzJKd2JVaEpRV3B4TlcxbUx6UjRUMmxLZUhGVWEwTjBObWhvTkdrNGFFNHZPSEptTXpVd0wwZERVRTFHWVRBMFVtaDJTaTloUVZSV01taHhMelI0VVhaVlVYaHpkelZzV25Vek0zZFpNRU5pUVhJMVozWjJiSFpRZDFnclYwcEZRalEzUlU5YWRFd3JkbTFuWlZkaWVHSkVUR05GTlVWYVNuSXhlakpJVjJaU1FrSXdlQzl1UW5nPQ==' + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/add-key.xml'); } @@ -41,7 +43,7 @@ describe('pkgcloud/amazon/keys', function () { }, function(err, data) { should.not.exist(err); data.should.equal(true); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -49,17 +51,18 @@ describe('pkgcloud/amazon/keys', function () { it('destroy KeyPair should succeed', function (done) { if (mock) { - server - .post('/?Action=DeleteKeyPair', { + hockInstance + .post('/', { + Action: 'DeleteKeyPair', KeyName: 'unittest' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/destroy-key.xml'); } client.destroyKey('unittest', function (err, data) { should.not.exist(err); data.should.equal(true); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -67,15 +70,15 @@ describe('pkgcloud/amazon/keys', function () { it('list KeyPairs should succeed', function (done) { if (mock) { - server - .post('/?Action=DescribeKeyPairs', {}) + hockInstance + .post('/', { Action: 'DescribeKeyPairs' }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/list-keys.xml'); } client.listKeys(function (err, data) { should.not.exist(err); - data.should.be.instanceOf(Array); - server && server.done(); + data.should.be.an.Array; + hockInstance && hockInstance.done(); done(); }); }); @@ -83,17 +86,18 @@ describe('pkgcloud/amazon/keys', function () { it('get KeyPair should succeed', function (done) { if (mock) { - server - .post('/?Action=DescribeKeyPairs', { + hockInstance + .post('/', { + Action: 'DescribeKeyPairs', 'KeyName.1': 'unittest' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../../fixtures/amazon/list-keys.xml'); } client.getKey('unittest', function (err, data) { should.not.exist(err); - // TODO - server && server.done(); + should.exist(data); + hockInstance && hockInstance.done(); done(); }); }); diff --git a/test/azure/compute/client/test-createImage.js b/test/azure/compute/client/test-createImage.js index 40166126f..a3f55422f 100644 --- a/test/azure/compute/client/test-createImage.js +++ b/test/azure/compute/client/test-createImage.js @@ -1,16 +1,12 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); -var fs = require('fs'); -var async = require('async'); - var client = helpers.createClient('azure', 'compute'); var options = { - name: "pkgcloud1", - server: "pkgcloud1" + name: 'pkgcloud1', + server: 'pkgcloud1' }; client.createImage(options, function (err, result) { diff --git a/test/azure/compute/client/test-createServer.js b/test/azure/compute/client/test-createServer.js index f9e13ff80..f21da9dc3 100644 --- a/test/azure/compute/client/test-createServer.js +++ b/test/azure/compute/client/test-createServer.js @@ -13,6 +13,8 @@ var fs = require('fs'), azureNock = require('../../../helpers/azureNock'), nock = require('nock'); +var testContext = {}; + var options = { name: 'create-test-ids2', flavor: 'ExtraSmall', @@ -25,8 +27,8 @@ var options = { }, ports: [ { - name: "foo", - protocol: "tcp", + name: 'foo', + protocol: 'tcp', port: 12333, localPort: 12333 } @@ -40,13 +42,13 @@ function testCreateServer(client) { var name = 'azure', test = {}; - test["The pkgcloud " + name + " compute client"] = { - "the createServer() method": { - "with image and flavor ids": { + test['The pkgcloud ' + name + ' compute client'] = { + 'the createServer() method': { + 'with image and flavor ids': { topic: function () { client.createServer(options, this.callback); }, - "should return a valid server": function (err, server) { + 'should return a valid server': function (err, server) { testContext.server = server; assert.isNull(err); @@ -63,17 +65,17 @@ function testCreateServer(client) { return test; } -function testSetWait(client) { +function testSetWait() { var name = 'azure', test = {}; - test["The pkgcloud " + name + " compute client"] = { - "the setWait() method": { - "with setWait({ status: testContext.server.STATUS.running },": { + test['The pkgcloud ' + name + ' compute client'] = { + 'the setWait() method': { + 'with setWait({ status: testContext.server.STATUS.running },': { topic: function () { testContext.server.setWait({ status: testContext.server.STATUS.running }, 1000, this.callback); }, - "should return a running server": function (err, server) { + 'should return a running server': function (err, server) { testContext.server = server; assert.isNull(err); if (err === null) { @@ -92,8 +94,6 @@ function testSetWait(client) { var client = helpers.createClient('azure', 'compute'); -var testContext = {}; - if (process.env.MOCK) { azureNock.serverTest(nock, helpers); } @@ -102,7 +102,7 @@ vows .describe('pkgcloud/azure/compute/createServer') .addBatch(testCreateServer(client)) .addBatch(testSetWait(client)) - ["export"](module); + ['export'](module); diff --git a/test/azure/compute/client/test-destroyImage.js b/test/azure/compute/client/test-destroyImage.js index 8cdb4ac0c..6a578e6d7 100644 --- a/test/azure/compute/client/test-destroyImage.js +++ b/test/azure/compute/client/test-destroyImage.js @@ -1,14 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); -var fs = require('fs'); -var async = require('async'); - var client = helpers.createClient('azure', 'compute'); - client.destroyImage('pkgcloud1', function (err, result) { if (err) { console.dir(err); diff --git a/test/azure/compute/client/test-destroyServer.js b/test/azure/compute/client/test-destroyServer.js index 6063a56e1..39b76ea7a 100644 --- a/test/azure/compute/client/test-destroyServer.js +++ b/test/azure/compute/client/test-destroyServer.js @@ -1,8 +1,6 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); -var pkgcloud = require('../../../../lib/pkgcloud'); var client = helpers.createClient('azure', 'compute'); @@ -10,7 +8,7 @@ client.destroyServer('test-reboot', function (err, result) { if (err) { console.log(err); } else { - console.log('ok'); + console.log(result); } }); diff --git a/test/azure/compute/client/test-getImages.js b/test/azure/compute/client/test-getImages.js index 25e899435..e66894105 100644 --- a/test/azure/compute/client/test-getImages.js +++ b/test/azure/compute/client/test-getImages.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); var client = helpers.createClient('azure', 'compute'); -var options = {}; - /* client.getImages(options, function (err, result) { if (err) { diff --git a/test/azure/compute/client/test-getServer.js b/test/azure/compute/client/test-getServer.js index d232b2a87..7ee322074 100644 --- a/test/azure/compute/client/test-getServer.js +++ b/test/azure/compute/client/test-getServer.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); var client = helpers.createClient('azure', 'compute'); -var options = {}; - client.getServer('pkgcloud2', function (err, result) { if (err) { console.log(err); diff --git a/test/azure/compute/client/test-getServers.js b/test/azure/compute/client/test-getServers.js index 7193abbc1..e6025f68e 100644 --- a/test/azure/compute/client/test-getServers.js +++ b/test/azure/compute/client/test-getServers.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); var client = helpers.createClient('azure', 'compute'); -var options = {}; - client.getServers(function (err, result) { if (err) { console.log(err); diff --git a/test/azure/compute/client/test-rebootServer.js b/test/azure/compute/client/test-rebootServer.js index 97b57b9b4..360b2debf 100644 --- a/test/azure/compute/client/test-rebootServer.js +++ b/test/azure/compute/client/test-rebootServer.js @@ -5,14 +5,14 @@ * */ -var fs = require('fs'), - path = require('path'), - vows = require('vows'), +var vows = require('vows'), assert = require('../../../helpers/assert'), helpers = require('../../../helpers'), azureNock = require('../../../helpers/azureNock'), nock = require('nock'); +var testContext = {}; + var options = { name: 'test-reboot', flavor: 'ExtraSmall', @@ -23,9 +23,9 @@ function testCreateServer(client) { var name = 'azure', test = {}; - test["The pkgcloud " + name + " compute client"] = { - "the createServer() method": { - "with image and flavor ids": { + test['The pkgcloud ' + name + ' compute client'] = { + 'the createServer() method': { + 'with image and flavor ids': { topic: function () { client.createServer({ name: options.name, @@ -33,7 +33,7 @@ function testCreateServer(client) { flavor: options.flavor }, this.callback); }, - "should return a valid server": function (err, server) { + 'should return a valid server': function (err, server) { testContext.server = server; assert.isNull(err); @@ -50,17 +50,17 @@ function testCreateServer(client) { return test; } -function testSetWait(client) { +function testSetWait() { var name = 'azure', test = {}; - test["The pkgcloud " + name + " compute client"] = { - "the setWait() method": { - "with setWait({ status: testContext.server.STATUS.running },": { + test['The pkgcloud ' + name + ' compute client'] = { + 'the setWait() method': { + 'with setWait({ status: testContext.server.STATUS.running },': { topic: function () { testContext.server.setWait({ status: testContext.server.STATUS.running }, 1000, this.callback); }, - "should return a running server": function (err, server) { + 'should return a running server': function (err, server) { testContext.server = server; assert.isNull(err); if (err === null) { @@ -81,13 +81,13 @@ function testRebootServer(client) { var name = 'azure', test = {}; - test["The pkgcloud " + name + " compute client"] = { - "the rebootServer() method": { - "rebooting server": { + test['The pkgcloud ' + name + ' compute client'] = { + 'the rebootServer() method': { + 'rebooting server': { topic: function () { client.rebootServer(testContext.server, this.callback); }, - "should return a valid server": function (err, res) { + 'should return a valid server': function (err, res) { assert.isNull(err); if (err === null) { @@ -105,8 +105,6 @@ function testRebootServer(client) { var client = helpers.createClient('azure', 'compute'); -var testContext = {}; - if (process.env.MOCK) { azureNock.serverTest(nock, helpers); } @@ -117,6 +115,6 @@ vows .addBatch(testSetWait(client)) .addBatch(testRebootServer(client)) .addBatch(testSetWait(client)) - ["export"](module); + ['export'](module); diff --git a/test/azure/compute/client/test-stopServer.js b/test/azure/compute/client/test-stopServer.js index 688f577b7..4d985eb6a 100644 --- a/test/azure/compute/client/test-stopServer.js +++ b/test/azure/compute/client/test-stopServer.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); var client = helpers.createClient('azure', 'compute'); -var options = {}; - client.stopServer('pkgcloud1', function (err, result) { if (err) { console.log(err); diff --git a/test/azure/compute/templates/test-linuxConfigSet.js b/test/azure/compute/templates/test-linuxConfigSet.js index 217c4af4a..266430e0a 100644 --- a/test/azure/compute/templates/test-linuxConfigSet.js +++ b/test/azure/compute/templates/test-linuxConfigSet.js @@ -1,11 +1,7 @@ //TODO: Make this a vows test -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; -var helpers = require('../../../helpers'); var templates = require('../../../../lib/pkgcloud/azure/compute/templates/templates'); -var _ = require('underscore'); - -var fs = require('fs'); +var _ = require('lodash'); var params = { HOSTNAME: 'pkgcloud1', @@ -19,6 +15,7 @@ templates.load('linuxConfigSet.xml', function (err, template) { console.dir(err); } else { - console.log(_.template(template, params)); + var compiled = _.template(template); + console.log(compiled(params)); } }); diff --git a/test/azure/databases/databases-test.js b/test/azure/databases/databases-test.js index 2ceacea8f..3aa768429 100644 --- a/test/azure/databases/databases-test.js +++ b/test/azure/databases/databases-test.js @@ -6,6 +6,7 @@ */ var helpers = require('../../helpers'), + http = require('http'), should = require('should'), urlJoin = require('url-join'), hock = require('hock'), @@ -13,7 +14,7 @@ var helpers = require('../../helpers'), describe('pkgcloud/azure/databases', function () { - var client, server, testContext = {}; + var client, server, testContext = {}, hockInstance; before(function (done) { client = helpers.createClient('azure', 'database'); @@ -28,24 +29,21 @@ describe('pkgcloud/azure/databases', function () { return urlJoin('http://localhost:12345/', (typeof options === 'string' ? options - : options.path)); + : options.path || '')); }; - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + hockInstance = hock.createHock(); + hockInstance.filteringRequestBodyRegEx(/.*/, '*'); - server = hockClient.filteringRequestBodyRegEx(/.*/, '*'); - - done(); - }); + server = http.createServer(hockInstance.handler); + server.listen(12345, done); }); describe('the pkgcloud azure db client', function() { it('the create() method with correct options should respond correctly', function(done) { if (mock) { - server + hockInstance .post('/Tables', '*') .replyWithFile(201, __dirname + '/../../fixtures/azure/database/createTableResponse.xml'); } @@ -61,7 +59,7 @@ describe('pkgcloud/azure/databases', function () { database.password.should.equal(''); testContext.databaseId = database.id; - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -85,7 +83,7 @@ describe('pkgcloud/azure/databases', function () { it('the list() method with correct options should respond correctly', function (done) { if (mock) { - server + hockInstance .get('/Tables') .replyWithFile(201, __dirname + '/../../fixtures/azure/database/listTables.xml'); } @@ -93,10 +91,10 @@ describe('pkgcloud/azure/databases', function () { client.list(function (err, databases) { should.not.exist(err); should.exist(databases); - databases.should.be.instanceOf(Array); + databases.should.be.an.Array; databases.should.have.length(1); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -104,15 +102,15 @@ describe('pkgcloud/azure/databases', function () { it('the remove() method with correct options should respond correctly', function (done) { if (mock) { - server - .delete("/Tables%28%27testDatabase%27%29") + hockInstance + .delete('/Tables%28%27testDatabase%27%29') .reply(204, '', {'content-length': '0'}); } client.remove(testContext.databaseId, function (err, result) { should.not.exist(err); result.should.equal(true); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); diff --git a/test/azure/databases/test-database.js b/test/azure/databases/test-database.js index 83613cd42..63ca325a9 100644 --- a/test/azure/databases/test-database.js +++ b/test/azure/databases/test-database.js @@ -4,7 +4,7 @@ var helpers = require('../../helpers'); var options = { name: 'test10' -} +}; var client = helpers.createClient('azure', 'database'); diff --git a/test/azure/databases/test-deleteTable.js b/test/azure/databases/test-deleteTable.js index c5fab9c8e..1788a4cc2 100644 --- a/test/azure/databases/test-deleteTable.js +++ b/test/azure/databases/test-deleteTable.js @@ -1,6 +1,5 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var client = helpers.createClient('azure', 'database'); diff --git a/test/azure/databases/test-getTables.js b/test/azure/databases/test-getTables.js index b799b5bea..dfc6547a4 100644 --- a/test/azure/databases/test-getTables.js +++ b/test/azure/databases/test-getTables.js @@ -1,11 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var client = helpers.createClient('azure', 'database'); - client.list(function (err, res) { if (err) { console.dir(err); diff --git a/test/azure/storage/test-download.js b/test/azure/storage/test-download.js index 18c1d478c..ea7635456 100644 --- a/test/azure/storage/test-download.js +++ b/test/azure/storage/test-download.js @@ -1,9 +1,6 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); -var fs = require('fs'); -var async = require('async'); var client = helpers.createClient('azure', 'storage'); @@ -12,9 +9,6 @@ var options = { remote: 'test-file.txt' }; - - - var stream = client.download(options, function (err, res) { if (err) { console.dir(err); diff --git a/test/azure/storage/test-files.js b/test/azure/storage/test-files.js index 96d47c4be..43c7a663a 100644 --- a/test/azure/storage/test-files.js +++ b/test/azure/storage/test-files.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); -var async = require('async'); var client = helpers.createClient('azure', 'storage'); - client.getFile('pkgcloud-test-container', 'test-file.txt', function (err, res) { if (err) { console.dir(err); diff --git a/test/azure/storage/test-removeFile.js b/test/azure/storage/test-removeFile.js index 244c5fc35..9489a5eb8 100644 --- a/test/azure/storage/test-removeFile.js +++ b/test/azure/storage/test-removeFile.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); -var async = require('async'); var client = helpers.createClient('azure', 'storage'); - client.removeFile('pkgcloud-test-container', 'test-file.txt', function (err, res) { if (err) { console.dir(err); diff --git a/test/azure/storage/test-upload.js b/test/azure/storage/test-upload.js index 787dfeaa7..c7183c7db 100644 --- a/test/azure/storage/test-upload.js +++ b/test/azure/storage/test-upload.js @@ -1,9 +1,7 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var fs = require('fs'); -var async = require('async'); var client = helpers.createClient('azure', 'storage'); @@ -12,7 +10,6 @@ var options = { remote: 'test-file.txt' }; - var stream = client.upload(options, function (err, res) { if (err) { console.dir(err); @@ -24,7 +21,7 @@ var stream = client.upload(options, function (err, res) { var file = fs.createReadStream(helpers.fixturePath('fillerama.txt')); file.pipe(stream); -options.remote = 'bigfile.raw' +options.remote = 'bigfile.raw'; var stream2 = client.upload(options, function (err, res) { if (err) { console.dir(err); diff --git a/test/azure/utils/test-cert.js b/test/azure/utils/test-cert.js index c6ea9f8c8..e3d93f04a 100644 --- a/test/azure/utils/test-cert.js +++ b/test/azure/utils/test-cert.js @@ -1,12 +1,6 @@ //TODO: Make this a vows test -var fs = require('fs'); var azureCert = require('../../../lib/pkgcloud/azure/utils/cert.js'); - -// load azure config file (you need to set this up with your azure account info) -var config = JSON.parse(fs.readFileSync("../configs/azure.json",'utf8')); - - azureCert.getAzureCert('../../fixtures/azure/cert/management/management.pem', function (err, result) { if (err) { console.dir(err); diff --git a/test/azure/utils/test-createHostedService.js b/test/azure/utils/test-createHostedService.js index 20fa84f05..db66bce3f 100644 --- a/test/azure/utils/test-createHostedService.js +++ b/test/azure/utils/test-createHostedService.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var azureApi = require('../../../lib/pkgcloud/azure/utils/azureApi'); var client = helpers.createClient('azure', 'compute'); -var options = {}; - azureApi.createHostedService(client, 'pkgcloud4', function (err, result) { if (err) { console.log(err); diff --git a/test/azure/utils/test-createServer.js b/test/azure/utils/test-createServer.js index dc7ce09ec..a998bdf41 100644 --- a/test/azure/utils/test-createServer.js +++ b/test/azure/utils/test-createServer.js @@ -1,6 +1,5 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var azureApi = require('../../../lib/pkgcloud/azure/utils/azureApi'); var client = helpers.createClient('azure', 'compute'); diff --git a/test/azure/utils/test-createWindowsServer.js b/test/azure/utils/test-createWindowsServer.js index 038a2adcb..59f119a6a 100644 --- a/test/azure/utils/test-createWindowsServer.js +++ b/test/azure/utils/test-createWindowsServer.js @@ -1,6 +1,5 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var azureApi = require('../../../lib/pkgcloud/azure/utils/azureApi'); var client = helpers.createClient('azure', 'compute'); diff --git a/test/azure/utils/test-getOSImage.js b/test/azure/utils/test-getOSImage.js index 16fec27f2..ab37a02a9 100644 --- a/test/azure/utils/test-getOSImage.js +++ b/test/azure/utils/test-getOSImage.js @@ -1,12 +1,9 @@ //TODO: Make this a vows test -var Client = new require('../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../helpers'); var azureApi = require('../../../lib/pkgcloud/azure/utils/azureApi'); var client = helpers.createClient('azure', 'compute'); -var options = {}; - azureApi.getOSImage(client, 'OpenLogic__OpenLogic-CentOS-62-20120531-en-us-30GB.vhd', function (err, result) { if (err) { console.log(err); diff --git a/test/common/base/client-test.js b/test/common/base/client-test.js index fa142793e..0de1f3b5d 100644 --- a/test/common/base/client-test.js +++ b/test/common/base/client-test.js @@ -1,11 +1,12 @@ -/* + /* * client-test.js: Tests for pkgcloud base client * -* (C) 2012 Nodejitsu Inc. +* (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var should = require('should'), + pkgcloud = require('../../../lib/pkgcloud'), Client = new require('../../../lib/pkgcloud/core/base/client').Client; describe('pkgcloud/core/base/client', function () { @@ -13,7 +14,7 @@ describe('pkgcloud/core/base/client', function () { it('with a wrong request with a cb', function(done) { var cli = new Client(); cli._getUrl = function () { - return "badurl"; + return 'badurl'; }; cli.failCodes = {}; cli._request({ path: '/' }, function(err) { @@ -26,28 +27,28 @@ describe('pkgcloud/core/base/client', function () { var cli = new Client(); cli._getUrl = function () { - return "badurl"; + return 'badurl'; }; cli.failCodes = {}; var stream = cli._request({ path: '/' }); - stream.on('error', function () { - return handleResponse(true); - }); - stream.on('end', function () { - return handleResponse(false); - }); function handleResponse(err) { should.exist(err); done(); } + stream.on('error', function () { + return handleResponse(true); + }); + stream.on('end', function () { + return handleResponse(false); + }); }); it('the before filters throwing an error with a callback should return the error on the cb', function(done) { var cli = new Client(); cli._getUrl = function () { - return "badurl"; + return 'badurl'; }; cli.failCodes = {}; cli.before = [function () { @@ -59,12 +60,24 @@ describe('pkgcloud/core/base/client', function () { }); }); + it('custom user agents should work', function () { + var cli = new Client(); + cli.setCustomUserAgent('my-app/1.2.3'); + cli.getUserAgent().should.equal('my-app/1.2.3 nodejs-pkgcloud/' + pkgcloud.version); + }); + it('the before filters throwing an error without a callback should return the error on the EE', function(done) { var cli = new Client(); cli._getUrl = function () { - return "badurl"; + return 'badurl'; }; + + function handleResponse(err) { + should.exist(err); + done(); + } + cli.failCodes = {}; var stream = cli._request({ path: '/' }); stream.on('error', function () { @@ -73,11 +86,6 @@ describe('pkgcloud/core/base/client', function () { stream.on('end', function () { handleResponse(false); }); - - function handleResponse(err) { - should.exist(err); - done(); - } }); }); }); diff --git a/test/common/base/pkgcloud-test.js b/test/common/base/pkgcloud-test.js index 33e55191a..b38e476ea 100644 --- a/test/common/base/pkgcloud-test.js +++ b/test/common/base/pkgcloud-test.js @@ -1,10 +1,9 @@ -var should = require('should'), - pkgcloud = require('../../../lib/pkgcloud'); +var pkgcloud = require('../../../lib/pkgcloud'); describe('pkgcloud/pkgcloud', function() { it('should throw an Error when the provider is not supported', function() { (function () { - pkgcloud.storage.createClient({"provider":"in-memory"}); - }).should.throw(new Error("in-memory is not a supported provider")); + pkgcloud.storage.createClient({ provider: 'in-memory' }); + }).should.throw(new Error('in-memory is not a supported provider')); }); }); diff --git a/test/common/base/useragent-test.js b/test/common/base/useragent-test.js new file mode 100644 index 000000000..b12fa243b --- /dev/null +++ b/test/common/base/useragent-test.js @@ -0,0 +1,30 @@ +/* + * useragent-test.js: Tests for pkgcloud base client useragent + * + * (C) 2015 Ken Perkins, Rackspace Inc. + * + */ + +var pkgcloud = require('../../../lib/pkgcloud'), + Client = new require('../../../lib/pkgcloud/core/base/client').Client; + +require('should'); + +describe('pkgcloud/core/base/client/useragent', function () { + describe('getUserAgent tests', function () { + it('should return the default useragent', function () { + var cli = new Client(); + + cli.getUserAgent().should.equal('nodejs-pkgcloud/' + pkgcloud.version); + }); + }); + + describe('setCustomUserAgent tests', function () { + it('should allow prefixing a custom useragent', function () { + var cli = new Client(); + + cli.setCustomUserAgent('my-app/1.2.3'); + cli.getUserAgent().should.equal('my-app/1.2.3 nodejs-pkgcloud/' + pkgcloud.version); + }); + }); +}); diff --git a/test/common/compute/base-test.js b/test/common/compute/base-test.js index b50bfd976..aae0a4de6 100644 --- a/test/common/compute/base-test.js +++ b/test/common/compute/base-test.js @@ -1,19 +1,17 @@ /* * base-test.js: Test that should be common to all providers. * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var fs = require('fs'), - path = require('path'), - should = require('should'), - qs = require('qs'), - utile = require('utile'), +var should = require('should'), + util = require('util'), async = require('async'), + http = require('http'), helpers = require('../../helpers'), hock = require('hock'), - _ = require('underscore'), + _ = require('lodash'), providers = require('../../configs/providers.json'), versions = require('../../fixtures/versions.json'), Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor, @@ -25,48 +23,55 @@ var fs = require('fs'), var azureOptions = require('../../fixtures/azure/azure-options.json'); +// Declaring variables for helper functions defined later +var setupVersionMock, setupFlavorMock, setupImagesMock, setupServerMock, setupDestroyMock; + azureApi._updateMinimumPollInterval(mock ? 10 : azureApi.MINIMUM_POLL_INTERVAL); -providers.forEach(function(provider) { +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].compute; +}).forEach(function (provider) { describe('pkgcloud/common/compute/base [' + provider + ']', function () { var client = helpers.createClient(provider, 'compute'), - context = {}, - authServer, server; + context = {}, + authServer, server, + authHockInstance, + hockInstance; - before(function(done) { + before(function (done) { if (!mock) { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + // setup a filtering path for aws + hockInstance.filteringPathRegEx(/https:\/\/ec2\.us-west-2\.amazonaws\.com([?\w\-\.\_0-9\/]*)/g, '$1'); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ - function(next) { - hock.createHock({ - port: 12345, - throwOnUnmatched: false - }, function(err, hockClient) { - server = hockClient; - next(); - }); + function (next) { + server.listen(12345, next); }, function (next) { - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - next(); - }); + authServer.listen(12346, next); } - ], done) + ], done); }); it('the getVersion() method with no arguments should return the version', function (done) { if (mock) { var errors = setupVersionMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } - + if (errors) { client.getVersion(function (err) { err.should.be.an.instanceof(Error); @@ -79,18 +84,18 @@ providers.forEach(function(provider) { should.exist(version); version.should.equal(versions[provider]); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); } }); - it('the getFlavors() method should return a list of flavors', function(done) { + it('the getFlavors() method should return a list of flavors', function (done) { if (mock) { setupFlavorMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -104,8 +109,8 @@ providers.forEach(function(provider) { context.flavors = flavors; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -114,8 +119,8 @@ providers.forEach(function(provider) { it('the getImages() method should return a list of images', function (done) { if (mock) { setupImagesMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -129,8 +134,8 @@ providers.forEach(function(provider) { context.images = images; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -141,12 +146,12 @@ providers.forEach(function(provider) { if (mock) { setupServerMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } - client.createServer(utile.mixin({ + client.createServer(_.extend({ name: 'create-test-setWait', image: context.images[0].id, flavor: context.flavors[0].id @@ -154,7 +159,7 @@ providers.forEach(function(provider) { should.not.exist(err); should.exist(srv1); - srv1.setWait({ status: srv1.STATUS.running }, 100 * m, function (err, srv2) { + srv1.setWait({ status: srv1.STATUS.running }, 100 * m, 1000, function (err, srv2) { should.not.exist(err); should.exist(srv2); srv2.should.be.instanceOf(Server); @@ -162,25 +167,25 @@ providers.forEach(function(provider) { srv2.status.should.equal(srv2.STATUS.running); context.server = srv2; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); }); - it('the setWait() method waiting for a server to be operational should return a running server', function (done) { + it('the destroyServer() method should destroy an existing server', function (done) { // TODO enable destroy tests for all providers - if (provider === 'joyent' || provider === 'amazon' || provider === 'azure') { + if (provider === 'amazon' || provider === 'azure') { done(); return; } if (mock) { setupDestroyMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -188,14 +193,14 @@ providers.forEach(function(provider) { should.not.exist(err); should.exist(result); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); - after(function(done) { + after(function (done) { if (!mock) { return done(); } @@ -207,12 +212,12 @@ providers.forEach(function(provider) { function (next) { server.close(next); } - ], done) + ], done); }); }); }); -function setupVersionMock(client, provider, servers) { +setupVersionMock = function (client, provider, servers) { if (provider === 'digitalocean') { return true; } @@ -229,7 +234,7 @@ function setupVersionMock(client, provider, servers) { .reply(200, helpers.getRackspaceAuthResponse()); servers.server - .get('/v2/', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .get('/v2/', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/rackspace/versions.json'); } else if (provider === 'openstack') { @@ -241,9 +246,9 @@ function setupVersionMock(client, provider, servers) { password: 'MOCK-PASSWORD' } } - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .reply(200, helpers._getOpenstackStandardResponse('../fixtures/openstack/initialToken.json')) - .get('/v2.0/tenants', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .get('/v2.0/tenants', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') .post('/v2.0/tokens', { auth: { @@ -253,174 +258,177 @@ function setupVersionMock(client, provider, servers) { }, tenantId: '72e90ecb69c44d0296072ea39e537041' } - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .reply(200, helpers.getOpenstackAuthResponse()); servers.server - .get('/v2/', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .get('/v2/', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/rackspace/versions.json'); } - else if (provider === 'joyent') { + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }, { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .reply(200, helpers._getOpenstackStandardResponse('../fixtures/hp/initialToken.json')) + .get('/v2.0/tenants', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }, { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .reply(200, helpers.gethpAuthResponse()); + servers.server - .get('/' + client.account + '/datacenters', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .reply(200, '', { 'x-api-version': '6.5.0' }); + .get('/v2/', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/versions.json'); } -} +}; -function setupFlavorMock(client, provider, servers) { +setupFlavorMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.server - .get('/v2/123456/flavors/detail', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .get('/v2/123456/flavors/detail', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/rackspace/flavors.json'); } else if (provider === 'openstack') { servers.server .get('/v2/72e90ecb69c44d0296072ea39e537041/flavors/detail', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/openstack/flavors.json'); } - else if (provider === 'joyent') { - servers.server - .get('/' + client.account + '/packages', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .replyWithFile(200, __dirname + '/../../fixtures/joyent/flavors.json'); - } else if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/sizes?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/sizes') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/flavors.json'); } -} + else if (provider === 'hp') { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/flavors/detail', + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/flavors.json'); + } + else if (provider === 'oneandone') { + servers.server + .get('/servers/fixed_instance_sizes') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/listFlavors.json'); + } +}; -function setupImagesMock(client, provider, servers) { +setupImagesMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.server .get('/v2/123456/images/detail', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/rackspace/images.json'); } else if (provider === 'openstack') { servers.server .get('/v2/72e90ecb69c44d0296072ea39e537041/images/detail', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/openstack/images.json'); } - else if (provider === 'joyent') { - servers.server - .get('/' + client.account + '/datasets', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .replyWithFile(200, __dirname + '/../../fixtures/joyent/images.json'); - } else if (provider === 'amazon') { servers.server .filteringRequestBody(helpers.authFilter) - .post('/?Action=DescribeImages', { 'Owner.0': 'self' }, - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .post('/', { Action: 'DescribeImages', 'Owner.1': 'self' }, + { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/images.xml'); } else if (provider === 'azure') { servers.server .get('/azure-account-subscription-id/services/images', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/images.xml'); } else if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/images?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/images?per_page=200&page=1') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/images.json'); } -} + else if (provider === 'hp') { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/images/detail', + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/images.json'); + } + else if (provider === 'oneandone') { + servers.server + .get('/server_appliances') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/listImages.json'); + } +}; -function setupServerMock(client, provider, servers) { +setupServerMock = function (client, provider, servers) { if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/droplets/new?' + qs.stringify({ + .post('/v2/droplets', { name: 'create-test-setWait', - region_id: 1, - size_id: 66, - image_id: 1601, - client_id: account.clientId, - api_key: account.apiKey - })) + region: 'nyc3', + size: '512mb', + image: 119192817 + }) .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/create-server.json') - .get('/droplets/354526?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/droplets/3164444') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/not-active.json') - .get('/droplets/354526?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/droplets/3164444') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/active.json'); } else if (provider === 'rackspace') { servers.server .post('/v2/123456/servers', { - server: { - name: 'create-test-setWait', - flavorRef: '2', - imageRef: '9922a7c7-5a42-4a56-bc6a-93f857ae2346' - } - }, - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + server: { + name: 'create-test-setWait', + flavorRef: '2', + imageRef: '9922a7c7-5a42-4a56-bc6a-93f857ae2346' + } + }, + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(202, __dirname + '/../../fixtures/rackspace/setWaitResp1.json') .get('/v2/123456/servers/a0a5f183-b94e-4a41-a854-64cff53375bf', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/rackspace/a0a5f183-b94e-4a41-a854-64cff53375bf.json'); } else if (provider === 'openstack') { servers.server .post('/v2/72e90ecb69c44d0296072ea39e537041/servers', { - server: { - name: 'create-test-setWait', - flavorRef: '1', - imageRef: '506d077e-66bf-44ff-907a-588c5c79fa66' - } - }, - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + server: { + name: 'create-test-setWait', + flavorRef: '1', + imageRef: '506d077e-66bf-44ff-907a-588c5c79fa66' + } + }, + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(202, __dirname + '/../../fixtures/openstack/creatingServer.json') .get('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverCreated.json'); } - else if (provider === 'joyent') { - servers.server - .post('/' + client.account + '/machines', - { name: 'create-test-setWait', - 'package': 'Small 1GB', - dataset: 'sdc:sdc:nodejitsu:1.0.0' - }, - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .replyWithFile(200, __dirname + '/../../fixtures/joyent/setWait.json') - .get('/' + client.account + - '/machines/534aa63a-104f-4d6d-a3b1-c0d341a20a53', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .replyWithFile(200, __dirname + '/../../fixtures/joyent/setWaitResp1.json'); - } else if (provider === 'amazon') { servers.server .filteringRequestBody(helpers.authFilter) - .post('/?Action=RunInstances', { + .post('/', { + 'Action': 'RunInstances', 'ImageId': 'ami-85db1cec', 'InstanceType': 'm1.small', 'MaxCount': '1', 'MinCount': '1', 'UserData': 'eyJuYW1lIjoiY3JlYXRlLXRlc3Qtc2V0V2FpdCJ9' - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/run-instances.xml') - .post('/?Action=DescribeInstances', { + .post('/', { + 'Action': 'DescribeInstances', 'Filter.1.Name': 'instance-state-code', 'Filter.1.Value.1': '0', 'Filter.1.Value.2': '16', @@ -428,14 +436,16 @@ function setupServerMock(client, provider, servers) { 'Filter.1.Value.4': '64', 'Filter.1.Value.5': '80', 'InstanceId.1': 'i-1d48637b' - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/pending-server.xml') - .post('/?Action=DescribeInstanceAttribute', { + .post('/', { + 'Action': 'DescribeInstanceAttribute', 'Attribute': 'userData', 'InstanceId': 'i-1d48637b' - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server-attr.xml') - .post('/?Action=DescribeInstances', { + .post('/', { + 'Action': 'DescribeInstances', 'Filter.1.Name': 'instance-state-code', 'Filter.1.Value.1': '0', 'Filter.1.Value.2': '16', @@ -443,75 +453,107 @@ function setupServerMock(client, provider, servers) { 'Filter.1.Value.4': '64', 'Filter.1.Value.5': '80', 'InstanceId.1': 'i-1d48637b' - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server.xml') - .post('/?Action=DescribeInstanceAttribute', { + .post('/', { + 'Action': 'DescribeInstanceAttribute', 'Attribute': 'userData', 'InstanceId': 'i-1d48637b' - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server-attr.xml'); } else if (provider === 'azure') { servers.server .get('/azure-account-subscription-id/services/hostedservices/create-test-setWait?embed-detail=true', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(404, __dirname + '/../../fixtures/azure/hosted-service-404.xml') - .post('/azure-account-subscription-id/services/hostedservices', helpers.loadFixture('azure/create-hosted-service.xml'), {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .post('/azure-account-subscription-id/services/hostedservices', helpers.loadFixture('azure/create-hosted-service.xml'), { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .reply(201, '', { location: 'https://management.core.windows.net/subscriptions/azure-account-subscription-id/compute/create-test-setWait', - 'x-ms-request-id': 'b67cc525ecc546618fd6fb3e57d724f5'}) + 'x-ms-request-id': 'b67cc525ecc546618fd6fb3e57d724f5' + }) .get('/azure-account-subscription-id/operations/b67cc525ecc546618fd6fb3e57d724f5', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/operation-succeeded.xml') - .get('/azure-account-subscription-id/services/images/CANONICAL__Canonical-Ubuntu-12-04-amd64-server-20120528.1.3-en-us-30GB.vhd', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .get('/azure-account-subscription-id/services/images/CANONICAL__Canonical-Ubuntu-12-04-amd64-server-20120528.1.3-en-us-30GB.vhd', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/image-1.xml') - .post('/azure-account-subscription-id/services/hostedservices/create-test-setWait/deployments', helpers.loadFixture('azure/create-deployment.xml'), {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .reply(202, '', {'x-ms-request-id': 'b67cc525ecc546618fd6fb3e57d724f5'}) + .post('/azure-account-subscription-id/services/hostedservices/create-test-setWait/deployments', helpers.loadFixture('azure/create-deployment.xml'), { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .reply(202, '', { 'x-ms-request-id': 'b67cc525ecc546618fd6fb3e57d724f5' }) .get('/azure-account-subscription-id/operations/b67cc525ecc546618fd6fb3e57d724f5', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/operation-inprogress.xml') .get('/azure-account-subscription-id/operations/b67cc525ecc546618fd6fb3e57d724f5', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/operation-succeeded.xml') // TODO: have to do this twice as setWait() does not check server status before calling server.refresh()? .get('/azure-account-subscription-id/services/hostedservices/create-test-setWait?embed-detail=true', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/running-server.xml') .get('/azure-account-subscription-id/services/hostedservices/create-test-setWait?embed-detail=true', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/running-server.xml') .filteringRequestBodyRegEx(/.*/, '*') .post('/azure-account-subscription-id/services/hostedservices/create-test-setWait/certificates', '*', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) - .reply(202, '', {'x-ms-request-id': 'b67cc525ecc546618fd6fb3e57d724f5'}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .reply(202, '', { 'x-ms-request-id': 'b67cc525ecc546618fd6fb3e57d724f5' }) .get('/azure-account-subscription-id/operations/b67cc525ecc546618fd6fb3e57d724f5', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .replyWithFile(200, __dirname + '/../../fixtures/azure/operation-succeeded.xml'); } -} + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers', { + server: { + name: 'create-test-setWait', + flavorRef: '1', + imageRef: '506d077e-66bf-44ff-907a-588c5c79fa66' + } + }, + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .replyWithFile(202, __dirname + '/../../fixtures/hp/creatingServer.json') + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07', + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/serverCreated.json'); + } + else if (provider === 'oneandone') { + servers.server + .post('/servers', { + name: 'create-test-setWait', + hardware: { fixed_instance_size_id: '8C626C1A7005D0D1F527143C413D461E' } + , appliance_id: 'A0FAA4587A7CB6BBAA1EA877C844977E' + }) + .replyWithFile(202, __dirname + '/../../fixtures/oneandone/getWaitServer.json') + .get('/servers/39AA65F5D5B02FA02D58173094EBAF95') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/getWaitServer.json'); + } +}; -function setupDestroyMock(client, provider, servers) { +setupDestroyMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.server .delete('/v2/123456/servers/a0a5f183-b94e-4a41-a854-64cff53375bf', - {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .reply(204); } else if (provider === 'openstack') { servers.server - .delete('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .delete('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07', { 'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version) }) .reply(204); } else if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); - servers.server - .get('/droplets/354526/destroy?' + qs.stringify({ - scrub_data: '1', - client_id: account.clientId, - api_key: account.apiKey - })) - .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/destroy-server.json'); + .delete('/v2/droplets/3164444') + .reply(204); + } + else if (provider === 'oneandone') { + servers.server + .delete('/servers/39AA65F5D5B02FA02D58173094EBAF95?keep_ips=false') + .replyWithFile(202, __dirname + '/../../fixtures/oneandone/getWaitServer.json'); } -} +}; diff --git a/test/common/compute/meta-test.js b/test/common/compute/meta-test.js new file mode 100644 index 000000000..f0fefc7ca --- /dev/null +++ b/test/common/compute/meta-test.js @@ -0,0 +1,165 @@ +/* +* meta-test.js: Openstack updateImageMeta() function test . +* +* (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. +* +*/ + +var should = require('should'), + helpers = require('../../helpers'), + http = require('http'), + hock = require('hock'), + async = require('async'), + Image = require('../../../lib/pkgcloud/core/compute/image').Image, + mock = !!process.env.MOCK; + +// Declaring variables for helper functions defined later +var setupMetaMock, setupImagesMock; + +var providers=['openstack']; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].compute; +}).forEach(function (provider) { + describe('pkgcloud/common/compute/server [' + provider + ']', function () { + + var client = helpers.createClient(provider, 'compute'), + context = {}, + authServer, server, + authHockInstance, + hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getImages() function should return a list of images', function(done) { + + if (mock) { + setupImagesMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getImages(function (err, images) { + should.not.exist(err); + should.exist(images); + + context.images = images; + + images.forEach(function(img) { + img.should.be.instanceOf(Image); + }); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + + it('the updateImageMeta() method should update the image metadata', function (done) { + if (mock) { + setupMetaMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + var testMetadata = {os_type: 'windows'}; + + client.updateImageMeta(context.images[0].id, testMetadata, function (err, reply) { + should.not.exist(err); + should.exist(reply); + should.exist(reply.metadata.os_type); + reply.metadata.os_type.should.equal('windows'); + + context.currentServer = server; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + + }); + }); + + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + authServer.close(next); + }, + function (next) { + server.close(next); + } + ], done); + }); + + }); +}); + +setupMetaMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/images/506d077e-66bf-44ff-907a-588c5c79fa66/metadata', + { metadata: { + os_type :'windows' + }}) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/metaResponse.json'); + } +}; + +setupImagesMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/images/detail') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/images.json'); + } +}; diff --git a/test/common/compute/server-test.js b/test/common/compute/server-test.js index f8a5b83bc..7928c445c 100644 --- a/test/common/compute/server-test.js +++ b/test/common/compute/server-test.js @@ -1,37 +1,39 @@ /* * server-test.js: Test that should be common to all providers. * -* (C) 2012 Nodejitsu Inc. +* (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ -var fs = require('fs'), - path = require('path'), - qs = require('qs'), - should = require('should'), - utile = require('utile'), +var should = require('should'), async = require('async'), helpers = require('../../helpers'), + http = require('http'), hock = require('hock'), - async = require('async'), - _ = require('underscore'), + _ = require('lodash'), providers = require('../../configs/providers.json'), - Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor, - Image = require('../../../lib/pkgcloud/core/compute/image').Image, Server = require('../../../lib/pkgcloud/core/compute/server').Server, azureApi = require('../../../lib/pkgcloud/azure/utils/azureApi'), mock = !!process.env.MOCK; var azureOptions = require('../../fixtures/azure/azure-options.json'); +// Declaring variables for helper functions defined later +var setupImagesMock, setupFlavorMock, setupServerMock, setupGetServersMock, + setupGetServerMock, setupRebootMock, serverStatusReply; + azureApi._updateMinimumPollInterval(mock ? 10 : azureApi.MINIMUM_POLL_INTERVAL); -providers.forEach(function (provider) { +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].compute; +}).forEach(function (provider) { describe('pkgcloud/common/compute/server [' + provider + ']', function () { var client = helpers.createClient(provider, 'compute'), context = {}, - authServer, server; + authServer, server, + authHockInstance, + hockInstance; before(function (done) { @@ -39,31 +41,31 @@ providers.forEach(function (provider) { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + // setup a filtering path for aws + hockInstance.filteringPathRegEx(/https:\/\/ec2\.us-west-2\.amazonaws\.com([?\w\-\.\_0-9\/]*)/g, '$1'); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock({ - port: 12345, - throwOnUnmatched: false - }, function (err, hockClient) { - server = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - next(); - }); + authServer.listen(12346, next); } - ], done) + ], done); }); it('the getImages() function should return a list of images', function(done) { if (mock) { setupImagesMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -71,10 +73,10 @@ providers.forEach(function (provider) { should.not.exist(err); should.exist(images); - context.images = images + context.images = images; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -84,8 +86,8 @@ providers.forEach(function (provider) { if (mock) { setupFlavorMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -93,10 +95,10 @@ providers.forEach(function (provider) { should.not.exist(err); should.exist(flavors); - context.flavors = flavors + context.flavors = flavors; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -107,12 +109,12 @@ providers.forEach(function (provider) { if (mock) { setupServerMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } - client.createServer(utile.mixin({ + client.createServer(_.extend({ name: 'create-test-ids2', image: context.images[0].id, flavor: context.flavors[0].id @@ -127,8 +129,8 @@ providers.forEach(function (provider) { srv2.name.should.equal('create-test-ids2'); srv2.imageId.should.equal(context.images[0].id); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -137,8 +139,8 @@ providers.forEach(function (provider) { it('the getServers() method should return a list of servers', function (done) { if (mock) { setupGetServersMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -146,24 +148,26 @@ providers.forEach(function (provider) { should.not.exist(err); should.exist(servers); - servers.should.be.instanceOf(Array); + servers.should.be.an.Array; servers.forEach(function(srv) { srv.should.be.instanceOf(Server); }); - authServer && authServer.done(); - server && server.done(); + context.servers = servers; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); - it.skip('the getServer() method should get a server instance', function (done) { + it('the getServer() method should get a server instance', function (done) { if (mock) { setupGetServerMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } @@ -173,10 +177,10 @@ providers.forEach(function (provider) { srv.should.be.instanceOf(Server); - context.currentServer = server; + context.currentServer = hockInstance; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -185,12 +189,13 @@ providers.forEach(function (provider) { it.skip('the server.rebootServer() method should restart a server instance', function (done) { if (mock) { setupRebootMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } context.currentServer.reboot(function(err) { + should.not.exist(err); done(); }); }); @@ -198,12 +203,13 @@ providers.forEach(function (provider) { it.skip('the destroyServer() method should delete a server instance', function (done) { if (mock) { setupRebootMock(client, provider, { - authServer: authServer, - server: server + authServer: authHockInstance, + server: hockInstance }); } context.currentServer.reboot(function (err) { + should.not.exist(err); done(); }); }); @@ -220,13 +226,13 @@ providers.forEach(function (provider) { function (next) { server.close(next); } - ], done) + ], done); }); }); }); -function setupImagesMock(client, provider, servers) { +setupImagesMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.authServer .post('/v2.0/tokens', { @@ -271,15 +277,13 @@ function setupImagesMock(client, provider, servers) { .get('/v2/72e90ecb69c44d0296072ea39e537041/images/detail') .replyWithFile(200, __dirname + '/../../fixtures/openstack/images.json'); } - else if (provider === 'joyent') { - servers.server - .get('/' + client.account + '/datasets') - .replyWithFile(200, __dirname + '/../../fixtures/joyent/images.json'); - } else if (provider === 'amazon') { servers.server .filteringRequestBody(helpers.authFilter) - .post('/?Action=DescribeImages', { 'Owner.0': 'self' }) + .post('/', + { Action: 'DescribeImages', + 'Owner.1': 'self' }, + { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/images.xml'); } else if (provider === 'azure') { @@ -288,17 +292,46 @@ function setupImagesMock(client, provider, servers) { .replyWithFile(200, __dirname + '/../../fixtures/azure/images.xml'); } else if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/images?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/images?per_page=200&page=1') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/images.json'); } -} + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); -function setupFlavorMock(client, provider, servers) { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/images/detail') + .replyWithFile(200, __dirname + '/../../fixtures/hp/images.json'); + } + else if (provider === 'oneandone') { + servers.server + .get('/server_appliances') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/listImages.json'); + } +}; + +setupFlavorMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.server .get('/v2/123456/flavors/detail') @@ -309,40 +342,35 @@ function setupFlavorMock(client, provider, servers) { .get('/v2/72e90ecb69c44d0296072ea39e537041/flavors/detail') .replyWithFile(200, __dirname + '/../../fixtures/openstack/flavors.json'); } - else if (provider === 'joyent') { - servers.server - .get('/' + client.account + '/packages') - .replyWithFile(200, __dirname + '/../../fixtures/joyent/flavors.json'); - } else if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/sizes?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/sizes') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/flavors.json'); } -} + else if (provider === 'hp') { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/flavors/detail') + .replyWithFile(200, __dirname + '/../../fixtures/hp/flavors.json'); + } + else if (provider === 'oneandone') { + servers.server + .get('/servers/fixed_instance_sizes') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/listFlavors.json'); + } +}; -function setupServerMock(client, provider, servers) { +setupServerMock = function (client, provider, servers) { if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/droplets/new?' + qs.stringify({ + .post('/v2/droplets', { name: 'create-test-ids2', - region_id: 1, - size_id: 66, - image_id: 1601, - client_id: account.clientId, - api_key: account.apiKey - })) + region: 'nyc3', + size: '512mb', + image: 119192817 + }) .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/create-server2.json') - .get('/droplets/354526?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/droplets/12345') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/active2.json'); } else if (provider === 'rackspace') { @@ -366,35 +394,28 @@ function setupServerMock(client, provider, servers) { .get('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverCreated2.json'); } - else if (provider === 'joyent') { - servers.server - .post('/' + client.account + '/machines', - require(__dirname + '/../../fixtures/joyent/createServer.json')) - .replyWithFile(201, __dirname + '/../../fixtures/joyent/createdServer.json') - .get('/' + client.account + - '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9') - .replyWithFile(200, - __dirname + '/../../fixtures/joyent/14186c17.json'); - } else if (provider === 'amazon') { servers.server .filteringRequestBody(helpers.authFilter) - .post('/?Action=RunInstances', { + .post('/', { + 'Action': 'RunInstances', 'ImageId': 'ami-85db1cec', 'InstanceType': 'm1.small', 'MaxCount': '1', 'MinCount': '1', 'UserData': 'eyJuYW1lIjoiY3JlYXRlLXRlc3QtaWRzMiJ9' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/run-instances.xml') // .post('/?Action=DescribeInstances') // .replyWithFile(200, __dirname + '/../../fixtures/amazon/pending-server.xml') - .post('/?Action=DescribeInstanceAttribute', { + .post('/', { + 'Action':'DescribeInstanceAttribute', 'Attribute': 'userData', 'InstanceId': 'i-1d48637b' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server-attr2.xml') - .post('/?Action=DescribeInstances', { + .post('/', { + 'Action': 'DescribeInstances', 'Filter.1.Name': 'instance-state-code', 'Filter.1.Value.1': '0', 'Filter.1.Value.2': '16', @@ -402,7 +423,7 @@ function setupServerMock(client, provider, servers) { 'Filter.1.Value.4': '64', 'Filter.1.Value.5': '80', 'InstanceId.1': 'i-1d48637b' - }) + }, { 'User-Agent': client.userAgent }) .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server.xml'); } else if (provider === 'azure') { @@ -432,11 +453,30 @@ function setupServerMock(client, provider, servers) { .get('/azure-account-subscription-id/services/hostedservices/create-test-ids2?embed-detail=true') .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')) .get('/azure-account-subscription-id/services/hostedservices/create-test-ids2?embed-detail=true') - .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')) + .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')); } -} + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers', + {server: {name: 'create-test-ids2', flavorRef: '1', imageRef: '506d077e-66bf-44ff-907a-588c5c79fa66'}}) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/creatingServer.json') + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverCreated2.json'); + } + else if (provider === 'oneandone') { + servers.server + .post('/servers', { + name: 'create-test-ids2', + hardware: {fixed_instance_size_id: '8C626C1A7005D0D1F527143C413D461E'} + , appliance_id: 'A0FAA4587A7CB6BBAA1EA877C844977E' + }) + .replyWithFile(202, __dirname + '/../../fixtures/oneandone/getServer.json') + .get('/servers/39AA65F5D5B02FA02D58173094EBAF95') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/getServer.json'); + } +}; -function setupGetServersMock(client, provider, servers) { +setupGetServersMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.server .get('/v2/123456/servers/detail') @@ -447,16 +487,13 @@ function setupGetServersMock(client, provider, servers) { .get('/v2/72e90ecb69c44d0296072ea39e537041/servers/detail') .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverList.json'); } - else if (provider === 'joyent') { - servers.server - .get('/' + client.account + '/machines') - .replyWithFile(200, __dirname + '/../../fixtures/joyent/servers.json'); - } else if (provider === 'amazon') { servers.server .filteringRequestBody(helpers.authFilter) - .post('/?Action=DescribeInstances', {}) - .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server.xml') + .post('/', { + Action: 'DescribeInstances' + }, { 'User-Agent': client.userAgent }) + .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server.xml'); } else if (provider === 'azure') { @@ -465,439 +502,92 @@ function setupGetServersMock(client, provider, servers) { servers.server .defaultReplyHeaders({'x-ms-request-id': requestId, 'Content-Type': 'application/xml'}) .get('/azure-account-subscription-id/services/hostedservices') - .reply(200, "https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z") + .reply(200, 'https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z') .get('/azure-account-subscription-id/services/hostedservices/create-test-ids2?embed-detail=true') - .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')) + .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')); } else if (provider === 'digitalocean') { - var account = require(__dirname + '/../../configs/mock/digitalocean'); servers.server - .get('/droplets?' + qs.stringify({ - client_id: account.clientId, - api_key: account.apiKey - })) + .get('/v2/droplets?per_page=200&page=1') .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/list-servers.json'); } -} + else if (provider === 'hp') { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/detail') + .replyWithFile(200, __dirname + '/../../fixtures/hp/serverList.json'); + } + else if (provider === 'oneandone') { + servers.server + .get('/servers') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/listServers.json'); + } +}; -function setupGetServerMock(client, provider, servers) { +setupGetServerMock = function (client, provider, servers) { if (provider === 'rackspace') { servers.server - .get('/v1.0/537645/servers/20578901') - .replyWithFile(200, __dirname + '/../../fixtures/rackspace/20578901.json'); + .get('/v2/123456/servers/a0a5f183-b94e-4a41-a854-00aa00aa00aa') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/a0a5f183-b94e-4a41-a854-00aa00aa00aa.json'); } else if (provider === 'openstack') { servers.server .get('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverCreated2.json'); } - else if (provider === 'joyent') { - servers.server - .get('/' + client.account + '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9') - .replyWithFile(200, __dirname + '/../../fixtures/joyent/14186c17.json'); - } else if (provider === 'amazon') { servers.server .filteringRequestBody(helpers.authFilter) .post('/?Action=DescribeInstances', {}) - .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server.xml') + .replyWithFile(200, __dirname + '/../../fixtures/amazon/running-server.xml'); } else if (provider === 'azure') { var requestId = 'b67cc525-ecc5-4661-8fd6-fb3e57d724f5'; - + servers.server .defaultReplyHeaders({'x-ms-request-id': requestId, 'Content-Type': 'application/xml'}) - .get('/azure-account-subscription-id/services/hostedservices') - .reply(200, "https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z") .get('/azure-account-subscription-id/services/hostedservices/create-test-ids2?embed-detail=true') - .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')) + .reply(200, serverStatusReply('create-test-ids2', 'ReadyRole')); } -} -// -//function batchThree(providerClient, providerName) { -// var name = providerName || 'rackspace', -// client = providerClient || clients['rackspace'], -// test = {}; -// -// test["The pkgcloud " + name + " compute client"] = { -// "the getServers() method": { -// topic: function () { -// client.getServers(this.callback); -// }, -// "should return the list of servers": function (err, servers) { -// assert.isNull(err); -// testContext.servers = servers; -// servers.forEach(function (server) { -// assert.assertServer(server); -// }); -// } -// }, -// "the getServer() method": { -// topic: function () { -// client.getServer(testContext.servers[0], this.callback); -// }, -// "should return a valid server": function (err, server) { -// client.destroyServer(server); -// assert.isNull(err); -// assert.assertServerDetails(server); -// assert.ok(Array.isArray(server.addresses["public"])); -// assert.ok(Array.isArray(server.addresses["private"])); -// if (name === 'openstack') { -// assert.ok(typeof server.addresses["private"][0] === 'object'); -// assert.ok(typeof server.addresses["public"][0] === 'object'); -// } -// else { -// assert.ok(typeof server.addresses["private"][0] === 'string'); -// assert.ok(typeof server.addresses["public"][0] === 'string'); -// } -// } -// } -// }; -// -// return test; -//} -// -//function batchReboot(providerClient, providerName, nock) { -// var name = providerName || 'rackspace', -// client = providerClient || clients['rackspace'], -// timeout = process.env.MOCK ? 1 : 10000, -// test = {}; -// -// test["The pkgcloud " + name + " compute client"] = { -// "the rebootServer() method": { -// topic: function () { -// var self = this; -// client.createServer(utile.mixin({ -// name : "test-reboot", -// image : testContext.images[0].id, -// flavor: testContext.flavors[0].id -// }, name === 'azure' ? azureOptions : {}), -// function (err, server, response) { -// if (err) { return self.callback(err); } -// -// function waitForReboot(server) { -// // should have used setWait -// // dont do this in your code -// return setTimeout(function () { -// server.refresh(function (err, srv) { -// if (err) { return self.callback(err); } -// if (srv.status === "RUNNING") { -// return self.callback(null, srv); -// } -// waitForReboot(srv); -// }); -// }, timeout); -// } -// -// function keepTrying() { -// // should have used setWait -// // dont do this in your code -// return setTimeout(function () { -// if (server.status==='RUNNING') { -// server.reboot(function (err, ok) { -// if (err) { return self.callback(err); } -// waitForReboot(server); -// }); -// } else { -// server.refresh(function (err, srv) { -// if (err) { return self.callback(err); } -// server = srv; -// keepTrying(); -// }); -// } -// }, timeout); -// } -// keepTrying(); -// }); -// }, -// "should return a server after reboot": function (err, server) { -// assert.isNull(err); -// assert.assertServer(server); -// } -// } -// }; -// -// return test; -//} -// -//function batchDestroy(providerClient, providerName) { -// var name = providerName || 'rackspace', -// client = providerClient || clients['rackspace'], -// test = {}; -// -// test["The pkgcloud " + name + " compute client"] = { -// "the destroyServer() method": { -// topic: function () { -// client.destroyServer(testContext.servers[0].id, this.callback); -// }, -// "should respond correctly": function (err, response) { -// assert.isNull(err); -// assert.ok(response.ok); -// assert.equal(response.ok, testContext.servers[0].id); -// } -// } -// }; -// -// return test; -//} -// -//JSON.parse(fs.readFileSync(__dirname + '/../../configs/providers.json')) -// .forEach(function (provider) { -// clients[provider] = helpers.createClient(provider, 'compute'); -// -// var client = clients[provider], -// nock = require('nock'); -// -// testData = {}; -// testContext = {}; -// -// if (process.env.MOCK) { -// if (provider === 'joyent') { -// nock('https://' + client.serversUrl) -// .get('/' + client.account + '/machines') -// .reply(200, "[]", {}) -// .get('/' + client.account + '/datasets') -// .reply(200, __dirname + '/../../fixturejoyent/images.json'), {}) -// .get('/' + client.account + '/packages') -// .reply(200, __dirname + '/../../fixturejoyent/flavors.json'), {}) -// -// -// ["delete"]('/' + client.account + -// '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9') -// .reply(204, "", {}) -// .get('/' + client.account + '/machines') -// .reply(200, __dirname + '/../../fixturejoyent/servers.json'), {}) -// .post('/' + client.account + '/machines', -// __dirname + '/../../fixturejoyent/rebootServerRequest1.json')) -// .reply(201, -// __dirname + '/../../fixturejoyent/rebootServerResponse1.json'), {}) -// .get('/' + client.account + -// '/machines/fe4d8e28-6154-4281-8f0e-dead21585ed5') -// .reply(200, -// __dirname + '/../../fixturejoyent/fe4d8e28.json'), {}) -// .post('/' + client.account + -// '/machines/fe4d8e28-6154-4281-8f0e-dead21585ed5?action=reboot') -// .reply(202, "", {}) -// .get('/' + client.account + -// '/machines/fe4d8e28-6154-4281-8f0e-dead21585ed5') -// .reply(200, -// __dirname + '/../../fixturejoyent/fe4d8e28.json'), {}) -// -// .get('/' + client.account + -// '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9') -// .reply(200, -// __dirname + '/../../fixturejoyent/14186c17.json'), {}) -// .get('/' + client.account + -// '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9') -// .reply(200, -// __dirname + '/../../fixturejoyent/14186c17.json'), {}) -// ["delete"]('/' + client.account + -// '/machines/fe4d8e28-6154-4281-8f0e-dead21585ed5') -// .reply(204, "", {}) -// .post('/' + client.account + -// '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9', { action: 'stop' }) -// .reply(202, "", {}) -// .get('/' + client.account + -// '/machines/14186c17-0fcd-4bb5-ab42-51b848bda7e9') -// .reply(200, -// __dirname + '/../../fixturejoyent/14186c17.json'), {}) -// ; -// } -// else if (provider === 'rackspace') { -// nock('https://' + client.authUrl) -// .get('/v1.0') -// .reply(204, "", -// JSON.parse(__dirname + '/../../fixturerackspace/auth.json'))); -// nock('https://' + client.serversUrl) -// .get('/v1.0/537645/flavors/detail.json') -// .reply(200, __dirname + '/../../fixturerackspace/serverFlavors.json'), {}) -// .get('/v1.0/537645/images/detail.json') -// .reply(200, __dirname + '/../../fixturerackspace/images.json'), {}) -// .get('/v1.0/537645/images/detail.json') -// .reply(200, __dirname + '/../../fixturerackspace/images.json'), {}) -// -// -// .post('/v1.0/537645/servers', -// __dirname + '/../../fixturerackspace/createServer.json')) -// .reply(202, __dirname + '/../../fixturerackspace/createdServer.json'), -// {}) -// .get('/v1.0/537645/servers/detail.json') -// .reply(204, __dirname + '/../../fixturerackspace/servers.json'), {}) -// ["delete"]('/v1.0/537645/servers/20592449') -// .reply(200, '{"ok": 20592449}', {}) -// .get('/v1.0/537645/servers/20592449') -// .reply(200, __dirname + '/../../fixturerackspace/20592449.json'), {}) -// .post('/v1.0/537645/servers', -// __dirname + '/../../fixturerackspace/createReboot.json')) -// .reply(202, -// __dirname + '/../../fixturerackspace/buildingReboot.json'), {}) -// .get('/v1.0/537645/servers/20596929') -// .reply(200, -// __dirname + '/../../fixturerackspace/activeReboot.json'), {}) -// .post('/v1.0/537645/servers/20596929/action', -// '{"reboot":{"type":"SOFT"}}') -// .reply(202, "", {}) -// .get('/v1.0/537645/servers/20596929') -// .reply(200, -// __dirname + '/../../fixturerackspace/activeReboot.json'), {}) -// ; -// } else if (provider === 'amazon') { -// nock('https://' + client.serversUrl) -// .filteringRequestBody(helpers.authFilter) -// -// -// .post('/?Action=TerminateInstances', { -// 'InstanceId': 'i-1d48637b' -// }) -// .reply(200, 'doesn\'t matter', {}) -// .post('/?Action=RunInstances', { -// 'ImageId': 'ami-85db1cec', -// 'InstanceType': 'm1.small', -// 'MaxCount': '1', -// 'MinCount': '1', -// 'UserData': 'eyJuYW1lIjoidGVzdC1yZWJvb3QifQ==' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/run-instances.xml'), {}) -// .post('/?Action=DescribeInstances', { -// 'Filter.1.Name': 'instance-state-code', -// 'Filter.1.Value.1': '0', -// 'Filter.1.Value.2': '16', -// 'Filter.1.Value.3': '32', -// 'Filter.1.Value.4': '64', -// 'Filter.1.Value.5': '80', -// 'InstanceId.1': 'i-1d48637b' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/pending-server.xml'), {}) -// .post('/?Action=DescribeInstanceAttribute', { -// 'Attribute': 'userData', -// 'InstanceId': 'i-1d48637b' -// }) -// .reply(200, -// __dirname + '/../../fixtureamazon/running-server-attr.xml', {})) -// .post('/?Action=DescribeInstances', { -// 'Filter.1.Name': 'instance-state-code', -// 'Filter.1.Value.1': '0', -// 'Filter.1.Value.2': '16', -// 'Filter.1.Value.3': '32', -// 'Filter.1.Value.4': '64', -// 'Filter.1.Value.5': '80', -// 'InstanceId.1': 'i-1d48637b' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/running-server.xml'), {}) -// .post('/?Action=DescribeInstanceAttribute', { -// 'Attribute': 'userData', -// 'InstanceId': 'i-1d48637b' -// }) -// .reply(200, -// __dirname + '/../../fixtureamazon/running-server-attr.xml', {})) -// .post('/?Action=RebootInstances', { -// 'InstanceId': 'i-1d48637b' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/reboot-server.xml', {})) -// .post('/?Action=DescribeInstances', { -// 'Filter.1.Name': 'instance-state-code', -// 'Filter.1.Value.1': '0', -// 'Filter.1.Value.2': '16', -// 'Filter.1.Value.3': '32', -// 'Filter.1.Value.4': '64', -// 'Filter.1.Value.5': '80', -// 'InstanceId.1': 'i-1d48637b' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/pending-server.xml'), {}) -// .post('/?Action=DescribeInstanceAttribute', { -// 'Attribute': 'userData', -// 'InstanceId': 'i-1d48637b' -// }) -// .reply(200, -// __dirname + '/../../fixtureamazon/running-server-attr.xml', {})) -// .post('/?Action=DescribeInstances', { -// 'Filter.1.Name': 'instance-state-code', -// 'Filter.1.Value.1': '0', -// 'Filter.1.Value.2': '16', -// 'Filter.1.Value.3': '32', -// 'Filter.1.Value.4': '64', -// 'Filter.1.Value.5': '80', -// 'InstanceId.1': 'i-1d48637b' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/running-server.xml'), {}) -// .post('/?Action=DescribeInstanceAttribute', { -// 'Attribute': 'userData', -// 'InstanceId': 'i-1d48637b' -// }) -// .reply(200, __dirname + '/../../fixtureamazon/running-server-attr.xml'), {}) -// } else if (provider === 'azure') { -// azureNock.serverTest(nock, helpers); -// } else if (provider === 'openstack') { -// nock(client.authUrl) -// -// -// nock('http://compute.myownendpoint.org:8774') -// -// -// .get('/v2/72e90ecb69c44d0296072ea39e537041/servers/detail') -// .reply(200, __dirname + '/../../fixtureopenstack/serverList.json')) -// .get('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') -// .reply(200, __dirname + '/../../fixtureopenstack/serverCreated2.json')) -// ["delete"]('/v2/72e90ecb69c44d0296072ea39e537041/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') -// .reply(204, ""); -// } -// } -// -// var suite = vows.describe('pkgcloud/common/compute/server [' + provider + ']') -// .addBatch(batchOne(client, provider)) -// .addBatch(batchTwo(client, provider)) -// ; -// -// // Delete the server created on step two -// if (provider === 'openstack') { -// suite -// .addBatch(batchDestroy(client, provider)) -// ; -// } -// -// suite -// .addBatch(batchThree(client, provider)) -// ; -// -// // Disable reboot test for openstack :( -// if (provider !== 'openstack') { -// suite -// .addBatch(batchReboot(client, provider, nock)) -// ; -// } -// -// suite -// .export(module) -// ; -// });z -/** - * serverStatusReply() - * fills in the nock xml reply from the server with server name and status - * @param name - name of the server - * @param status - status to be returned in reply - * status should be: - * ReadyRole - server is RUNNING - * VMStopped - server is still PROVISIONING - * Provisioning - server is still PROVISIONING - * see lib/pkgcloud/azure/compute/server.js for more status values - * - * @return {String} - the xml reply containing the server name and status - */ -var serverStatusReply = function (name, status) { - - var template = helpers.loadFixture('azure/server-status-template.xml'), - params = {NAME: name, STATUS: status}; + else if (provider === 'digitalocean') { + servers.server + .get('/v2/droplets/3164444') + .replyWithFile(200, __dirname + '/../../fixtures/digitalocean/active.json'); + } + else if (provider === 'hp') { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/serverCreated2.json'); + } + else if (provider === 'oneandone') { + servers.server + .get('/servers/39AA65F5D5B02FA02D58173094EBAF95') + .replyWithFile(200, __dirname + '/../../fixtures/oneandone/getServer.json'); + } +}; - var result = _.template(template, params); - return result; +setupRebootMock = function() { + // TODO }; -var filterPath = function (path) { - var name = PATH.basename(path); - if (path.search('embed-detail=true') !== -1) { - return '/getStatus?name=' + name; - } +/** + - * serverStatusReply() + - * fills in the nock xml reply from the server with server name and status + - * @param name - name of the server + - * @param status - status to be returned in reply + - * status should be: + - * ReadyRole - server is RUNNING + - * VMStopped - server is still PROVISIONING + - * Provisioning - server is still PROVISIONING + - * see lib/pkgcloud/azure/compute/server.js for more status values + - * + - * @return {String} - the xml reply containing the server name and status + - */ +serverStatusReply = function (name, status) { + + var template = helpers.loadFixture('azure/server-status-template.xml'), + params = {NAME: name, STATUS: status}, + compiled = _.template(template); - return path; + return compiled(params); }; diff --git a/test/common/compute/signature-test.js b/test/common/compute/signature-test.js index 17402c616..fd1e0c70b 100644 --- a/test/common/compute/signature-test.js +++ b/test/common/compute/signature-test.js @@ -1,68 +1,69 @@ /* * signature-test.js: Test that shared methods meet some expectations for arguments. * - * (C) 2013 Nodejitsu Inc. + * (C) 2013 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var should = require('should'), providers = require('../../configs/providers.json'), - helpers = require('../../helpers'), - _ = require('underscore'); + helpers = require('../../helpers'); -providers.forEach(function (provider) { +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].compute; +}).forEach(function (provider) { describe('pkgcloud/common/compute/signatures [' + provider + ']', function () { var client = helpers.createClient(provider, 'compute'); it('client.getVersion should have length 1', function () { - client.getVersion.should.be.a('function'); + client.getVersion.should.be.a.Function; client.getVersion.should.have.length(1); }); it('client.createServer should take 2 arguments', function () { - client.createServer.should.be.a('function'); + client.createServer.should.be.a.Function; client.createServer.should.have.length(2); }); it('client.getServers should take at least 1 argument', function () { - client.getServers.should.be.a('function'); + client.getServers.should.be.a.Function; should.ok(client.getServers.length >= 1); }); it('client.getServer should take 2 arguments', function () { - client.getServer.should.be.a('function'); + client.getServer.should.be.a.Function; client.getServer.should.have.length(2); }); it('client.rebootServer should have minimum 2 arguments', function () { - client.rebootServer.should.be.a('function'); + client.rebootServer.should.be.a.Function; should.ok(client.rebootServer.length >= 2); }); it('client.destroyServer should take at least 2 arguments', function () { - client.destroyServer.should.be.a('function'); + client.destroyServer.should.be.a.Function; should.ok(client.destroyServer.length >= 2); }); it('client.getFlavor should take 2 arguments', function () { - client.getFlavor.should.be.a('function'); + client.getFlavor.should.be.a.Function; client.getFlavor.should.have.length(2); }); it('client.getFlavors should take 1 argument', function () { - client.getFlavors.should.be.a('function'); + client.getFlavors.should.be.a.Function; client.getFlavors.should.have.length(1); }); it('client.getImage should take 2 arguments', function () { - client.getImage.should.be.a('function'); + client.getImage.should.be.a.Function; client.getImage.should.have.length(2); }); it('client.getImages should have minimum 1 argument', function () { - client.getImages.should.be.a('function'); + client.getImages.should.be.a.Function; should.ok(client.getImages.length >= 1); }); }); diff --git a/test/common/databases/databases-test.js b/test/common/databases/databases-test.js new file mode 100644 index 000000000..08c20c639 --- /dev/null +++ b/test/common/databases/databases-test.js @@ -0,0 +1,507 @@ +/* + * databases-test.js: Tests for Openstack Trove Databases within an instances + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var should = require('should'), + hock = require('hock'), + http = require('http'), + async = require('async'), + helpers = require('../../helpers'), + providers = require('../../configs/providers.json'), + mock = !!process.env.MOCK; + +// Declaring variables for helper functions defined later +var setupCreateDatabasesMock, setupCreateDatabasesForPaginationMock, + setupModelCreateDatabasesMock, setupGetDatabasesMock, + setupDestroyDatabasesMock, setupDestroyLastDatabasesMock; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].database && provider !== 'azure'; +}).forEach(function (provider) { + describe('pkgcloud/['+provider+']/databases/databases', function() { + + var client, testContext = {}, hockInstance, authHockInstance, server, authServer; + + describe('The pkgcloud '+provider+' Database client', function() { + + before(function (done) { + client = helpers.createClient(provider, 'database'); + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the createDatabases() method should respond correctly', function(done) { + if (mock) { + helpers.setupAuthenticationMock(authHockInstance, provider); + setupCreateDatabasesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createDatabase({name: 'TestDatabase', instance: instance}, function(err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('create another database for pagination test should respond correctly', function (done) { + + if (mock) { + setupCreateDatabasesForPaginationMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createDatabase({name: 'TestDatabaseTwo', instance: instance}, function(err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the create() method should respond correctly', function (done) { + + if (mock) { + setupModelCreateDatabasesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.create({name: 'TestDatabaseThree', instance: instance}, function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the createDatabase() method with no name should get an error for name', function(done) { + client.createDatabase({}, function(err, response) { + should.exist(err); + should.not.exist(response); + err.message.should.equal('options. Name is a required argument'); + done(); + }); + }); + + it('the createDatabase() method with no instance should get an error for instance', function (done) { + client.createDatabase({ name: 'NotCreated' }, function (err, response) { + should.exist(err); + should.not.exist(response); + err.message.should.equal('options. Instance is a required argument'); + done(); + }); + }); + + it('the getDatabases() method should return a list of databases', function (done) { + + if (mock) { + setupGetDatabasesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.getDatabases({ instance: instance }, function(err, list) { + should.not.exist(err); + should.exist(list); + list.should.be.an.instanceOf(Array); + list.should.have.length(2); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getDatabases() method should return a list of databases with names', function (done) { + + if (mock) { + setupGetDatabasesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.getDatabases({ instance: instance }, function (err, list) { + should.not.exist(err); + should.exist(list); + should.exist(list[0]); + list[0].name.should.equal('TestDatabase'); + list[0].name.should.be.a.String; + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getDatabases() method with limit should respond one element', function (done) { + if (provider !== 'rackspace') { + return done(); + } + + if (mock) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?limit=1') + .reply(200, {databases: [ + {name: 'TestDatabase'} + ]}); + } + + helpers.selectInstance(client, function (instance) { + client.getDatabases({ instance: instance, limit: 1 }, function (err, instances) { + should.not.exist(err); + should.exist(instances); + instances.should.be.an.instanceOf(Array); + instances.should.have.length(1); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getDatabases() method with limit should pass as third argument the offset mark', function (done) { + if (provider !== 'rackspace') { + return done(); + } + + if (mock) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?limit=1') + .reply(200, helpers.loadFixture('rackspace/databasesLimit.json')); + } + + helpers.selectInstance(client, function (instance) { + client.getDatabases({ instance: instance, limit: 1 }, function (err, instances, offset) { + should.not.exist(err); + should.exist(instances); + should.exist(offset); + offset.should.equal('TestDatabase'); + testContext.marker = offset; + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getDatabases() method with offset should respond less quantity', function (done) { + if (provider !== 'rackspace') { + return done(); + } + + if (mock) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?marker=TestDatabase') + .reply(200, { databases: [{ name: 'TestDatabaseTwo '}] }); + } + + helpers.selectInstance(client, function (instance) { + client.getDatabases({ instance: instance, offset: testContext.marker }, + function(err, instances, offset) { + should.not.exist(err); + should.exist(instances); + instances.should.have.length(1); + should.not.exist(offset); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getDatabases() method with limit and offset ' + + 'should respond just one result with no more next points', function (done) { + if (provider !== 'rackspace') { + return done(); + } + + if (mock) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?limit=1&marker=TestDatabase') + .reply(200, { databases: [{ name: 'TestDatabaseTwo' }] }); + } + + helpers.selectInstance(client, function (instance) { + client.getDatabases({ instance: instance, limit: 1, offset: testContext.marker }, + function (err, instances, offset) { + should.not.exist(err); + should.exist(instances); + instances.should.be.an.Array; + instances.should.have.length(1); + should.not.exist(offset); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the destroyDatabase() method with first db should respond correctly', function (done) { + + if (mock) { + setupDestroyDatabasesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.destroyDatabase('TestDatabase', instance, function(err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the destroyDatabase() method with last db should respond correctly', function (done) { + + if (mock) { + setupDestroyLastDatabasesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.destroyDatabase('TestDatabaseTwo', instance, function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + }); + }); +}); + +setupCreateDatabasesMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabase' } + ] + }) + .reply(202); + } + else if (provider ==='openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabase' } + ] + }) + .reply(202); + } + else if (provider ==='hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabase' } + ] + }) + .reply(202); + } +}; + +setupCreateDatabasesForPaginationMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabaseTwo' } + ] + }) + .reply(202); + } + else if (provider ==='openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabaseTwo' } + ] + }) + .reply(202); + } + else if (provider ==='hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabaseTwo' } + ] + }) + .reply(202); + } +}; + +setupModelCreateDatabasesMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabaseThree' } + ] + }) + .reply(202); + } + else if (provider ==='openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabaseThree' } + ] + }) + .reply(202); + } + else if (provider ==='hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', + { + databases: [ + { name: 'TestDatabaseThree' } + ] + }) + .reply(202); + } +}; + +setupGetDatabasesMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases') + .reply(200, {databases: [{name: 'TestDatabase'}, {name: 'TestDatabaseTwo'}]}); + } + else if (provider ==='openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases') + .reply(200, {databases: [{name: 'TestDatabase'}, {name: 'TestDatabaseTwo'}]}); + } + else if (provider ==='hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases') + .reply(200, {databases: [{name: 'TestDatabase'}, {name: 'TestDatabaseTwo'}]}); + } +}; + +setupDestroyDatabasesMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabase') + .reply(202); + } + else if (provider ==='openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabase') + .reply(202); + } + else if (provider ==='hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabase') + .reply(202); + } +}; + +setupDestroyLastDatabasesMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabaseTwo') + .reply(202); + } + else if (provider ==='openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabaseTwo') + .reply(202); + } + else if (provider ==='hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabaseTwo') + .reply(202); + } +}; diff --git a/test/common/databases/errors-test.js b/test/common/databases/errors-test.js new file mode 100644 index 000000000..986ecc656 --- /dev/null +++ b/test/common/databases/errors-test.js @@ -0,0 +1,182 @@ +/* +* errors-test.js: Tests for Openstack Trove client errors +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + helpers = require('../../helpers'), + providers = require('../../configs/providers.json'); + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].database && provider !== 'azure'; +}).forEach(function (provider) { + describe('pkgcloud/['+provider+']/databases/errors', function() { + var client = helpers.createClient(provider, 'database'); + + describe('The pkgcloud '+provider+' Database client', function() { + describe('breaking the function', function() { + + it('createInstance() when no options should return an error', function(done) { + client.createInstance(function(err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createInstance() with bad options should return an error', function (done) { + client.createInstance({}, function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createInstance() with no instance options should return an error', function (done) { + client.createInstance({ name: 'shouldGetError' }, function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('destroyInstance() with no instance should return an error', function (done) { + client.destroyInstance(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('getInstance() with no instance should return an error', function (done) { + client.getInstance(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createDatabase() with no options should return an error', function (done) { + client.createDatabase(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createDatabase() with no instance should return an error', function (done) { + client.createDatabase({ name: 'shouldGetError' }, function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('getDatabases() with no instance should return an error', function (done) { + client.getDatabases(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('destroyDatabase() with no options should return an error', function (done) { + client.destroyDatabase(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('destroyDatabase() with no instance should return an error', function (done) { + client.destroyDatabase('shouldGetError', function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createUser() with no options should return an error', function (done) { + client.createUser(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createUser() with empty objects should return an error', function (done) { + client.createUser({}, function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createUser() with no db or instance should return an error', function (done) { + client.createUser({ + username: 'testing', + password: 'shouldFail' + }, function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('createUser() with no instance should return an error', function (done) { + client.createUser({ + username: 'testing', + password: 'shouldFail', + database: 'none' + }, function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('getUsers() with no instance should return an error', function (done) { + client.getUsers(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('destroyUser() with no instance should return an error', function (done) { + client.destroyUser(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('destroyUser() with no user should return an error', function (done) { + client.destroyUser('shouldGetError', function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('enableRoot() with no instance should return an error', function (done) { + client.enableRoot(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + + it('rootEnabled() with no instance should return an error', function (done) { + client.rootEnabled(function (err, instance) { + should.exist(err); + should.not.exist(instance); + done(); + }); + }); + }); + }); + }); +}); diff --git a/test/common/databases/flavor-test.js b/test/common/databases/flavor-test.js new file mode 100644 index 000000000..d1f57fb37 --- /dev/null +++ b/test/common/databases/flavor-test.js @@ -0,0 +1,162 @@ +/* +* flavor-test.js: Test for pkgcloud Openstack Trove database flavors. +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + hock = require('hock'), + http = require('http'), + async = require('async'), + helpers = require('../../helpers'), + Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor, + providers = require('../../configs/providers.json'), + mock = !!process.env.MOCK; + +// Declaring variables for helper functions defined later +var setupGetFlavorMock; +function setupGetFlavorsMock(hockInstance, provider ){ + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/flavors') + .reply(200, helpers.loadFixture('rackspace/databaseFlavors.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors') + .reply(200, helpers.loadFixture('openstack/databaseFlavors.json')); + + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/flavors') + .reply(200, helpers.loadFixture('hp/databaseFlavors.json')); + } +} + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].database && provider !== 'azure'; +}).forEach(function (provider) { + describe('pkgcloud/rackspace/['+provider+']/flavors', function () { + var testContext = {}, + client, authHockInstance, hockInstance, server, authServer; + + describe('The pkgcloud '+provider+' Database client', function () { + + before(function (done) { + client = helpers.createClient(provider, 'database'); + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + function getFlavors(auth, callback) { + if (mock) { + if (auth) { + helpers.setupAuthenticationMock(authHockInstance, provider); + } + + setupGetFlavorsMock(hockInstance, provider); + } + + client.getFlavors(callback); + } + + it('the getFlavors() method should return the list of flavors', function(done) { + getFlavors(true, function (err, flavors) { + should.not.exist(err); + should.exist(flavors); + flavors.should.be.an.Array; + flavors.forEach(function (flavor) { + flavor.should.be.instanceOf(Flavor); + }); + + hockInstance && hockInstance.done(); + authHockInstance && authHockInstance.done(); + testContext.flavors = flavors; + done(); + }); + }); + + it('the getFlavors() method should return the list of flavor with rackspace specific information', function (done) { + getFlavors(false, function (err, flavors) { + should.not.exist(err); + should.exist(flavors); + flavors.should.be.an.Array; + flavors.forEach(function (flavor) { + flavor.ram.should.be.a.Number; + flavor.href.should.be.a.String; + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the getFlavor() method should return a valid flavor', function(done) { + if (mock) { + setupGetFlavorMock(hockInstance, provider); + } + + client.getFlavor(testContext.flavors[2].id, function(err, flavor) { + should.not.exist(err); + should.exist(flavor); + flavor.should.be.instanceOf(Flavor); + flavor.id.should.equal(testContext.flavors[2].id); + hockInstance && hockInstance.done(); + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + }); + }); +}); + + +setupGetFlavorMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/flavors/3') + .reply(200, helpers.loadFixture('rackspace/databaseFlavor3.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/3') + .reply(200, helpers.loadFixture('openstack/databaseFlavor3.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/flavors/3') + .reply(200, helpers.loadFixture('hp/databaseFlavor3.json')); + } +}; diff --git a/test/common/databases/instances-test.js b/test/common/databases/instances-test.js new file mode 100644 index 000000000..5bd0a8929 --- /dev/null +++ b/test/common/databases/instances-test.js @@ -0,0 +1,827 @@ +/* +* instances-test.js: Tests for Openstack Trove instances +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + hock = require('hock'), + http = require('http'), + async = require('async'), + helpers = require('../../helpers'), + providers = require('../../configs/providers.json'), + Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor, + Instance = require('../../../lib/pkgcloud/openstack/database/instance').Instance, + mock = !!process.env.MOCK; + +// Declaring variables for helper functions defined later +var assertLinks, setupCreateInstanceMock, setupGetInstancesMock, + setupGetDatabaseInstancesWithLimitMock, setupDestroyInstanceMock, + setGetInstanceMock, setGetFlavorsMock, setupSetFlavorMock, setupResizeMock, + setupGetOneFlavorMock, setupRestartInstanceMock, setupEnableRootMock; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].database && provider !== 'azure'; +}).forEach(function (provider) { + describe('pkgcloud/['+provider+']/databases/instances', function () { + var testContext = {}, + client, authHockInstance, hockInstance, authServer, server; + + describe('The pkgcloud '+provider+' Database client', function () { + + before(function (done) { + client = helpers.createClient(provider, 'database'); + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + describe('the create() method', function() { + + var err, instance; + + before(function(done) { + helpers.setupAuthenticationMock(authHockInstance, provider); + setupCreateInstanceMock(hockInstance, provider); + + client.getFlavor(1, function (err, flavor) { + should.not.exist(err); + should.exist(flavor); + flavor.should.be.instanceOf(Flavor); + + client.createInstance({ + name: 'test-instance', + flavor: flavor, + databases: ['db1'] + }, function(e, i) { + err = e; + instance = i; + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('should return a valid instance', function() { + should.not.exist(err); + should.exist(instance); + instance.should.be.instanceOf(Instance); + }); + + it('should return the same name and flavor used', function() { + should.not.exist(err); + should.exist(instance); + instance.name.should.equal('test-instance'); + should.equal(1, instance.flavor.id); + }); + }); + + describe('the getInstances() method', function() { + describe('without options', function() { + + var err, instances, offset; + + before(function(done) { + + if (mock) { + setupGetInstancesMock(hockInstance, provider); + } + + client.getInstances(function(e, i, o) { + err = e; + instances = i; + offset = o; + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('should return the list of instances', function () { + should.not.exist(err); + should.exist(instances); + instances.should.be.an.Array; + instances.length.should.be.above(0); + + testContext.instancesQuantity = instances.length; + }); + + it('should valid instance each item in the list', function () { + instances.forEach(function (instance) { + instance.should.be.instanceOf(Instance); + }); + }); + + it('should response with extra info', function () { + + instances.forEach(function (instance) { + should.exist(instance.id); + instance.links.should.be.an.Array; + instance.flavor.should.be.a.Object; + instance.volume.should.be.a.Object; + instance.volume.size.should.be.a.Number; + }); + }); + + it('should have correct flavor', function () { + instances.forEach(function (instance) { + should.exist(instance.flavor.id); + assertLinks(instance.flavor.links); + }); + }); + + it('should have correct links', function () { + instances.forEach(function (instance) { + assertLinks(instance.links); + }); + }); + + it('should have a null offset', function () { + should.not.exist(offset); + }); + }); + + describe('with limit', function () { + + var err, instances, offset; + + before(function (done) { + + if (mock) { + setupGetDatabaseInstancesWithLimitMock(hockInstance, provider); + } + + client.getInstances({ limit: 2 }, function (e, i, o) { + err = e; + instances = i; + offset = o; + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('should respond at least 2 elements', function() { + should.not.exist(err); + should.exist(instances); + instances.should.be.an.Array; + instances.should.have.length(2); + }); + + it('should pass as third argument the offset mark', function() { + should.exist(offset); + testContext.marker = offset; + }); + }); + }); + + describe('the destroyInstance() method', function() { + it('should respond correctly', function(done) { + + if (mock) { + setupDestroyInstanceMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + testContext.Instance = instance; + client.destroyInstance(testContext.Instance, function(err, result) { + should.not.exist(err); + should.exist(result); + result.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + + describe('the getInstance() method', function () { + it('should response with details', function (done) { + + if (mock) { + setGetInstanceMock(hockInstance, provider); + } + + client.getInstance(testContext.Instance.id, function (err, instance) { + should.not.exist(err); + should.exist(instance); + instance.should.be.instanceOf(Instance); + instance.id.should.equal(testContext.Instance.id); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + describe('the getInstances() method', function () { + it('with offset should respond less quantity', function (done) { + if(provider !== 'rackspace') { + return done(); + } + + if (mock) { + hockInstance + .get('/v1.0/123456/instances?marker=55041e91-98ab-4cd5-8148-f3b3978b3262') + .reply(200, helpers.loadFixture('rackspace/databaseInstanceOffset.json')); + } + + client.getInstances({ offset: testContext.marker }, function (err, instances, offset) { + should.not.exist(err); + should.exist(instances); + should.not.exist(offset); + instances.should.be.an.Array; + should.ok(instances.length >= 2 + && instances.length < testContext.instancesQuantity); + hockInstance && hockInstance.done(); + done(); + }); + + }); + + it('with limit and offset should respond just one result with more next points', function (done) { + if(provider !== 'rackspace') { + return done(); + } + + if (mock) { + hockInstance + .get('/v1.0/123456/instances?limit=1&marker=55041e91-98ab-4cd5-8148-f3b3978b3262') + .reply(200, helpers.loadFixture('rackspace/databaseInstanceLimitOffset.json')); + } + + client.getInstances({limit: 1, offset: testContext.marker }, function (err, instances, offset) { + should.not.exist(err); + should.exist(instances); + instances.should.be.an.Array; + should.exist(offset); + instances.should.have.length(1); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + describe('the setFlavor() method', function () { + it('without instance and flavor parameters should get errors', function (done) { + client.setFlavor(function (err) { + should.exist(err); + done(); + }); + }); + + it('without flavor parameter should get errors', function (done) { + + if (mock) { + setupGetInstancesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.setFlavor(instance, function (err) { + should.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('without instance parameter should get errors', function (done) { + + if (mock) { + setGetFlavorsMock(hockInstance, provider); + } + + client.getFlavor(2, function (err, flavor) { + should.not.exist(err); + should.exist(flavor); + + client.setFlavor(flavor, function(err) { + should.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('with correct inputs should respond correctly', function (done) { + + if (mock) { + setupSetFlavorMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + var newFlavor = (Number(instance.flavor.id) === 4) ? 1 : Number(instance.flavor.id) + 1; + client.getFlavor(newFlavor, function (err, flavor) { + should.not.exist(err); + should.exist(flavor); + client.setFlavor(instance, flavor, function (err) { + should.not.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + }); + + describe('the setVolumeSize() method', function() { + it('without instance and size parameters should get errors', function(done) { + client.setVolumeSize(function(err) { + should.exist(err); + done(); + }); + }); + + it('without size parameter should get errors', function (done) { + + if (mock) { + setupGetInstancesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.setVolumeSize(instance, function (err) { + should.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('without invalid size parameter should get errors', function (done) { + + if (mock) { + setupGetInstancesMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.setVolumeSize(instance, 12, function (err) { + should.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('with correct inputs should respond correctly', function (done) { + + if (mock) { + setupResizeMock (hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + var newSize = (Number(instance.volume.size) === 8) ? 1 : Number(instance.volume.size) + 1; + + client.setVolumeSize(instance, newSize, function (err) { + should.not.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + + describe('the create() method with errors', function () { + it('should respond with errors', function (done) { + client.createInstance(function(err) { + should.exist(err); + done(); + }); + }); + + it('without flavor should respond with errors', function (done) { + client.createInstance({ name: 'test-without-flavor' }, function (err) { + should.exist(err); + done(); + }); + }); + + it('with invalid size should respond with errors', function (done) { + if (mock) { + setupGetOneFlavorMock(hockInstance, provider); + } + + client.getFlavor(1, function (err, flavor) { + client.createInstance({ + name: 'test-instance', + flavor: flavor, + size: '1' + }, function(err) { + should.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + + if (provider === 'rackspace' || provider === 'openstack') { + describe('the enableRootUser() method', function() { + it('with no instance should return error', function (done) { + if (mock) { + setupEnableRootMock(hockInstance, provider); + } + client.enableRootUser(testContext.Instance.id, function (err, body) { + if (err) { + return done(err); + } + + should.exist(body); + body.should.have.property('user'); + done(); + }); + }); + + it('with valid instance should work', function (done) { + client.listRootStatus(testContext.Instance.id, function (err, body) { + if (err) { + return done(err); + } + + should.exist(body); + body.should.have.property('rootEnabled'); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + } + + describe('the restartInstance() method', function () { + it('with no instance should return error', function (done) { + client.restartInstance(function (err) { + should.exist(err); + done(); + }); + }); + + it('with valid instance should restart', function (done) { + if (mock) { + setupRestartInstanceMock (hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.restartInstance(instance, function (err) { + should.not.exist(err); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + }); + }); + +}); + +assertLinks = function (links) { + links.should.be.an.Array; + links.forEach(function (link) { + should.exist(link.href); + should.exist(link.rel); + }); +}; + +setupCreateInstanceMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/flavors/1') + .reply(200, helpers.loadFixture('rackspace/databaseFlavor1.json')) + .post('/v1.0/123456/instances', { + instance: { + name: 'test-instance', + flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/1', + databases: [{ + name: 'db1', + character_set: 'utf8', + collate: 'utf8_general_ci' + }], + volume: { + size:1 + } + } + }) + .reply(200, helpers.loadFixture('rackspace/createdDatabaseInstance.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/1') + .reply(200, helpers.loadFixture('openstack/databaseFlavor1.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances', { + instance: { + name: 'test-instance', + flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/1', + databases: [{ + name: 'db1', + character_set: 'utf8', + collate: 'utf8_general_ci' + }], + volume: { + size:1 + } + } + }) + .reply(200, helpers.loadFixture('rackspace/createdDatabaseInstance.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/flavors/1') + .reply(200, helpers.loadFixture('hp/databaseFlavor1.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances', { + instance: { + name: 'test-instance', + flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/1', + databases: [{ + name: 'db1', + character_set: 'utf8', + collate: 'utf8_general_ci' + }], + volume: { + size:1 + } + } + }) + .reply(200, helpers.loadFixture('rackspace/createdDatabaseInstance.json')); + } +}; + +setupGetInstancesMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')); + } +}; + +setupGetDatabaseInstancesWithLimitMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances?limit=2') + .reply(200, helpers.loadFixture('rackspace/databaseInstancesLimit2.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances?limit=2') + .reply(200, helpers.loadFixture('openstack/databaseInstancesLimit2.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances?limit=2') + .reply(200, helpers.loadFixture('hp/databaseInstancesLimit2.json')); + } +}; + +setupDestroyInstanceMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .delete('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') + .reply(202); + } +}; + +setGetInstanceMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') + .reply(200, helpers.loadFixture('rackspace/databaseInstance.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') + .reply(200, helpers.loadFixture('openstack/databaseInstance.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') + .reply(200, helpers.loadFixture('hp/databaseInstance.json')); + } +}; + +setGetFlavorsMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/flavors/2') + .reply(200, helpers.loadFixture('rackspace/databaseFlavor2.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/2') + .reply(200, helpers.loadFixture('openstack/databaseFlavor2.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/flavors/2') + .reply(200, helpers.loadFixture('hp/databaseFlavor2.json')); + } +}; + +setupSetFlavorMock = function (hockInstance, provider) { + if (provider ==='rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/flavors/2') + .reply(200, helpers.loadFixture('rackspace/databaseFlavor2.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { + resize: { + flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/2' + } + }) + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/2') + .reply(200, helpers.loadFixture('openstack/databaseFlavor2.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { + resize: { + flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/2' + } + }) + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/flavors/2') + .reply(200, helpers.loadFixture('hp/databaseFlavor2.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { + resize: { + flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/2' + } + }) + .reply(202); + } +}; + +setupResizeMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { + resize: { + volume :{ + size :2 + } + } + }) + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { + resize: { + volume :{ + size :2 + } + } + }) + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { + resize: { + volume :{ + size :2 + } + } + }) + .reply(202); + } +}; + +setupGetOneFlavorMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/flavors/1') + .reply(200, helpers.loadFixture('rackspace/databaseFlavor1.json')); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/1') + .reply(200, helpers.loadFixture('openstack/databaseFlavor1.json')); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/flavors/1') + .reply(200, helpers.loadFixture('hp/databaseFlavor1.json')); + } +}; + +setupRestartInstanceMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { restart :{}}) + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { restart :{}}) + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { restart :{}}) + .reply(202); + } +}; + +setupEnableRootMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + user: { + name: 'root', + password: '12345' + } + }) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + rootEnabled: true + }); + } else if (provider === 'openstack') { + hockInstance + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + user: { + name: 'root', + password: '12345' + } + }) + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + rootEnabled: true + }); + } +}; diff --git a/test/common/databases/users-test.js b/test/common/databases/users-test.js new file mode 100644 index 000000000..c2e990106 --- /dev/null +++ b/test/common/databases/users-test.js @@ -0,0 +1,763 @@ +/* + * users-test.js: Tests for Openstack Trove users within an instace + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + helpers = require('../../helpers'), + User = require('../../../lib/pkgcloud/openstack/database/user').User, + providers = require('../../configs/providers.json'), + mock = !!process.env.MOCK; + +// Declaring variables for helper functions defined later +var setupAuthenticationMock, setupCreateUserMock, setupCreateAnotherUserMock, + setupCreateMultiplsUsersMock, setupCreateUsersWithRestrictedCharacters, + setupGetUsersMock, setupEnableRootMock, setupEnableRootMockWithStatus, + setupDestroyUsersMock, setupDestroyUsersMockWithPagination; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].database && provider !== 'azure'; +}).forEach(function (provider) { +describe('pkgcloud/['+provider+']/databases/users', function () { + var client, authHockInstance, hockInstance, authServer, server; + + describe('The pkgcloud '+provider+' Database client', function () { + + before(function (done) { + client = helpers.createClient(provider, 'database'); + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the createUser() method should respond correctly', function (done) { + if (mock) { + setupAuthenticationMock(authHockInstance, hockInstance, provider); + setupCreateUserMock(authHockInstance, hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createUser({ + username: 'joeTest', + password: 'joepasswd', + database: 'TestDatabase', + instance: instance + }, function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + }); + + it('the createUser() method should work with databases argument', function (done) { + if (mock) { + setupCreateUserMock(authHockInstance, hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createUser({ + username: 'joeTest', + password: 'joepasswd', + databases: ['TestDatabase'], + instance: instance + }, function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + }); + + it('create an other user for test pagination should response correctly', function (done) { + + if (mock) { + setupCreateAnotherUserMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createUser({ + username: 'joeTestTwo', + password: 'joepasswd', + database: 'TestDatabase', + instance: instance + }, function () { + client.createUser({ + username: 'joeTestThree', + password: 'joepasswd', + database: 'TestDatabase', + instance: instance + }, function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + + it('create multiple users in one request should response correctly', function (done) { + + if (mock) { + setupCreateMultiplsUsersMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createUser([ + { + username: 'joeTestFour', + password: 'joepasswd', + database: 'TestDatabase', + instance: instance + }, + { + username: 'joeTestFive', + password: 'joepasswd', + database: 'TestDatabase', + instance: instance + } + ], function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('create users with questionable characters should respond with error', function (done) { + + if (mock) { + setupCreateUsersWithRestrictedCharacters(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.createUser({ + username: '@joeTestSix', + password: 'joepasswd', + database: 'TestDatabase', + instance: instance + }, function (err, response) { + should.exist(err); + should.not.exist(response); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getUsers() method should get the list of users', function (done) { + + if (mock) { + setupGetUsersMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.getUsers({ instance: instance }, function (err, list) { + should.not.exist(err); + should.exist(list); + list.should.be.an.Array; + list.forEach(function (user) { + user.should.be.instanceOf(User); + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + describe('the destroyUsers() method', function() { + it('should respond correctly', function(done) { + + if (mock) { + setupDestroyUsersMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.destroyUser(instance, 'joeTest', function(err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('should destroy the user used for pagination', function(done) { + + if (mock) { + setupDestroyUsersMockWithPagination(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.destroyUser(instance, 'joeTestTwo', function (err, response) { + should.not.exist(err); + should.exist(response); + response.statusCode.should.equal(202); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + + it('the enableRoot() method should respond correctly', function(done) { + + if (mock) { + setupEnableRootMock(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.enableRoot(instance, function(err, user, response) { + should.not.exist(err); + should.exist(user); + should.exist(response); + response.statusCode.should.equal(200); + should.exist(response.body); + response.body.user.should.be.a.Object; + should.exist(response.body.user.password); + response.body.user.name.should.equal('root'); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the enableRoot() method should respond correctly', function (done) { + + if (mock) { + setupEnableRootMockWithStatus(hockInstance, provider); + } + + helpers.selectInstance(client, function (instance) { + client.rootEnabled(instance, function (err, root, response) { + should.not.exist(err); + should.exist(root); + should.exist(response); + response.statusCode.should.equal(200); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + }); +}); +}); + +setupAuthenticationMock = function (authHockInstance, hockInstance, provider) { + if (provider === 'rackspace') { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + } + else if (provider === 'openstack') { + authHockInstance + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + } + else if (provider === 'hp') { + authHockInstance.post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + } + else if (provider === 'openstack') { + authHockInstance.post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + } + else { + throw new Error('not supported'); + } +}; + +setupCreateUserMock = function (authHockInstance, hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTest', + password: 'joepasswd', + databases: [ { name: 'TestDatabase' } ] + } + ] + }) + .reply(202); + } + else if ( provider === 'openstack' ){ + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTest', + password: 'joepasswd', + databases: [ { name: 'TestDatabase' } ] + } + ] + }) + .reply(202); + } + else if ( provider === 'hp' ){ + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTest', + password: 'joepasswd', + databases: [ { name: 'TestDatabase' } ] + } + ] + }) + .reply(202); + } + else { + throw new Error('not supported'); + } +}; + +setupCreateAnotherUserMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestTwo', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestThree', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestTwo', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestThree', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestTwo', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestThree', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202); + } + else { + throw new Error('not supported'); + } +}; + +setupCreateMultiplsUsersMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestFour', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + }, + { + name: 'joeTestFive', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202); + } + else if ( provider === 'hp' ){ + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestFour', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + }, + { + name: 'joeTestFive', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202); + } + else if ( provider === 'openstack' ){ + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { + users: [ + { + name: 'joeTestFour', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + }, + { + name: 'joeTestFive', + password: 'joepasswd', + databases: [ + { name: 'TestDatabase' } + ] + } + ] + }) + .reply(202); + } + else { + throw new Error('not supported'); + } +}; + +setupCreateUsersWithRestrictedCharacters = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')); + } + else if ( provider === 'openstack' ){ + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')); + } + else if ( provider === 'hp' ){ + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')); + } + else { + throw new Error('not supported'); + } +}; + +setupGetUsersMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users') + .reply(200, helpers.loadFixture('rackspace/databaseUsers.json')); + } + else if ( provider === 'openstack' ){ + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users') + .reply(200, helpers.loadFixture('openstack/databaseUsers.json')); + } + else if ( provider === 'hp' ){ + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users') + .reply(200, helpers.loadFixture('hp/databaseUsers.json')); + } + else { + throw new Error('not supported'); + } + +}; + +setupEnableRootMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + user: { + password: 'dbba235b-d078-42ec-b992-dec1464c49cc', + name: 'root' + } + }); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + user: { + password: 'dbba235b-d078-42ec-b992-dec1464c49cc', + name: 'root' + } + }); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .post('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { + user: { + password: 'dbba235b-d078-42ec-b992-dec1464c49cc', + name: 'root' + } + }); + } +}; + +setupEnableRootMockWithStatus = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { rootEnabled: true }); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { rootEnabled: true }); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') + .reply(200, { rootEnabled: true }); + } +}; + +setupDestroyUsersMock = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTest') + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTest') + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .delete('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTest') + .reply(202); + } +}; + +setupDestroyUsersMockWithPagination = function (hockInstance, provider) { + if (provider === 'rackspace') { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTestTwo') + .reply(202); + } + else if (provider === 'openstack') { + hockInstance + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/instances') + .reply(200, helpers.loadFixture('openstack/databaseInstances.json')) + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTestTwo') + .reply(202); + } + else if (provider === 'hp') { + hockInstance + .get('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances') + .reply(200, helpers.loadFixture('hp/databaseInstances.json')) + .delete('/v1.0/5ACED3DC3AA740ABAA41711243CC6949/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTestTwo') + .reply(202); + } +}; diff --git a/test/common/network/base-test.js b/test/common/network/base-test.js new file mode 100644 index 000000000..39b24a42e --- /dev/null +++ b/test/common/network/base-test.js @@ -0,0 +1,21 @@ +/* + * base-test.js: Test that should be common to all providers. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var should = require('should'), + helpers = require('../../helpers'), + providers = require('../../configs/providers.json'); + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function(provider) { + describe('pkgcloud/common/network/base [' + provider + ']', function () { + it('provider should implement networking client', function () { + var networkClient = helpers.createClient(provider, 'network'); + should.exist(networkClient); + }); + }); + }); diff --git a/test/common/network/network-test.js b/test/common/network/network-test.js new file mode 100644 index 000000000..cf7f643fc --- /dev/null +++ b/test/common/network/network-test.js @@ -0,0 +1,486 @@ +/* +* network-test.js: Test that should be common to all providers. +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + async = require('async'), + helpers = require('../../helpers'), + http = require('http'), + hock = require('hock'), + _ = require('lodash'), + providers = require('../../configs/providers.json'), + Network = require('../../../lib/pkgcloud/core/network/network').Network, + mock = !!process.env.MOCK, + urlJoin = require('url-join'); + +// Declaring variables for helper functions defined later +var setupDestroyNetworkMock, setupUpdateNetworkMock, setupModelDestroyedNetworkMock, + setupNetworksMock, setupNetworkMock, setupRefreshNetworkMock, + setupNetworkModelCreateMock, setupGetNetworkMock; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function (provider) { + describe('pkgcloud/common/network/networks [' + provider + ']', function () { + + var client = helpers.createClient(provider, 'network'), + context = {}, + authServer, server, + authHockInstance, hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getNetworks() function should return a list of networks', function(done) { + + if (mock) { + setupNetworksMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getNetworks(function (err, networks) { + should.not.exist(err); + should.exist(networks); + + context.networks = networks; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the getNetwork() method should get a network instance', function (done) { + if (mock) { + setupGetNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getNetwork(context.networks[0].id, function (err, network) { + should.not.exist(err); + should.exist(network); + network.should.be.an.instanceOf(Network); + network.should.have.property('id', context.networks[0].id); + context.currentNetwork = network; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + + }); + }); + + it('the createNetwork() method should create a network', function (done) { + if (mock) { + setupNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.createNetwork(_.extend({ + name: 'create-test-ids2' + }), function (err, network) { + should.not.exist(err); + should.exist(network); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the destroyNetwork() method should delete a network', function (done) { + if (mock) { + setupDestroyNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentNetwork); + } + + client.destroyNetwork(context.currentNetwork, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the destroyNetwork() method should take an id, delete a network', function (done) { + if (mock) { + setupDestroyNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentNetwork); + } + + client.destroyNetwork(context.currentNetwork.id, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the updateNetwork() method should update a network', function (done) { + + var networkToUpdate = context.currentNetwork; + networkToUpdate.adminStateUp = false; + + if (mock) { + setupUpdateNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, networkToUpdate); + } + + client.updateNetwork(networkToUpdate, function(err,network){ + should.not.exist(err); + should.exist(network); + done(); + }); + }); + + it('the network.create() method should create a network', function (done) { + if (mock) { + setupNetworkModelCreateMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + var network = new Network(client); + network.name = 'model created network'; + network.create(function (err, createdNetwork) { + should.not.exist(err); + should.exist(createdNetwork); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the network.refresh() method should get a network', function (done) { + var network = new Network(client); + network.id = 'd32019d3-bc6e-4319-9c1d-6722fc136a22'; + + if (mock) { + setupRefreshNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, network); + } + + network.refresh(function (err, refreshedNetwork) { + should.not.exist(err); + should.exist(refreshedNetwork); + refreshedNetwork.should.have.property('name', 'private-network'); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the network.destroy() method should delete a network', function (done) { + var network = new Network(client); + network.name = 'model deleted network'; + network.id = 'THISISANETWORKID'; + + if (mock) { + setupModelDestroyedNetworkMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, network); + } + + network.destroy(function (err) { + should.not.exist(err); + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + }); +}); + +setupDestroyNetworkMock = function (client, provider, servers, currentNetwork){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks', currentNetwork.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks', currentNetwork.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/networks', currentNetwork.id)) + .reply(204); + } +}; + +setupUpdateNetworkMock = function (client, provider, servers, currentNetwork){ + if (provider === 'openstack') { + servers.server + .put(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks', currentNetwork.id), { + network: { + admin_state_up: false, + name: 'private-network', + shared: true, + tenant_id: '4fd44f30292945e481c7b8a0c8908869' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'hp') { + servers.server + .put(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks', currentNetwork.id), { + network: { + admin_state_up: false, + name: 'private-network', + shared: true, + tenant_id: '4fd44f30292945e481c7b8a0c8908869' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'rackspace') { + servers.server + .put(urlJoin('/v2.0/networks', currentNetwork.id), { + network: { + admin_state_up: false, + name: 'private-network', + shared: true, + tenant_id: '4fd44f30292945e481c7b8a0c8908869' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/network.json'); + } +}; + +setupModelDestroyedNetworkMock = function (client, provider, servers, currentNetwork){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks', currentNetwork.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks', currentNetwork.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/networks', currentNetwork.id)) + .reply(204); + } +}; + +setupNetworksMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/networks.json'); + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/networks.json'); + } + else if (provider === 'rackspace') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + + servers.server + .get('/v2.0/networks') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/networks.json'); + } +}; + +setupNetworkMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks', { + network: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks', { + network: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/networks', { + network: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/network.json'); + } +}; + +setupRefreshNetworkMock = function (client, provider, servers, network) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks',network.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks',network.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/networks',network.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/network.json'); + } +}; + +setupNetworkModelCreateMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks', { + network: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks', { + network: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/networks', { + network: { + name: 'model created network' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/network.json'); + } +}; + +setupGetNetworkMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'hp') { + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/network.json'); + } + else if (provider === 'rackspace') { + servers.server + .get('/v2.0/networks/d32019d3-bc6e-4319-9c1d-6722fc136a22') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/network.json'); + } +}; diff --git a/test/common/network/port-test.js b/test/common/network/port-test.js new file mode 100644 index 000000000..55cb5e4b3 --- /dev/null +++ b/test/common/network/port-test.js @@ -0,0 +1,511 @@ +/* +* port-test.js: Test that should be common to all providers. +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + async = require('async'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + providers = require('../../configs/providers.json'), + Port = require('../../../lib/pkgcloud/core/network/port').Port, + mock = !!process.env.MOCK, + urlJoin = require('url-join'); + +// Declaring variables for helper functions defined later +var setupDestroyPortMock, setupUpdatePortMock, setupModelDestroyedPortMock, + setupPortsMock, setupCreatePortMock, setupRefreshPortMock, + setupPortModelCreateMock, setupGetPortMock; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function (provider) { + describe('pkgcloud/common/network/ports [' + provider + ']', function () { + + var client = helpers.createClient(provider, 'network'), + context = {}, + authServer, server, + authHockInstance, hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getPorts() function should return a list of ports', function(done) { + + if (mock) { + setupPortsMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getPorts(function (err, ports) { + should.not.exist(err); + should.exist(ports); + + context.ports = ports; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the getPort() method should get a port instance', function (done) { + if (mock) { + setupGetPortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.ports[0]); + } + + client.getPort(context.ports[0].id, function (err, port) { + should.not.exist(err); + should.exist(port); + port.should.be.an.instanceOf(Port); + port.should.have.property('id', context.ports[0].id); + context.currentPort = port; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + + }); + }); + + it('the createPort() method should create a port', function (done) { + if (mock) { + setupCreatePortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.createPort({ + name: 'create-test-ids2' + }, function (err, port) { + should.not.exist(err); + should.exist(port); + port.should.be.an.instanceOf(Port); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the destroyPort() method should delete a port', function (done) { + if (mock) { + setupDestroyPortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentPort); + } + + client.destroyPort(context.currentPort, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the destroyPort() method should take an id, delete a port', function (done) { + if (mock) { + setupDestroyPortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentPort); + } + + client.destroyPort(context.currentPort.id, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the updatePort() method should update a port', function (done) { + + var portToUpdate = context.currentPort; + portToUpdate.adminStateUp = false; + + if (mock) { + setupUpdatePortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, portToUpdate); + } + + client.updatePort(portToUpdate, function(err,network){ + should.not.exist(err); + should.exist(network); + done(); + }); + }); + + it('the port.create() method should create a port', function (done) { + if (mock) { + setupPortModelCreateMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + var port = new Port(client); + port.name= 'model created network'; + port.create(function (err, createdPort) { + should.not.exist(err); + should.exist(createdPort); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the port.refresh() method should get a network', function (done) { + var port = new Port(client); + port.id = context.ports[0].id; + + if (mock) { + setupRefreshPortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, port); + } + + port.refresh(function (err, refreshedPort) { + should.not.exist(err); + should.exist(refreshedPort); + refreshedPort.should.have.property('name', 'my_port'); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the port.destroy() method should delete a port', function (done) { + var port = new Port(client); + port.name = 'model deleted port'; + port.id = 'THISISANETWORKID'; + + if (mock) { + setupModelDestroyedPortMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, port); + } + + port.destroy(function (err) { + should.not.exist(err); + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + }); +}); + +setupDestroyPortMock = function (client, provider, servers, currentPort){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', currentPort.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', currentPort.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/ports', currentPort.id)) + .reply(204); + } +}; + +setupUpdatePortMock = function (client, provider, servers, currentPort){ + if (provider === 'openstack') { + servers.server + .put(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', currentPort.id), { + port: { + status: 'ACTIVE', + name: 'my_port', + admin_state_up: false, + mac_address: 'fa:16:3e:58:42:ed', + fixed_ips: [ + { + subnet_id: '008ba151-0b8c-4a67-98b5-0d2b87666062', + ip_address: '172.24.4.2' + } + ], + security_groups:[], + network_id: '70c1db1f-b701-45bd-96e0-a313ee3430b3' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'hp') { + servers.server + .put(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', currentPort.id), { + port: { + status: 'ACTIVE', + name: 'my_port', + admin_state_up: false, + mac_address: 'fa:16:3e:58:42:ed', + fixed_ips: [ + { + subnet_id: '008ba151-0b8c-4a67-98b5-0d2b87666062', + ip_address: '172.24.4.2' + } + ], + security_groups:[], + network_id: '70c1db1f-b701-45bd-96e0-a313ee3430b3' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'rackspace') { + servers.server + .put(urlJoin('/v2.0/ports', currentPort.id), { + port: { + status: 'ACTIVE', + name: 'my_port', + admin_state_up: false, + mac_address: 'fa:16:3e:58:42:ed', + fixed_ips: [ + { + subnet_id: '008ba151-0b8c-4a67-98b5-0d2b87666062', + ip_address: '172.24.4.2' + } + ], + security_groups:[], + network_id: '70c1db1f-b701-45bd-96e0-a313ee3430b3' + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/port.json'); + } +}; + +setupModelDestroyedPortMock = function (client, provider, servers, currentPort){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', currentPort.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', currentPort.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/ports', currentPort.id)) + .reply(204); + } +}; + +setupPortsMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/ports.json'); + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/ports.json'); + } + else if (provider === 'rackspace') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + + servers.server + .get('/v2.0/ports') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/ports.json'); + } +}; + +setupCreatePortMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', { + port: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', { + port: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/ports', { + port: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/port.json'); + } +}; + +setupRefreshPortMock = function (client, provider, servers, port) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', port.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', port.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/ports', port.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/port.json'); + } +}; + +setupPortModelCreateMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', { + port: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', { + port: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/ports', { + port: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/rackspace/port.json'); + } +}; + +setupGetPortMock = function (client, provider, servers, currentPort) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/ports', currentPort.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/ports', currentPort.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/port.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/ports', currentPort.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/port.json'); + } +}; diff --git a/test/common/network/security-group-rule-test.js b/test/common/network/security-group-rule-test.js new file mode 100644 index 000000000..870009e5f --- /dev/null +++ b/test/common/network/security-group-rule-test.js @@ -0,0 +1,434 @@ +/* + * security-group-rule-test.js: Test for Networking (Neutron)'s security group rules + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + providers = require('../../configs/providers.json'), + SecurityGroupRule = require('../../../lib/pkgcloud/core/network/securityGroupRule').SecurityGroupRule, + mock = !!process.env.MOCK, + urlJoin = require('url-join'); + +// Declaring variables for helper functions defined later +var setupSecurityGroupRulesMock, setupGetSecurityGroupRuleMock, setupCreateSecurityGroupRuleMock, + setupSecurityGroupRuleModelCreateMock, setupRefreshSecurityGroupRuleMock, + setupModelDestroyedSecurityGroupRuleMock, setupDestroySecurityGroupRuleMock; + +providers.filter(function(provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function(provider) { + + describe('pkgcloud/common/network/security-group-rules [' + provider + ']', function() { + + var client = helpers.createClient(provider, 'network'), + context = {}, + authServer, server, + authHockInstance, hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getSecurityGroupRules() function should return a list of security group rules', function(done) { + + if (mock) { + setupSecurityGroupRulesMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getSecurityGroupRules(function (err, securityGroupRules) { + should.not.exist(err); + should.exist(securityGroupRules); + + context.securityGroupRules = securityGroupRules; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the getSecurityGroupRule() method should get a security group rule instance', function (done) { + + if (mock) { + setupGetSecurityGroupRuleMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + },context.securityGroupRules[0]); + } + + client.getSecurityGroupRule(context.securityGroupRules[0].id, function (err, securityGroupRule) { + should.not.exist(err); + should.exist(securityGroupRule); + securityGroupRule.should.be.an.instanceOf(SecurityGroupRule); + securityGroupRule.should.have.property('id', context.securityGroupRules[0].id); + context.currentSecurityGroupRule = securityGroupRule; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + + }); + }); + + it('the createSecurityGroupRule() method should create a security group rule', function (done) { + if (mock) { + setupCreateSecurityGroupRuleMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.createSecurityGroupRule({ + direction: 'ingress' + }, function (err, securityGroupRule) { + should.not.exist(err); + should.exist(securityGroupRule); + securityGroupRule.should.be.an.instanceOf(SecurityGroupRule); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the destroySecurityGroupRule() method should delete a security group rule', function (done) { + if (mock) { + setupDestroySecurityGroupRuleMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentSecurityGroupRule); + } + + client.destroySecurityGroupRule(context.currentSecurityGroupRule, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the destroySecurityGroupRule() method should take an id, delete a security group rule', function (done) { + if (mock) { + setupDestroySecurityGroupRuleMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentSecurityGroupRule); + } + + client.destroySecurityGroupRule(context.currentSecurityGroupRule.id, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the securityGroupRule.create() method should create a security group rule', function (done) { + if (mock) { + setupSecurityGroupRuleModelCreateMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + var securityGroupRule = new SecurityGroupRule(client); + securityGroupRule.direction = 'ingress'; + securityGroupRule.create(function (err, createdSecurityGroupRule) { + should.not.exist(err); + should.exist(createdSecurityGroupRule); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the securityGroupRule.refresh() method should get a security group rule', function (done) { + var securityGroupRule = new SecurityGroupRule(client); + securityGroupRule.id = 'd32019d3-bc6e-4319-9c1d-6722fc136a22'; + + if (mock) { + setupRefreshSecurityGroupRuleMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, securityGroupRule); + } + + securityGroupRule.refresh(function (err, refreshedSecurityGroupRule) { + should.not.exist(err); + should.exist(refreshedSecurityGroupRule); + refreshedSecurityGroupRule.should.have.property('direction', 'ingress'); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the securityGroupRule.destroy() method should delete a security group rule', function (done) { + var securityGroupRule = new SecurityGroupRule(client); + securityGroupRule.name = 'model deleted securityGroupRule'; + securityGroupRule.id = 'THISISASECURITYGROUPRULEID'; + + if (mock) { + setupModelDestroyedSecurityGroupRuleMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, securityGroupRule); + } + + securityGroupRule.destroy(function (err) { + should.not.exist(err); + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + }); +}); + +setupSecurityGroupRulesMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroupRules.json'); + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroupRules.json'); + } + else if (provider === 'rackspace') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + + servers.server + .get('/v2.0/security-group-rules') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/securityGroupRules.json'); + } +}; + +setupCreateSecurityGroupRuleMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules', { + security_group_rule: { + direction: 'ingress' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules', { + security_group_rule: { + direction: 'ingress' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/security-group-rules', { + security_group_rule: { + direction: 'ingress' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/securityGroupRule.json'); + } +}; + +setupRefreshSecurityGroupRuleMock = function (client, provider, servers, securityGroupRule) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules', securityGroupRule.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules', securityGroupRule.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/security-group-rules', securityGroupRule.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/securityGroupRule.json'); + } +}; + +setupSecurityGroupRuleModelCreateMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules', { + security_group_rule: { + direction: 'ingress' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules', { + security_group_rule: { + direction: 'ingress' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/security-group-rules', { + security_group_rule: { + direction: 'ingress' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/securityGroupRule.json'); + } +}; + +setupGetSecurityGroupRuleMock = function (client, provider, servers, currentSecurityGroupRule) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroupRule.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/securityGroupRule.json'); + } +}; + +setupDestroySecurityGroupRuleMock = function (client, provider, servers, currentSecurityGroupRule){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .reply(204); + } +}; + +setupModelDestroyedSecurityGroupRuleMock = function (client, provider, servers, currentSecurityGroupRule){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/security-group-rules', currentSecurityGroupRule.id)) + .reply(204); + } +}; + diff --git a/test/common/network/security-group-test.js b/test/common/network/security-group-test.js new file mode 100644 index 000000000..b9584d399 --- /dev/null +++ b/test/common/network/security-group-test.js @@ -0,0 +1,434 @@ +/* + * security-group-test.js: Test for Networking (Neutron)'s security groups + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + providers = require('../../configs/providers.json'), + SecurityGroup = require('../../../lib/pkgcloud/core/network/securityGroup').SecurityGroup, + mock = !!process.env.MOCK, + urlJoin = require('url-join'); + +// Declaring variables for helper functions defined later +var setupSecurityGroupsMock, setupGetSecurityGroupMock, setupCreateSecurityGroupMock, + setupSecurityGroupModelCreateMock, setupRefreshSecurityGroupMock, + setupModelDestroyedSecurityGroupMock, setupDestroySecurityGroupMock; + +providers.filter(function(provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function(provider) { + + describe('pkgcloud/common/network/security-groups [' + provider + ']', function() { + + var client = helpers.createClient(provider, 'network'), + context = {}, + authServer, server, + authHockInstance, hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getSecurityGroups() function should return a list of security groups', function(done) { + + if (mock) { + setupSecurityGroupsMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getSecurityGroups(function (err, securityGroups) { + should.not.exist(err); + should.exist(securityGroups); + + context.securityGroups = securityGroups; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the getSecurityGroup() method should get a security group instance', function (done) { + + if (mock) { + setupGetSecurityGroupMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + },context.securityGroups[0]); + } + + client.getSecurityGroup(context.securityGroups[0].id, function (err, securityGroup) { + should.not.exist(err); + should.exist(securityGroup); + securityGroup.should.be.an.instanceOf(SecurityGroup); + securityGroup.should.have.property('id', context.securityGroups[0].id); + context.currentSecurityGroup = securityGroup; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + + }); + }); + + it('the createSecurityGroup() method should create a security group', function (done) { + if (mock) { + setupCreateSecurityGroupMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.createSecurityGroup({ + name: 'create-test-ids2' + }, function (err, securityGroup) { + should.not.exist(err); + should.exist(securityGroup); + securityGroup.should.be.an.instanceOf(SecurityGroup); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the destroySecurityGroup() method should delete a security group', function (done) { + if (mock) { + setupDestroySecurityGroupMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentSecurityGroup); + } + + client.destroySecurityGroup(context.currentSecurityGroup, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the destroySecurityGroup() method should take an id, delete a security group', function (done) { + if (mock) { + setupDestroySecurityGroupMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentSecurityGroup); + } + + client.destroySecurityGroup(context.currentSecurityGroup.id, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the securityGroup.create() method should create a security group', function (done) { + if (mock) { + setupSecurityGroupModelCreateMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + var securityGroup = new SecurityGroup(client); + securityGroup.name= 'model created security group'; + securityGroup.create(function (err, createdSecurityGroup) { + should.not.exist(err); + should.exist(createdSecurityGroup); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the securityGroup.refresh() method should get a security group', function (done) { + var securityGroup = new SecurityGroup(client); + securityGroup.id = 'd32019d3-bc6e-4319-9c1d-6722fc136a22'; + + if (mock) { + setupRefreshSecurityGroupMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, securityGroup); + } + + securityGroup.refresh(function (err, refreshedSecurityGroup) { + should.not.exist(err); + should.exist(refreshedSecurityGroup); + refreshedSecurityGroup.should.have.property('name', 'default'); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the securityGroup.destroy() method should delete a security group', function (done) { + var securityGroup = new SecurityGroup(client); + securityGroup.name = 'model deleted securityGroup'; + securityGroup.id = 'THISISASECURITYGROUPID'; + + if (mock) { + setupModelDestroyedSecurityGroupMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, securityGroup); + } + + securityGroup.destroy(function (err) { + should.not.exist(err); + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + }); +}); + +setupSecurityGroupsMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroups.json'); + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroups.json'); + } + else if (provider === 'rackspace') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + + servers.server + .get('/v2.0/security-groups') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/securityGroups.json'); + } +}; + +setupCreateSecurityGroupMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups', { + security_group: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups', { + security_group: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/security-groups', { + security_group: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/securityGroup.json'); + } +}; + +setupRefreshSecurityGroupMock = function (client, provider, servers, securityGroup) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups', securityGroup.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups', securityGroup.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/security-groups', securityGroup.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/securityGroup.json'); + } +}; + +setupSecurityGroupModelCreateMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups', { + security_group: { + name: 'model created security group' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups', { + security_group: { + name: 'model created security group' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/security-groups', { + security_group: { + name: 'model created security group' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/securityGroup.json'); + } +}; + +setupGetSecurityGroupMock = function (client, provider, servers, currentSecurityGroup) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups', currentSecurityGroup.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups', currentSecurityGroup.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/securityGroup.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/security-groups', currentSecurityGroup.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/securityGroup.json'); + } +}; + +setupDestroySecurityGroupMock = function (client, provider, servers, currentSecurityGroup){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups', currentSecurityGroup.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups', currentSecurityGroup.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/security-groups', currentSecurityGroup.id)) + .reply(204); + } +}; + +setupModelDestroyedSecurityGroupMock = function (client, provider, servers, currentSecurityGroup){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/security-groups', currentSecurityGroup.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/security-groups', currentSecurityGroup.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/security-groups', currentSecurityGroup.id)) + .reply(204); + } +}; + diff --git a/test/common/network/signature-test.js b/test/common/network/signature-test.js new file mode 100644 index 000000000..843cef022 --- /dev/null +++ b/test/common/network/signature-test.js @@ -0,0 +1,41 @@ +/* + * signature-test.js: Test that shared methods meet some expectations for arguments. + * + * (C) 2014 Hewlett-Packard Development Company, L.P. + * + */ + +var should = require('should'), + providers = require('../../configs/providers.json'), + helpers = require('../../helpers'); + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function (provider) { + + describe('pkgcloud/common/network/signatures [' + provider + ']', function () { + + var client = helpers.createClient(provider, 'network'); + + it('client.getNetworks should take 2 arguments', function () { + client.getNetworks.should.be.a.Function; + client.getNetworks.should.have.length(2); + }); + + it('client.getNetwork should take 2 arguments', function () { + client.getNetwork.should.be.a.Function; + client.getNetwork.should.have.length(2); + }); + + it('client.getNetworks should take at least 1 argument', function () { + client.getNetworks.should.be.a.Function; + should.ok(client.getNetworks.length >= 1); + }); + + it('client.createNetwork should take 2 arguments', function () { + client.createNetwork.should.be.a.Function; + client.createNetwork.should.have.length(2); + }); + + }); +}); diff --git a/test/common/network/subnet-test.js b/test/common/network/subnet-test.js new file mode 100644 index 000000000..80ccff35b --- /dev/null +++ b/test/common/network/subnet-test.js @@ -0,0 +1,514 @@ +/* +* subnet-test.js: Test that should be common to all providers. +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + async = require('async'), + helpers = require('../../helpers'), + http = require('http'), + hock = require('hock'), + providers = require('../../configs/providers.json'), + Subnet = require('../../../lib/pkgcloud/core/network/subnet').Subnet, + mock = !!process.env.MOCK, + urlJoin = require('url-join'); + +// Declaring variables for helper functions defined later +var setupDestroySubnetMock, setupUpdateSubnetMock, setupModelDestroyedSubnetMock, + setupSubnetsMock, setupCreateSubnetMock, setupRefreshSubnetMock, + setupSubnetModelCreateMock, setupGetSubnetMock; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].network; +}).forEach(function (provider) { + describe('pkgcloud/common/network/subnets [' + provider + ']', function () { + + var client = helpers.createClient(provider, 'network'), + context = {}, + authServer, server, + authHockInstance, hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getSubnets() function should return a list of subnets', function(done) { + + if (mock) { + setupSubnetsMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getSubnets(function (err, subnets) { + should.not.exist(err); + should.exist(subnets); + + context.subnets = subnets; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the getSubnet() method should get a subnet instance', function (done) { + if (mock) { + setupGetSubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + },context.subnets[0]); + } + + client.getSubnet(context.subnets[0].id, function (err, subnet) { + should.not.exist(err); + should.exist(subnet); + subnet.should.be.an.instanceOf(Subnet); + subnet.should.have.property('id', context.subnets[0].id); + context.currentSubnet = subnet; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + + }); + }); + + it('the createSubnet() method should create a subnet', function (done) { + if (mock) { + setupCreateSubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.createSubnet({ + name: 'create-test-ids2' + }, function (err, subnet) { + should.not.exist(err); + should.exist(subnet); + subnet.should.be.an.instanceOf(Subnet); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the destroySubnet() method should delete a subnet', function (done) { + if (mock) { + setupDestroySubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentSubnet); + } + + client.destroySubnet(context.currentSubnet, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the destroySubnet() method should take an id, delete a subnet', function (done) { + if (mock) { + setupDestroySubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, context.currentSubnet); + } + + client.destroySubnet(context.currentSubnet.id, function (err) { + should.not.exist(err); + done(); + }); + }); + + it('the updateSubnet() method should update a subnet', function (done) { + + var subnetToUpdate = context.currentSubnet; + subnetToUpdate.enableDhcp = false; + + if (mock) { + setupUpdateSubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, subnetToUpdate); + } + + client.updateSubnet(subnetToUpdate, function(err,network){ + should.not.exist(err); + should.exist(network); + done(); + }); + }); + + it('the subnet.create() method should create a subnet', function (done) { + if (mock) { + setupSubnetModelCreateMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }); + } + + var subnet = new Subnet(client); + subnet.name= 'model created network'; + subnet.create(function (err, createdSubnet) { + should.not.exist(err); + should.exist(createdSubnet); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the subnet.refresh() method should get a network', function (done) { + var subnet = new Subnet(client); + subnet.id = 'd32019d3-bc6e-4319-9c1d-6722fc136a22'; + + if (mock) { + setupRefreshSubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, subnet); + } + + subnet.refresh(function (err, refreshedSubnet) { + should.not.exist(err); + should.exist(refreshedSubnet); + refreshedSubnet.should.have.property('name', 'my_subnet'); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the subnet.destroy() method should delete a subnet', function (done) { + var subnet = new Subnet(client); + subnet.name = 'model deleted subnet'; + subnet.id = 'THISISANETWORKID'; + + if (mock) { + setupModelDestroyedSubnetMock(client, provider, { + authServer: authHockInstance, + server: hockInstance + }, subnet); + } + + subnet.destroy(function (err) { + should.not.exist(err); + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + }); +}); + +setupDestroySubnetMock = function (client, provider, servers, currentSubnet){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', currentSubnet.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', currentSubnet.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/subnets', currentSubnet.id)) + .reply(204); + } +}; + +setupUpdateSubnetMock = function (client, provider, servers, currentSubnet){ + if (provider === 'openstack') { + servers.server + .put(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', currentSubnet.id), { + subnet: { + name: 'my_subnet', + network_id: 'd32019d3-bc6e-4319-9c1d-6722fc136a22', + tenant_id: '4fd44f30292945e481c7b8a0c8908869', + allocation_pools: [ + { + start: '192.0.0.2', + end: '192.255.255.254' + } + ], + gateway_ip: '192.0.0.1', + ip_version: 4, + cidr: '192.0.0.0/8', + enable_dhcp: false + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'hp') { + servers.server + .put(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', currentSubnet.id), { + subnet: { + name: 'my_subnet', + network_id: 'd32019d3-bc6e-4319-9c1d-6722fc136a22', + tenant_id: '4fd44f30292945e481c7b8a0c8908869', + allocation_pools: [ + { + start: '192.0.0.2', + end: '192.255.255.254' + } + ], + gateway_ip: '192.0.0.1', + ip_version: 4, + cidr: '192.0.0.0/8', + enable_dhcp: false + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'rackspace') { + servers.server + .put(urlJoin('/v2.0/subnets', currentSubnet.id), { + subnet: { + name: 'my_subnet', + network_id: 'd32019d3-bc6e-4319-9c1d-6722fc136a22', + tenant_id: '4fd44f30292945e481c7b8a0c8908869', + allocation_pools: [ + { + start: '192.0.0.2', + end: '192.255.255.254' + } + ], + gateway_ip: '192.0.0.1', + ip_version: 4, + cidr: '192.0.0.0/8', + enable_dhcp: false + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/subnet.json'); + } +}; + +setupModelDestroyedSubnetMock = function (client, provider, servers, currentSubnet){ + if (provider === 'openstack') { + servers.server + .delete(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', currentSubnet.id)) + .reply(204); + } + else if (provider === 'hp') { + servers.server + .delete(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', currentSubnet.id)) + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .delete(urlJoin('/v2.0/subnets', currentSubnet.id)) + .reply(204); + } +}; + +setupSubnetsMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnets.json'); + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnets.json'); + } + else if (provider === 'rackspace') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + + servers.server + .get('/v2.0/subnets') + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/subnets.json'); + } +}; + +setupCreateSubnetMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', { + subnet: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', { + subnet: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/subnets', { + subnet: { + name: 'create-test-ids2' + } + }) + .replyWithFile(201, __dirname + '/../../fixtures/rackspace/subnet.json'); + } +}; + +setupRefreshSubnetMock = function (client, provider, servers, subnet) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', subnet.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', subnet.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/subnets', subnet.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/subnet.json'); + } +}; + +setupSubnetModelCreateMock = function (client, provider, servers) { + if (provider === 'openstack') { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', { + subnet: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'hp') { + servers.server + .post('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', { + subnet: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'rackspace') { + servers.server + .post('/v2.0/subnets', { + subnet: { + name: 'model created network' + } + }) + .replyWithFile(202, __dirname + '/../../fixtures/rackspace/subnet.json'); + } +}; + +setupGetSubnetMock= function (client, provider, servers, currentSubnet) { + if (provider === 'openstack') { + servers.server + .get(urlJoin('/v2/72e90ecb69c44d0296072ea39e537041/v2.0/subnets', currentSubnet.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'hp') { + servers.server + .get(urlJoin('/v2/5ACED3DC3AA740ABAA41711243CC6949/v2.0/subnets', currentSubnet.id)) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/subnet.json'); + } + else if (provider === 'rackspace') { + servers.server + .get(urlJoin('/v2.0/subnets', currentSubnet.id)) + .replyWithFile(200, __dirname + '/../../fixtures/rackspace/subnet.json'); + } +}; diff --git a/test/common/storage/base-test.js b/test/common/storage/base-test.js index 8743288f4..dc8e5d5bb 100644 --- a/test/common/storage/base-test.js +++ b/test/common/storage/base-test.js @@ -1,37 +1,53 @@ /* * base-test.js: Test that should be common to all providers. * -* (C) 2012 Nodejitsu Inc. +* (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var fs = require('fs'), - path = require('path'), Buffer = require('buffer').Buffer, assert = require('../../helpers/assert'), helpers = require('../../helpers'), should = require('should'), - utile = require('utile'), + util = require('util'), async = require('async'), hock = require('hock'), + http = require('http'), urlJoin = require('url-join'), - _ = require('underscore'), + request = require('request'), providers = require('../../configs/providers.json'), - versions = require('../../fixtures/versions.json'), Container = require('../../../lib/pkgcloud/core/storage/container').Container, File = require('../../../lib/pkgcloud/core/storage/file').File, mock = !!process.env.MOCK, pkgcloud = require('../../../lib/pkgcloud'), - fillerama = fs.readFileSync(helpers.fixturePath('fillerama.txt'), 'utf8'); + fillerama = fs.readFileSync(helpers.fixturePath('fillerama.txt'), 'utf8'), + bigFillerama = fs.readFileSync(helpers.fixturePath('bigfile.raw')); + +// Declaring variables for helper functions defined later +var setupCreateContainerMock, setupGetContainersMock, setupUploadStreamMock, + setupBigDataUploadStreamMock, setupDownloadStreamMock, setupBigDataDownloadStreamMock, setupGetFileMock, + setupGetFilesMock, setupRemoveFileMock, setupDestroyContainerMock, + setupGetContainers2Mock; providers.filter(function (provider) { return !!helpers.pkgcloud.providers[provider].storage; }).forEach(function (provider) { describe('pkgcloud/common/storage/base [' + provider + ']', function () { - var client = helpers.createClient(provider, 'storage'), - context = {}, - authServer, server; + var config = null; + + if (!mock && provider === 'google') { + config = { + keyFilename: process.env.GCLOUD_KEYFILE, + projectId: process.env.GCLOUD_PROJECT_ID + }; + } + + var client = helpers.createClient(provider, 'storage', config), + context = {}, + authServer, server, + authHockInstance, hockInstance; before(function (done) { @@ -39,33 +55,31 @@ providers.filter(function (provider) { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + // setup a filtering path for aws + hockInstance.filteringPathRegEx(/https:\/\/[\w\-\.]*s3-us-west-2\.amazonaws\.com([\w\-\.\_0-9\/]*)/g, '$1'); + async.parallel([ function (next) { - hock.createHock(12345, function (err, hockClient) { - server = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - next(); - }); + authServer.listen(12346, next); } - ], done) + ], done); }); it('the createContainer() method should return newly created container', function(done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupCreateContainerMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } @@ -76,8 +90,8 @@ providers.filter(function (provider) { context.container = container; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -86,28 +100,23 @@ providers.filter(function (provider) { it('the getContainers() method should return newly created container', function (done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupGetContainersMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } client.getContainers(function (err, containers) { should.not.exist(err); should.exist(containers); - containers.should.be.instanceOf(Array); + containers.should.be.an.Array; containers.forEach(function(container) { container.should.be.instanceOf(Container); }); // TODO Name check - server && server.done(); + hockInstance && hockInstance.done(); done(); }); @@ -116,14 +125,19 @@ providers.filter(function (provider) { it('the upload() method with container and filename should succeed', function (done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests + // FIXME: Added 'google' until finding a way to "simulate" upload + if (provider === 'google') { + // TODO: Remove once 'google' upload & download tests are functional + context.file = { + name: 'test-file.txt', + size: Buffer.byteLength(fillerama) + }; return done(); } setupUploadStreamMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } @@ -131,20 +145,24 @@ providers.filter(function (provider) { container: context.container, remote: 'test-file.txt', headers: {'x-amz-acl': 'public-read'} - }, function(err, ok, response) { + }); + + stream.on('error', function(err, response) { should.not.exist(err); - should.exist(ok); + should.not.exist(response); + done(); + }); + + stream.on('success', function(file) { + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + file.should.be.an.instanceof(File); context.file = { name: 'test-file.txt', size: Buffer.byteLength(fillerama) }; - should.exist(response); - should.exist(response.statusCode); - should.exist(response.headers); - - server && server.done(); done(); }); @@ -155,86 +173,41 @@ providers.filter(function (provider) { it('the download() method with container and filename should succeed', function (done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests + // FIXME: Added 'google' until finding a way to "simulate" download + if (provider === 'google') { return done(); } setupDownloadStreamMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } var stream = client.download({ container: context.container, remote: context.file.name - }, function (err, file) { - should.not.exist(err); - should.exist(file); - - file.name.should.equal(context.file.name); - context.fileContents.should.equal(fillerama); - file.size.should.equal(Buffer.byteLength(context.fileContents)); - - server && server.done(); - done(); }); context.fileContents = ''; + stream.on('data', function (data) { context.fileContents += data; }); - stream.end(); - }); - it('the download() method with container and filename should succeed', function (done) { - - if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - - setupDownloadStreamMock(provider, client, { - server: server, - authServer: authServer - }); - } - - var stream = client.download({ - container: context.container, - remote: context.file.name - }, function (err, file) { - should.not.exist(err); - should.exist(file); - - file.name.should.equal(context.file.name); + stream.on('end', function() { context.fileContents.should.equal(fillerama); - file.size.should.equal(Buffer.byteLength(context.fileContents)); - - server && server.done(); + hockInstance && hockInstance.done(); done(); }); - - context.fileContents = ''; - stream.on('data', function (data) { - context.fileContents += data; - }); - stream.end(); }); it('the getFile() method with container and filename should succeed', function (done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupGetFileMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } @@ -245,7 +218,7 @@ providers.filter(function (provider) { file.name.should.equal(context.file.name); file.size.should.equal(context.file.size); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -253,30 +226,25 @@ providers.filter(function (provider) { it('the getFiles() method with container should succeed', function (done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupGetFilesMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } - client.getFiles(context.container, false, function (err, files) { + client.getFiles(context.container, null, function (err, files) { should.not.exist(err); should.exist(files); - files.should.be.instanceOf(Array); + files.should.be.an.Array; files.forEach(function(file) { file.should.be.instanceOf(File); - }) + }); // TODO look for context.file in array - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -284,14 +252,9 @@ providers.filter(function (provider) { it('the removeFile() method with container and filename should succeed', function (done) { if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupRemoveFileMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } @@ -299,7 +262,7 @@ providers.filter(function (provider) { should.not.exist(err); should.exist(ok); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -307,80 +270,95 @@ providers.filter(function (provider) { it('the upload() method with large file should succeed', function (done) { if (mock) { - return done(); - // TODO mock these out + // TODO make it work for google + // TODO make it work for azure - no idea why it fails on node 0.10 (it passes for node 6.8) + if (['google', 'azure'].indexOf(provider) !== -1) { + return done(); + } + setupBigDataUploadStreamMock(provider, client, { + server: hockInstance, + authServer: authHockInstance + }); } var stream = client.upload({ container: context.container, - remote: 'bigfile.raw' - }, function (err, ok) { - should.not.exist(err); - should.exist(ok); + remote: 'bigfile.raw', + headers: {'x-amz-acl': 'public-read'} + }); - context.file = { + stream.on('error', function(err) { + done(err); + }); + + stream.on('success', function(file) { + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + file.should.be.an.instanceof(File); + + context.bigFile = { name: 'bigfile.raw', - size: fs.readFileSync(helpers.fixturePath('bigfile.raw')).length + size: bigFillerama.length }; done(); }); - var file = fs.createReadStream(helpers.fixturePath('bigfile.raw')); + var file = fs.createReadStream(helpers.fixturePath('bigfile.raw'), {encoding: 'ascii'}); file.pipe(stream); }); it('the download() method with large file should succeed', function (done) { if (mock) { - return done(); - // TODO mock these out + // TODO make it work for google + // TODO make it work for azure - no idea why it fails on node 0.10 (it passes for node 6.8) + if (['google', 'azure'].indexOf(provider) !== -1) { + return done(); + } + setupBigDataDownloadStreamMock(provider, client, { + server: hockInstance, + authServer: authHockInstance + }); } var stream = client.download({ container: context.container, - remote: context.file.name - }, function (err, file) { + remote: context.bigFile.name + }); - should.not.exist(err); - should.exist(file); - file.should.be.instanceOf(File); + context.fileContents = []; + context.fileContentsSize = 0; + stream.on('data', function (data) { + context.fileContents.push(data); + context.fileContentsSize += data.length; + }); - file.name.should.equal(context.file.name); - file.size.should.equal(context.fileContentsSize); + stream.on('error', function(err) { + return done(err); + }); + + stream.on('end', function() { + hockInstance && hockInstance.done(); context.fileContents = Buffer.concat(context.fileContents, - file.size); + context.fileContentsSize).toString('ascii'); - // Compare byte by byte - var original = fs.readFileSync(helpers.fixturePath('bigfile.raw')); - for (var i = 0; i < file.size; i++) { + //Compare byte by byte + var original = bigFillerama.toString('ascii'); + for (var i = 0; i < original.length; i++) { assert.equal(context.fileContents[i], original[i]); } - done(); - }); - - context.fileContents = []; - context.fileContentsSize = 0; - stream.on('data', function (data) { - context.fileContents.push(data); - context.fileContentsSize += data.length; + return done(); }); - stream.end(); }); it('the destroyContainer() method with container should succeed', function (done) { - if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupDestroyContainerMock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } @@ -388,22 +366,16 @@ providers.filter(function (provider) { should.not.exist(err); should.exist(ok); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); it('the getContainers() method should succeed', function (done) { - if (mock) { - if (provider === 'joyent') { - // TODO figure out why joyent was disabled in vows based tests - return done(); - } - setupGetContainers2Mock(provider, client, { - server: server, - authServer: authServer + server: hockInstance, + authServer: authHockInstance }); } @@ -411,7 +383,7 @@ providers.filter(function (provider) { should.not.exist(err); should.exist(ok); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -423,17 +395,17 @@ providers.filter(function (provider) { async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); }); }); -function setupCreateContainerMock(provider, client, servers) { +setupCreateContainerMock = function (provider, client, servers) { if (provider === 'rackspace') { servers.authServer .post('/v2.0/tokens', { @@ -460,9 +432,9 @@ function setupCreateContainerMock(provider, client, servers) { password: 'MOCK-PASSWORD' } } - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) .reply(200, helpers._getOpenstackStandardResponse('../fixtures/openstack/initialToken.json')) - .get('/v2.0/tenants', {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .get('/v2.0/tenants', {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') .post('/v2.0/tokens', { auth: { @@ -472,7 +444,7 @@ function setupCreateContainerMock(provider, client, servers) { }, tenantId: '72e90ecb69c44d0296072ea39e537041' } - }, {'User-Agent': utile.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) .reply(200, helpers.getOpenstackAuthResponse()); servers.server @@ -480,20 +452,8 @@ function setupCreateContainerMock(provider, client, servers) { .reply(201); } else if (provider === 'amazon') { - - // Override the clients getUrl method as it tries to prefix the container name onto the request - client._getUrl = function (options) { - options = options || {}; - - if (typeof options === 'string') { - return urlJoin(this.protocol + this.serversUrl, options); - } - - return urlJoin(this.protocol + this.serversUrl, options.path); - }; - servers.server - .put('/') + .put('/', 'us-west-2') .reply(200); } else if (provider === 'azure') { @@ -512,9 +472,55 @@ function setupCreateContainerMock(provider, client, servers) { .put('/pkgcloud-test-container?restype=container') .reply(201, '', helpers.azureResponseHeaders()); } -} + else if (provider === 'google') { + servers.server + .post('/storage/v1/b?project=test-project', { + name: 'pkgcloud-test-container' + }) + .replyWithFile(200, __dirname + '/../../fixtures/google/create-bucket.json'); + + client.storage.baseUrl = client.storage.baseUrl.replace(/.*\.com/, 'http://localhost:12345'); + client.storage.makeAuthenticatedRequest = function (reqOpts, callback) { + reqOpts.uri = reqOpts.uri.replace(/.*\.com/, 'http://localhost:12345'); + return request(reqOpts, callback); + }; + + client.storage.authClient.request = function (reqOpts) { + reqOpts.url = reqOpts.url.replace(/.*\.com/, 'http://localhost:12345'); + return request(reqOpts); + }; + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .reply(200, helpers._getOpenstackStandardResponse('../fixtures/hp/initialToken.json')) + .get('/v2.0/tenants', {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .put('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(201); + } +}; -function setupGetContainersMock(provider, client, servers) { +setupGetContainersMock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json') @@ -528,67 +534,188 @@ function setupGetContainersMock(provider, client, servers) { else if (provider === 'azure') { servers.server .get('/?comp=list') - .reply(200, helpers.loadFixture('azure/list-containers.xml'),helpers.azureResponseHeaders()) + .reply(200, helpers.loadFixture('azure/list-containers.xml'),helpers.azureResponseHeaders()); } -} + else if (provider === 'google') { + servers.server + .get('/storage/v1/b?project=test-project') + .replyWithFile(200, __dirname + '/../../fixtures/google/get-buckets.json'); + } + else if (provider === 'hp') { + servers.server + .get('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json') + .reply(200, helpers.loadFixture('hp/postContainers.json')); + } +}; -function setupUploadStreamMock(provider, client, servers) { +setupUploadStreamMock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .put('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt', fillerama) .reply(200) + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt?format=json') + .reply(200, '', { 'content-length': fillerama.length + 2 }); } else if (provider === 'amazon') { servers.server .put('/test-file.txt', fillerama) - .reply(200, '', {}) + .reply(200); + } else if (provider === 'azure') { servers.server .put('/pkgcloud-test-container/test-file.txt?comp=block&blockid=block000000000000000', fillerama) .reply(201, '', helpers.azureResponseHeaders({'content-md5': 'mw0KEVFFwT8SgYGK3Cu8vg=='})) - .put('/pkgcloud-test-container/test-file.txt?comp=blocklist', "block000000000000000") + .put('/pkgcloud-test-container/test-file.txt?comp=blocklist', 'block000000000000000') .reply(201, '', helpers.azureResponseHeaders({'content-md5': 'VuFw1xub9CF3KoozbZ3kZw=='})) + .get('/pkgcloud-test-container/test-file.txt') + .reply(200, fillerama, helpers.azureGetFileResponseHeaders({'content-length': fillerama.length + 2, 'content-type': 'text/plain'})); } -} + else if (provider === 'hp') { + servers.server + .put('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt', fillerama) + .reply(200) + .head('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt?format=json') + .reply(200, '', { 'content-length': fillerama.length + 2 }); + } +}; -function setupDownloadStreamMock(provider, client, servers) { +setupBigDataUploadStreamMock = function (provider, client, servers) { + if (provider === 'rackspace' || provider === 'openstack') { + servers.server + .put('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/bigfile.raw', bigFillerama.toString('ascii')) + .reply(200) + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/bigfile.raw?format=json') + .reply(200, '', { 'content-length': bigFillerama.length + 2 }); + } + else if (provider === 'amazon') { + servers.server + .post('/bigfile.raw?uploads') + .reply(200, '\npkgcloud-test-containerbigfile.rawU4vzbMZVEkBOyxMPHMCu7nRSUw.eNLeqK0oYOPA6BeeiDSu6OTjrsMkkTsOFav3qCpgvIJluGWe_Yi.ypTVxEg--', {}) + .put('/bigfile.raw?partNumber=1&uploadId=U4vzbMZVEkBOyxMPHMCu7nRSUw.eNLeqK0oYOPA6BeeiDSu6OTjrsMkkTsOFav3qCpgvIJluGWe_Yi.ypTVxEg--', bigFillerama.slice(0, 5*1024*1024).toString('ascii')) + .reply(200, '\n\nhttps://pkgcloud-test-container.s3.amazonaws.com/bigfile.rawpkgcloud-test-containerbigfile.raw"b2286fe4aac65809a1b7a053d07fc99f-1"') + .put('/bigfile.raw?partNumber=2&uploadId=U4vzbMZVEkBOyxMPHMCu7nRSUw.eNLeqK0oYOPA6BeeiDSu6OTjrsMkkTsOFav3qCpgvIJluGWe_Yi.ypTVxEg--', bigFillerama.slice(5*1024*1024, 10*1024*1024).toString('ascii')) + .reply(200, '\n\nhttps://pkgcloud-test-container.s3.amazonaws.com/bigfile.rawpkgcloud-test-containerbigfile.raw"b2286fe4aac65809a1b7a053d07fc99f-2"') + .post('/bigfile.raw?uploadId=U4vzbMZVEkBOyxMPHMCu7nRSUw.eNLeqK0oYOPA6BeeiDSu6OTjrsMkkTsOFav3qCpgvIJluGWe_Yi.ypTVxEg--', '"b2286fe4aac65809a1b7a053d07fc99f-1"1"b2286fe4aac65809a1b7a053d07fc99f-2"2') + .reply(200); + } + else if (provider === 'azure') { + servers.server + .put('/pkgcloud-test-container/bigfile.raw?comp=block&blockid=block000000000000000', bigFillerama.slice(0, 4*1024*1024).toString('ascii')) + .reply(201, '', helpers.azureResponseHeaders({'content-md5': 'mw0KEVFFwT8SgYGK3Cu8vg=='})) + .put('/pkgcloud-test-container/bigfile.raw?comp=block&blockid=block000000000000001', bigFillerama.slice(4*1024*1024, 8*1024*1024).toString('ascii')) + .reply(201, '', helpers.azureResponseHeaders({'content-md5': 'mw0KEVFFwT8SgYGK3Cu8vg=='})) + .put('/pkgcloud-test-container/bigfile.raw?comp=block&blockid=block000000000000002', bigFillerama.slice(8*1024*1024).toString('ascii')) + .reply(201, '', helpers.azureResponseHeaders({'content-md5': 'mw0KEVFFwT8SgYGK3Cu8vg=='})) + .put('/pkgcloud-test-container/bigfile.raw?comp=blocklist', 'block000000000000000block000000000000001block000000000000002') + .reply(201, '', helpers.azureResponseHeaders({'content-md5': 'VuFw1xub9CF3KoozbZ3kZw=='})) + .get('/pkgcloud-test-container/bigfile.raw') + .reply(200, bigFillerama.toString('ascii'), helpers.azureGetFileResponseHeaders({'content-length': bigFillerama.length + 2, 'content-type': 'text/plain'})); + } + else if (provider === 'hp') { + servers.server + .put('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/bigfile.raw', bigFillerama.toString('ascii')) + .reply(200) + .head('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/bigfile.raw?format=json') + .reply(200, '', { 'content-length': bigFillerama.length + 2 }); + } +}; + +setupDownloadStreamMock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt') - .reply(200, fillerama, { 'content-length': fillerama.length + 2}) + .reply(200, fillerama, { 'content-length': fillerama.length + 2}); } else if (provider === 'amazon') { servers.server .get('/test-file.txt') - .reply(200, fillerama, { 'content-length': fillerama.length + 2 }) + .reply(200, fillerama, { 'content-length': fillerama.length + 2 }); } else if (provider === 'azure') { servers.server .get('/pkgcloud-test-container/test-file.txt') - .reply(200, fillerama, helpers.azureGetFileResponseHeaders({'content-length': fillerama.length + 2,'content-type': 'text/plain'})) + .reply(200, fillerama, helpers.azureGetFileResponseHeaders({'content-length': fillerama.length + 2,'content-type': 'text/plain'})); + } + else if (provider === 'google') { + servers.server + .get('/storage/v1/b/pkgcloud-test-container/o/test-file.txt') + .reply(200, { mediaLink: 'http://localhost:12345/mediaLink' }) + .get('/storage/v1/b/pkgcloud-test-container/o/test-file.txt?alt=media') + .reply(200, { mediaLink: 'http://localhost:12345/mediaLink' }) + .get('/mediaLink') + .reply(200, fillerama); + } + else if (provider === 'hp') { + servers.server + .get('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt') + .reply(200, fillerama, { 'content-length': fillerama.length + 2}); + } +}; + +setupBigDataDownloadStreamMock = function (provider, client, servers) { + if (provider === 'rackspace' || provider === 'openstack') { + servers.server + .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/bigfile.raw') + .reply(200, bigFillerama.toString('ascii')); + } + else if (provider === 'amazon') { + servers.server + .get('/bigfile.raw') + .reply(200, bigFillerama.toString('ascii')); + } + else if (provider === 'azure') { + servers.server + .get('/pkgcloud-test-container/bigfile.raw') + .reply(200, bigFillerama.toString('ascii'), helpers.azureGetFileResponseHeaders({'content-type': 'text/plain'})); + } + else if (provider === 'google') { + servers.server + .get('/storage/v1/b/pkgcloud-test-container/o/bigfile.raw') + .reply(200, { mediaLink: 'http://localhost:12345/mediaLink' }) + .get('/mediaLink') + .reply(200, bigFillerama.toString('ascii')); + } + else if (provider === 'hp') { + servers.server + .get('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/bigfile.raw') + .reply(200, bigFillerama.toString('ascii')); } -} +}; -function setupGetFileMock(provider, client, servers) { +setupGetFileMock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt?format=json') - .reply(200, '', { 'content-length': fillerama.length + 2 }) + .reply(200, '', { 'content-length': fillerama.length + 2 }); } else if (provider === 'amazon') { servers.server .head('/test-file.txt') - .reply(200, '', { 'content-length': fillerama.length + 2 }) + .reply(200, '', { 'content-length': fillerama.length + 2 }); } else if (provider === 'azure') { servers.server .get('/pkgcloud-test-container/test-file.txt') - .reply(200, '', helpers.azureGetFileResponseHeaders({'content-length': fillerama.length + 2, 'content-type': 'text/plain'})) + .reply(200, fillerama, helpers.azureGetFileResponseHeaders({'content-length': fillerama.length + 2, 'content-type': 'text/plain'})); + } + else if (provider === 'google') { + client.storage.request = function (reqOpts, callback) { + reqOpts.uri = urlJoin('http://localhost:12345/storage/v1', reqOpts.uri); + return request(reqOpts, (err, response, body) => callback(err, body? JSON.parse(body): body)); + }; + servers.server + .get('/storage/v1/b/pkgcloud-test-container/o/test-file.txt') + .replyWithFile(200, __dirname + '/../../fixtures/google/get-file.json'); + } + else if (provider === 'hp') { + servers.server + .head('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt?format=json') + .reply(200, '', { 'content-length': fillerama.length + 2 }); } -} +}; -function setupGetFilesMock(provider, client, servers) { +setupGetFilesMock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container?format=json') @@ -601,16 +728,30 @@ function setupGetFilesMock(provider, client, servers) { else if (provider === 'amazon') { servers.server .get('/') - .reply(200, helpers.loadFixture('amazon/list-bucket-files.xml')) + .reply(200, helpers.loadFixture('amazon/list-bucket-files.xml')); } else if (provider === 'azure') { servers.server .get('/pkgcloud-test-container?restype=container&comp=list') - .reply(200, helpers.loadFixture('azure/list-container-files.xml'), helpers.azureResponseHeaders({'content-type': 'application/xml'})) + .reply(200, helpers.loadFixture('azure/list-container-files.xml'), helpers.azureResponseHeaders({'content-type': 'application/xml'})); + } + else if (provider === 'google') { + servers.server + .get('/storage/v1/b/pkgcloud-test-container/o') + .replyWithFile(200, __dirname + '/../../fixtures/google/get-files.json'); + } + else if (provider === 'hp') { + servers.server + .get('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container?format=json') + .reply(200, [{ + bytes: fillerama.length, + name: 'test-file.txt', + content_type: 'text/plain' + }]); } -} +}; -function setupRemoveFileMock(provider, client, servers) { +setupRemoveFileMock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .delete('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt') @@ -624,14 +765,54 @@ function setupRemoveFileMock(provider, client, servers) { else if (provider === 'azure') { servers.server .delete('/pkgcloud-test-container/test-file.txt') - .reply(202, '', helpers.azureDeleteResponseHeaders()) + .reply(202, '', helpers.azureDeleteResponseHeaders()); + } + else if (provider === 'google') { + servers.server + .delete('/storage/v1/b/pkgcloud-test-container/o/test-file.txt') + .reply(204, {}); + } + else if (provider === 'hp') { + servers.server + .delete('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt') + .reply(204, ''); } -} +}; -function setupDestroyContainerMock(provider, client, servers) { - if (provider === 'rackspace' || provider === 'openstack') { +setupDestroyContainerMock = function (provider, client, servers) { + if (provider === 'openstack') { servers.server - .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container?format=json') + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(200, {}, { + 'x-container-object-count': 1, + 'x-container-bytes-used': fillerama.length + }) + .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container?format=json&limit=1001') + .reply(200, [ + { + bytes: fillerama.length, + name: 'test-file.txt', + content_type: 'text/plain' + } + ]) + .delete('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt') + .reply(204, '') + .delete('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(204); + } + else if (provider === 'rackspace') { + servers.server + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(200, {}, { + 'x-container-object-count': 1, + 'x-container-bytes-used': fillerama.length + }) + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(200, {}, { + 'x-container-object-count': 1, + 'x-container-bytes-used': fillerama.length + }) + .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container?format=json&limit=1001') .reply(200, [ { bytes: fillerama.length, @@ -658,9 +839,38 @@ function setupDestroyContainerMock(provider, client, servers) { .delete('/pkgcloud-test-container?restype=container') .reply(202, '', helpers.azureDeleteResponseHeaders()); } -} + else if (provider === 'google') { + servers.server + .get('/storage/v1/b/pkgcloud-test-container/o') + .replyWithFile(200, __dirname + '/../../fixtures/google/get-files.json') + .delete('/storage/v1/b/pkgcloud-test-container/o/test-file.txt') + .reply(204, {}) + .delete('/storage/v1/b/pkgcloud-test-container') + .reply(204, {}); + } + else if (provider === 'hp') { + servers.server + .head('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(200, {}, { + 'x-container-object-count': 1, + 'x-container-bytes-used': fillerama.length + }) + .get('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container?format=json&limit=1001') + .reply(200, [ + { + bytes: fillerama.length, + name: 'test-file.txt', + content_type: 'text/plain' + } + ]) + .delete('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt') + .reply(204, '') + .delete('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container') + .reply(204); + } +}; -function setupGetContainers2Mock(provider, client, servers) { +setupGetContainers2Mock = function (provider, client, servers) { if (provider === 'rackspace' || provider === 'openstack') { servers.server .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json') @@ -674,6 +884,16 @@ function setupGetContainers2Mock(provider, client, servers) { else if (provider === 'azure') { servers.server .get('/?comp=list') - .reply(200, helpers.loadFixture('azure/list-containers2.xml'), helpers.azureResponseHeaders()) + .reply(200, helpers.loadFixture('azure/list-containers2.xml'), helpers.azureResponseHeaders()); + } + else if (provider === 'google') { + servers.server + .get('/storage/v1/b?project=test-project') + .replyWithFile(200, __dirname + '/../../fixtures/google/get-buckets.json'); + } + else if (provider === 'hp') { + servers.server + .get('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json') + .reply(200, helpers.loadFixture('hp/preContainers.json')); } -} \ No newline at end of file +}; diff --git a/test/common/storage/upload-test.js b/test/common/storage/upload-test.js new file mode 100644 index 000000000..0b7bb5875 --- /dev/null +++ b/test/common/storage/upload-test.js @@ -0,0 +1,196 @@ +/* + * base-test.js: Test that should be common to all providers. + * + * (C) 2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var helpers = require('../../helpers'), + should = require('should'), + util = require('util'), + async = require('async'), + hock = require('hock'), + http = require('http'), + urlJoin = require('url-join'), + providers = require('../../configs/providers.json'), + mock = !!process.env.MOCK, + pkgcloud = require('../../../lib/pkgcloud'); + +// Declaring variables for helper functions defined later +var setupUploadStreamError; + +providers.filter(function (provider) { + return !!helpers.pkgcloud.providers[provider].storage; +}).forEach(function (provider) { + describe('pkgcloud/common/storage/base [' + provider + ']', function () { + + var client = helpers.createClient(provider, 'storage'), + authServer, server, + authHockInstance, hockInstance; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + // setup a filtering path for aws + hockInstance.filteringPathRegEx(/https:\/\/[\w\-\.]*s3-us-west-2\.amazonaws\.com([\w\-\.\_0-9\/]*)/g, '$1'); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the client.upload stream should emit error', function (done) { + if (mock) { + setupUploadStreamError(provider, client, { + server: hockInstance, + authServer: authHockInstance + }); + } + + var stream = client.upload({ + container: 'pkgcloud-test-container', + remote: 'test-file.txt' + }); + + stream.on('error', function (err) { + should.exist(err); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + done(); + }); + + stream.on('success', function (file) { + should.not.exist(file); + done(); + }); + + stream.end('foo'); + }); + + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + }); +}); + +setupUploadStreamError = function (provider, client, servers) { + if (provider === 'rackspace') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + + servers.server + .put('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt', 'foo') + .reply(400); + } + else if (provider === 'openstack') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .reply(200, helpers._getOpenstackStandardResponse('../fixtures/openstack/initialToken.json')) + .get('/v2.0/tenants', {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .put('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt', 'foo') + .reply(400); + } + else if (provider === 'amazon') { + servers.server + .put('/test-file.txt', 'foo') + .reply(400); + } + else if (provider === 'azure') { + + // Override the clients getUrl method as it tries to prefix the container name onto the request + client._getUrl = function (options) { + options = options || {}; + + return urlJoin('http://localhost:12345/', + (typeof options === 'string' + ? options + : options.path)); + }; + + servers.server + .put('/pkgcloud-test-container/test-file.txt?comp=block&blockid=block000000000000000', 'foo') + .reply(400); + } + else if (provider === 'hp') { + servers.authServer + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .reply(200, helpers._getOpenstackStandardResponse('../fixtures/hp/initialToken.json')) + .get('/v2.0/tenants', {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .replyWithFile(200, __dirname + '/../../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }, {'User-Agent': util.format('nodejs-pkgcloud/%s', pkgcloud.version)}) + .reply(200, helpers.gethpAuthResponse()); + + servers.server + .put('/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/pkgcloud-test-container/test-file.txt', 'foo') + .reply(400); + } +}; diff --git a/test/configs/mock/amazon.json b/test/configs/mock/amazon.json index a353271bc..5c3a4b0fe 100644 --- a/test/configs/mock/amazon.json +++ b/test/configs/mock/amazon.json @@ -2,5 +2,6 @@ "keyId": "MOCK-KEYID", "key": "MOCK-KEY", "serversUrl": "localhost:12345", - "protocol": "http://" + "protocol": "http://", + "region": "us-west-2" } diff --git a/test/configs/mock/digitalocean.json b/test/configs/mock/digitalocean.json index e57861477..6297f75e7 100644 --- a/test/configs/mock/digitalocean.json +++ b/test/configs/mock/digitalocean.json @@ -1,6 +1,5 @@ { - "clientId": "MOCK-ACCOUNT", - "apiKey": "mock-api-key", + "token": "mock-api-key", "protocol" : "http://", "serversUrl" : "localhost:12345" } diff --git a/test/configs/mock/google.json b/test/configs/mock/google.json new file mode 100644 index 000000000..2453eae8b --- /dev/null +++ b/test/configs/mock/google.json @@ -0,0 +1,10 @@ +{ + "credentials": { + "private_key_id": "7", + "private_key": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAK8Q+ToR4tWGshaKYRHKJ3ZmMUF6jjwCS/u1A8v1tFbQiVpBlxYB\npaNcT2ENEXBGdmWqr8VwSl0NBIKyq4p0rhsCAQMCQHS1+3wL7I5ZzA8G62Exb6RE\nINZRtCgBh/0jV91OeDnfQUc07SE6vs31J8m7qw/rxeB3E9h6oGi9IVRebVO+9zsC\nIQDWb//KAzrSOo0P0yktnY57UF9Q3Y26rulWI6LqpsxZDwIhAND/cmlg7rUz34Pf\nSmM61lJEmMEjKp8RB/xgghzmCeI1AiEAjvVVMVd8jCcItTdwyRO0UjWU4JOz0cnw\n5BfB8cSIO18CIQCLVPbw60nOIpUClNxCJzmMLbsrbMcUtgVS6wFomVvsIwIhAK+A\nYqT6WwsMW2On5l9di+RPzhDT1QdGyTI5eFNS+GxY\n-----END RSA PRIVATE KEY-----", + "client_email": "firstpart@secondpart.com", + "client_id": "8", + "type": "service_account" + }, + "projectId": "test-project" +} diff --git a/test/configs/mock/hp.json b/test/configs/mock/hp.json new file mode 100644 index 000000000..e9081e7b2 --- /dev/null +++ b/test/configs/mock/hp.json @@ -0,0 +1,6 @@ +{ + "username":"MOCK-USERNAME", + "apiKey": "MOCK-API-KEY", + "authUrl":"http://localhost:12346", + "region": "region-a.geo-1" +} diff --git a/test/configs/mock/iriscouch.json b/test/configs/mock/iriscouch.json deleted file mode 100644 index 2b0e5502a..000000000 --- a/test/configs/mock/iriscouch.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "username": "nodejitsu", - "password": "MOCK-PASSWORD", - "protocol": "http://", - "databaseUrl": "localhost:12345/hosting_public" -} \ No newline at end of file diff --git a/test/configs/mock/joyent.json b/test/configs/mock/joyent.json deleted file mode 100644 index c3ed6e879..000000000 --- a/test/configs/mock/joyent.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "account" : "MOCK-ACCOUNT", - "serversUrl" : "localhost:12345", - "protocol" : "http://", - "identity" : "./test/fixtures/testkey" -} diff --git a/test/configs/mock/mongohq.json b/test/configs/mock/mongohq.json deleted file mode 100644 index b5c210ffd..000000000 --- a/test/configs/mock/mongohq.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "username": "nodejitsu", - "password": "MOCK-PASSWORD", - "protocol": "http://", - "databaseUrl": "localhost:12345" -} \ No newline at end of file diff --git a/test/configs/mock/mongolab.json b/test/configs/mock/mongolab.json deleted file mode 100644 index 5caeef383..000000000 --- a/test/configs/mock/mongolab.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "username": "nodejitsu", - "password": "MOCK-PASSWORD", - "cloud": "JYC_us-sw-1", - "protocol": "http://", - "databaseUrl": "localhost:12345" -} \ No newline at end of file diff --git a/test/configs/mock/oneandone.json b/test/configs/mock/oneandone.json new file mode 100644 index 000000000..567061458 --- /dev/null +++ b/test/configs/mock/oneandone.json @@ -0,0 +1,7 @@ +{ + "token": "MOCK", + "serversUrl": "localhost:12345", + "authUrl": "http://localhost:12346", + "databaseUrl": "localhost:12345", + "protocol": "http://" +} \ No newline at end of file diff --git a/test/configs/mock/redistogo.json b/test/configs/mock/redistogo.json deleted file mode 100644 index e3559e901..000000000 --- a/test/configs/mock/redistogo.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "username": "nodejitsu", - "password": "MOCK-PASSWORD", - "url": "http://localhost:12345" -} \ No newline at end of file diff --git a/test/configs/providers.json b/test/configs/providers.json index dda968475..0d2a54369 100644 --- a/test/configs/providers.json +++ b/test/configs/providers.json @@ -1 +1,2 @@ -["rackspace", "openstack", "joyent", "amazon", "azure", "digitalocean"] \ No newline at end of file +["rackspace", "openstack", "azure", "digitalocean", "hp", "google", "oneandone"] + diff --git a/test/fixtures/digitalocean/active.json b/test/fixtures/digitalocean/active.json index d503f2259..41352d764 100644 --- a/test/fixtures/digitalocean/active.json +++ b/test/fixtures/digitalocean/active.json @@ -1,17 +1,90 @@ { - "status": "OK", "droplet": { - "id": 354526, + "id": 3164444, "name": "create-test-setWait", - "image_id": 1601, - "size_id": 66, - "region_id": 1, - "backups_active": false, - "ip_address": "0.0.0.0", - "locked": true, + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": false, "status": "active", - "created_at": "2013-08-09T21:57:57Z", - "backups": [], - "snapshots": [] + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "ipv6", + "virtio" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + 7938206 + ], + "image": { + "id": 6918990, + "name": "14.04 x64", + "distribution": "Ubuntu", + "slug": "ubuntu-14-04-x64", + "public": true, + "regions": [ + "nyc1", + "ams1", + "sfo1", + "nyc2", + "ams2", + "sgp1", + "lon1", + "nyc3", + "ams3", + "nyc3" + ], + "created_at": "2014-10-17T20:24:33Z", + "min_disk_size": 20 + }, + "size_slug": "512mb", + "networks": { + "v4": [ + { + "ip_address": "104.131.186.241", + "netmask": "255.255.240.0", + "gateway": "104.131.176.1", + "type": "public" + } + ], + "v6": [ + { + "ip_address": "2604:A880:0800:0010:0000:0000:031D:2001", + "netmask": 64, + "gateway": "2604:A880:0800:0010:0000:0000:0000:0001", + "type": "public" + } + ] + }, + "region": { + "name": "New York 3", + "slug": "nyc3", + "sizes": [ + "32gb", + "16gb", + "2gb", + "1gb", + "4gb", + "8gb", + "512mb", + "64gb", + "48gb" + ], + "features": [ + "virtio", + "private_networking", + "backups", + "ipv6", + "metadata" + ], + "available": true + } } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/active2.json b/test/fixtures/digitalocean/active2.json index 3cbd09964..4c5ab5fbc 100644 --- a/test/fixtures/digitalocean/active2.json +++ b/test/fixtures/digitalocean/active2.json @@ -1,17 +1,90 @@ { - "status": "OK", "droplet": { - "id": 354526, + "id": 12345, "name": "create-test-ids2", - "image_id": 1601, - "size_id": 66, - "region_id": 1, - "backups_active": false, - "ip_address": "0.0.0.0", - "locked": true, + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": false, "status": "active", - "created_at": "2013-08-09T21:57:57Z", - "backups": [], - "snapshots": [] + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "ipv6", + "virtio" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + 7938206 + ], + "image": { + "id": 119192817, + "name": "14.04 x64", + "distribution": "Ubuntu", + "slug": "ubuntu-14-04-x64", + "public": true, + "regions": [ + "nyc1", + "ams1", + "sfo1", + "nyc2", + "ams2", + "sgp1", + "lon1", + "nyc3", + "ams3", + "nyc3" + ], + "created_at": "2014-10-17T20:24:33Z", + "min_disk_size": 20 + }, + "size_slug": "512mb", + "networks": { + "v4": [ + { + "ip_address": "104.131.186.241", + "netmask": "255.255.240.0", + "gateway": "104.131.176.1", + "type": "public" + } + ], + "v6": [ + { + "ip_address": "2604:A880:0800:0010:0000:0000:031D:2001", + "netmask": 64, + "gateway": "2604:A880:0800:0010:0000:0000:0000:0001", + "type": "public" + } + ] + }, + "region": { + "name": "New York 3", + "slug": "nyc3", + "sizes": [ + "32gb", + "16gb", + "2gb", + "1gb", + "4gb", + "8gb", + "512mb", + "64gb", + "48gb" + ], + "features": [ + "virtio", + "private_networking", + "backups", + "ipv6", + "metadata" + ], + "available": true + } } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/create-server.json b/test/fixtures/digitalocean/create-server.json index d76c8afd3..c0a33a9a0 100644 --- a/test/fixtures/digitalocean/create-server.json +++ b/test/fixtures/digitalocean/create-server.json @@ -1,10 +1,44 @@ { - "status": "OK", "droplet": { - "id": 354526, + "id": 3164444, "name": "create-test-setWait", - "image_id": 1601, - "size_id": 66, - "event_id": 4565420 + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": true, + "status": "new", + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "virtio", + "backups", + "ipv6" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + + ], + "image": { + }, + "size_slug": "512mb", + "networks": { + }, + "region": { + } + }, + "links": { + "actions": [ + { + "id": 36805096, + "rel": "create", + "href": "https://api.digitalocean.com/v2/actions/36805096" + } + ] } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/create-server2.json b/test/fixtures/digitalocean/create-server2.json index 217fa2581..a46a37316 100644 --- a/test/fixtures/digitalocean/create-server2.json +++ b/test/fixtures/digitalocean/create-server2.json @@ -1,10 +1,44 @@ { - "status": "OK", "droplet": { - "id": 354526, + "id": 12345, "name": "create-test-ids2", - "image_id": 1601, - "size_id": 66, - "event_id": 4565420 + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": true, + "status": "active", + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "virtio", + "backups", + "ipv6" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + + ], + "image": { + }, + "size_slug": "512mb", + "networks": { + }, + "region": { + } + }, + "links": { + "actions": [ + { + "id": 36805096, + "rel": "create", + "href": "https://api.digitalocean.com/v2/actions/36805096" + } + ] } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/destroy-server.json b/test/fixtures/digitalocean/destroy-server.json deleted file mode 100644 index e610aa97a..000000000 --- a/test/fixtures/digitalocean/destroy-server.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "status": "OK", - "event_id": 4565807 -} \ No newline at end of file diff --git a/test/fixtures/digitalocean/flavors.json b/test/fixtures/digitalocean/flavors.json index c196b6808..5c6b0034f 100644 --- a/test/fixtures/digitalocean/flavors.json +++ b/test/fixtures/digitalocean/flavors.json @@ -1,105 +1,35 @@ { - "status": "OK", "sizes": [ { - "id": 66, - "name": "512MB", - "slug": null, + "slug": "512mb", "memory": 512, - "cpu": 1, + "vcpus": 1, "disk": 20, - "cost_per_hour": 0.00744, - "cost_per_month": "5.0" + "transfer": 1, + "price_monthly": 5.0, + "price_hourly": 0.00744, + "regions": [ + "nyc1", + "ams1", + "sfo1" + ] }, { - "id": 63, - "name": "1GB", - "slug": null, + "slug": "1gb", "memory": 1024, - "cpu": 1, + "vcpus": 2, "disk": 30, - "cost_per_hour": 0.01488, - "cost_per_month": "10.0" - }, - { - "id": 62, - "name": "2GB", - "slug": null, - "memory": 2048, - "cpu": 2, - "disk": 40, - "cost_per_hour": 0.02976, - "cost_per_month": "20.0" - }, - { - "id": 64, - "name": "4GB", - "slug": null, - "memory": 4096, - "cpu": 2, - "disk": 60, - "cost_per_hour": 0.05952, - "cost_per_month": "40.0" - }, - { - "id": 65, - "name": "8GB", - "slug": null, - "memory": 8192, - "cpu": 4, - "disk": 80, - "cost_per_hour": 0.11905, - "cost_per_month": "80.0" - }, - { - "id": 61, - "name": "16GB", - "slug": null, - "memory": 16384, - "cpu": 8, - "disk": 160, - "cost_per_hour": 0.2381, - "cost_per_month": "160.0" - }, - { - "id": 60, - "name": "32GB", - "slug": null, - "memory": 32768, - "cpu": 12, - "disk": 320, - "cost_per_hour": 0.47619, - "cost_per_month": "320.0" - }, - { - "id": 70, - "name": "48GB", - "slug": null, - "memory": 49152, - "cpu": 16, - "disk": 480, - "cost_per_hour": 0.71429, - "cost_per_month": "480.0" - }, - { - "id": 69, - "name": "64GB", - "slug": null, - "memory": 65536, - "cpu": 20, - "disk": 640, - "cost_per_hour": 0.95238, - "cost_per_month": "640.0" - }, - { - "id": 68, - "name": "96GB", - "slug": null, - "memory": 94208, - "cpu": 24, - "disk": 960, - "cost_per_hour": 1.42857, - "cost_per_month": "960.0" + "transfer": 2, + "price_monthly": 10.0, + "price_hourly": 0.01488, + "regions": [ + "nyc1", + "ams1", + "sfo1" + ] } - ] + ], + "meta": { + "total": 2 + } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/images.json b/test/fixtures/digitalocean/images.json index e8f6493d2..5c64f80d8 100644 --- a/test/fixtures/digitalocean/images.json +++ b/test/fixtures/digitalocean/images.json @@ -1,194 +1,41 @@ { - "status": "OK", "images": [ { - "id": 1601, - "name": "CentOS 5.8 x64", - "slug": null, - "distribution": "CentOS", - "public": true - }, - { - "id": 1602, - "name": "CentOS 5.8 x32", - "slug": null, - "distribution": "CentOS", - "public": true - }, - { - "id": 12573, - "name": "Debian 6.0 x64", - "slug": null, - "distribution": "Debian", - "public": true - }, - { - "id": 12575, - "name": "Debian 6.0 x32", - "slug": null, - "distribution": "Debian", - "public": true - }, - { - "id": 14097, - "name": "Ubuntu 10.04 x64", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 14098, - "name": "Ubuntu 10.04 x32", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 32387, - "name": "Fedora 17 x32", - "slug": null, - "distribution": "Fedora", - "public": true - }, - { - "id": 32399, - "name": "Fedora 17 x32 Desktop", - "slug": null, - "distribution": "Fedora", - "public": true - }, - { - "id": 32419, - "name": "Fedora 17 x64 Desktop", - "slug": null, - "distribution": "Fedora", - "public": true - }, - { - "id": 32428, - "name": "Fedora 17 x64", - "slug": null, - "distribution": "Fedora", - "public": true - }, - { - "id": 284203, - "name": "Ubuntu 12.04 x64", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 284211, - "name": "Ubuntu 12.04 x32", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 303619, - "name": "Debian 7.0 x32", - "slug": null, - "distribution": "Debian", - "public": true - }, - { - "id": 308287, - "name": "Debian 7.0 x64", - "slug": null, - "distribution": "Debian", - "public": true - }, - { - "id": 345791, - "name": "Ubuntu 13.04 x32", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 350076, - "name": "Ubuntu 13.04 x64", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 350424, - "name": "Arch Linux 2013.05 x64", - "slug": null, - "distribution": "Arch Linux", - "public": true - }, - { - "id": 361740, - "name": "Arch Linux 2013.05 x32", - "slug": null, - "distribution": "Arch Linux", - "public": true - }, - { - "id": 376568, - "name": "CentOS 6.4 x32", - "slug": null, - "distribution": "CentOS", - "public": true - }, - { - "id": 433240, - "name": "Ubuntu 12.10 x32", - "slug": null, + "id": 119192817, + "name": "14.04 x64", "distribution": "Ubuntu", - "public": true + "slug": "ubuntu-14-04-x64", + "public": true, + "regions": [ + "nyc1" + ], + "created_at": "2014-07-29T14:35:40Z" }, { - "id": 459444, - "name": "LAMP on Ubuntu 12.04", - "slug": null, + "id": 449676376, + "name": "14.04 x32", "distribution": "Ubuntu", - "public": true + "slug": "ubuntu-14-04-x32", + "public": true, + "regions": [ + "nyc1" + ], + "created_at": "2014-07-29T14:35:40Z" }, { - "id": 464235, - "name": "Ruby on Rails on Ubuntu 12.10 (Nginx + Unicorn)", - "slug": null, + "id": 449676856, + "name": "My Snapshot", "distribution": "Ubuntu", - "public": true - }, - { - "id": 473123, - "name": "Ubuntu 12.10 x64", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 473136, - "name": "Ubuntu 12.10 x64 Desktop", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 483575, - "name": "Redmine on Ubuntu 12.04", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 532043, - "name": "Wordpress on Ubuntu 12.10", - "slug": null, - "distribution": "Ubuntu", - "public": true - }, - { - "id": 562354, - "name": "CentOS 6.4 x64", - "slug": null, - "distribution": "CentOS", - "public": true + "slug": "", + "public": false, + "regions": [ + "nyc1", + "nyc3" + ], + "created_at": "2014-08-18T16:35:40Z" } - ] + ], + "meta": { + "total": 3 + } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/list-servers.json b/test/fixtures/digitalocean/list-servers.json index 48c483f78..0f676c77c 100644 --- a/test/fixtures/digitalocean/list-servers.json +++ b/test/fixtures/digitalocean/list-servers.json @@ -1,17 +1,88 @@ { - "status": "OK", "droplets": [ { - "id": 354526, - "name": "create-test-ids2", - "image_id": 1601, - "size_id": 66, - "region_id": 1, - "backups_active": false, - "ip_address": "0.0.0.0", + "id": 3164444, + "name": "example.com", + "memory": 512, + "vcpus": 1, + "disk": 20, "locked": false, "status": "active", - "created_at": "2013-07-16T23:05:08Z" + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:29:21Z", + "features": [ + "backups", + "ipv6", + "virtio" + ], + "backup_ids": [ + 7938002 + ], + "snapshot_ids": [ + + ], + "image": { + "id": 6918990, + "name": "14.04 x64", + "distribution": "Ubuntu", + "slug": "ubuntu-14-04-x64", + "public": true, + "regions": [ + "nyc1", + "ams1", + "sfo1", + "nyc2", + "ams2", + "sgp1", + "lon1", + "nyc3", + "ams3", + "nyc3" + ], + "created_at": "2014-10-17T20:24:33Z", + "min_disk_size": 20 + }, + "size_slug": "512mb", + "networks": { + "v4": [ + { + "ip_address": "104.236.32.182", + "netmask": "255.255.192.0", + "gateway": "104.236.0.1", + "type": "public" + } + ], + "v6": [ + { + "ip_address": "2604:A880:0800:0010:0000:0000:02DD:4001", + "netmask": 64, + "gateway": "2604:A880:0800:0010:0000:0000:0000:0001", + "type": "public" + } + ] + }, + "region": { + "name": "New York 3", + "slug": "nyc3", + "sizes": [ + + ], + "features": [ + "virtio", + "private_networking", + "backups", + "ipv6", + "metadata" + ], + "available": null + } } - ] + ], + "meta": { + "total": 1 + } } \ No newline at end of file diff --git a/test/fixtures/digitalocean/not-active.json b/test/fixtures/digitalocean/not-active.json index a1c81ee28..8d893fd4f 100644 --- a/test/fixtures/digitalocean/not-active.json +++ b/test/fixtures/digitalocean/not-active.json @@ -1,17 +1,90 @@ { - "status": "OK", "droplet": { - "id": 354526, + "id": 3164444, "name": "create-test-setWait", - "image_id": 1601, - "size_id": 66, - "region_id": 1, - "backups_active": false, - "ip_address": null, - "locked": true, + "memory": 512, + "vcpus": 1, + "disk": 20, + "locked": false, "status": "new", - "created_at": "2013-08-09T21:57:57Z", - "backups": [], - "snapshots": [] + "kernel": { + "id": 2233, + "name": "Ubuntu 14.04 x64 vmlinuz-3.13.0-37-generic", + "version": "3.13.0-37-generic" + }, + "created_at": "2014-11-14T16:36:31Z", + "features": [ + "ipv6", + "virtio" + ], + "backup_ids": [ + + ], + "snapshot_ids": [ + 7938206 + ], + "image": { + "id": 6918990, + "name": "14.04 x64", + "distribution": "Ubuntu", + "slug": "ubuntu-14-04-x64", + "public": true, + "regions": [ + "nyc1", + "ams1", + "sfo1", + "nyc2", + "ams2", + "sgp1", + "lon1", + "nyc3", + "ams3", + "nyc3" + ], + "created_at": "2014-10-17T20:24:33Z", + "min_disk_size": 20 + }, + "size_slug": "512mb", + "networks": { + "v4": [ + { + "ip_address": "104.131.186.241", + "netmask": "255.255.240.0", + "gateway": "104.131.176.1", + "type": "public" + } + ], + "v6": [ + { + "ip_address": "2604:A880:0800:0010:0000:0000:031D:2001", + "netmask": 64, + "gateway": "2604:A880:0800:0010:0000:0000:0000:0001", + "type": "public" + } + ] + }, + "region": { + "name": "New York 3", + "slug": "nyc3", + "sizes": [ + "32gb", + "16gb", + "2gb", + "1gb", + "4gb", + "8gb", + "512mb", + "64gb", + "48gb" + ], + "features": [ + "virtio", + "private_networking", + "backups", + "ipv6", + "metadata" + ], + "available": true + } } } \ No newline at end of file diff --git a/test/fixtures/google/create-bucket.json b/test/fixtures/google/create-bucket.json new file mode 100644 index 000000000..a17cbaa85 --- /dev/null +++ b/test/fixtures/google/create-bucket.json @@ -0,0 +1,9 @@ +{ + "kind": "storage#bucket", + "id": "test-bucket", + "selfLink": "https://www.googleapis.com/storage/v1/b/pkgcloud-test-container", + "name": "test-bucket", + "location": "US", + "storageClass": "STANDARD", + "etag": "CAE=" +} \ No newline at end of file diff --git a/test/fixtures/google/create-file.json b/test/fixtures/google/create-file.json new file mode 100644 index 000000000..b82cfa49b --- /dev/null +++ b/test/fixtures/google/create-file.json @@ -0,0 +1,16 @@ +{ + "kind": "storage#object", + "id": "pkgcloud-test-container/test-file.txt/1415899273437000", + "selfLink": "https://www.googleapis.com/storage/v1/b/pkgcloud-test-container/o/test-file.txt", + "name": "test-file.txt", + "bucket": "pkgcloud-test-container", + "generation": "1415899273437000", + "contentType": "application/txt", + "updated": "2014-11-13T17:21:13.436Z", + "storageClass": "STANDARD", + "size": "1102", + "md5Hash": "CatH1NCRnQx11HYkd8d4WA==", + "mediaLink": "https://www.googleapis.com/download/storage/v1/b/pkgcloud-test-container/o/test-file.txt?generation=1415899273437000&alt=media", + "crc32c": "xvcxag==", + "etag": "CMi+7OmL+MECEAE=" +} \ No newline at end of file diff --git a/test/fixtures/google/get-buckets.json b/test/fixtures/google/get-buckets.json new file mode 100644 index 000000000..541ee2c5f --- /dev/null +++ b/test/fixtures/google/get-buckets.json @@ -0,0 +1,23 @@ +{ + "kind": "storage#buckets", + "items": [ + { + "kind": "storage#bucket", + "id": "gcloud-pkgcloud-test-container-temp-d6daf200-6a92-11e4-81bd-493abfe0afb5", + "selfLink": "https://www.googleapis.com/storage/v1/b/gcloud-pkgcloud-test-container-temp-d6daf200-6a92-11e4-81bd-493abfe0afb5", + "name": "gcloud-pkgcloud-test-container-temp-d6daf200-6a92-11e4-81bd-493abfe0afb5", + "location": "US", + "storageClass": "STANDARD", + "etag": "CAE=" + }, + { + "kind": "storage#bucket", + "id": "gcloud-pkgcloud-test-container-temp-e992db50-6a93-11e4-b0ae-0f9ab23bdc30", + "selfLink": "https://www.googleapis.com/storage/v1/b/gcloud-pkgcloud-test-container-temp-e992db50-6a93-11e4-b0ae-0f9ab23bdc30", + "name": "gcloud-pkgcloud-test-container-temp-e992db50-6a93-11e4-b0ae-0f9ab23bdc30", + "location": "US", + "storageClass": "STANDARD", + "etag": "CAE=" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/google/get-file.json b/test/fixtures/google/get-file.json new file mode 100644 index 000000000..b82cfa49b --- /dev/null +++ b/test/fixtures/google/get-file.json @@ -0,0 +1,16 @@ +{ + "kind": "storage#object", + "id": "pkgcloud-test-container/test-file.txt/1415899273437000", + "selfLink": "https://www.googleapis.com/storage/v1/b/pkgcloud-test-container/o/test-file.txt", + "name": "test-file.txt", + "bucket": "pkgcloud-test-container", + "generation": "1415899273437000", + "contentType": "application/txt", + "updated": "2014-11-13T17:21:13.436Z", + "storageClass": "STANDARD", + "size": "1102", + "md5Hash": "CatH1NCRnQx11HYkd8d4WA==", + "mediaLink": "https://www.googleapis.com/download/storage/v1/b/pkgcloud-test-container/o/test-file.txt?generation=1415899273437000&alt=media", + "crc32c": "xvcxag==", + "etag": "CMi+7OmL+MECEAE=" +} \ No newline at end of file diff --git a/test/fixtures/google/get-files.json b/test/fixtures/google/get-files.json new file mode 100644 index 000000000..1c2675c57 --- /dev/null +++ b/test/fixtures/google/get-files.json @@ -0,0 +1,21 @@ +{ + "kind": "storage#objects", + "items": [ + { + "kind": "storage#object", + "id": "pkgcloud-test-container/test-file.txt/1415899273437000", + "selfLink": "https://www.googleapis.com/storage/v1/b/pkgcloud-test-container/o/test-file.txt", + "name": "test-file.txt", + "bucket": "pkgcloud-test-container", + "generation": "1415899273437000", + "contentType": "application/txt", + "updated": "2014-11-13T17:21:13.436Z", + "storageClass": "STANDARD", + "size": "1102", + "md5Hash": "CatH1NCRnQx11HYkd8d4WA==", + "mediaLink": "https://www.googleapis.com/download/storage/v1/b/pkgcloud-test-container/o/test-file.txt?generation=1415899273437000&alt=media", + "crc32c": "xvcxag==", + "etag": "CMi+7OmL+MECEAE=" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/creatingServer.json b/test/fixtures/hp/creatingServer.json new file mode 100644 index 000000000..b155f7a6b --- /dev/null +++ b/test/fixtures/hp/creatingServer.json @@ -0,0 +1,16 @@ +{ + "server": { + "OS-DCF:diskConfig": "MANUAL", + "id": "5a023de8-957b-4822-ad84-8c7a9ef83c07", + "links": [ + { + "href": "http://compute.myownendpoint.org:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07", + "rel": "self" + }, { + "href": "http://compute.myownendpoint.org:8774/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07", + "rel": "bookmark" + } + ], + "adminPass": "i9SaeyU3sxKX" + } +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseFlavor1.json b/test/fixtures/hp/databaseFlavor1.json new file mode 100644 index 000000000..5df3dc30d --- /dev/null +++ b/test/fixtures/hp/databaseFlavor1.json @@ -0,0 +1,16 @@ + { + "flavor": { + "ram": 512, + "id": 1, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny" + } +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseFlavor2.json b/test/fixtures/hp/databaseFlavor2.json new file mode 100644 index 000000000..f44c45b08 --- /dev/null +++ b/test/fixtures/hp/databaseFlavor2.json @@ -0,0 +1,16 @@ +{ + "flavor": { + "ram": 1024, + "id": 2, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/2", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small" + } +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseFlavor3.json b/test/fixtures/hp/databaseFlavor3.json new file mode 100644 index 000000000..99eb47471 --- /dev/null +++ b/test/fixtures/hp/databaseFlavor3.json @@ -0,0 +1,18 @@ +{ + "flavor": { + "vcpus": 1, + "ram": 2048, + "id": 3, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/3", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium" + } +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseFlavors.json b/test/fixtures/hp/databaseFlavors.json new file mode 100644 index 000000000..9a4ff3485 --- /dev/null +++ b/test/fixtures/hp/databaseFlavors.json @@ -0,0 +1,64 @@ +{ + "flavors": [ + { + "id": 1, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "ram": 512 + }, + { + "id": 2, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/2", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small", + "ram": 1024 + }, + { + "id": 3, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/3", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium", + "ram": 2048 + }, + { + "id": 4, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/4", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large", + "ram": 4096 + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseFlavorsDetail.json b/test/fixtures/hp/databaseFlavorsDetail.json new file mode 100644 index 000000000..6e30a06b7 --- /dev/null +++ b/test/fixtures/hp/databaseFlavorsDetail.json @@ -0,0 +1,68 @@ +{ + "flavors": [ + { + "vcpus": 1, + "ram": 2048, + "id": 3, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/3", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium" + }, + { + "vcpus": 1, + "ram": 4096, + "id": 4, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/4", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large" + }, + { + "vcpus": 1, + "ram": 512, + "id": 1, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny" + }, + { + "vcpus": 1, + "ram": 1024, + "id": 2, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/2", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseInstance.json b/test/fixtures/hp/databaseInstance.json new file mode 100644 index 000000000..8d8b77bdb --- /dev/null +++ b/test/fixtures/hp/databaseInstance.json @@ -0,0 +1,33 @@ +{ + "instance": { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f" + } +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseInstanceLimitOffset.json b/test/fixtures/hp/databaseInstanceLimitOffset.json new file mode 100644 index 000000000..58c549b4d --- /dev/null +++ b/test/fixtures/hp/databaseInstanceLimitOffset.json @@ -0,0 +1,39 @@ +{ + "instances": [ + { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904" + } + ], + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances?marker=8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904&limit=1", + "rel": "next" + } + ] +} diff --git a/test/fixtures/hp/databaseInstances.json b/test/fixtures/hp/databaseInstances.json new file mode 100644 index 000000000..b37d94731 --- /dev/null +++ b/test/fixtures/hp/databaseInstances.json @@ -0,0 +1,145 @@ +{ + "instances": [ + { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "55041e91-98ab-4cd5-8148-f3b3978b3262" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904" + }, { + "status": "BUILD", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/91c21563-74d2-4b8a-9f8f-871e61714446", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/91c21563-74d2-4b8a-9f8f-871e61714446", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "91c21563-74d2-4b8a-9f8f-871e61714446" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/c98bf7db-bfa2-4d88-9469-c0a458a99e86", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/c98bf7db-bfa2-4d88-9469-c0a458a99e86", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "2", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/2", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ] + }, + "id": "c98bf7db-bfa2-4d88-9469-c0a458a99e86" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/databaseInstancesLimit2.json b/test/fixtures/hp/databaseInstancesLimit2.json new file mode 100644 index 000000000..ca539781e --- /dev/null +++ b/test/fixtures/hp/databaseInstancesLimit2.json @@ -0,0 +1,67 @@ +{ + "instances": [ + { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "55041e91-98ab-4cd5-8148-f3b3978b3262" + } + ], + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances?marker=55041e91-98ab-4cd5-8148-f3b3978b3262&limit=2", + "rel": "next" + } + ] +} diff --git a/test/fixtures/hp/databaseUsers.json b/test/fixtures/hp/databaseUsers.json new file mode 100644 index 000000000..5620bcf4e --- /dev/null +++ b/test/fixtures/hp/databaseUsers.json @@ -0,0 +1,12 @@ +{ + "users": [{ + "name": "joeTest", + "databases": [] + }, { + "name": "joeTestTwo", + "databases": [] + }, { + "name": "jowTestThree", + "databases": [] + }] +} \ No newline at end of file diff --git a/test/fixtures/hp/flavor1.json b/test/fixtures/hp/flavor1.json new file mode 100644 index 000000000..b2f3adca0 --- /dev/null +++ b/test/fixtures/hp/flavor1.json @@ -0,0 +1,21 @@ +{ + "flavor": { + "vcpus": 4, + "disk": 150, + "name": "m1.highbank", + "links": [ + { + "href": "http://compute.myownendpoint.org:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "self" + }, { + "href": "http://compute.myownendpoint.org:8774/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "bookmark" + } + ], + "rxtx_factor": 1.0, + "OS-FLV-EXT-DATA:ephemeral": 0, + "ram": 3584, + "id": "1", + "swap": "" + } +} \ No newline at end of file diff --git a/test/fixtures/hp/flavors.json b/test/fixtures/hp/flavors.json new file mode 100644 index 000000000..85d32b504 --- /dev/null +++ b/test/fixtures/hp/flavors.json @@ -0,0 +1,23 @@ +{ + "flavors": [ + { + "vcpus": 4, + "disk": 150, + "name": "m1.highbank", + "links": [ + { + "href": "http://compute.myownendpoint.org:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "self" + }, { + "href": "http://compute.myownendpoint.org:8774/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "bookmark" + } + ], + "rxtx_factor": 1.0, + "OS-FLV-EXT-DATA:ephemeral": 0, + "ram": 3584, + "id": "1", + "swap": "" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/getFile.json b/test/fixtures/hp/getFile.json new file mode 100644 index 000000000..c57f0d478 --- /dev/null +++ b/test/fixtures/hp/getFile.json @@ -0,0 +1,9 @@ +[ + { + "hash": "cb5c530452af82fb875dc0fb1a00a2c4", + "last_modified": "2013-05-20T22:48:08.059180", + "bytes": 2027, + "name": "test-file", + "content_type": "application/octet-stream" + } +] \ No newline at end of file diff --git a/test/fixtures/hp/image1.json b/test/fixtures/hp/image1.json new file mode 100644 index 000000000..15cb67aac --- /dev/null +++ b/test/fixtures/hp/image1.json @@ -0,0 +1,26 @@ +{ + "image": { + "status": "ACTIVE", + "updated": "2012-07-13T15:37:58Z", + "name": "Ubuntu 12.04", + "links": [ + { + "href": "http://208.123.85.197:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "self" + }, { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "bookmark" + }, { + "href": "http://10.225.0.9:9292/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "type": "application/vnd.openstack.image", + "rel": "alternate" + } + ], + "created": "2012-07-13T15:37:54Z", + "progress": 100, + "minRam": 0, + "minDisk": 0, + "id": "506d077e-66bf-44ff-907a-588c5c79fa66", + "metadata": {} + } +} \ No newline at end of file diff --git a/test/fixtures/hp/images.json b/test/fixtures/hp/images.json new file mode 100644 index 000000000..f5f75adb9 --- /dev/null +++ b/test/fixtures/hp/images.json @@ -0,0 +1,28 @@ +{ + "images": [ + { + "status": "ACTIVE", + "updated": "2012-07-13T15:37:58Z", + "name": "Ubuntu 12.04", + "links": [ + { + "href": "http://compute.myownendpoint.org:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "self" + }, { + "href": "http://compute.myownendpoint.org:8774/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "bookmark" + }, { + "href": "http://10.225.0.9:9292/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "type": "application/vnd.openstack.image", + "rel": "alternate" + } + ], + "created": "2012-07-13T15:37:54Z", + "progress": 100, + "minRam": 0, + "minDisk": 0, + "id": "506d077e-66bf-44ff-907a-588c5c79fa66", + "metadata": {} + } + ] +} diff --git a/test/fixtures/hp/initialToken-admin.json b/test/fixtures/hp/initialToken-admin.json new file mode 100644 index 000000000..6f49990cb --- /dev/null +++ b/test/fixtures/hp/initialToken-admin.json @@ -0,0 +1,16 @@ +{ + "access": { + "token": { + "expires": "2012-12-26T18:25:45Z", + "id": "e93be67f91724754aeb9409c9c69d305" + }, + "serviceCatalog": {}, + "user": { + "username": "MOCK-ADMIN", + "roles_links": [], + "id": "bf3b85477d06430c8044d5b2e5e6dc5e", + "roles": [], + "name": "MOCK-ADMIN" + } + } +} \ No newline at end of file diff --git a/test/fixtures/hp/initialToken.json b/test/fixtures/hp/initialToken.json new file mode 100644 index 000000000..519ce55d2 --- /dev/null +++ b/test/fixtures/hp/initialToken.json @@ -0,0 +1,16 @@ +{ + "access": { + "token": { + "expires": "2012-12-26T18:25:45Z", + "id": "e93be67f91724754aeb9409c9c69d304" + }, + "serviceCatalog": {}, + "user": { + "username": "MOCK-USERNAME", + "roles_links": [], + "id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "roles": [], + "name": "MOCK-USERNAME" + } + } +} \ No newline at end of file diff --git a/test/fixtures/hp/no-activeTenants.json b/test/fixtures/hp/no-activeTenants.json new file mode 100644 index 000000000..bd2c911ac --- /dev/null +++ b/test/fixtures/hp/no-activeTenants.json @@ -0,0 +1,11 @@ +{ + "tenants_links": [], + "tenants": [ + { + "enabled": "false", + "description": "MOCK-USERNAME", + "name": "MOCK-USERNAME", + "id": "5ACED3DC3AA740ABAA41711243CC6949" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/no-tenants.json b/test/fixtures/hp/no-tenants.json new file mode 100644 index 000000000..fc9c352dd --- /dev/null +++ b/test/fixtures/hp/no-tenants.json @@ -0,0 +1,4 @@ +{ + "tenants_links": [], + "tenants": [] +} \ No newline at end of file diff --git a/test/fixtures/hp/postContainers.json b/test/fixtures/hp/postContainers.json new file mode 100644 index 000000000..8fccead0c --- /dev/null +++ b/test/fixtures/hp/postContainers.json @@ -0,0 +1 @@ +[{"count": 0, "bytes": 0, "name": "lost+found"}, {"count": 0, "bytes": 0, "name": "pkgcloud-test-container"}] diff --git a/test/fixtures/hp/preContainers.json b/test/fixtures/hp/preContainers.json new file mode 100644 index 000000000..cdb6cbc35 --- /dev/null +++ b/test/fixtures/hp/preContainers.json @@ -0,0 +1 @@ +[{"count": 0, "bytes": 0, "name": "lost+found"}] diff --git a/test/fixtures/hp/realToken-admin.json b/test/fixtures/hp/realToken-admin.json new file mode 100644 index 000000000..f3548289c --- /dev/null +++ b/test/fixtures/hp/realToken-admin.json @@ -0,0 +1,72 @@ +{ + "access": { + "token": { + "expires": "2012-12-26T18:25:46Z", + "id": "4bc7c5dabf3e4a49918683437d386b8b", + "tenant": { + "enabled": true, + "id": "72e90ecb69c44d0296072ea39e537123", + "name": "MOCK-ADMIN", + "description": "MOCK-ADMIN" + } + }, + "serviceCatalog": [ + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://volume.myownendpoint.org:8776/v1/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "volume", + "name": "volume" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:9292/v1", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:9292/v1", + "publicURL": "http://image.myownendpoint.org:9292/v1" + } + ], + "endpoints_links": [], + "type": "image", + "name": "glance" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://localhost:12345/v2/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "compute", + "name": "nova" + }, { + "endpoints": [ + { + "adminURL": "http://localhost:12347/v2.0", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:5000/v2.0", + "publicURL": "http://identity.myownendpoint.org:5000/v2.0" + } + ], + "endpoints_links": [], + "type": "identity", + "name": "keystone" + } + ], + "user": { + "username": "MOCK-ADMIN", + "roles_links": [], + "id": "bf3b85477d06430c8044d5b2e5e6dc5e", + "roles": [], + "name": "MOCK-ADMIN" + } + } +} diff --git a/test/fixtures/hp/realToken-multiRegionVolume.json b/test/fixtures/hp/realToken-multiRegionVolume.json new file mode 100644 index 000000000..db2af928e --- /dev/null +++ b/test/fixtures/hp/realToken-multiRegionVolume.json @@ -0,0 +1,78 @@ +{ + "access": { + "token": { + "expires": "2012-12-26T18:25:46Z", + "id": "4bc7c5dabf3e4a49918683437d386b8a", + "tenant": { + "enabled": true, + "id": "5ACED3DC3AA740ABAA41711243CC6949", + "name": "MOCK-USERNAME", + "description": "MOCK-USERNAME" + } + }, + "serviceCatalog": [ + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://volume.myownendpoint.org:8776/v1/5ACED3DC3AA740ABAA41711243CC6949" + }, + { + "adminURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-b.geo-1", + "internalURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://volume.myownendpoint.org:8776/v1/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "volume", + "name": "volume" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:9292/v1", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:9292/v1", + "publicURL": "http://image.myownendpoint.org:9292/v1" + } + ], + "endpoints_links": [], + "type": "image", + "name": "glance" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://localhost:12345/v2/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "compute", + "name": "nova" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:35357/v2.0", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:5000/v2.0", + "publicURL": "http://identity.myownendpoint.org:5000/v2.0" + } + ], + "endpoints_links": [], + "type": "identity", + "name": "keystone" + } + ], + "user": { + "username": "MOCK-USERNAME", + "roles_links": [], + "id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "roles": [], + "name": "MOCK-USERNAME" + } + } +} diff --git a/test/fixtures/hp/realToken-noRegion.json b/test/fixtures/hp/realToken-noRegion.json new file mode 100644 index 000000000..484e01e2d --- /dev/null +++ b/test/fixtures/hp/realToken-noRegion.json @@ -0,0 +1,71 @@ +{ + "access": { + "token": { + "expires": "2012-12-26T18:25:46Z", + "id": "4bc7c5dabf3e4a49918683437d386b8a", + "tenant": { + "enabled": true, + "id": "5ACED3DC3AA740ABAA41711243CC6949", + "name": "MOCK-USERNAME", + "description": "MOCK-USERNAME" + } + }, + "serviceCatalog": [ + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "internalURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://volume.myownendpoint.org:8776/v1/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "volume", + "name": "volume" + }, + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:9292/v1", + "internalURL": "http://10.225.0.8:9292/v1", + "publicURL": "http://image.myownendpoint.org:9292/v1" + } + ], + "endpoints_links": [], + "type": "image", + "name": "glance" + }, + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "internalURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://compute.myownendpoint.org:8774/v2/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "compute", + "name": "nova" + }, + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:35357/v2.0", + "internalURL": "http://10.225.0.8:5000/v2.0", + "publicURL": "http://identity.myownendpoint.org:5000/v2.0" + } + ], + "endpoints_links": [], + "type": "identity", + "name": "keystone" + } + ], + "user": { + "username": "MOCK-USERNAME", + "roles_links": [], + "id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "roles": [], + "name": "MOCK-USERNAME" + } + } +} \ No newline at end of file diff --git a/test/fixtures/hp/realToken.json b/test/fixtures/hp/realToken.json new file mode 100644 index 000000000..5e12a6dba --- /dev/null +++ b/test/fixtures/hp/realToken.json @@ -0,0 +1,109 @@ +{ + "access": { + "token": { + "expires": "2012-12-26T18:25:46Z", + "id": "4bc7c5dabf3e4a49918683437d386b8a", + "tenant": { + "enabled": true, + "id": "5ACED3DC3AA740ABAA41711243CC6949", + "name": "MOCK-USERNAME", + "description": "MOCK-USERNAME" + } + }, + "serviceCatalog": [ + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8776/v1/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://volume.myownendpoint.org:8776/v1/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "volume", + "name": "volume" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:9292/v1", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:9292/v1", + "publicURL": "http://image.myownendpoint.org:9292/v1" + } + ], + "endpoints_links": [], + "type": "image", + "name": "glance" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://localhost:12345/v2/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "compute", + "name": "nova" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8774/v2/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://localhost:12345/v2/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "network", + "name": "neutron" + },{ + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v1.0/5ACED3DC3AA740ABAA41711243CC6949", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:8774/v1.0/5ACED3DC3AA740ABAA41711243CC6949", + "publicURL": "http://localhost:12345/v1.0/5ACED3DC3AA740ABAA41711243CC6949" + } + ], + "endpoints_links": [], + "type": "database", + "name": "database" + }, + { + "endpoints": [ + { + "region": "region-a.geo-1", + "tenantId": "HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00", + "publicURL": "http://localhost:12345/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00", + "internalURL": "https://snet-storage101.ord1.clouddrive.com/v1/HPCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00" + } + ], + "name": "swift", + "type": "object-store" + }, + { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:35357/v2.0", + "region": "region-a.geo-1", + "internalURL": "http://10.225.0.8:5000/v2.0", + "publicURL": "http://identity.myownendpoint.org:5000/v2.0" + } + ], + "endpoints_links": [], + "type": "identity", + "name": "keystone" + } + ], + "user": { + "username": "MOCK-USERNAME", + "roles_links": [], + "id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "roles": [], + "name": "MOCK-USERNAME" + } + } +} diff --git a/test/fixtures/hp/serverCreated.json b/test/fixtures/hp/serverCreated.json new file mode 100644 index 000000000..01ae03343 --- /dev/null +++ b/test/fixtures/hp/serverCreated.json @@ -0,0 +1,57 @@ +{ + "server": { + "OS-EXT-STS:task_state": null, + "addresses": { + "private": [ + { + "version": 4, + "addr": "208.123.85.201" + } + ] + }, + "links": [ + { + "href": "http://208.123.85.197:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07", + "rel": "self" + }, { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07", + "rel": "bookmark" + } + ], + "image": { + "id": "506d077e-66bf-44ff-907a-588c5c79fa66", + "links": [ + { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "bookmark" + } + ] + }, + "OS-EXT-STS:vm_state": "active", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "5a023de8-957b-4822-ad84-8c7a9ef83c07", + "user_id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "OS-DCF:diskConfig": "MANUAL", + "accessIPv4": "", + "accessIPv6": "", + "progress": 0, + "OS-EXT-STS:power_state": 1, + "config_drive": "", + "status": "ACTIVE", + "updated": "2012-12-25T18:26:02Z", + "hostId": "c41a6390eaac457345c29655688b16ac53182ae874c69770ca36e936", + "key_name": "", + "name": "create-test-setWait", + "created": "2012-12-25T18:25:54Z", + "tenant_id": "5ACED3DC3AA740ABAA41711243CC6949", + "metadata": {} + } +} \ No newline at end of file diff --git a/test/fixtures/hp/serverCreated2.json b/test/fixtures/hp/serverCreated2.json new file mode 100644 index 000000000..ca8cadeac --- /dev/null +++ b/test/fixtures/hp/serverCreated2.json @@ -0,0 +1,63 @@ +{ + "server": { + "OS-EXT-STS:task_state": null, + "addresses": { + "private": [ + { + "version": 4, + "addr": "208.123.85.201" + } + ], + "public": [ + { + "version": 4, + "addr": "208.123.85.201" + } + ] + }, + "links": [ + { + "href": "http://208.123.85.197:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07", + "rel": "self" + }, { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/servers/5a023de8-957b-4822-ad84-8c7a9ef83c07", + "rel": "bookmark" + } + ], + "image": { + "id": "506d077e-66bf-44ff-907a-588c5c79fa66", + "links": [ + { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "bookmark" + } + ] + }, + "OS-EXT-STS:vm_state": "active", + "flavor": { + "id": "1", + "links": [ + { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "5a023de8-957b-4822-ad84-8c7a9ef83c07", + "user_id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "OS-DCF:diskConfig": "MANUAL", + "accessIPv4": "", + "accessIPv6": "", + "progress": 0, + "OS-EXT-STS:power_state": 1, + "config_drive": "", + "status": "ACTIVE", + "updated": "2012-12-25T18:26:02Z", + "hostId": "c41a6390eaac457345c29655688b16ac53182ae874c69770ca36e936", + "key_name": "", + "name": "create-test-ids2", + "created": "2012-12-25T18:25:54Z", + "tenant_id": "5ACED3DC3AA740ABAA41711243CC6949", + "metadata": {} + } +} \ No newline at end of file diff --git a/test/fixtures/hp/serverList.json b/test/fixtures/hp/serverList.json new file mode 100644 index 000000000..9d34a9644 --- /dev/null +++ b/test/fixtures/hp/serverList.json @@ -0,0 +1,48 @@ +{ + "servers": [{ + "OS-EXT-STS:task_state": "deleting", + "addresses": { + "private": [{ + "version": 4, "addr": "208.123.85.203" + }] + }, + "links": [{ + "href": "http://208.123.85.197:8774/v2/5ACED3DC3AA740ABAA41711243CC6949/servers/c94e7443-6a35-4f8b-b4e5-da59881a9451", + "rel": "self" + }, { + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/servers/c94e7443-6a35-4f8b-b4e5-da59881a9451", + "rel": "bookmark" + }], + "image": { + "id": "506d077e-66bf-44ff-907a-588c5c79fa66", + "links": [{ + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/images/506d077e-66bf-44ff-907a-588c5c79fa66", + "rel": "bookmark" + }] + }, + "OS-EXT-STS:vm_state": "active", + "flavor": { + "id": "1", + "links": [{ + "href": "http://208.123.85.197:8774/5ACED3DC3AA740ABAA41711243CC6949/flavors/1", + "rel": "bookmark" + }] + }, + "id": "5a023de8-957b-4822-ad84-8c7a9ef83c07", + "user_id": "bf3b85477d06430c8044d5b2e5e6dc5f", + "OS-DCF:diskConfig": "MANUAL", + "accessIPv4": "", + "accessIPv6": "", + "progress": 0, + "OS-EXT-STS:power_state": 1, + "config_drive": "", + "status": "ACTIVE", + "updated": "2013-02-14T20:24:52Z", + "hostId": "ab50598d09cc91aa5c32f6afabe30fd1ded4cf8100eb77f782dd3e19", + "key_name": "", + "name": "create-test-ids2", + "created": "2013-02-14T20:24:42Z", + "tenant_id": "5ACED3DC3AA740ABAA41711243CC6949", + "metadata": {} + }] +} \ No newline at end of file diff --git a/test/fixtures/hp/tenantId-admin.json b/test/fixtures/hp/tenantId-admin.json new file mode 100644 index 000000000..df6a207d2 --- /dev/null +++ b/test/fixtures/hp/tenantId-admin.json @@ -0,0 +1,11 @@ +{ + "tenants_links": [], + "tenants": [ + { + "enabled": "true", + "description": "MOCK-ADMIN", + "name": "MOCK-ADMIN", + "id": "5ACED3DC3AA740ABAA41711243CC6949" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/tenantId.json b/test/fixtures/hp/tenantId.json new file mode 100644 index 000000000..c1025f93e --- /dev/null +++ b/test/fixtures/hp/tenantId.json @@ -0,0 +1,11 @@ +{ + "tenants_links": [], + "tenants": [ + { + "enabled": "true", + "description": "MOCK-USERNAME", + "name": "MOCK-USERNAME", + "id": "5ACED3DC3AA740ABAA41711243CC6949" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/hp/versions.json b/test/fixtures/hp/versions.json new file mode 100644 index 000000000..893d18aee --- /dev/null +++ b/test/fixtures/hp/versions.json @@ -0,0 +1,30 @@ +{ + "version": { + "status": "CURRENT", + "updated": "2011-01-21T11:33:21Z", + "media-types": [ + { + "base": "application/xml", + "type": "application/vnd.openstack.compute+xml;version=2" + }, { + "base": "application/json", + "type": "application/vnd.openstack.compute+json;version=2" + } + ], + "id": "v1", + "links": [ + { + "href": "http://compute.myownendpoint.org:8774/v2/", + "rel": "self" + }, { + "href": "http://docs.openstack.org/api/openstack-compute/1.1/os-compute-devguide-1.1.pdf", + "type": "application/pdf", + "rel": "describedby" + }, { + "href": "http://docs.openstack.org/api/openstack-compute/1.1/wadl/os-compute-1.1.wadl", + "type": "application/vnd.sun.wadl+xml", + "rel": "describedby" + } + ] + } +} \ No newline at end of file diff --git a/test/fixtures/iriscouch/database-redis.json b/test/fixtures/iriscouch/database-redis.json deleted file mode 100644 index fdbb3333d..000000000 --- a/test/fixtures/iriscouch/database-redis.json +++ /dev/null @@ -1 +0,0 @@ -{"_id":"Redis/nodejitsudb43639","partner":"nodejitsu","creation":{"first_name":"Marak","last_name":"Squires","email":"marak.squires@gmail.com","subdomain":"nodejitsudb43639","password":"sTTi:lh9vCF["}} \ No newline at end of file diff --git a/test/fixtures/iriscouch/database.json b/test/fixtures/iriscouch/database.json deleted file mode 100644 index 7ff10691b..000000000 --- a/test/fixtures/iriscouch/database.json +++ /dev/null @@ -1 +0,0 @@ -{"_id":"Server/nodejitsudb908","partner":"nodejitsu","creation":{"first_name":"Marak","last_name":"Squires","email":"marak.squires@gmail.com","subdomain":"nodejitsudb908"}} \ No newline at end of file diff --git a/test/fixtures/joyent/14186c17.json b/test/fixtures/joyent/14186c17.json deleted file mode 100644 index 855894265..000000000 --- a/test/fixtures/joyent/14186c17.json +++ /dev/null @@ -1,3 +0,0 @@ -{"id": "14186c17-0fcd-4bb5-ab42-51b848bda7e9", "name": "create-test-ids2", "type": "virtualmachine", "state": "running", "dataset": "sdc:sdc:nodejitsu:1.0.0", "ips": ["192.168.26.63", "64.30.132.118"], "memory": 1024, "disk": 30720, "metadata": { - "root_authorized_keys": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn" -}, "created": "2012-02-15T15:32:20+00:00", "updated": "2012-02-15T15:32:29+00:00"} \ No newline at end of file diff --git a/test/fixtures/joyent/createServer.json b/test/fixtures/joyent/createServer.json deleted file mode 100644 index 08b5de638..000000000 --- a/test/fixtures/joyent/createServer.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"create-test-ids2","package":"Small 1GB","dataset":"sdc:sdc:nodejitsu:1.0.0"} \ No newline at end of file diff --git a/test/fixtures/joyent/createdServer.json b/test/fixtures/joyent/createdServer.json deleted file mode 100644 index 34cdabb5e..000000000 --- a/test/fixtures/joyent/createdServer.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "14186c17-0fcd-4bb5-ab42-51b848bda7e9", - "name": "create-test-ids2", - "type": "virtualmachine", - "state": "provisioning", - "dataset": "sdc:sdc:nodejitsu:1.0.0", - "ips": [ - "192.168.26.26", - "64.30.132.75" - ], - "memory": 1024, - "disk": 30720, - "metadata": { - "root_authorized_keys": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn" - }, - "created": "2012-02-14T16:08:38+00:00", - "updated": "2012-02-14T16:08:38+00:00" -} \ No newline at end of file diff --git a/test/fixtures/joyent/fe4d8e28.json b/test/fixtures/joyent/fe4d8e28.json deleted file mode 100644 index 6a81e371c..000000000 --- a/test/fixtures/joyent/fe4d8e28.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"fe4d8e28-6154-4281-8f0e-dead21585ed5","name":"test-reboot","type":"virtualmachine","state":"running","dataset":"sdc:sdc:nodejitsu:1.0.0","ips":["192.168.26.63","64.30.132.118"],"memory":1024,"disk":30720,"metadata":{"root_authorized_keys":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn"},"created":"2012-02-15T15:32:20+00:00","updated":"2012-02-15T15:32:29+00:00"} \ No newline at end of file diff --git a/test/fixtures/joyent/flavor.json b/test/fixtures/joyent/flavor.json deleted file mode 100644 index 75fc350a3..000000000 --- a/test/fixtures/joyent/flavor.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "Small 1GB", - "memory": 1024, - "disk": 30720, - "swap": 2048, - "default": true -} \ No newline at end of file diff --git a/test/fixtures/joyent/flavors.json b/test/fixtures/joyent/flavors.json deleted file mode 100644 index d99bea1ef..000000000 --- a/test/fixtures/joyent/flavors.json +++ /dev/null @@ -1,72 +0,0 @@ -[ - { - "name": "Small 1GB", - "memory": 1024, - "disk": 30720, - "swap": 2048, - "default": true - }, - { - "name": "Medium 2GB", - "memory": 2048, - "disk": 61440, - "swap": 4096, - "default": false - }, - { - "name": "Medium 4GB", - "memory": 4096, - "disk": 122880, - "swap": 8192, - "default": false - }, - { - "name": "Large 8GB", - "memory": 8192, - "disk": 245760, - "swap": 16384, - "default": false - }, - { - "name": "Large 16GB", - "memory": 16384, - "disk": 491520, - "swap": 32768, - "default": false - }, - { - "name": "XL 32GB", - "memory": 32768, - "disk": 778240, - "swap": 65536, - "default": false - }, - { - "name": "XXL 48GB", - "memory": 49152, - "disk": 1048576, - "swap": 98304, - "default": false - }, - { - "name": "XXXL 64GB ", - "memory": 65536, - "disk": 1572864, - "swap": 131072, - "default": false - }, - { - "name": "XL 8GB High CPU", - "memory": 8192, - "disk": 245760, - "swap": 16384, - "default": false - }, - { - "name": "Medium 1GB High-CPU", - "memory": 1024, - "disk": 61440, - "swap": 2048, - "default": false - } -] \ No newline at end of file diff --git a/test/fixtures/joyent/image.json b/test/fixtures/joyent/image.json deleted file mode 100644 index 796c6efe4..000000000 --- a/test/fixtures/joyent/image.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "id": "880c3250-51ff-11e1-b7aa-837f081ac1a9", - "urn": "sdc:sdc:nodejitsu:1.0.0", - "name": "nodejitsu", - "os": "linux", - "type": "virtualmachine", - "description": "nodejitsu 1.0.0 VM Image", - "default": true, - "requirements": {}, - "version": "1.0.0", - "created": "2012-02-08T18:41:41+00:00" -} \ No newline at end of file diff --git a/test/fixtures/joyent/images.json b/test/fixtures/joyent/images.json deleted file mode 100644 index ac9f17851..000000000 --- a/test/fixtures/joyent/images.json +++ /dev/null @@ -1,350 +0,0 @@ -[ - { - "id": "880c3250-51ff-11e1-b7aa-837f081ac1a9", - "urn": "sdc:sdc:nodejitsu:1.0.0", - "name": "nodejitsu", - "os": "linux", - "type": "virtualmachine", - "description": "nodejitsu 1.0.0 VM Image", - "default": true, - "requirements": {}, - "version": "1.0.0", - "created": "2012-02-08T18:41:41+00:00" - }, - { - "id": "7a4f84be-df6d-11e0-a504-3f6609d83831", - "urn": "sdc:admin:windows2008r2:1.5.0", - "name": "windows2008r2", - "os": "windows", - "type": "virtualmachine", - "description": "windows2008r2 1.5.0 VM image", - "default": false, - "requirements": {}, - "version": "1.5.0", - "created": "2011-09-15T08:45:09+00:00" - }, - { - "id": "988c2f4e-4314-11e1-8dc3-2bc6d58f4be2", - "urn": "sdc:sdc:centos-5.7:1.2.1", - "name": "centos-5.7", - "os": "linux", - "type": "virtualmachine", - "description": "Centos 5.7 VM 1.2.1", - "default": false, - "requirements": {}, - "version": "1.2.1", - "created": "2012-02-10T19:17:40+00:00" - }, - { - "id": "e62c30b4-cdda-11e0-9dd4-af4d032032e3", - "urn": "sdc:sdc:nodejs:1.2.3", - "name": "nodejs", - "os": "smartos", - "type": "smartmachine", - "description": "Node.js git-deploy PaaS dataset", - "default": false, - "requirements": {}, - "version": "1.2.3", - "created": "2011-09-15T08:23:54+00:00" - }, - { - "id": "f953e97e-4991-11e1-9ea4-27c6e7e8afda", - "urn": "sdc:sdc:nodejs:1.3.3", - "name": "nodejs", - "os": "smartos", - "type": "smartmachine", - "description": "Node.js git-deploy PaaS dataset", - "default": false, - "requirements": {}, - "version": "1.3.3", - "created": "2012-01-31T16:18:06+00:00" - }, - { - "id": "3f8a3d02-43e4-11e1-9565-7f82a075e289", - "urn": "sdc:sdc:fedora-14:1.0.1", - "name": "fedora-14", - "os": "linux", - "type": "virtualmachine", - "description": "Fedora 14 VM 1.0.1", - "default": false, - "requirements": {}, - "version": "1.0.1", - "created": "2012-01-27T18:16:24+00:00" - }, - { - "id": "e6ac6784-44b3-11e1-8555-87c3dd87aafe", - "urn": "sdc:sdc:debian-6.03:1.0.0", - "name": "debian-6.03", - "os": "linux", - "type": "virtualmachine", - "description": "Debian 6.03 VM 1.0.0", - "default": false, - "requirements": {}, - "version": "1.0.0", - "created": "2012-01-22T04:49:31+00:00" - }, - { - "id": "71101322-43a5-11e1-8f01-cf2a3031a7f3", - "urn": "sdc:sdc:ubuntu-10.04:1.0.0", - "name": "ubuntu-10.04", - "os": "linux", - "type": "virtualmachine", - "description": "Ubuntu 10.04 VM 1.0.0", - "default": false, - "requirements": {}, - "version": "1.0.0", - "created": "2012-01-27T18:10:51+00:00" - }, - { - "id": "e4cd7b9e-4330-11e1-81cf-3bb50a972bd9", - "urn": "sdc:sdc:centos-6:1.0.0", - "name": "centos-6", - "os": "linux", - "type": "virtualmachine", - "description": "Centos 6 VM 1.0.0", - "default": false, - "requirements": {}, - "version": "1.0.0", - "created": "2012-01-20T06:47:12+00:00" - }, - { - "id": "1796eb3a-48d3-11e1-94db-3ba91709fad9", - "urn": "sdc:sdc:riak:1.5.5", - "name": "riak", - "os": "smartos", - "type": "smartmachine", - "description": "Riak SmartMachine template", - "default": false, - "requirements": {}, - "version": "1.5.5", - "created": "2012-01-30T17:41:29+00:00" - }, - { - "id": "1a9570ec-48e0-11e1-851f-13b84c932a46", - "urn": "local:admin:riakeds:1.5.5", - "name": "riakeds", - "os": "smartos", - "type": "smartmachine", - "description": "Riak EDS SmartMachine template", - "default": false, - "requirements": {}, - "version": "1.5.5", - "created": "2012-01-31T18:03:04+00:00" - }, - { - "id": "8eafbcca-1f8d-11e1-b18b-036f107bbf62", - "urn": "sdc:sdc:mongodb:1.0.6", - "name": "mongodb", - "os": "smartos", - "type": "smartmachine", - "description": "MongoDB SmartMachine", - "default": false, - "requirements": {}, - "version": "1.0.6", - "created": "2011-12-05T23:17:00+00:00" - }, - { - "id": "e05dbcac-1d44-11e1-b8ab-bf1bc04c2d65", - "urn": "sdc:sdc:smartosplus64:3.0.7", - "name": "smartosplus64", - "os": "smartos", - "type": "smartmachine", - "description": "Generic multi-purpose SmartMachine template", - "default": false, - "requirements": {}, - "version": "3.0.7", - "created": "2011-12-05T22:48:35+00:00" - }, - { - "id": "fcc5996a-1d34-11e1-899e-7bd98b87947a", - "urn": "sdc:sdc:smartosplus:3.0.7", - "name": "smartosplus", - "os": "smartos", - "type": "smartmachine", - "description": "Generic multi-purpose SmartMachine template", - "default": false, - "requirements": {}, - "version": "3.0.7", - "created": "2011-12-05T22:45:42+00:00" - }, - { - "id": "e483afce-10b2-11e1-86bc-ff468add832f", - "urn": "sdc:sdc:debian603:0.1.0", - "name": "debian603", - "os": "linux", - "type": "virtualmachine", - "description": "debian603 base install seed image", - "default": false, - "requirements": {}, - "version": "0.1.0", - "created": "2011-11-22T20:49:05+00:00" - }, - { - "id": "5fef6eda-05f2-11e1-90fc-13dac5e4a347", - "urn": "sdc:sdc:percona:1.2.2", - "name": "percona", - "os": "smartos", - "type": "smartmachine", - "description": "Percona SmartMachine", - "default": false, - "requirements": {}, - "version": "1.2.2", - "created": "2011-11-03T20:27:48+00:00" - }, - { - "id": "34359ccc-21d2-2e4e-87e8-69fb36412008", - "urn": "sdc:sdc:windows2008r2standard:1.5.1", - "name": "windows2008r2standard", - "os": "windows", - "type": "virtualmachine", - "description": "windows2008r2standard VM image", - "default": false, - "requirements": {}, - "version": "1.5.1", - "created": "2011-10-11T22:15:41+00:00" - }, - { - "id": "a9380908-ea0e-11e0-aeee-4ba794c83c33", - "urn": "sdc:sdc:percona:1.0.7", - "name": "percona", - "os": "smartos", - "type": "smartmachine", - "description": "Percona SmartMachine", - "default": false, - "requirements": {}, - "version": "1.0.7", - "created": "2011-09-28T22:51:07+00:00" - }, - { - "id": "df3589dc-df9a-11e0-a3a3-07ceee3e7d54", - "urn": "sdc:sdc:smartosplus64:3.0.4", - "name": "smartosplus64", - "os": "smartos", - "type": "smartmachine", - "description": "Generic multi-purpose SmartMachine template", - "default": false, - "requirements": {}, - "version": "3.0.4", - "created": "2011-09-15T13:21:28+00:00" - }, - { - "id": "aded640a-df98-11e0-b050-1f55ff3ddfa7", - "urn": "sdc:sdc:smartosplus:3.0.4", - "name": "smartosplus", - "os": "smartos", - "type": "smartmachine", - "description": "Generic multi-purpose SmartMachine template", - "default": true, - "requirements": {}, - "version": "3.0.4", - "created": "2011-09-15T13:20:11+00:00" - }, - { - "id": "ea9c36aa-c90b-4e0f-8516-72ec78be2470", - "urn": "sdc:sdc:zxtm-standard-1gbps:1.1.1", - "name": "zxtm-standard-1gbps", - "os": "smartos", - "type": "smartmachine", - "description": "Zeus TM Standard 1 Gbps SmartMachine", - "default": false, - "requirements": {}, - "version": "1.1.1", - "created": "2011-09-15T08:37:31+00:00" - }, - { - "id": "3ffa8676-2a5d-4497-b446-656173b2e738", - "urn": "sdc:sdc:zxtm-standard-200mbps:1.1.1", - "name": "zxtm-standard-200mbps", - "os": "smartos", - "type": "smartmachine", - "description": "Zeus TM Standard 200 Mbps SmartMachine", - "default": false, - "requirements": {}, - "version": "1.1.1", - "created": "2011-09-15T08:34:54+00:00" - }, - { - "id": "6f9c6970-9a31-4b64-b0f4-f6b05ab515f4", - "urn": "sdc:sdc:zeus-lb-200mbps:1.1.1", - "name": "zeus-lb-200mbps", - "os": "smartos", - "type": "smartmachine", - "description": "Zeus Load Balancer 200 Mbps SmartMachine", - "default": false, - "requirements": {}, - "version": "1.1.1", - "created": "2011-09-15T08:31:14+00:00" - }, - { - "id": "875798c3-44b6-4ade-b89c-46e71b020a15", - "urn": "sdc:sdc:zxtm-enterprise-1gbps:1.1.1", - "name": "zxtm-enterprise-1gbps", - "os": "smartos", - "type": "smartmachine", - "description": "Zeus TM Enterprise 1 Gbps SmartMachine", - "default": false, - "requirements": {}, - "version": "1.1.1", - "created": "2011-09-15T08:51:59+00:00" - }, - { - "id": "a6e2bccd-6121-4d67-ac76-7ea541a589cf", - "urn": "sdc:sdc:zxtm-enterprise-200mbps:1.1.1", - "name": "zxtm-enterprise-200mbps", - "os": "smartos", - "type": "smartmachine", - "description": "Zeus TM Enterprise 200 Mbps SmartMachine", - "default": false, - "requirements": {}, - "version": "1.1.1", - "created": "2011-09-15T08:51:37+00:00" - }, - { - "id": "33904834-1f01-49d3-bed3-b642e158c375", - "urn": "sdc:sdc:zeus-simple-lb-200mbps:1.1.1", - "name": "zeus-simple-lb-200mbps", - "os": "smartos", - "type": "smartmachine", - "description": "Zeus Simple Load Balancer 200 Mbps SmartMachine", - "default": false, - "requirements": {}, - "version": "1.1.1", - "created": "2011-09-15T08:30:22+00:00" - }, - { - "id": "3fcf35d2-dd79-11e0-bdcd-b3c7ac8aeea6", - "urn": "sdc:sdc:mysql:1.4.1", - "name": "mysql", - "os": "smartos", - "type": "smartmachine", - "description": "MySQL SmartMachine", - "default": false, - "requirements": {}, - "version": "1.4.1", - "created": "2011-09-15T07:18:23+00:00" - }, - { - "id": "141194fa-dd77-11e0-8539-27dd8d8264b8", - "urn": "sdc:sdc:smartos64:1.4.7", - "name": "smartos64", - "os": "smartos", - "type": "smartmachine", - "description": "Base template to build other templates on", - "default": false, - "requirements": {}, - "version": "1.4.7", - "created": "2011-09-15T07:32:37+00:00" - }, - { - "id": "f8ea0bb8-dd75-11e0-87c3-af5352ad3bd6", - "urn": "sdc:sdc:smartos:1.4.7", - "name": "smartos", - "os": "smartos", - "type": "smartmachine", - "description": "Base template to build other templates on", - "default": false, - "requirements": {}, - "version": "1.4.7", - "created": "2011-09-15T07:31:01+00:00" - } -] \ No newline at end of file diff --git a/test/fixtures/joyent/rebootServerRequest1.json b/test/fixtures/joyent/rebootServerRequest1.json deleted file mode 100644 index b553fafd5..000000000 --- a/test/fixtures/joyent/rebootServerRequest1.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"test-reboot","package":"Small 1GB","dataset":"sdc:sdc:nodejitsu:1.0.0"} \ No newline at end of file diff --git a/test/fixtures/joyent/rebootServerResponse1.json b/test/fixtures/joyent/rebootServerResponse1.json deleted file mode 100644 index 3f23de771..000000000 --- a/test/fixtures/joyent/rebootServerResponse1.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"fe4d8e28-6154-4281-8f0e-dead21585ed5","name":"test-reboot","type":"virtualmachine","state":"provisioning","dataset":"sdc:sdc:nodejitsu:1.0.0","ips":["192.168.26.63","64.30.132.118"],"memory":1024,"disk":30720,"metadata":{"root_authorized_keys":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn"},"created":"2012-02-15T15:32:18+00:00","updated":"2012-02-15T15:32:18+00:00"} \ No newline at end of file diff --git a/test/fixtures/joyent/servers.json b/test/fixtures/joyent/servers.json deleted file mode 100644 index 5f5c2fee9..000000000 --- a/test/fixtures/joyent/servers.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "id": "14186c17-0fcd-4bb5-ab42-51b848bda7e9", - "name": "create-test-ids2", - "type": "virtualmachine", - "state": "provisioning", - "dataset": "sdc:sdc:nodejitsu:1.0.0", - "ips": [ - "192.168.26.26", - "64.30.132.75" - ], - "memory": 1024, - "disk": 30720, - "metadata": { - "root_authorized_keys": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn" - }, - "created": "2012-02-14T16:08:38+00:00", - "updated": "2012-02-14T16:08:38+00:00" - } -] \ No newline at end of file diff --git a/test/fixtures/joyent/setWait.json b/test/fixtures/joyent/setWait.json deleted file mode 100644 index c8b0d40c0..000000000 --- a/test/fixtures/joyent/setWait.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"534aa63a-104f-4d6d-a3b1-c0d341a20a53","name":"create-test-setWait","type":"virtualmachine","state":"provisioning","dataset":"sdc:sdc:centos-6:1.0.1","ips":["199.192.240.167","192.168.24.83"],"memory":1024,"disk":30720,"metadata":{"root_authorized_keys":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn"},"created":"2012-02-16T16:39:04+00:00","updated":"2012-02-16T16:39:04+00:00"} \ No newline at end of file diff --git a/test/fixtures/joyent/setWaitResp1.json b/test/fixtures/joyent/setWaitResp1.json deleted file mode 100644 index 051accaae..000000000 --- a/test/fixtures/joyent/setWaitResp1.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"534aa63a-104f-4d6d-a3b1-c0d341a20a53","name":"create-test-setWait","type":"virtualmachine","state":"running","dataset":"sdc:sdc:centos-6:1.0.1","ips":["199.192.240.167","192.168.24.83"],"memory":1024,"disk":30720,"metadata":{"root_authorized_keys":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCyiWqkB9aKGiD7Z8KaBqZA67oo1Ysb5MV+47rQ1gydEyL3Y01FL3HIZMGvxvXLr1lqy3eiWJgTyt0SawqFTtQVY7kMHHkvu2A97h+LP8wdyXrm+MBuxjbQUVGaBRXyv5rFJP9GNZpig+KDBPo54AREmvduwtFAcWFdOC0BCgQ7MwVWcL9xhO02+Ra3fQ70dUV2kUDv/BZFkJaD3Gqc/4cir6oWF2wmdDs1vFpXrfKDi5rLG7CKOo8+pQk3NvSaNBQwonpML4/M1N0NlIM2io7soq5VNknpiCrPdyz2yXl8KFt3/kWLOLMtoxfbHnuuhmHDqLoVy3h7/2vx8Fgr6PWB Charlie@Charlie-Robbinss-MacBook-Pro.localnssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDZe2jje7DAL11/Py4/7mFU8jZY4lkdJg0Ikbaq95nizHOqd23P1ONTlJ4OaLuunJSyoa9C4yWcznRCjgj0ZSZyhC+ySTN2aqudPENUksmzu15CxM8yEcs26OVQKqITG3qeBcKeDmyCAkuAWoehwzHfirVvCbBbIvDvbtjLiifCILxUjRxVz0kSJ6BARqWgdVBSI8c3GTm4q0vXp4M2P3Cyk08ZGj31oEsoYdFfPHG3lDgbmYtSVnwsrMK/ZByL6oufzO84Hk1B5KMMCOnjfsUknFZc5tn085T/A1ngkGOtxdoG/8OMchUcmjJfsVBTpy3shyuFUrOzNxipCin5oR5X dscape@air-2.localnssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAgEA15sKCPHrYG51vn0lCBVtV4ZJfHxRGoNweb9Rz4KaB8oAEDEGR0pLf63F78Un2d1V/fM3UdYBQbUXiEOfDvWyMtJCdBPdXbpxfDQxDvT9QxM8cPrwItE1rfgfuCZIl/7sh8UX5DU/TFFSwbgT28xGDe1Cfpu5HCBDKwfXEq3x3thBCxCngmBv69KYf45YC3kvmQ4sBym4+/OFS85L1Mltrk811BAoPulYQW1ZeATlOGwB4c52nI7jKeBaIeHqOLLG7ERGXlikCoWL4m5HaomqoHNDk1t1CHPu2izy+1zsw3wWQOoaQzRWTF6ozEEJ6jN2pLj440w4UBnYx4Nzfi0Og/f5T7+LulLE1xfZO7wawTUc9sTCyAs6jCm/QwZmUzks34IjT/l3F0S3ZDkqRV+Bbd/wPXHHW6PrDRnQJEfstHJB+iqPaRxP2vvQMO0dGsX4x/IYiPCTUJQaFXBa0Dy7V5CNYKImtAxcUAB0gJcNzrsyy2v15YXC2iJz1Xwly6sQHxyxu7wMdoTTcSHYca7cIJTqiDWNpn4dFg+nbzr4OP7RlYkvny3jh9sBEGYKXzHB2opQhD8VR65kPwbmIwBuF7t4BYFFvg/TGyJZ4NUfbSvPIp3zgsdHmtY5OVBXve0Bu8lhoFJ3QWSmRTLD1+Tpl6t8MbbaQZ06X+oFMzJ30cE= jwurster@Jasun-Wursters-MacBook-Pro.localn"},"created":"2012-02-16T16:39:09+00:00","updated":"2012-02-16T16:39:20+00:00"} \ No newline at end of file diff --git a/test/fixtures/mongohq/database.json b/test/fixtures/mongohq/database.json deleted file mode 100644 index eb0c2ae66..000000000 --- a/test/fixtures/mongohq/database.json +++ /dev/null @@ -1 +0,0 @@ -{"id":63562,"config":{"MONGOHQ_URL":"mongodb://nodejitsu:484441bf13a1f89bb8f53eec55942193@alex.mongohq.com:10039/testDatabase"}} \ No newline at end of file diff --git a/test/fixtures/mongolab/customUser.json b/test/fixtures/mongolab/customUser.json deleted file mode 100644 index 3321caca9..000000000 --- a/test/fixtures/mongolab/customUser.json +++ /dev/null @@ -1 +0,0 @@ -{ "name" : "nodejitsu_custompassword" , "adminUser" : { "username" : "nodejitsu_custompassword" , "email" : "custom@password.com"}} \ No newline at end of file diff --git a/test/fixtures/mongolab/database.json b/test/fixtures/mongolab/database.json deleted file mode 100644 index f77683b68..000000000 --- a/test/fixtures/mongolab/database.json +++ /dev/null @@ -1 +0,0 @@ -{ "name" : "nodejitsu_daniel_testDatabase" , "uri" : "mongodb://nodejitsu_daniel:ubcv84coc2euiqsghlp0g0h2gm@dbh86-a.mongolab.com:27867/nodejitsu_daniel_testDatabase"} \ No newline at end of file diff --git a/test/fixtures/mongolab/reqDatabase.json b/test/fixtures/mongolab/reqDatabase.json deleted file mode 100644 index e094686cb..000000000 --- a/test/fixtures/mongolab/reqDatabase.json +++ /dev/null @@ -1 +0,0 @@ -{"name":"nodejitsu_daniel_testDatabase","plan":"free","username":"nodejitsu_daniel","cloud":"JYC_us-sw-1"} \ No newline at end of file diff --git a/test/fixtures/mongolab/user.json b/test/fixtures/mongolab/user.json deleted file mode 100644 index 83b913693..000000000 --- a/test/fixtures/mongolab/user.json +++ /dev/null @@ -1 +0,0 @@ -{ "name" : "nodejitsu_daniel" , "adminUser" : { "username" : "nodejitsu_daniel" , "email" : "daniel@nodejitsu.com" , "password" : "tmmrutb4ho11lta6bggjahjsc5"}} \ No newline at end of file diff --git a/test/fixtures/mongolab/userList.json b/test/fixtures/mongolab/userList.json deleted file mode 100644 index 4f4d11b06..000000000 --- a/test/fixtures/mongolab/userList.json +++ /dev/null @@ -1 +0,0 @@ -[ { "name" : "nodejitsu_custompassword" , "adminUser" : { "username" : "nodejitsu_custompassword" , "email" : "custom@password.com"}} , { "name" : "nodejitsu_daniel" , "adminUser" : { "username" : "nodejitsu_daniel" , "email" : "daniel@nodejitsu.com"}} ] \ No newline at end of file diff --git a/test/fixtures/oneandone/addNodes.json b/test/fixtures/oneandone/addNodes.json new file mode 100644 index 000000000..2f861387b --- /dev/null +++ b/test/fixtures/oneandone/addNodes.json @@ -0,0 +1,12 @@ +[ + { + "id": "7C88E50FBC500A3D9D7F94E414255D6B", + "ip": "123.45.79.100", + "server_name": "My server 1" + }, + { + "id": "7C88E50FBC500A3D9D7F94E414255D6B", + "ip": "87.64.31.100", + "server_name": "My server 2" + } +] \ No newline at end of file diff --git a/test/fixtures/oneandone/createLoadBalancer.json b/test/fixtures/oneandone/createLoadBalancer.json new file mode 100644 index 000000000..d75bfc912 --- /dev/null +++ b/test/fixtures/oneandone/createLoadBalancer.json @@ -0,0 +1,38 @@ +{ + "id": "BD8318616581A9C3C53F94402503230F", + "name": "My load balancer", + "state": "CONFIGURING", + "creation_date": "2015-05-04T07:26:24+00:00", + "description": "My load balancer description", + "ip": null, + "health_check_test": "TCP", + "health_check_interval": 40, + "health_check_path": null, + "health_check_path_parser": null, + "persistence": true, + "persistence_time": 1200, + "method": "ROUND_ROBIN", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "rules": [ + { + "id": "BCFAF421227674B2B324F779C1163ECB", + "protocol": "TCP", + "port_balancer": 80, + "port_server": 80, + "source": "0.0.0.0" + }, + { + "id": "7390C04142800E006FF1B0132FFD8F9A", + "protocol": "TCP", + "port_balancer": 9999, + "port_server": 8888, + "source": "0.0.0.0" + } + ], + "server_ips": [], + "cloudpanel_id": "LB99AA4_1" +} \ No newline at end of file diff --git a/test/fixtures/oneandone/createSnapshot.json b/test/fixtures/oneandone/createSnapshot.json new file mode 100644 index 000000000..b93349483 --- /dev/null +++ b/test/fixtures/oneandone/createSnapshot.json @@ -0,0 +1,79 @@ +{ + "id": "92AA60BEC8333A21EDB9EAAA61852860", + "cloudpanel_id": "5061DE9", + "name": "My server", + "description": null, + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "creation_date": "2015-05-06T11:09:55+00:00", + "first_password": null, + "status": { + "state": "CONFIGURING", + "percent": null + }, + "hardware": { + "fixed_instance_size_id": 0, + "vcore": 1, + "cores_per_processor": 1, + "ram": 1, + "hdds": [ + { + "id": "77F5BC74C9B3532A37D31FEED070BE01", + "size": 40, + "is_main": true + } + ] + }, + "image": { + "id": "07C170D67C8EC776933FCFF1C299C1C5", + "name": "w2012r2datacenter64min" + }, + "dvd": { + "id": "05F2EF8C89A12782B7DBE445256BD7A5", + "name": "centos7-64iso" + }, + "snapshot": { + "id": "D609F69D08EB0C77D8EADE22F70462B4", + "creation_date": "2015-05-07T10:47:23+00:00" + }, + "ips": [ + { + "id": "696469547988D3945B3FE59DFCB93C90", + "ip": "10.4.141.161", + "type": "IPV4", + "reverse_dns": null, + "firewall_policy": { + "id": "5DE607955050915229D602931F942F32", + "name": "Linux" + }, + "load_balancers": [] + } + ], + "alerts": { + "critical": [ + { + "type": "agent", + "description": "MONITORING_AGENT_NOT_INSTALLED", + "date": "2015-05-07 10:47:23" + }, + { + "type": "internal_ping", + "description": "CRITICAL - 10.4.141.161: rta nan, lost 100%", + "date": "2015-05-07 09:59:21" + } + ] + }, + "monitoring_policy": { + "id": "5DF232A92E9635249B8A4EB31C5B14F4", + "name": "My Monitoring Policy 1" + }, + "private_networks": [ + { + "id": "6B7051F17199EF9EA994CD3E4AA450E6", + "name": "New private network 1" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/oneandone/getFlavor.json b/test/fixtures/oneandone/getFlavor.json new file mode 100644 index 000000000..57efad307 --- /dev/null +++ b/test/fixtures/oneandone/getFlavor.json @@ -0,0 +1,17 @@ +{ + "name": "M", + "id": "8C626C1A7005D0D1F527143C413D461E", + "hardware": { + "vcore": 1, + "cores_per_processor": 1, + "ram": 1, + "unit": "GB", + "hdds": [ + { + "size": 40, + "unit": "GB", + "is_main": true + } + ] + } +} \ No newline at end of file diff --git a/test/fixtures/oneandone/getImage.json b/test/fixtures/oneandone/getImage.json new file mode 100644 index 000000000..db5220a9e --- /dev/null +++ b/test/fixtures/oneandone/getImage.json @@ -0,0 +1,31 @@ +{ + "id": "842F09CAF954298C6A4BCD25E1CA3689", + "name": "My Image 2", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "os_family": "Linux", + "os": "Centos", + "os_version": "Centos6", + "architecture": 64, + "os_image_type": "Personal", + "type": "MY_IMAGE", + "min_hdd_size": 40, + "licenses": [], + "cloudpanel_id": "ap8962D_248", + "state": "ACTIVE", + "description": null, + "hdds": [ + { + "id": "35AAD7EB668D7ECF58AF9413DB127BD3", + "size": 40, + "is_main": true + } + ], + "server_id": "CB8A023E99CE2026981A110018A00764", + "frequency": "ONCE", + "num_images": 1, + "creation_date": "2015-04-14T07:18:20+00:00" +} \ No newline at end of file diff --git a/test/fixtures/oneandone/getServer.json b/test/fixtures/oneandone/getServer.json new file mode 100644 index 000000000..e3262c2d9 --- /dev/null +++ b/test/fixtures/oneandone/getServer.json @@ -0,0 +1,49 @@ +{ + "id": "39AA65F5D5B02FA02D58173094EBAF95", + "cloudpanel_id": "958FA92", + "name": "create-test-ids2", + "description": "", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "creation_date": "2015-05-04T06:32:15+00:00", + "first_password": "Fg52K21nz8", + "status": { + "state": "POWERED_ON", + "percent": null + }, + "hardware": { + "fixed_instance_size_id": 0, + "vcore": 1, + "cores_per_processor": 1, + "ram": 2, + "hdds": [ + { + "id": "8C626C1A7005D0D1F527143C413D461E", + "size": 40, + "is_main": true + } + ] + }, + "image": { + "id": "A0FAA4587A7CB6BBAA1EA877C844977E", + "name": "New image name" + }, + "dvd": null, + "snapshot": null, + "ips": [ + { + "id": "8D135204687B9CF9E79E7A93C096E336", + "ip": "10.4.140.213", + "type": "IPV4", + "reverse_dns": null, + "firewall_policy": null, + "load_balancers": [] + } + ], + "alerts": [], + "monitoring_policy": null, + "private_networks": null +} \ No newline at end of file diff --git a/test/fixtures/oneandone/getWaitServer.json b/test/fixtures/oneandone/getWaitServer.json new file mode 100644 index 000000000..7cc27b753 --- /dev/null +++ b/test/fixtures/oneandone/getWaitServer.json @@ -0,0 +1,49 @@ +{ + "id": "39AA65F5D5B02FA02D58173094EBAF95", + "cloudpanel_id": "958FA92", + "name": "create-test-setWait", + "description": "", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "creation_date": "2015-05-04T06:32:15+00:00", + "first_password": "Fg52K21nz8", + "status": { + "state": "POWERED_ON", + "percent": null + }, + "hardware": { + "fixed_instance_size_id": 0, + "vcore": 1, + "cores_per_processor": 1, + "ram": 2, + "hdds": [ + { + "id": "8C626C1A7005D0D1F527143C413D461E", + "size": 40, + "is_main": true + } + ] + }, + "image": { + "id": "A0FAA4587A7CB6BBAA1EA877C844977E", + "name": "New image name" + }, + "dvd": null, + "snapshot": null, + "ips": [ + { + "id": "8D135204687B9CF9E79E7A93C096E336", + "ip": "10.4.140.213", + "type": "IPV4", + "reverse_dns": null, + "firewall_policy": null, + "load_balancers": [] + } + ], + "alerts": [], + "monitoring_policy": null, + "private_networks": null +} \ No newline at end of file diff --git a/test/fixtures/oneandone/listFlavors.json b/test/fixtures/oneandone/listFlavors.json new file mode 100644 index 000000000..07103de64 --- /dev/null +++ b/test/fixtures/oneandone/listFlavors.json @@ -0,0 +1,70 @@ +[ + { + "name": "M", + "id": "8C626C1A7005D0D1F527143C413D461E", + "hardware": { + "vcore": 1, + "cores_per_processor": 1, + "ram": 1, + "unit": "GB", + "hdds": [ + { + "size": 40, + "unit": "GB", + "is_main": true + } + ] + } + }, + { + "name": "L", + "id": "8C626C1A7005D0D1F527143C413D461F", + "hardware": { + "vcore": 2, + "cores_per_processor": 1, + "ram": 2, + "unit": "GB", + "hdds": [ + { + "size": 80, + "unit": "GiB", + "is_main": true + } + ] + } + }, + { + "name": "XL", + "id": "8C626C1A7005D0D1F527143C413D4620", + "hardware": { + "vcore": 2, + "cores_per_processor": 1, + "ram": 4, + "unit": "GB", + "hdds": [ + { + "size": 120, + "unit": "GB", + "is_main": true + } + ] + } + }, + { + "name": "XXL", + "id": "8C626C1A7005D0D1F527143C413D4621", + "hardware": { + "vcore": 4, + "cores_per_processor": 1, + "ram": 8, + "unit": "GB", + "hdds": [ + { + "size": 160, + "unit": "GiB", + "is_main": true + } + ] + } + } +] \ No newline at end of file diff --git a/test/fixtures/oneandone/listImages.json b/test/fixtures/oneandone/listImages.json new file mode 100644 index 000000000..22414295a --- /dev/null +++ b/test/fixtures/oneandone/listImages.json @@ -0,0 +1,63 @@ +[ + { + "id": "A0FAA4587A7CB6BBAA1EA877C844977E", + "name": "New image name", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "os_family": "Linux", + "os": "Centos", + "os_version": "Centos7", + "architecture": 64, + "os_image_type": "Personal", + "type": "MY_IMAGE", + "min_hdd_size": 40, + "licenses": [], + "cloudpanel_id": "ap99AA4_1", + "state": "DEPLOYING", + "description": "New image description", + "hdds": [ + { + "id": "200FC47388A30470A376913E73B9C32C", + "size": 40, + "is_main": true + } + ], + "server_id": "E83777750130E1111AA89623B9557CAF", + "frequency": "DAILY", + "num_images": 5, + "creation_date": "2015-04-29T07:46:40+00:00" + }, + { + "id": "A0FAA4587A7CB6BBAA1EA877C844977F", + "name": "New image name 2", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "name": "United States" + }, + "os_family": "Linux", + "os": "Centos", + "os_version": "Centos7", + "architecture": 64, + "os_image_type": "Personal", + "type": "MY_IMAGE", + "min_hdd_size": 40, + "licenses": [], + "cloudpanel_id": "ap99AA4_1", + "state": "DEPLOYING", + "description": "New image description", + "hdds": [ + { + "id": "200FC47388A30470A376913E73B9C32D", + "size": 40, + "is_main": true + } + ], + "server_id": "E83777750130E1111AA89623B9557CAF", + "frequency": "DAILY", + "num_images": 5, + "creation_date": "2015-04-29T07:46:40+00:00" + } +] \ No newline at end of file diff --git a/test/fixtures/oneandone/listServers.json b/test/fixtures/oneandone/listServers.json new file mode 100644 index 000000000..18d7f88ea --- /dev/null +++ b/test/fixtures/oneandone/listServers.json @@ -0,0 +1,88 @@ +[ + { + "id": "39AA65F5D5B02FA02D58173094EBAF95", + "cloudpanel_id": "958FA92", + "name": "create-test-ids2", + "description": "", + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "creation_date": "2015-05-04T06:32:15+00:00", + "first_password": "Fg52K21nz8", + "status": { + "state": "POWERED_ON", + "percent": null + }, + "hardware": { + "fixed_instance_size_id": 0, + "vcore": 1, + "cores_per_processor": 1, + "ram": 2, + "hdds": [ + { + "id": "8C626C1A7005D0D1F527143C413D461E", + "size": 40, + "is_main": true + } + ] + }, + "image": { + "id": "3C3B80327CBBD7F0023F793F666C24D0", + "name": "w2008r2datacenter64std" + }, + "dvd": null, + "snapshot": null, + "ips": [ + { + "id": "8D135204687B9CF9E79E7A93C096E336", + "ip": "10.4.140.213", + "type": "IPV4", + "reverse_dns": null, + "firewall_policy": null, + "load_balancers": [] + } + ], + "alerts": [], + "monitoring_policy": null, + "private_networks": null + }, + { + "id": "D6F1239315503A1C44607BB04B3BD9C9", + "name": "My Server 2", + "status": { + "state": "POWERED_ON", + "percent": null + }, + "datacenter": { + "id": "D0F6D8C8ED29D3036F94C27BBB7BAD36", + "location": "USA", + "country_code": "US" + }, + "image": { + "id": "3C3F2FA3DB91318E8DCCB9F405339F76", + "name": "centos7-64std" + }, + "hardware": { + "fixed_instance_size_id": 0, + "vcore": 1, + "cores_per_processor": 1, + "ram": 2, + "hdds": [ + { + "id": "14B3CD8FE65C69F0AF28BE42431182E1", + "size": 40, + "is_main": true + } + ] + }, + "ips": [ + { + "id": "F116AFAAF937A5661601D31463B3A776", + "ip": "10.4.141.154" + } + ], + "alerts": null + } +] \ No newline at end of file diff --git a/test/fixtures/oneandone/snapshots.json b/test/fixtures/oneandone/snapshots.json new file mode 100644 index 000000000..72fb37129 --- /dev/null +++ b/test/fixtures/oneandone/snapshots.json @@ -0,0 +1,7 @@ +[ +{ + "id": "D609F69D08EB0C77D8EADE22F70462B4", + "creation_date": "2015-04-06T23:48:38Z", + "deletion_date": "2015-04-09T23:48:38Z" +} +] \ No newline at end of file diff --git a/test/fixtures/openstack/cdnFlavor.json b/test/fixtures/openstack/cdnFlavor.json new file mode 100644 index 000000000..c4f585250 --- /dev/null +++ b/test/fixtures/openstack/cdnFlavor.json @@ -0,0 +1,20 @@ +{ + "id": "cdn", + "providers": [ + { + "provider": "akamai", + "links": [ + { + "href": "http://www.akamai.com", + "rel": "provider_url" + } + ] + } + ], + "links": [ + { + "href": "http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/cdn", + "rel": "self" + } + ] +} diff --git a/test/fixtures/openstack/cdnFlavors.json b/test/fixtures/openstack/cdnFlavors.json new file mode 100644 index 000000000..602899330 --- /dev/null +++ b/test/fixtures/openstack/cdnFlavors.json @@ -0,0 +1,24 @@ +{ + "flavors": [ + { + "id": "cdn", + "providers": [ + { + "provider": "akamai", + "links": [ + { + "href": "http://www.akamai.com", + "rel": "provider_url" + } + ] + } + ], + "links": [ + { + "href": "http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/cdn", + "rel": "self" + } + ] + } + ] +} diff --git a/test/fixtures/openstack/cdnHomeDocument.json b/test/fixtures/openstack/cdnHomeDocument.json new file mode 100644 index 000000000..6792894c1 --- /dev/null +++ b/test/fixtures/openstack/cdnHomeDocument.json @@ -0,0 +1,21 @@ +{ + "resources":{ + "rel/cdn":{ + "href-template":"services{?marker,limit}", + "href-vars":{ + "marker":"param/marker", + "limit":"param/limit" + }, + "hints":{ + "allow":[ + "GET" + ], + "formats":{ + "application/json":{ + + } + } + } + } + } +} diff --git a/test/fixtures/openstack/cdnService.json b/test/fixtures/openstack/cdnService.json new file mode 100644 index 000000000..8184b7a7b --- /dev/null +++ b/test/fixtures/openstack/cdnService.json @@ -0,0 +1,49 @@ +{ + "id": "d49cd860-911f-11e4-b4a9-0800200c9a66", + "name":"pkgcloud-site", + "domains":[ + { + "domain":"pkgcloud.com", + "protocol":"http" + }, + { + "domain":"www.pkgcloud.com", + "protocol":"http" + } + ], + "origins":[ + { + "origin":"origin.pkgcloud.com", + "port":80, + "ssl":false, + "rules":[ + + ] + } + ], + "restrictions":[ + + ], + "caching":[ + + ], + "status":"deployed", + "flavor_id":"cdn", + "errors":[ + + ], + "links":[ + { + "href":"http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/services/pkgcloud-site", + "rel":"self" + }, + { + "href":"http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/cdn", + "rel":"flavor" + }, + { + "href":"http://www.pkgcloud.com.cdn369.altcdn.com", + "rel":"access_url" + } + ] +} diff --git a/test/fixtures/openstack/cdnServices.json b/test/fixtures/openstack/cdnServices.json new file mode 100644 index 000000000..d00595585 --- /dev/null +++ b/test/fixtures/openstack/cdnServices.json @@ -0,0 +1,59 @@ +{ + "services":[ + { + "id": "d49cd860-911f-11e4-b4a9-0800200c9a66", + "name":"pkgcloud-site", + "domains":[ + { + "domain":"pkgcloud.com", + "protocol":"http" + }, + { + "domain":"www.pkgcloud.com", + "protocol":"http" + } + ], + "origins":[ + { + "origin":"origin.pkgcloud.com", + "port":80, + "ssl":false, + "rules":[ + + ] + } + ], + "restrictions":[ + + ], + "caching":[ + + ], + "status":"deployed", + "flavor_id":"cdn", + "errors":[ + + ], + "links":[ + { + "href":"http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/services/pkgcloud-site", + "rel":"self" + }, + { + "href":"http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/cdn", + "rel":"flavor" + }, + { + "href":"http://www.pkgcloud.com.cdn92.altcdn.com", + "rel":"access_url" + } + ] + } + ], + "links":[ + { + "href":"http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/services?marker=d49cd860-911f-11e4-b4a9-0800200c9a66&limit=10", + "rel":"next" + } + ] +} diff --git a/test/fixtures/openstack/databaseFlavor1.json b/test/fixtures/openstack/databaseFlavor1.json new file mode 100644 index 000000000..5df3dc30d --- /dev/null +++ b/test/fixtures/openstack/databaseFlavor1.json @@ -0,0 +1,16 @@ + { + "flavor": { + "ram": 512, + "id": 1, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny" + } +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseFlavor2.json b/test/fixtures/openstack/databaseFlavor2.json new file mode 100644 index 000000000..f44c45b08 --- /dev/null +++ b/test/fixtures/openstack/databaseFlavor2.json @@ -0,0 +1,16 @@ +{ + "flavor": { + "ram": 1024, + "id": 2, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/2", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small" + } +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseFlavor3.json b/test/fixtures/openstack/databaseFlavor3.json new file mode 100644 index 000000000..99eb47471 --- /dev/null +++ b/test/fixtures/openstack/databaseFlavor3.json @@ -0,0 +1,18 @@ +{ + "flavor": { + "vcpus": 1, + "ram": 2048, + "id": 3, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/3", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium" + } +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseFlavors.json b/test/fixtures/openstack/databaseFlavors.json new file mode 100644 index 000000000..9a4ff3485 --- /dev/null +++ b/test/fixtures/openstack/databaseFlavors.json @@ -0,0 +1,64 @@ +{ + "flavors": [ + { + "id": 1, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny", + "ram": 512 + }, + { + "id": 2, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/2", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small", + "ram": 1024 + }, + { + "id": 3, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/3", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium", + "ram": 2048 + }, + { + "id": 4, + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/4", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large", + "ram": 4096 + } + ] +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseFlavorsDetail.json b/test/fixtures/openstack/databaseFlavorsDetail.json new file mode 100644 index 000000000..6e30a06b7 --- /dev/null +++ b/test/fixtures/openstack/databaseFlavorsDetail.json @@ -0,0 +1,68 @@ +{ + "flavors": [ + { + "vcpus": 1, + "ram": 2048, + "id": 3, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/3", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/3", + "rel": "bookmark" + } + ], + "name": "m1.medium" + }, + { + "vcpus": 1, + "ram": 4096, + "id": 4, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/4", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/4", + "rel": "bookmark" + } + ], + "name": "m1.large" + }, + { + "vcpus": 1, + "ram": 512, + "id": 1, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ], + "name": "m1.tiny" + }, + { + "vcpus": 1, + "ram": 1024, + "id": 2, + "links": [ + { + "href": "http://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/2", + "rel": "self" + }, + { + "href": "http://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ], + "name": "m1.small" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseInstance.json b/test/fixtures/openstack/databaseInstance.json new file mode 100644 index 000000000..8d8b77bdb --- /dev/null +++ b/test/fixtures/openstack/databaseInstance.json @@ -0,0 +1,33 @@ +{ + "instance": { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, + { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f" + } +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseInstances.json b/test/fixtures/openstack/databaseInstances.json new file mode 100644 index 000000000..b37d94731 --- /dev/null +++ b/test/fixtures/openstack/databaseInstances.json @@ -0,0 +1,145 @@ +{ + "instances": [ + { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "55041e91-98ab-4cd5-8148-f3b3978b3262" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "8e31b45f-7bc7-4f3c-ad2d-3ae86e51a904" + }, { + "status": "BUILD", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/91c21563-74d2-4b8a-9f8f-871e61714446", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/91c21563-74d2-4b8a-9f8f-871e61714446", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "91c21563-74d2-4b8a-9f8f-871e61714446" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/c98bf7db-bfa2-4d88-9469-c0a458a99e86", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/c98bf7db-bfa2-4d88-9469-c0a458a99e86", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "2", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/2", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/2", + "rel": "bookmark" + } + ] + }, + "id": "c98bf7db-bfa2-4d88-9469-c0a458a99e86" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseInstancesLimit2.json b/test/fixtures/openstack/databaseInstancesLimit2.json new file mode 100644 index 000000000..c22fca1c9 --- /dev/null +++ b/test/fixtures/openstack/databaseInstancesLimit2.json @@ -0,0 +1,67 @@ +{ + "instances": [ + { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f" + }, { + "status": "ACTIVE", + "name": "test-instance", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/instances/55041e91-98ab-4cd5-8148-f3b3978b3262", + "rel": "bookmark" + } + ], + "volume": { + "size": 1 + }, + "flavor": { + "id": "1", + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/flavors/1", + "rel": "self" + }, { + "href": "https://ord.databases.api.rackspacecloud.com/flavors/1", + "rel": "bookmark" + } + ] + }, + "id": "55041e91-98ab-4cd5-8148-f3b3978b3262" + } + ], + "links": [ + { + "href": "https://ord.databases.api.rackspacecloud.com/v1.0/537645/instances?marker=55041e91-98ab-4cd5-8148-f3b3978b3262&limit=2", + "rel": "next" + } + ] +} \ No newline at end of file diff --git a/test/fixtures/openstack/databaseUsers.json b/test/fixtures/openstack/databaseUsers.json new file mode 100644 index 000000000..5620bcf4e --- /dev/null +++ b/test/fixtures/openstack/databaseUsers.json @@ -0,0 +1,12 @@ +{ + "users": [{ + "name": "joeTest", + "databases": [] + }, { + "name": "joeTestTwo", + "databases": [] + }, { + "name": "jowTestThree", + "databases": [] + }] +} \ No newline at end of file diff --git a/test/fixtures/openstack/getStack-create-in-progress.json b/test/fixtures/openstack/getStack-create-in-progress.json new file mode 100644 index 000000000..5b7c45647 --- /dev/null +++ b/test/fixtures/openstack/getStack-create-in-progress.json @@ -0,0 +1,39 @@ +{ + "stack": { + "disable_rollback": true, + "description": "This is a Heat template to deploy a single Linux server running a Minecraft\nserver. The Minecraft server will be setup leveraging Chef solo.\n", + "parameters": { + "server_hostname": "minecraft", + "terms": "True", + "OS::stack_id": "e20b614b-c3ee-4b8b-bf4f-c05aaca947a2", + "OS::stack_name": "stack-test", + "minecraft_server_port": "25565", + "image": "Ubuntu 14.04 LTS (Trusty Tahr)", + "minecraft_spawn_monsters": "true", + "minecraft_spawn_npcs": "true", + "minecraft_gamemode": "0", + "minecraft_version": "1.8", + "minecraft_spawn_animals": "true", + "flavor": "4 GB Performance", + "chef_version": "11.16.0", + "kitchen": "https://github.com/rackspace-orchestration-templates/minecraft" + }, + "stack_status_reason": "Stack CREATE started", + "stack_name": "stack-test", + "stack_owner": null, + "creation_time": "2014-10-03T16:49:10Z", + "links": [ + { + "href": "http://localhost:12345/v1/72e90ecb69c44d0296072ea39e537041/stacks/stack-test/e20b614b-c3ee-4b8b-bf4f-c05aaca947a2", + "rel": "self" + } + ], + "capabilities": [], + "notification_topics": [], + "timeout_mins": 30, + "stack_status": "CREATE_IN_PROGRESS", + "updated_time": null, + "id": "e20b614b-c3ee-4b8b-bf4f-c05aaca947a2", + "template_description": "This is a Heat template to deploy a single Linux server running a Minecraft\nserver. The Minecraft server will be setup leveraging Chef solo.\n" + } +} \ No newline at end of file diff --git a/test/fixtures/openstack/metaResponse.json b/test/fixtures/openstack/metaResponse.json new file mode 100644 index 000000000..fe50aee57 --- /dev/null +++ b/test/fixtures/openstack/metaResponse.json @@ -0,0 +1,5 @@ +{ + "metadata" : { + "os_type" : "windows" + } +} diff --git a/test/fixtures/openstack/network.json b/test/fixtures/openstack/network.json new file mode 100644 index 000000000..1b127c396 --- /dev/null +++ b/test/fixtures/openstack/network.json @@ -0,0 +1,18 @@ +{ + "network": + { + "status":"ACTIVE", + "subnets":[ + "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + ], + "name":"private-network", + "provider:physical_network":null, + "admin_state_up":true, + "tenant_id":"4fd44f30292945e481c7b8a0c8908869", + "provider:network_type":"local", + "router:external":true, + "shared":true, + "id":"d32019d3-bc6e-4319-9c1d-6722fc136a22", + "provider:segmentation_id":null + } +} \ No newline at end of file diff --git a/test/fixtures/openstack/networks.json b/test/fixtures/openstack/networks.json new file mode 100644 index 000000000..a895f1523 --- /dev/null +++ b/test/fixtures/openstack/networks.json @@ -0,0 +1,34 @@ +{ + "networks":[ + { + "status":"ACTIVE", + "subnets":[ + "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + ], + "name":"private-network", + "provider:physical_network":null, + "admin_state_up":true, + "tenant_id":"4fd44f30292945e481c7b8a0c8908869", + "provider:network_type":"local", + "router:external":true, + "shared":true, + "id":"d32019d3-bc6e-4319-9c1d-6722fc136a22", + "provider:segmentation_id":null + }, + { + "status":"ACTIVE", + "subnets":[ + "08eae331-0402-425a-923c-34f7cfe39c1b" + ], + "name":"private", + "provider:physical_network":null, + "admin_state_up":true, + "tenant_id":"26a7980765d0414dbc1fc1f88cdb7e6e", + "provider:network_type":"local", + "router:external":true, + "shared":true, + "id":"db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "provider:segmentation_id":null + } + ] +} \ No newline at end of file diff --git a/test/fixtures/openstack/port.json b/test/fixtures/openstack/port.json new file mode 100644 index 000000000..5260c87ad --- /dev/null +++ b/test/fixtures/openstack/port.json @@ -0,0 +1,30 @@ +{ + "port": { + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "my_port", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", + "tenant_id": "", + "extra_dhcp_opts": [], + "binding:vif_details": { + "port_filter": true, + "ovs_hybrid_plug": true + }, + "binding:vif_type": "ovs", + "device_owner": "network:router_gateway", + "mac_address": "fa:16:3e:58:42:ed", + "binding:profile": {}, + "binding:vnic_type": "normal", + "fixed_ips": [ + { + "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062", + "ip_address": "172.24.4.2" + } + ], + "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + } +} diff --git a/test/fixtures/openstack/ports.json b/test/fixtures/openstack/ports.json new file mode 100644 index 000000000..779e056bf --- /dev/null +++ b/test/fixtures/openstack/ports.json @@ -0,0 +1,60 @@ +{ + "ports": [ + { + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", + "tenant_id": "", + "extra_dhcp_opts": [], + "binding:vif_details": { + "port_filter": true, + "ovs_hybrid_plug": true + }, + "binding:vif_type": "ovs", + "device_owner": "network:router_gateway", + "mac_address": "fa:16:3e:58:42:ed", + "binding:profile": {}, + "binding:vnic_type": "normal", + "fixed_ips": [ + { + "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062", + "ip_address": "172.24.4.2" + } + ], + "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + }, + { + "status": "ACTIVE", + "binding:host_id": "devstack", + "name": "", + "allowed_address_pairs": [], + "admin_state_up": true, + "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2", + "tenant_id": "d397de8a63f341818f198abb0966f6f3", + "extra_dhcp_opts": [], + "binding:vif_details": { + "port_filter": true, + "ovs_hybrid_plug": true + }, + "binding:vif_type": "ovs", + "device_owner": "network:router_interface", + "mac_address": "fa:16:3e:bb:3c:e4", + "binding:profile": {}, + "binding:vnic_type": "normal", + "fixed_ips": [ + { + "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17", + "ip_address": "10.0.0.1" + } + ], + "id": "f71a6703-d6de-4be1-a91a-a570ede1d159", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + } + ] +} diff --git a/test/fixtures/openstack/realToken-admin.json b/test/fixtures/openstack/realToken-admin.json index 855e86957..beed16ba3 100644 --- a/test/fixtures/openstack/realToken-admin.json +++ b/test/fixtures/openstack/realToken-admin.json @@ -59,6 +59,18 @@ "endpoints_links": [], "type": "identity", "name": "keystone" + }, { + "endpoints": [ + { + "adminURL": "http://localhost:12347/v2.0", + "region": "Calxeda-AUS1", + "internalURL": "http://10.225.0.8:9000/v2.0", + "publicURL": "http://network.myownendpoint.org:5000/v2.0" + } + ], + "endpoints_links": [], + "type": "network", + "name": "network" } ], "user": { diff --git a/test/fixtures/openstack/realToken.json b/test/fixtures/openstack/realToken.json index 719d71cdc..4b798d34e 100644 --- a/test/fixtures/openstack/realToken.json +++ b/test/fixtures/openstack/realToken.json @@ -31,7 +31,7 @@ "internalURL": "http://10.225.0.8:9292/v1", "publicURL": "http://image.myownendpoint.org:9292/v1" } - ], + ], "endpoints_links": [], "type": "image", "name": "glance" @@ -43,10 +43,34 @@ "internalURL": "http://10.225.0.8:8774/v2/72e90ecb69c44d0296072ea39e537041", "publicURL": "http://localhost:12345/v2/72e90ecb69c44d0296072ea39e537041" } - ], - "endpoints_links": [], + ], + "endpoints_links": [], "type": "compute", "name": "nova" + }, { + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v2/72e90ecb69c44d0296072ea39e537041", + "region": "Calxeda-AUS1", + "internalURL": "http://10.225.0.8:8774/v2/72e90ecb69c44d0296072ea39e537041", + "publicURL": "http://localhost:12345/v2/72e90ecb69c44d0296072ea39e537041" + } + ], + "endpoints_links": [], + "type": "network", + "name": "neutron" + },{ + "endpoints": [ + { + "adminURL": "http://10.225.0.8:8774/v1.0/72e90ecb69c44d0296072ea39e537041", + "region": "Calxeda-AUS1", + "internalURL": "http://10.225.0.8:8774/v1.0/72e90ecb69c44d0296072ea39e537041", + "publicURL": "http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041" + } + ], + "endpoints_links": [], + "type": "database", + "name": "database" }, { "endpoints": [ @@ -72,6 +96,26 @@ "endpoints_links": [], "type": "identity", "name": "keystone" + }, + { + "name": "heat", + "endpoints": [ + { + "region": "Calxeda-AUS1", + "publicURL": "http://localhost:12345/v1/72e90ecb69c44d0296072ea39e537041" + } + ], + "type": "orchestration" + }, + { + "name": "poppy", + "endpoints": [ + { + "region": "Calxeda-AUS1", + "publicURL": "http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041" + } + ], + "type": "cdn" } ], "user": { @@ -82,4 +126,4 @@ "name": "MOCK-USERNAME" } } -} \ No newline at end of file +} diff --git a/test/fixtures/openstack/securityGroup.json b/test/fixtures/openstack/securityGroup.json new file mode 100644 index 000000000..126b5d20c --- /dev/null +++ b/test/fixtures/openstack/securityGroup.json @@ -0,0 +1,58 @@ +{ + "security_group": { + "description": "default", + "id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "name": "default", + "security_group_rules": [ + { + "direction": "egress", + "ethertype": "IPv6", + "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "egress", + "ethertype": "IPv4", + "id": "93aa42e5-80db-4581-9391-3a608bd0e448", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "ingress", + "ethertype": "IPv6", + "id": "c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "ingress", + "ethertype": "IPv4", + "id": "f7d45c89-008e-4bab-88ad-d6811724c51c", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } + ], + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } +} diff --git a/test/fixtures/openstack/securityGroupRule.json b/test/fixtures/openstack/securityGroupRule.json new file mode 100644 index 000000000..080cc2aee --- /dev/null +++ b/test/fixtures/openstack/securityGroupRule.json @@ -0,0 +1,14 @@ +{ + "security_group_rule": { + "direction": "ingress", + "ethertype": "IPv6", + "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } +} diff --git a/test/fixtures/openstack/securityGroupRules.json b/test/fixtures/openstack/securityGroupRules.json new file mode 100644 index 000000000..fbdeddf05 --- /dev/null +++ b/test/fixtures/openstack/securityGroupRules.json @@ -0,0 +1,52 @@ +{ + "security_group_rules": [ + { + "direction": "egress", + "ethertype": "IPv6", + "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "egress", + "ethertype": "IPv4", + "id": "93aa42e5-80db-4581-9391-3a608bd0e448", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "ingress", + "ethertype": "IPv6", + "id": "c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "ingress", + "ethertype": "IPv4", + "id": "f7d45c89-008e-4bab-88ad-d6811724c51c", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } + ] +} diff --git a/test/fixtures/openstack/securityGroups.json b/test/fixtures/openstack/securityGroups.json new file mode 100644 index 000000000..331792e60 --- /dev/null +++ b/test/fixtures/openstack/securityGroups.json @@ -0,0 +1,60 @@ +{ + "security_groups": [ + { + "description": "default", + "id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "name": "default", + "security_group_rules": [ + { + "direction": "egress", + "ethertype": "IPv6", + "id": "3c0e45ff-adaf-4124-b083-bf390e5482ff", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "egress", + "ethertype": "IPv4", + "id": "93aa42e5-80db-4581-9391-3a608bd0e448", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "ingress", + "ethertype": "IPv6", + "id": "c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + }, + { + "direction": "ingress", + "ethertype": "IPv4", + "id": "f7d45c89-008e-4bab-88ad-d6811724c51c", + "port_range_max": null, + "port_range_min": null, + "protocol": null, + "remote_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } + ], + "tenant_id": "e4f50856753b4dc6afee5fa6b9b6c550" + } + ] +} diff --git a/test/fixtures/openstack/subnet.json b/test/fixtures/openstack/subnet.json new file mode 100644 index 000000000..b369ffe98 --- /dev/null +++ b/test/fixtures/openstack/subnet.json @@ -0,0 +1,20 @@ +{ + "subnet": { + "name": "my_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.0.0.2", + "end": "192.255.255.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } +} diff --git a/test/fixtures/openstack/subnets.json b/test/fixtures/openstack/subnets.json new file mode 100644 index 000000000..2c5ba82ab --- /dev/null +++ b/test/fixtures/openstack/subnets.json @@ -0,0 +1,40 @@ +{ + "subnets": [ + { + "name": "private-subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + }, + { + "name": "my_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.0.0.2", + "end": "192.255.255.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + } + ] +} diff --git a/test/fixtures/openstack/tenantInfo-admin.json b/test/fixtures/openstack/tenantInfo-admin.json new file mode 100644 index 000000000..68b7cafe3 --- /dev/null +++ b/test/fixtures/openstack/tenantInfo-admin.json @@ -0,0 +1,8 @@ +{ + "tenant": { + "id": "72e90ecb69c44d0296072ea39e537041", + "name": "ACME corp", + "description": "A description ...", + "enabled": true + } +} diff --git a/test/fixtures/openstack/validateToken-admin.json b/test/fixtures/openstack/validateToken-admin.json new file mode 100644 index 000000000..5f3eea3d4 --- /dev/null +++ b/test/fixtures/openstack/validateToken-admin.json @@ -0,0 +1,12 @@ +{ + "access": { + "token": { + "id": "4bc7c5dabf3e4a49918683437d386b8a", + "expires": "2010-11-01T03:32:15-05:00", + "tenant": { + "id": "72e90ecb69c44d0296072ea39e537041", + "name": "My Project" + } + } + } +} diff --git a/test/fixtures/rackspace/auth.json b/test/fixtures/rackspace/auth.json index 8af3cf8eb..62870e7ff 100644 --- a/test/fixtures/rackspace/auth.json +++ b/test/fixtures/rackspace/auth.json @@ -155,6 +155,42 @@ ], "name": "cloudServersOpenStack", "type": "compute" + }, + { + "name": "cloudNetworks", + "endpoints": [ + { + "region": "IAD", + "tenantId": "12345", + "publicURL": "http://localhost:12345/v2.0" + }, + { + "region": "LON", + "tenantId": "12345", + "publicURL": "http://localhost:12345/v2.0" + }, + { + "region": "ORD", + "tenantId": "12345", + "publicURL": "http://localhost:12345/v2.0" + }, + { + "region": "SYD", + "tenantId": "12345", + "publicURL": "http://localhost:12345/v2.0" + }, + { + "region": "DFW", + "tenantId": "12345", + "publicURL": "http://localhost:12345/v2.0" + }, + { + "region": "HKG", + "tenantId": "12345", + "publicURL": "http://localhost:12345/v2.0" + } + ], + "type": "network" } ], "user": { @@ -170,4 +206,4 @@ "RAX-AUTH:defaultRegion": "" } } -} \ No newline at end of file +} diff --git a/test/fixtures/rackspace/network.json b/test/fixtures/rackspace/network.json new file mode 100644 index 000000000..ca228f174 --- /dev/null +++ b/test/fixtures/rackspace/network.json @@ -0,0 +1,11 @@ +{ + "network": { + "name": "private-network", + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "admin_state_up": false, + "status": "ACTIVE", + "shared": true, + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "subnets": [] + } +} diff --git a/test/fixtures/rackspace/networks.json b/test/fixtures/rackspace/networks.json new file mode 100644 index 000000000..8fb5b53e5 --- /dev/null +++ b/test/fixtures/rackspace/networks.json @@ -0,0 +1,24 @@ +{ + "networks":[ + { + "name": "private-network", + "id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "admin_state_up": true, + "status": "ACTIVE", + "shared": false, + "tenant_id": "123456", + "subnets": [ + "96c1b28c-7be1-4035-b41f-247a8d81a61d" + ] + }, + { + "name": "private", + "id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "admin_state_up": true, + "status": "ACTIVE", + "shared": false, + "tenant_id": "123456", + "subnets": [] + } + ] +} diff --git a/test/fixtures/rackspace/port.json b/test/fixtures/rackspace/port.json new file mode 100644 index 000000000..65d222feb --- /dev/null +++ b/test/fixtures/rackspace/port.json @@ -0,0 +1,20 @@ +{ + "port": { + "status": "ACTIVE", + "name": "my_port", + "admin_state_up": true, + "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", + "tenant_id": "", + "device_owner": "network:router_gateway", + "mac_address": "fa:16:3e:58:42:ed", + "fixed_ips": [ + { + "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062", + "ip_address": "172.24.4.2" + } + ], + "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + } +} diff --git a/test/fixtures/rackspace/ports.json b/test/fixtures/rackspace/ports.json new file mode 100644 index 000000000..8a1406a81 --- /dev/null +++ b/test/fixtures/rackspace/ports.json @@ -0,0 +1,40 @@ +{ + "ports": [ + { + "status": "ACTIVE", + "name": "", + "admin_state_up": true, + "network_id": "70c1db1f-b701-45bd-96e0-a313ee3430b3", + "tenant_id": "", + "device_owner": "network:router_gateway", + "mac_address": "fa:16:3e:58:42:ed", + "fixed_ips": [ + { + "subnet_id": "008ba151-0b8c-4a67-98b5-0d2b87666062", + "ip_address": "172.24.4.2" + } + ], + "id": "d80b1a3b-4fc1-49f3-952e-1e2ab7081d8b", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + }, + { + "status": "ACTIVE", + "name": "", + "admin_state_up": true, + "network_id": "f27aa545-cbdd-4907-b0c6-c9e8b039dcc2", + "tenant_id": "d397de8a63f341818f198abb0966f6f3", + "device_owner": "network:router_interface", + "mac_address": "fa:16:3e:bb:3c:e4", + "fixed_ips": [ + { + "subnet_id": "288bf4a1-51ba-43b6-9d0a-520e9005db17", + "ip_address": "10.0.0.1" + } + ], + "id": "f71a6703-d6de-4be1-a91a-a570ede1d159", + "security_groups": [], + "device_id": "9ae135f4-b6e0-4dad-9e91-3c223e385824" + } + ] +} diff --git a/test/fixtures/rackspace/securityGroup.json b/test/fixtures/rackspace/securityGroup.json new file mode 100644 index 000000000..51322361c --- /dev/null +++ b/test/fixtures/rackspace/securityGroup.json @@ -0,0 +1,34 @@ +{ + "security_group":{ + "description":"default", + "id":"85cc3048-abc3-43cc-89b3-377341426ac5", + "name":"default", + "security_group_rules":[ + { + "direction":"ingress", + "ethertype":"IPv6", + "id":"c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max":22, + "port_range_min":22, + "protocol":"TCP", + "remote_group_id":null, + "remote_ip_prefix":null, + "security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id":"5831008" + }, + { + "direction":"ingress", + "ethertype":"IPv4", + "id":"f7d45c89-008e-4bab-88ad-d6811724c51c", + "port_range_max":22, + "port_range_min":22, + "protocol":"TCP", + "remote_group_id":null, + "remote_ip_prefix":null, + "security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id":"5831008" + } + ], + "tenant_id":"5831008" + } +} diff --git a/test/fixtures/rackspace/securityGroupRule.json b/test/fixtures/rackspace/securityGroupRule.json new file mode 100644 index 000000000..3f21c1ee0 --- /dev/null +++ b/test/fixtures/rackspace/securityGroupRule.json @@ -0,0 +1,14 @@ +{ + "security_group_rule":{ + "direction":"ingress", + "ethertype":"IPv6", + "id":"c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max":22, + "port_range_min":22, + "protocol":"TCP", + "remote_group_id":null, + "remote_ip_prefix":null, + "security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id":"5831008" + } +} diff --git a/test/fixtures/rackspace/securityGroupRules.json b/test/fixtures/rackspace/securityGroupRules.json new file mode 100644 index 000000000..e6eb9e05a --- /dev/null +++ b/test/fixtures/rackspace/securityGroupRules.json @@ -0,0 +1,28 @@ +{ + "security_group_rules":[ + { + "direction":"ingress", + "ethertype":"IPv6", + "id":"c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max":22, + "port_range_min":22, + "protocol":"TCP", + "remote_group_id":null, + "remote_ip_prefix":null, + "security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id":"5831008" + }, + { + "direction":"ingress", + "ethertype":"IPv4", + "id":"f7d45c89-008e-4bab-88ad-d6811724c51c", + "port_range_max":22, + "port_range_min":22, + "protocol":"TCP", + "remote_group_id":null, + "remote_ip_prefix":null, + "security_group_id":"85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id":"5831008" + } + ] +} diff --git a/test/fixtures/rackspace/securityGroups.json b/test/fixtures/rackspace/securityGroups.json new file mode 100644 index 000000000..bdbb1da0c --- /dev/null +++ b/test/fixtures/rackspace/securityGroups.json @@ -0,0 +1,36 @@ +{ + "security_groups": [ + { + "description": "default", + "id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "name": "default", + "security_group_rules": [ + { + "direction": "ingress", + "ethertype": "IPv6", + "id": "c0b09f00-1d49-4e64-a0a7-8a186d928138", + "port_range_max": 22, + "port_range_min": 22, + "protocol": "TCP", + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "5831008" + }, + { + "direction": "ingress", + "ethertype": "IPv4", + "id": "f7d45c89-008e-4bab-88ad-d6811724c51c", + "port_range_max": 22, + "port_range_min": 22, + "protocol": "TCP", + "remote_group_id": null, + "remote_ip_prefix": null, + "security_group_id": "85cc3048-abc3-43cc-89b3-377341426ac5", + "tenant_id": "5831008" + } + ], + "tenant_id": "5831008" + } + ] +} diff --git a/test/fixtures/rackspace/subnet.json b/test/fixtures/rackspace/subnet.json new file mode 100644 index 000000000..b369ffe98 --- /dev/null +++ b/test/fixtures/rackspace/subnet.json @@ -0,0 +1,20 @@ +{ + "subnet": { + "name": "my_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.0.0.2", + "end": "192.255.255.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + } +} diff --git a/test/fixtures/rackspace/subnets.json b/test/fixtures/rackspace/subnets.json new file mode 100644 index 000000000..2c5ba82ab --- /dev/null +++ b/test/fixtures/rackspace/subnets.json @@ -0,0 +1,40 @@ +{ + "subnets": [ + { + "name": "private-subnet", + "enable_dhcp": true, + "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324", + "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "10.0.0.2", + "end": "10.0.0.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "10.0.0.1", + "cidr": "10.0.0.0/24", + "id": "08eae331-0402-425a-923c-34f7cfe39c1b" + }, + { + "name": "my_subnet", + "enable_dhcp": true, + "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22", + "tenant_id": "4fd44f30292945e481c7b8a0c8908869", + "dns_nameservers": [], + "allocation_pools": [ + { + "start": "192.0.0.2", + "end": "192.255.255.254" + } + ], + "host_routes": [], + "ip_version": 4, + "gateway_ip": "192.0.0.1", + "cidr": "192.0.0.0/8", + "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b" + } + ] +} diff --git a/test/fixtures/redistogo/database.json b/test/fixtures/redistogo/database.json deleted file mode 100644 index 314d912b9..000000000 --- a/test/fixtures/redistogo/database.json +++ /dev/null @@ -1 +0,0 @@ -{"activerehashing":true,"appendfsync":"everysec","auto_aof_rewrite_min_size":67108864,"auto_aof_rewrite_percentage":100,"config_command":"54b70572f4e2354bb5af0e5d21a22ad4","connections":10,"created_at":"2012-09-21T23:01:31Z","databases":1,"gc_serial_number":null,"hash_max_zipmap_entries":64,"hash_max_zipmap_value":512,"heroku_hash":null,"id":253739,"info_cache":null,"info_cached_on":null,"label":"ray-9441","last_backup_at":null,"last_bgrewriteaof_at":null,"list_max_ziplist_entries":512,"list_max_ziplist_value":64,"log_level":"notice","master_host":null,"master_password":null,"master_port":null,"maxmemory_policy":"volatile-lru","maxmemory_samples":3,"memory":5242880,"memory_limit_notification":false,"no_appendfsync_on_rewrite":"no","password":"26a5f9c140b50f58e6e414f5f62af0bd","persistence":"snapshot","plan":"nano","port":9441,"server_id":36,"set_max_intset_entries":512,"slave":false,"slave_serve_stale_data":true,"slowlog_enabled":false,"slowlog_log_slower_than":50000,"slowlog_max_len":102,"snapshots":["900 1","300 10","60 10000"],"status":"starting","timeout":150,"trial_expires_at":null,"updated_at":"2012-09-21T23:01:31Z","user_id":123,"version":"2.4.11","vm_enabled":false,"vm_max_memory":4718592,"vm_max_threads":4,"vm_page_size":32,"vm_size":0,"zset_max_ziplist_entries":128,"zset_max_ziplist_value":64} \ No newline at end of file diff --git a/test/fixtures/testkey b/test/fixtures/testkey index 947f4c830..9c8e8e42c 100644 --- a/test/fixtures/testkey +++ b/test/fixtures/testkey @@ -1,3 +1,8 @@ -----BEGIN RSA PRIVATE KEY----- -U29NZVRoSW5nRnVOaEVyRWFOZE90aEVyc1JhTmRvTXNDaEFyQXRFcmVz +MIIBOwIBAAJBAL6YfTXeMD+4//GFMdr4YPyXPwGv0dm791+AYyajULskbCnEMyAUslPNYA8O +WMtRiJgLSozPCcc7m79KxhOn/U0CAwEAAQJALeODCgwz67O8E8dw6Hqyxz4XEm3lhZnnBlxg +AvaBQn0o7LYVOtEZZbRfQRBdgovdoNa2jqkHGNQiR14YxT7f4QIhAOpDZJKHVy/crzfSinX9 +lwig+1SmBa1l58AgXU1LGLEFAiEA0EfU4TwtLTX7V0d4/YKKOvgDl2PdZsxi+h0tbMnMbakC +ID3qC+WyQXfT4rdlPNUMbeOI8IQh0PRQL50WsLIh++elAiEAvJrvaO6nMjOZ4FU2eMpHBlMk +XWjvSnF2h2r4gXTTi9ECIQDiA9SPQVbZRUX+OGHUerkGINj7/mPVCuc0h5qicAQV0w== -----END RSA PRIVATE KEY----- diff --git a/test/fixtures/versions.json b/test/fixtures/versions.json index 85a5fdf83..c583c18e3 100644 --- a/test/fixtures/versions.json +++ b/test/fixtures/versions.json @@ -1 +1 @@ -{"joyent": "6.5.0", "rackspace": "v2", "amazon": "2012-04-01", "azure": "2012-03-01", "openstack": "v2"} \ No newline at end of file +{"rackspace": "v2", "amazon": "2014-06-15", "azure": "2012-03-01", "openstack": "v2", "hp": "v1","oneandone":"1.7"} diff --git a/test/helpers/azureNock.js b/test/helpers/azureNock.js index 138627d30..ca1730ce6 100644 --- a/test/helpers/azureNock.js +++ b/test/helpers/azureNock.js @@ -23,11 +23,13 @@ */ var azureApi = require('../../lib/pkgcloud/azure/utils/azureApi'), - _ = require('underscore'), + _ = require('lodash'), requestId = 'b67cc525-ecc5-4661-8fd6-fb3e57d724f5', - PATH = require('path'), helpers; +// Declaring variables for helper functions defined later +var serverStatusReply; + exports.serverTest = function (nock, testHelpers) { helpers = testHelpers; @@ -47,19 +49,19 @@ exports.serverTest = function (nock, testHelpers) { .defaultReplyHeaders({'x-ms-request-id': requestId, 'Content-Type': 'application/xml'}) // createServer() create-test-ids2 .post('/azure-account-subscription-id/services/hostedservices/create-test-ids2/deployments', helpers.loadFixture('azure/create-test-ids2.xml')) - .reply(202, "", {}) + .reply(202, '', {}) // createServer() test-reboot .post('/azure-account-subscription-id/services/hostedservices/test-reboot/deployments', helpers.loadFixture('azure/create-test-reboot.xml')) - .reply(202, "", {}) + .reply(202, '', {}) // destroyServer() .delete('/azure-account-subscription-id/services/hostedservices/create-test-ids2/deployments/create-test-ids2') - .reply(202, "", {}) + .reply(202, '', {}) // shutDown server .post('/azure-account-subscription-id/services/hostedservices/create-test-ids2/deployments/create-test-ids2/roleInstances/create-test-ids2/Operations', helpers.loadFixture('azure/shutdown-role.xml')) - .reply(201, "", {}) + .reply(201, '', {}) // rebootServer() .post('/azure-account-subscription-id/services/hostedservices/test-reboot/deployments/test-reboot/roleInstances/test-reboot/Operations', 'RestartRoleOperation') - .reply(202, "", {}); + .reply(202, '', {}); // getServer() status nock('https://management.core.windows.net') @@ -91,31 +93,31 @@ exports.serverTest = function (nock, testHelpers) { nock('https://management.core.windows.net') .defaultReplyHeaders({'x-ms-request-id': requestId, 'Content-Type': 'application/xml'}) .get('/azure-account-subscription-id/services/hostedservices') - .reply(200, "https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z", {}) + .reply(200, 'https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z', {}) .get('/azure-account-subscription-id/services/hostedservices') - .reply(200, "https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z", {}) + .reply(200, 'https://management.core.windows.net/azure-account-subscription-id/services/hostedservices/create-test-ids2create-test-ids2service created by pkgcloudEast USCreated2012-11-11T18:13:55Z2012-11-11T18:14:37Z', {}) .get('/azure-account-subscription-id/services/hostedservices/test-reboot?embed-detail=true') .reply(404,helpers.loadFixture('azure/hosted-service-404.xml'),{}) .post('/azure-account-subscription-id/services/hostedservices', helpers.loadFixture('azure/create-test-reboot-hosted-service.xml')) - .reply(201, "", {}) + .reply(201, '', {}) //delete hosted service .delete('/azure-account-subscription-id/services/hostedservices/create-test-ids2') - .reply(200, "", {}) + .reply(200, '', {}) .get('/azure-account-subscription-id/services/hostedservices/create-test-ids2?embed-detail=true') .reply(404,helpers.loadFixture('azure/hosted-service-404.xml'),{}) .post('/azure-account-subscription-id/services/hostedservices', helpers.loadFixture('azure/create-test-ids2-hosted-service.xml')) - .reply(201, "", {}); + .reply(201, '', {}); // VM OSImage disk nock('https://management.core.windows.net') .defaultReplyHeaders({'x-ms-request-id': requestId, 'Content-Type': 'application/xml'}) .delete('/azure-account-subscription-id/services/disks/create-test-ids2-create-test-ids2-0-20121111181413') - .reply(200, "", {}) + .reply(200, '', {}); // VM image blob nock('http://test-storage-account.' + azureApi.STORAGE_ENDPOINT) .delete('/vhd/create-test-ids2.vhd') - .reply(202, "", helpers.azureDeleteResponseHeaders()); + .reply(202, '', helpers.azureDeleteResponseHeaders()); // Certificates nock('https://management.core.windows.net') @@ -123,9 +125,9 @@ exports.serverTest = function (nock, testHelpers) { // need to filter request body because base64 of cert is different on mac/windows .filteringRequestBody(/.*/, '*') .post('/azure-account-subscription-id/services/hostedservices/test-reboot/certificates', '*') - .reply(202, "", {}) + .reply(202, '', {}) .post('/azure-account-subscription-id/services/hostedservices/create-test-ids2/certificates', '*') - .reply(202, "", {}); + .reply(202, '', {}); // Get Operations status requests // we need a lot of these @@ -189,22 +191,11 @@ exports.serverTest = function (nock, testHelpers) { * @param helpers - test helper object * @return {String} - the xml reply containing the server name and status */ -var serverStatusReply = function (name, status) { +serverStatusReply = function (name, status) { var template = helpers.loadFixture('azure/server-status-template.xml'), - params = {NAME: name, STATUS: status}; - - var result = _.template(template, params); - return result; -}; + params = {NAME: name, STATUS: status}, + compiled = _.template(template); -var filterPath = function (path) { - var name = PATH.basename(path); - if (path.search('embed-detail=true') !== -1) { - return '/getStatus?name=' + name; - } - - return path; + return compiled(params); }; - - diff --git a/test/helpers/index.js b/test/helpers/index.js index 3a10e33e4..2909d4c48 100644 --- a/test/helpers/index.js +++ b/test/helpers/index.js @@ -9,37 +9,6 @@ helpers.createClient = function createClient(provider, service, config) { config = config || helpers.loadConfig(provider); config.provider = provider; - // use your key for testing, so our credentials dont need to go in the repo - if (provider === 'joyent') { - if (!config.username) { - if (!config.account) { - config.account = process.env.SDC_CLI_ACCOUNT; - } - - if (!config.identity) { - if (process.env.SDC_CLI_IDENTITY) { - config.identity = process.env.SDC_CLI_IDENTITY; - } else { - config.identity = process.env.HOME + '/.ssh/id_rsa'; - } - } - - if (!config.keyId) { - if (process.env.SDC_CLI_KEY_ID) { - config.keyId = process.env.SDC_CLI_KEY_ID; - } else { - config.keyId = 'id_rsa'; - } - } - - if (config.account) { - config.keyId = '/' + config.account + '/keys/' + config.keyId; - config.key = fs.readFileSync(config.identity,'ascii'); - } else { - throw new Error("Can't test without username and account"); - } - } - } return pkgcloud[service].createClient(config); }; @@ -80,16 +49,16 @@ helpers.loadFixture = function loadFixture(path, json) { helpers.personalityPost = function persPost(pubkey) { return JSON.stringify({ - "server": { - "name": "create-personality-test", - "image": 49, - "flavor": 1, - "personality": [{ - "path": "/root/.ssh/authorized_keys", - "contents": pubkey.toString('base64') + server: { + name: 'create-personality-test', + image: 49, + flavor: 1, + personality: [{ + path: '/root/.ssh/authorized_keys', + contents: pubkey.toString('base64') }], - "flavorId": 1, - "imageId": 49 + flavorId: 1, + imageId: 49 } }); }; @@ -100,15 +69,18 @@ helpers.selectInstance = function selectInstance(client, callback) { return (instance.status == instance.STATUS.running); }); if (ready.length === 0) { - console.log('ERROR: Is necessary have Instances actived for this test.'); + console.log('ERROR: No running instances found.'); } return ready[0]; } client.getInstances(function (err, instances) { - if (err) throw new Error(err); + if (err) { + throw new Error(err); + } + if (instances.length === 0) { - throw new Error({ message:'No instances found.' }) + throw new Error({ message:'No instances found.' }); } callback(filterInstances(instances)); }); @@ -129,7 +101,6 @@ helpers.authFilter = function authFilter(body) { helpers.azureResponseHeaders = function azureHeaders(headers) { var headers = headers || {}; - headers['transfer-encoding'] = 'chunked'; headers['last-modified'] = 'Sat, 10 Nov 2012 14:15:36 GMT'; headers['x-ms-request-id'] = '0ec15c65-970b-4342-bf34-383650212189'; headers['x-ms-version'] = '2011-08-18'; @@ -142,7 +113,6 @@ helpers.azureResponseHeaders = function azureHeaders(headers) { helpers.azureDeleteResponseHeaders = function azureHeaders(headers) { var headers = headers || {}; - headers['transfer-encoding'] = 'chunked'; headers['x-ms-request-id'] = '0ec15c65-970b-4342-bf34-383650212189'; headers['x-ms-version'] = '2011-08-18'; headers.server = 'Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0'; @@ -152,7 +122,6 @@ helpers.azureDeleteResponseHeaders = function azureHeaders(headers) { helpers.azureGetFileResponseHeaders = function azureHeaders(headers) { var headers = headers || {}; - headers['transfer-encoding'] = 'chunked'; headers['accept-ranges'] = 'bytes'; headers['x-ms-lease-status'] = 'unlocked'; headers['x-ms-blob-type'] = 'BlockBlob'; @@ -180,6 +149,10 @@ helpers.getOpenstackAuthResponse = function (time) { return helpers._getOpenstackStandardResponse('../fixtures/openstack/realToken.json', time); }; +helpers.gethpAuthResponse = function (time) { + return helpers._getOpenstackStandardResponse('../fixtures/hp/realToken.json', time); +}; + helpers._getOpenstackStandardResponse = function(file, time) { if (!time) { time = new Date(new Date().getTime() + (1000 * 60 * 60 * 24)); @@ -190,6 +163,71 @@ helpers._getOpenstackStandardResponse = function(file, time) { response.access.token.expires = time.toString(); return response; -} +}; + +helpers.setupAuthenticationMock = function (authHockInstance, provider) { + if (provider === 'rackspace') { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); + } + else if (provider === 'openstack') { + authHockInstance + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + } + else if (provider === 'hp') { + authHockInstance.post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .replyWithFile(200, __dirname + '/../fixtures/hp/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../fixtures/hp/tenantId.json') + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + }, + tenantId: '5ACED3DC3AA740ABAA41711243CC6949' + } + }) + .reply(200, helpers.gethpAuthResponse()); + } + else { + throw new Error('provider ['+provider+'] not supported'); + } +}; helpers.pkgcloud = pkgcloud; diff --git a/test/hp/common/client-test.js b/test/hp/common/client-test.js new file mode 100644 index 000000000..a5b7e12e2 --- /dev/null +++ b/test/hp/common/client-test.js @@ -0,0 +1,113 @@ +/* +* client-test.js: Tests for pkgcloud HP client functionality. +* +* (C) 2014 Phani Raj. +* +*/ + +var pkgcloud = require('../../../lib/pkgcloud'); + +describe('pkgcloud/hp/client', function () { + + describe('Region validation', function () { + it('User should specify authUrl: compute client', function() { + (function () { + pkgcloud.compute.createClient({ + provider: 'hp', + username: 'username', + password: 'password' + }); + }).should.throw(new Error('authUrl is invalid')); + }); + + it('User should specify authUrl: storage client', function() { + (function () { + pkgcloud.storage.createClient({ + provider: 'hp', + username: 'username', + password: 'password' + }); + }).should.throw(new Error('authUrl is invalid')); + }); + + + it('User can specify custom region: storage client', function() { + var customRegionClient = pkgcloud.storage.createClient({ + provider: 'hp', + username: 'username', + password: 'password', + region: 'mycustomregion', + authUrl: 'http://my-identity-service.com' + }); + + customRegionClient.config.should.have.property('region','mycustomregion'); + }); + + it('User can specify custom region: compute client', function() { + var customRegionClient = pkgcloud.compute.createClient({ + provider: 'hp', + username: 'username', + password: 'password', + region: 'mycustomregion', + authUrl: 'http://my-identity-service.com' + }); + + customRegionClient.config.should.have.property('region','mycustomregion'); + }); + }); + + describe('Private cloud Uri resolution', function () { + var eastUSRegion = 'region-b.geo-1', westUSRegion='region-a.geo-1'; + it('Client will not resolve URI if useInternal is true : East US',function(){ + (function () { + pkgcloud.compute.createClient({ + provider: 'hp', + username: 'username', + password: 'password', + useInternal: true, + region: eastUSRegion + }); + }).should.throw('authUrl is invalid'); + + }); + + it('Client will not resolve URI if useInternal is true : West US',function(){ + (function () { + pkgcloud.compute.createClient({ + provider: 'hp', + username: 'username', + password: 'password', + useInternal: true, + region: westUSRegion + }); + }).should.throw('authUrl is invalid'); + + it('User can specify custom url for private cloud : West US',function(){ + var privateClient = pkgcloud.compute.createClient({ + provider: 'hp', + username: 'username', + password: 'password', + useInternal: true, + authUrl: 'http://my-internal-identity-service.com', + region: westUSRegion + }); + + privateClient.config.should.have.property('authUrl','http://my-internal-identity-service.com'); + }); + + it('User can specify custom url for private cloud : East US',function(){ + var privateClient = pkgcloud.compute.createClient({ + provider: 'hp', + username: 'username', + password: 'password', + useInternal: true, + authUrl: 'http://my-internal-identity-service.com', + region: eastUSRegion + }); + + privateClient.config.should.have.property('authUrl','http://my-internal-identity-service.com'); + }); + + }); + }); +}); diff --git a/test/hp/compute/authentication-test.js b/test/hp/compute/authentication-test.js new file mode 100644 index 000000000..a5db897de --- /dev/null +++ b/test/hp/compute/authentication-test.js @@ -0,0 +1,222 @@ +/* +* authentication-test.js: Tests for pkgcloud hp compute authentication +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + macros = require('../macros'), + helpers = require('../../helpers'), + mock = !!process.env.MOCK; + +describe('pkgcloud/hp/compute/authentication', function () { + var client, authHockInstance, hockInstance, authServer, server; + + describe('The pkgcloud hp Compute client', function () { + + before(function (done) { + client = helpers.createClient('hp', 'compute'); + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('should have core methods defined', function() { + macros.shouldHaveCreds(client); + }); + + describe('the auth() method with a valid username and api key', function () { + beforeEach(function (done) { + + client = helpers.createClient('hp', 'compute'); + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.gethpAuthResponse()); + } + + client.auth(function (e) { + should.not.exist(e); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + it('should update the config with appropriate urls', function () { + client._identity.should.be.a.Object; + }); + }); + + describe('the auth() method with an invalid username and api key', function () { + + var badClient = helpers.createClient('hp', 'compute', { + username: 'fake', + apiKey: 'data', + authUrl: 'localhost:12346', + protocol: 'http://', + region: 'custom region' + }); + + var err; + + beforeEach(function (done) { + + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'fake', + secretKey: 'data' + } + } + }) + .reply(401, { + unauthorized: { + message: 'Username or api key is invalid', code: 401 + } + }); + } + + badClient.auth(function (e) { + err = e; + authHockInstance && authHockInstance.done(); + done(); + }); + + }); + + it('should respond with Error code 401', function () { + should.exist(err); + // TODO resolve identity responses + }); + }); + + describe('auth tokens should expire', function () { + var tokenExpiry; + + beforeEach(function (done) { + + client = helpers.createClient('hp', 'compute'); + + client.on('log::*', function(message, obj) { + // Ken: why is this console log present? + if (this.event !== 'log::trace') { + console.log(message); + console.dir(obj); + } + }); + + if (mock) { + + var response = helpers.gethpAuthResponse(new Date(new Date().getTime() + 1)); + + tokenExpiry = response.access.token.expires; + + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, response); + } + + client.auth(function (e) { + should.not.exist(e); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + it('should update the config with appropriate urls', function () { + client._identity.should.be.a.Object; + client._identity.token.expires.toString().should.equal(tokenExpiry); + }); + + it('should expire the token and set authorized to false', function(done) { + setTimeout(function() { + client._isAuthorized().should.equal(false); + done(); + }, 5); + }); + + it('should expire the token and reauth on next call', function (done) { + + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.gethpAuthResponse()); + + hockInstance + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/images/detail') + .replyWithFile(200, __dirname + '/../../fixtures/hp/images.json'); + } + + setTimeout(function () { + client._isAuthorized().should.equal(false); + client.getImages(function(err, images) { + client._isAuthorized().should.equal(true); + should.not.exist(err); + should.exist(images); + hockInstance && hockInstance.done(); + authHockInstance && authHockInstance.done(); + done(); + }); + }, 5); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + }); +}); diff --git a/test/hp/macros.js b/test/hp/macros.js new file mode 100644 index 000000000..78c95fc77 --- /dev/null +++ b/test/hp/macros.js @@ -0,0 +1,105 @@ +/* + * macros.js: Tests macros for Rackspace + * + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * + */ + +var fs = require('fs'), + filed = require('filed-mimefix'), + assert = require('../helpers/assert'); + +exports.shouldHaveCreds = function (client) { + return function () { + assert.isObject(client.config); + assert.include(client.config, 'username'); + assert.include(client.config, 'apiKey'); + + assert.isFunction(client.auth); + }; +}; + +exports.shouldCreateContainer = function (client, name, message) { + message = message || 'when creating a container'; + + var context = {}; + context[message] = { + topic: function () { + client.createContainer(name, this.callback); + }, + 'should return a valid container': function (err, container) { + assert.isNull(err); + assert.assertContainer(container); + } + }; + + return { + 'The pkgcloud Rackspace storage client': { + 'the createContainer() method': context + } + }; +}; + +exports.shouldDestroyContainer = function (client, name) { + return { + 'The pkgcloud Rackspace storage client': { + 'the destroyContainer() method': { + topic: function () { + client.destroyContainer(name, this.callback); + }, + 'should return true': function (err, success) { + assert.isTrue(success); + } + } + } + }; +}; + +exports.upload = {}; + +exports.upload.fullpath = function (client, options) { + return { + topic: function () { + client.upload(options, function () { }) + .on('end', this.callback); + }, + 'should raise the `end` event': function () { + assert.isTrue(true); + } + }; +}; + +exports.upload.stream = function (client, container, local, remote) { + return { + topic: function () { + client.upload({ + container: container, + remote: remote, + stream: fs.createReadStream(local), + headers: { + 'content-length': fs.statSync(local).size + } + }, function () { }).on('end', this.callback); + }, + 'should raise the `end` event': function () { + assert.isTrue(true); + } + }; +}; + +exports.upload.piped = function (client, container, local, remote) { + return { + topic: function () { + var ustream = client.upload({ + container: container, + remote: remote + }, function () { }); + + filed(local).pipe(ustream); + ustream.on('end', this.callback); + }, + 'should raise the `end` event': function () { + assert.isTrue(true); + } + }; +}; diff --git a/test/hp/network/authentication-test.js b/test/hp/network/authentication-test.js new file mode 100644 index 000000000..613d625cb --- /dev/null +++ b/test/hp/network/authentication-test.js @@ -0,0 +1,221 @@ +/* +* authentication-test.js: Tests for pkgcloud hp network authentication +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + macros = require('../macros'), + helpers = require('../../helpers'), + mock = !!process.env.MOCK; + +describe('pkgcloud/hp/network/authentication', function () { + var client, authHockInstance, hockInstance, authServer, server; + + describe('The pkgcloud hp network client', function () { + + before(function (done) { + client = helpers.createClient('hp', 'network'); + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('should have core methods defined', function() { + macros.shouldHaveCreds(client); + }); + + describe('the auth() method with a valid username and api key', function () { + beforeEach(function (done) { + + client = helpers.createClient('hp', 'compute'); + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.gethpAuthResponse()); + } + + client.auth(function (e) { + should.not.exist(e); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + it('should update the config with appropriate urls', function () { + client._identity.should.be.a.Object; + }); + }); + + describe('the auth() method with an invalid username and api key', function () { + + var badClient = helpers.createClient('hp', 'compute', { + username: 'fake', + apiKey: 'data', + authUrl: 'localhost:12346', + protocol: 'http://', + region: 'custom region' + }); + + var err; + + beforeEach(function (done) { + + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'fake', + secretKey: 'data' + } + } + }) + .reply(401, { + unauthorized: { + message: 'Username or api key is invalid', code: 401 + } + }); + } + + badClient.auth(function (e) { + err = e; + authHockInstance && authHockInstance.done(); + done(); + }); + + }); + + it('should respond with Error code 401', function () { + should.exist(err); + // TODO resolve identity responses + }); + }); + + describe('auth tokens should expire', function () { + var tokenExpiry; + + beforeEach(function (done) { + + client = helpers.createClient('hp', 'compute'); + + client.on('log::*', function(message, obj) { + if (this.event !== 'log::trace') { + console.log(message); + console.dir(obj); + } + }); + + if (mock) { + + var response = helpers.gethpAuthResponse(new Date(new Date().getTime() + 1)); + + tokenExpiry = response.access.token.expires; + + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, response); + } + + client.auth(function (e) { + should.not.exist(e); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + it('should update the config with appropriate urls', function () { + client._identity.should.be.a.Object; + client._identity.token.expires.toString().should.equal(tokenExpiry); + }); + + it('should expire the token and set authorized to false', function(done) { + setTimeout(function() { + client._isAuthorized().should.equal(false); + done(); + }, 5); + }); + + it('should expire the token and reauth on next call', function (done) { + + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.gethpAuthResponse()); + + hockInstance + .get('/v2/5ACED3DC3AA740ABAA41711243CC6949/images/detail') + .replyWithFile(200, __dirname + '/../../fixtures/hp/images.json'); + } + + setTimeout(function () { + client._isAuthorized().should.equal(false); + client.getImages(function(err, images) { + client._isAuthorized().should.equal(true); + should.not.exist(err); + should.exist(images); + hockInstance && hockInstance.done(); + authHockInstance && authHockInstance.done(); + done(); + }); + }, 5); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + }); +}); diff --git a/test/hp/storage/authentication-test.js b/test/hp/storage/authentication-test.js new file mode 100644 index 000000000..c3ddb6eae --- /dev/null +++ b/test/hp/storage/authentication-test.js @@ -0,0 +1,156 @@ +/* +* authentication-test.js: Tests for pkgcloud hp storage authentication +* +* (C) 2014 Hewlett-Packard Development Company, L.P. +* +*/ + +var should = require('should'), + macros = require('../macros'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK; + +describe('pkgcloud/hp/storage/authentication', function () { + describe('The pkgcloud hp Storage client', function () { + it('should have core methods defined', function() { + var client = helpers.createClient('hp', 'storage'); + macros.shouldHaveCreds(client); + }); + + describe('the auth() method', function() { + describe('with a valid user name and api key', function() { + var authHockInstance, authServer; + + before(function(done) { + if (!mock) { + return done(); + } + + authHockInstance = hock.createHock(); + authServer = http.createServer(authHockInstance.handler); + authServer.listen(12346, done); + }); + + it('should respond with 204 and appropriate info', function (done) { + + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.gethpAuthResponse()); + } + + var client = helpers.createClient('hp', 'storage'); + + client.auth(function (err) { + should.not.exist(err); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + it('should update the config with appropriate urls', function (done) { + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.gethpAuthResponse()); + } + + var client = helpers.createClient('hp', 'storage'); + + client.auth(function (err) { + should.not.exist(err); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + after(function(done) { + if (authServer) { + authServer.close(function () { + done(); + }); + } + else { + done(); + } + }); + }); + + describe('with an invalid user name and api key shouldn\'t authenticate', function () { + var authHockInstance, authServer; + + before(function (done) { + if (!mock) { + return done(); + } + + authHockInstance = hock.createHock(); + authServer = http.createServer(authHockInstance.handler); + authServer.listen(12346, done); + }); + + it('should respond with 401 unauthorized', function (done) { + + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'apiAccessKeyCredentials': { + accessKey: 'fake', + secretKey: 'data' + } + } + }) + .reply(401, { + unauthorized: { + message: 'accessKey or api key is invalid', code: 401 + } + }); + } + + var badClient = helpers.createClient('hp', 'compute', { + username: 'fake', + apiKey: 'data', + authUrl: 'localhost:12346', + protocol: 'http://', + region: 'region-a.geo-1' + }); + + badClient.auth(function (err, res) { + should.exist(err); + should.not.exist(res); + authHockInstance && authHockInstance.done(); + done(); + }); + }); + + after(function (done) { + if (authServer) { + authServer.close(function () { + done(); + }); + } + else { + done(); + } + }); + }); + }); + }); +}); diff --git a/test/hp/storage/file-test.js b/test/hp/storage/file-test.js new file mode 100644 index 000000000..451c55acf --- /dev/null +++ b/test/hp/storage/file-test.js @@ -0,0 +1,300 @@ +var helpers = require('../../helpers'), + async = require('async'), + http = require('http'), + hock = require('hock'), + _ = require('lodash'); + +var authenticate = function (hockInstance) { + hockInstance + .post('/v2.0/tokens', { + auth: { + apiAccessKeyCredentials: { + accessKey: 'MOCK-USERNAME', + secretKey: 'MOCK-API-KEY' + } + } + }) + .many() + .reply(200, { + access: { + token: { + expires: '2017-12-26T18:25:46Z', + id: '4bc7c5dabf3e4a49918683437d386b8a', + tenant: { + enabled: true, + id: '5ACED3DC3AA740ABAA41711243CC6949', + name: 'MOCK-USERNAME', + description: 'MOCK-USERNAME' + } + }, + serviceCatalog: [ + { + endpoints: [ + { + region: 'region-a.geo-1', + tenantId: 'HPCloudFS_00aa00aa', + publicURL: 'http://localhost:12345/v1/HPCloudFS_00aa00aa', + internalURL: 'https://snet-storage101.ord1.clouddrive.com/v1/HPCloudFS_00aa00aa' + } + ], + name: 'swift', + type: 'object-store' + } + ], + user: { + username: 'MOCK-USERNAME', + roles_links: [], + id: 'bf3b85477d06430c8044d5b2e5e6dc5f', + roles: [], + name: 'MOCK-USERNAME' + } + } + }); +}; + +describe('pkgcloud/openstack/storage/', function () { + + describe('The openstack Storage client', function () { + + describe('copy', function () { + var client, hockInstance, authHockInstance, server, authServer; + + beforeEach(function (done) { + client = helpers.createClient('hp', 'storage'); + + hockInstance = hock.createHock(); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + afterEach(function (done) { + async.parallel([ + function (next) { + server.close(next); + }, function (next) { + authServer.close(next); + }], done); + }); + + it('should copy within the same container', function (done) { + authenticate(authHockInstance); + hockInstance + .head('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1?format=json') + .replyWithFile(200, __dirname + '/../../fixtures/hp/getFile.json') + .copy('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1', {}, { + destination: '/pkgcloud-test-container-1/pkgcloud-test-file-2' + }) + .reply(200); + + client.getFile('pkgcloud-test-container-1', 'pkgcloud-test-file-1', function (err, file) { + if (err) { + done(err); + } else { + file.copy({ + sourceContainer: 'pkgcloud-test-container-1', + sourceFile: 'pkgcloud-test-file-1', + destinationContainer: 'pkgcloud-test-container-1', + destinationFile: 'pkgcloud-test-file-2' + }, done); + } + }); + }); + + it('should copy within the same container with container objects', function (done) { + authenticate(authHockInstance); + hockInstance + .head('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1') + .reply(200) + .head('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1?format=json') + .replyWithFile(200, __dirname + '/../../fixtures/hp/getFile.json') + .copy('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1', {}, { + destination: '/pkgcloud-test-container-1/pkgcloud-test-file-2' + }) + .reply(200); + + async.waterfall([ + _.bind(client.getContainer, client, 'pkgcloud-test-container-1'), + function (container, next) { + client.getFile(container, 'pkgcloud-test-file-1', function (err, file) { + if (err) { + done(err); + } else { + file.copy({ + sourceContainer: container, + sourceFile: 'pkgcloud-test-file-1', + destinationContainer: container, + destinationFile: 'pkgcloud-test-file-2' + }, next); + } + }); + } + ], done); + }); + + it('should copy within the same container with headers', function (done) { + authenticate(authHockInstance); + hockInstance + .head('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1?format=json') + .replyWithFile(200, __dirname + '/../../fixtures/hp/getFile.json') + .copy('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1', {}, { + destination: '/pkgcloud-test-container-1/pkgcloud-test-file-2', + 'x-delete-after': '1209600' + }) + .reply(200); + + client.getFile('pkgcloud-test-container-1', 'pkgcloud-test-file-1', function (err, file) { + if (err) { + done(err); + } else { + file.copy({ + sourceContainer: 'pkgcloud-test-container-1', + sourceFile: 'pkgcloud-test-file-1', + destinationContainer: 'pkgcloud-test-container-1', + destinationFile: 'pkgcloud-test-file-2', + headers: { 'x-delete-after': 1209600 } + }, done); + } + }); + }); + + it('should copy to a different container', function (done) { + authenticate(authHockInstance); + hockInstance + .head('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1?format=json') + .replyWithFile(200, __dirname + '/../../fixtures/hp/getFile.json') + .copy('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1', {}, { + destination: '/pkgcloud-test-container-2/pkgcloud-test-file-2' + }) + .reply(200); + + client.getFile('pkgcloud-test-container-1', 'pkgcloud-test-file-1', function (err, file) { + if (err) { + done(err); + } else { + file.copy({ + sourceContainer: 'pkgcloud-test-container-1', + sourceFile: 'pkgcloud-test-file-1', + destinationContainer: 'pkgcloud-test-container-2', + destinationFile: 'pkgcloud-test-file-2' + }, done); + } + }); + }); + + it('should copy to a different container defaulting to original file name', function (done) { + authenticate(authHockInstance); + hockInstance + .head('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1?format=json') + .replyWithFile(200, __dirname + '/../../fixtures/hp/getFile.json') + .copy('/v1/HPCloudFS_00aa00aa/pkgcloud-test-container-1/pkgcloud-test-file-1', {}, { + destination: '/pkgcloud-test-container-2/pkgcloud-test-file-1' + }) + .reply(200); + + client.getFile('pkgcloud-test-container-1', 'pkgcloud-test-file-1', function (err, file) { + if (err) { + done(err); + } else { + file.copy({ + sourceContainer: 'pkgcloud-test-container-1', + sourceFile: 'pkgcloud-test-file-1', + destinationContainer: 'pkgcloud-test-container-2' + }, done); + } + }); + }); + + //testing all permutations of containers and source files as objects and strings + _.each([ + { name: 'container-1', object: true }, + { name: 'container-1', object: false } + ], function (srcContainer) { + _.each([ + { name: 'container-2', object: true }, + { name: 'container-2', object: false } + ], function (dstContainer) { + _.each([ + { name: 'file-1', object: true }, + { name: 'file-1', object: false } + ], function (srcFile) { + + var name = 'should copy from '; + name += srcContainer.object ? 'object src container ' : 'string src container '; + name += 'to '; + name += dstContainer.object ? 'object dst container ' : 'string dst container '; + name += 'with '; + name += srcFile.object ? 'object src file' : 'string src file'; + + it(name, function (done) { + authenticate(authHockInstance); + + async.series({ + sourceContainer: function (next) { + if (srcContainer.object) { + hockInstance + .head('/v1/HPCloudFS_00aa00aa/' + srcContainer.name) + .reply(200); + + client.getContainer(srcContainer.name, next); + } else { + next(null, srcContainer.name); + } + }, + + destinationContainer: function (next) { + if (dstContainer.object) { + hockInstance + .head('/v1/HPCloudFS_00aa00aa/' + dstContainer.name) + .reply(200); + + client.getContainer(dstContainer.name, next); + } else { + next(null, dstContainer.name); + } + } + }, function (err, res) { + if (err) { + done(err); + } else { + hockInstance + .head('/v1/HPCloudFS_00aa00aa/' + srcContainer.name + '/' + srcFile.name + '?format=json') + .replyWithFile(200, __dirname + '/../../fixtures/hp/getFile.json') + .copy('/v1/HPCloudFS_00aa00aa/' + srcContainer.name + '/' + srcFile.name, {}, { + destination: '/' + dstContainer.name + '/' + srcFile.name + }) + .reply(200); + + client.getFile(srcContainer.name, srcFile.name, function (err, file) { + if (err) { + done(err); + } else { + file.copy({ + sourceContainer: res.sourceContainer, + sourceFile: srcFile.object ? file : srcFile.name, + destinationContainer: res.destinationContainer + }, done); + } + }); + } + }); + }); + }); + + }); + }); + + }); + + }); +}); diff --git a/test/iriscouch/databases/databases-redis-test.js b/test/iriscouch/databases/databases-redis-test.js deleted file mode 100644 index 386606570..000000000 --- a/test/iriscouch/databases/databases-redis-test.js +++ /dev/null @@ -1,135 +0,0 @@ -/* -* databases-redis-test.js: Tests for IrisCouch Redis database service -* -* (C) 2012 Nodejitsu Inc. -* MIT LICENSE -* -*/ - -var helpers = require('../../helpers'), - should = require('should'), - hock = require('hock'), - mock = !!process.env.MOCK; - -describe('pkgcloud/iriscouch/databases-redis', function () { - var context = {}, client, server; - - before(function (done) { - client = helpers.createClient('iriscouch', 'database'); - - if (!mock) { - return done(); - } - - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - done(); - }); - }); - - it('the create() method with correct options should respond correctly', function(done) { - var subdomain = (mock ? 'nodejitsudb43639' : 'nodejitsudb' + Math.floor(Math.random() * 100000)); - context.tempPassword = (mock ? 'sTTi:lh9vCF[' : randomPassword(12).replace("\\", "")); - - if (mock) { - server - .post('/hosting_public', helpers.loadFixture('iriscouch/database-redis.json')) - .reply(201, { ok: true, - id: 'Redis/nodejitsudb43639', - rev: '1-63cf360ebc115cdc8a709a910fdef6d7' - }); - } - - client.create({ - subdomain: subdomain, - first_name: "Marak", - last_name: "Squires", - email: "marak.squires@gmail.com", - type: "redis", // For redis instead of couch just put type to redis - password: context.tempPassword - }, function(err, database) { - should.not.exist(err); - should.exist(database); - should.exist(database.id); - should.exist(database.uri); - context.databaseId = database.id; - database.password.should.equal([database.host, context.tempPassword].join(':')); - - server && server.done(); - done(); - }); - }); - - describe('the create() method with invalid options like', function() { - it('no options should respond with errors', function(done) { - client.create(function(err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no invalid options should respond with errors', function (done) { - client.create({ invalid: 'keys' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no email should respond with errors', function (done) { - client.create({ - subdomain: 'testDatabase', - first_name: 'Daniel', - last_name: 'Aristizabal'}, - function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no subdomain should respond with errors', function (done) { - client.create({ - email: 'daniel@nodejitsu.com', - first_name: 'Daniel', - last_name: 'Aristizabal'}, - function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no names should respond with errors', function (done) { - client.create({ email: 'daniel@nodejitsu.com', subdomain: 'testDatabase'}, - function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - server.close(done); - }); - -}); - -// -// Just a quick and lazy random password generator -// -function randomPassword(length) { - if (length == 1) { - return String.fromCharCode(Math.floor(Math.random() * (122 - 48 + 1)) + 48); - } - return String.fromCharCode(Math.floor(Math.random() * (122 - 48 + 1)) + 48) + randomPassword(length - 1); -} \ No newline at end of file diff --git a/test/iriscouch/databases/databases-test.js b/test/iriscouch/databases/databases-test.js deleted file mode 100644 index 6de16f013..000000000 --- a/test/iriscouch/databases/databases-test.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * databases-test.js: Tests for IrisCouch database service - * - * (C) 2012 Nodejitsu Inc. - * MIT LICENSE - * - */ - -var helpers = require('../../helpers'), - should = require('should'), - hock = require('hock'), - mock = !!process.env.MOCK; - -describe('pkgcloud/iriscouch/databases', function () { - var context = {}, client, server; - - before(function (done) { - client = helpers.createClient('iriscouch', 'database'); - - if (!mock) { - return done(); - } - - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - done(); - }); - }); - - it('the create() method with correct options should respond correctly', function (done) { - var subdomain = (mock ? 'nodejitsudb908' : 'nodejitsudb' + Math.floor(Math.random() * 100000)); - - if (mock) { - - client._getCouchPollingUrl = function() { - return 'http://localhost:12345'; - }; - - server - .post('/hosting_public', helpers.loadFixture('iriscouch/database.json')) - .reply(201, { - ok: true, - id: 'Server/nodejitsudb908', - rev: '1-dc91e4ee524420e6f32607b0c24151de' - }) - .get('/') - .reply(200); - } - - client.create({ - subdomain: subdomain, - first_name: 'Marak', - last_name: 'Squires', - email: 'marak.squires@gmail.com' - }, function (err, database) { - should.not.exist(err); - should.exist(database); - should.exist(database.id); - should.exist(database.uri); - context.databaseId = database.id; - - server && server.done(); - done(); - }); - }); - - describe('the create() method with invalid options like', function () { - it('no options should respond with errors', function (done) { - client.create(function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no invalid options should respond with errors', function (done) { - client.create({ invalid: 'keys' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no email should respond with errors', function (done) { - client.create({ - subdomain: 'testDatabase', - first_name: 'Daniel', - last_name: 'Aristizabal'}, - function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no subdomain should respond with errors', function (done) { - client.create({ - email: 'daniel@nodejitsu.com', - first_name: 'Daniel', - last_name: 'Aristizabal'}, - function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no names should respond with errors', function (done) { - client.create({ email: 'daniel@nodejitsu.com', subdomain: 'testDatabase'}, - function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - server.close(done); - }); - -}); diff --git a/test/mongohq/databases/databases-test.js b/test/mongohq/databases/databases-test.js deleted file mode 100644 index ccda3730d..000000000 --- a/test/mongohq/databases/databases-test.js +++ /dev/null @@ -1,111 +0,0 @@ -/* -* databases-test.js: Tests for MongoHQ databases service -* -* (C) 2012 Nodejitsu Inc. -* MIT LICENSE -* -*/ - -var helpers = require('../../helpers'), - should = require('should'), - hock = require('hock'), - mock = !!process.env.MOCK; - -describe('pkgcloud/mongohq/databases', function () { - var context = {}, client, server; - - before(function (done) { - client = helpers.createClient('mongohq', 'database'); - - if (!mock) { - return done(); - } - - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - done(); - }); - }); - - it('the create() method with correct options should respond correctly', function (done) { - - if (mock) { - server - .post('/provider/resources', "app_id=testDatabase&plan=free") - .reply(200, helpers.loadFixture('mongohq/database.json')); - } - - client.create({ - plan: 'free', - name: 'testDatabase' - }, function (err, database) { - should.not.exist(err); - should.exist(database); - should.exist(database.id); - should.exist(database.uri); - should.exist(database.username); - should.exist(database.password); - context.databaseId = database.id; - - server && server.done(); - done(); - }); - }); - - it('the remove() method with correct options should respond correctly', function (done) { - - if (mock) { - server - .delete('/provider/resources/63562') - .reply(200, "OK"); - } - - client.remove(context.databaseId, function (err, confirm) { - should.not.exist(err); - should.exist(confirm); - confirm.should.equal('deleted');; - - server && server.done(); - done(); - }); - }); - - describe('the create() method with invalid options like', function () { - it('no options should respond with errors', function (done) { - client.create(function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no invalid options should respond with errors', function (done) { - client.create({ invalid: 'keys' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - describe('the remove() method with invalid options like', function () { - it('no options should respond with errors', function (done) { - client.remove(function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - server.close(done); - }); -}); diff --git a/test/mongolab/databases/databases-test.js b/test/mongolab/databases/databases-test.js deleted file mode 100644 index 21e62ad83..000000000 --- a/test/mongolab/databases/databases-test.js +++ /dev/null @@ -1,457 +0,0 @@ -/* -* databases-test.js: Tests for MongoLab databases service -* -* (C) 2012 Nodejitsu Inc. -* MIT LICENSE -* -*/ - -var helpers = require('../../helpers'), - should = require('should'), - hock = require('hock'), - mock = !!process.env.MOCK; - -describe('pkgcloud/mongolab/databases', function () { - var context = {}, client, server; - - before(function (done) { - client = helpers.createClient('mongolab', 'database'); - - if (!mock) { - return done(); - } - - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - done(); - }); - }); - - it('the createAccount() method with correct options should respond correctly', function (done) { - - if (mock) { - server - .post('/api/1/partners/nodejitsu/accounts', { - name: 'nodejitsu_daniel', - adminUser: { - email: 'daniel@nodejitsu.com' - } - }) - .reply(200, helpers.loadFixture('mongolab/user.json')) - } - - client.createAccount({ - name: 'daniel', - email: 'daniel@nodejitsu.com' - }, function (err, response) { - should.not.exist(err); - should.exist(response); - should.exist(response.account); - should.exist(response.account.username); - should.exist(response.account.email); - should.exist(response.account.password); - context.account = response.account; - - server && server.done(); - done(); - }); - }); - -// it('the remove() method with correct options should respond correctly', function (done) { -// -// if (mock) { -// server -// .delete('/provider/resources/63562') -// .reply(200, "OK"); -// } -// -// client.remove(context.databaseId, function (err, confirm) { -// should.not.exist(err); -// should.exist(confirm); -// confirm.should.equal('deleted'); -// ; -// -// server && server.done(); -// done(); -// }); -// }); - - describe('the createAccount() method with invalid options like', function () { - it('no options should respond with errors', function (done) { - client.createAccount(function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no invalid options should respond with errors', function (done) { - client.createAccount({ invalid: 'keys' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no email should respond with errors', function (done) { - client.createAccount({ name: 'testDatabase' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - describe('the createAccount() method with custom passwords with', function () { - it('no numbers should respond with errors', function(done) { - client.createAccount({ - name: 'custompassword', - email: 'custom@password.com', - password: 'mycustompassword' - }, function(err, response) { - should.exist(err); - should.not.exist(response); - - done(); - }); - }); - - it('with numbers should respond with success', function(done) { - - if (mock) { - server - .post('/api/1/partners/nodejitsu/accounts', { - name: 'nodejitsu_custompassword', - adminUser: { - email: 'custom@password.com', - password: 'my1custom2password' - } - }) - .reply(200, helpers.loadFixture('mongolab/customUser.json')); - } - - client.createAccount({ - name: 'custompassword', - email: 'custom@password.com', - password: 'my1custom2password' - }, function(err, response) { - should.not.exist(err); - should.exist(response); - should.exist(response.account); - should.exist(response.account.username); - should.exist(response.account.email); - context.custompw = response.account; - - server && server.done(); - done(); - }); - }); - }); - - it('the getAccounts() method should respond with all accounts', function(done) { - - if (mock) { - server - .get('/api/1/partners/nodejitsu/accounts') - .reply(200, helpers.loadFixture('mongolab/userList.json')) - } - - client.getAccounts(function(err, accounts) { - should.not.exist(err); - should.exist(accounts); - accounts.should.be.instanceOf(Array); - accounts.should.have.length(2); - accounts.forEach(function(account) { - should.exist(account.username); - should.exist(account.email); - }); - - server && server.done(); - done(); - }); - }); - - it('the getAccount() method should return the matching account', function(done) { - - if (mock) { - server - .get('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel') - .reply(200, { - name: 'nodejitsu_daniel', - adminUser: { - username: 'nodejitsu_daniel', - email: 'daniel@nodejitsu.com' - } - }); - } - - client.getAccount(context.account.username, function (err, account) { - should.not.exist(err); - should.exist(account); - account.username.should.equal(context.account.username); - account.email.should.equal(context.account.email); - - server && server.done(); - done(); - }); - - }); - - it('the create() method with correct options should respond correctly', function (done) { - - if (mock) { - server - .post('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel/databases', helpers.loadFixture('mongolab/reqDatabase.json')) - .reply(200, helpers.loadFixture('mongolab/database.json')); - } - - client.create({ - plan:'free', - name:'testDatabase', - owner: context.account.username - }, function (err, database) { - should.not.exist(err); - should.exist(database); - should.exist(database.id); - should.exist(database.uri); - should.exist(database.username); - should.exist(database.password); - context.databaseId = database.id; - - server && server.done(); - done(); - }); - }); - - describe('the create() method with invalid options like', function () { - it('no options should respond with errors', function (done) { - client.create(function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no invalid options should respond with errors', function (done) { - client.create({ invalid: 'keys' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - - it('no plan should respond with errors', function (done) { - client.create({ name: 'testDatabase' }, function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - describe('the getDatabases() method', function() { - it('with valid options should respond correctly', function (done) { - - if (mock) { - server - .get('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel/databases') - .reply(200, [ { name : 'nodejitsu_daniel_testDatabase' } ]); - } - - client.getDatabases(context.account.username, function (err, databases) { - should.not.exist(err); - should.exist(databases); - databases.should.be.instanceOf(Array); - databases.should.have.length(1); - databases[0].should.be.a('object'); - databases[0].name.should.equal(context.account.username + '_testDatabase'); - context.databaseName = databases[0].name; - - server && server.done(); - done(); - }); - }); - - it('with invalid options should respond with errors', function(done) { - client.getDatabases(function(err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - }); - - describe('the getDatabase() method', function () { - it('with valid options should respond correctly', function (done) { - - if (mock) { - server - .get('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel/databases/nodejitsu_daniel_testDatabase') - .reply(200, { name : 'nodejitsu_daniel_testDatabase' }); - } - - client.getDatabase({ - owner: context.account.username, - name: context.databaseName }, - function (err, database) { - should.not.exist(err); - should.exist(database); - database.should.be.a('object'); - database.name.should.equal(context.account.username + '_testDatabase'); - - server && server.done(); - done(); - }); - }); - - it('with invalid options should respond with errors', function (done) { - client.getDatabase(function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - - it('with no owner should respond with errors', function (done) { - client.getDatabase({ name: 'no-owner' }, function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - - it('with no name should respond with errors', function (done) { - client.getDatabase({ owner: 'no-name' }, function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - }); - - describe('the remove() method', function () { - it('with valid options should respond correctly', function (done) { - - if (mock) { - server - .delete('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel/databases/nodejitsu_daniel_testDatabase') - .reply(200, " null ") - } - - client.remove({ - owner: context.account.username, - name: context.databaseName }, - function (err) { - should.not.exist(err); - - server && server.done(); - done(); - }); - }); - - it('and have no databases left after getDatabases()', function (done) { - - if (mock) { - server - .get('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel/databases') - .reply(200, []); - } - - client.getDatabases(context.account.username, function (err, databases) { - should.not.exist(err); - should.exist(databases); - databases.should.be.instanceOf(Array); - databases.should.have.length(0); - - server && server.done(); - done(); - }); - }); - - it('with invalid options should respond with errors', function (done) { - client.remove(function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - - it('with no owner should respond with errors', function (done) { - client.remove({ name: 'no-owner' }, function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - - it('with no name should respond with errors', function (done) { - client.remove({ owner: 'no-name' }, function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - }); - - describe('the deleteAccount() method', function () { - it('with valid options should respond correctly', function (done) { - - if (mock) { - server - .delete('/api/1/partners/nodejitsu/accounts/nodejitsu_daniel') - .reply(200, " null ") - } - - client.deleteAccount(context.account.username, - function (err) { - should.not.exist(err); - - server && server.done(); - done(); - }); - }); - - it('and delete the other account', function (done) { - - if (mock) { - server - .delete('/api/1/partners/nodejitsu/accounts/nodejitsu_custompassword') - .reply(200, " null "); - } - - client.deleteAccount(context.custompw.username, function (err, databases) { - should.not.exist(err); - - server && server.done(); - done(); - }); - }); - - it('with invalid options should respond with errors', function (done) { - client.deleteAccount(function (err, databases) { - should.exist(err); - should.not.exist(databases); - - done(); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - server.close(done); - }); -}); diff --git a/test/oneandone/blockstorage/test-snapshot.js b/test/oneandone/blockstorage/test-snapshot.js new file mode 100644 index 000000000..862424134 --- /dev/null +++ b/test/oneandone/blockstorage/test-snapshot.js @@ -0,0 +1,142 @@ +/** + * Created by Ali Bazlamit on 8/30/2017. + */ +var server, + _snapshot, + blockStorage, + client; +var should = require('should'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK, + Snapshot = require('../../../lib/pkgcloud/oneandone/blockstorage/snapshot').Snapshot; + +var srvr_options = { + name: 'create-test-oao', + flavor: '81504C620D98BCEBAA5202D145203B4B', + image: '6631A1589A2CC87FEA9B99AB07399281', + location: '4EFAD5836CE43ACA502FD5B99BEE44EF', + token: process.env.OAO_TOKEN +}; + +describe('Snapshot tests', function () { + this.timeout(18000000); + var hockInstance, mockServer; + + before(function (done) { + client = helpers.createClient('oneandone', 'compute', srvr_options); + blockStorage = helpers.createClient('oneandone', 'blockstorage', srvr_options); + if (!mock) { + + client.createServer(srvr_options, function (err, srv1) { + should.not.exist(err); + should.exist(srv1); + server = srv1; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + blockStorage.createSnapshot(server, function (err, snapshot) { + should.not.exist(err); + should.exist(snapshot); + _snapshot = snapshot; + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + } else { + hockInstance = hock.createHock({ throwOnUnmatched: false }); + hockInstance.filteringRequestBody(helpers.authFilter); + mockServer = http.createServer(hockInstance.handler); + mockServer.listen(12345, done); + } + }); + + after(function (done) { + if (hockInstance) { + mockServer.close(function () { + done(); + }); + } + else { + var deleteOps = {}; + deleteOps.server = server; + deleteOps.snapshot = _snapshot; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + blockStorage.deleteSnapshot(deleteOps, function (err, response) { + should.not.exist(err); + should.exist(response); + server.setWait({ status: server.STATUS.running }, 15000, function (err) { + if (err) { + console.dir(err); + return; + } + client.destroyServer(server, function (err, response) { + should.not.exist(err); + should.exist(response); + done(); + }); + }); + }); + }); + } + }); + + it('the getSnapshots() method should return a list of snapshots', function (done) { + if (mock) { + hockInstance + .get('servers/{server_id}/snapshots') + .reply(200, helpers.loadFixture('oneandone/snapshots.json')); + } + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + blockStorage.getSnapshots(server, function (err, snapshots) { + should.not.exist(err); + should.exist(snapshots); + + snapshots.should.be.an.Array; + + snapshots.forEach(function (snp) { + snp.should.be.instanceOf(Snapshot); + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the updateSnapshot() method should restore a snapshot into a server', function (done) { + if (mock) { + hockInstance + .get('/servers/92AA60BEC8333A21EDB9EAAA61852860/snapshots/D609F69D08EB0C77D8EADE22F70462B4') + .reply(202, helpers.loadFixture('oneandone/createSnapshot.json')); + } + var updateops = {}; + updateops.server = server; + updateops.snapshot = _snapshot; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + blockStorage.updateSnapshot(updateops, function (err, response) { + should.not.exist(err); + should.exist(response); + response.should.be.instanceOf(Snapshot); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); +}); + diff --git a/test/oneandone/compute/test-flavor.js b/test/oneandone/compute/test-flavor.js new file mode 100644 index 000000000..6fd05a909 --- /dev/null +++ b/test/oneandone/compute/test-flavor.js @@ -0,0 +1,91 @@ +/** + * Created by Ali Bazlamit on 8/21/2017. + */ +var should = require('should'), + async = require('async'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = true, + Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor; + +var flavors = [], + client; +var options = { + token: process.env.OAO_TOKEN +}; +describe('Flavor tests', function () { + this.timeout(18000000); + var authHockInstance, hockInstance, authServer, mockServer; + + before(function (done) { + client = helpers.createClient('oneandone', 'compute', options); + if (!mock) { + return done(); + } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + mockServer = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ + function (next) { + mockServer.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + mockServer.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + + + it('the getFlavors() method should return the list of flavors', function (done) { + if (mock) { + hockInstance + .get('/servers/fixed_instance_sizes') + .reply(200, helpers.loadFixture('oneandone/listFlavors.json')); + } + client.getFlavors(function (err, _flavors) { + should.exist(_flavors); + _flavors.should.be.an.Array; + _flavors.forEach(function (flavor) { + flavor.should.be.instanceOf(Flavor); + }); + flavors = _flavors; + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the getFlavor() method should return a valid flavor', function (done) { + if (mock) { + hockInstance + .get('/servers/fixed_instance_sizes/8C626C1A7005D0D1F527143C413D461E') + .reply(200, helpers.loadFixture('oneandone/getFlavor.json')); + } + client.getFlavor(flavors[0], function (err, flavor) { + should.not.exist(err); + should.exist(flavor); + flavor.should.be.instanceOf(Flavor); + flavor.id.should.equal(flavors[0].id); + hockInstance && hockInstance.done(); + done(); + }); + }); +}); + + diff --git a/test/oneandone/compute/test-images.js b/test/oneandone/compute/test-images.js new file mode 100644 index 000000000..b969897b7 --- /dev/null +++ b/test/oneandone/compute/test-images.js @@ -0,0 +1,107 @@ +/** + * Created by Ali Bazlamit on 8/19/2017. + */ + + +var image, + server, + client; +var should = require('should'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK, + Image = require('../../../lib/pkgcloud/core/compute/image').Image; + +var srvr_options = { + name: 'create-test-oao2', + flavor: '81504C620D98BCEBAA5202D145203B4B', + image: '6631A1589A2CC87FEA9B99AB07399281', + location: '4EFAD5836CE43ACA502FD5B99BEE44EF', +}; +var image_options = { + name: 'pkgcloud image2', + server: '', + token: process.env.OAO_TOKEN +}; + +describe('Images tests', function () { + this.timeout(18000000); + var hockInstance, mockServer; + before(function (done) { + client = helpers.createClient('oneandone', 'compute'); + if (!mock) { + client.createServer(srvr_options, function (err, srv1) { + should.not.exist(err); + should.exist(srv1); + server = srv1; + image_options.server = server; + client.createImage(image_options, function (err, img1) { + should.not.exist(err); + should.exist(img1); + image = img1; + done(); + }); + }); + } + hockInstance = hock.createHock({throwOnUnmatched: false}); + hockInstance.filteringRequestBody(helpers.authFilter); + mockServer = http.createServer(hockInstance.handler); + mockServer.listen(12345, done); + }); + + after(function (done) { + if (hockInstance) { + mockServer.close(function () { + done(); + }); + } + else { + client.destroyServer(server, function (err, response) { + should.not.exist(err); + should.exist(response); + client.destroyImage(image, function (err, deleteResponse) { + should.not.exist(err); + should.exist(deleteResponse); + done(); + }); + }); + done(); + } + }); + + it('the getImages() method should return a list of images', function (done) { + if (mock) { + hockInstance + .get('images/') + .reply(200, helpers.loadFixture('oneandone/listImages.json')); + } + client.getImages(function (err, images) { + should.not.exist(err); + should.exist(images); + + images.should.be.an.Array; + + images.forEach(function (img) { + img.should.be.instanceOf(Image); + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the getImage() method should return an image information', function (done) { + if (mock) { + hockInstance + .get('images/842F09CAF954298C6A4BCD25E1CA3689') + .reply(200, helpers.loadFixture('oneandone/getImage.json')); + } + client.getImage(image, function (err, response) { + should.not.exist(err); + should.exist(response); + response.should.be.instanceOf(Image); + hockInstance && hockInstance.done(); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/oneandone/compute/test-servers.js b/test/oneandone/compute/test-servers.js new file mode 100644 index 000000000..a3ed44709 --- /dev/null +++ b/test/oneandone/compute/test-servers.js @@ -0,0 +1,101 @@ +var server; +var should = require('should'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK, + Server = require('../../../lib/pkgcloud/core/compute/server').Server; + +var client; +var options = { + name: 'create-test-oao2', + flavor: '81504C620D98BCEBAA5202D145203B4B', + image: '6631A1589A2CC87FEA9B99AB07399281', + location: '4EFAD5836CE43ACA502FD5B99BEE44EF', + token: process.env.OAO_TOKEN +}; + +describe('Server tests', function () { + this.timeout(18000000); + var hockInstance, mockServer; + before(function (done) { + client = helpers.createClient('oneandone', 'compute'); + if (!mock) { + client.createServer(options, function (err, srv1) { + should.not.exist(err); + should.exist(srv1); + server = srv1; + srv1.should.be.instanceOf(Server); + srv1.name.should.equal('create-test-oao2'); + srv1.image.id.should.equal(options.image); + done(); + }); + }else{ + hockInstance = hock.createHock({throwOnUnmatched: false}); + hockInstance.filteringRequestBody(helpers.authFilter); + mockServer = http.createServer(hockInstance.handler); + mockServer.listen(12345, done); + } + }); + + after(function (done) { + if (hockInstance) { + mockServer.close(function () { + done(); + }); + } else { + client.destroyServer(server, function (err, response) { + should.not.exist(err); + should.exist(response); + done(); + }); + } + }); + + it('the getServers() method should return a list of servers', function (done) { + if (mock) { + hockInstance + .get('/servers') + .reply(200, helpers.loadFixture('oneandone/listServers.json')); + } + client.getServers(function (err, servers) { + should.not.exist(err); + should.exist(servers); + servers.should.be.an.Array; + servers.forEach(function (srv) { + srv.should.be.instanceOf(Server); + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the getServer() method should return a server information', function (done) { + if (mock) { + hockInstance + .get('/servers/39AA65F5D5B02FA02D58173094EBAF95') + .reply(200, helpers.loadFixture('oneandone/getServer.json')); + } + client.getServer(server, function (err, srv1) { + should.not.exist(err); + should.exist(srv1); + srv1.should.be.instanceOf(Server); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the rebootServer() method should restart the server', function (done) { + if (mock) { + hockInstance + .get('/servers/39AA65F5D5B02FA02D58173094EBAF95/status/action') + .reply(200, helpers.loadFixture('oneandone/getServer.json')); + } + client.rebootServer(server, function (err, srv1) { + should.not.exist(err); + srv1.should.be.instanceOf(Server); + hockInstance && hockInstance.done(); + done(); + }); + }); +}); \ No newline at end of file diff --git a/test/oneandone/loadbalancer/test-loadbalancers.js b/test/oneandone/loadbalancer/test-loadbalancers.js new file mode 100644 index 000000000..8264ebe18 --- /dev/null +++ b/test/oneandone/loadbalancer/test-loadbalancers.js @@ -0,0 +1,232 @@ +/** + * Created by Ali Bazlamit on 8/30/2017. + */ +var server, + _loadBalancer, + lbClint, + client; +var should = require('should'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK, + oneandone = require('liboneandone'), + LoadBalancer = require('../../../lib/pkgcloud/oneandone/loadbalancer/loadbalancer').LoadBalancer, + Node = require('../../../lib/pkgcloud/oneandone/loadbalancer/node').Node; + +var srvr_options = { + name: 'create-test-oao', + flavor: '81504C620D98BCEBAA5202D145203B4B', + image: '6631A1589A2CC87FEA9B99AB07399281', + location: '4EFAD5836CE43ACA502FD5B99BEE44EF', + token: process.env.OAO_TOKEN +}; + +describe('LoadBalancer tests', function () { + this.timeout(18000000); + var hockInstance, mockServer; + + before(function (done) { + client = helpers.createClient('oneandone', 'compute', srvr_options); + lbClint = helpers.createClient('oneandone', 'loadbalancer', srvr_options); + if (!mock) { + + client.createServer(srvr_options, function (err, srv1) { + should.not.exist(err); + should.exist(srv1); + server = srv1; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + var options = { + name: 'lb test', + healthCheckInterval: 40, + Persistence: true, + persistenceTime: 1200, + method: oneandone.LoadBalancerMethod.ROUND_ROBIN, + rules: [ + { + protocol: 'TCP', + port_balancer: 80, + port_server: 80, + source: '0.0.0.0' + } + ], + location: '4EFAD5836CE43ACA502FD5B99BEE44EF' + }; + lbClint.createLoadBalancer(options, function (err, loadbalancer) { + should.not.exist(err); + should.exist(loadbalancer); + _loadBalancer = loadbalancer; + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + } else { + hockInstance = hock.createHock({ throwOnUnmatched: false }); + hockInstance.filteringRequestBody(helpers.authFilter); + mockServer = http.createServer(hockInstance.handler); + mockServer.listen(12345, done); + } + }); + + after(function (done) { + if (hockInstance) { + mockServer.close(function () { + done(); + }); + } + else { + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + lbClint.deleteLoadBalancer(_loadBalancer, function (err, response) { + should.not.exist(err); + should.exist(response); + server.setWait({ status: server.STATUS.running }, 15000, function (err) { + if (err) { + console.dir(err); + return; + } + client.destroyServer(server, function (err, response) { + should.not.exist(err); + should.exist(response); + done(); + }); + }); + }); + }); + } + }); + + it('the getLoadBalancers() method should return a list of loadbalancers', function (done) { + if (mock) { + hockInstance + .get('/load_balancers/{load_balancer_id}') + .reply(200, helpers.loadFixture('oneandone/loadbalancers.json')); + } + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + lbClint.getLoadBalancers(server, function (err, loadbalancers) { + should.not.exist(err); + should.exist(loadbalancers); + + loadbalancers.should.be.an.Array; + + loadbalancers.forEach(function (lb) { + lb.should.be.instanceOf(LoadBalancer); + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the updateLoadBalancer() method should update a loadbalancer ', function (done) { + if (mock) { + hockInstance + .get('/load_balancers/{load_balancer_id}') + .reply(202, helpers.loadFixture('oneandone/createLoadBalancer.json')); + } + var updateops = {}; + updateops.name = 'update oao'; + updateops.healthCheckInterval = 100; + updateops.healthCheckPath = 'path'; + updateops.healthCheckParser = 100; + updateops.Persistence = true; + updateops.persistenceTime = 1000; + updateops.method = oneandone.LoadBalancerMethod.ROUND_ROBIN; + updateops.loadbalancer = _loadBalancer; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + lbClint.updateLoadBalancer(updateops, function (err, response) { + should.not.exist(err); + should.exist(response); + response.should.be.instanceOf(LoadBalancer); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the addNodes() method should add a server ip to the load balancer ', function (done) { + if (mock) { + hockInstance + .get('/load_balancers/{load_balancer_id}/server_ips') + .reply(202, helpers.loadFixture('oneandone/createLoadBalancer.json')); + } + var updateops = {}; + updateops.serverIps = [server.ips[0].id]; + updateops.loadbalancer = _loadBalancer; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + lbClint.addNodes(updateops, function (err, response) { + should.not.exist(err); + should.exist(response); + response.should.be.instanceOf(LoadBalancer); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('the getNodes() method should update a loadbalancer ', function (done) { + if (mock) { + hockInstance + .get('/load_balancers/{load_balancer_id}/server_ips') + .reply(202, helpers.loadFixture('oneandone/addNodes.json')); + } + + lbClint.getNodes(_loadBalancer, function (err, nodes) { + should.not.exist(err); + should.exist(nodes); + + nodes.should.be.an.Array; + + nodes.forEach(function (nd) { + nd.should.be.instanceOf(Node); + }); + hockInstance && hockInstance.done(); + done(); + }); + }); + + it('the removeNode() method should update a loadbalancer ', function (done) { + if (mock) { + hockInstance + .get('/load_balancers/{load_balancer_id}') + .reply(202, helpers.loadFixture('oneandone/createLoadBalancer.json')); + } + var updateops = {}; + updateops.serverIp = server.ips[0].id; + updateops.loadbalancer = _loadBalancer; + server.setWait({ status: server.STATUS.running }, 5000, function (err) { + if (err) { + console.dir(err); + return; + } + lbClint.removeNode(updateops, function (err, response) { + should.not.exist(err); + should.exist(response); + response.should.be.instanceOf(LoadBalancer); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); +}); + diff --git a/test/openstack/cdn/base-test.js b/test/openstack/cdn/base-test.js new file mode 100644 index 000000000..b6954df63 --- /dev/null +++ b/test/openstack/cdn/base-test.js @@ -0,0 +1,143 @@ +/* + * base-resource-test.js: Unit tests for the CDN service's base resources + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'), + mock = !!process.env.MOCK, + hock = require('hock'), + http = require('http'), + async = require('async'), + should = require('should'); + +// Declaring variables for helper functions defined later +var setupGetHomeDocumentMock, setupGetPingMock; + +describe('pkgcloud/openstack/cdn/base', function() { + + // Create CDN service client + var client = helpers.createClient('openstack', 'cdn'), + authHockInstance, hockInstance, + authServer, server; + + // Runs before all unit tests are run + before(function (done) { + + if (!mock) { + return done(); + } + + // Spin up an authentication server as well as a CDN flavor server + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + // Runs after all unit tests have run + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + + }); + + // Unit tests follow... + + it('the client.getHomeDocument() method should return the home document', function(done) { + + if (mock) { + setupGetHomeDocumentMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getHomeDocument(function (err, homeDocument) { + should.not.exist(err); + should.exist(homeDocument); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.getPing() method should return the ping response', function(done) { + + if (mock) { + setupGetPingMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getPing(function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + +}); + +setupGetHomeDocumentMock = function (client, servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnHomeDocument.json'); +}; + +setupGetPingMock = function (client, servers) { + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/ping') + .reply(204); +}; diff --git a/test/openstack/cdn/flavors-test.js b/test/openstack/cdn/flavors-test.js new file mode 100644 index 000000000..c187dd47e --- /dev/null +++ b/test/openstack/cdn/flavors-test.js @@ -0,0 +1,176 @@ +/* + * flavors-test.js: Unit tests for the CDN flavors resource + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'), + mock = !!process.env.MOCK, + hock = require('hock'), + http = require('http'), + async = require('async'), + should = require('should'), + Flavor = require('../../../lib/pkgcloud/openstack/cdn/flavor').Flavor; + +// Declaring variables for helper functions defined later +var setupGetFlavorsMock, setupGetFlavorMock; + +describe('pkgcloud/openstack/cdn/flavors', function() { + + // Create CDN service client + var client = helpers.createClient('openstack', 'cdn'), + context = {}, + authHockInstance, hockInstance, + authServer, server; + + // Runs before all unit tests are run + before(function (done) { + + if (!mock) { + return done(); + } + + // Spin up an authentication server as well as a CDN flavor server + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + // Runs after all unit tests have run + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + + }); + + // Unit tests follow... + + it('the client.getFlavors() method should return a list of flavors', function(done) { + + if (mock) { + setupGetFlavorsMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getFlavors(function (err, flavors) { + should.not.exist(err); + should.exist(flavors); + + context.flavors = flavors; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.getFlavor() method should return a flavor instance', function(done) { + + if (mock) { + setupGetFlavorMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getFlavor(context.flavors[0], function (err, flavor) { + should.not.exist(err); + should.exist(flavor); + flavor.should.be.an.instanceOf(Flavor); + flavor.should.have.property('id', context.flavors[0].id); + + context.currentFlavor = flavor; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.getFlavor() method should take a id, return a flavor instance', function(done) { + + if (mock) { + setupGetFlavorMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getFlavor(context.flavors[0].id, function (err, flavor) { + should.not.exist(err); + should.exist(flavor); + flavor.should.be.an.instanceOf(Flavor); + flavor.should.have.property('id', context.flavors[0].id); + + context.currentFlavor = flavor; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + +}); + +setupGetFlavorsMock = function (client, servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnFlavors.json'); +}; + +setupGetFlavorMock = function (client, servers) { + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/flavors/cdn') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnFlavor.json'); +}; diff --git a/test/openstack/cdn/services-test.js b/test/openstack/cdn/services-test.js new file mode 100644 index 000000000..120de4bba --- /dev/null +++ b/test/openstack/cdn/services-test.js @@ -0,0 +1,375 @@ +/* + * services-test.js: Unit tests for the CDN services resource + * + * (C) 2014 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'), + mock = !!process.env.MOCK, + hock = require('hock'), + http = require('http'), + async = require('async'), + should = require('should'), + Service = require('../../../lib/pkgcloud/openstack/cdn/service').Service; + +// Declaring variables for helper functions defined later +var setupCreateServiceMock, setupGetServicesMock, setupGetServiceMock, + setupUpdateServiceMock, setupDeleteServiceMock, + setupDeleteServiceAllCachedAssetsMock, setupDeleteServiceCachedAssetMock; + +describe('pkgcloud/openstack/cdn/services', function() { + + // Create CDN service client + var client = helpers.createClient('openstack', 'cdn'), + context = {}, + authHockInstance, hockInstance, + authServer, server; + + // Runs before all unit tests are run + before(function (done) { + + if (!mock) { + return done(); + } + + // Spin up an authentication server as well as a CDN service server + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + // Runs after all unit tests have run + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + + }); + + // Unit tests follow... + + it('the client.createService() method should create a service', function(done) { + + if (mock) { + setupCreateServiceMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.createService({ + name: 'pkgcloud-site', + domains: [ + { + domain: 'pkgcloud.com' + }, + { + domain: 'www.pkgcloud.com' + } + ], + origins: [ + { + origin: 'origin.pkgcloud.com' + } + ], + flavorId: 'cdn' + }, function (err, service) { + should.not.exist(err); + should.exist(service); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.getServices() method should return a list of services', function(done) { + + if (mock) { + setupGetServicesMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getServices(function (err, services) { + should.not.exist(err); + should.exist(services); + + context.services = services; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.getService() method should return a service instance', function(done) { + + if (mock) { + setupGetServiceMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getService(context.services[0], function (err, service) { + should.not.exist(err); + should.exist(service); + service.should.be.an.instanceOf(Service); + service.should.have.property('name', context.services[0].name); + + context.currentService = service; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.getService() method should take an id, return a service instance', function(done) { + + if (mock) { + setupGetServiceMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getService(context.services[0].id, function (err, service) { + should.not.exist(err); + should.exist(service); + service.should.be.an.instanceOf(Service); + service.should.have.property('name', context.services[0].name); + + context.currentService = service; + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.updateService() method should update a service', function(done) { + + var serviceToUpdate = context.currentService; + serviceToUpdate.origins[0].origin = 'updated-origin.pkgcloud.com'; + + if (mock) { + setupUpdateServiceMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.updateService(serviceToUpdate, function (err, service) { + should.not.exist(err); + should.exist(service); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.deleteService() method should delete a service', function(done) { + if (mock) { + setupDeleteServiceMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.deleteService(context.currentService, function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.deleteService() method should take an id, delete a service', function(done) { + if (mock) { + setupDeleteServiceMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.deleteService(context.currentService.id, function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.deleteServiceCachedAssets() method should delete all cached assets of a service', function(done) { + if (mock) { + setupDeleteServiceAllCachedAssetsMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.deleteServiceCachedAssets(context.currentService, function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.deleteServiceCachedAssets() method should take an asset URL, delete that cached asset of a service', function(done) { + if (mock) { + setupDeleteServiceCachedAssetMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.deleteServiceCachedAssets(context.currentService, '/images/logo.png', function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + +}); + +setupCreateServiceMock = function (client, servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .post('/v1.0/72e90ecb69c44d0296072ea39e537041/services', { + name: 'pkgcloud-site', + domains: [ + { + domain: 'pkgcloud.com' + }, + { + domain: 'www.pkgcloud.com' + } + ], + origins: [ + { + origin: 'origin.pkgcloud.com' + } + ], + flavor_id: 'cdn' + }) + .reply(202, null, { Location: 'http://localhost:12345/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66' }) + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnService.json'); +}; + +setupGetServicesMock = function (client, servers) { + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/services') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnServices.json'); +}; + +setupGetServiceMock = function (client, servers) { + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnService.json'); +}; + +setupUpdateServiceMock = function (client, servers) { + servers.server + .get('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/cdnService.json') + .patch('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66', [ + { + op: 'replace', + path: '/origins/0/origin', + value: 'updated-origin.pkgcloud.com' + }, + { op: 'remove', path: '/client' }, + { op: 'remove', path: '/listenerTree' }, + { op: 'remove', path: '/wildcard' }, + { op: 'remove', path: '/_maxListeners' }, + { op: 'remove', path: '/delimiter' }, + { op: 'remove', path: '/_conf' }, + { op: 'remove', path: '/verboseMemoryLeak' }, + { op: 'remove', path: '/_removeListener' }, + { op: 'remove', path: '/_newListener' }, + { op: 'remove', path: '/_events' } + ]) + .reply(202); +}; + +setupDeleteServiceMock = function (client, servers) { + servers.server + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66') + .reply(202); +}; + +setupDeleteServiceAllCachedAssetsMock = function (client, servers) { + servers.server + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66/assets?all=true') + .reply(202); +}; + +setupDeleteServiceCachedAssetMock = function (client, servers) { + servers.server + .delete('/v1.0/72e90ecb69c44d0296072ea39e537041/services/d49cd860-911f-11e4-b4a9-0800200c9a66/assets?url=%2Fimages%2Flogo.png') + .reply(202); +}; diff --git a/test/openstack/compute/client/rebuildServer-test.js b/test/openstack/compute/client/rebuildServer-test.js new file mode 100644 index 000000000..51a677f8a --- /dev/null +++ b/test/openstack/compute/client/rebuildServer-test.js @@ -0,0 +1,167 @@ +/* + * rebuildServer-test.js: Test for rebuilding servers + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + compute = require('../../../../lib/pkgcloud/openstack/compute'), + mock = !!process.env.MOCK; + +var client = helpers.createClient('openstack', 'compute'); + +// Declaring variables for helper functions defined later +var setupRebuildServerWithImageIdMock, setupRebuildServerWithImageObjMock, + setupRebuildServerWithOptionsMock; + +describe('pkgcloud/openstack/compute/server[openstack]', function() { + + var authHockInstance, hockInstance, authServer, server; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the server.rebuildServer() method with image ID should rebuild a server instance', function (done) { + if (mock) { + setupRebuildServerWithImageIdMock({ + authServer: authHockInstance, + server: hockInstance + }); + } + + client.rebuildServer('a2e90ecb69c44d0296072ea39e53704a', 'd42f821e-c2d1-4796-9f07-af5ed7912d0e', function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the server.rebuildServer() method with image object should rebuild a server instance', function (done) { + if (mock) { + setupRebuildServerWithImageObjMock({ + authServer: authHockInstance, + server: hockInstance + }); + } + + var imageObj = new compute.Image(client, { id: 'd42f821e-c2d1-4796-9f07-af5ed7912d0e' }); + client.rebuildServer('a2e90ecb69c44d0296072ea39e53704a', imageObj, function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the server.rebuildServer() method with options should rebuild a server instance', function (done) { + if (mock) { + setupRebuildServerWithOptionsMock({ + authServer: authHockInstance, + server: hockInstance + }); + } + + var options = { + image: 'd42f821e-c2d1-4796-9f07-af5ed7912d0e', + adminPass: 'foobar' + }; + client.rebuildServer('a2e90ecb69c44d0296072ea39e53704a', options, function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); +}); + +setupRebuildServerWithImageIdMock = function(servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', + { 'rebuild': { 'imageRef': 'd42f821e-c2d1-4796-9f07-af5ed7912d0e' } }) + .reply(202, ''); +}; + +setupRebuildServerWithImageObjMock = function(servers) { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', + { 'rebuild': { 'imageRef': 'd42f821e-c2d1-4796-9f07-af5ed7912d0e' } }) + .reply(202, ''); +}; + +setupRebuildServerWithOptionsMock = function(servers) { + servers.server + .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', + { 'rebuild': { 'adminPass': 'foobar', 'imageRef': 'd42f821e-c2d1-4796-9f07-af5ed7912d0e' } }) + .reply(202, ''); +}; diff --git a/test/openstack/compute/client/startServer-test.js b/test/openstack/compute/client/startServer-test.js index 03cf56c22..8fde5b6c0 100644 --- a/test/openstack/compute/client/startServer-test.js +++ b/test/openstack/compute/client/startServer-test.js @@ -4,23 +4,19 @@ * */ - -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); var should = require('should'), async = require('async'), hock = require('hock'), + http = require('http'), mock = !!process.env.MOCK; - var client = helpers.createClient('openstack', 'compute'); -var options = {}; - describe('pkgcloud/common/compute/server[openstack]', function () { - var authServer, server; + var authHockInstance, hockInstance, authServer, server; before(function (done) { @@ -28,28 +24,25 @@ var options = {}; return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock({ - port: 12345, - throwOnUnmatched: false - }, function (err, hockClient) { - server = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - next(); - }); + authServer.listen(12346, next); } - ], done) + ], done); }); it('the server.startServer() method should start a server instance', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -72,16 +65,17 @@ var options = {}; }) .reply(200, helpers.getOpenstackAuthResponse()); - server - .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', {"os-start":null}) + hockInstance + .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', + { 'os-start': null }) .reply(202, ''); } client.startServer('a2e90ecb69c44d0296072ea39e53704a', function (err) { should.not.exist(err); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -96,12 +90,12 @@ var options = {}; async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); }); diff --git a/test/openstack/compute/client/stopServer-test.js b/test/openstack/compute/client/stopServer-test.js index 5bd203f30..ec1378f33 100644 --- a/test/openstack/compute/client/stopServer-test.js +++ b/test/openstack/compute/client/stopServer-test.js @@ -4,23 +4,19 @@ * */ - -var Client = new require('../../../../lib/pkgcloud/core/base/client').Client; var helpers = require('../../../helpers'); var should = require('should'), async = require('async'), hock = require('hock'), + http = require('http'), mock = !!process.env.MOCK; - var client = helpers.createClient('openstack', 'compute'); -var options = {}; - describe('pkgcloud/common/compute/server[openstack]', function () { - var authServer, server; + var authHockInstance, hockInstance, authServer, server; before(function (done) { @@ -28,28 +24,25 @@ var options = {}; return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock({ - port: 12345, - throwOnUnmatched: false - }, function (err, hockClient) { - server = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - next(); - }); + authServer.listen(12346, next); } - ], done) + ], done); }); it('the server.stopServer() method should stop a server instance', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -72,16 +65,17 @@ var options = {}; }) .reply(200, helpers.getOpenstackAuthResponse()); - server - .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', {"os-stop":null}) + hockInstance + .post('/v2/72e90ecb69c44d0296072ea39e537041/servers/a2e90ecb69c44d0296072ea39e53704a/action', + { 'os-stop' : null }) .reply(202, ''); } client.stopServer('a2e90ecb69c44d0296072ea39e53704a', function (err) { should.not.exist(err); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -96,12 +90,12 @@ var options = {}; async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); }); diff --git a/test/openstack/identity/identity-test.js b/test/openstack/identity/identity-test.js index 1e24becf7..db59b6dd8 100644 --- a/test/openstack/identity/identity-test.js +++ b/test/openstack/identity/identity-test.js @@ -3,10 +3,11 @@ var identity = require('../../../lib/pkgcloud/openstack/identity'), async = require('async'), helpers = require('../../helpers'), hock = require('hock'), + http = require('http'), mock = !!process.env.MOCK; describe('pkgcloud/openstack/identity', function () { - var server, adminServer; + var hockInstance, adminHockInstance, server, adminServer; before(function (done) { @@ -14,32 +15,27 @@ describe('pkgcloud/openstack/identity', function () { return done(); } - async.parallel([ - function(next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + hockInstance = hock.createHock({ throwOnUnmatched: false }); + adminHockInstance = hock.createHock(); - server = hockClient; - next(); - }); - }, - function(next) { - hock.createHock(12347, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + server = http.createServer(hockInstance.handler); + adminServer = http.createServer(adminHockInstance.handler); - adminServer = hockClient; - next(); - }); - }], done); + async.parallel([ + function (next) { + server.listen(12346, next); + }, + function (next) { + adminServer.listen(12347, next); + } + ], done); }); describe('the pkgcloud openstack identity.createIdentity() function', function() { it('with valid inputs should return an identity', function(done) { if (mock) { - server + hockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -72,7 +68,7 @@ describe('pkgcloud/openstack/identity', function () { client.auth(function(err) { should.not.exist(err); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -80,7 +76,7 @@ describe('pkgcloud/openstack/identity', function () { it('with no tenants listed from /v2.0/tenants should return an error', function (done) { if (mock) { - server + hockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -104,14 +100,14 @@ describe('pkgcloud/openstack/identity', function () { should.exist(err); err.message.should.equal('Unable to find tenants'); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); it('user token should validate with admin token', function(done) { if (mock) { - server + hockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -135,7 +131,7 @@ describe('pkgcloud/openstack/identity', function () { .reply(200, helpers.getOpenstackAuthResponse()); - adminServer + adminHockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -158,7 +154,7 @@ describe('pkgcloud/openstack/identity', function () { }) .reply(200, helpers._getOpenstackStandardResponse('../fixtures/openstack/realToken-admin.json')) .get('/v2.0/tokens/4bc7c5dabf3e4a49918683437d386b8a?belongsTo=72e90ecb69c44d0296072ea39e537041') - .reply(200); + .replyWithFile(200, __dirname + '/../../fixtures/openstack/validateToken-admin.json'); } var userClient = identity.createClient({ @@ -184,6 +180,7 @@ describe('pkgcloud/openstack/identity', function () { userClient._identity.token.tenant.id, function (err, body) { should.not.exist(err); + should.exist(body); done(); }); }); @@ -191,7 +188,7 @@ describe('pkgcloud/openstack/identity', function () { it('get the tenant info with admin token', function(done) { if (mock) { - server + hockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -212,10 +209,10 @@ describe('pkgcloud/openstack/identity', function () { tenantId: '72e90ecb69c44d0296072ea39e537041' } }) - .reply(200, helpers.getOpenstackAuthResponse()) + .reply(200, helpers.getOpenstackAuthResponse()); - adminServer + adminHockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -238,7 +235,7 @@ describe('pkgcloud/openstack/identity', function () { }) .reply(200, helpers._getOpenstackStandardResponse('../fixtures/openstack/realToken-admin.json')) .get('/v2.0/tenants/72e90ecb69c44d0296072ea39e537041', { 'X-Auth-Token': '4bc7c5dabf3e4a49918683437d386b8b' }) - .reply(200) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantInfo-admin.json') .get('/v2.0/tenants/72e90ecb69c44d0296072ea39e537123', { 'X-Auth-Token': '4bc7c5dabf3e4a49918683437d386b8a' }) .reply(403); @@ -268,12 +265,14 @@ describe('pkgcloud/openstack/identity', function () { function (next) { adminClient.getTenantInfo(userClient._identity.token.tenant.id, function (err, success) { should.not.exist(err); + should.exist(success); next(); }); }, function (next) { userClient.getTenantInfo(userClient._identity.token.tenant.id, function (err, success) { should.exist(err); + should.not.exist(success); next(); }); } @@ -288,7 +287,7 @@ describe('pkgcloud/openstack/identity', function () { it('with no active tenants listed from /v2.0/tenants should return an error', function (done) { if (mock) { - server + hockInstance .post('/v2.0/tokens', { auth: { passwordCredentials: { @@ -313,7 +312,7 @@ describe('pkgcloud/openstack/identity', function () { should.exist(err); err.message.should.equal('Unable to find an active tenant'); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); diff --git a/test/openstack/identity/service-test.js b/test/openstack/identity/service-test.js index 18ef4e65a..a26c7cd60 100644 --- a/test/openstack/identity/service-test.js +++ b/test/openstack/identity/service-test.js @@ -1,32 +1,31 @@ -var context = require('../../../lib/pkgcloud/openstack/context'), - should = require('should'); +var context = require('../../../lib/pkgcloud/openstack/context'); describe('pkgcloud openstack context Service Class', function() { it('with no options should throw an error', function() { (function() { - var x = new context.Service(); + new context.Service(); }).should.throw('details are a required argument'); }); it('with no details should throw an error', function () { (function () { - var x = new context.Service('ORD'); + new context.Service('ORD'); }).should.throw('details are a required argument'); }); it('with valid options should return a service', function () { var service = new context.Service({ - "endpoints": [ + endpoints: [ { - "adminURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041" + adminURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041' } ], - "endpoints_links": [], - "type": "volume", - "name": "volume" + endpoints_links: [], + type: 'volume', + name: 'volume' }); service.should.be.instanceOf(context.Service); @@ -34,16 +33,16 @@ describe('pkgcloud openstack context Service Class', function() { it('with valid options getEndpointUrl should return a endpoint URL', function () { var service = new context.Service({ - "endpoints": [ + endpoints: [ { - "adminURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041" + adminURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041' } ], - "endpoints_links": [], - "type": "volume", - "name": "volume" + endpoints_links: [], + type: 'volume', + name: 'volume' }); service.getEndpointUrl().should.equal('http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041'); @@ -51,42 +50,42 @@ describe('pkgcloud openstack context Service Class', function() { it('with valid options getEndpointUrl with invalid region should throw', function () { var service = new context.Service({ - "endpoints": [ + endpoints: [ { - "adminURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041" + adminURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041' } ], - "endpoints_links": [], - "type": "volume", - "name": "volume" + endpoints_links: [], + type: 'volume', + name: 'volume' }); (function() { service.getEndpointUrl({ - region: 'ORD' }) + region: 'ORD' }); }).should.throw('Unable to identify endpoint url'); }); it('with valid options getEndpointUrl with valid region return correctly', function () { var service = new context.Service({ - "endpoints": [ + endpoints: [ { - "adminURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041" + adminURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041' }, { - "adminURL": "http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume2.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041", - "region":"ORD" + adminURL: 'http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume2.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041', + region:'ORD' } ], - "endpoints_links": [], - "type": "volume", - "name": "volume" + endpoints_links: [], + type: 'volume', + name: 'volume' }); service.getEndpointUrl({ region: 'ORD' }) @@ -95,23 +94,23 @@ describe('pkgcloud openstack context Service Class', function() { it('with valid options getEndpointUrl without region when region is available return correctly', function () { var service = new context.Service({ - "endpoints": [ + endpoints: [ { - "adminURL": "http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume2.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041", - "region": "ORD" + adminURL: 'http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.9:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume2.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041', + region: 'ORD' }, { - "adminURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "internalURL": "http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041", - "publicURL": "http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041" + adminURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + internalURL: 'http://10.225.0.8:8776/v1/72e90ecb69c44d0296072ea39e537041', + publicURL: 'http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041' } ], - "endpoints_links": [], - "type": "volume", - "name": "volume" + endpoints_links: [], + type: 'volume', + name: 'volume' }); service.getEndpointUrl().should.equal('http://volume.myownendpoint.org:8776/v1/72e90ecb69c44d0296072ea39e537041'); diff --git a/test/openstack/orchestration/create-stacks-test.js b/test/openstack/orchestration/create-stacks-test.js new file mode 100644 index 000000000..fa323d63a --- /dev/null +++ b/test/openstack/orchestration/create-stacks-test.js @@ -0,0 +1,128 @@ +/* + * create-stacks-test.js: Test Methods for openstack heat stacks + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ + +var helpers = require('../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + Stack = require('../../../lib/pkgcloud/openstack/orchestration/stack').Stack, + mock = !!process.env.MOCK; + +var client = helpers.createClient('openstack', 'orchestration'); + +describe('pkgcloud/openstack/orchestration/stacks[createStacks]', function () { + + var authHockInstance, hockInstance, authServer, server; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the createStacks method should return a stack', function (done) { + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + hockInstance + .post('/v1/72e90ecb69c44d0296072ea39e537041/stacks', { + 'stack_name': 'stack-test', + environment: JSON.stringify({ parameters: { terms: true } }), + 'timeout_mins': 30, + template_url: 'https://raw.githubusercontent.com/rackspace-orchestration-templates/minecraft/master/minecraft-server.yaml' + }) + .reply(201, { stack: + { id: 'b39ecc51-8ac0-4396-a178-17fdc63f5d40', + links: [ + { href: 'http://localhost:12345//v1/72e90ecb69c44d0296072ea39e537041/stacks/stack-test/b39ecc51-8ac0-4396-a178-17fdc63f5d40', + rel: 'self' } + ] } }) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/b39ecc51-8ac0-4396-a178-17fdc63f5d40') + .reply(301, {}, { Location: 'http://localhost:12345/v1/72e90ecb69c44d0296072ea39e537041/stacks/stack-test/b39ecc51-8ac0-4396-a178-17fdc63f5d40' }) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/stack-test/b39ecc51-8ac0-4396-a178-17fdc63f5d40') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/getStack-create-in-progress.json'); + } + + client.createStack({ + name: 'stack-test', + timeout: 30, + templateUrl: 'https://raw.githubusercontent.com/rackspace-orchestration-templates/minecraft/master/minecraft-server.yaml', + environment: { parameters: { terms: true } } + },function (err, stack) { + should.not.exist(err); + stack.should.be.instanceof(Stack); + stack.name.should.equal('stack-test'); + stack.status.should.equal('CREATE_IN_PROGRESS'); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + +}); + + + + + diff --git a/test/openstack/orchestration/delete-stack-test.js b/test/openstack/orchestration/delete-stack-test.js new file mode 100644 index 000000000..a864f6910 --- /dev/null +++ b/test/openstack/orchestration/delete-stack-test.js @@ -0,0 +1,141 @@ +/* + * delete-stack-test.js: Unit tests for deleting OpenStack Orchestration (Heat) stacks + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK, + Stack = require('../../../lib/pkgcloud/openstack/orchestration/stack').Stack; + +var client = helpers.createClient('openstack', 'orchestration'); + +// Declaring variables for helper functions defined later +var setupDeleteStackWithObjectMock, setupDeleteStackWithNameMock; + +describe('pkgcloud/openstack/orchestration/stacks[deleteStack]', function () { + + var authHockInstance, hockInstance, authServer, server; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the deleteStack method with a stack object should delete the stack', function (done) { + if (mock) { + setupDeleteStackWithObjectMock({ + authServer: authHockInstance, + server: hockInstance + }); + } + + var stack = new Stack(client, { id: '87xxxx1-9xx9-4xxe-bxxf', stack_name: 'emptystack' }); + client.deleteStack(stack, function (err, success) { + should.not.exist(err); + success.should.equal(true); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the deleteStack method with a stack name should delete the stack', function (done) { + if (mock) { + setupDeleteStackWithNameMock({ + authServer: authHockInstance, + server: hockInstance + }); + } + + client.deleteStack('emptystack', function (err, success) { + should.not.exist(err); + success.should.equal(true); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); +}); + +setupDeleteStackWithObjectMock = function(servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .delete('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf') + .reply(204); +}; + +setupDeleteStackWithNameMock = function(servers) { + servers.server + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack') + .reply(302, null, { 'Location': '/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf' }) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf') + .reply(200, { stack: { id: '87xxxx1-9xx9-4xxe-bxxf', stack_name: 'emptystack' } }) + .delete('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf') + .reply(204); +}; diff --git a/test/openstack/orchestration/get-stack-resources-test.js b/test/openstack/orchestration/get-stack-resources-test.js new file mode 100644 index 000000000..3ad8ce034 --- /dev/null +++ b/test/openstack/orchestration/get-stack-resources-test.js @@ -0,0 +1,130 @@ +/* + * get-stack-resources-test.js: Test Methods for OpenStack Heat stack resources + * + * (C) 2015 Rackspace + * Shaunak Kashyap + * MIT LICENSE + */ + +var helpers = require('../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK; + +var client = helpers.createClient('openstack', 'orchestration'); + +describe('pkgcloud/openstack/orchestration/resources[getResources]', function() { + + var authHockInstance, hockInstance, authServer, server; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getResources method should return an empty array', function (done) { + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack') + .reply(302, {}, { Location: 'http://localhost:12345/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068' }) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068') + .reply(200, { stack: { id: '87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068', stack_name: 'emptystack' }}) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068/resources') + .reply(200, { resources: [] }); + } + + client.getResources('emptystack', function (err, resources) { + should.not.exist(err); + resources.should.be.an.Array; + resources.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + it('the getResources method with nested_depth should pass option correctly', function (done) { + if (mock) { + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack') + .reply(302, {}, { Location: 'http://localhost:12345/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068' }) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068') + .reply(200, { stack: { id: '87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068', stack_name: 'emptystack' }}) + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks/emptystack/87xxxx1-9xx9-4xxe-bxxf-a7xxxxx068/resources?nested_depth=3') + .reply(200, { resources: [] }); + } + + client.getResources('emptystack', { nestedDepth: 3 }, function (err, resources) { + console.log(err); + should.not.exist(err); + resources.should.be.an.Array; + resources.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + +}); diff --git a/test/openstack/orchestration/get-stacks-test.js b/test/openstack/orchestration/get-stacks-test.js new file mode 100644 index 000000000..00c156b33 --- /dev/null +++ b/test/openstack/orchestration/get-stacks-test.js @@ -0,0 +1,202 @@ +/* + * get-stacks-test.js: Test Methods for openstack heat stacks + * + * (C) 2014 Rackspace + * Ken Perkins + * MIT LICENSE + */ + +var helpers = require('../../helpers'); + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK; + +var client = helpers.createClient('openstack', 'orchestration'); + +describe('pkgcloud/openstack/orchestration/stacks[getStacks]', function () { + + var authHockInstance, hockInstance, authServer, server; + + before(function (done) { + + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + }); + + it('the getStacks method should return an empty array', function (done) { + if (mock) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks') + .reply(200, { stacks: [] }); + } + + client.getStacks(function (err, stacks) { + should.not.exist(err); + stacks.should.be.an.Array; + stacks.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + it('the getStacks method with name should pass option correctly', function (done) { + if (mock) { + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks?name=foo') + .reply(200, { stacks: [] }); + } + + client.getStacks({ name: 'foo' }, function (err, stacks) { + should.not.exist(err); + stacks.should.be.an.Array; + stacks.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + it('the getStacks method with sortDir should pass option correctly', function (done) { + if (mock) { + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks?sort_dir=foo') + .reply(200, { stacks: [] }); + } + + client.getStacks({ sortDir: 'foo' }, function (err, stacks) { + should.not.exist(err); + stacks.should.be.an.Array; + stacks.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + it('the getStacks method with sortKeys should pass option correctly', function (done) { + if (mock) { + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks?sort_keys=foo') + .reply(200, { stacks: [] }); + } + + client.getStacks({ sortKeys: 'foo' }, function (err, stacks) { + should.not.exist(err); + stacks.should.be.an.Array; + stacks.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + it('the getStacks method with status should pass option correctly', function (done) { + if (mock) { + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks?status=foo') + .reply(200, { stacks: [] }); + } + + client.getStacks({ status: 'foo' }, function (err, stacks) { + should.not.exist(err); + stacks.should.be.an.Array; + stacks.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + it('the getStacks method with limit & marker should pass option correctly', function (done) { + if (mock) { + hockInstance + .get('/v1/72e90ecb69c44d0296072ea39e537041/stacks?limit=10&marker=foo') + .reply(200, { stacks: [] }); + } + + client.getStacks({ limit: 10, marker: 'foo' }, function (err, stacks) { + should.not.exist(err); + stacks.should.be.an.Array; + stacks.length.should.equal(0); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + + }); + + after(function (done) { + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + }); + +}); + + + + + diff --git a/test/openstack/storage/temp-url-test.js b/test/openstack/storage/temp-url-test.js new file mode 100644 index 000000000..eef6dc0fb --- /dev/null +++ b/test/openstack/storage/temp-url-test.js @@ -0,0 +1,195 @@ +'use strict'; + +var should = require('should'), + async = require('async'), + helpers = require('../../helpers'), + hock = require('hock'), + http = require('http'), + mock = !!process.env.MOCK; + +var setupSetTemporaryUrlKeyMock, setupGetTemporaryUrlKeyMock, setupGenerateTempUrlKeyMock; + +describe('pkgcloud/openstack/storage', function(){ + var client, authHockInstance, hockInstance, + authServer, server; + + beforeEach(function (done) { + client = helpers.createClient('openstack', 'storage'); + + if(!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ], done); + + }); + + afterEach(function (done) { + client = null; + if (!mock) { + return done(); + } + + async.parallel([ + function (next) { + server.close(next); + }, + function (next) { + authServer.close(next); + } + ], done); + + }); + + it('the client.getTemporaryUrlKey() method should return the temporary url key', function(done){ + if(mock) { + setupGetTemporaryUrlKeyMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.getTemporaryUrlKey(function (err, temporaryUrlKey) { + should.not.exist(err); + should.exist(temporaryUrlKey); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.setTemporaryUrlKey() method should have the proper header', function(done){ + if(mock) { + setupSetTemporaryUrlKeyMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.setTemporaryUrlKey('54321', function (err) { + should.not.exist(err); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); + + it('the client.generateTempUrl() method should return the temporary url', function(done){ + if(mock) { + setupGenerateTempUrlKeyMock(client, { + authServer: authHockInstance, + server: hockInstance + }); + } + + client.generateTempUrl('container', 'file', 'GET', 60, '12345', function (err, temporaryUrl) { + should.not.exist(err); + should.exist(temporaryUrl); + + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); + + done(); + }); + }); +}); + +setupGetTemporaryUrlKeyMock = function (client, servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00') + .reply(200, null, {'x-account-meta-temp-url-key': '12345'}); +}; + +setupSetTemporaryUrlKeyMock = function (client, servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); + + servers.server + .post('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00', null, {'x-account-meta-temp-url-key': '54321'}) + .reply(201); +}; + +setupGenerateTempUrlKeyMock = function (client, servers) { + servers.authServer + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + } + } + }) + .replyWithFile(200, __dirname + '/../../fixtures/openstack/initialToken.json') + .get('/v2.0/tenants') + .replyWithFile(200, __dirname + '/../../fixtures/openstack/tenantId.json') + .post('/v2.0/tokens', { + auth: { + passwordCredentials: { + username: 'MOCK-USERNAME', + password: 'MOCK-PASSWORD' + }, + tenantId: '72e90ecb69c44d0296072ea39e537041' + } + }) + .reply(200, helpers.getOpenstackAuthResponse()); +}; diff --git a/test/rackspace/blockstorage/volumes-test.js b/test/rackspace/blockstorage/volumes-test.js index 5dba17ebf..201cc4689 100644 --- a/test/rackspace/blockstorage/volumes-test.js +++ b/test/rackspace/blockstorage/volumes-test.js @@ -6,18 +6,17 @@ * MIT LICENSE * */ -var fs = require('fs'), - path = require('path'), - should = require('should'), +var should = require('should'), async = require('async'), hock = require('hock'), + http = require('http'), helpers = require('../../helpers'), - Volume = require('../../../lib/pkgcloud/rackspace/blockstorage/volume').Volume, + Volume = require('../../../lib/pkgcloud/openstack/blockstorage/volume').Volume, mock = !!process.env.MOCK; describe('pkgcloud/rackspace/blockstorage/volumes', function () { var client, - testContext = {}, authServer, server; + authHockInstance, hockInstance, server, authServer; before(function (done) { client = helpers.createClient('rackspace', 'blockstorage'); @@ -26,24 +25,18 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); + authServer.listen(12346, next); } ], done); }); @@ -52,7 +45,7 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { it('should get an empty list of volumes', function(done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -63,7 +56,7 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/v1/123456/volumes') .reply(200, { volumes: [] }); } @@ -71,10 +64,10 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { client.getVolumes(function (err, volumes) { should.not.exist(err); should.exist(volumes); - volumes.should.be.instanceOf(Array); + volumes.should.be.an.Array; volumes.length.should.equal(0); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -82,7 +75,7 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { it('should create a new volume', function (done) { if (mock) { - server + hockInstance .post('/v1/123456/volumes', { volume: { display_name: 'foo3', @@ -102,8 +95,8 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { should.exist(volume); volume.should.be.instanceOf(Volume); volume.name.should.equal('foo3'); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); @@ -111,7 +104,7 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { it('should get a list of volumes', function (done) { if (mock) { - server + hockInstance .get('/v1/123456/volumes') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/volumes.json'); } @@ -119,12 +112,12 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { client.getVolumes(function (err, volumes) { should.not.exist(err); should.exist(volumes); - volumes.should.be.instanceOf(Array); + volumes.should.be.an.Array; volumes.forEach(function(volume) { volume.should.be.instanceOf(Volume); }); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -177,11 +170,11 @@ describe('pkgcloud/rackspace/blockstorage/volumes', function () { async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); -}); \ No newline at end of file +}); diff --git a/test/rackspace/compute/authentication-test.js b/test/rackspace/compute/authentication-test.js index 4baf5bd0b..4262a808a 100644 --- a/test/rackspace/compute/authentication-test.js +++ b/test/rackspace/compute/authentication-test.js @@ -1,19 +1,20 @@ /* * authentication-test.js: Tests for pkgcloud Rackspace compute authentication * -* (C) 2010 Nodejitsu Inc. +* (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var should = require('should'), async = require('async'), + http = require('http'), hock = require('hock'), macros = require('../macros'), helpers = require('../../helpers'), mock = !!process.env.MOCK; describe('pkgcloud/rackspace/compute/authentication', function () { - var client, authServer, server; + var client, authHockInstance, hockInstance, authServer, server; describe('The pkgcloud Rackspace Compute client', function () { @@ -24,24 +25,18 @@ describe('pkgcloud/rackspace/compute/authentication', function () { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); + authServer.listen(12346, next); } ], done); }); @@ -52,7 +47,7 @@ describe('pkgcloud/rackspace/compute/authentication', function () { it('the getVersion() method should return the proper version', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -63,7 +58,7 @@ describe('pkgcloud/rackspace/compute/authentication', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/v2/') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/versions.json'); } @@ -72,8 +67,8 @@ describe('pkgcloud/rackspace/compute/authentication', function () { should.not.exist(err); should.exist(version); version.should.equal('v2'); - server && server.done(); - authServer && authServer.done(); + hockInstance && hockInstance.done(); + authHockInstance && authHockInstance.done(); done(); }); }); @@ -81,14 +76,12 @@ describe('pkgcloud/rackspace/compute/authentication', function () { describe('the auth() method with a valid username and api key', function () { - var err, res; - beforeEach(function (done) { client = helpers.createClient('rackspace', 'compute'); if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -102,18 +95,18 @@ describe('pkgcloud/rackspace/compute/authentication', function () { client.auth(function (e) { should.not.exist(e); - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); }); it('should update the config with appropriate urls', function () { - client._identity.should.be.a('object'); + client._identity.should.be.a.Object; }); it('the getLimits() method should return the proper limits', function (done) { if (mock) { - server + hockInstance .get('/v2/123456/limits') .reply(200, { limits: { absolute: { maxPrivateIPs: 0 }, rate: [] } }, {}); } @@ -123,8 +116,8 @@ describe('pkgcloud/rackspace/compute/authentication', function () { should.exist(limits); should.exist(limits.absolute); should.exist(limits.rate); - limits.rate.should.be.instanceOf(Array); - server && server.done(); + limits.rate.should.be.an.Array; + hockInstance && hockInstance.done(); done(); }); }); @@ -139,12 +132,12 @@ describe('pkgcloud/rackspace/compute/authentication', function () { protocol: 'http://' }); - var err, res; + var err; beforeEach(function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -162,7 +155,7 @@ describe('pkgcloud/rackspace/compute/authentication', function () { badClient.auth(function (e) { err = e; - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); @@ -194,7 +187,7 @@ describe('pkgcloud/rackspace/compute/authentication', function () { tokenExpiry = response.access.token.expires; - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -208,13 +201,13 @@ describe('pkgcloud/rackspace/compute/authentication', function () { client.auth(function (e) { should.not.exist(e); - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); }); it('should update the config with appropriate urls', function () { - client._identity.should.be.a('object'); + client._identity.should.be.a.Object; client._identity.token.expires.toString().should.equal(tokenExpiry); }); @@ -228,7 +221,7 @@ describe('pkgcloud/rackspace/compute/authentication', function () { it('should expire the token and reauth on next call', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -239,7 +232,7 @@ describe('pkgcloud/rackspace/compute/authentication', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/v2/123456/images/detail') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/images.json'); } @@ -250,8 +243,8 @@ describe('pkgcloud/rackspace/compute/authentication', function () { client._isAuthorized().should.equal(true); should.not.exist(err); should.exist(images); - server && server.done(); - authServer && authServer.done(); + hockInstance && hockInstance.done(); + authHockInstance && authHockInstance.done(); done(); }); }, 5); @@ -265,12 +258,12 @@ describe('pkgcloud/rackspace/compute/authentication', function () { async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); }); }); diff --git a/test/rackspace/compute/image-test.js b/test/rackspace/compute/image-test.js index 01278cd8f..6a43da9cb 100644 --- a/test/rackspace/compute/image-test.js +++ b/test/rackspace/compute/image-test.js @@ -1,22 +1,21 @@ /* * image-test.js: Tests for pkgcloud Rackspace compute image requests * -* (C) 2010-2012 Nodejitsu Inc. +* (C) 2010-2012 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * MIT LICENSE * */ -var fs = require('fs'), - path = require('path'), - should = require('should'), +var should = require('should'), async = require('async'), hock = require('hock'), + http = require('http'), helpers = require('../../helpers'), mock = !!process.env.MOCK; describe('pkgcloud/rackspace/compute/images', function () { var client, - testContext = {}, authServer, server; + testContext = {}, authHockInstance, hockInstance, authServer, server; before(function (done) { client = helpers.createClient('rackspace', 'compute'); @@ -25,24 +24,18 @@ describe('pkgcloud/rackspace/compute/images', function () { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); + authServer.listen(12346, next); } ], done); }); @@ -50,7 +43,7 @@ describe('pkgcloud/rackspace/compute/images', function () { describe('The pkgcloud Rackspace Compute client', function () { before(function(done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -61,7 +54,7 @@ describe('pkgcloud/rackspace/compute/images', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/v2/123456/servers/detail') .reply(200, helpers.loadFixture('rackspace/servers.json')); } @@ -69,17 +62,17 @@ describe('pkgcloud/rackspace/compute/images', function () { client.getServers(function(err, servers) { should.not.exist(err); should.exist(servers); - servers.should.be.instanceOf(Array); + servers.should.be.an.Array; testContext.servers = servers; - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); it('the createImage() method with a serverId should create a new image', function(done) { if (mock) { - server + hockInstance .post('/v2/123456/servers/a0a5f183-b94e-4a41-a854-00aa00aa00aa/action', { createImage: { name: 'test-img-id' } }) @@ -97,22 +90,22 @@ describe('pkgcloud/rackspace/compute/images', function () { should.not.exist(err); should.exist(image); testContext.image = image; - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); - + after(function(done) { if (mock) { - server + hockInstance .delete('/v2/123456/images/a52cce1f-73fa-49ed-8382-0ab1c9caa322') .reply(204, '', {}); } client.destroyImage(testContext.image, function(err) { should.not.exist(err); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -125,11 +118,11 @@ describe('pkgcloud/rackspace/compute/images', function () { async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); -}); \ No newline at end of file +}); diff --git a/test/rackspace/compute/personality-test.js b/test/rackspace/compute/personality-test.js index e5ae5aed0..98f2dbdc8 100644 --- a/test/rackspace/compute/personality-test.js +++ b/test/rackspace/compute/personality-test.js @@ -2,16 +2,14 @@ * personality-test.js: tests cloudserver's ability to add files * to a server's filesystem during creationg * -* (C) 2010 Nodejitsu Inc. +* (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * MIT LICENSE * */ var fs = require('fs'), - path = require('path'), spawn = require('child_process').spawn, should = require('should'), - pkgcloud = require('../../../lib/pkgcloud'), helpers = require('../../helpers'), mock = !!process.env.MOCK; @@ -33,7 +31,7 @@ describe('pkgcloud/rackspace/compute/personality', function () { flavor: 1, // 256 server personality: [ { - path: "/root/.ssh/authorized_keys", + path: '/root/.ssh/authorized_keys', contents: keyBuffer.toString('base64') } ] @@ -47,7 +45,7 @@ describe('pkgcloud/rackspace/compute/personality', function () { it('should connect over ssh without a password prompt and validate the key', function(done) { - var data; + var data, errorData; testServer.setWait({ status: testServer.STATUS.running }, 5000, function () { var ssh = spawn('ssh', [ @@ -56,7 +54,7 @@ describe('pkgcloud/rackspace/compute/personality', function () { '-q', '-o', 'StrictHostKeyChecking no', - 'root@' + testServer.addresses["public"][0], + 'root@' + testServer.addresses['public'][0], 'cat /root/.ssh/authorized_keys' ]); @@ -66,6 +64,7 @@ describe('pkgcloud/rackspace/compute/personality', function () { ssh.stderr.on('error', onError); ssh.stderr.on('data', function (chunk) { + errorData += chunk; }); ssh.stdout.on('error', onError); ssh.stdout.on('data', function (chunk) { diff --git a/test/rackspace/databases/authentication-test.js b/test/rackspace/databases/authentication-test.js index 0809ba887..8940ca076 100644 --- a/test/rackspace/databases/authentication-test.js +++ b/test/rackspace/databases/authentication-test.js @@ -2,7 +2,7 @@ /* * authentication-test.js: Tests for pkgcloud Rackspace compute authentication * - * (C) 2010 Nodejitsu Inc. + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ @@ -10,11 +10,12 @@ var should = require('should'), macros = require('../macros'), async = require('async'), hock = require('hock'), + http = require('http'), helpers = require('../../helpers'), mock = process.env.MOCK; describe('pkgcloud/rackspace/database/authentication', function() { - var client, testContext = {}, authServer, server; + var client, authHockInstance, hockInstance, authServer, server; before(function(done) { client = helpers.createClient('rackspace', 'database'); @@ -23,27 +24,20 @@ describe('pkgcloud/rackspace/database/authentication', function() { return done(); } - async.parallel([ - function(next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); - authServer = hockClient; - next(); - }); - }, - function(next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); - server = hockClient; - next(); - }); + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); } - ], done) - + ], done); }); describe('The pkgcloud Rackspace Database client', function() { @@ -53,7 +47,7 @@ describe('pkgcloud/rackspace/database/authentication', function() { it('the getVersion() method should return the proper version', function(done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -64,18 +58,18 @@ describe('pkgcloud/rackspace/database/authentication', function() { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/') .reply(200, { versions: [ { - "status": "CURRENT", - "updated": "2012-08-01T00:00:00Z", - "id": "v1.0", - "links": [ + status: 'CURRENT', + updated: '2012-08-01T00:00:00Z', + id: 'v1.0', + links: [ { - "href": "http://dfw.databases.api.rackspacecloud.com/v1.0/", - "rel": "self" + href: 'http://dfw.databases.api.rackspacecloud.com/v1.0/', + rel: 'self' } ] } @@ -86,11 +80,11 @@ describe('pkgcloud/rackspace/database/authentication', function() { client.getVersion(function (err, versions) { should.not.exist(err); should.exist(versions); - versions.should.be.instanceOf(Array); + versions.should.be.an.Array; versions.should.have.length(1); - server && server.done(); - authServer && authServer.done(); + hockInstance && hockInstance.done(); + authHockInstance && authHockInstance.done(); done(); }); }); @@ -98,12 +92,12 @@ describe('pkgcloud/rackspace/database/authentication', function() { describe('the auth() method with a valid username and api key', function() { var client = helpers.createClient('rackspace', 'database'), - err, res; + err; beforeEach(function(done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -117,7 +111,7 @@ describe('pkgcloud/rackspace/database/authentication', function() { client.auth(function (e) { err = e; - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); @@ -149,12 +143,12 @@ describe('pkgcloud/rackspace/database/authentication', function() { authUrl: 'localhost:12346' }); - var err, res; + var err; beforeEach(function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -172,7 +166,7 @@ describe('pkgcloud/rackspace/database/authentication', function() { badClient.auth(function (e) { err = e; - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); }); @@ -191,11 +185,11 @@ describe('pkgcloud/rackspace/database/authentication', function() { async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); }); diff --git a/test/rackspace/databases/databases-test.js b/test/rackspace/databases/databases-test.js deleted file mode 100644 index 6583c238e..000000000 --- a/test/rackspace/databases/databases-test.js +++ /dev/null @@ -1,359 +0,0 @@ -/* - * databases-test.js: Tests for Rackspace Cloud Database instances - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENSE - * - */ - -var should = require('should'), - hock = require('hock'), - async = require('async'), - helpers = require('../../helpers'), - mock = !!process.env.MOCK; - -describe('pkgcloud/rackspace/databases/databases', function() { - - var client, testContext = {}, server, authServer; - - describe('The pkgcloud Rackspace Database client', function() { - - before(function (done) { - client = helpers.createClient('rackspace', 'database'); - - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); - }, - function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); - } - ], done); - }); - - it('the createDatabases() method should respond correctly', function(done) { - if (mock) { - authServer - .post('/v2.0/tokens', { - auth: { - 'RAX-KSKEY:apiKeyCredentials': { - username: 'MOCK-USERNAME', - apiKey: 'MOCK-API-KEY' - } - } - }) - .reply(200, helpers.getRackspaceAuthResponse()); - - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', - { - databases: [ - { name: 'TestDatabase' } - ] - }) - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.createDatabase({name: 'TestDatabase', instance: instance}, function(err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - authServer && authServer.done(); - server && server.done(); - done(); - }); - }); - }); - - it('create another database for pagination test should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', - { - databases: [ - { name: 'TestDatabaseTwo' } - ] - }) - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.createDatabase({name: 'TestDatabaseTwo', instance: instance}, function(err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - - it('the create() method should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases', - { - databases: [ - { name: 'TestDatabaseThree' } - ] - }) - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.create({name: 'TestDatabaseThree', instance: instance}, function (err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - - it('the createDatabase() method with no name should get an error for name', function(done) { - client.createDatabase({}, function(err, response) { - should.exist(err); - should.not.exist(response); - err.message.should.equal('options. Name is a required argument'); - done(); - }); - }); - - it('the createDatabase() method with no instance should get an error for instance', function (done) { - client.createDatabase({ name: 'NotCreated' }, function (err, response) { - should.exist(err); - should.not.exist(response); - err.message.should.equal('options. Instance is a required argument'); - done(); - }); - }); - - it('the getDatabases() method should return a list of databases', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases') - .reply(200, {databases: [{name: 'TestDatabase'}, {name: 'TestDatabaseTwo'}]}); - } - - helpers.selectInstance(client, function (instance) { - client.getDatabases({ instance: instance }, function(err, list) { - should.not.exist(err); - should.exist(list); - list.should.be.an.instanceOf(Array); - list.should.have.length(2); - server && server.done(); - done(); - }); - }); - }); - - it('the getDatabases() method should return a list of databases with names', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases') - .reply(200, {databases: [ - {name: 'TestDatabase'}, - {name: 'TestDatabaseTwo'} - ]}); - } - - helpers.selectInstance(client, function (instance) { - client.getDatabases({ instance: instance }, function (err, list) { - should.not.exist(err); - should.exist(list); - should.exist(list[0]) - list[0].name.should.equal('TestDatabase'); - list[0].name.should.be.a('string'); - server && server.done(); - done(); - }); - }); - }); - - it('the getDatabases() method with limit should respond one element', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?limit=1') - .reply(200, {databases: [ - {name: 'TestDatabase'} - ]}); - } - - helpers.selectInstance(client, function (instance) { - client.getDatabases({ instance: instance, limit: 1 }, function (err, instances) { - should.not.exist(err); - should.exist(instances); - instances.should.be.an.instanceOf(Array); - instances.should.have.length(1); - server && server.done(); - done(); - }); - }); - }); - - it('the getDatabases() method with limit should pass as third argument the offset mark', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?limit=1') - .reply(200, helpers.loadFixture('rackspace/databasesLimit.json')); - } - - helpers.selectInstance(client, function (instance) { - client.getDatabases({ instance: instance, limit: 1 }, function (err, instances, offset) { - should.not.exist(err); - should.exist(instances); - should.exist(offset); - offset.should.equal('TestDatabase'); - testContext.marker = offset; - server && server.done(); - done(); - }); - }); - }); - - it('the getDatabases() method with offset should respond less quantity', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?marker=TestDatabase') - .reply(200, { databases: [{ name: 'TestDatabaseTwo '}] }); - } - - helpers.selectInstance(client, function (instance) { - client.getDatabases({ instance: instance, offset: testContext.marker }, - function(err, instances, offset) { - should.not.exist(err); - should.exist(instances); - instances.should.have.length(1); - should.not.exist(offset); - server && server.done(); - done(); - }); - }); - }); - - it('the getDatabases() method with limit and offset ' + - 'should respond just one result with no more next points', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases?limit=1&marker=TestDatabase') - .reply(200, { databases: [{ name: 'TestDatabaseTwo' }] }); - } - - helpers.selectInstance(client, function (instance) { - client.getDatabases({ instance: instance, limit: 1, offset: testContext.marker }, - function (err, instances, offset) { - should.not.exist(err); - should.exist(instances); - instances.should.be.instanceOf(Array); - instances.should.have.length(1); - should.not.exist(offset); - server && server.done(); - done(); - }); - }); - }); - - it('the destroyDatabase() method with first db should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabase') - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.destroyDatabase('TestDatabase', instance, function(err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - - it('the destroyDatabase() method with last db should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/databases/TestDatabaseTwo') - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.destroyDatabase('TestDatabaseTwo', instance, function (err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - authServer.close(next); - }, - function (next) { - server.close(next); - } - ], done) - }); - - }); -}); - diff --git a/test/rackspace/databases/errors-test.js b/test/rackspace/databases/errors-test.js deleted file mode 100644 index ea621f56e..000000000 --- a/test/rackspace/databases/errors-test.js +++ /dev/null @@ -1,179 +0,0 @@ -/* -* errors-test.js: Tests for Rackspace Cloud Database instances -* -* (C) 2010 Nodejitsu Inc. -* MIT LICENSE -* -*/ - -var should = require('should'), - helpers = require('../../helpers'); - -describe('pkgcloud/rackspace/databases/errors', function() { - var client = helpers.createClient('rackspace', 'database'); - - describe('The pkgcloud Rackspace Database client', function() { - describe('breaking the function', function() { - - it('createInstance() when no options should return an error', function(done) { - client.createInstance(function(err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createInstance() with bad options should return an error', function (done) { - client.createInstance({}, function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createInstance() with no instance options should return an error', function (done) { - client.createInstance({ name: 'shouldGetError' }, function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('destroyInstance() with no instance should return an error', function (done) { - client.destroyInstance(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('getInstance() with no instance should return an error', function (done) { - client.getInstance(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createDatabase() with no options should return an error', function (done) { - client.createDatabase(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createDatabase() with no instance should return an error', function (done) { - client.createDatabase({ name: 'shouldGetError' }, function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('getDatabases() with no instance should return an error', function (done) { - client.getDatabases(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('destroyDatabase() with no options should return an error', function (done) { - client.destroyDatabase(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('destroyDatabase() with no instance should return an error', function (done) { - client.destroyDatabase('shouldGetError', function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createUser() with no options should return an error', function (done) { - client.createUser(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createUser() with empty objects should return an error', function (done) { - client.createUser({}, function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createUser() with no db or instance should return an error', function (done) { - client.createUser({ - username: 'testing', - password: 'shouldFail' - }, function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('createUser() with no instance should return an error', function (done) { - client.createUser({ - username: 'testing', - password: 'shouldFail', - database: 'none' - }, function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('getUsers() with no instance should return an error', function (done) { - client.getUsers(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('destroyUser() with no instance should return an error', function (done) { - client.destroyUser(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('destroyUser() with no user should return an error', function (done) { - client.destroyUser('shouldGetError', function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('enableRoot() with no instance should return an error', function (done) { - client.enableRoot(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - - it('rootEnabled() with no instance should return an error', function (done) { - client.rootEnabled(function (err, instance) { - should.exist(err); - should.not.exist(instance); - done(); - }); - }); - }); - }); -}); - diff --git a/test/rackspace/databases/flavor-test.js b/test/rackspace/databases/flavor-test.js deleted file mode 100644 index 3e8df1e28..000000000 --- a/test/rackspace/databases/flavor-test.js +++ /dev/null @@ -1,136 +0,0 @@ -/* -* flavor-test.js: Test for pkgcloud Rackspace database flavor requests -* -* (C) 2010 Nodejitsu Inc. -* -*/ - -var should = require('should'), - hock = require('hock'), - async = require('async'), - helpers = require('../../helpers'), - Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor, - mock = !!process.env.MOCK; - -describe('pkgcloud/rackspace/databases/errors', function () { - var testContext = {}, - client, authServer, server; - - describe('The pkgcloud Rackspace Database client', function () { - - before(function (done) { - client = helpers.createClient('rackspace', 'database'); - - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); - }, - function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); - } - ], done); - }); - - function getFlavors(auth, callback) { - if (mock) { - if (auth) { - authServer - .post('/v2.0/tokens', { - auth: { - 'RAX-KSKEY:apiKeyCredentials': { - username: 'MOCK-USERNAME', - apiKey: 'MOCK-API-KEY' - } - } - }) - .reply(200, helpers.getRackspaceAuthResponse()); - } - - server - .get('/v1.0/123456/flavors') - .reply(200, helpers.loadFixture('rackspace/databaseFlavors.json')) - } - - client.getFlavors(callback); - } - - it('the getFlavors() method should return the list of flavors', function(done) { - getFlavors(true, function (err, flavors) { - should.not.exist(err); - should.exist(flavors); - flavors.should.be.instanceOf(Array); - flavors.forEach(function (flavor) { - flavor.should.be.instanceOf(Flavor); - }); - - server && server.done(); - authServer && authServer.done(); - testContext.flavors = flavors; - done(); - }); - }); - - it('the getFlavors() method should return the list of flavor with rackspace specific information', function (done) { - getFlavors(false, function (err, flavors) { - should.not.exist(err); - should.exist(flavors); - flavors.should.be.instanceOf(Array); - flavors.forEach(function (flavor) { - flavor.ram.should.be.a('number'); - flavor.href.should.be.a('string'); - }); - server && server.done(); - done(); - }); - }); - - it('the getFlavor() method should return a valid flavor', function(done) { - if (mock) { - server - .get('/v1.0/123456/flavors/3') - .reply(200, helpers.loadFixture('rackspace/databaseFlavor3.json')); - } - - client.getFlavor(testContext.flavors[2].id, function(err, flavor) { - should.not.exist(err); - should.exist(flavor); - flavor.should.be.instanceOf(Flavor); - flavor.id.should.equal(testContext.flavors[2].id); - server && server.done(); - done(); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - authServer.close(next); - }, - function (next) { - server.close(next); - } - ], done) - }); - }); -}); - diff --git a/test/rackspace/databases/instances-test.js b/test/rackspace/databases/instances-test.js deleted file mode 100644 index 456756768..000000000 --- a/test/rackspace/databases/instances-test.js +++ /dev/null @@ -1,537 +0,0 @@ -/* -* instances-test.js: Tests for Rackspace Cloud Database instances -* -* (C) 2010 Nodejitsu Inc. -* MIT LICENSE -* -*/ - -var should = require('should'), - hock = require('hock'), - async = require('async'), - helpers = require('../../helpers'), - Flavor = require('../../../lib/pkgcloud/core/compute/flavor').Flavor, - Instance = require('../../../lib/pkgcloud/rackspace/database/instance').Instance, - mock = !!process.env.MOCK; - -describe('pkgcloud/rackspace/databases/instances', function () { - var testContext = {}, - client, authServer, server; - - describe('The pkgcloud Rackspace Database client', function () { - - before(function (done) { - client = helpers.createClient('rackspace', 'database'); - - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); - }, - function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); - } - ], done); - }); - - describe('the create() method', function() { - - var err, instance; - - before(function(done) { - - if (mock) { - authServer - .post('/v2.0/tokens', { - auth: { - 'RAX-KSKEY:apiKeyCredentials': { - username: 'MOCK-USERNAME', - apiKey: 'MOCK-API-KEY' - } - } - }) - .reply(200, helpers.getRackspaceAuthResponse()); - - server - .get('/v1.0/123456/flavors/1') - .reply(200, helpers.loadFixture('rackspace/databaseFlavor1.json')) - - .post('/v1.0/123456/instances', { - instance: { - name: 'test-instance', - flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/1', - databases: [], - volume: { - size:1 - } - } - }) - .reply(200, helpers.loadFixture('rackspace/createdDatabaseInstance.json')); - - } - client.getFlavor(1, function (err, flavor) { - should.not.exist(err); - should.exist(flavor); - flavor.should.be.instanceOf(Flavor); - - client.createInstance({ - name: 'test-instance', - flavor: flavor - }, function(e, i) { - err = e; - instance = i; - authServer && authServer.done(); - server && server.done(); - done(); - }); - }); - }); - - it('should return a valid instance', function() { - should.not.exist(err); - should.exist(instance); - instance.should.be.instanceOf(Instance); - }); - - it('should return the same name and flavor used', function() { - should.not.exist(err); - should.exist(instance); - instance.name.should.equal('test-instance'); - should.equal(1, instance.flavor.id); - }); - }); - - describe('the getInstances() method', function() { - describe('without options', function() { - - var err, instances, offset - - before(function(done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - } - - client.getInstances(function(e, i, o) { - err = e; - instances = i; - offset = o; - server && server.done(); - done(); - }); - }); - - it('should return the list of instances', function () { - should.not.exist(err); - should.exist(instances); - instances.should.be.instanceOf(Array); - instances.length.should.be.above(0); - - testContext.instancesQuantity = instances.length; - }); - - it('should valid instance each item in the list', function () { - instances.forEach(function (instance) { - instance.should.be.instanceOf(Instance); - }); - }); - - it('should response with extra info', function () { - - instances.forEach(function (instance) { - should.exist(instance.id); - instance.links.should.be.instanceOf(Array); - instance.flavor.should.be.a('object'); - instance.volume.should.be.a('object'); - instance.volume.size.should.be.a('number'); - }); - }); - - it('should have correct flavor', function () { - instances.forEach(function (instance) { - should.exist(instance.flavor.id); - assertLinks(instance.flavor.links); - }); - }); - - it('should have correct links', function () { - instances.forEach(function (instance) { - assertLinks(instance.links); - }); - }); - - it('should have a null offset', function () { - should.not.exist(offset); - }); - }); - - describe('with limit', function () { - var err, instances, offset - - before(function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances?limit=2') - .reply(200, helpers.loadFixture('rackspace/databaseInstancesLimit2.json')) - } - - client.getInstances({ limit: 2 }, function (e, i, o) { - err = e; - instances = i; - offset = o; - server && server.done(); - done(); - }); - }); - - it('should respond at least 2 elements', function() { - should.not.exist(err); - should.exist(instances); - instances.should.be.instanceOf(Array); - instances.should.have.length(2); - }); - - it('should pass as third argument the offset mark', function() { - should.exist(offset); - testContext.marker = offset; - }); - }); - }); - - describe('the destroyInstance() method', function() { - it('should respond correctly', function(done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') - .reply(202) - } - - helpers.selectInstance(client, function (instance) { - testContext.Instance = instance; - client.destroyInstance(testContext.Instance, function(err, result) { - should.not.exist(err); - should.exist(result); - result.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - }); - - describe('the getInstance() method', function () { - it('should response with details', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f') - .reply(200, helpers.loadFixture('rackspace/databaseInstance.json')) - } - - client.getInstance(testContext.Instance.id, function (err, instance) { - should.not.exist(err); - should.exist(instance); - instance.should.be.instanceOf(Instance); - instance.id.should.equal(testContext.Instance.id); - server && server.done(); - done(); - }); - }); - }); - - describe('the getInstances() method', function () { - it('with offset should respond less quantity', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances?marker=55041e91-98ab-4cd5-8148-f3b3978b3262') - .reply(200, helpers.loadFixture('rackspace/databaseInstanceOffset.json')) - } - - client.getInstances({ offset: testContext.marker }, function (err, instances, offset) { - should.not.exist(err); - should.exist(instances); - instances.should.be.instanceOf(Array); - should.ok(instances.length >= 2 - && instances.length < testContext.instancesQuantity); - server && server.done(); - done(); - }); - - }); - - it('with limit and offset should respond just one result with more next points', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances?limit=1&marker=55041e91-98ab-4cd5-8148-f3b3978b3262') - .reply(200, helpers.loadFixture('rackspace/databaseInstanceLimitOffset.json')) - } - - client.getInstances({limit: 1, offset: testContext.marker }, function (err, instances, offset) { - should.not.exist(err); - should.exist(instances); - instances.should.be.instanceOf(Array); - should.exist(offset); - instances.should.have.length(1); - server && server.done(); - done(); - }); - }); - }); - - describe('the setFlavor() method', function () { - it('without instance and flavor parameters should get errors', function (done) { - client.setFlavor(function (err) { - should.exist(err); - done(); - }); - }); - - it('without flavor parameter should get errors', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - } - - helpers.selectInstance(client, function (instance) { - client.setFlavor(instance, function (err) { - should.exist(err); - server && server.done(); - done(); - }); - }); - }); - - it('without instance parameter should get errors', function (done) { - - if (mock) { - server - .get('/v1.0/123456/flavors/2') - .reply(200, helpers.loadFixture('rackspace/databaseFlavor2.json')) - } - - client.getFlavor(2, function (err, flavor) { - should.not.exist(err); - should.exist(flavor); - - client.setFlavor(flavor, function(err) { - should.exist(err); - server && server.done(); - done(); - }); - }); - }); - - it('with correct inputs should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/flavors/2') - .reply(200, helpers.loadFixture('rackspace/databaseFlavor2.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { - resize: { - flavorRef: 'https://ord.databases.api.rackspacecloud.com/v1.0/123456/flavors/2' - } - }) - .reply(202) - } - - helpers.selectInstance(client, function (instance) { - var newFlavor = (Number(instance.flavor.id) === 4) ? 1 : Number(instance.flavor.id) + 1; - client.getFlavor(newFlavor, function (err, flavor) { - should.not.exist(err); - should.exist(flavor); - client.setFlavor(instance, flavor, function (err) { - should.not.exist(err); - server && server.done(); - done(); - }); - }); - }); - }); - }); - - describe('the setVolumeSize() method', function() { - it('without instance and size parameters should get errors', function(done) { - client.setVolumeSize(function(err) { - should.exist(err); - done(); - }); - }); - - it('without size parameter should get errors', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - } - - helpers.selectInstance(client, function (instance) { - client.setVolumeSize(instance, function (err) { - should.exist(err); - server && server.done(); - done(); - }); - }); - }); - - it('without invalid size parameter should get errors', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - } - - helpers.selectInstance(client, function (instance) { - client.setVolumeSize(instance, 12, function (err) { - should.exist(err); - server && server.done(); - done(); - }); - }); - }); - - it('with correct inputs should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { - resize: { - volume :{ - size :2 - } - } - }) - .reply(202) - } - - helpers.selectInstance(client, function (instance) { - var newSize = (Number(instance.volume.size) === 8) ? 1 : Number(instance.volume.size) + 1; - - client.setVolumeSize(instance, newSize, function (err) { - should.not.exist(err); - server && server.done(); - done(); - }); - }); - }); - }); - - describe('the create() method with errors', function () { - it('should respond with errors', function (done) { - client.createInstance(function(err) { - should.exist(err); - done(); - }) - }); - - it('without flavor should respond with errors', function (done) { - client.createInstance({ name: 'test-without-flavor' }, function (err) { - should.exist(err); - done(); - }) - }); - - it('with invalid size should respond with errors', function (done) { - if (mock) { - server - .get('/v1.0/123456/flavors/1') - .reply(200, helpers.loadFixture('rackspace/databaseFlavor1.json')) - } - - client.getFlavor(1, function (err, flavor) { - client.createInstance({ - name: 'test-instance', - flavor: flavor, - size: '1' - }, function(err) { - should.exist(err); - server && server.done(); - done(); - }); - }); - }); - }); - - describe('the restartInstance() method', function () { - it('with no instance should return error', function (done) { - client.restartInstance(function (err) { - should.exist(err); - done(); - }) - }); - - it('with valid instance should restart', function (done) { - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/action', { restart :{}}) - .reply(202) - } - - helpers.selectInstance(client, function (instance) { - client.restartInstance(instance, function (err) { - should.not.exist(err); - server && server.done(); - done(); - }); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - authServer.close(next); - }, - function (next) { - server.close(next); - } - ], done) - }); - }); -}); - -function assertLinks(links) { - links.should.be.instanceOf(Array); - links.forEach(function (link) { - should.exist(link.href); - should.exist(link.rel); - }); -} - diff --git a/test/rackspace/databases/user-limit-test.js b/test/rackspace/databases/user-limit-test.js new file mode 100644 index 000000000..c86e94a00 --- /dev/null +++ b/test/rackspace/databases/user-limit-test.js @@ -0,0 +1,151 @@ +/* + * users-limit-test.js: Tests for Cloud Database users within an instace + * + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. + * MIT LICENSE + * + */ + +var should = require('should'), + async = require('async'), + hock = require('hock'), + http = require('http'), + helpers = require('../../helpers'), + mock = !!process.env.MOCK; + +// Declaring variables for helper functions defined later +var setupAuthenticationMock, setupGetUsersMock; + + describe('pkgcloud/[rackspace]/databases/users/limits', function () { + var testContext = {}, + client, authHockInstance, hockInstance, authServer, + server, err, list, offset; + + before(function (done) { + client = helpers.createClient('rackspace', 'database'); + if (!mock) { + return done(); + } + + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + + async.parallel([ + function (next) { + server.listen(12345, next); + }, + function (next) { + authServer.listen(12346, next); + } + ]); + + if (mock) { + setupAuthenticationMock(authHockInstance); + setupGetUsersMock(hockInstance); + } + + helpers.selectInstance(client, function (instance) { + client.getUsers({ instance: instance, limit: 1 }, function (e, l, o) { + err = e; + list = l; + offset = o; + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + after(function() { + server.close(); + authServer.close(); + }); + + it('with limit should respond with one element', function () { + should.not.exist(err); + should.exist(list); + list.should.have.length(1); + }); + + it('with limitshould pass as third argument the offset mark', function () { + should.exist(offset); + testContext.marker = offset; + }); + + it('with offset should respond less quantity', function (done) { + + if (mock) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users?marker=joeTest') + .reply(200, { + users: [ + { name: 'joeTestTwo', databases: []}, + { name: 'joeTestThree', databases: []} + ] + }); + } + + helpers.selectInstance(client, function (instance) { + client.getUsers({ instance: instance, offset: testContext.marker }, function (err, list, offset) { + should.not.exist(err); + should.exist(list); + list.should.be.an.Array; + list.should.have.length(2); + should.not.exist(offset); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('with limit and offset should responsd with just result with more next points', function(done) { + + if (mock) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users?limit=1&marker=joeTest') + .reply(200, helpers.loadFixture('rackspace/databaseUsersLimitOffset.json')); + } + + helpers.selectInstance(client, function (instance) { + client.getUsers({ + instance: instance, + limit: 1, + offset:testContext.marker }, function(err, list, offset) { + should.not.exist(err); + should.exist(list); + list.should.be.an.Array; + list.should.have.length(1); + should.exist(offset); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + }); + +setupGetUsersMock = function(hockInstance) { + hockInstance + .get('/v1.0/123456/instances') + .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) + .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users?limit=1') + .reply(200, helpers.loadFixture('rackspace/databaseUsersLimit.json')); +}; + +setupAuthenticationMock = function(authHockInstance) { + authHockInstance + .post('/v2.0/tokens', { + auth: { + 'RAX-KSKEY:apiKeyCredentials': { + username: 'MOCK-USERNAME', + apiKey: 'MOCK-API-KEY' + } + } + }) + .reply(200, helpers.getRackspaceAuthResponse()); +}; diff --git a/test/rackspace/databases/users-test.js b/test/rackspace/databases/users-test.js deleted file mode 100644 index 35defc579..000000000 --- a/test/rackspace/databases/users-test.js +++ /dev/null @@ -1,445 +0,0 @@ -/* - * users-test.js: Tests for Rackspace Cloud Database users within an instace - * - * (C) 2010 Nodejitsu Inc. - * MIT LICENSE - * - */ - -var should = require('should'), - async = require('async'), - hock = require('hock'), - helpers = require('../../helpers'), - User = require('../../../lib/pkgcloud/rackspace/database/user').User, - mock = !!process.env.MOCK; - -describe('pkgcloud/rackspace/databases/users', function () { - var testContext = {}, - client, authServer, server; - - describe('The pkgcloud Rackspace Database client', function () { - - before(function (done) { - client = helpers.createClient('rackspace', 'database'); - - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); - }, - function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); - } - ], done); - }); - - it('the createUser() method should respond correctly', function (done) { - if (mock) { - authServer - .post('/v2.0/tokens', { - auth: { - 'RAX-KSKEY:apiKeyCredentials': { - username: 'MOCK-USERNAME', - apiKey: 'MOCK-API-KEY' - } - } - }) - .reply(200, helpers.getRackspaceAuthResponse()); - - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { - users: [ - { - name: 'joeTest', - password: 'joepasswd', - databases: [] - } - ] - }) - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.createUser({ - username: 'joeTest', - password: 'joepasswd', - database: 'TestDatabase', - instance: instance - }, function (err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - authServer && authServer.done(); - server && server.done(); - done(); - }); - }); - - }); - - it('create an other user for test pagination should response correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { - users: [ - { - name: 'joeTestTwo', - password: 'joepasswd', - databases: [] - } - ] - }) - .reply(202) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { - users: [ - { - name: 'joeTestThree', - password: 'joepasswd', - databases: [] - } - ] - }) - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.createUser({ - username: 'joeTestTwo', - password: 'joepasswd', - database: 'TestDatabase', - instance: instance - }, function () { - client.createUser({ - username: 'joeTestThree', - password: 'joepasswd', - database: 'TestDatabase', - instance: instance - }, function (err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - }); - - it('create multiple users in one request should response correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users', { - users: [ - { - name: 'joeTestFour', - password: 'joepasswd', - databases: [] - }, - { - name: 'joeTestFive', - password: 'joepasswd', - databases: [] - } - ] - }) - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.createUser([ - { - username: 'joeTestFour', - password: 'joepasswd', - database: 'TestDatabase', - instance: instance - }, - { - username: 'joeTestFive', - password: 'joepasswd', - database: 'TestDatabase', - instance: instance - } - ], function (err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - - it('create users with questionable characters should respond with error', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')); - } - - helpers.selectInstance(client, function (instance) { - client.createUser({ - username: '@joeTestSix', - password: 'joepasswd', - database: 'TestDatabase', - instance: instance - }, function (err, response) { - should.exist(err); - should.not.exist(response); - server && server.done(); - done(); - }); - }); - }); - - it('the getUsers() method should get the list of users', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users') - .reply(200, helpers.loadFixture('rackspace/databaseUsers.json')); - } - - helpers.selectInstance(client, function (instance) { - client.getUsers({ instance: instance }, function (err, list) { - should.not.exist(err); - should.exist(list); - list.should.be.instanceOf(Array); - list.forEach(function (user) { - user.should.be.instanceOf(User); - }); - server && server.done(); - done(); - }); - }); - }); - - describe('the getUsers() method', function () { - - var err, list, offset; - - before(function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users?limit=1') - .reply(200, helpers.loadFixture('rackspace/databaseUsersLimit.json')); - } - - helpers.selectInstance(client, function (instance) { - client.getUsers({ instance: instance, limit: 1 }, function (e, l, o) { - err = e; - list = l; - offset = o; - server && server.done(); - done(); - }); - }); - }); - - it('with limit should respond with one element', function () { - should.not.exist(err); - should.exist(list); - list.should.have.length(1); - }); - - it('with limitshould pass as third argument the offset mark', function () { - should.exist(offset); - testContext.marker = offset; - }); - - it('with offset should respond less quantity', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users?marker=joeTest') - .reply(200, { - users: [ - { name: 'joeTestTwo', databases: []}, - { name: 'joeTestThree', databases: []} - ] - }); - } - - helpers.selectInstance(client, function (instance) { - client.getUsers({ instance: instance, offset: testContext.marker }, function (err, list, offset) { - should.not.exist(err); - should.exist(list); - list.should.be.instanceOf(Array); - list.should.have.length(2); - should.not.exist(offset); - server && server.done(); - done(); - }); - }); - }); - - it('with limit and offset should responsd with just result with more next points', function(done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users?limit=1&marker=joeTest') - .reply(200, helpers.loadFixture('rackspace/databaseUsersLimitOffset.json')); - } - - helpers.selectInstance(client, function (instance) { - client.getUsers({ - instance: instance, - limit: 1, - offset:testContext.marker }, function(err, list, offset) { - should.not.exist(err); - should.exist(list); - list.should.be.instanceOf(Array); - list.should.have.length(1); - should.exist(offset); - server && server.done(); - done(); - }); - }); - }); - }); - - describe('the destroyUsers() method', function() { - it('should respond correctly', function(done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTest') - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.destroyUser(instance, 'joeTest', function(err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - - it('should destroy the user used for pagination', function(done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .delete('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/users/joeTestTwo') - .reply(202); - } - - helpers.selectInstance(client, function (instance) { - client.destroyUser(instance, 'joeTestTwo', function (err, response) { - should.not.exist(err); - should.exist(response); - response.statusCode.should.equal(202); - server && server.done(); - done(); - }); - }); - }); - }); - - it('the enableRoot() method should respond correctly', function(done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .post('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') - .reply(200, { - user: { - password: 'dbba235b-d078-42ec-b992-dec1464c49cc', - name: 'root' - } - }); - } - - helpers.selectInstance(client, function (instance) { - client.enableRoot(instance, function(err, user, response) { - should.not.exist(err); - should.exist(user); - should.exist(response); - response.statusCode.should.equal(200); - should.exist(response.body); - response.body.user.should.be.a('object'); - should.exist(response.body.user.password); - response.body.user.name.should.equal('root'); - server && server.done(); - done(); - }); - }); - }); - - it('the enableRoot() method should respond correctly', function (done) { - - if (mock) { - server - .get('/v1.0/123456/instances') - .reply(200, helpers.loadFixture('rackspace/databaseInstances.json')) - .get('/v1.0/123456/instances/51a28a3e-2b7b-4b5a-a1ba-99b871af2c8f/root') - .reply(200, { rootEnabled: true }); - } - - helpers.selectInstance(client, function (instance) { - client.rootEnabled(instance, function (err, root, response) { - should.not.exist(err); - should.exist(root); - should.exist(response); - response.statusCode.should.equal(200); - server && server.done(); - done(); - }); - }); - }); - - after(function (done) { - if (!mock) { - return done(); - } - - async.parallel([ - function (next) { - authServer.close(next); - }, - function (next) { - server.close(next); - } - ], done) - }); - }); -}); diff --git a/test/rackspace/macros.js b/test/rackspace/macros.js index 89bbc1341..78c95fc77 100644 --- a/test/rackspace/macros.js +++ b/test/rackspace/macros.js @@ -1,16 +1,13 @@ /* * macros.js: Tests macros for Rackspace * - * (C) 2011 Nodejitsu Inc. + * (C) 2011 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var fs = require('fs'), - filed = require('filed'), - assert = require('../helpers/assert'), - should = require('should'), - helpers = require('../helpers'), - mock = !!process.env.MOCK; + filed = require('filed-mimefix'), + assert = require('../helpers/assert'); exports.shouldHaveCreds = function (client) { return function () { @@ -19,38 +16,38 @@ exports.shouldHaveCreds = function (client) { assert.include(client.config, 'apiKey'); assert.isFunction(client.auth); - } + }; }; exports.shouldCreateContainer = function (client, name, message) { - message = message || "when creating a container"; + message = message || 'when creating a container'; var context = {}; context[message] = { topic: function () { client.createContainer(name, this.callback); }, - "should return a valid container": function (err, container) { + 'should return a valid container': function (err, container) { assert.isNull(err); assert.assertContainer(container); } }; return { - "The pkgcloud Rackspace storage client": { - "the createContainer() method": context + 'The pkgcloud Rackspace storage client': { + 'the createContainer() method': context } }; }; exports.shouldDestroyContainer = function (client, name) { return { - "The pkgcloud Rackspace storage client": { - "the destroyContainer() method": { + 'The pkgcloud Rackspace storage client': { + 'the destroyContainer() method': { topic: function () { - client.destroyContainer(name, this.callback) + client.destroyContainer(name, this.callback); }, - "should return true": function (err, success) { + 'should return true': function (err, success) { assert.isTrue(success); } } @@ -66,10 +63,10 @@ exports.upload.fullpath = function (client, options) { client.upload(options, function () { }) .on('end', this.callback); }, - "should raise the `end` event": function () { + 'should raise the `end` event': function () { assert.isTrue(true); } - } + }; }; exports.upload.stream = function (client, container, local, remote) { @@ -84,10 +81,10 @@ exports.upload.stream = function (client, container, local, remote) { } }, function () { }).on('end', this.callback); }, - "should raise the `end` event": function () { + 'should raise the `end` event': function () { assert.isTrue(true); } - } + }; }; exports.upload.piped = function (client, container, local, remote) { @@ -99,10 +96,10 @@ exports.upload.piped = function (client, container, local, remote) { }, function () { }); filed(local).pipe(ustream); - ustream.on('end', this.callback) + ustream.on('end', this.callback); }, - "should raise the `end` event": function () { + 'should raise the `end` event': function () { assert.isTrue(true); } - } + }; }; diff --git a/test/rackspace/storage/authentication-test.js b/test/rackspace/storage/authentication-test.js index d63e28ac0..7271f9102 100644 --- a/test/rackspace/storage/authentication-test.js +++ b/test/rackspace/storage/authentication-test.js @@ -1,14 +1,14 @@ /* * authentication-test.js: Tests for pkgcloud Rackspace storage authentication * -* (C) 2010 Nodejitsu Inc. +* (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * */ var should = require('should'), macros = require('../macros'), helpers = require('../../helpers'), - async = require('async'), + http = require('http'), hock = require('hock'), mock = !!process.env.MOCK; @@ -21,23 +21,22 @@ describe('pkgcloud/rackspace/storage/authentication', function () { describe('the auth() method', function() { describe('with a valid user name and api key', function() { - var authServer; + var authHockInstance, authServer; before(function(done) { if (!mock) { return done(); } - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - done(); - }); + authHockInstance = hock.createHock(); + authServer = http.createServer(authHockInstance.handler); + authServer.listen(12346, done); }); it('should respond with 204 and appropriate info', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -53,14 +52,14 @@ describe('pkgcloud/rackspace/storage/authentication', function () { client.auth(function (err) { should.not.exist(err); - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); }); it('should update the config with appropriate urls', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -76,13 +75,13 @@ describe('pkgcloud/rackspace/storage/authentication', function () { client.auth(function (err) { should.not.exist(err); - authServer && authServer.done(); + authHockInstance && authHockInstance.done(); done(); }); }); after(function(done) { - if (authServer) { + if (authHockInstance) { authServer.close(function () { done(); }); @@ -94,23 +93,22 @@ describe('pkgcloud/rackspace/storage/authentication', function () { }); describe('with an invalid user name and api key shouldn\'t authenticate', function () { - var authServer; + var authHockInstance, authServer; before(function (done) { if (!mock) { return done(); } - hock.createHock(12346, function (err, hockClient) { - authServer = hockClient; - done(); - }); + authHockInstance = hock.createHock(); + authServer = http.createServer(authHockInstance.handler); + authServer.listen(12346, done); }); it('should respond with 401 unauthorized', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -135,13 +133,14 @@ describe('pkgcloud/rackspace/storage/authentication', function () { badClient.auth(function (err, res) { should.exist(err); - authServer && authServer.done(); + should.not.exist(res); + authHockInstance && authHockInstance.done(); done(); }); }); after(function (done) { - if (authServer) { + if (authHockInstance) { authServer.close(function () { done(); }); diff --git a/test/rackspace/storage/container-test.js b/test/rackspace/storage/container-test.js index 3f67a8087..a13c53f67 100755 --- a/test/rackspace/storage/container-test.js +++ b/test/rackspace/storage/container-test.js @@ -1,18 +1,16 @@ /* * container-test.js: Tests for Rackspace Cloudfiles containers * -* (C) 2010 Nodejitsu Inc. +* (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * MIT LICENSE * */ -var path = require('path'), - fs = require('fs'), - should = require('should'), - pkgcloud = require('../../../lib/pkgcloud'), +var should = require('should'), helpers = require('../../helpers'), async = require('async'), hock = require('hock'), + http = require('http'), Container = require('../../../lib/pkgcloud/core/storage/container').Container, mock = !!process.env.MOCK; @@ -23,7 +21,7 @@ if (!mock) { describe('pkgcloud/rackspace/storage/containers', function () { describe('The pkgcloud Rackspace Storage client', function () { - var client, server, authServer; + var client, hockInstance, authHockInstance, authServer, server; before(function (done) { client = helpers.createClient('rackspace', 'storage'); @@ -32,24 +30,18 @@ describe('pkgcloud/rackspace/storage/containers', function () { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); + authServer.listen(12346, next); } ], done); }); @@ -57,7 +49,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainers should return a list of containers', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -68,7 +60,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainers.json'); } @@ -80,8 +72,8 @@ describe('pkgcloud/rackspace/storage/containers', function () { containers.forEach(function(c) { c.should.be.instanceof(Container); }); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -89,7 +81,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainers with options should get CDN attributes and return a list of containers', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainers.json') .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') @@ -161,7 +153,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { 'content-length': '0', 'x-trans-id': 'tx8a8acb8f3f7142c8bd36f27a18415996', date: 'Wed, 12 Jun 2013 19:04:25 GMT', - connection: 'keep-alive' }) + connection: 'keep-alive' }); } @@ -174,8 +166,8 @@ describe('pkgcloud/rackspace/storage/containers', function () { containers.forEach(function (c) { c.should.be.instanceof(Container); }); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -183,7 +175,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainers with limit should return reduced set', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json&limit=3') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainersLimit.json'); } @@ -195,7 +187,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { containers.forEach(function (c) { c.should.be.instanceof(Container); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -203,7 +195,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainers with limit should return reduced set', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json&limit=3') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainersLimit.json'); } @@ -215,7 +207,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { containers.forEach(function (c) { c.should.be.instanceof(Container); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -223,7 +215,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainers with marker should start offset appropriately', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json&marker=0.1.3-90') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainersMarker.json'); } @@ -235,7 +227,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { containers.forEach(function (c) { c.should.be.instanceof(Container); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -243,7 +235,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainers with marker and limit should start offset appropriatley', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?format=json&limit=4&marker=0.1.3-85') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainersLimitMarker.json'); } @@ -255,14 +247,14 @@ describe('pkgcloud/rackspace/storage/containers', function () { containers.forEach(function (c) { c.should.be.instanceof(Container); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); it('getContainer should URL encode container names', function (done) { if (mock) { - server + hockInstance .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/~!%40%23%24%25%5E%26*()_%2B') .reply(200, '', { 'content-length': '0', 'x-container-object-count': '144', @@ -286,16 +278,48 @@ describe('pkgcloud/rackspace/storage/containers', function () { container.should.be.instanceof(Container); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); + it('getContainer should allow 403 cdn response (for ACL)', function (done) { + + if (mock) { + hockInstance + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') + .reply(200, '', { 'content-length': '0', + 'x-container-object-count': '144', + 'x-container-meta-awesome': 'Tue Jun 04 2013 07:58:52 GMT-0700 (PDT)', + 'x-timestamp': '1368837729.84945', + 'x-container-meta-foo': 'baz', + 'x-container-bytes-used': '134015617', + 'content-type': 'application/json; charset=utf-8', + 'accept-ranges': 'bytes', + 'x-trans-id': 'txb0bcacabf853476e87f846ff0e85a22f', + date: 'Thu, 13 Jun 2013 15:18:17 GMT', + connection: 'keep-alive' } + ) + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') + .reply(403); + } + + client.getContainer('0.1.3-85', function (err, container) { + should.not.exist(err); + should.exist(container); + + container.should.be.instanceof(Container); + + hockInstance && hockInstance.done(); + done(); + }); + }); + it('getContainer should include cdn metadata', function (done) { if (mock) { - server + hockInstance .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') .reply(200, '', { 'content-length': '0', 'x-container-object-count': '144', @@ -322,7 +346,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { 'content-length': '0', 'x-trans-id': 'tx8a8acb8f3f7142c8bd36f27a18415996', date: 'Wed, 12 Jun 2013 19:04:25 GMT', - connection: 'keep-alive' }) + connection: 'keep-alive' }); } client.getContainer('0.1.3-85', function (err, container) { @@ -333,7 +357,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { container.cdnEnabled.should.equal(true); container.cdnUri.should.equal('http://cbebcab2b59eae3d0c71-edfcb31ae70ea7c07367728d50539bc7.r63.cf1.rackcdn.com'); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -341,7 +365,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('getContainer and enable CDN ', function (done) { if (mock) { - server + hockInstance .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') .reply(200, '', { 'content-length': '0', 'x-container-object-count': '144', @@ -384,7 +408,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { 'x-cdn-streaming-uri': 'http://e5addf7be8783adf8c6d-edfcb31ae70ea7c07367728d50539bc7.r63.stream.cf1.rackcdn.com', 'content-length': '0', 'x-trans-id': 'tx8a8acb8f3f7142c8bd36f27a18415996', - date: 'Wed, 12 Jun 2013 19:04:25'}) + date: 'Wed, 12 Jun 2013 19:04:25'}); } client.getContainer('0.1.3-85', function (err, container) { @@ -404,7 +428,143 @@ describe('pkgcloud/rackspace/storage/containers', function () { container.cdnEnabled.should.equal(true); container.cdnUri.should.equal('http://cbebcab2b59eae3d0c71-edfcb31ae70ea7c07367728d50539bc7.r63.cf1.rackcdn.com'); - server && server.done(); + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('getContainer and set static website index page and error page ', function (done) { + + if (mock) { + hockInstance + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') + .reply(200, '', { 'content-length': '0', + 'x-container-object-count': '144', + 'x-container-meta-awesome': 'Tue Jun 04 2013 07:58:52 GMT-0700 (PDT)', + 'x-timestamp': '1368837729.84945', + 'x-container-meta-foo': 'baz', + 'x-container-bytes-used': '134015617', + 'content-type': 'application/json; charset=utf-8', + 'accept-ranges': 'bytes', + 'x-trans-id': 'txb0bcacabf853476e87f846ff0e85a22f', + date: 'Thu, 13 Jun 2013 15:18:17 GMT', + connection: 'keep-alive' } + ) + .post('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85', null, null) + .reply(200, '', { 'content-length': '0', + 'x-container-object-count': '144', + 'x-container-meta-awesome': 'Tue Jun 04 2013 07:58:52 GMT-0700 (PDT)', + 'x-container-meta-web-index': 'index.htm', + 'x-container-meta-web-error': 'error.htm', + 'x-timestamp': '1368837729.84945', + 'x-container-meta-foo': 'baz', + 'x-container-bytes-used': '134015617', + 'content-type': 'application/json; charset=utf-8', + 'accept-ranges': 'bytes', + 'x-trans-id': 'txb0bcacabf853476e87f846ff0e85a22f', + date: 'Thu, 13 Jun 2013 15:18:17 GMT', + connection: 'keep-alive' } + ) + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') + .reply(200, '', { + 'x-cdn-ssl-uri': 'https://c98c1215ec09a78cd287-edfcb31ae70ea7c07367728d50539bc7.ssl.cf1.rackcdn.com', + 'x-ttl': '186400', + 'x-container-meta-web-index': 'index.htm', + 'x-container-meta-web-error': 'error.htm', + 'x-log-retention': 'True', + 'content-type': 'text/html; charset=UTF-8', + 'x-cdn-streaming-uri': 'http://e5addf7be8783adf8c6d-edfcb31ae70ea7c07367728d50539bc7.r63.stream.cf1.rackcdn.com', + 'content-length': '0', + 'x-trans-id': 'tx8a8acb8f3f7142c8bd36f27a18415996', + date: 'Wed, 12 Jun 2013 19:04:25'}); + } + + client.getContainer('0.1.3-85', function (err, container) { + should.not.exist(err); + should.exist(container); + + container.should.be.instanceof(Container); + + (container.metadata['web-index'] == undefined).should.be.true; + (container.metadata['web-error'] == undefined).should.be.true; + + container.setStaticWebsite({indexFile: 'index.htm', errorFile: 'error.htm'}, function (err, container) { + should.not.exist(err); + should.exist(container); + container.should.be.instanceof(Container); + + container.metadata['web-index'].should.equal('index.htm'); + container.metadata['web-error'].should.equal('error.htm'); + + hockInstance && hockInstance.done(); + done(); + }); + }); + }); + + it('getContainer and remove static website', function (done) { + + if (mock) { + hockInstance + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') + .reply(200, '', { 'content-length': '0', + 'x-container-object-count': '144', + 'x-container-meta-awesome': 'Tue Jun 04 2013 07:58:52 GMT-0700 (PDT)', + 'x-container-meta-web-index': 'index.htm', + 'x-container-meta-web-error': 'error.htm', + 'x-timestamp': '1368837729.84945', + 'x-container-meta-foo': 'baz', + 'x-container-bytes-used': '134015617', + 'content-type': 'application/json; charset=utf-8', + 'accept-ranges': 'bytes', + 'x-trans-id': 'txb0bcacabf853476e87f846ff0e85a22f', + date: 'Thu, 13 Jun 2013 15:18:17 GMT', + connection: 'keep-alive' } + ) + .post('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85', null, null) + .reply(200, '', { 'content-length': '0', + 'x-container-object-count': '144', + 'x-container-meta-awesome': 'Tue Jun 04 2013 07:58:52 GMT-0700 (PDT)', + 'x-timestamp': '1368837729.84945', + 'x-container-meta-foo': 'baz', + 'x-container-bytes-used': '134015617', + 'content-type': 'application/json; charset=utf-8', + 'accept-ranges': 'bytes', + 'x-trans-id': 'txb0bcacabf853476e87f846ff0e85a22f', + date: 'Thu, 13 Jun 2013 15:18:17 GMT', + connection: 'keep-alive' } + ) + .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.3-85') + .reply(200, '', { + 'x-cdn-ssl-uri': 'https://c98c1215ec09a78cd287-edfcb31ae70ea7c07367728d50539bc7.ssl.cf1.rackcdn.com', + 'x-ttl': '186400', + 'x-log-retention': 'True', + 'content-type': 'text/html; charset=UTF-8', + 'x-cdn-streaming-uri': 'http://e5addf7be8783adf8c6d-edfcb31ae70ea7c07367728d50539bc7.r63.stream.cf1.rackcdn.com', + 'content-length': '0', + 'x-trans-id': 'tx8a8acb8f3f7142c8bd36f27a18415996', + date: 'Wed, 12 Jun 2013 19:04:25'}); + } + + client.getContainer('0.1.3-85', function (err, container) { + should.not.exist(err); + should.exist(container); + + container.should.be.instanceof(Container); + + container.metadata['web-index'].should.equal('index.htm'); + container.metadata['web-error'].should.equal('error.htm'); + + container.removeStaticWebsite(function (err, container) { + should.not.exist(err); + should.exist(container); + container.should.be.instanceof(Container); + + (container.metadata['web-index'] == undefined).should.be.true; + (container.metadata['web-error'] == undefined).should.be.true; + + hockInstance && hockInstance.done(); done(); }); }); @@ -412,7 +572,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { it('updateContainerMetadata should throw if passed non container', function() { (function() { - client.updateContainerMetadata({ name: 'foo' }) + client.updateContainerMetadata({ name: 'foo' }); }).should.throw(); }); @@ -428,7 +588,7 @@ describe('pkgcloud/rackspace/storage/containers', function () { function (next) { server.close(next); } - ], done) + ], done); }); }); }); diff --git a/test/rackspace/storage/storage-object-large-test.js b/test/rackspace/storage/storage-object-large-test.js index 06bf43a21..495f5c367 100755 --- a/test/rackspace/storage/storage-object-large-test.js +++ b/test/rackspace/storage/storage-object-large-test.js @@ -1,7 +1,7 @@ /* * container-test.js: Tests for Rackspace Cloudfiles containers * - * (C) 2010 Nodejitsu Inc. + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * MIT LICENSE * */ diff --git a/test/rackspace/storage/storage-object-metadata-test.js b/test/rackspace/storage/storage-object-metadata-test.js index 0eb9decd6..e98f98352 100755 --- a/test/rackspace/storage/storage-object-metadata-test.js +++ b/test/rackspace/storage/storage-object-metadata-test.js @@ -1,7 +1,7 @@ /* * container-test.js: Tests for Rackspace Cloudfiles containers * - * (C) 2010 Nodejitsu Inc. + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * MIT LICENSE * */ diff --git a/test/rackspace/storage/storage-object-noauth-test.js b/test/rackspace/storage/storage-object-noauth-test.js index 37e3bbad9..82bb189aa 100644 --- a/test/rackspace/storage/storage-object-noauth-test.js +++ b/test/rackspace/storage/storage-object-noauth-test.js @@ -1,7 +1,7 @@ ///* // * storage-object-test.js: Tests for uploading files to Rackspace Cloudfiles when not authenticated. // * -// * (C) 2010 Nodejitsu Inc. +// * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. // * MIT LICENSE // * // */ diff --git a/test/rackspace/storage/storage-object-test.js b/test/rackspace/storage/storage-object-test.js index baf544645..2b332e7b8 100755 --- a/test/rackspace/storage/storage-object-test.js +++ b/test/rackspace/storage/storage-object-test.js @@ -1,21 +1,20 @@ /* * storage-object-test.js: Tests for Rackspace Cloudfiles containers * - * (C) 2010 Nodejitsu Inc. + * (C) 2010 Charlie Robbins, Ken Perkins, Ross Kukulinski & the Contributors. * MIT LICENSE * */ -var path = require('path'), - fs = require('fs'), +var fs = require('fs'), should = require('should'), - pkgcloud = require('../../../lib/pkgcloud'), helpers = require('../../helpers'), async = require('async'), + http = require('http'), hock = require('hock'), File = require('../../../lib/pkgcloud/core/storage/file').File, mock = !!process.env.MOCK, - Buffer = require("buffer").Buffer; + Buffer = require('buffer').Buffer; if (!mock) { return; // these tests are disabled when running for real @@ -24,7 +23,7 @@ if (!mock) { describe('pkgcloud/rackspace/storage/storage-object', function () { describe('The pkgcloud Rackspace Storage client', function () { - var client, server, authServer; + var client, hockInstance, authHockInstance, server, authServer; /** * Generates a container file list response of specified size for large container tests. @@ -36,17 +35,17 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { var generateFilesResponse = function (start, end) { var files = []; function padToFive(number) { - if (number<=99999) { number = ("0000"+number).slice(-5); } + if (number<=99999) { number = ('0000'+number).slice(-5); } return number; } for (var i = start; i < end; i++) { files.push({ - "hash": "cb5c530452af82fb875dc0fb1a00a2c4", - "last_modified": "2013-05-20T22:48:08.059180", - "bytes": 2027, - "name": "FILE" + padToFive(i), - "content_type": "application/octet-stream" + hash: 'cb5c530452af82fb875dc0fb1a00a2c4', + last_modified: '2013-05-20T22:48:08.059180', + bytes: 2027, + name: 'FILE' + padToFive(i), + content_type: 'application/octet-stream' }); } @@ -60,24 +59,18 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { return done(); } + hockInstance = hock.createHock({ throwOnUnmatched: false }); + authHockInstance = hock.createHock(); + + server = http.createServer(hockInstance.handler); + authServer = http.createServer(authHockInstance.handler); + async.parallel([ function (next) { - hock.createHock(12346, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - authServer = hockClient; - next(); - }); + server.listen(12345, next); }, function (next) { - hock.createHock(12345, function (err, hockClient) { - should.not.exist(err); - should.exist(hockClient); - - server = hockClient; - next(); - }); + authServer.listen(12346, next); } ], done); }); @@ -85,7 +78,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { it('getFiles should return a list of files', function (done) { if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -96,7 +89,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getFiles.json'); } @@ -108,8 +101,8 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { files.forEach(function (f) { f.should.be.instanceof(File); }); - authServer && authServer.done(); - server && server.done(); + authHockInstance && authHockInstance.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -120,7 +113,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { return done(); } - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json') .reply(200, generateFilesResponse(0, 10000)); @@ -128,7 +121,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { should.not.exist(err); should.exist(files); files.length.should.equal(10000); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -136,7 +129,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { it('getFiles with limit should return reduced set', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json&limit=3') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getContainersLimit.json'); } @@ -148,7 +141,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { files.forEach(function (f) { f.should.be.instanceof(File); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -159,7 +152,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { return done(); } - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json') .reply(200, generateFilesResponse(0, 10000)) .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json&marker=FILE09999') @@ -171,7 +164,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { should.not.exist(err); should.exist(files); files.should.have.length(23400); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -182,7 +175,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { return done(); } - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json') .reply(200, generateFilesResponse(0, 10000)) .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json&marker=FILE09999') @@ -194,7 +187,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { should.not.exist(err); should.exist(files); files.should.have.length(20000); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -202,7 +195,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { it('getFiles with marker should start offset appropriately', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json&marker=ubuntu-10.04-x86_64%2Fconf%2Fdistributions') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getFilesMarker.json'); } @@ -214,7 +207,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { files.forEach(function (f) { f.should.be.instanceof(File); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); @@ -222,7 +215,7 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { it('getFiles with marker and limit should start offset appropriately', function (done) { if (mock) { - server + hockInstance .get('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215?format=json&limit=4&marker=CHANGELOG') .replyWithFile(200, __dirname + '/../../fixtures/rackspace/getFilesLimitMarker.json'); } @@ -234,14 +227,14 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { files.forEach(function (f) { f.should.be.instanceof(File); }); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); it('getFile should URL encode the file name', function (done) { if (mock) { - server + hockInstance .head('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00/0.1.7-215/~!%40%23%24%25%5E%26*()_%2B/~!%40%23%24%25%5E%26*()_%2B?format=json') .reply(200); } @@ -250,19 +243,19 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { should.not.exist(err); should.exist(file); file.should.be.instanceof(File); - server && server.done(); + hockInstance && hockInstance.done(); done(); }); }); it('extract should ask server to extract the uploaded tar file', function(done) { - - var data = "H4sIABub81EAA+3TzUrEMBAH8CiIeNKTXvMC1nxuVzx58CiC9uBNam1kQZt1N4X1XXwDX9IJXVi6UDxo6sH/D4akadJOmY7z/owlJkhubTdOulEo040dJpXMTS6tjuuSriTjNnViUbsM5YJztvCPs+atHdxH25wbI6FxOap/9hDqZcjCKqR5RyzwxJjB+iurN/WXiuqvpdGMizTp9P3z+rO94322y9h1WfGbO37P1+IaO6BQFO8U8fqzd/Jo6JGXRXG7nsYTHxSHW1t2NusnlX/Nyvn8pc6KehWumso/zZpnutkGdzq9kNrQv3E+Nb/yudAX+z9t93/f/0LIrf5XNEP/j0H+dQIAAAAAAAAAAAAAAAAAAADwY194ELb5ACgAAA=="; - var tmp = "./foo.tar.gz"; - fs.writeFileSync(tmp, new Buffer(data, "base64")); - + + var data = 'H4sIABub81EAA+3TzUrEMBAH8CiIeNKTXvMC1nxuVzx58CiC9uBNam1kQZt1N4X1XXwDX9IJXVi6UDxo6sH/D4akadJOmY7z/owlJkhubTdOulEo040dJpXMTS6tjuuSriTjNnViUbsM5YJztvCPs+atHdxH25wbI6FxOap/9hDqZcjCKqR5RyzwxJjB+iurN/WXiuqvpdGMizTp9P3z+rO94322y9h1WfGbO37P1+IaO6BQFO8U8fqzd/Jo6JGXRXG7nsYTHxSHW1t2NusnlX/Nyvn8pc6KehWumso/zZpnutkGdzq9kNrQv3E+Nb/yudAX+z9t93/f/0LIrf5XNEP/j0H+dQIAAAAAAAAAAAAAAAAAAADwY194ELb5ACgAAA=='; + var tmp = './foo.tar.gz'; + fs.writeFileSync(tmp, new Buffer(data, 'base64')); + if (mock) { - authServer + authHockInstance .post('/v2.0/tokens', { auth: { 'RAX-KSKEY:apiKeyCredentials': { @@ -273,40 +266,40 @@ describe('pkgcloud/rackspace/storage/storage-object', function () { }) .reply(200, helpers.getRackspaceAuthResponse()); - server - .put('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?extract-archive=tar.gz', new Buffer(data, "base64").toString()) + hockInstance + .put('/v1/MossoCloudFS_00aa00aa-aa00-aa00-aa00-aa00aa00aa00?extract-archive=tar.gz', new Buffer(data, 'base64').toString()) .replyWithFile(200, __dirname + '/../../fixtures/rackspace/extract.json'); } - - - + + + client.extract({ local: tmp }, function(e, ok, resp) { should.not.exist(e); should.exist(resp); - server && server.done(); - + hockInstance && hockInstance.done(); + fs.unlinkSync(tmp); done(); }); - + }); after(function (done) { if (!mock) { return done(); } - + async.parallel([ function (next) { - authServer.close(next); + server.close(next); }, function (next) { - server.close(next); + authServer.close(next); } - ], done) + ], done); }); }); }); diff --git a/test/redistogo/databases/databases-test.js b/test/redistogo/databases/databases-test.js deleted file mode 100644 index fec3a795b..000000000 --- a/test/redistogo/databases/databases-test.js +++ /dev/null @@ -1,130 +0,0 @@ -/* -* databases-test.js: Tests for Redistogo databases service -* -* (C) 2012 Nodejitsu Inc. -* MIT LICENSE -* -*/ - -var should = require('should'), - helpers = require('../../helpers'), - hock = require('hock'), - mock = !!process.env.MOCK; - -describe('pkgcloud/redistogo/databases', function () { - var testContext = {}, - client = helpers.createClient('redistogo', 'database'), - server = null; - - before(function(done) { - if (!mock) { - return done(); - } - - hock.createHock(12345, function(err, hockClient) { - server = hockClient; - done(); - }); - - }); - - describe('The pkgcloud RedisToGo Database client', function () { - describe('the create method()', function() { - it('with correct options should respond correctly', function(done) { - - if (mock) { - server - .post('/instances.json', "instance%5Bplan%5D=nano") - .replyWithFile(201, __dirname + '/../../fixtures/redistogo/database.json'); - } - - client.create({ plan: 'nano' }, function(err, database) { - should.not.exist(err); - should.exist(database); - should.exist(database.id); - should.exist(database.uri); - should.exist(database.username); - should.exist(database.password); - testContext.databaseId = database.id; - server && server.done(); - done(); - }); - }); - - it('with no options should respond with errors', function (done) { - client.create(function (err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - describe('the get() method', function() { - it('with correct options should respond correctly', function(done) { - if (mock) { - server - .get('/instances/253739.json') - .replyWithFile(200, __dirname + '/../../fixtures/redistogo/database.json'); - } - - client.get(testContext.databaseId, function (err, database) { - should.not.exist(err); - should.exist(database); - should.exist(database.id); - should.exist(database.uri); - should.exist(database.username); - should.exist(database.password); - server && server.done(); - done(); - }); - }); - - it('with options should respond with an error', function(done) { - client.get(function(err, database) { - should.exist(err); - should.not.exist(database); - done(); - }); - }); - }); - - describe('the remove() method', function () { - it('with correct options should respond correctly', function (done) { - if (mock) { - server - .delete('/instances/253739.json') - .reply(200); - } - - client.remove(testContext.databaseId, function (err, confirm) { - should.not.exist(err); - should.exist(confirm); - confirm.should.equal('deleted'); - server && server.done(); - done(); - }); - }); - - it('with options should respond with an error', function (done) { - client.remove(function (err, confirm) { - should.exist(err); - should.not.exist(confirm); - done(); - }); - }); - }); - }); - - after(function(done) { - if (server) { - server.close(function() { - done(); - }); - } - else { - done(); - } - }) -}); -