From e8f9c16f02a880d45c3d5b53e7450adb163a6025 Mon Sep 17 00:00:00 2001
From: Stephen Sawchuk
Date: Mon, 14 Sep 2015 15:43:56 -0400
Subject: [PATCH] Integrate resource manager support
---
README.md | 52 ++-
docs/json/master/resource/.gitkeep | 0
docs/site/components/docs/docs-values.js | 16 +-
docs/site/components/docs/docs.html | 2 +-
.../components/docs/resource-overview.html | 10 +
lib/common/util.js | 6 +-
lib/index.js | 23 ++
lib/resource/index.js | 301 ++++++++++++++
lib/resource/project.js | 208 ++++++++++
scripts/docs.sh | 3 +
system-test/resource.js | 129 ++++++
test/resource/index.js | 369 ++++++++++++++++++
test/resource/project.js | 318 +++++++++++++++
13 files changed, 1427 insertions(+), 10 deletions(-)
create mode 100644 docs/json/master/resource/.gitkeep
create mode 100644 docs/site/components/docs/resource-overview.html
create mode 100644 lib/resource/index.js
create mode 100644 lib/resource/project.js
create mode 100644 system-test/resource.js
create mode 100644 test/resource/index.js
create mode 100644 test/resource/project.js
diff --git a/README.md b/README.md
index 1e056347fa9a..7e1e5336b5f5 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ This client supports the following Google Cloud Platform services:
* [Google Cloud Pub/Sub](#google-cloud-pubsub)
* [Google Cloud Storage](#google-cloud-storage)
* [Google Compute Engine](#google-compute-engine)
+* [Google Cloud Resource Manager](#google-cloud-resource-manager-beta) (Beta)
* [Google Cloud Search](#google-cloud-search-alpha) (Alpha)
If you need support for other Google APIs, check out the [Google Node.js API Client library][googleapis].
@@ -181,8 +182,8 @@ var gcloud = require('gcloud');
// global basis (see Authorization section above).
var dns = gcloud.dns({
- keyFilename: '/path/to/keyfile.json',
- projectId: 'my-project'
+ projectId: 'my-project',
+ keyFilename: '/path/to/keyfile.json'
});
// Create a managed zone.
@@ -264,8 +265,8 @@ var gcloud = require('gcloud');
// global basis (see Authorization section above).
var gcs = gcloud.storage({
- keyFilename: '/path/to/keyfile.json',
- projectId: 'my-project'
+ projectId: 'my-project',
+ keyFilename: '/path/to/keyfile.json'
});
// Create a new bucket.
@@ -335,6 +336,42 @@ zone.createVM(name, { os: 'ubuntu' }, function(err, vm, operation) {
```
+## Google Cloud Resource Manager (Beta)
+
+> This is a *Beta* release of Google Cloud Resource Manager. This feature is not covered by any SLA or deprecation policy and may be subject to backward-incompatible changes.
+
+- [API Documentation][gcloud-resource-docs]
+- [Official Documentation][cloud-resource-docs]
+
+#### Preview
+
+```js
+var gcloud = require('gcloud');
+
+// Authorizing on a per-API-basis. You don't need to do this if you auth on a
+// global basis (see Authorization section above).
+
+var resource = gcloud.resource({
+ projectId: 'my-project',
+ keyFilename: '/path/to/keyfile.json'
+});
+
+// Get all of the projects you maintain.
+resource.getProjects(function(err, projects) {
+ if (!err) {
+ // `projects` contains all of your projects.
+ }
+});
+
+// Get the metadata from your project. (defaults to `my-project`)
+var project = resource.project();
+
+project.getMetadata(function(err, metadata) {
+ // `metadata` describes your project.
+});
+```
+
+
## Google Cloud Search (Alpha)
> This is an *Alpha* release of Google Cloud Search. This feature is not covered by any SLA or deprecation policy and may be subject to backward-incompatible changes.
@@ -351,8 +388,8 @@ var gcloud = require('gcloud');
// global basis (see Authorization section above).
var search = gcloud.search({
- keyFilename: '/path/to/keyfile.json',
- projectId: 'my-project'
+ projectId: 'my-project',
+ keyFilename: '/path/to/keyfile.json'
});
// Create a document in a new index.
@@ -396,6 +433,7 @@ Apache 2.0 - See [COPYING](COPYING) for more information.
[gcloud-datastore-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/datastore
[gcloud-dns-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/dns
[gcloud-pubsub-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/pubsub
+[gcloud-resource-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/resource
[gcloud-search-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/search
[gcloud-storage-docs]: https://googlecloudplatform.github.io/gcloud-node/#/docs/storage
@@ -421,6 +459,8 @@ Apache 2.0 - See [COPYING](COPYING) for more information.
[cloud-pubsub-docs]: https://cloud.google.com/pubsub/docs
+[cloud-resource-docs]: https://cloud.google.com/resource-manager
+
[cloud-search-docs]: https://cloud.google.com/search/
[cloud-storage-docs]: https://cloud.google.com/storage/docs/overview
diff --git a/docs/json/master/resource/.gitkeep b/docs/json/master/resource/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/docs/site/components/docs/docs-values.js b/docs/site/components/docs/docs-values.js
index 59c2ef1638f6..ace3d321cb56 100644
--- a/docs/site/components/docs/docs-values.js
+++ b/docs/site/components/docs/docs-values.js
@@ -150,6 +150,17 @@ angular.module('gcloud.docs')
]
},
+ resource: {
+ title: 'Resource',
+ _url: '{baseUrl}/resource',
+ pages: [
+ {
+ title: 'Project',
+ url: '/project'
+ }
+ ]
+ },
+
search: {
title: 'Search',
_url: '{baseUrl}/search',
@@ -226,6 +237,9 @@ angular.module('gcloud.docs')
'>=0.18.0': ['dns'],
// introduce compute api.
- '>=0.20.0': ['compute']
+ '>=0.20.0': ['compute'],
+
+ // introduce resource api.
+ '>=0.22.0': ['resource']
}
});
diff --git a/docs/site/components/docs/docs.html b/docs/site/components/docs/docs.html
index 1d94fa00dbdf..214ea44b7de8 100644
--- a/docs/site/components/docs/docs.html
+++ b/docs/site/components/docs/docs.html
@@ -44,7 +44,7 @@
-
diff --git a/docs/site/components/docs/resource-overview.html b/docs/site/components/docs/resource-overview.html
new file mode 100644
index 000000000000..2992b01f435e
--- /dev/null
+++ b/docs/site/components/docs/resource-overview.html
@@ -0,0 +1,10 @@
+Resource Overview
+
+ This is a Beta release of Cloud Resource Manager. This feature is not covered by any SLA or deprecation policy and may be subject to backward-incompatible changes.
+
+
+ The object returned from gcloud.resource gives you complete access to your projects.
+
+
+ To learn more about Resource Manager, see What is the Google Cloud Resource Manager?
+
diff --git a/lib/common/util.js b/lib/common/util.js
index a1df22a62328..585a5c1d3638 100644
--- a/lib/common/util.js
+++ b/lib/common/util.js
@@ -473,11 +473,13 @@ util.decorateRequest = decorateRequest;
* @param {?object} localConfig - api level configurations
* @return {object} config - merged and validated configurations
*/
-function normalizeArguments(globalContext, localConfig) {
+function normalizeArguments(globalContext, localConfig, options) {
var globalConfig = globalContext && globalContext.config_ || {};
var config = util.extendGlobalConfig(globalConfig, localConfig);
- if (!config.projectId) {
+ options = options || {};
+
+ if (!config.projectId && options.projectIdRequired !== false) {
throw util.missingProjectIdError;
}
diff --git a/lib/index.js b/lib/index.js
index 0481d0a9d1ca..2b6df5963405 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -126,6 +126,29 @@ var apis = {
*/
pubsub: require('./pubsub'),
+ /**
+ * [The Cloud Resource Manager](https://cloud.google.com/resource-manager/)
+ * provides methods that you can use to programmatically manage your projects
+ * in the Google Cloud Platform. With this API, you can do the following:
+ *
+ * - Get a list of all projects associated with an account.
+ * - Create new projects.
+ * - Update existing projects.
+ * - Delete projects.
+ * - Recover projects.
+ *
+ *
+ * **This is a *Beta* release of Cloud Resource Manager.** This feature is
+ * not covered by any SLA or deprecation policy and may be subject to
+ * backward-incompatible changes.
+ *
+ *
+ * @type {module:resource}
+ *
+ * @return {module:resource}
+ */
+ resource: require('./resource'),
+
/**
* [Google Cloud Search](https://cloud.google.com/search/) allows you to
* quickly perform full-text and geospatial searches against your data without
diff --git a/lib/resource/index.js b/lib/resource/index.js
new file mode 100644
index 000000000000..0f6901a019ca
--- /dev/null
+++ b/lib/resource/index.js
@@ -0,0 +1,301 @@
+/*!
+ * Copyright 2015 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 resource
+ */
+
+'use strict';
+
+var extend = require('extend');
+var is = require('is');
+
+/**
+ * @type {module:resource/project}
+ * @private
+ */
+var Project = require('./project.js');
+
+/**
+ * @type {module:common/streamrouter}
+ * @private
+ */
+var streamRouter = require('../common/stream-router.js');
+
+/**
+ * @type {module:common/util}
+ * @private
+ */
+var util = require('../common/util.js');
+
+/**
+ * @const {string}
+ * @private
+ */
+var BASE_URL = 'https://cloudresourcemanager.googleapis.com/v1beta1/projects';
+
+/**
+ * Required scopes for Google Cloud Resource Manager API.
+ * @const {array}
+ * @private
+ */
+var SCOPES = [
+ 'https://www.googleapis.com/auth/cloud-platform'
+];
+
+/**
+ * [The Cloud Resource Manager](https://cloud.google.com/resource-manager/)
+ * provides methods that you can use to programmatically manage your projects
+ * in the Google Cloud Platform. With this API, you can do the following:
+ *
+ * - Get a list of all projects associated with an account.
+ * - Create new projects.
+ * - Update existing projects.
+ * - Delete projects.
+ * - Recover projects.
+ *
+ * @alias module:resource
+ * @constructor
+ *
+ * @param {object} options - [Configuration object](#/docs/?method=gcloud).
+ *
+ * @example
+ * var gcloud = require('gcloud')({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'grape-spaceship-123'
+ * });
+ *
+ * var resource = gcloud.resource();
+ */
+function Resource(options) {
+ if (!(this instanceof Resource)) {
+ options = util.normalizeArguments(this, options, {
+ projectIdRequired: false
+ });
+ return new Resource(options);
+ }
+
+ this.defaultProjectId_ = options.projectId;
+
+ this.makeAuthorizedRequest_ = util.makeAuthorizedRequestFactory({
+ credentials: options.credentials,
+ keyFile: options.keyFilename,
+ scopes: SCOPES,
+ email: options.email
+ });
+}
+
+/**
+ * Create a project.
+ *
+ * @resource [Projects Overview]{@link https://cloud.google.com/compute/docs/networking#networks}
+ * @resource [projects: create API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/create}
+ *
+ * @private
+ *
+ * @param {string} name - Name of the project.
+ * @param {object=} options - See a
+ * [Project resource](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project).
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {module:resource/project} callback.project - The created Project
+ * object.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * resource.createProject('new project name', function(err, project) {
+ * if (!err) {
+ * // `project` is a new Project instance.
+ * }
+ * });
+ */
+Resource.prototype.createProject = function(id, options, callback) {
+ var self = this;
+
+ if (is.fn(options)) {
+ callback = options;
+ options = {};
+ }
+
+ var body = extend({}, options, {
+ projectId: id
+ });
+
+ this.makeReq_('POST', '/', null, body, function(err, resp) {
+ if (err) {
+ callback(err, null, resp);
+ return;
+ }
+
+ var project = self.project(resp.projectId);
+ project.metadata = resp;
+
+ callback(null, project, resp);
+ });
+};
+
+/**
+ * Get a list of projects.
+ *
+ * @resource [Projects Overview]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects}
+ * @resource [projects: list API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/list}
+ *
+ * @param {object=} options - Operation search options.
+ * @param {boolean} options.autoPaginate - Have pagination handled
+ * automatically. Default: true.
+ * @param {string} options.filter - An expression for filtering the results.
+ * @param {number} options.pageSize - Maximum number of projects to return.
+ * @param {string} options.pageToken - A previously-returned page token
+ * representing part of the larger set of results to view.
+ * @param {function} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {module:resource/project} callback.operations - Project objects from
+ * your account.
+ * @param {?object} callback.nextQuery - If present, query with this object to
+ * check for more results.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * resource.getProjects(function(err, projects) {
+ * // `projects` is an array of `Project` objects.
+ * });
+ *
+ * //-
+ * // To control how many API requests are made and page through the results
+ * // manually, set `autoPaginate` to `false`.
+ * //-
+ * function callback(err, projects, nextQuery, apiResponse) {
+ * if (nextQuery) {
+ * // More results exist.
+ * resource.getProjects(nextQuery, callback);
+ * }
+ * }
+ *
+ * resource.getProjects({
+ * autoPaginate: false
+ * }, callback);
+ *
+ * //-
+ * // Get the projects from your account as a readable object stream.
+ * //-
+ * resource.getProjects()
+ * .on('error', console.error)
+ * .on('data', function(project) {
+ * // `project` is a `Project` object.
+ * })
+ * .on('end', function() {
+ * // All projects retrieved.
+ * });
+ *
+ * //-
+ * // If you anticipate many results, you can end a stream early to prevent
+ * // unnecessary processing and API requests.
+ * //-
+ * resource.getProjects()
+ * .on('data', function(project) {
+ * this.end();
+ * });
+ */
+Resource.prototype.getProjects = function(options, callback) {
+ var self = this;
+
+ if (is.fn(options)) {
+ callback = options;
+ options = {};
+ }
+
+ options = options || {};
+
+ this.makeReq_('GET', '/', options, null, function(err, resp) {
+ if (err) {
+ callback(err, null, null, resp);
+ return;
+ }
+
+ var nextQuery = null;
+
+ if (resp.nextPageToken) {
+ nextQuery = extend({}, options, {
+ pageToken: resp.nextPageToken
+ });
+ }
+
+ var projects = (resp.projects || []).map(function(project) {
+ var projectInstance = self.project(project.name);
+ projectInstance.metadata = project;
+ return projectInstance;
+ });
+
+ callback(null, projects, nextQuery, resp);
+ });
+};
+
+/**
+ * Create a Project object to reference an existing project. See
+ * {module:resoucemanager/createProject} to create a project.
+ *
+ * @throws {Error} If an ID is not provided.
+ *
+ * @param {string} id - The ID of the project (eg: `grape-spaceship-123`).
+ * @return {module:resource/project}
+ *
+ * @example
+ * var project = resource.project('grape-spaceship-123');
+ */
+Resource.prototype.project = function(id) {
+ id = id || this.defaultProjectId_;
+
+ if (!id) {
+ throw new Error('A project ID is required.');
+ }
+
+ return new Project(this, id);
+};
+
+/**
+ * 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.
+ */
+Resource.prototype.makeReq_ = function(method, path, query, body, callback) {
+ var reqOpts = {
+ method: method,
+ qs: query,
+ uri: BASE_URL + 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(Resource, ['getProjects']);
+
+module.exports = Resource;
diff --git a/lib/resource/project.js b/lib/resource/project.js
new file mode 100644
index 000000000000..d52dfdc65477
--- /dev/null
+++ b/lib/resource/project.js
@@ -0,0 +1,208 @@
+/*!
+ * Copyright 2015 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 resource/project
+ */
+
+'use strict';
+
+/**
+ * @type {module:common/util}
+ * @private
+ */
+var util = require('../common/util.js');
+
+/*! Developer Documentation
+ *
+ * @param {module:resource} resource - Resource object this project belongs to.
+ * @param {string} id - The project's ID.
+ */
+/**
+ * A Project object allows you to interact with a Google Cloud Platform project.
+ *
+ * @resource [Projects Overview]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects}
+ * @resource [Project Resource]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project}
+ *
+ * @constructor
+ * @alias module:resource/project
+ *
+ * @example
+ * var gcloud = require('gcloud')({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'grape-spaceship-123'
+ * });
+ *
+ * var resource = gcloud.resource();
+ * var project = resource.project('grape-spaceship-123');
+ *
+ * //-
+ * // If no ID is passed to `resource.project()`, the returned object will refer
+ * // to the project originally specified during instantiation of `gcloud`.
+ * //
+ * // Thus, in this case, these are equivalent:
+ * //-
+ * var project = resource.project('grape-spaceship-123');
+ * var project = resource.project();
+ */
+function Project(resource, id) {
+ this.resource = resource;
+ this.id = id;
+ this.metadata = {};
+}
+
+/**
+ * Delete the project.
+ *
+ * @resource [projects: delete API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/delete}
+ *
+ * @private
+ *
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * project.delete(function(err, apiResponse) {
+ * if (!err) {
+ * // The project was deleted!
+ * }
+ * });
+ */
+Project.prototype.delete = function(callback) {
+ callback = callback || util.noop;
+
+ this.makeReq_('DELETE', '', null, null, function(err, resp) {
+ callback(err, resp);
+ });
+};
+
+/**
+ * Get the metadata for the project.
+ *
+ * @resource [projects: get API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/get}
+ *
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {?object} callback.metadata - Metadata of the project from the API.
+ * @param {object} callback.apiResponse - Raw API response.
+ *
+ * @example
+ * project.getMetadata(function(err, metadata, apiResponse) {});
+ */
+Project.prototype.getMetadata = function(callback) {
+ var self = this;
+
+ callback = callback || util.noop;
+
+ this.makeReq_('GET', '', null, null, function(err, resp) {
+ if (err) {
+ callback(err, null, resp);
+ return;
+ }
+
+ self.metadata = resp;
+
+ callback(null, self.metadata, resp);
+ });
+};
+
+/**
+ * Restore a project.
+ *
+ * @resource [projects: undelete API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/undelete}
+ *
+ * @private
+ *
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {object} callback.apiResponse - Raw API response.
+ *
+ * @example
+ * project.restore(function(err, apiResponse) {
+ * if (!err) {
+ * // Project restored.
+ * }
+ * });
+ */
+Project.prototype.restore = function(callback) {
+ callback = callback || util.noop;
+
+ this.makeReq_('POST', ':undelete', null, null, function(err, resp) {
+ callback(err, resp);
+ });
+};
+
+/**
+ * Set the project's metadata.
+ *
+ * @resource [projects: update API Documentation]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects/update}
+ * @resource [Project Resource]{@link https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project}
+ *
+ * @private
+ *
+ * @param {object} metadata - See a
+ * [Project resource](https://cloud.google.com/resource-manager/reference/rest/v1beta1/projects#Project).
+ * @param {function=} callback - The callback function.
+ * @param {?error} callback.err - An error returned while making this request.
+ * @param {object} callback.apiResponse - The full API response.
+ *
+ * @example
+ * var metadata = {
+ * name: 'New name'
+ * };
+ *
+ * project.setMetadata(metadata, function(err, apiResponse) {
+ * if (!err) {
+ * // The project has been successfully updated.
+ * }
+ * });
+ */
+Project.prototype.setMetadata = function(metadata, callback) {
+ var self = this;
+
+ callback = callback || util.noop;
+
+ this.makeReq_('PUT', '', null, metadata, function(err, resp) {
+ if (err) {
+ callback(err, resp);
+ return;
+ }
+
+ self.metadata = resp;
+
+ callback(null, 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.
+ */
+Project.prototype.makeReq_ = function(method, path, query, body, callback) {
+ path = '/' + this.id + path;
+ this.resource.makeReq_(method, path, query, body, callback);
+};
+
+module.exports = Project;
diff --git a/scripts/docs.sh b/scripts/docs.sh
index a4f6c6f2f70d..b88506605d37 100755
--- a/scripts/docs.sh
+++ b/scripts/docs.sh
@@ -48,6 +48,9 @@
./node_modules/.bin/dox < lib/pubsub/topic.js > docs/json/master/pubsub/topic.json &
./node_modules/.bin/dox < lib/pubsub/iam.js > docs/json/master/pubsub/iam.json &
+./node_modules/.bin/dox < lib/resource/index.js > docs/json/master/resource/index.json &
+./node_modules/.bin/dox < lib/resource/project.js > docs/json/master/resource/project.json &
+
./node_modules/.bin/dox < lib/search/index.js > docs/json/master/search/index.json &
./node_modules/.bin/dox < lib/search/index-class.js > docs/json/master/search/index-class.json &
./node_modules/.bin/dox < lib/search/document.js > docs/json/master/search/document.json &
diff --git a/system-test/resource.js b/system-test/resource.js
new file mode 100644
index 000000000000..78a4ea1ec557
--- /dev/null
+++ b/system-test/resource.js
@@ -0,0 +1,129 @@
+/*!
+ * Copyright 2015 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.
+ */
+
+'use strict';
+
+var assert = require('assert');
+
+var env = require('./env.js');
+var Resource = require('../lib/resource/index.js');
+
+describe('Resource', function() {
+ // -------------
+ // >> Attention!
+ // -------------
+ // As of 9/14/15, creating projects is not supported. Once we have support,
+ // the following description outlines how we should run our tests.
+ // -------------
+ //
+ // Before the tests run, we create a project. That acts as the test for being
+ // able to create a project. Similarly, the after hook deletes it, testing if
+ // a project can be deleted.
+ //
+ // ----------
+ // >> Notice!
+ // ----------
+ // All tests should only manipulate a short-lived project. NOT the project the
+ // user has been running our test suite with. That would just be rude.
+ // ----------
+
+ // var PROJECT_ID = 'gcloud-tests-' + Date.now();
+ var PROJECT_NAME = 'gcloud-tests-project-name';
+
+ var resource = new Resource(env);
+ var project;
+
+ before(function(done) {
+ // Uncomment after we support creating a project.
+ // resource.createProject(PROJECT_ID, function(err, project_) {
+ // if (err) {
+ // done(err);
+ // return;
+ // }
+ //
+ // project = project_;
+ // });
+
+ // ** SEE "Notice!" SECTION ABOVE **
+ // Remove once we support creating a project.
+ project = resource.project();
+ done();
+ });
+
+ // Uncomment after we support creating a project.
+ // after(function(done) {
+ // project.delete(done);
+ // });
+
+ describe('resource', function() {
+ it('should get a list of projects', function(done) {
+ resource.getProjects(function(err, projects) {
+ assert.ifError(err);
+ assert(projects.length > 0);
+ done();
+ });
+ });
+
+ it('should get a list of projects in stream mode', function(done) {
+ var resultsMatched = 0;
+
+ resource.getProjects()
+ .on('error', done)
+ .on('data', function() {
+ resultsMatched++;
+ })
+ .on('end', function() {
+ assert(resultsMatched > 0);
+ done();
+ });
+ });
+ });
+
+ describe('project', function() {
+ it('should get metadata', function(done) {
+ project.getMetadata(function(err, metadata) {
+ assert.ifError(err);
+ assert.strictEqual(metadata.projectId, project.id);
+ done();
+ });
+ });
+
+ it.skip('should set metadata', function(done) {
+ project.getMetadata(function(err, metadata) {
+ assert.ifError(err);
+
+ var originalProjectName = metadata.name;
+
+ project.setMetadata({
+ name: PROJECT_NAME
+ }, function(err) {
+ assert.ifError(err);
+
+ project.setMetadata({
+ name: originalProjectName
+ }, done);
+ });
+ });
+ });
+
+ it.skip('should delete and restore a project', function(done) {
+ project.delete(function(err) {
+ assert.ifError(err);
+ project.restore(done);
+ });
+ });
+ });
+});
diff --git a/test/resource/index.js b/test/resource/index.js
new file mode 100644
index 000000000000..6c4bc39c1673
--- /dev/null
+++ b/test/resource/index.js
@@ -0,0 +1,369 @@
+/*!
+ * Copyright 2015 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.
+ */
+
+'use strict';
+
+var arrify = require('arrify');
+var assert = require('assert');
+var extend = require('extend');
+var mockery = require('mockery');
+var util = require('../../lib/common/util.js');
+
+function FakeProject() {
+ this.calledWith_ = [].slice.call(arguments);
+}
+
+var extended = false;
+var fakeStreamRouter = {
+ extend: function(Class, methods) {
+ if (Class.name !== 'Resource') {
+ return;
+ }
+
+ methods = arrify(methods);
+ assert.equal(Class.name, 'Resource');
+ assert.deepEqual(methods, ['getProjects']);
+ extended = true;
+ }
+};
+
+var makeAuthorizedRequestFactoryOverride;
+var fakeUtil = extend({}, util, {
+ makeAuthorizedRequestFactory: function() {
+ if (makeAuthorizedRequestFactoryOverride) {
+ return makeAuthorizedRequestFactoryOverride.apply(null, arguments);
+ } else {
+ return util.makeAuthorizedRequestFactory.apply(null, arguments);
+ }
+ }
+});
+
+describe('Resource', function() {
+ var PROJECT_ID = 'test-project-id';
+
+ var Resource;
+ var resource;
+
+ before(function() {
+ mockery.registerMock('../common/stream-router.js', fakeStreamRouter);
+ mockery.registerMock('../common/util.js', fakeUtil);
+ mockery.registerMock('./project.js', FakeProject);
+ mockery.enable({
+ useCleanCache: true,
+ warnOnUnregistered: false
+ });
+
+ Resource = require('../../lib/resource/index.js');
+ });
+
+ after(function() {
+ mockery.deregisterAll();
+ mockery.disable();
+ });
+
+ beforeEach(function() {
+ makeAuthorizedRequestFactoryOverride = null;
+
+ resource = new Resource({
+ projectId: PROJECT_ID
+ });
+ });
+
+ describe('instantiation', function() {
+ it('should extend the correct methods', function() {
+ assert(extended); // See `fakeStreamRouter.extend`
+ });
+
+ it('should normalize the arguments', function() {
+ var normalizeArguments = fakeUtil.normalizeArguments;
+ var normalizeArgumentsCalled = false;
+ var fakeOptions = { projectId: PROJECT_ID };
+ var fakeContext = {};
+
+ fakeUtil.normalizeArguments = function(context, options) {
+ normalizeArgumentsCalled = true;
+ assert.strictEqual(context, fakeContext);
+ assert.strictEqual(options, fakeOptions);
+ return options;
+ };
+
+ Resource.call(fakeContext, fakeOptions);
+ assert(normalizeArgumentsCalled);
+
+ fakeUtil.normalizeArguments = normalizeArguments;
+ });
+
+ it('should create an authorized request function', function(done) {
+ var options = {
+ projectId: 'projectId',
+ credentials: 'credentials',
+ email: 'email',
+ keyFilename: 'keyFile'
+ };
+
+ makeAuthorizedRequestFactoryOverride = function(options_) {
+ assert.deepEqual(options_, {
+ credentials: options.credentials,
+ email: options.email,
+ keyFile: options.keyFilename,
+ scopes: [
+ 'https://www.googleapis.com/auth/cloud-platform'
+ ]
+ });
+ return done;
+ };
+
+ var resource = new Resource(options);
+ resource.makeAuthorizedRequest_();
+ });
+
+ it('should localize the projectId', function() {
+ assert.equal(resource.defaultProjectId_, PROJECT_ID);
+ });
+ });
+
+ describe('createProject', function() {
+ var NEW_PROJECT_ID = 'new-project-id';
+ var OPTIONS = { a: 'b', c: 'd' };
+ var EXPECTED_BODY = extend({}, OPTIONS, { projectId: NEW_PROJECT_ID });
+
+ it('should not require any options', function(done) {
+ var expectedBody = { projectId: NEW_PROJECT_ID };
+
+ resource.makeReq_ = function(method, path, query, body) {
+ assert.deepEqual(body, expectedBody);
+ done();
+ };
+
+ resource.createProject(NEW_PROJECT_ID, assert.ifError);
+ });
+
+ it('should make the correct API request', function(done) {
+ resource.makeReq_ = function(method, path, query, body) {
+ assert.strictEqual(method, 'POST');
+ assert.strictEqual(path, '/');
+ assert.strictEqual(query, null);
+ assert.deepEqual(body, EXPECTED_BODY);
+
+ done();
+ };
+
+ resource.createProject(NEW_PROJECT_ID, OPTIONS, assert.ifError);
+ });
+
+ describe('error', function() {
+ var error = new Error('Error.');
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ resource.makeReq_ = function(method, path, query, body, callback) {
+ callback(error, apiResponse);
+ };
+ });
+
+ it('should execute callback with error & API response', function(done) {
+ resource.createProject(NEW_PROJECT_ID, OPTIONS, function(err, p, res) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(p, null);
+ assert.strictEqual(res, apiResponse);
+ done();
+ });
+ });
+ });
+
+ describe('success', function() {
+ var apiResponse = { projectId: NEW_PROJECT_ID };
+
+ beforeEach(function() {
+ resource.makeReq_ = function(method, path, query, body, callback) {
+ callback(null, apiResponse);
+ };
+ });
+
+ it('should exec callback with Project & API response', function(done) {
+ var project = {};
+
+ resource.project = function(id) {
+ assert.strictEqual(id, NEW_PROJECT_ID);
+ return project;
+ };
+
+ resource.createProject(NEW_PROJECT_ID, OPTIONS, function(err, p, res) {
+ assert.ifError(err);
+
+ assert.strictEqual(p, project);
+
+ assert.strictEqual(res, apiResponse);
+ done();
+ });
+ });
+ });
+ });
+
+ describe('getProjects', function() {
+ it('should accept only a callback', function(done) {
+ resource.makeReq_ = function(method, path, query) {
+ assert.deepEqual(query, {});
+ done();
+ };
+
+ resource.getProjects(assert.ifError);
+ });
+
+ it('should make the correct API request', function(done) {
+ var query = { a: 'b', c: 'd' };
+
+ resource.makeReq_ = function(method, path, query_, body) {
+ assert.strictEqual(method, 'GET');
+ assert.strictEqual(path, '/');
+ assert.strictEqual(query_, query);
+ assert.strictEqual(body, null);
+
+ done();
+ };
+
+ resource.getProjects(query, assert.ifError);
+ });
+
+ describe('error', function() {
+ var error = new Error('Error.');
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ resource.makeReq_ = function(method, path, query, body, callback) {
+ callback(error, apiResponse);
+ };
+ });
+
+ it('should execute callback with error & API response', function(done) {
+ resource.getProjects({}, function(err, projects, nextQuery, apiResp) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(projects, null);
+ assert.strictEqual(nextQuery, null);
+ assert.strictEqual(apiResp, apiResponse);
+ done();
+ });
+ });
+ });
+
+ describe('success', function() {
+ var apiResponse = {
+ projects: [
+ { projectId: PROJECT_ID }
+ ]
+ };
+
+ beforeEach(function() {
+ resource.makeReq_ = function(method, path, query, body, callback) {
+ callback(null, apiResponse);
+ };
+ });
+
+ it('should build a nextQuery if necessary', function(done) {
+ var nextPageToken = 'next-page-token';
+ var apiResponseWithNextPageToken = extend({}, apiResponse, {
+ nextPageToken: nextPageToken
+ });
+ var expectedNextQuery = {
+ pageToken: nextPageToken
+ };
+
+ resource.makeReq_ = function(method, path, query, body, callback) {
+ callback(null, apiResponseWithNextPageToken);
+ };
+
+ resource.getProjects({}, function(err, projects, nextQuery) {
+ assert.ifError(err);
+
+ assert.deepEqual(nextQuery, expectedNextQuery);
+
+ done();
+ });
+ });
+
+ it('should execute callback with Projects & API resp', function(done) {
+ var project = {};
+
+ resource.project = function(name) {
+ assert.strictEqual(name, apiResponse.projects[0].name);
+ return project;
+ };
+
+ resource.getProjects({}, function(err, projects, nextQuery, apiResp) {
+ assert.ifError(err);
+
+ assert.strictEqual(projects[0], project);
+ assert.strictEqual(projects[0].metadata, apiResponse.projects[0]);
+
+ assert.strictEqual(apiResp, apiResponse);
+
+ done();
+ });
+ });
+ });
+ });
+
+ describe('project', function() {
+ it('should return a Project object', function() {
+ var project = resource.project(PROJECT_ID);
+ assert(project instanceof FakeProject);
+ assert.strictEqual(project.calledWith_[0], resource);
+ assert.strictEqual(project.calledWith_[1], PROJECT_ID);
+ });
+
+ it('should use the project ID from the resource', function() {
+ var project = resource.project();
+ assert(project instanceof FakeProject);
+ assert.strictEqual(project.calledWith_[1], PROJECT_ID);
+ });
+
+ it('should throw if no project ID was given or found', function() {
+ var resourceWithoutProjectId = new Resource({});
+
+ assert.throws(function() {
+ resourceWithoutProjectId.project();
+ }, /A project ID is required/);
+ });
+ });
+
+ describe('makeReq_', function() {
+ it('should make the correct request', function(done) {
+ var base = 'https://cloudresourcemanager.googleapis.com/v1beta1/projects';
+
+ var method = 'POST';
+ var path = '/test';
+ var query = {
+ a: 'b',
+ c: 'd'
+ };
+ var body = {
+ a: 'b',
+ c: 'd'
+ };
+
+ resource.makeAuthorizedRequest_ = function(reqOpts, callback) {
+ assert.strictEqual(reqOpts.method, method);
+
+ assert.strictEqual(reqOpts.uri, base + path);
+ assert.strictEqual(reqOpts.qs, query);
+ assert.strictEqual(reqOpts.json, body);
+ callback();
+ };
+
+ resource.makeReq_(method, path, query, body, done);
+ });
+ });
+});
diff --git a/test/resource/project.js b/test/resource/project.js
new file mode 100644
index 000000000000..10a65c285482
--- /dev/null
+++ b/test/resource/project.js
@@ -0,0 +1,318 @@
+/*!
+ * Copyright 2015 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.
+ */
+
+'use strict';
+
+var assert = require('assert');
+
+var Project = require('../../lib/resource/project.js');
+
+describe('Project', function() {
+ var RESOURCE = {};
+ var ID = 'project-id';
+
+ var project;
+
+ beforeEach(function() {
+ project = new Project(RESOURCE, ID);
+ });
+
+ describe('instantiation', function() {
+ it('should localize the resource', function() {
+ assert.strictEqual(project.resource, RESOURCE);
+ });
+
+ it('should localize the ID', function() {
+ assert.strictEqual(project.id, ID);
+ });
+
+ it('should default metadata to an empty object', function() {
+ assert.deepEqual(project.metadata, {});
+ });
+ });
+
+ describe('delete', function() {
+ it('should make the correct API request', function(done) {
+ project.makeReq_ = function(method, path, query, body) {
+ assert.strictEqual(method, 'DELETE');
+ assert.strictEqual(path, '');
+ assert.strictEqual(query, null);
+ assert.strictEqual(body, null);
+ done();
+ };
+
+ project.delete(assert.ifError);
+ });
+
+ describe('error', function() {
+ var error = new Error('Error.');
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(error, apiResponse);
+ };
+ });
+
+ it('should return an error if the request fails', function(done) {
+ project.delete(function(err, apiResponse_) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.delete();
+ });
+ });
+ });
+
+ describe('success', function() {
+ var apiResponse = {
+ projectId: ID
+ };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(null, apiResponse);
+ };
+ });
+
+ it('should execute callback with error and API response', function(done) {
+ project.delete(function(err, apiResponse_) {
+ assert.ifError(err);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.delete();
+ });
+ });
+ });
+ });
+
+ describe('getMetadata', function() {
+ it('should make the correct API request', function(done) {
+ project.makeReq_ = function(method, path, query, body) {
+ assert.strictEqual(method, 'GET');
+ assert.strictEqual(path, '');
+ assert.strictEqual(query, null);
+ assert.strictEqual(body, null);
+
+ done();
+ };
+
+ project.getMetadata(assert.ifError);
+ });
+
+ describe('error', function() {
+ var error = new Error('Error.');
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(error, apiResponse);
+ };
+ });
+
+ it('should execute callback with error and API response', function(done) {
+ project.getMetadata(function(err, metadata, apiResponse_) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(metadata, null);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.getMetadata();
+ });
+ });
+ });
+
+ describe('success', function() {
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(null, apiResponse);
+ };
+ });
+
+ it('should update the metadata to the API response', function(done) {
+ project.getMetadata(function(err) {
+ assert.ifError(err);
+ assert.strictEqual(project.metadata, apiResponse);
+ done();
+ });
+ });
+
+ it('should exec callback with metadata and API response', function(done) {
+ project.getMetadata(function(err, metadata, apiResponse_) {
+ assert.ifError(err);
+ assert.strictEqual(metadata, apiResponse);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.getMetadata();
+ });
+ });
+ });
+ });
+
+ describe('restore', function() {
+ var error = new Error('Error.');
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(error, apiResponse);
+ };
+ });
+
+ it('should make the correct API request', function(done) {
+ project.makeReq_ = function(method, path, query, body) {
+ assert.strictEqual(method, 'POST');
+ assert.strictEqual(path, ':undelete');
+ assert.strictEqual(query, null);
+ assert.strictEqual(body, null);
+
+ done();
+ };
+
+ project.restore(assert.ifError);
+ });
+
+ it('should execute the callback with error & API response', function(done) {
+ project.restore(function(err, apiResponse_) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.restore();
+ });
+ });
+ });
+
+ describe('setMetadata', function() {
+ var METADATA = { a: 'b', c: 'd' };
+
+ it('should make the correct API request', function(done) {
+ project.makeReq_ = function(method, path, query, body) {
+ assert.strictEqual(method, 'PUT');
+ assert.strictEqual(path, '');
+ assert.strictEqual(query, null);
+ assert.strictEqual(body, METADATA);
+
+ done();
+ };
+
+ project.setMetadata(METADATA, assert.ifError);
+ });
+
+ describe('error', function() {
+ var error = new Error('Error.');
+ var apiResponse = { a: 'b', c: 'd' };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(error, apiResponse);
+ };
+ });
+
+ it('should return an error if the request fails', function(done) {
+ project.setMetadata(METADATA, function(err, apiResponse_) {
+ assert.strictEqual(err, error);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.setMetadata(METADATA);
+ });
+ });
+ });
+
+ describe('success', function() {
+ var apiResponse = {
+ projectId: ID
+ };
+
+ beforeEach(function() {
+ project.makeReq_ = function(method, path, query, body, callback) {
+ callback(null, apiResponse);
+ };
+ });
+
+ it('should execute callback with API response', function(done) {
+ project.setMetadata(METADATA, function(err, apiResponse_) {
+ assert.ifError(err);
+ assert.strictEqual(apiResponse_, apiResponse);
+ done();
+ });
+ });
+
+ it('should not require a callback', function() {
+ assert.doesNotThrow(function() {
+ project.setMetadata(METADATA);
+ });
+ });
+ });
+ });
+
+ describe('makeReq_', function() {
+ it('should make the correct request to Resource', function(done) {
+ var expectedPathPrefix = '/' + project.id;
+
+ var method = 'POST';
+ var path = '/test';
+ var query = {
+ a: 'b',
+ c: 'd'
+ };
+ var body = {
+ a: 'b',
+ c: 'd'
+ };
+
+ project.resource.makeReq_ = function(method_, path_, query_, body_, cb) {
+ assert.strictEqual(method_, method);
+ assert.strictEqual(path_, expectedPathPrefix + path);
+ assert.strictEqual(query_, query);
+ assert.strictEqual(body_, body);
+ cb();
+ };
+
+ project.makeReq_(method, path, query, body, done);
+ });
+ });
+});