+ If you are running your app on Google App Engine or Google Compute Engine, you won't need to worry about supplying connection configuration options to gcloud
— we figure that out for you.
+
+
+ However, if you're running your app elsewhere, you will need to provide this information.
+
+
+// App Engine and Compute Engine
+var gcloud = require('gcloud');
-
-
- The gcloud.datastore
object gives you some convenience methods, as well as exposes a Dataset
function. This will allow you to create a Dataset
, which is the object from which you will interact with the Google Cloud Datastore.
-
-
+// Elsewhere
+var gcloud = require('gcloud')({
+ keyFilename: '/path/to/keyfile.json'
+});
+
+ In any environment, you are free to provide these and other default properties, which eventually will be passed to the gcloud
sub-modules (Datastore, Storage, etc.).
+
+
+
+
+ Overview
+
+ The gcloud.datastore
object gives you some convenience methods, as well as exposes a dataset
function. This will allow you to create a dataset
, which is the object from which you will interact with the Google Cloud Datastore.
+
+
var datastore = gcloud.datastore;
-var dataset = new datastore.Dataset();
-
- See the Dataset documentation for examples of how to query the datastore, save entities, run a transaction, and others.
-
-
-
-
-
- The gcloud.storage
object contains a Bucket
object, which is how you will interact with your Google Cloud Storage bucket.
-
-
-var storage = gcloud.storage;
-var bucket = new storage.Bucket({
- bucketName: 'MyBucket'
+var dataset = datastore.dataset({
+ projectId: 'myProject',
+ keyFilename: '/path/to/keyfile.json'
});
-
- See examples below for more on how to upload a file, read from your bucket's files, create signed URLs, and more.
-
-
+
+ See the Dataset documentation for examples of how to query the datastore, save entities, run a transaction, and others.
+
+
+
+ Overview
+
+ The gcloud.storage
object contains a bucket
object, which is how you will interact with your Google Cloud Storage bucket. See the guide on Google Cloud Storage to create a bucket.
+
+
+ See examples below for more on how to access your bucket to upload a file, read its files, create signed URLs, and more.
+
b.name;
+ return a.constructor ? -1: a.name > b.name;
});
};
}
diff --git a/docs/css/main.css b/docs/css/main.css
index 7870e6a3968a..12a46ff478e0 100755
--- a/docs/css/main.css
+++ b/docs/css/main.css
@@ -625,6 +625,21 @@ h2, h3 {
display: block;
}
+.sub-heading {
+ color: #5d6061;
+ margin: 0;
+}
+
+.toggler {
+ float: left;
+ min-width: 15px;
+ margin: auto;
+}
+
+.toggle {
+ cursor: pointer;
+}
+
/*
Page Title
*/
diff --git a/lib/common/util.js b/lib/common/util.js
index 5b1b8f53e0d7..8158afce99df 100644
--- a/lib/common/util.js
+++ b/lib/common/util.js
@@ -21,36 +21,50 @@
* @module common/util
*/
+var extend = require('extend');
var util = require('util');
/**
- * Extend a base object with properties from another.
+ * Extend a global configuration object with user options provided at the time
+ * of sub-module instantiation.
*
- * @param {object} from - The base object.
- * @param {object} to - The object to extend with.
+ * Connection details currently come in two ways: `credentials` or
+ * `keyFilename`. Because of this, we have a special exception when overriding a
+ * global configuration object. If a user provides either to the global
+ * configuration, then provides another at submodule instantiation-time, the
+ * latter is preferred.
+ *
+ * @param {object} globalConfig - The global configuration object.
+ * @param {object} overrides - The instantiation-time configuration object.
* @return {object}
- * ```
+ *
+ * @example
+ * // globalConfig = {
+ * // credentials: {...}
+ * // }
+ * Datastore.prototype.dataset = function(options) {
+ * // options = {
+ * // keyFilename: 'keyfile.json'
+ * // }
+ * return extendGlobalConfig(this.config, options);
+ * // returns:
+ * // {
+ * // keyFilename: 'keyfile.json'
+ * // }
+ * };
*/
-function extend(from, to) {
- if (from === null || typeof from !== 'object') {
- return from;
- }
- if (from.constructor === Date || from.constructor === Function ||
- from.constructor === String || from.constructor === Number ||
- from.constructor === Boolean) {
- return new from.constructor(from);
- }
- if (from.constructor !== Object && from.constructor !== Array) {
- return from;
- }
- to = to || new from.constructor();
- for (var name in from) {
- to[name] = to[name] ? extend(from[name], null) : to[name];
+function extendGlobalConfig(globalConfig, overrides) {
+ var options = extend({}, globalConfig);
+ var hasGlobalConnection = options.credentials || options.keyFilename;
+ var isOverridingConnection = overrides.credentials || overrides.keyFilename;
+ if (hasGlobalConnection && isOverridingConnection) {
+ delete options.credentials;
+ delete options.keyFilename;
}
- return to;
+ return extend(true, {}, options, overrides);
}
-module.exports.extend = extend;
+module.exports.extendGlobalConfig = extendGlobalConfig;
/**
* Wrap an array around a non-Array object. If given an Array, it is returned.
diff --git a/lib/datastore/dataset.js b/lib/datastore/dataset.js
index ed0e71b3db8d..5ef2b8609b9b 100644
--- a/lib/datastore/dataset.js
+++ b/lib/datastore/dataset.js
@@ -74,7 +74,7 @@ var SCOPES = [
* @alias module:datastore/dataset
*
* @param {object=} options
- * @param {string} options.projectId - Dataset ID. This is your project ID from
+ * @param {string=} options.projectId - Dataset ID. This is your project ID from
* the Google Developers Console.
* @param {string=} options.keyFilename - Full path to the JSON key downloaded
* from the Google Developers Console. Alternatively, you may provide a
@@ -84,12 +84,16 @@ var SCOPES = [
* @param {string} options.namespace - Namespace to isolate transactions to.
*
* @example
- * var dataset = new datastore.Dataset({
+ * var dataset = datastore.dataset({
* projectId: 'my-project',
* keyFilename: '/path/to/keyfile.json'
* });
*/
function Dataset(options) {
+ if (!(this instanceof Dataset)) {
+ return new Dataset(options);
+ }
+
options = options || {};
this.connection = new conn.Connection({
@@ -97,7 +101,7 @@ function Dataset(options) {
keyFilename: options.keyFilename,
scopes: SCOPES
});
- this.id = options.projectId;
+ this.projectId = options.projectId;
this.namespace = options.namespace;
this.transaction = this.createTransaction_();
}
@@ -108,13 +112,11 @@ function Dataset(options) {
* You may also specify a configuration object to define a namespace and path.
*
* @example
- * var key;
- *
* // Create a key from the dataset's namespace.
- * key = dataset.key('Company', 123);
+ * var company123 = dataset.key('Company', 123);
*
* // Create a key from a provided namespace and path.
- * key = dataset.key({
+ * var nsCompany123 = dataset.key({
* namespace: 'My-NS',
* path: ['Company', 123]
* });
@@ -347,7 +349,7 @@ Dataset.prototype.allocateIds = function(incompleteKey, n, callback) {
* @private
*/
Dataset.prototype.createTransaction_ = function() {
- return new Transaction(this.connection, this.id);
+ return new Transaction(this.connection, this.projectId);
};
module.exports = Dataset;
diff --git a/lib/datastore/index.js b/lib/datastore/index.js
index 1ee836ac2e6d..9bcd7044409e 100644
--- a/lib/datastore/index.js
+++ b/lib/datastore/index.js
@@ -26,22 +26,97 @@
*/
var entity = require('./entity');
-/*!
- * @alias module:datastore
+/**
+ * @type module:common/util
+ * @private
+ */
+var util = require('../common/util.js');
+
+/**
+ * @type module:datastore/dataset
+ * @private
*/
-var datastore = {};
+var Dataset = require('./dataset');
+/*! Developer Documentation
+ *
+ * Invoking the Datastore class allows you to provide configuration up-front.
+ * This configuration will be used for future invokations of the returned
+ * `dataset` method.
+ *
+ * @example
+ * var datastore = require('gcloud/lib/datastore')({
+ * keyFilename: '/path/to/keyfile.json'
+ * });
+ *
+ * var dataset = datastore.dataset();
+ * // equal to:
+ * // datastore.dataset({
+ * // keyFilename: '/path/to/keyfile.json'
+ * // });
+ */
/**
- * @see {module:datastore/dataset}
+ * The example below will demonstrate the different usage patterns your app may
+ * need to support to retrieve a datastore object.
+ *
+ * @alias module:datastore
+ * @constructor
*
* @example
* var gcloud = require('gcloud');
- * var datastore = gcloud.datastore;
+ *
+ * // Providing configuration details up-front.
+ * var myProject = gcloud({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'my-project'
+ * });
+ *
+ * var dataset = myProject.datastore.dataset();
+ *
+ *
+ * // Overriding default configuration details.
+ * var anotherDataset = myProject.datastore.dataset({
+ * keyFilename: '/path/to/another/keyfile.json'
+ * });
+ *
+ *
+ * // Not using a default configuration.
+ * var myOtherProject = gcloud.datastore.dataset({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'my-project'
+ * });
+ */
+function Datastore(config) {
+ this.config = config || {};
+}
+
+/*! Developer Documentation
+ *
+ * Use this static method to create a dataset without any pre-configured
+ * options.
+ *
+ * @example
+ * var datastore = require('gcloud/lib/datastore');
*
* // Create a Dataset object.
- * var dataset = new datastore.Dataset();
+ * var dataset = datastore.dataset({
+ * keyFilename: '/path/to/keyfile.json'
+ * });
*/
-datastore.Dataset = require('./dataset');
+Datastore.dataset = Dataset;
+
+/*! Developer Documentation
+ *
+ * Create a dataset using the instance method when you want to use your
+ * pre-configured options from the Datastore instance.
+ *
+ * @param {object=} options - Configuration object.
+ * @return {module:datastore/dataset}
+ */
+Datastore.prototype.dataset = function(options) {
+ // Mix in global config data to the provided options.
+ return new Dataset(util.extendGlobalConfig(this.config, options));
+};
/**
* Helper function to get a Datastore Integer object.
@@ -55,7 +130,7 @@ datastore.Dataset = require('./dataset');
* // Create an Integer.
* var sevenInteger = gcloud.datastore.int(7);
*/
-datastore.int = function(value) {
+Datastore.int = function(value) {
return new entity.Int(value);
};
@@ -71,8 +146,8 @@ datastore.int = function(value) {
* // Create a Double.
* var threeDouble = gcloud.datastore.double(3.0);
*/
-datastore.double = function(value) {
+Datastore.double = function(value) {
return new entity.Double(value);
};
-module.exports = datastore;
+module.exports = Datastore;
diff --git a/lib/datastore/query.js b/lib/datastore/query.js
index f5fac2baf110..4258c977110b 100644
--- a/lib/datastore/query.js
+++ b/lib/datastore/query.js
@@ -20,6 +20,8 @@
'use strict';
+var extend = require('extend');
+
/**
* @type {module:common/util}
* @private
@@ -42,17 +44,15 @@ var util = require('../common/util.js');
* @param {string[]} kinds - Kinds to query.
*
* @example
- * var query;
- *
* // If your dataset was scoped to a namespace at initialization, your query
* // will likewise be scoped to that namespace.
- * query = dataset.createQuery(['Lion', 'Chimp']);
+ * dataset.createQuery(['Lion', 'Chimp']);
*
* // However, you may override the namespace per query.
- * query = dataset.createQuery('AnimalNamespace', ['Lion', 'Chimp']);
+ * dataset.createQuery('AnimalNamespace', ['Lion', 'Chimp']);
*
* // You may also remove the namespace altogether.
- * query = dataset.createQuery(null, ['Lion', 'Chimp']);
+ * dataset.createQuery(null, ['Lion', 'Chimp']);
*/
function Query(namespace, kinds) {
if (!kinds) {
@@ -100,13 +100,17 @@ function Query(namespace, kinds) {
*/
Query.prototype.filter = function(filter, value) {
// TODO: Add filter validation.
- var q = util.extend(this, new Query());
+ var query = extend(new Query(), this);
filter = filter.trim();
var fieldName = filter.replace(/[>|<|=|>=|<=]*$/, '').trim();
var op = filter.substr(fieldName.length, filter.length).trim();
- q.filters = q.filters || [];
- q.filters.push({ name: fieldName, op: op, val: value });
- return q;
+ query.filters = query.filters || [];
+ query.filters.push({
+ name: fieldName,
+ op: op,
+ val: value
+ });
+ return query;
};
/**
@@ -121,9 +125,9 @@ Query.prototype.filter = function(filter, value) {
* var ancestoryQuery = query.hasAncestor(dataset.key('Parent', 123));
*/
Query.prototype.hasAncestor = function(key) {
- var q = util.extend(this, new Query());
- this.filters.push({ name: '__key__', op: 'HAS_ANCESTOR', val: key });
- return q;
+ var query = extend(new Query(), this);
+ query.filters.push({ name: '__key__', op: 'HAS_ANCESTOR', val: key });
+ return query;
};
/**
@@ -143,15 +147,15 @@ Query.prototype.hasAncestor = function(key) {
* var companiesDescending = companyQuery.order('-size');
*/
Query.prototype.order = function(property) {
- var q = util.extend(this, new Query());
+ var query = extend(new Query(), this);
var sign = '+';
if (property[0] === '-' || property[0] === '+') {
sign = property[0];
property = property.substr(1);
}
- q.orders = q.orders || [];
- q.orders.push({ name: property, sign: sign });
- return q;
+ query.orders = query.orders || [];
+ query.orders.push({ name: property, sign: sign });
+ return query;
};
/**
@@ -164,10 +168,9 @@ Query.prototype.order = function(property) {
* var groupedQuery = companyQuery.groupBy(['name', 'size']);
*/
Query.prototype.groupBy = function(fieldNames) {
- var fields = util.arrayize(fieldNames);
- var q = util.extend(this, new Query());
- q.groupByVal = fields;
- return q;
+ var query = extend(new Query(), this);
+ query.groupByVal = util.arrayize(fieldNames);
+ return query;
};
/**
@@ -183,9 +186,9 @@ Query.prototype.groupBy = function(fieldNames) {
* var selectQuery = companyQuery.select(['name', 'size']);
*/
Query.prototype.select = function(fieldNames) {
- var q = util.extend(this, new Query());
- q.selectVal = fieldNames;
- return q;
+ var query = extend(new Query(), this);
+ query.selectVal = fieldNames;
+ return query;
};
/**
@@ -203,9 +206,9 @@ Query.prototype.select = function(fieldNames) {
* var startQuery = companyQuery.start(cursorToken);
*/
Query.prototype.start = function(start) {
- var q = util.extend(this, new Query());
- q.startVal = start;
- return q;
+ var query = extend(new Query(), this);
+ query.startVal = start;
+ return query;
};
/**
@@ -223,9 +226,9 @@ Query.prototype.start = function(start) {
* var endQuery = companyQuery.end(cursorToken);
*/
Query.prototype.end = function(end) {
- var q = util.extend(this, new Query());
- q.endVal = end;
- return q;
+ var query = extend(new Query(), this);
+ query.endVal = end;
+ return query;
};
/**
@@ -241,9 +244,9 @@ Query.prototype.end = function(end) {
* var limitQuery = companyQuery.limit(10);
*/
Query.prototype.limit = function(n) {
- var q = util.extend(this, new Query());
- q.limitVal = n;
- return q;
+ var query = extend(new Query(), this);
+ query.limitVal = n;
+ return query;
};
/**
@@ -259,9 +262,9 @@ Query.prototype.limit = function(n) {
* var offsetQuery = companyQuery.offset(100);
*/
Query.prototype.offset = function(n) {
- var q = util.extend(this, new Query());
- q.offsetVal = n;
- return q;
+ var query = extend(new Query(), this);
+ query.offsetVal = n;
+ return query;
};
module.exports = Query;
diff --git a/lib/index.js b/lib/index.js
index c693970ce710..530f42352d9c 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -20,15 +20,87 @@
'use strict';
-/*!
+/**
+ * @type {module:datastore}
+ * @private
+ */
+var Datastore = require('./datastore');
+
+/**
+ * @type {module:pubsub}
+ * @private
+ */
+var pubsub = require('./pubsub');
+
+/**
+ * @type {module:storage}
+ * @private
+ */
+var Storage = require('./storage');
+
+/**
+ * There are two key ways to use the `gcloud` module.
+ *
+ * 1. Provide connection & configuration details up-front.
+ *
+ * 2. Provide them at the time of instantiation of sub-modules, e.g. a Datastore
+ * dataset, a Cloud Storage bucket, etc.
+ *
+ * If you are using Google App Engine or Google Compute Engine, your connection
+ * details are handled for you. You won't have to worry about specifying these,
+ * however you may find it advantageous to provide a `projectId` at
+ * instantiation.
+ *
+ * To specify the configuration details up-front, invoke the gcloud module,
+ * passing in an object. The properties defined on this object will be persisted
+ * to the instantiation of every sub-module. It's important to note that you can
+ * override any of these defaults when you invoke a sub-module later.
+ *
+ * You can invoke this module as many times as your project warrants. Each time,
+ * your provided configuration will remain isolated to the returned gcloud
+ * module.
+ *
* @alias module:gcloud
+ * @constructor
+ *
+ * @param {object} config - Connection configuration options.
+ * @param {string=} config.keyFilename - Full path to the JSON key downloaded
+ * from the Google Developers Console. Alternatively, you may provide a
+ * `credentials` object.
+ * @param {object=} config.credentials - Credentials object.
+ * @param {string} config.credentials.client_email
+ * @param {string} config.credentials.private_key
+ *
+ * @example
+ * var gcloud = require('gcloud')({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'my-project'
+ * });
+ *
+ * var dataset = gcloud.datastore.dataset();
+ * // equal to:
+ * // gcloud.datastore.dataset({
+ * // keyFilename: '/path/to/keyfile.json',
+ * // projectId: 'my-project'
+ * // });
+ *
+ * var bucket = gcloud.storage.bucket({
+ * bucketName: 'PhotosBucket',
+ * // properties may be overriden:
+ * keyFilename: '/path/to/other/keyfile.json'
+ * });
*/
-var gcloud = {};
+function gcloud(config) {
+ return {
+ datastore: new Datastore(config),
+ storage: new Storage(config)
+ };
+}
/**
- * [Google Cloud Datastore]{@link https://developers.google.com/datastore/} is a
- * fully managed, schemaless database for storing non-relational data. Use this
- * object to create a Dataset to interact with your data, an "Int", and a
+ * [Google Cloud Datastore]{@link https://developers.google.com/datastore/} is
+ * a fully managed, schemaless database for storing non-relational data. Use
+ * this object to create a Dataset to interact with your data, an "Int", and a
* "Double" representation.
*
* @type {module:datastore}
@@ -46,20 +118,20 @@ var gcloud = {};
* // int: function() {}
* // }
*/
-gcloud.datastore = require('./datastore');
+gcloud.datastore = Datastore;
/**
* **Experimental**
*
* [Google Cloud Pub/Sub]{@link https://developers.google.com/pubsub/overview}
- * is a reliable, many-to-many, asynchronous messaging service from Google Cloud
- * Platform.
+ * is a reliable, many-to-many, asynchronous messaging service from Google
+ * Cloud Platform.
*
* Note: Google Cloud Pub/Sub API is available as a Limited Preview and the
* client library we provide is currently experimental. The API and/or the
* client might be changed in backward-incompatible ways. This API is not
- * subject to any SLA or deprecation policy. Request to be whitelisted to use it
- * by filling the
+ * subject to any SLA or deprecation policy. Request to be whitelisted to use
+ * it by filling the
* [Limited Preview application form]{@link http://goo.gl/sO0wTu}.
*
* @type {module:pubsub}
@@ -75,17 +147,19 @@ gcloud.datastore = require('./datastore');
* keyFilename: '/path/to/the/key.json'
* });
*/
-gcloud.pubsub = require('./pubsub');
+gcloud.pubsub = pubsub;
/**
- * Google Cloud Storage allows you to store data on Google infrastructure. Read
+ * Google Cloud Storage allows you to store data on Google infrastructure.
+ * Read
* [Google Cloud Storage API docs]{@link https://developers.google.com/storage/}
* for more information.
*
- * You need to create a Google Cloud Storage bucket to use this client library.
+ * You need to create a Google Cloud Storage bucket to use this client
+ * library.
* Follow the steps on
- * [Google Cloud Storage docs]{@link https://developers.google.com/storage/} to
- * create a bucket.
+ * [Google Cloud Storage docs]{@link https://developers.google.com/storage/}
+ * to create a bucket.
* @type {module:storage}
*
@@ -100,6 +174,6 @@ gcloud.pubsub = require('./pubsub');
* // Bucket: function() {}
* // }
*/
-gcloud.storage = require('./storage');
+gcloud.storage = Storage;
module.exports = gcloud;
diff --git a/lib/pubsub/index.js b/lib/pubsub/index.js
index facc8c52bd48..31d67eec1e32 100644
--- a/lib/pubsub/index.js
+++ b/lib/pubsub/index.js
@@ -21,6 +21,7 @@
'use strict';
var events = require('events');
+var extend = require('extend');
var nodeutil = require('util');
/** @type {module:common/connection} */
@@ -252,7 +253,7 @@ Connection.prototype.listSubscriptions = function(query, callback) {
callback = query;
query = {};
}
- var q = util.extend({}, query);
+ var q = extend({}, query);
if (q.filterByTopic) {
q.query =
'pubsub.googleapis.com/topic in (' +
@@ -350,7 +351,7 @@ Connection.prototype.listTopics = function(query, callback) {
callback = query;
query = {};
}
- var q = util.extend({}, query);
+ var q = extend({}, query);
q.query = 'cloud.googleapis.com/project in (' + this.fullProjectName_() + ')';
this.makeReq('GET', 'topics', q, true, function(err, result) {
if (err) {
diff --git a/lib/storage/index.js b/lib/storage/index.js
index 6f3eb8c5d9b0..93b22a389735 100644
--- a/lib/storage/index.js
+++ b/lib/storage/index.js
@@ -22,6 +22,7 @@
var crypto = require('crypto');
var duplexify = require('duplexify');
+var extend = require('extend');
var stream = require('stream');
var uuid = require('node-uuid');
@@ -56,13 +57,115 @@ var STORAGE_BASE_URL = 'https://www.googleapis.com/storage/v1/b';
*/
var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b';
+
+/*! Developer Documentation
+ *
+ * Invoke this method to create a new Storage object bound with pre-determined
+ * configuration options. For each object that can be created (e.g., a bucket),
+ * there is an equivalent static and instance method. While they are classes,
+ * they can be instantiated without use of the `new` keyword.
+ * @param {object} config - Configuration object.
+ */
/**
- * Google Cloud Storage allows you to store data on Google infrastructure. See
- * the guide on {@link https://developers.google.com/storage} to create a
- * bucket.
+ * To access your Cloud Storage buckets, you will use the `bucket` function
+ * returned from this `storage` object.
+ *
+ * The example below will demonstrate the different usage patterns your app may
+ * need to connect to `gcloud` and access your bucket.
*
* @alias module:storage
+ * @constructor
+ *
+ * @example
+ * var gcloud = require('gcloud');
+ *
+ * // From Google Compute Engine and Google App Engine:
+ *
+ * // Access `storage` through the `gcloud` module directly.
+ * var musicBucket = gcloud.storage.bucket({
+ * bucketName: 'MusicBucket'
+ * });
+ *
+ * // Elsewhere:
+ *
+ * // Provide configuration details up-front.
+ * var myProject = gcloud({
+ * keyFilename: '/path/to/keyfile.json',
+ * projectId: 'my-project'
+ * });
+ *
+ * var albums = myProject.storage.bucket({
+ * bucketName: 'AlbumsBucket'
+ * });
+ *
+ * var photos = myProject.storage.bucket({
+ * bucketName: 'PhotosBucket'
+ * });
+ *
+ *
+ * // Override default configuration details.
+ * var records = myProject.storage.bucket({
+ * bucketName: 'RecordsBucket',
+ * keyFilename: '/path/to/another/keyfile.json',
+ * });
+ *
+ *
+ * // Specify all options at instantiation.
+ * var misc = gcloud.storage.bucket({
+ * keyFilename: '/path/to/keyfile.json',
+ * bucketName: 'MiscBucket'
+ * });
+ */
+function Storage(config) {
+ this.config = config || {};
+}
+
+/*! Developer Documentation
+ *
+ * Static method to create a Bucket without any pre-configured options.
+ *
+ * @example
+ * var gcloud = require('gcloud');
+ *
+ * var Albums = gcloud.storage.bucket({
+ * bucketName: 'AlbumsBucket',
+ * keyFilename: '/path/to/keyfile.json'
+ * });
+ */
+Storage.bucket = Bucket;
+
+/*! Developer Documentation
+ *
+ * Instance method for creating a Bucket. Options configured at instantiation of
+ * the Storage class will be passed through, allowing for overridden options
+ * specified here.
+ *
+ * @param {object} options
+ * @return {Bucket}
+ *
+ * @example
+ * var gcloud = require('gcloud')({
+ * keyFilename: '/path/to/keyfile.json'
+ * });
+ *
+ * var Albums = gcloud.storage.bucket({
+ * bucketName: 'AlbumsBucket'
+ * });
+ *
+ * var Photos = gcloud.storage.bucket({
+ * bucketName: 'PhotosBucket'
+ * });
+ */
+Storage.prototype.bucket = function(options) {
+ // Mix in instance config data to the provided options.
+ return new Bucket(util.extendGlobalConfig(this.config, options));
+};
+
+module.exports = Storage;
+/**
+ * Create a Bucket object to interact with a Google Cloud Storage bucket.
+ *
* @throws if a bucket name isn't provided.
*
* @param {object} options - Configuration options.
@@ -75,26 +178,29 @@ var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b';
*
* @example
* var gcloud = require('gcloud');
- * var storage = gcloud.storage;
- * var bucket;
*
* // From Google Compute Engine
- * bucket = new storage.Bucket({
- * bucketName: YOUR_BUCKET_NAME
+ * var Albums = gcloud.storage.bucket({
+ * bucketName: 'Albums'
* });
*
* // From elsewhere
- * bucket = new storage.Bucket({
- * bucketName: YOUR_BUCKET_NAME,
- * keyFilename: '/path/to/the/key.json'
+ * var Photos = gcloud.storage.bucket({
+ * bucketName: 'PhotosBucket',
+ * keyFilename: '/path/to/keyfile.json'
* });
*/
function Bucket(options) {
+ if (!(this instanceof Bucket)) {
+ return new Bucket(options);
+ }
+
if (!options.bucketName) {
throw Error('A bucket name is needed to use Google Cloud Storage');
}
+
this.bucketName = options.bucketName;
- this.conn = new conn.Connection({
+ this.connection = new conn.Connection({
credentials: options.credentials,
keyFilename: options.keyFilename,
scopes: SCOPES
@@ -141,7 +247,7 @@ Bucket.prototype.list = function(query, callback) {
}
var nextQuery = null;
if (resp.nextPageToken) {
- nextQuery = util.extend({}, query);
+ nextQuery = extend({}, query);
nextQuery.pageToken = resp.nextPageToken;
}
callback(null, resp.items, nextQuery);
@@ -250,7 +356,7 @@ Bucket.prototype.getSignedUrl = function(options, callback) {
resource: options.resource
});
- this.conn.getCredentials(function(err, credentials) {
+ this.connection.getCredentials(function(err, credentials) {
if (err) {
callback(err);
return;
@@ -299,13 +405,13 @@ Bucket.prototype.createReadStream = function(name) {
dup.emit('error', err);
return;
}
- that.conn.createAuthorizedReq(
+ that.connection.createAuthorizedReq(
{ uri: metadata.mediaLink }, function(err, req) {
if (err) {
dup.emit('error', err);
return;
}
- dup.setReadable(that.conn.requester(req));
+ dup.setReadable(that.connection.requester(req));
});
});
return dup;
@@ -401,7 +507,7 @@ Bucket.prototype.getWritableStream_ = function(name, metadata, callback) {
var boundary = uuid.v4();
var that = this;
metadata.contentType = metadata.contentType || 'text/plain';
- this.conn.createAuthorizedReq({
+ this.connection.createAuthorizedReq({
method: 'POST',
uri: util.format('{base}/{bucket}/o', {
base: STORAGE_UPLOAD_BASE_URL,
@@ -419,7 +525,7 @@ Bucket.prototype.getWritableStream_ = function(name, metadata, callback) {
callback(err);
return;
}
- var remoteStream = that.conn.requester(req);
+ var remoteStream = that.connection.requester(req);
remoteStream.callback = util.noop;
remoteStream.write('--' + boundary + '\n');
remoteStream.write('Content-Type: application/json\n\n');
@@ -462,9 +568,7 @@ Bucket.prototype.makeReq_ = function(method, path, query, body, callback) {
if (body) {
reqOpts.json = body;
}
- this.conn.req(reqOpts, function(err, res, body) {
+ this.connection.req(reqOpts, function(err, res, body) {
util.handleResp(err, res, body, callback);
});
};
-
-module.exports.Bucket = Bucket;
diff --git a/package.json b/package.json
index 8e514c96c45b..b5a6d8fda14f 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
],
"dependencies": {
"duplexify": "^3.1.2",
+ "extend": "^1.3.0",
"gapitoken": "^0.1.3",
"node-uuid": "^1.4.1",
"protobufjs": "^3.4.0",
diff --git a/regression/datastore.js b/regression/datastore.js
index 3a1ed3f86ecb..0179bdacd71b 100644
--- a/regression/datastore.js
+++ b/regression/datastore.js
@@ -22,7 +22,7 @@ var env = require('./env.js');
var assert = require('assert');
var datastore = require('../lib/datastore');
-var ds = new datastore.Dataset(env);
+var ds = datastore.dataset(env);
var entity = require('../lib/datastore/entity.js');
describe('datastore', function() {
diff --git a/regression/storage.js b/regression/storage.js
index 6789f2d666a5..ab39409b21e8 100644
--- a/regression/storage.js
+++ b/regression/storage.js
@@ -26,9 +26,9 @@ var request = require('request');
var tmp = require('tmp');
var env = require('./env.js');
-var gcloud = require('../lib');
+var storage = require('../lib/storage');
-var bucket = new gcloud.storage.Bucket(env);
+var bucket = storage.bucket(env);
var files = {
logo: {
diff --git a/test/common/util.js b/test/common/util.js
index 8eb99d463cd0..c56494ab7804 100644
--- a/test/common/util.js
+++ b/test/common/util.js
@@ -21,20 +21,6 @@
var assert = require('assert');
var util = require('../../lib/common/util.js');
-describe('extend', function() {
- it ('should return null for null input', function() {
- var copy = util.extend(null, {});
- assert.strictEqual(copy, null);
- });
-
- it('should return a new Date for Date input', function() {
- var now = new Date();
- var copy = util.extend(now, {});
- assert.notStrictEqual(copy, now);
- assert.strictEqual(copy.toString(), now.toString());
- });
-});
-
describe('arrayize', function() {
it('should arrayize if the input is not an array', function(done) {
var o = util.arrayize('text');
@@ -43,6 +29,28 @@ describe('arrayize', function() {
});
});
+describe('extendGlobalConfig', function() {
+ it('should favor `keyFilename` when `credentials` is global', function() {
+ var globalConfig = { credentials: {} };
+ var options = util.extendGlobalConfig(globalConfig, {
+ keyFilename: 'key.json'
+ });
+ assert.deepEqual(options, { keyFilename: 'key.json' });
+ });
+
+ it('should favor `credentials` when `keyFilename` is global', function() {
+ var globalConfig = { keyFilename: 'key.json' };
+ var options = util.extendGlobalConfig(globalConfig, { credentials: {} });
+ assert.deepEqual(options, { credentials: {} });
+ });
+
+ it('should not modify original object', function() {
+ var globalConfig = { keyFilename: 'key.json' };
+ util.extendGlobalConfig(globalConfig, { credentials: {} });
+ assert.deepEqual(globalConfig, { keyFilename: 'key.json' });
+ });
+});
+
describe('handleResp', function() {
it('should handle errors', function(done) {
var defaultErr = new Error('new error');
diff --git a/test/datastore/dataset.js b/test/datastore/dataset.js
index e442928f8fe0..ae2325e9bb58 100644
--- a/test/datastore/dataset.js
+++ b/test/datastore/dataset.js
@@ -20,21 +20,48 @@
var assert = require('assert');
var ByteBuffer = require('bytebuffer');
+var gcloud = require('../../lib');
var datastore = require('../../lib').datastore;
var entity = require('../../lib/datastore/entity.js');
var mockRespGet = require('../testdata/response_get.json');
var Transaction = require('../../lib/datastore/transaction.js');
describe('Dataset', function() {
+ it('should not require connection details', function() {
+ var credentials = require('../testdata/privateKeyFile.json');
+ var project = gcloud({
+ projectId: 'test-project',
+ credentials: credentials
+ });
+ var ds = project.datastore.dataset({ hi: 'there' });
+ assert.equal(ds.projectId, 'test-project');
+ assert.deepEqual(ds.connection.credentials, credentials);
+ });
+
+ it('should allow overriding connection details', function() {
+ var projectCredentials = require('../testdata/privateKeyFile.json');
+ var uniqueCredentials = require('../testdata/privateKeyFile-2.json');
+ var project = gcloud({
+ projectId: 'test-project',
+ credentials: projectCredentials
+ });
+ var ds = project.datastore.dataset({
+ projectId: 'another-project',
+ credentials: uniqueCredentials
+ });
+ assert.equal(ds.projectId, 'another-project');
+ assert.deepEqual(ds.connection.credentials, uniqueCredentials);
+ });
+
it('should return a key scoped by namespace', function() {
- var ds = new datastore.Dataset({ projectId: 'test', namespace: 'my-ns' });
+ var ds = datastore.dataset({ projectId: 'test', namespace: 'my-ns' });
var key = ds.key('Company', 1);
assert.equal(key.namespace, 'my-ns');
assert.deepEqual(key.path, ['Company', 1]);
});
it('should allow namespace specification when creating a key', function() {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
var key = ds.key({
namespace: 'custom-ns',
path: ['Company', 1]
@@ -44,7 +71,7 @@ describe('Dataset', function() {
});
it('should get by key', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'lookup');
assert.equal(proto.key.length, 1);
@@ -61,7 +88,7 @@ describe('Dataset', function() {
});
it('should multi get by keys', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'lookup');
assert.equal(proto.key.length, 1);
@@ -80,7 +107,7 @@ describe('Dataset', function() {
});
it('should continue looking for deferred results', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
var key = ds.key('Kind', 5732568548769792);
var key2 = ds.key('Kind', 5732568548769792);
var lookupCount = 0;
@@ -103,7 +130,7 @@ describe('Dataset', function() {
});
it('should delete by key', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'commit');
assert.equal(!!proto.mutation.delete, true);
@@ -113,7 +140,7 @@ describe('Dataset', function() {
});
it('should multi delete by keys', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'commit');
assert.equal(proto.mutation.delete.length, 2);
@@ -126,7 +153,7 @@ describe('Dataset', function() {
});
it('should save with incomplete key', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'commit');
assert.equal(proto.mutation.insert_auto_id.length, 1);
@@ -137,7 +164,7 @@ describe('Dataset', function() {
});
it('should save with keys', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'commit');
assert.equal(proto.mutation.upsert.length, 2);
@@ -153,7 +180,7 @@ describe('Dataset', function() {
});
it('should produce proper allocate IDs req protos', function(done) {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
ds.transaction.makeReq = function(method, proto, typ, callback) {
assert.equal(method, 'allocateIds');
assert.equal(proto.key.length, 1);
@@ -174,7 +201,7 @@ describe('Dataset', function() {
});
it('should throw if trying to allocate IDs with complete keys', function() {
- var ds = new datastore.Dataset({ projectId: 'test' });
+ var ds = datastore.dataset({ projectId: 'test' });
assert.throws(function() {
ds.allocateIds(ds.key('Kind', 123));
});
@@ -185,7 +212,7 @@ describe('Dataset', function() {
var transaction;
beforeEach(function() {
- ds = new datastore.Dataset({ projectId: 'test' });
+ ds = datastore.dataset({ projectId: 'test' });
ds.createTransaction_ = function() {
transaction = new Transaction();
transaction.makeReq = function(method, proto, typ, callback) {
@@ -221,8 +248,8 @@ describe('Dataset', function() {
var dsWithNs;
beforeEach(function() {
- ds = new datastore.Dataset({ projectId: 'test' });
- dsWithNs = new datastore.Dataset({
+ ds = datastore.dataset({ projectId: 'test' });
+ dsWithNs = datastore.dataset({
projectId: 'test',
namespace: 'my-ns'
});
@@ -269,7 +296,7 @@ describe('Dataset', function() {
};
beforeEach(function() {
- ds = new datastore.Dataset({ projectId: 'test' });
+ ds = datastore.dataset({ projectId: 'test' });
query = ds.createQuery('Kind');
});
diff --git a/test/datastore/entity.js b/test/datastore/entity.js
index f3a789b36777..2ac8fa9ea305 100644
--- a/test/datastore/entity.js
+++ b/test/datastore/entity.js
@@ -322,7 +322,7 @@ describe('entityToEntityProto', function() {
describe('queryToQueryProto', function() {
it('should support filters and ancestory filtering', function(done) {
- var ds = new datastore.Dataset({ projectId: 'project-id' });
+ var ds = datastore.dataset({ projectId: 'project-id' });
var q = ds.createQuery('Kind1')
.filter('name =', 'John')
.hasAncestor(new entity.Key({ path: [ 'Kind2', 'somename' ] }));
diff --git a/test/datastore/index.js b/test/datastore/index.js
index a71fa6e3913e..e39377c7f8b5 100644
--- a/test/datastore/index.js
+++ b/test/datastore/index.js
@@ -39,7 +39,7 @@ var datastore = require('sandboxed-module')
describe('Datastore', function() {
it('should expose Dataset class', function() {
- assert.equal(typeof datastore.Dataset, 'function');
+ assert.equal(typeof datastore.dataset, 'function');
});
it('should expose Int builder', function() {
diff --git a/test/datastore/transaction.js b/test/datastore/transaction.js
index 4b9097dae516..c273c7a1e1de 100644
--- a/test/datastore/transaction.js
+++ b/test/datastore/transaction.js
@@ -26,7 +26,7 @@ describe('Transaction', function() {
var transaction;
beforeEach(function() {
- ds = new datastore.Dataset({ projectId: 'test' });
+ ds = datastore.dataset({ projectId: 'test' });
transaction = ds.createTransaction_(null, 'test');
});
diff --git a/test/storage/index.js b/test/storage/index.js
index dd1c8548cf56..eda3489ca406 100644
--- a/test/storage/index.js
+++ b/test/storage/index.js
@@ -14,30 +14,48 @@
* limitations under the License.
*/
-/*global describe, it */
+/*global describe, it, beforeEach */
'use strict';
var assert = require('assert');
-var storage = require('../../lib').storage;
+var gcloud = require('../../lib');
+var storage = require('../../lib/storage');
+var credentials = require('../testdata/privateKeyFile.json');
var noop = function() {};
-function createBucket() {
- return new storage.Bucket({
- bucketName: 'bucket-name',
- email: 'xxx@email.com',
- credentials: require('../testdata/privateKeyFile.json')
+describe('Bucket', function() {
+ var bucket;
+
+ beforeEach(function() {
+ bucket = storage.bucket({
+ bucketName: 'bucket-name',
+ credentials: credentials
+ });
+ });
+
+ it('should not require connection details', function() {
+ var project = gcloud({ credentials: credentials });
+ var aBucket = project.storage.bucket({ bucketName: 'test' });
+ assert.deepEqual(aBucket.connection.credentials, credentials);
+ });
+
+ it('should allow overriding connection details', function() {
+ var uniqueCredentials = require('../testdata/privateKeyFile-2.json');
+ var project = gcloud({ credentials: credentials });
+ var aBucket = project.storage.bucket({
+ bucketName: 'another-bucket',
+ credentials: uniqueCredentials
+ });
+ assert.deepEqual(aBucket.connection.credentials, uniqueCredentials);
});
-}
-describe('Bucket', function() {
it('should throw if a bucket name is not passed', function() {
- assert.throws(function() { new storage.Bucket(); }, Error);
+ assert.throws(storage.bucket, Error);
});
it('should list without a query', function(done) {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body) {
assert.strictEqual(method, 'GET');
assert.strictEqual(path, 'o');
@@ -49,7 +67,6 @@ describe('Bucket', function() {
});
it('should list with a query', function(done) {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body) {
assert.strictEqual(method, 'GET');
assert.strictEqual(path, 'o');
@@ -61,7 +78,6 @@ describe('Bucket', function() {
});
it('should return nextQuery if more results', function() {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body, callback) {
callback(null, { nextPageToken: 'next-page-token', items: [] });
};
@@ -72,7 +88,6 @@ describe('Bucket', function() {
});
it('should return no nextQuery if no more results', function() {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body, callback) {
callback(null, { items: [] });
};
@@ -82,7 +97,6 @@ describe('Bucket', function() {
});
it('should stat a file', function(done) {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body) {
assert.strictEqual(method, 'GET');
assert.strictEqual(path, 'o/file-name');
@@ -94,7 +108,6 @@ describe('Bucket', function() {
});
it('should copy a file', function(done) {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body) {
assert.strictEqual(method, 'POST');
assert.strictEqual(path, 'o/file-name/copyTo/b/new-bucket/o/new-name');
@@ -107,7 +120,6 @@ describe('Bucket', function() {
});
it('should use the same bucket if nothing else is provided', function(done) {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body) {
assert.strictEqual(method, 'POST');
assert.strictEqual(path, 'o/file-name/copyTo/b/bucket-name/o/new-name');
@@ -120,7 +132,6 @@ describe('Bucket', function() {
});
it('should remove a file', function(done) {
- var bucket = createBucket();
bucket.makeReq_ = function(method, path, q, body) {
assert.strictEqual(method, 'DELETE');
assert.strictEqual(path, 'o/file-name');
@@ -132,7 +143,6 @@ describe('Bucket', function() {
});
it('should create a signed url', function(done) {
- var bucket = createBucket();
bucket.getSignedUrl({
action: 'read',
resource: 'filename',
diff --git a/test/testdata/privateKeyFile-2.json b/test/testdata/privateKeyFile-2.json
new file mode 100644
index 000000000000..f462a3ea951b
--- /dev/null
+++ b/test/testdata/privateKeyFile-2.json
@@ -0,0 +1,7 @@
+{
+ "private_key_id": "8",
+ "private_key": "-----BEGIN RSA PRIVATE KEY-----IIBOgIBAAJBAK8Q+ToR4tWGshaKYRHKJ3ZmMUF6jjwCS/u1A8v1tFbQiVpBlxYB\npaNcT2ENEXBGdmWqr8VwSl0NBIKyq4p0rhsCAQMCQHS1+3wL7I5ZzA8G62Exb6RE\nINZRtCgBh/0jV91OeDnfQUc07SE6vs31J8m7qw/rxeB3E9h6oGi9IVRebVO+9zsC\nIQDWb//KAzrSOo0P0yktnY57UF9Q3Y26rulWI6LqpsxZDwIhAND/cmlg7rUz34Pf\nSmM61lJEmMEjKp8RB/xgghzmCeI1AiEAjvVVMVd8jCcItTdwyRO0UjWU4JOz0cnw\n5BfB8cSIO18CIQCLVPbw60nOIpUClNxCJzmMLbsrbMcUtgVS6wFomVvsIwIhAK+A\nYqT6WwsMW2On5l9di+RPzhDT1QdGyTI5eFNS+GxY\n-----END RSA PRIVATE KEY-----",
+ "client_email": "secondpart@firstpart.com",
+ "client_id": "9",
+ "type": "service_account"
+}