diff --git a/lib/compute/address.js b/lib/compute/address.js new file mode 100644 index 00000000000..a7d4cad33a3 --- /dev/null +++ b/lib/compute/address.js @@ -0,0 +1,129 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/address + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create an Address object to interact with a Google Compute Engine address. + * + * @constructor + * @alias module:region/address + * + * @throws {Error} if an address name or a region are not provided. + * + * @param {module:region} region - The Google Compute Engine region this + * address belongs to. + * @param {string} name - The name of the address. + * @param {object=} metadata - Address metadata. + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myRegion = compute.region('region-name'); + * + * var address = myRegion.address('address1'); + */ +function Address(region, name, metadata) { + this.region = region; + this.name = name; + this.metadata = metadata; + + if (!util.is(this.name, 'string')) { + throw new Error('A name is needed to use a Compute Engine Address.'); + } + if (!this.region) { + throw new Error('A region is needed to use a Compute Engine Address.'); + } +} + +/** + * Delete the address. + * + * @param {function} callback - The callback function. + * + * @example + * address.delete(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of address deletion. + * }); + */ +Address.prototype.delete = function(callback) { + callback = callback || util.noop; + var region = this.region; + this.makeReq_('DELETE', '', null, true, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = region.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get the address' metadata. + * + * @param {function=} callback - The callback function. + * + * @example + * address.getMetadata(function(err, metadata, apiResponse) {}); + */ +Address.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Address.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/addresses/' + this.name + path; + this.region.makeReq_(method, path, query, body, callback); +}; + +module.exports = Address; \ No newline at end of file diff --git a/lib/compute/disk.js b/lib/compute/disk.js new file mode 100644 index 00000000000..6c1a9398961 --- /dev/null +++ b/lib/compute/disk.js @@ -0,0 +1,169 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/disk + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create a Disk object to interact with a Google Compute Engine disk. + * + * @constructor + * @alias module:zone/disk + * + * @throws {Error} if a disk name or a zone are not provided. + * + * @param {module:zone} zone - The Google Compute Engine zone this + * disk belongs to. + * @param {string} name - Name of the disk. + * @param {object=} metadata - Disk metadata. + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myZone = compute.zone('zone-name'); + * + * var disk = myZone.disk('disk1'); + */ +function Disk(zone, name, metadata) { + this.name = name; + this.zone = zone; + this.metadata = metadata; + + if (!util.is(this.name, 'string')) { + throw new Error('A name is needed to use a Compute Engine Disk.'); + } + if (!this.zone) { + throw new Error('A zone is needed to use a Compute Engine Disk.'); + } +} + +/** + * Delete the disk. + * + * @param {function} callback - The callback function. + * + * @example + * disk.delete(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of disk deletion. + * }); + */ +Disk.prototype.delete = function(callback) { + callback = callback || util.noop; + var zone = this.zone; + this.makeReq_('DELETE', '', null, true, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get the disk's metadata. + * + * @param {function=} callback - The callback function. + * + * @example + * disk.getMetadata(function(err, metadata, apiResponse) {}); + */ +Disk.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Create a snapshot of a disk. + * + * @param {string|object} options - Snapshot options or snapshot name. + * @param {string} options.name - Name of the snapshot. + * @param {string=} options.description - Description of the snapshot. + * @param {function} callback - The callback function. + * + * @example + * disk.createSnapshot('my-snapshot', function(err, snapshot, operation) { + * // `snapshot` is a Snapshot object. + * // `operation` is an Operation object and can be used to check the status + * // of snapshot creation. + * }); + */ +Disk.prototype.createSnapshot = function(options, callback) { + if (util.is(options, 'string')) { + options = { + name: options + }; + } + if (!options.name) { + throw new Error('A name is required to create a snapshot.'); + } else if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(options.name)) { + throw new Error('Name must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + + var self = this; + this.makeReq_('POST', '/createSnapshot', null, options, function(err, resp) { + if (err) { + callback(err); + return; + } + var snapshot = self.zone.compute.snapshot(options.name); + var operation = self.zone.operation(resp.name); + operation.metadata = resp; + callback(null, snapshot, operation); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Disk.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/disks/' + this.name + path; + this.zone.makeReq_(method, path, query, body, callback); +}; + +module.exports = Disk; \ No newline at end of file diff --git a/lib/compute/firewall.js b/lib/compute/firewall.js new file mode 100644 index 00000000000..639ffd32318 --- /dev/null +++ b/lib/compute/firewall.js @@ -0,0 +1,192 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/firewall + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create a Firewall object to interact with a Google Compute Engine firewall. + * + * @constructor + * @alias module:compute/firewall + * + * @throws {Error} if a firewall name isn't provided. + * + * @param {module:compute} compute - The Compute module instance this firewall + * belongs to. + * @param {string} name - Name of the firewall. + * @param {object} options - Firewall options. + * @param {string=} options.description - Description of the firewall. + * @param {string=} options.network - Network to which the firewall applies. + * Default value is 'global/networks/default'. + * @param {object[]=} options.allowed - List of allowed protocols and ports. + * @param {string[]=} options.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} options.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} options.sourceTags - List of instance tags to which + * this rule applies. + * @param {string[]=} options.targetTags - List of instance tags indicating + * instances that may process connections according to this rule. + * + * @example + * var gcloud = require('gcloud'); + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var firewall = compute.firewall('tcp-3000'); + */ +function Firewall(compute, name, options) { + this.compute = compute; + this.name = name; + this.metadata = options || {}; + this.metadata.network = this.metadata.network || 'global/networks/default'; + + if (!this.name) { + throw new Error('A name is needed to use a Compute Engine Firewall.'); + } +} + +/** + * Delete the firewall rule. + * + * @param {function} callback - The callback function. + * + * @example + * firewall.delete(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of firewall rule deletion. + * }); + */ +Firewall.prototype.delete = function(callback) { + callback = callback || util.noop; + var compute = this.compute; + this.makeReq_('DELETE', '', null, true, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = compute.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get the firewalls's metadata. + * + * @param {function=} callback - The callback function. + * + * @example + * firewall.getMetadata(function(err, metadata, apiResponse) {}); + */ +Firewall.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Set the firewall's metadata. + * + * @throws {Error} if firewall options are not provided. + * + * @param {module:compute} compute - The Google Compute Engine instance this + * this firewall belongs to. + * @param {object} metadata - Firewall metadata. See a + * [Firewall resource](https://goo.gl/7FpjXA) for detailed information. + * @param {string=} metadata.network - Network to which the firewall applies. + * Default value is 'global/networks/default'. + * @param {object[]=} metadata.allowed - List of allowed protocols and ports. + * @param {string[]=} metadata.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} metadata.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} metadata.sourceTags - List of instance tags to which + * this rule applies. + * @param {string[]=} metadata.targetTags - List of instance tags indicating + * instances that may process connections according to this rule. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of firewall rule update. + * }; + * + * firewall.setMetadata( + * { + * description: 'Brand new description', + * }, callback); + */ +Firewall.prototype.setMetadata = function(metadata, callback) { + callback = callback || util.noop; + if (!util.is(metadata, 'object')) { + throw new Error('Firewall rule options must be provided.'); + } + + metadata.name = this.name; + metadata.network = this.metadata.network; + + var self = this; + this.makeReq_('PATCH', '', null, metadata, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = metadata; + var operation = self.compute.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Firewall.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/global/firewalls/' + this.name + path; + this.compute.makeReq_(method, path, query, body, callback); +}; + +module.exports = Firewall; \ No newline at end of file diff --git a/lib/compute/index.js b/lib/compute/index.js new file mode 100644 index 00000000000..c1d74835ee8 --- /dev/null +++ b/lib/compute/index.js @@ -0,0 +1,1119 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute + */ + +'use strict'; + +var extend = require('extend'); + +/** + * @type {module:compute/region} + * @private + */ +var Region = require('./region.js'); + +/** + * @type {module:compute/zone} + * @private + */ +var Zone = require('./zone.js'); + +/** + * @type {module:compute/firewall} + * @private + */ +var Firewall = require('./firewall.js'); + +/** + * @type {module:compute/snapshot} + * @private + */ +var Snapshot = require('./snapshot.js'); + +/** + * @type {module:compute/firewall} + * @private + */ +var Network = require('./network.js'); + +/** + * @type {module:compute/operation} + * @private + */ +var Operation = require('./operation.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * @type {module:common/streamrouter} + * @private + */ +var streamRouter = require('../common/stream-router.js'); + +/** + * Required scopes for Google Compute Engine API. + * @const {array} + * @private + */ +var SCOPES = ['https://www.googleapis.com/auth/compute']; + +/** + * @const {string} + * @private + */ +var COMPUTE_BASE_URL = 'https://www.googleapis.com/compute/v1/projects/'; + +/** + * Create a Compute object to Interact with the Google Compute Engine API. + * Using this object, you can access your instances with + * {module:compute/instance}, disks with {module:compute/disk} and firewall + * rules with {module:compute/firewall}. + * + * Follow along with the examples to see how to do everything. With a Compute + * object it is possible to search for existing instances, disks and firewall + * as well as to create new ones. Instances can be stopped, reset and started + * and their tags can be updated. Disks can be deleted and attached to running + * instances. Firewall rules can be created and updated. + * + * @alias module:compute + * @constructor + * + * @param {object} options - [Configuration object](#/docs/?method=gcloud). + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json', + * projectId: 'grape-spaceship-123' + * }); + * + * var compute = gcloud.compute(); + */ +function Compute(options) { + if (!(this instanceof Compute)) { + return new Compute(options); + } + + options = options || {}; + + this.makeAuthorizedRequest_ = util.makeAuthorizedRequestFactory({ + credentials: options.credentials, + keyFile: options.keyFilename, + scopes: SCOPES, + email: options.email + }); + + this.projectId = options.projectId; +} + +/** + * Get a reference to a Google Compute Engine region. + * + * @param {string} name - Name of the region. + * @return {module:compute/region} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myRegion = compute.region('region-name'); + */ +Compute.prototype.region = function(name) { + return new Region(this, name); +}; + +/** + * Get a reference to a Google Compute Engine zone. + * + * @param {string} name - Name of the zone. + * @return {module:compute/zone} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myZone = compute.zone('zone-name'); + */ +Compute.prototype.zone = function(name) { + return new Zone(this, name); +}; + +/** + * Get a reference to a Google Compute Engine firewall. + * + * @param {string} name - Name of the existing firewall. + * @param {string=} networkName - Network name for the existing firewall. + * Default value is 'global/networks/default'. + * @return {module:compute/disk} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var firewallRule = compute.firewall('rule1'); + */ +Compute.prototype.firewall = function(name, networkName) { + var options = {}; + options.networkName = networkName; + return new Firewall(this, name, options); +}; + +/** + * Get a reference to a Google Compute Engine snapshot. + * + * @param {string} name - Name of the existing snapshot. + * @return {module:compute/snapshot} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var snapshot = compute.snapshot('snapshot-name'); + */ +Compute.prototype.snapshot = function(name) { + return new Snapshot(this, name); +}; + +/** + * Get a reference to a Google Compute Engine network. + * + * @param {string} name - Name of the existing network. + * @return {module:compute/network} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var network = compute.network('network-name'); + */ +Compute.prototype.network = function(name) { + return new Network(this, name); +}; + +/** + * Get a reference to a Google Compute Engine operation. + * + * @param {string} name - Name of the existing operation. + * @return {module:compute/operation} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var operation = compute.operation('operation-name'); + */ +Compute.prototype.operation = function(name) { + return new Operation(this, name); +}; + +/** + * Create a firewall rule. For a detailed description of method's options see + * [API reference](https://goo.gl/kTMHep). + * + * @throws {Error} if a firewall name or firewall options are not provided. + * If allowed ports, source tags or source ranges are not provided. + * + * @param {string} name - Name of the firewall. + * @param {object} options - Firewall options. + * @param {string=} options.description - Description of the firewall. + * @param {string=|module:compute/network} options.network - Network to which + * the firewall applies. Default value is 'global/networks/default'. + * @param {object[]=} options.allowed - List of allowed protocols and ports. + * @param {string[]=} options.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} options.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} options.sourceTags - List of instance tags to which + * this rule applies. + * @param {string[]=} options.targetTags - List of instance tags indicating + * instances that may process connections according to this rule. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, firewall, operation) { + * // `firewall` is a Firewall object. + * // `operation` is an Operation object and can be used to check the status + * // of firewall rule creation. + * }; + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var firewall = compute.createFirewall('tcp-3000', + * { + * description: 'yada yada', + * allowed: [{ + * IPProtocol: 'tcp', + * ports: ['3000'] + * }], + * sourceRanges: ['0.0.0.0/0'], + * targetTags: ['tcp-3000-tag'] + * }, callback); + */ +Compute.prototype.createFirewall = function(name, options, callback) { + if (!name) { + throw new Error('A name is needed to use a Compute Engine Firewall.'); + } else if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(name)) { + throw new Error('Name must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + if (!options || !util.is(options.allowed, 'array')) { + throw new Error('Allowed protocols and ports must be provided.'); + } + if (!util.is(options.sourceRanges, 'array') && + !util.is(options.sourceTags, 'array')) { + throw new Error('Source ranges or source tags must be provided.'); + } + + if (util.is(options.network, 'object')) { + options.network = + 'projects/' + + options.network.compute.projectId + + '/global/networks/' + + options.network.name; + } else { + options.network = options.network || 'global/networks/default'; + } + + options.name = name; + + var self = this; + this.makeReq_('POST', + '/global/firewalls', + null, + options, function(err, resp) { + if (err) { + callback(err); + return; + } + var firewall = self.firewall(name); + firewall.metadata = options; + var operation = self.operation(resp.name); + operation.metadata = resp; + callback(null, firewall, operation); + }); +}; + +/** + * Create a network. For a detailed description of method's options see + * [API reference](https://goo.gl/cWYdER). + * + * @throws {Error} if a network name or an IPv4 range is not provided. + * + * @param {string} name - Name of the network. + * @param {object} options - Network options. + * @param {string} options.IPv4Range - CIDR range of addresses that are legal on + * this network. + * @param {string=} options.description - Description of the network. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, network, operation) { + * // `network` is a Network object. + * // `operation` is an Operation object and can be used to check the status + * // of network creation. + * }; + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var network = compute.createNetwork('network', + * { + * IPv4Range: '192.168.0.0/16' + * }, callback); + */ +Compute.prototype.createNetwork = function(name, options, callback) { + if (!name) { + throw new Error('A name is needed to use a Compute Engine Network.'); + } else if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(name)) { + throw new Error('Name must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + if (!options || !util.is(options.IPv4Range, 'string')) { + throw new Error('An IPv4 range must be provided.'); + } + + options.name = name; + + var self = this; + this.makeReq_('POST', '/global/networks', null, options, function(err, resp) { + if (err) { + callback(err); + return; + } + var network = self.network(name); + network.metadata = options; + var operation = self.operation(resp.name); + operation.metadata = resp; + callback(null, network, operation); + }); +}; + +/** + * Get a list of instances. For a detailed description of method's options see + * [API reference](https://goo.gl/GeDAwy). + * + * @param {object} options - Instance search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of instances to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, instances) { + * // `instances` is an array of `Instance` objects. + * }; + * + * compute.getInstances( + * { + * filter: 'name eq instance-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, instances, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getInstances(nextQuery, callback); + * } + * }; + * + * compute.getInstances({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the instances from your project as a readable object stream. + * //- + * compute.getInstances() + * .on('error', console.error) + * .on('data', function(instance) { + * // `instance` is an `Instance` object. + * }) + * .on('end', function() { + * // All instances retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getInstances() + * .on('data', function(instance) { + * this.end(); + * }); + */ +Compute.prototype.getInstances = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_( + 'GET', + '/aggregated/instances', + options, + null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var items = resp.items || {}; + var instances = []; + var appendInstance = function(instanceObject) { + var instance = zone.instance(instanceObject.name); + instance.metadata = instanceObject; + instances.push(instance); + }; + for (var zoneName in items) { + var zone = self.zone(zoneName.replace('zones/', '')); + (items[zoneName].instances || []).forEach(appendInstance); + } + callback(null, instances, nextQuery, resp); + }); +}; + +/** + * Get a list of disks. For a detailed description of method's options see + * [API reference](https://goo.gl/M9Qjb3). + * + * @param {object} options - Disk search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of disks to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, disks) { + * // `disks` is an array of `Disk` objects. + * }; + * + * compute.getDisks( + * { + * filter: 'name eq disk-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, disks, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getDisks(nextQuery, callback); + * } + * }; + * + * compute.getDisks({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the disks from your project as a readable object stream. + * //- + * compute.getDisks() + * .on('error', console.error) + * .on('data', function(disk) { + * // `disk` is a `Disk` object. + * }) + * .on('end', function() { + * // All disks retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getDisks() + * .on('data', function(disk) { + * this.end(); + * }); + */ +Compute.prototype.getDisks = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/aggregated/disks', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var items = resp.items || {}; + var disks = []; + var appendDisk = function(diskObject) { + var disk = zone.disk(diskObject.name); + disk.metadata = diskObject; + disks.push(disk); + }; + for (var zoneName in items) { + var zone = self.zone(zoneName.replace('zones/', '')); + (items[zoneName].disks || []).forEach(appendDisk); + } + callback(null, disks, nextQuery, resp); + }); +}; + +/** + * Get a list of addresses. For a detailed description of method's options see + * [API reference](https://goo.gl/r9XmXJ). + * + * @param {object} options - Address search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of addresses to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, addresses) { + * // `addresses` is an array of `Address` objects. + * }; + * + * compute.getAddresses( + * { + * filter: 'name eq address-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, addresses, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getAddresses(nextQuery, callback); + * } + * }; + * + * compute.getAddresses({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the addresses from your project as a readable object stream. + * //- + * compute.getAddresses() + * .on('error', console.error) + * .on('data', function(address) { + * // `address` is an `Address` object. + * }) + * .on('end', function() { + * // All addresses retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getAddresses() + * .on('data', function(address) { + * this.end(); + * }); + */ +Compute.prototype.getAddresses = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_( + 'GET', + '/aggregated/addresses', + options, + null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var items = resp.items || {}; + var addresses = []; + var appendAddress = function(addressObject) { + var address = region.address(addressObject.name); + address.metadata = addressObject; + addresses.push(address); + }; + for (var regionName in items) { + var region = self.region(regionName.replace('regions/', '')); + (items[regionName].addresses || []).forEach(appendAddress); + } + callback(null, addresses, nextQuery, resp); + }); +}; + +/** + * Get a list of snapshots. For a detailed description of method's options see + * [API reference](https://goo.gl/IEMVgi). + * + * @param {object} options - Snapshot search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of snapshots to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, snapshots) { + * // `snapshots` is an array of `Snapshot` objects. + * }; + * + * compute.getSnapshots( + * { + * filter: 'name eq snapshot-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, snapshots, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getSnapshots(nextQuery, callback); + * } + * }; + * + * compute.getSnapshots({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the snapshots from your project as a readable object stream. + * //- + * compute.getSnapshots() + * .on('error', console.error) + * .on('data', function(snapshot) { + * // `snapshot` is a `Snapshot` object. + * }) + * .on('end', function() { + * // All snapshots retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getSnapshots() + * .on('data', function(snapshot) { + * this.end(); + * }); + */ +Compute.prototype.getSnapshots = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/global/snapshots', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var snapshots = (resp.items || []).map(function(snapshotObject) { + var snapshot = self.snapshot(snapshotObject.name); + snapshot.metadata = snapshotObject; + return snapshot; + }); + callback(null, snapshots, nextQuery, resp); + }); +}; + +/** + * Get a list of firewall rules. For a detailed description of method's options + * see [API reference](https://goo.gl/TZRxht). + * + * @param {object} options - Firewall search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of firewalls to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, firewalls) { + * // `firewalls` is an array of `Firewall` objects. + * }; + * + * compute.getFirewalls( + * { + * filter: 'name eq firewall-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, firewalls, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getFirewalls(nextQuery, callback); + * } + * }; + * + * compute.getFirewalls({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the firewalls from your project as a readable object stream. + * //- + * compute.getFirewalls() + * .on('error', console.error) + * .on('data', function(firewall) { + * // `firewall` is a `Firewall` object. + * }) + * .on('end', function() { + * // All firewalls retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getFirewalls() + * .on('data', function(firewall) { + * this.end(); + * }); + */ +Compute.prototype.getFirewalls = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/global/firewalls', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var firewalls = (resp.items || []).map(function(firewallObject) { + var firewall = self.firewall(firewallObject.name); + firewall.metadata = firewallObject; + return firewall; + }); + callback(null, firewalls, nextQuery, resp); + }); +}; + +/** + * Get a list of networks. For a detailed description of method's options + * see [API reference](https://goo.gl/yx70Gc). + * + * @param {object} options - Network search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of networks to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, networks) { + * // `networks` is an array of `Network` objects. + * }; + * + * compute.getNetworks( + * { + * filter: 'name eq network-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, networks, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getNetworks(nextQuery, callback); + * } + * }; + * + * compute.getNetworks({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the networks from your project as a readable object stream. + * //- + * compute.getNetworks() + * .on('error', console.error) + * .on('data', function(network) { + * // `network` is a `Network` object. + * }) + * .on('end', function() { + * // All networks retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getNetworks() + * .on('data', function(network) { + * this.end(); + * }); + */ +Compute.prototype.getNetworks = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/global/networks', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var networks = (resp.items || []).map(function(networkObject) { + var network = self.network(networkObject.name); + network.metadata = networkObject; + return network; + }); + callback(null, networks, nextQuery, resp); + }); +}; + +/** + * Get a list of global operations. For a detailed description of method's + * options see [API reference](https://goo.gl/gX4C1u). + * + * @param {object} options - Operation search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of operations to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, operations) { + * // `operations` is an array of `Operation` objects. + * }; + * + * compute.getOperations( + * { + * filter: 'name eq operation-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, operations, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getOperations(nextQuery, callback); + * } + * }; + * + * compute.getOperations({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the operations from your project as a readable object stream. + * //- + * compute.getOperations() + * .on('error', console.error) + * .on('data', function(operation) { + * // `operation` is a `Operation` object. + * }) + * .on('end', function() { + * // All operations retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getOperations() + * .on('data', function(operation) { + * this.end(); + * }); + */ +Compute.prototype.getOperations = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_( + 'GET', + '/global/operations', + options, + null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var operations = (resp.items || []).map(function(operationObject) { + var operation = self.operation(operationObject.name); + operation.metadata = operationObject; + return operation; + }); + callback(null, operations, nextQuery, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Compute.prototype.makeReq_ = function(method, path, query, body, callback) { + var reqOpts = { + method: method, + qs: query, + uri: COMPUTE_BASE_URL + this.projectId + path + }; + + if (body) { + reqOpts.json = body; + } + + this.makeAuthorizedRequest_(reqOpts, callback); +}; + +/*! Developer Documentation + * + * These methods can be used with either a callback or as a readable object + * stream. `streamRouter` is used to add this dual behavior. + */ +streamRouter.extend(Compute, [ + 'getInstances', + 'getDisks', + 'getAddresses', + 'getSnapshots', + 'getFirewalls', + 'getNetworks', + 'getOperations' +]); + +module.exports = Compute; + diff --git a/lib/compute/instance.js b/lib/compute/instance.js new file mode 100644 index 00000000000..a1a72a86aa4 --- /dev/null +++ b/lib/compute/instance.js @@ -0,0 +1,425 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/instance + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create a Instance object to interact with a Google Compute Engine instance. + * + * @constructor + * @alias module:compute/istance + * + * @throws {Error} if an instance name or a zone are not provided. + * + * @param {module:zone} zone - The Google Compute Engine Zone this + * instance belongs to. + * @param {string} name - Name of the instance. + * @param {object=} metadata - Instance metadata. + * + * @example + * var gcloud = require('gcloud'); + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var myZone = compute.zone('zone-name'); + * + * var instance = myZone.instance('instance1'); + */ +function Instance(zone, name, metadata) { + this.name = name; + this.zone = zone; + this.metadata = metadata; + + if (!util.is(this.name, 'string')) { + throw Error('A name is needed to use a Compute Engine Instance.'); + } + if (!this.zone) { + throw Error('A zone is needed to use a Compute Engine Instance.'); + } +} + +/** + * Delete the instance. + * + * @param {function} callback - The callback function. + * + * @example + * instance.delete(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of instance deletion. + * }); + */ +Instance.prototype.delete = function(callback) { + callback = callback || util.noop; + var zone = this.zone; + this.makeReq_('DELETE', '', null, true, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get the instances's metadata. + * + * @param {function=} callback - The callback function. + * + * @example + * instance.getMetadata(function(err, metadata, apiResponse) {}); + */ +Instance.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Attach a disk to the instance. + * + * @throws {Error} if no disk is provided. + * + * @param {module:compute/disk} disk - The disk to attach. + * @param {object=} options - Disk attach options. + * @param {boolean=} otpions.boot - If true the disk is attached as a boot disk. + * @param {boolean=} options.readOnly - If true the disk is attached as + * `READ_ONLY`. + * @param {number=} options.index - Zero based index assigned from the instance + * to the disk. + * @param {string=} options.deviceName - Device name assigned from the instance + * to the disk. + * @param {boolean=} options.autoDelete - If true the disk is deleted when the + * instance is deleted. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of disk attachment. + * }; + * + * instance.attachDisk( + * disk, + * { + * readOnly : true + * }, callback); + */ +Instance.prototype.attachDisk = function(disk, options, callback) { + if (!disk) { + throw new Error('A disk must be provided.'); + } + if (!callback) { + callback = options; + } + + var diskPath = 'projects/' + + this.compute.projectId + + '/zones/' + + disk.zone.name + + '/disks/' + + disk.name; + + var body = {}; + + body.source = diskPath; + + if (util.is(options.index, 'number')) { + body.index = options.index; + } + if (options.readOnly) { + body.mode = 'READ_ONLY'; + } + if (options.deviceName) { + body.deviceName = options.deviceName; + } + if (options.boot) { + body.boot = options.boot; + } + if (options.autoDelete) { + body.autoDelete = options.autoDelete; + } + + var zone = this.zone; + this.makeReq_('POST', '/attachDisk', null, body, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Detach a disk from the instance. + * + * @param {string} deviceName - The name of the device to detach. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of disk detachment. + * }; + * + * instance.detachDisk( + * disk, + * { + * readOnly : true + * }, callback); + */ +Instance.prototype.detachDisk = function(deviceName, callback) { + if (!util.is(deviceName, 'string')) { + throw new Error('A device name must be provided.'); + } + + var query = {}; + query.deviceName = deviceName; + + var zone = this.zone; + this.makeReq_('POST', '/detachDisk', query, null, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Returns the serial port output for the instance. + * + * @param {number=} port - The port from which the output is retrieved. + * A number in the interval [1,4] (default is 1). + * @param {function} callback - The callback function. + * + * @example + * instance.getSerialPortOutput( + * 4, + * function(err, output) {}); + */ +Instance.prototype.getSerialPortOutput = function(port, callback) { + if (!callback) { + callback = port; + port = 1; + } + if (!util.is(port, 'number') || port < 1 || port > 4) { + throw new Error('Port must be a number between 1 and 4 (inclusive).'); + } + + var query = {}; + query.port = port; + + this.makeReq_( + 'GET', + '/serialPort', + query, + null, function(err, resp) { + if (err) { + callback(err); + return; + } + callback(null, resp.content); + }); +}; + +/** + * Reset the instance. + * + * @param {function} callback - The callback function. + * + * @example + * instance.reset(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of instance reset. + * }); + */ +Instance.prototype.reset = function(callback) { + callback = callback || util.noop; + var zone = this.zone; + this.makeReq_('POST', '/reset', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Start the instance. + * + * @param {function} callback - The callback function. + * + * @example + * instance.start(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of instance start. + * }); + */ +Instance.prototype.start = function(callback) { + callback = callback || util.noop; + var zone = this.zone; + this.makeReq_('POST', '/start', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Stop the instance. + * + * @param {function} callback - The callback function. + * + * @example + * instance.stop(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of instance stop. + * }); + */ +Instance.prototype.stop = function(callback) { + callback = callback || util.noop; + var zone = this.zone; + this.makeReq_('POST', '/stop', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get instance's tags and tags fingerprint. + * + * @param {function} callback - The callback function. + * + * @example + * instance.getTags(function(err, tags, fingerprint) {}); + */ +Instance.prototype.getTags = function(callback) { + if (!callback) { + throw new Error('A callback must be provided to get tags'); + } + this.getMetadata(function(err, metadata) { + if (err) { + callback(err); + return; + } + callback(null, metadata.tags.items, metadata.tags.fingerprint); + }); +}; + +/** + * Set instance's tags. + * + * @param {{string[]} tags - The new tags for the instance. + * @param {string} fingerprint - The current tags fingerprint. Up-to-date + * fingerprint must be provided to set tags. + * @param {function} callback - The callback function. + * + * @example + * instance.getTags(function(err, tags, fingerprint) { + * if (err) throw err; + * tags.push('new-tag'); + * instance.setTags(tags, fingerprint, function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of instance tags set. + * }); + * }); + */ +Instance.prototype.setTags = function(tags, fingerprint, callback) { + callback = callback || util.noop; + if (!util.is(tags, 'array')) { + throw new Error('You must provide an array of tags.'); + } + if (!util.is(fingerprint, 'string')) { + throw new Error('You must provide a fingerprint.'); + } + for (var i = 0; i < tags.length; i++) { + if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(tags[i])) { + throw new Error('Tags must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + } + + var body = {}; + body.items = tags; + body.fingerprint = fingerprint; + + var zone = this.zone; + this.makeReq_('POST', '/setTags', null, body, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = zone.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Instance.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/instances/' + this.name + path; + this.zone.makeReq_(method, path, query, body, callback); +}; + +module.exports = Instance; diff --git a/lib/compute/network.js b/lib/compute/network.js new file mode 100644 index 00000000000..658e234b377 --- /dev/null +++ b/lib/compute/network.js @@ -0,0 +1,248 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/network + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create a Network object to interact with a Google Compute Engine network. + * + * @constructor + * @alias module:compute/network + * + * @throws {Error} if a network name is not provided. + * + * @param {module:compute} compute - The Compute module this network belongs to. + * @param {string} name - Network name. + * @param {object=} metadata - Network metadata. + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var network = compute.network('network-name'); + */ +function Network(compute, name, metadata) { + this.compute = compute; + this.name = name; + this.metadata = metadata; + + if (!util.is(this.name, 'string')) { + throw new Error('A name is needed to use a Compute Engine Network.'); + } +} + +/** + * Delete the network. + * + * @param {function} callback - The callback function. + * + * @example + * network.delete(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of network deletion. + * }); + */ +Network.prototype.delete = function(callback) { + callback = callback || util.noop; + var compute = this.compute; + this.makeReq_('DELETE', '', null, true, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = compute.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get the network's metadata. + * + * @param {function=} callback - The callback function. + * + * @example + * network.getMetadata(function(err, metadata, apiResponse) {}); + */ +Network.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Create a firewall rule for this network. For a detailed description of + * method's options see [API reference](https://goo.gl/kTMHep). + * + * @throws {Error} if a firewall name is not provided. If allowed ports, + * source tags or source ranges are not provided. + * + * @param {string} name - Name of the firewall. + * @param {object} options - Firewall options. + * @param {string=} options.description - Description of the firewall. + * @param {object[]=} options.allowed - List of allowed protocols and ports. + * @param {string[]=} options.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} options.sourceRanges - IP address blocks to which this + * rule applies (in CIDR format). + * @param {string[]=} options.sourceTags - List of instance tags to which + * this rule applies. + * @param {string[]=} options.targetTags - List of instance tags indicating + * instances that may process connections according to this rule. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, firewall, operation) { + * // `firewall` is a Firewall object. + * // `operation` is an Operation object and can be used to check the status + * // of firewall rule creation. + * }; + * + * var network = compute.network('network-name'); + * + * var firewall = network.createFirewall('tcp-3000', + * { + * description: 'yada yada', + * allowed: [{ + * IPProtocol: 'tcp', + * ports: ['3000'] + * }], + * sourceRanges: ['0.0.0.0/0'], + * targetTags: ['tcp-3000-tag'] + * }, callback); + */ +Network.prototype.createFirewall = function(name, options, callback) { + options = options || {}; + options.network = + 'projects/' + + this.compute.projectId + + '/global/networks/' + + this.name; + this.compute.createFirewall(name, options, callback); +}; + +/** + * Get a list of firewall rules for this network. + * + * @param {object} options - Firewall search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of firewalls to return. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, firewalls) { + * // `firewalls` is an array of `Firewall` objects. + * }; + * + * compute.getFirewalls(callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, firewalls, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * compute.getFirewalls(nextQuery, callback); + * } + * }; + * + * compute.getFirewalls({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the firewalls from your project as a readable object stream. + * //- + * compute.getFirewalls() + * .on('error', console.error) + * .on('data', function(firewall) { + * // `firewall` is an `Firewall` object. + * }) + * .on('end', function() { + * // All firewalls retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * compute.getFirewalls() + * .on('data', function(firewall) { + * this.end(); + * }); + */ +Network.prototype.getFirewalls = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + options.filter = + 'network eq ' + + '.*projects/' + + this.compute.projectId + + '/global/networks/' + + this.name; + + if (callback) { + this.compute.getFirewalls(options, callback); + } else { + return this.compute.getFirewalls(options); + } +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Network.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/global/networks/' + this.name + path; + this.compute.makeReq_(method, path, query, body, callback); +}; + +module.exports = Network; \ No newline at end of file diff --git a/lib/compute/operation.js b/lib/compute/operation.js new file mode 100644 index 00000000000..c863ae612c9 --- /dev/null +++ b/lib/compute/operation.js @@ -0,0 +1,143 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/operation + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create an Operation object to interact with a Google Compute Engine + * operation. + * + * @constructor + * + * @throws {Error} if an operation name is not provided. + * + * @param {module:compute} scope - The scope of the operation, can be either a + * `Compute` object, a `Zone` object or a `Region` object. + * @param {string} name - Operation name. + * @param {object=} metadata - Operation metadata. + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * var myZone = compute.zone('example-zone'); + * var myRegion = compute.region('example-region'); + * + * var globalOperation = compute.operation('global-operation'); + * var regionOperation = myRegion.operation('region-operation'); + * var zoneOperation = myZone.operation('zone-operation'); + */ +function Operation(scope, name, metadata) { + this.scope = scope; + this.name = name; + this.metadata = metadata; + + if (!util.is(this.name, 'string')) { + throw new Error('A name is needed to use a Compute Engine Operation.'); + } +} + +/** + * Delete the operation. + * + * @param {function} callback - The callback function. + * + * @example + * operation.delete(function(err) {}); + */ +Operation.prototype.delete = function(callback) { + callback = callback || util.noop; + this.makeReq_('DELETE', '', null, true, callback); +}; + +/** + * Get the operation's metadata. For a detailed description of metadata see + * [Operation resource](https://goo.gl/sWm1rt). + * + * @param {function=} callback - The callback function. + * + * @example + * operation.getMetadata(function(err, metadata, apiResponse) {}); + * + * //- + * // To check the status of an operation and its progress `getMetadata` in + * // combination with `setTimeout` can be used. + * //- + * var checkStatus = function() { + * operation.getMetadata(function(err, metadata, apiResponse) { + * if (err) { + * // An error occurred + * } else { + * if (metadata.status === 'DONE') { + * // the operation completed + * // `metadata.error` is set with errors if the operation failed + * // `metadata.warnings` possibly contains warnings + * } else { + * // `metadata.progress` is a progress indicator in [0,100] + * setTimeout(checkStatus, 10); + * } + * } + * }); + * }; + * + * setTimeout(checkStatus, 10); + */ +Operation.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err && (!resp || resp.name !== self.name)) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Operation.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/operations/' + this.name + path; + if (this.scope.constructor && this.scope.constructor.name === 'Compute') { + path = '/global' + path; + } + this.scope.makeReq_(method, path, query, body, callback); +}; + +module.exports = Operation; \ No newline at end of file diff --git a/lib/compute/region.js b/lib/compute/region.js new file mode 100644 index 00000000000..a972d4eca9a --- /dev/null +++ b/lib/compute/region.js @@ -0,0 +1,392 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/region + */ + +'use strict'; + +var extend = require('extend'); + +/** + * @type {module:compute/address} + * @private + */ +var Address = require('./address.js'); + +/** + * @type {module:compute/operation} + * @private + */ +var Operation = require('./operation.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * @type {module:common/streamrouter} + * @private + */ +var streamRouter = require('../common/stream-router.js'); + +/** + * Create a Region object to interact with a Google Compute Engine region. + * + * @constructor + * @alias module:compute/region + * + * @throws {Error} if a region name is not provided. + * + * @param {module:compute} compute - The Google Compute Engine object this + * region belongs to. + * @param {string} name - Name of the region. + * + * @example + * var gcloud = require('gcloud'); + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var myRegion = compute.region('region-name'); + */ +function Region(compute, name) { + this.name = name; + this.compute = compute; + + if (!this.name) { + throw new Error('A name is needed to use a Compute Engine Region.'); + } +} + +/** + * Get a reference to a Google Compute Engine address in this region. + * + * @param {string} name - Name of the existing address. + * @return {module:compute/address} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myRegion = compute.region('region-name'); + * + * var address = myRegion.address('address-name'); + */ +Region.prototype.address = function(name) { + return new Address(this, name); +}; + +/** + * Get a reference to a Google Compute Engine region operation. + * + * @param {string} name - Name of the existing operation. + * @return {module:compute/operation} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myRegion = compute.region('region-name'); + * + * var operation = myRegion.operation('operation-name'); + */ +Region.prototype.operation = function(name) { + return new Operation(this, name); +}; + +/** + * Create an address in this region. For a detailed description of method's + * options see [API reference](https://goo.gl/lY8Y3u). + * + * @throws {Error} if an address name or a callback are not provided. + * + * @param {string} name - Name of the address. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, address, operation) { + * // `address` is a Address object. + * // `operation` is an Operation object and can be used to check the status + * // of address creation. + * }; + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var myRegion = compute.region('region-name'); + * + * myRegion.createDisk('new-address', callback); + */ +Region.prototype.createAddress = function(name, callback) { + if (!name) { + throw new Error('A name is required to create an address.'); + } else if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(name)) { + throw new Error('Name must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + if (!callback) { + throw new Error('A callback must be defined.'); + } + + var body = { + name: name + }; + + var self = this; + this.makeReq_('POST', '/addresses', null, body, function(err, resp) { + if (err) { + callback(err); + return; + } + var address = self.address(name); + var operation = self.operation(resp.name); + operation.metadata = resp; + callback(null, address, operation); + }); +}; + +/** + * Get a list of addresses in this region. For a detailed description of + * method's options see [API reference](https://goo.gl/By1Az6). + * + * @param {object} options - Address search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of addresses to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, addresses) { + * // `addresses` is an array of `Address` objects. + * }; + * + * var myRegion = compute.region('region-name'); + * + * myRegion.getAddresses( + * { + * filter: 'name eq address-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, addresses, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * myRegion.getAddresses(nextQuery, callback); + * } + * }; + * + * myRegion.getAddresses({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the addresses from your project as a readable object stream. + * //- + * myRegion.getAddresses() + * .on('error', console.error) + * .on('data', function(address) { + * // `address` is an `Address` object. + * }) + * .on('end', function() { + * // All addresses retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * myRegion.getAddresses() + * .on('data', function(address) { + * this.end(); + * }); + */ +Region.prototype.getAddresses = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/addresses', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var addresses = (resp.items || []).map(function(addressObject) { + var address = self.address(addressObject.name); + address.metadata = addressObject; + return address; + }); + callback(null, addresses, nextQuery, resp); + }); +}; + +/** + * Get a list of operations for this region. For a detailed description of + * method's options see [API reference](https://goo.gl/saRUxf). + * + * @param {object} options - Operation search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of operations to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, operations) { + * // `operations` is an array of `Operation` objects. + * }; + * + * var myRegion = compute.region('region-name'); + * + * myRegion.getOperations( + * { + * filter: 'name eq operation-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, operations, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * myRegion.getOperations(nextQuery, callback); + * } + * }; + * + * myRegion.getOperations({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the operations from your project as a readable object stream. + * //- + * myRegion.getOperations() + * .on('error', console.error) + * .on('data', function(operation) { + * // `operation` is a `Operation` object. + * }) + * .on('end', function() { + * // All operations retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * myRegion.getOperations() + * .on('data', function(operation) { + * this.end(); + * }); + */ +Region.prototype.getOperations = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_( + 'GET', + '/operations', + options, + null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var operations = (resp.items || []).map(function(operationObject) { + var operation = self.operation(operationObject.name); + operation.metadata = operationObject; + return operation; + }); + callback(null, operations, nextQuery, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Region.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/regions/' + this.name + path; + this.compute.makeReq_(method, path, query, body, callback); +}; + +/*! Developer Documentation + * + * These methods can be used with either a callback or as a readable object + * stream. `streamRouter` is used to add this dual behavior. + */ +streamRouter.extend(Region, ['getAddresses', 'getOperations']); + +module.exports = Region; diff --git a/lib/compute/snapshot.js b/lib/compute/snapshot.js new file mode 100644 index 00000000000..4795b0f7743 --- /dev/null +++ b/lib/compute/snapshot.js @@ -0,0 +1,124 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/snapshot + */ + +'use strict'; + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * Create a Snapshot object to interact with a Google Compute Engine snapshot. + * + * @constructor + * @alias module:compute/snapshot + * + * @throws {Error} if a snapshot name is not provided. + * + * @param {module:compute} compute - The Compute module this snapshot belongs + * to. + * @param {string} name - Snapshot name. + * @param {object=} metadata - Snapshot metadata. + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var snapshot = compute.snapshot('snapshot-name'); + */ +function Snapshot(compute, name, metadata) { + this.compute = compute; + this.name = name; + this.metadata = metadata; + + if (!util.is(this.name, 'string')) { + throw new Error('A name is needed to use a Compute Engine Snapshot.'); + } +} + +/** + * Delete the snapshot. + * + * @param {function} callback - The callback function. + * + * @example + * snapshot.delete(function(err, operation) { + * // `operation` is an Operation object and can be used to check the status + * // of snapshot deletion. + * }); + */ +Snapshot.prototype.delete = function(callback) { + callback = callback || util.noop; + var compute = this.compute; + this.makeReq_('DELETE', '', null, true, function(err, resp) { + if (err) { + callback(err); + return; + } + var operation = compute.operation(resp.name); + operation.metadata = resp; + callback(null, operation); + }); +}; + +/** + * Get the snapshots's metadata. + * + * @param {function=} callback - The callback function. + * + * @example + * snapshot.getMetadata(function(err, metadata, apiResponse) {}); + */ +Snapshot.prototype.getMetadata = function(callback) { + callback = callback || util.noop; + var self = this; + this.makeReq_('GET', '', null, null, function(err, resp) { + if (err) { + callback(err); + return; + } + self.metadata = resp; + callback(null, self.metadata, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Snapshot.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/global/snapshots/' + this.name + path; + this.compute.makeReq_(method, path, query, body, callback); +}; + +module.exports = Snapshot; \ No newline at end of file diff --git a/lib/compute/zone.js b/lib/compute/zone.js new file mode 100644 index 00000000000..dce273810d3 --- /dev/null +++ b/lib/compute/zone.js @@ -0,0 +1,704 @@ +/*! + * Copyright 2014 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/*! + * @module compute/zone + */ + +'use strict'; + +var extend = require('extend'); + +/** + * @type {module:compute/instance} + * @private + */ +var Instance = require('./instance.js'); + +/** + * @type {module:compute/disk} + * @private + */ +var Disk = require('./disk.js'); + +/** + * @type {module:compute/operation} + * @private + */ +var Operation = require('./operation.js'); + +/** + * @type {module:common/util} + * @private + */ +var util = require('../common/util.js'); + +/** + * @type {module:common/streamrouter} + * @private + */ +var streamRouter = require('../common/stream-router.js'); + +/** + * Create a Zone object to interact with a Google Compute Engine zone. + * + * @constructor + * @alias module:compute/zone + * + * @throws {Error} if a zone name is not provided. + * + * @param {module:compute} compute - The Google Compute Engine object this + * zone belongs to. + * @param {string} name - Name of the zone. + * + * @example + * var gcloud = require('gcloud'); + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var myZone = compute.zone('zone-name'); + */ +function Zone(compute, name) { + this.name = name; + this.compute = compute; + + if (!this.name) { + throw new Error('A name is needed to use a Compute Engine Zone.'); + } +} + +/** + * Get a reference to a Google Compute Engine instance in this zone. + * + * @param {string} name - Name of the existing instance. + * @return {module:compute/instance} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myZone = compute.zone('zone-name'); + * + * var instance = myZone.instance('instance1'); + */ +Zone.prototype.instance = function(name) { + return new Instance(this, name); +}; + +/** + * Get a reference to a Google Compute Engine disk in this zone. + * + * @param {string} name - Name of the existing disk. + * @return {module:compute/disk} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myZone = compute.zone('zone-name'); + * + * var disk = myZone.disk('disk1'); + */ +Zone.prototype.disk = function(name) { + return new Disk(this, name); +}; + +/** + * Get a reference to a Google Compute Engine zone operation. + * + * @param {string} name - Name of the existing operation. + * @return {module:compute/operation} + * + * @example + * var gcloud = require('gcloud')({ + * keyFilename: '/path/to/keyfile.json' + * }); + * + * var compute = gcloud.compute(); + * + * var myZone = compute.zone('zone-name'); + * + * var operation = myZone.operation('operation-name'); + */ +Zone.prototype.operation = function(name) { + return new Operation(this, name); +}; + +/** + * Create an instance in the zone. For a detailed description of method's + * options see [API reference](https://goo.gl/oWcGvQ). + * + * @throws {Error} if an instance name or options are not provided. + * + * @param {string} name - Name of the instance. + * @param {object} options - Options for instance creation. + * @param {string} options.machineType - Type of the instance. + * @param {object[]} options.disks - Disks to attach to the instance. + * @param {boolean} options.disks[].createNew - True if the disk has to be + * created. Default value is false. + * @param {string} options.disks[].source - Source for the disk to be created, + * either an image or a snapshot. + * @param {string=} options.disks[].diskType - Type of the disk. + * @param {number=} options.disks[].sizeGb - Size of the disk in GB. + * @param {boolean=} options.disks[].boot - True of the disk is a boot disk. + * @param {string=} options.disks[].mode - Attach mode, either `READ_ONLY` or + * `READ_WRITE`. Default value is `READ_WRITE`. + * @param {number=} options.disks[].index - Zero based index assigned from the + * instance to the disk. + * @param {string=} options.disks[].deviceName - Device name assigned from the + * instance to the disk. + * @param {boolean=} options.disks[].autoDelete - If true the disk is deleted + * when the instance is deleted. + * @param {object[]} options.networkInterfaces - Network interfaces for the + * instance. + * @param {string} options.networkInterfaces[].network - Full/partial URL of a + * network interface for this instance. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, instance, operation) { + * // `instance` is an Instance object. + * // `operation` is an Operation object and can be used to check the status + * // of instance creation. + * }; + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var myZone = compute.zone('zone-name'); + * + * myZone.createInstance( + * 'instance-1', + * { + * machineType: 'zones/europe-west1-b/machineTypes/n1-standard-1', + * disks: [{ + * createNew: true, + * diskName: 'test-create-disk', + * source: + * 'projects/debian-cloud/global/images/debian-7-wheezy-v20150325', + * diskSizeGb: 10, + * boot: true, + * autoDelete: true + * }], + * networkInterfaces: [{ + * network: 'global/networks/default' + * }] + * }, callback); + */ +Zone.prototype.createInstance = function(name, options, callback) { + if (!name) { + throw new Error('A name is required to create an instance.'); + } else if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(name)) { + throw new Error('Name must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + if (!options) { + throw new Error('Options are required to create an instance.'); + } + if (!options.machineType) { + throw new Error('A machine type is required to create an instance.'); + } + if (!callback) { + throw new Error('A callback must be defined.'); + } + + var query = {}; + + var body = { + name: name, + machineType: options.machineType, + disks: [], + networkInterfaces: [] + }; + + if (!util.is(options.disks, 'array')) { + options.disks = [options.disks]; + } + + options.disks.forEach(function(disk) { + var requestDisk = {}; + if (util.is(disk.source, 'string')) { + if (disk.createNew) { + requestDisk.initializeParams = { + sourceImage: disk.source + }; + } else { + requestDisk.source = disk.source; + } + } else { + throw new Error('A disk source must be provided.'); + } + if (util.is(disk.diskSizeGb, 'long')) { + requestDisk.initializeParams.diskSizeGb = disk.diskSizeGb; + } + if (util.is(disk.diskType, 'string')) { + requestDisk.initializeParams.diskType = disk.diskType; + } + if (util.is(disk.diskName, 'string')) { + requestDisk.initializeParams.diskName = disk.diskName; + } + if (util.is(disk.type, 'string')) { + requestDisk.type = disk.type; + } + if (util.is(disk.mode, 'string')) { + requestDisk.mode = disk.mode; + } + if (util.is(disk.index, 'integer')) { + requestDisk.index = disk.index; + } + if (util.is(disk.deviceName, 'string')) { + requestDisk.deviceName = disk.deviceName; + } + if (util.is(disk.boot, 'boolean')) { + requestDisk.boot = disk.boot; + } + if (util.is(disk.autoDelete, 'boolean')) { + requestDisk.autoDelete = disk.autoDelete; + } + body.disks.push(requestDisk); + }); + if (body.disks.lenght === 0) { + throw new Error('A disk must be provided to create an instance.'); + } + if (!util.is(options.networkIntefaces, 'array')) { + options.networkIntefaces = [options.networks]; + } + options.networkInterfaces.forEach(function(networkInterface) { + body.networkInterfaces.push({ + network: networkInterface.network + }); + }); + + var self = this; + this.makeReq_('POST', '/instances', query, body, function(err, resp) { + if (err) { + callback(err); + return; + } + var instance = self.instance(name); + instance.metadata = options; + var operation = self.operation(resp.name); + operation.metadata = resp; + callback(null, instance, operation); + }); +}; + +/** + * Get a list of instances in this zone. For a detailed description of method's + * options see [API reference](https://goo.gl/80ya6l). + * + * @param {object} options - Instance search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of instances to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, instances) { + * // `instances` is an array of `Instance` objects. + * }; + * + * var myZone = compute.zone('zone-name'); + * + * myZone.getInstances( + * { + * filter: 'name eq instance-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, instances, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * myZone.getInstances(nextQuery, callback); + * } + * }; + * + * myZone.getInstances({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the instances from your project as a readable object stream. + * //- + * myZone.getInstances() + * .on('error', console.error) + * .on('data', function(instance) { + * // `instance` is an `Instance` object. + * }) + * .on('end', function() { + * // All instances retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * myZone.getInstances() + * .on('data', function(instance) { + * this.end(); + * }); + */ +Zone.prototype.getInstances = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/instances', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var instances = (resp.items || []).map(function(instanceObject) { + var instance = self.instance(instanceObject.name); + instance.metadata = instanceObject; + return instance; + }); + callback(null, instances, nextQuery, resp); + }); +}; + +/** + * Create a disk in this zone. For a detailed description of method's options + * see [API reference](https://goo.gl/suU3qn). + * + * @throws {Error} if a disk name or options are not provided or if both a + * source image and a source snapshot are provided. + * + * @param {string} name - Name of the disk. + * @param {object} options - Options for disk creation. + * @param {string=} options.sourceImage - Source image for the disk. + * @param {string=} options.sourceSnapshot - Source snapshot for the disk. + * @param {number=} options.sizeGb - Size of the disk in GB. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, disk, apiResponse) { + * // `disk` is a Disk object. + * // `operation` is an Operation object and can be used to check the status + * // of disk creation. + * }; + * + * var compute = gcloud.compute({ + * projectId: 'grape-spaceship-123' + * }); + * + * var myZone = compute.zone('zone-name'); + * + * myZone.createDisk( + * 'new-disk', + * { + * sizeGb: 10 + * }, callback); + */ +Zone.prototype.createDisk = function(name, options, callback) { + if (!name) { + throw new Error('A name is required to create a disk.'); + } else if (!/^(?:[a-z](?:[-a-z0-9]{0,61}[a-z0-9])?)$/.test(name)) { + throw new Error('Name must match [a-z]([-a-z0-9]{0,61}[a-z0-9])?'); + } + if (!options) { + throw new Error('Options are required to create a disk.'); + } + if (!options.sourceImage && !options.sizeGb && !options.sourceSnapshot) { + throw new Error('An image, a snapshot or the size is required.'); + } + if (options.sourceImage && options.sourceSnapshot) { + throw new Error('An image and a snapshot can not be both provided.'); + } + if (!callback) { + throw new Error('A callback must be defined.'); + } + + var query = {}; + var body = {}; + + if (options.sourceImage) { + query.sourceImage = options.sourceImage; + } else if (options.sourceSnapshot) { + body.sourceSnapshot = options.sourceSnapshot; + } + + if (util.is(options.sizeGb, 'number')) { + body.sizeGb = options.sizeGb.toString(); + } + + body.name = name; + + var self = this; + this.makeReq_('POST', '/disks', query, body, function(err, resp) { + if (err) { + callback(err); + return; + } + var disk = self.disk(name); + self.metadata = options; + var operation = self.operation(resp.name); + operation.metadata = resp; + callback(null, disk, operation); + }); +}; + +/** + * Get a list of disks in this zone. For a detailed description of method's + * options see [API reference](https://goo.gl/0R67mp). + * + * @param {object} options - Disk search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of disks to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, disks) { + * // `disks` is an array of `Disk` objects. + * }; + * + * var myZone = compute.zone('zone-name'); + * + * myZone.getDisks( + * { + * filter: 'name eq disk-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, disks, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * myZone.getDisks(nextQuery, callback); + * } + * }; + * + * myZone.getDisks({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the disks from your project as a readable object stream. + * //- + * myZone.getDisks() + * .on('error', console.error) + * .on('data', function(disk) { + * // `disk` is a `Disk` object. + * }) + * .on('end', function() { + * // All disks retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * myZone.getDisks() + * .on('data', function(disk) { + * this.end(); + * }); + */ +Zone.prototype.getDisks = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_('GET', '/disks', options, null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var disks = (resp.items || []).map(function(diskObject) { + var disk = self.disk(diskObject.name); + disk.metadata = diskObject; + return disk; + }); + callback(null, disks, nextQuery, resp); + }); +}; + +/** + * Get a list of operations for this zone. For a detailed description of + * method's options see [API reference](https://goo.gl/5n74cP). + * + * @param {object} options - Operation search options. + * @param {boolean=} options.autoPaginate - Have pagination handled + * automatically. Default: true. + * @param {number=} options.maxResults - Maximum number of operations to return. + * @param {string=} options.filter - Search filter. The filter must contain: + * `FIELD_NAME COMPARISON_STRING LITERAL_STRING`. `FIELD_NAME` is the name + * of the field to compare. `COMPARISON` is the comparison operator that can + * be either `eq` or `ne`. `LITERAL_STRING` is the string to filter to. + * For string fields `LITERAL_STRING` can be a regular expression. + * @param {string=} pageToken - Page identifier used in paginated search. + * @param {function} callback - The callback function. + * + * @example + * var callback = function(err, operations) { + * // `operations` is an array of `Operation` objects. + * }; + * + * var myZone = compute.zone('zone-name'); + * + * myZone.getOperations( + * { + * filter: 'name eq operation-[0-9]' + * }, callback); + * + * //- + * // To control how many API requests are made and page through the results + * // manually, set `autoPaginate` to `false`. + * //- + * var callback = function(err, operations, nextQuery, apiResponse) { + * if (nextQuery) { + * // More results exist. + * myZone.getOperations(nextQuery, callback); + * } + * }; + * + * myZone.getOperations({ + * autoPaginate: false + * }, callback); + * + * //- + * // Get the operations from your project as a readable object stream. + * //- + * myZone.getOperations() + * .on('error', console.error) + * .on('data', function(operation) { + * // `operation` is a `Operation` object. + * }) + * .on('end', function() { + * // All operations retrieved. + * }); + * + * //- + * // If you anticipate many results, you can end a stream early to prevent + * // unnecessary processing and API requests. + * //- + * myZone.getOperations() + * .on('data', function(operation) { + * this.end(); + * }); + */ +Zone.prototype.getOperations = function(options, callback) { + if (util.is(options, 'function')) { + callback = options; + options = {}; + } + + options = options || {}; + + var self = this; + this.makeReq_( + 'GET', + '/operations', + options, + null, function(err, resp) { + if (err) { + callback(err); + return; + } + + var nextQuery = null; + + if (resp.nextPageToken) { + nextQuery = extend({}, options, { + pageToken: resp.nextPageToken + }); + } + + var operations = (resp.items || []).map(function(operationObject) { + var operation = self.operation(operationObject.name); + operation.metadata = operationObject; + return operation; + }); + callback(null, operations, nextQuery, resp); + }); +}; + +/** + * Make a new request object from the provided arguments and wrap the callback + * to intercept non-successful responses. + * + * @private + * + * @param {string} method - Action. + * @param {string} path - Request path. + * @param {*} query - Request query object. + * @param {*} body - Request body contents. + * @param {function} callback - The callback function. + */ +Zone.prototype.makeReq_ = function(method, path, query, body, callback) { + path = '/zones/' + this.name + path; + this.compute.makeReq_(method, path, query, body, callback); +}; + +/*! Developer Documentation + * + * These methods can be used with either a callback or as a readable object + * stream. `streamRouter` is used to add this dual behavior. + */ +streamRouter.extend(Zone, ['getInstances', 'getDisks', 'getOperations']); + +module.exports = Zone; \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index dedd6f70f80..95fcab10c2e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -56,6 +56,12 @@ var Search = require('./search'); */ var Storage = require('./storage'); +/** + * @type {module:storage} + * @private + */ +var Compute = require('./compute'); + /** * @type {module:common/util} * @private @@ -143,6 +149,10 @@ function gcloud(config) { storage: function(options) { options = options || {}; return new Storage(util.extendGlobalConfig(config, options)); + }, + compute: function(options) { + options = options || {}; + return new Compute(util.extendGlobalConfig(config, options)); } }; } @@ -259,4 +269,6 @@ gcloud.search = function(config) { */ gcloud.storage = Storage; +gcloud.compute = Compute; + module.exports = gcloud;