Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit ca1b4b7

Browse files
shaffeeullahd-googrelease-please[bot]ddelgrosso1
authored
samples: storage.objects.insert precondition samples (#2047)
* test: add retries (#2039) * refactor: remove unused `restart` private method (#2038) Co-authored-by: Sameena Shaffeeullah <shaffeeullah@google.com> * fix: Retry `EPIPE` Connection Errors + Attempt Retries in Resumable Upload Connection Errors (#2040) * feat: Add `epipe` as retryable error * fix: capture and retry potential connection errors * test: Add tests and remove logs * test: set `retryOptions` by copy rather than reference * fix: grammar * chore(main): release 6.4.1 (#2036) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> * test: add delay to bucket tests to reduce rate limiting errors (#2043) * samples: storage.objects.insert precondition samples * updated test Co-authored-by: Daniel Bankhead <danielbankhead@google.com> Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: Denis DelGrosso <85250797+ddelgrosso1@users.noreply.github.com>
1 parent 49fa4d5 commit ca1b4b7

14 files changed

Lines changed: 160 additions & 105 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
[1]: https://www.npmjs.com/package/@google-cloud/storage?activeTab=versions
66

7+
## [6.4.1](https://github.com/googleapis/nodejs-storage/compare/v6.4.0...v6.4.1) (2022-08-12)
8+
9+
10+
### Bug Fixes
11+
12+
* Remove `pumpify` ([#2029](https://github.com/googleapis/nodejs-storage/issues/2029)) ([edc1d64](https://github.com/googleapis/nodejs-storage/commit/edc1d64069a6038c301c3b775f116fbf69b10b28))
13+
* Retry `EPIPE` Connection Errors + Attempt Retries in Resumable Upload Connection Errors ([#2040](https://github.com/googleapis/nodejs-storage/issues/2040)) ([ba35321](https://github.com/googleapis/nodejs-storage/commit/ba35321c3fef9a1874ff8fbea43885e2e7746b4f))
14+
715
## [6.4.0](https://github.com/googleapis/nodejs-storage/compare/v6.3.0...v6.4.0) (2022-08-10)
816

917

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@google-cloud/storage",
33
"description": "Cloud Storage Client Library for Node.js",
4-
"version": "6.4.0",
4+
"version": "6.4.1",
55
"license": "Apache-2.0",
66
"author": "Google Inc.",
77
"engines": {

samples/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
},
1818
"dependencies": {
1919
"@google-cloud/pubsub": "^3.0.0",
20-
"@google-cloud/storage": "^6.4.0",
20+
"@google-cloud/storage": "^6.4.1",
2121
"node-fetch": "^2.6.7",
2222
"uuid": "^8.0.0",
2323
"yargs": "^16.0.0"

samples/system-test/encryption.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const kmsKeyName = process.env.GOOGLE_CLOUD_KMS_KEY_US;
3434
const fileName = 'test.txt';
3535
const filePath = path.join(__dirname, '../resources', fileName);
3636
const downloadFilePath = path.join(__dirname, '../resources/downloaded.txt');
37+
const doesNotExistPrecondition = 0;
3738

3839
const key = crypto.randomBytes(32).toString('base64');
3940

@@ -56,7 +57,7 @@ it('should generate a key', () => {
5657

5758
it('should upload a file', async () => {
5859
const output = execSync(
59-
`node uploadEncryptedFile.js ${bucketName} ${filePath} ${fileName} ${key}`
60+
`node uploadEncryptedFile.js ${bucketName} ${filePath} ${fileName} ${key} ${doesNotExistPrecondition}`
6061
);
6162
assert.match(
6263
output,

samples/system-test/files.test.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ describe('file', () => {
6262

6363
it('should upload a file', async () => {
6464
const output = execSync(
65-
`node uploadFile.js ${bucketName} ${filePath} ${fileName}`
65+
`node uploadFile.js ${bucketName} ${filePath} ${fileName} ${doesNotExistPrecondition}`
6666
);
6767
assert.match(output, new RegExp(`${filePath} uploaded to ${bucketName}`));
6868
const [exists] = await bucket.file(fileName).exists();
@@ -85,7 +85,7 @@ describe('file', () => {
8585

8686
it('should upload a file without authentication', async () => {
8787
const output = execSync(
88-
`node uploadWithoutAuthentication.js ${bucketName} ${fileContents} ${fileName}`
88+
`node uploadWithoutAuthentication.js ${bucketName} ${fileContents} ${fileName} ${doesNotExistPrecondition}`
8989
);
9090
assert.match(output, new RegExp(`${fileName} uploaded to ${bucketName}`));
9191
const [exists] = await bucket.file(fileName).exists();
@@ -111,8 +111,9 @@ describe('file', () => {
111111
});
112112

113113
it('should upload a file with a kms key', async () => {
114+
const [metadata] = await bucket.file(fileName).getMetadata();
114115
const output = execSync(
115-
`node uploadFileWithKmsKey.js ${bucketName} ${filePath} ${kmsKeyName}`
116+
`node uploadFileWithKmsKey.js ${bucketName} ${filePath} ${kmsKeyName} ${metadata.generation}`
116117
);
117118
assert.include(
118119
output,

samples/uploadEncryptedFile.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ function main(
1717
bucketName = 'my-bucket',
1818
filePath = path.join(__dirname, '../resources', 'test.txt'),
1919
destFileName = 'test.txt',
20-
key = process.env.GOOGLE_CLOUD_KMS_KEY_US
20+
key = process.env.GOOGLE_CLOUD_KMS_KEY_US,
21+
generationMatchPrecondition = 0
2122
) {
2223
// [START storage_upload_encrypted_file]
2324
/**
@@ -45,6 +46,15 @@ function main(
4546
const options = {
4647
destination: destFileName,
4748
encryptionKey: Buffer.from(key, 'base64'),
49+
50+
// Optional:
51+
// Set a generation-match precondition to avoid potential race conditions
52+
// and data corruptions. The request to upload is aborted if the object's
53+
// generation number does not match your precondition. For a destination
54+
// object that does not yet exist, set the ifGenerationMatch precondition to 0
55+
// If the destination object already exists in your bucket, set instead a
56+
// generation-match precondition using its generation number.
57+
preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
4858
};
4959

5060
await storage.bucket(bucketName).upload(filePath, options);

samples/uploadFile.js

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
function main(
1616
bucketName = 'my-bucket',
1717
filePath = './local/path/to/file.txt',
18-
destFileName = 'file.txt'
18+
destFileName = 'file.txt',
19+
generationMatchPrecondition = 0
1920
) {
2021
// [START storage_upload_file]
2122
/**
@@ -37,10 +38,19 @@ function main(
3738
const storage = new Storage();
3839

3940
async function uploadFile() {
40-
await storage.bucket(bucketName).upload(filePath, {
41+
const options = {
4142
destination: destFileName,
42-
});
43-
43+
// Optional:
44+
// Set a generation-match precondition to avoid potential race conditions
45+
// and data corruptions. The request to upload is aborted if the object's
46+
// generation number does not match your precondition. For a destination
47+
// object that does not yet exist, set the ifGenerationMatch precondition to 0
48+
// If the destination object already exists in your bucket, set instead a
49+
// generation-match precondition using its generation number.
50+
preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
51+
};
52+
53+
await storage.bucket(bucketName).upload(filePath, options);
4454
console.log(`${filePath} uploaded to ${bucketName}`);
4555
}
4656

samples/uploadFileWithKmsKey.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
function main(
2424
bucketName = 'my-bucket',
2525
filePath = 'test.txt',
26-
kmsKeyName = process.env.GOOGLE_CLOUD_KMS_KEY_US
26+
kmsKeyName = process.env.GOOGLE_CLOUD_KMS_KEY_US,
27+
generationMatchPrecondition = 0
2728
) {
2829
// [START storage_upload_with_kms_key]
2930
/**
@@ -45,9 +46,19 @@ function main(
4546
const storage = new Storage();
4647

4748
async function uploadFileWithKmsKey() {
48-
await storage.bucket(bucketName).upload(filePath, {
49+
const options = {
4950
kmsKeyName,
50-
});
51+
// Optional:
52+
// Set a generation-match precondition to avoid potential race conditions
53+
// and data corruptions. The request to upload is aborted if the object's
54+
// generation number does not match your precondition. For a destination
55+
// object that does not yet exist, set the ifGenerationMatch precondition to 0
56+
// If the destination object already exists in your bucket, set instead a
57+
// generation-match precondition using its generation number.
58+
preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
59+
};
60+
61+
await storage.bucket(bucketName).upload(filePath, options);
5162

5263
console.log(`${filePath} uploaded to ${bucketName} using ${kmsKeyName}.`);
5364
}

samples/uploadWithoutAuthentication.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
function main(
1616
bucketName = 'my-bucket',
1717
contents = 'these are my file contents',
18-
destFileName = 'file.txt'
18+
destFileName = 'file.txt',
19+
generationMatchPrecondition = 0
1920
) {
2021
// [START storage_upload_without_authentication]
2122
/**
@@ -43,13 +44,24 @@ function main(
4344
// you can make requests without credentials.
4445
const [location] = await file.createResumableUpload(); //auth required
4546

46-
// Passes the location to file.save so you don't need to
47-
// authenticate this call
48-
await file.save(contents, {
47+
const options = {
4948
uri: location,
5049
resumable: true,
5150
validation: false,
52-
});
51+
52+
// Optional:
53+
// Set a generation-match precondition to avoid potential race conditions
54+
// and data corruptions. The request to upload is aborted if the object's
55+
// generation number does not match your precondition. For a destination
56+
// object that does not yet exist, set the ifGenerationMatch precondition to 0
57+
// If the destination object already exists in your bucket, set instead a
58+
// generation-match precondition using its generation number.
59+
preconditionOpts: {ifGenerationMatch: generationMatchPrecondition},
60+
};
61+
62+
// Passes the location to file.save so you don't need to
63+
// authenticate this call
64+
await file.save(contents, options);
5365

5466
console.log(`${destFileName} uploaded to ${bucketName}`);
5567
}

src/resumable-upload.ts

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -742,9 +742,18 @@ export class Upload extends Writable {
742742
responseReceived = true;
743743
this.responseHandler(resp);
744744
}
745-
} catch (err) {
746-
const e = err as Error;
747-
this.destroy(e);
745+
} catch (e) {
746+
const err = e as ApiError;
747+
748+
if (this.retryOptions.retryableErrorFn!(err)) {
749+
this.attemptDelayedRetry({
750+
status: NaN,
751+
data: err,
752+
});
753+
return;
754+
}
755+
756+
this.destroy(err);
748757
}
749758
}
750759

@@ -833,7 +842,16 @@ export class Upload extends Writable {
833842
}
834843
this.offset = 0;
835844
} catch (e) {
836-
const err = e as GaxiosError;
845+
const err = e as ApiError;
846+
847+
if (this.retryOptions.retryableErrorFn!(err)) {
848+
this.attemptDelayedRetry({
849+
status: NaN,
850+
data: err,
851+
});
852+
return;
853+
}
854+
837855
this.destroy(err);
838856
}
839857
}
@@ -899,25 +917,6 @@ export class Upload extends Writable {
899917
return successfulRequest ? res : null;
900918
}
901919

902-
private restart() {
903-
if (this.numBytesWritten) {
904-
const message =
905-
'Attempting to restart an upload after unrecoverable bytes have been written from upstream. Stopping as this could result in data loss. Initiate a new upload to continue.';
906-
907-
this.emit('error', new RangeError(message));
908-
return;
909-
}
910-
911-
this.lastChunkSent = Buffer.alloc(0);
912-
this.createURI(err => {
913-
if (err) {
914-
return this.destroy(err);
915-
}
916-
this.startUploading();
917-
return;
918-
});
919-
}
920-
921920
/**
922921
* @return {bool} is the request good?
923922
*/
@@ -941,7 +940,7 @@ export class Upload extends Writable {
941940
/**
942941
* @param resp GaxiosResponse object from previous attempt
943942
*/
944-
private attemptDelayedRetry(resp: GaxiosResponse) {
943+
private attemptDelayedRetry(resp: Pick<GaxiosResponse, 'data' | 'status'>) {
945944
if (this.numRetries < this.retryOptions.maxRetries!) {
946945
if (
947946
resp.status === NOT_FOUND_STATUS_CODE &&

0 commit comments

Comments
 (0)