Skip to content

Commit d387283

Browse files
committed
feat(storage): support gs:// copy/move dests
Fixes #1395
1 parent 0a6da60 commit d387283

3 files changed

Lines changed: 90 additions & 4 deletions

File tree

lib/storage/file.js

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ var STORAGE_DOWNLOAD_BASE_URL = 'https://storage.googleapis.com';
7979
*/
8080
var STORAGE_UPLOAD_BASE_URL = 'https://www.googleapis.com/upload/storage/v1/b';
8181

82+
/**
83+
* @const {RegExp}
84+
* @private
85+
*/
86+
var GS_URL_REGEXP = /^gs\:\/\/([a-z0-9_\.\-]+)\/(.+)$/;
87+
8288
/*! Developer Documentation
8389
*
8490
* @param {module:storage/bucket} bucket - The Bucket instance this file is
@@ -284,7 +290,7 @@ nodeutil.inherits(File, ServiceObject);
284290
/**
285291
* Copy this file to another file. By default, this will copy the file to the
286292
* same bucket, but you can choose to copy it to another Bucket by providing
287-
* either a Bucket or File object.
293+
* a Bucket or File object or a URL starting with "gs://".
288294
*
289295
* @resource [Objects: copy API Documentation]{@link https://cloud.google.com/storage/docs/json_api/v1/objects/copy}
290296
*
@@ -321,6 +327,22 @@ nodeutil.inherits(File, ServiceObject);
321327
* });
322328
*
323329
* //-
330+
* // If you pass in a string starting with "gs://" for the destination, the
331+
* // file is copied to the other bucket and under the new name provided.
332+
* //-
333+
* var newLocation = 'gs://another-bucket/my-image-copy.png';
334+
* file.copy(newLocation, function(err, copiedFile, apiResponse) {
335+
* // `my-bucket` still contains:
336+
* // - "my-image.png"
337+
* //
338+
* // `another-bucket` now contains:
339+
* // - "my-image-copy.png"
340+
*
341+
* // `copiedFile` is an instance of a File object that refers to your new
342+
* // file.
343+
* });
344+
*
345+
* //-
324346
* // If you pass in a Bucket object, the file will be copied to that bucket
325347
* // using the same name.
326348
* //-
@@ -366,8 +388,14 @@ File.prototype.copy = function(destination, callback) {
366388
var newFile;
367389

368390
if (is.string(destination)) {
369-
destBucket = this.bucket;
370-
destName = destination;
391+
var parsedDestination = GS_URL_REGEXP.exec(destination);
392+
if (parsedDestination !== null && parsedDestination.length === 3) {
393+
destBucket = this.storage.bucket(parsedDestination[1]);
394+
destName = parsedDestination[2];
395+
} else {
396+
destBucket = this.bucket;
397+
destName = destination;
398+
}
371399
} else if (destination.constructor &&
372400
destination.constructor.name === 'Bucket') {
373401
destBucket = destination;
@@ -1467,7 +1495,7 @@ File.prototype.makePublic = function(callback) {
14671495
/**
14681496
* Move this file to another location. By default, this will move the file to
14691497
* the same bucket, but you can choose to move it to another Bucket by providing
1470-
* either a Bucket or File object.
1498+
* a Bucket or File object or a URL beginning with "gs://".
14711499
*
14721500
* **Warning**:
14731501
* There is currently no atomic `move` method in the Google Cloud Storage API,
@@ -1513,6 +1541,22 @@ File.prototype.makePublic = function(callback) {
15131541
* });
15141542
*
15151543
* //-
1544+
* // If you pass in a string starting with "gs://" for the destination, the
1545+
* // file is copied to the other bucket and under the new name provided.
1546+
* //-
1547+
* var newLocation = 'gs://another-bucket/my-image-new.png';
1548+
* file.move(newLocation, function(err, destinationFile, apiResponse) {
1549+
* // `my-bucket` no longer contains:
1550+
* // - "my-image.png"
1551+
* //
1552+
* // `another-bucket` now contains:
1553+
* // - "my-image-new.png"
1554+
*
1555+
* // `destinationFile` is an instance of a File object that refers to your
1556+
* // new file.
1557+
* });
1558+
*
1559+
* //-
15161560
* // If you pass in a Bucket object, the file will be moved to that bucket
15171561
* // using the same name.
15181562
* //-

system-test/storage.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,13 +849,42 @@ describe('storage', function() {
849849
assert.ifError(err);
850850

851851
file.copy('CloudLogoCopy', function(err, copiedFile) {
852+
assert.ifError(err);
852853
async.parallel([
853854
file.delete.bind(file),
854855
copiedFile.delete.bind(copiedFile)
855856
], done);
856857
});
857858
});
858859
});
860+
861+
it('should copy to another bucket given a gs:// URL', function(done) {
862+
var opts = { destination: 'CloudLogo' };
863+
bucket.upload(FILES.logo.path, opts, function(err, file) {
864+
assert.ifError(err);
865+
866+
var otherBucket = storage.bucket(generateName());
867+
otherBucket.create(function(err) {
868+
assert.ifError(err);
869+
870+
var destPath = 'gs://' + otherBucket.name + '/CloudLogoCopy';
871+
file.copy(destPath, function(err) {
872+
assert.ifError(err);
873+
874+
otherBucket.getFiles(function(err, files) {
875+
assert.ifError(err);
876+
877+
assert.strictEqual(files.length, 1);
878+
var newFile = files[0];
879+
880+
assert.strictEqual(newFile.name, 'CloudLogoCopy');
881+
882+
done();
883+
});
884+
});
885+
});
886+
});
887+
});
859888
});
860889

861890
describe('channels', function() {

test/storage/file.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ describe('File', function() {
125125
} else {
126126
return (requestOverride || requestCached)(req);
127127
}
128+
},
129+
bucket: function(name) {
130+
return new Bucket(this, name);
128131
}
129132
};
130133

@@ -311,6 +314,16 @@ describe('File', function() {
311314
file.copy(newFileName);
312315
});
313316

317+
it('should allow a "gs://..." string', function(done) {
318+
var newFileName = 'gs://other-bucket/new-file-name.png';
319+
var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
320+
destBucket: 'other-bucket',
321+
destName: 'new-file-name.png'
322+
});
323+
assertPathEquals(file, expectedPath, done);
324+
file.copy(newFileName);
325+
});
326+
314327
it('should allow a Bucket', function(done) {
315328
var expectedPath = format('/copyTo/b/{destBucket}/o/{destName}', {
316329
destBucket: BUCKET.name,

0 commit comments

Comments
 (0)