From cbbbf6c7f111af93444a2863c8938e0d7ad673dd Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 26 Jun 2017 13:58:31 -0400 Subject: [PATCH] storage/bucket: add label accessor methods (#2387) --- packages/storage/src/bucket.js | 141 ++++++++++++++++++++++ packages/storage/system-test/storage.js | 84 ++++++++++++++ packages/storage/test/bucket.js | 148 ++++++++++++++++++++++++ 3 files changed, 373 insertions(+) diff --git a/packages/storage/src/bucket.js b/packages/storage/src/bucket.js index 4f13ff40512..9d4abc6a057 100644 --- a/packages/storage/src/bucket.js +++ b/packages/storage/src/bucket.js @@ -627,6 +627,75 @@ Bucket.prototype.deleteFiles = function(query, callback) { }); }; + +/** + * Delete one or more labels from this bucket. + * + * @param {string=|string[]=} labels - The labels to delete. If no labels are + * provided, all of the labels are removed. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.metadata - The bucket's metadata. + * + * @example + * //- + * // Delete all of the labels from this bucket. + * //- + * bucket.deleteLabels(function(err, metadata) {}); + * + * //- + * // Delete a single label. + * //- + * bucket.deleteLabels('labelone', function(err, metadata) {}); + * + * //- + * // Delete a specific set of labels. + * //- + * bucket.deleteLabels([ + * 'labelone', + * 'labeltwo' + * ], function(err, metadata) {}); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * bucket.deleteLabels().then(function(data) { + * var metadata = data[0]; + * }); + */ +Bucket.prototype.deleteLabels = function(labels, callback) { + var self = this; + + if (is.fn(labels)) { + callback = labels; + labels = []; + } + + labels = arrify(labels); + + if (labels.length === 0) { + this.getLabels(function(err, labels) { + if (err) { + callback(err); + return; + } + + deleteLabels(Object.keys(labels)); + }); + } else { + deleteLabels(labels); + } + + function deleteLabels(labels) { + var nullLabelMap = labels.reduce(function(nullLabelMap, labelKey) { + nullLabelMap[labelKey] = null; + return nullLabelMap; + }, {}); + + self.setLabels(nullLabelMap, callback); + } +}; + /** * Create a File object. See {module:storage/file} to see how to handle * the different use cases you may have. @@ -794,6 +863,43 @@ Bucket.prototype.getFiles = function(query, callback) { */ Bucket.prototype.getFilesStream = common.paginator.streamify('getFiles'); +/** + * Get the labels from this bucket. + * + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.labels - The labels currently set on this bucket. + * + * @example + * bucket.getLabels(function(err, labels) { + * if (err) { + * // Error handling omitted. + * } + * + * // labels = { + * // label: 'labelValue', + * // ... + * // } + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * bucket.getLabels().then(function(data) { + * var labels = data[0]; + * }); + */ +Bucket.prototype.getLabels = function(callback) { + this.getMetadata(function(err, metadata) { + if (err) { + callback(err); + return; + } + + callback(null, metadata.labels || {}); + }); +}; + /** * Make the bucket listing private. * @@ -1031,6 +1137,41 @@ Bucket.prototype.makePublic = function(options, callback) { } }; +/** + * Set labels on the bucket. + * + * This makes an underlying call to {module:storage/bucket#setMetadata}, which + * is a PATCH request. This means an individual label can be overwritten, but + * unmentioned labels will not be touched. + * + * @param {type} labels - Labels to set on the bucket. + * @param {function} callback - The callback function. + * @param {?error} callback.err - An error returned while making this request. + * @param {object} callback.metadata - The bucket's metadata. + * + * @example + * var labels = { + * labelone: 'labelonevalue', + * labeltwo: 'labeltwovalue' + * }; + * + * bucket.setLabels(labels, function(err, metadata) { + * if (!err) { + * // Labels set successfully. + * } + * }); + * + * //- + * // If the callback is omitted, we'll return a Promise. + * //- + * bucket.setLabels(labels).then(function(data) { + * var metadata = data[0]; + * }); + */ +Bucket.prototype.setLabels = function(labels, callback) { + this.setMetadata({labels}, callback); +}; + /** * Upload a file to the bucket. This is a convenience method that wraps * {module:storage/file#createWriteStream}. diff --git a/packages/storage/system-test/storage.js b/packages/storage/system-test/storage.js index 60ee65ca43c..5ce7ba7683a 100644 --- a/packages/storage/system-test/storage.js +++ b/packages/storage/system-test/storage.js @@ -19,6 +19,7 @@ var assert = require('assert'); var async = require('async'); var crypto = require('crypto'); +var extend = require('extend'); var fs = require('fs'); var normalizeNewline = require('normalize-newline'); var path = require('path'); @@ -629,6 +630,89 @@ describe('storage', function() { done(); }); }); + + describe('labels', function() { + var LABELS = { + label: 'labelvalue', // no caps or spaces allowed (?) + labeltwo: 'labelvaluetwo' + }; + + beforeEach(function(done) { + bucket.deleteLabels(done); + }); + + it('should set labels', function(done) { + bucket.setLabels(LABELS, function(err) { + assert.ifError(err); + + bucket.getLabels(function(err, labels) { + assert.ifError(err); + assert.deepStrictEqual(labels, LABELS); + done(); + }); + }); + }); + + it('should update labels', function(done) { + var newLabels = { + siblinglabel: 'labelvalue' + }; + + bucket.setLabels(LABELS, function(err) { + assert.ifError(err); + + bucket.setLabels(newLabels, function(err) { + assert.ifError(err); + + bucket.getLabels(function(err, labels) { + assert.ifError(err); + assert.deepStrictEqual(labels, extend({}, LABELS, newLabels)); + done(); + }); + }); + }); + }); + + it('should delete a single label', function(done) { + if (LABELS.length <= 1) { + done(new Error('Maintainer Error: `LABELS` needs 2 labels.')); + return; + } + + var labelKeyToDelete = Object.keys(LABELS)[0]; + + bucket.setLabels(LABELS, function(err) { + assert.ifError(err); + + bucket.deleteLabels(labelKeyToDelete, function(err) { + assert.ifError(err); + + bucket.getLabels(function(err, labels) { + assert.ifError(err); + + var expectedLabels = extend({}, LABELS); + delete expectedLabels[labelKeyToDelete]; + + assert.deepStrictEqual(labels, expectedLabels); + + done(); + }); + }); + }); + }); + + it('should delete all labels', function(done) { + bucket.deleteLabels(function(err) { + assert.ifError(err); + + bucket.getLabels(function(err, labels) { + assert.ifError(err); + assert.deepStrictEqual(labels, {}); + done(); + }); + }); + }); + }); }); // RE: https://github.com/GoogleCloudPlatform/google-cloud-node/issues/2224 diff --git a/packages/storage/test/bucket.js b/packages/storage/test/bucket.js index bfa62001667..378c4749145 100644 --- a/packages/storage/test/bucket.js +++ b/packages/storage/test/bucket.js @@ -625,6 +625,86 @@ describe('Bucket', function() { }); }); + describe('deleteLabels', function() { + describe('all labels', function() { + it('should get all of the label names', function(done) { + bucket.getLabels = function() { + done(); + }; + + bucket.deleteLabels(assert.ifError); + }); + + it('should return an error from getLabels()', function(done) { + var error = new Error('Error.'); + + bucket.getLabels = function(callback) { + callback(error); + }; + + bucket.deleteLabels(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should call setLabels with all label names', function(done) { + var labels = { + labelone: 'labelonevalue', + labeltwo: 'labeltwovalue' + }; + + bucket.getLabels = function(callback) { + callback(null, labels); + }; + + bucket.setLabels = function(labels, callback) { + assert.deepStrictEqual(labels, { + labelone: null, + labeltwo: null + }); + callback(); // done() + }; + + bucket.deleteLabels(done); + }); + }); + + describe('single label', function() { + var LABEL = 'labelname'; + + it('should call setLabels with a single label', function(done) { + bucket.setLabels = function(labels, callback) { + assert.deepStrictEqual(labels, { + [LABEL]: null + }); + callback(); // done() + }; + + bucket.deleteLabels(LABEL, done); + }); + }); + + describe('multiple labels', function() { + var LABELS = [ + 'labelonename', + 'labeltwoname' + ]; + + it('should call setLabels with multiple labels', function(done) { + bucket.setLabels = function(labels, callback) { + assert.deepStrictEqual(labels, { + labelonename: null, + labeltwoname: null + }); + callback(); // done() + }; + + bucket.deleteLabels(LABELS, done); + }); + }); + }); + describe('file', function() { var FILE_NAME = 'remote-file-name.jpg'; var file; @@ -774,6 +854,61 @@ describe('Bucket', function() { }); }); + describe('getLabels', function() { + it('should refresh metadata', function(done) { + bucket.getMetadata = function() { + done(); + }; + + bucket.getLabels(assert.ifError); + }); + + it('should return error from getMetadata', function(done) { + var error = new Error('Error.'); + + bucket.getMetadata = function(callback) { + callback(error); + }; + + bucket.getLabels(function(err) { + assert.strictEqual(err, error); + done(); + }); + }); + + it('should return labels metadata property', function(done) { + var metadata = { + labels: { + label: 'labelvalue' + } + }; + + bucket.getMetadata = function(callback) { + callback(null, metadata); + }; + + bucket.getLabels(function(err, labels) { + assert.ifError(err); + assert.strictEqual(labels, metadata.labels); + done(); + }); + }); + + it('should return empty object if no labels exist', function(done) { + var metadata = {}; + + bucket.getMetadata = function(callback) { + callback(null, metadata); + }; + + bucket.getLabels(function(err, labels) { + assert.ifError(err); + assert.deepStrictEqual(labels, {}); + done(); + }); + }); + }); + describe('makePrivate', function() { it('should set predefinedAcl & privatize files', function(done) { var didSetPredefinedAcl = false; @@ -906,6 +1041,19 @@ describe('Bucket', function() { }); }); + describe('setLabels', function() { + it('should correctly call setMetadata', function(done) { + var labels = {}; + + bucket.setMetadata = function(metadata, callback) { + assert.strictEqual(metadata.labels, labels); + callback(); // done() + }; + + bucket.setLabels(labels, done); + }); + }); + describe('upload', function() { var basename = 'testfile.json'; var filepath = path.join(__dirname, 'testdata/' + basename);