From ab89a665154432cab36ab83b7c465388d1f27a66 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:30:10 -0400 Subject: [PATCH 01/18] chore(main): release 2.58.2-SNAPSHOT (#3331) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> --- gapic-google-cloud-storage-v2/pom.xml | 4 ++-- google-cloud-storage-bom/pom.xml | 16 ++++++++-------- google-cloud-storage-control/pom.xml | 4 ++-- google-cloud-storage/pom.xml | 4 ++-- grpc-google-cloud-storage-control-v2/pom.xml | 4 ++-- grpc-google-cloud-storage-v2/pom.xml | 4 ++-- pom.xml | 16 ++++++++-------- proto-google-cloud-storage-control-v2/pom.xml | 4 ++-- proto-google-cloud-storage-v2/pom.xml | 4 ++-- samples/snapshot/pom.xml | 6 +++--- storage-shared-benchmarking/pom.xml | 4 ++-- versions.txt | 14 +++++++------- 12 files changed, 42 insertions(+), 42 deletions(-) diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml index 4443ec3d3..bebf7a572 100644 --- a/gapic-google-cloud-storage-v2/pom.xml +++ b/gapic-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index d1c5674a8..286175ffc 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.google.cloud google-cloud-storage-bom - 2.58.1 + 2.58.2-SNAPSHOT pom com.google.cloud @@ -69,37 +69,37 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml index 856d03291..36fa826cf 100644 --- a/google-cloud-storage-control/pom.xml +++ b/google-cloud-storage-control/pom.xml @@ -5,13 +5,13 @@ 4.0.0 com.google.cloud google-cloud-storage-control - 2.58.1 + 2.58.2-SNAPSHOT google-cloud-storage-control GRPC library for google-cloud-storage-control com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 170b8b17e..c4ff68a86 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.58.1 + 2.58.2-SNAPSHOT jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT google-cloud-storage diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml index 42534e179..a1d12db85 100644 --- a/grpc-google-cloud-storage-control-v2/pom.xml +++ b/grpc-google-cloud-storage-control-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.58.1 + 2.58.2-SNAPSHOT grpc-google-cloud-storage-control-v2 GRPC library for google-cloud-storage com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index b72149d67..67fa24cff 100644 --- a/grpc-google-cloud-storage-v2/pom.xml +++ b/grpc-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/pom.xml b/pom.xml index a8c81db55..fa0d915d3 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.58.1 + 2.58.2-SNAPSHOT Storage Parent https://github.com/googleapis/java-storage @@ -82,7 +82,7 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.58.2-SNAPSHOT com.google.apis @@ -104,32 +104,32 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc gapic-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.58.1 + 2.58.2-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.58.1 + 2.58.2-SNAPSHOT com.google.cloud diff --git a/proto-google-cloud-storage-control-v2/pom.xml b/proto-google-cloud-storage-control-v2/pom.xml index 6f868e12a..2038a1286 100644 --- a/proto-google-cloud-storage-control-v2/pom.xml +++ b/proto-google-cloud-storage-control-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.58.1 + 2.58.2-SNAPSHOT proto-google-cloud-storage-control-v2 Proto library for proto-google-cloud-storage-control-v2 com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index 85530d5cd..b7630f746 100644 --- a/proto-google-cloud-storage-v2/pom.xml +++ b/proto-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.58.1 + 2.58.2-SNAPSHOT proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index e21f90590..87150d879 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,12 +28,12 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.58.2-SNAPSHOT com.google.cloud google-cloud-storage-control - 2.58.1 + 2.58.2-SNAPSHOT compile @@ -70,7 +70,7 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.58.2-SNAPSHOT tests test diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml index 134504016..0f68aa834 100644 --- a/storage-shared-benchmarking/pom.xml +++ b/storage-shared-benchmarking/pom.xml @@ -10,7 +10,7 @@ com.google.cloud google-cloud-storage-parent - 2.58.1 + 2.58.2-SNAPSHOT @@ -31,7 +31,7 @@ com.google.cloud google-cloud-storage - 2.58.1 + 2.58.2-SNAPSHOT tests diff --git a/versions.txt b/versions.txt index 1f8f3d6f0..353146a92 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.58.1:2.58.1 -gapic-google-cloud-storage-v2:2.58.1:2.58.1 -grpc-google-cloud-storage-v2:2.58.1:2.58.1 -proto-google-cloud-storage-v2:2.58.1:2.58.1 -google-cloud-storage-control:2.58.1:2.58.1 -proto-google-cloud-storage-control-v2:2.58.1:2.58.1 -grpc-google-cloud-storage-control-v2:2.58.1:2.58.1 +google-cloud-storage:2.58.1:2.58.2-SNAPSHOT +gapic-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT +grpc-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT +proto-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT +google-cloud-storage-control:2.58.1:2.58.2-SNAPSHOT +proto-google-cloud-storage-control-v2:2.58.1:2.58.2-SNAPSHOT +grpc-google-cloud-storage-control-v2:2.58.1:2.58.2-SNAPSHOT From 64e2b2ef839e69da0605b9e53989c1f5a2b09e66 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 7 Oct 2025 11:39:48 -0400 Subject: [PATCH 02/18] fix: update grpc single-shot uploads to attach the callers stracktrace as suppressed exception if an error happens in the background (#3330) --- .../storage/GapicUnbufferedDirectWritableByteChannel.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java index aa6bcacec..95f6472ac 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedDirectWritableByteChannel.java @@ -273,11 +273,14 @@ void await() { try { invocationHandle.get(); } catch (InterruptedException | ExecutionException e) { + RuntimeException runtimeException; if (e.getCause() instanceof RuntimeException) { - throw (RuntimeException) e.getCause(); + runtimeException = (RuntimeException) e.getCause(); } else { - throw new RuntimeException(e); + runtimeException = new RuntimeException(e); } + runtimeException.addSuppressed(new AsyncStorageTaskException()); + throw runtimeException; } } } From 2698d43481f9ce7a9663356d536791903c21b793 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 7 Oct 2025 11:40:15 -0400 Subject: [PATCH 03/18] test: add preconditions to several tests so they can be retried (#3326) --- .../google/cloud/storage/it/ITAccessTest.java | 23 +- .../google/cloud/storage/it/ITBucketTest.java | 100 ++----- .../google/cloud/storage/it/ITObjectTest.java | 268 ++++++------------ 3 files changed, 125 insertions(+), 266 deletions(-) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java index 2b5e7b9e7..04d74ebcf 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITAccessTest.java @@ -385,12 +385,18 @@ public void testEnableAndDisableUniformBucketLevelAccessOnExistingBucket() throw BucketTargetOption.metagenerationMatch()); Bucket remoteBucket = - storage.get(bpoBucket, Storage.BucketGetOption.fields(BucketField.IAMCONFIGURATION)); + storage.get( + bpoBucket, + Storage.BucketGetOption.fields( + BucketField.IAMCONFIGURATION, BucketField.METAGENERATION)); assertTrue(remoteBucket.getIamConfiguration().isUniformBucketLevelAccessEnabled()); assertNotNull(remoteBucket.getIamConfiguration().getUniformBucketLevelAccessLockedTime()); - remoteBucket.toBuilder().setIamConfiguration(ublaDisabledIamConfiguration).build().update(); + remoteBucket.toBuilder() + .setIamConfiguration(ublaDisabledIamConfiguration) + .build() + .update(BucketTargetOption.metagenerationMatch()); remoteBucket = storage.get( @@ -600,7 +606,10 @@ public void testRetentionPolicyNoLock() throws Exception { assertThat(remoteBucket.retentionPolicyIsLocked()).isAnyOf(null, false); Bucket remoteBucket2 = - storage.get(bucketName, Storage.BucketGetOption.fields(BucketField.RETENTION_POLICY)); + storage.get( + bucketName, + Storage.BucketGetOption.fields( + BucketField.RETENTION_POLICY, BucketField.METAGENERATION)); assertEquals(RETENTION_PERIOD, remoteBucket2.getRetentionPeriod()); assertThat(remoteBucket2.getRetentionPeriodDuration()).isEqualTo(RETENTION_PERIOD_DURATION); assertNotNull(remoteBucket2.getRetentionEffectiveTime()); @@ -611,7 +620,11 @@ public void testRetentionPolicyNoLock() throws Exception { Blob remoteBlob = storage.create(blobInfo); assertNotNull(remoteBlob.getRetentionExpirationTime()); - Bucket remoteBucket3 = remoteBucket2.toBuilder().setRetentionPeriod(null).build().update(); + Bucket remoteBucket3 = + remoteBucket2.toBuilder() + .setRetentionPeriod(null) + .build() + .update(BucketTargetOption.metagenerationMatch()); assertNull(remoteBucket3.getRetentionPeriod()); } } @@ -653,7 +666,7 @@ public void testEnableAndDisableBucketPolicyOnlyOnExistingBucket() throws Except .setIamConfiguration( bpoEnabledIamConfiguration.toBuilder().setIsBucketPolicyOnlyEnabled(false).build()) .build() - .update(); + .update(BucketTargetOption.metagenerationMatch()); remoteBucket = storage.get( diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java index 378f1bd45..7cf2f7f4e 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITBucketTest.java @@ -17,7 +17,6 @@ package com.google.cloud.storage.it; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -40,6 +39,7 @@ import com.google.cloud.storage.Rpo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.Storage.BlobField; +import com.google.cloud.storage.Storage.BlobTargetOption; import com.google.cloud.storage.Storage.BucketField; import com.google.cloud.storage.Storage.BucketGetOption; import com.google.cloud.storage.Storage.BucketListOption; @@ -62,12 +62,10 @@ import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.StreamSupport; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -321,54 +319,6 @@ private void retentionPolicyLockRequesterPays(boolean requesterPays) { } } - @Test - // Bug in UpdateMask - @CrossRun.Exclude(transports = Transport.GRPC) - @Ignore("Make hermetic, currently mutates global bucket") - public void testUpdateBucketLabel() { - Bucket remoteBucket = - storage.get( - bucket.getName(), Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING)); - assertNull(remoteBucket.getLabels()); - remoteBucket = remoteBucket.toBuilder().setLabels(BUCKET_LABELS).build(); - Bucket updatedBucket = storage.update(remoteBucket); - assertEquals(BUCKET_LABELS, updatedBucket.getLabels()); - remoteBucket.toBuilder().setLabels(Collections.emptyMap()).build().update(); - assertNull(storage.get(bucket.getName()).getLabels()); - } - - @Test - @CrossRun.Exclude(transports = Transport.GRPC) - @Ignore("Make hermetic, currently mutates global bucket") - public void testUpdateBucketRequesterPays() { - // Bug in UpdateMask - unsetRequesterPays(); - Bucket remoteBucket = - storage.get( - requesterPaysBucket.getName(), - Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING)); - assertTrue(remoteBucket.requesterPays() == null || !remoteBucket.requesterPays()); - remoteBucket = remoteBucket.toBuilder().setRequesterPays(true).build(); - Bucket updatedBucket = storage.update(remoteBucket); - assertTrue(updatedBucket.requesterPays()); - - String projectId = storage.getOptions().getProjectId(); - Bucket.BlobTargetOption option = Bucket.BlobTargetOption.userProject(projectId); - String blobName = "test-create-empty-blob-requester-pays"; - Blob remoteBlob = updatedBucket.create(blobName, BLOB_BYTE_CONTENT, option); - assertNotNull(remoteBlob); - byte[] readBytes = - storage.readAllBytes( - requesterPaysBucket.getName(), - blobName, - Storage.BlobSourceOption.userProject(projectId)); - assertArrayEquals(BLOB_BYTE_CONTENT, readBytes); - remoteBucket = remoteBucket.toBuilder().setRequesterPays(false).build(); - updatedBucket = storage.update(remoteBucket, Storage.BucketTargetOption.userProject(projectId)); - - assertFalse(updatedBucket.requesterPays()); - } - @Test public void testEnableDisableBucketDefaultEventBasedHold() { String bucketName = generator.randomBucketName(); @@ -378,19 +328,30 @@ public void testEnableDisableBucketDefaultEventBasedHold() { assertTrue(remoteBucket.getDefaultEventBasedHold()); remoteBucket = storage.get( - bucketName, Storage.BucketGetOption.fields(BucketField.DEFAULT_EVENT_BASED_HOLD)); + bucketName, + Storage.BucketGetOption.fields( + BucketField.DEFAULT_EVENT_BASED_HOLD, BucketField.METAGENERATION)); assertTrue(remoteBucket.getDefaultEventBasedHold()); String blobName = generator.randomObjectName(); BlobInfo blobInfo = BlobInfo.newBuilder(bucketName, blobName).build(); - Blob remoteBlob = storage.create(blobInfo); + Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist()); assertTrue(remoteBlob.getEventBasedHold()); remoteBlob = storage.get( - blobInfo.getBlobId(), Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD)); + blobInfo.getBlobId(), + Storage.BlobGetOption.fields(BlobField.EVENT_BASED_HOLD, BlobField.METAGENERATION)); assertTrue(remoteBlob.getEventBasedHold()); - remoteBlob = remoteBlob.toBuilder().setEventBasedHold(false).build().update(); + remoteBlob = + remoteBlob.toBuilder() + .setEventBasedHold(false) + .build() + .update(BlobTargetOption.metagenerationMatch()); assertFalse(remoteBlob.getEventBasedHold()); - remoteBucket = remoteBucket.toBuilder().setDefaultEventBasedHold(false).build().update(); + remoteBucket = + remoteBucket.toBuilder() + .setDefaultEventBasedHold(false) + .build() + .update(BucketTargetOption.metagenerationMatch()); assertFalse(remoteBucket.getDefaultEventBasedHold()); } finally { BucketCleaner.doCleanup(bucketName, storage); @@ -465,7 +426,9 @@ public void testObjectRetention() { .setRetainUntilTime(now.plusHours(1)) .build()) .build() - .update(Storage.BlobTargetOption.overrideUnlockedRetention(true)); + .update( + Storage.BlobTargetOption.overrideUnlockedRetention(true), + BlobTargetOption.metagenerationMatch()); remoteBlob = storage.get(bucketName, "retentionObject"); assertEquals( @@ -480,7 +443,9 @@ public void testObjectRetention() { remoteBlob.toBuilder() .setRetention(null) .build() - .update(Storage.BlobTargetOption.overrideUnlockedRetention(true)); + .update( + Storage.BlobTargetOption.overrideUnlockedRetention(true), + BlobTargetOption.metagenerationMatch()); remoteBlob = storage.get(bucketName, "retentionObject"); assertNull(remoteBlob.getRetention()); @@ -545,7 +510,7 @@ public void testUpdateBucket_noModification() throws Exception { storage.get( bucket.getName(), BucketGetOption.metagenerationMatch(bucket.getMetageneration())); - Bucket gen2 = storage.update(gen1); + Bucket gen2 = storage.update(gen1, BucketTargetOption.metagenerationMatch()); assertThat(gen2).isEqualTo(gen1); } } @@ -606,7 +571,7 @@ public void testSoftDeletePolicy() { .setRetentionDuration(Duration.ofDays(20)) .build()) .build() - .update(); + .update(BucketTargetOption.metagenerationMatch()); assertEquals( Duration.ofDays(20), @@ -688,19 +653,4 @@ public void testListObjectsWithFolders() throws Exception { BucketCleaner.doCleanup(bucketName, storage); } } - - private void unsetRequesterPays() { - Bucket remoteBucket = - storage.get( - requesterPaysBucket.getName(), - Storage.BucketGetOption.fields(BucketField.ID, BucketField.BILLING), - Storage.BucketGetOption.userProject(storage.getOptions().getProjectId())); - // Disable requester pays in case a test fails to clean up. - if (remoteBucket.requesterPays() != null && remoteBucket.requesterPays() == true) { - remoteBucket.toBuilder() - .setRequesterPays(false) - .build() - .update(Storage.BucketTargetOption.userProject(storage.getOptions().getProjectId())); - } - } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java index 3b3c3e7dc..dc2797616 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITObjectTest.java @@ -84,7 +84,6 @@ import java.nio.file.Paths; import java.security.Key; import java.util.Arrays; -import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -146,7 +145,7 @@ public void testCreateBlob() { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setCustomTime(System.currentTimeMillis()).build(); - Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT); + Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, BlobTargetOption.doesNotExist()); assertNotNull(remoteBlob); assertNotNull(remoteBlob.getCustomTime()); assertEquals(blob.getBucket(), remoteBlob.getBucket()); @@ -165,7 +164,7 @@ public void testCreateBlobMd5Crc32cFromHexString() { .setMd5FromHexString("3b54781b51c94835084898e821899585") .setCrc32cFromHexString("f4ddc43d") .build(); - Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT); + Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, BlobTargetOption.doesNotExist()); assertNotNull(remoteBlob); assertEquals(blob.getBucket(), remoteBlob.getBucket()); assertEquals(blob.getName(), remoteBlob.getName()); @@ -180,7 +179,12 @@ public void testCreateBlobMd5Crc32cFromHexString() { public void testCreateGetBlobWithEncryptionKey() { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); - Blob remoteBlob = storage.create(blob, BLOB_BYTE_CONTENT, BlobTargetOption.encryptionKey(KEY)); + Blob remoteBlob = + storage.create( + blob, + BLOB_BYTE_CONTENT, + BlobTargetOption.encryptionKey(KEY), + BlobTargetOption.doesNotExist()); assertNotNull(remoteBlob); assertEquals(blob.getBucket(), remoteBlob.getBucket()); assertEquals(blob.getName(), remoteBlob.getName()); @@ -201,7 +205,7 @@ public void testCreateGetBlobWithEncryptionKey() { public void testCreateEmptyBlob() { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); - Blob remoteBlob = storage.create(blob); + Blob remoteBlob = storage.create(blob, BlobTargetOption.doesNotExist()); assertNotNull(remoteBlob); assertEquals(blob.getBucket(), remoteBlob.getBucket()); assertEquals(blob.getName(), remoteBlob.getName()); @@ -218,7 +222,8 @@ public void testZeroByteFileUpload() throws Exception { File zeroByteFile = File.createTempFile("zerobyte", null); zeroByteFile.deleteOnExit(); - storage.createFrom(blobInfo, Paths.get(zeroByteFile.getAbsolutePath())); + storage.createFrom( + blobInfo, Paths.get(zeroByteFile.getAbsolutePath()), BlobWriteOption.doesNotExist()); byte[] readBytes = storage.readAllBytes(bucket.getName(), blobName); assertArrayEquals(new byte[0], readBytes); @@ -230,7 +235,7 @@ public void testCreateBlobStream() { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setContentType(CONTENT_TYPE).build(); ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8)); - Blob remoteBlob = storage.create(blob, stream); + Blob remoteBlob = storage.create(blob, stream, BlobWriteOption.doesNotExist()); assertNotNull(remoteBlob); assertEquals(blob.getBucket(), remoteBlob.getBucket()); assertEquals(blob.getName(), remoteBlob.getName()); @@ -245,7 +250,9 @@ public void testCreateBlobStreamDisableGzipContent() { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setContentType(CONTENT_TYPE).build(); ByteArrayInputStream stream = new ByteArrayInputStream(BLOB_STRING_CONTENT.getBytes(UTF_8)); - Blob remoteBlob = storage.create(blob, stream, BlobWriteOption.disableGzipContent()); + Blob remoteBlob = + storage.create( + blob, stream, BlobWriteOption.disableGzipContent(), BlobWriteOption.doesNotExist()); assertNotNull(remoteBlob); assertEquals(blob.getBucket(), remoteBlob.getBucket()); assertEquals(blob.getName(), remoteBlob.getName()); @@ -258,7 +265,7 @@ public void testCreateBlobStreamDisableGzipContent() { public void testCreateBlobFail() { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); - Blob remoteBlob = storage.create(blob); + Blob remoteBlob = storage.create(blob, BlobTargetOption.doesNotExist()); assertNotNull(remoteBlob); BlobInfo wrongGenerationBlob = BlobInfo.newBuilder(bucket, blobName, -1L).build(); try { @@ -687,75 +694,15 @@ public void testUpdateBlob() { BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); Blob remoteBlob = storage.create(blob); assertNotNull(remoteBlob); - Blob updatedBlob = remoteBlob.toBuilder().setContentType(CONTENT_TYPE).build().update(); - assertNotNull(updatedBlob); - assertEquals(blob.getName(), updatedBlob.getName()); - assertEquals(blob.getBucket(), updatedBlob.getBucket()); - assertEquals(CONTENT_TYPE, updatedBlob.getContentType()); - } - - @Test - public void testUpdateBlobReplaceMetadata() { - String blobName = generator.randomObjectName(); - ImmutableMap metadata = ImmutableMap.of("k1", "a"); - ImmutableMap newMetadata = ImmutableMap.of("k2", "b"); - BlobInfo blob = - BlobInfo.newBuilder(bucket, blobName) + Blob updatedBlob = + remoteBlob.toBuilder() .setContentType(CONTENT_TYPE) - .setMetadata(metadata) - .build(); - Blob remoteBlob = storage.create(blob); - assertNotNull(remoteBlob); - Blob updatedBlob = remoteBlob.toBuilder().setMetadata(null).build().update(); + .build() + .update(BlobTargetOption.metagenerationMatch()); assertNotNull(updatedBlob); - assertNull(updatedBlob.getMetadata()); - updatedBlob = remoteBlob.toBuilder().setMetadata(newMetadata).build().update(); assertEquals(blob.getName(), updatedBlob.getName()); assertEquals(blob.getBucket(), updatedBlob.getBucket()); - assertEquals(newMetadata, updatedBlob.getMetadata()); - } - - @Test - public void testUpdateBlobMergeMetadata() { - String blobName = generator.randomObjectName(); - ImmutableMap metadata = ImmutableMap.of("k1", "a"); - ImmutableMap newMetadata = ImmutableMap.of("k2", "b"); - ImmutableMap expectedMetadata = ImmutableMap.of("k1", "a", "k2", "b"); - BlobInfo blob = - BlobInfo.newBuilder(bucket, blobName) - .setContentType(CONTENT_TYPE) - .setMetadata(metadata) - .build(); - Blob remoteBlob = storage.create(blob); - assertNotNull(remoteBlob); - Blob updatedBlob = remoteBlob.toBuilder().setMetadata(newMetadata).build().update(); - assertNotNull(updatedBlob); - assertEquals(blob.getName(), updatedBlob.getName()); - assertEquals(blob.getBucket(), updatedBlob.getBucket()); - assertEquals(expectedMetadata, updatedBlob.getMetadata()); - } - - @Test - public void testUpdateBlobUnsetMetadata() { - - String blobName = generator.randomObjectName(); - ImmutableMap metadata = ImmutableMap.of("k1", "a", "k2", "b"); - Map newMetadata = new HashMap<>(); - newMetadata.put("k1", "a"); - newMetadata.put("k2", null); - ImmutableMap expectedMetadata = ImmutableMap.of("k1", "a"); - BlobInfo blob = - BlobInfo.newBuilder(bucket, blobName) - .setContentType(CONTENT_TYPE) - .setMetadata(metadata) - .build(); - Blob remoteBlob = storage.create(blob); - assertNotNull(remoteBlob); - Blob updatedBlob = remoteBlob.toBuilder().setMetadata(newMetadata).build().update(); - assertNotNull(updatedBlob); - assertEquals(blob.getName(), updatedBlob.getName()); - assertEquals(blob.getBucket(), updatedBlob.getBucket()); - assertEquals(expectedMetadata, updatedBlob.getMetadata()); + assertEquals(CONTENT_TYPE, updatedBlob.getContentType()); } @Test @@ -767,7 +714,9 @@ public void testUpdateBlobFail() { BlobInfo wrongGenerationBlob = BlobInfo.newBuilder(bucket, blobName, -1L).setContentType(CONTENT_TYPE).build(); try { - storage.update(wrongGenerationBlob, BlobTargetOption.generationMatch()); + storage.update( + wrongGenerationBlob, + BlobTargetOption.metagenerationMatch(remoteBlob.getMetageneration())); fail("StorageException was expected"); } catch (StorageException ex) { // expected @@ -957,7 +906,8 @@ public void testCopyBlobWithPredefinedAcl() { .setSource(source) .setTarget( BlobId.of(bucket.getName(), targetBlobName), - BlobTargetOption.predefinedAcl(PredefinedAcl.PUBLIC_READ)) + BlobTargetOption.predefinedAcl(PredefinedAcl.PUBLIC_READ), + BlobTargetOption.doesNotExist()) .build(); CopyWriter copyWriter = storage.copy(req); Blob gen1 = copyWriter.getResult(); @@ -992,7 +942,10 @@ public void testCopyBlobWithEncryptionKeys() { CopyRequest req1 = CopyRequest.newBuilder() .setSource(source) - .setTarget(target, BlobTargetOption.encryptionKey(OTHER_BASE64_KEY)) + .setTarget( + target, + BlobTargetOption.encryptionKey(OTHER_BASE64_KEY), + BlobTargetOption.doesNotExist()) .setSourceOptions(BlobSourceOption.decryptionKey(BASE64_KEY)) .build(); CopyWriter copyWriter1 = storage.copy(req1); @@ -1037,7 +990,11 @@ public void testCopyBlobUpdateMetadata() { .setContentType(CONTENT_TYPE) .setMetadata(metadata) .build(); - CopyRequest req = CopyRequest.of(source, target); + CopyRequest req = + CopyRequest.newBuilder() + .setSource(source) + .setTarget(target, BlobTargetOption.doesNotExist()) + .build(); CopyWriter copyWriter = storage.copy(req); Blob gen1 = copyWriter.getResult(); assertEquals(bucket.getName(), gen1.getBucket()); @@ -1049,52 +1006,6 @@ public void testCopyBlobUpdateMetadata() { assertTrue(storage.delete(gen1.getBlobId())); } - @Test - public void testCopyBlobUpdateStorageClass() { - String sourceBlobName = generator.randomObjectName() + "-source"; - BlobId source = BlobId.of(bucket.getName(), sourceBlobName); - BlobInfo sourceInfo = - BlobInfo.newBuilder(source).setStorageClass(StorageClass.STANDARD).build(); - Blob remoteSourceBlob = storage.create(sourceInfo, BLOB_BYTE_CONTENT); - assertNotNull(remoteSourceBlob); - assertEquals(StorageClass.STANDARD, remoteSourceBlob.getStorageClass()); - - String targetBlobName = generator.randomObjectName() + "-target"; - BlobInfo targetInfo = - BlobInfo.newBuilder(bucket, targetBlobName).setStorageClass(StorageClass.COLDLINE).build(); - CopyRequest req = CopyRequest.of(source, targetInfo); - CopyWriter copyWriter = storage.copy(req); - Blob gen1 = copyWriter.getResult(); - assertEquals(bucket.getName(), gen1.getBucket()); - assertEquals(targetBlobName, gen1.getName()); - assertEquals(StorageClass.COLDLINE, gen1.getStorageClass()); - assertTrue(copyWriter.isDone()); - assertTrue(remoteSourceBlob.delete()); - assertTrue(storage.delete(gen1.getBlobId())); - } - - @Test - public void testCopyBlobNoContentType() { - - String sourceBlobName = generator.randomObjectName() + "-source"; - BlobId source = BlobId.of(bucket.getName(), sourceBlobName); - Blob remoteSourceBlob = storage.create(BlobInfo.newBuilder(source).build(), BLOB_BYTE_CONTENT); - assertNotNull(remoteSourceBlob); - String targetBlobName = generator.randomObjectName() + "-target"; - ImmutableMap metadata = ImmutableMap.of("k", "v"); - BlobInfo target = BlobInfo.newBuilder(bucket, targetBlobName).setMetadata(metadata).build(); - CopyRequest req = CopyRequest.of(source, target); - CopyWriter copyWriter = storage.copy(req); - Blob gen1 = copyWriter.getResult(); - assertEquals(bucket.getName(), gen1.getBucket()); - assertEquals(targetBlobName, gen1.getName()); - assertTrue(gen1.getContentType() == null || gen1.getContentType().isEmpty()); - assertEquals(metadata, gen1.getMetadata()); - assertTrue(copyWriter.isDone()); - assertTrue(remoteSourceBlob.delete()); - assertTrue(storage.delete(gen1.getBlobId())); - } - @Test public void testCopyBlobFail() { @@ -1110,7 +1021,7 @@ public void testCopyBlobFail() { CopyRequest.newBuilder() .setSource(bucket.getName(), sourceBlobName) .setSourceOptions(BlobSourceOption.generationMatch(-1L)) - .setTarget(target) + .setTarget(target, BlobTargetOption.doesNotExist()) .build(); try { storage.copy(req); @@ -1137,7 +1048,9 @@ public void testReadAndWriteChannelWithEncryptionKey() throws IOException { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); byte[] stringBytes; - try (WriteChannel writer = storage.writer(blob, BlobWriteOption.encryptionKey(BASE64_KEY))) { + try (WriteChannel writer = + storage.writer( + blob, BlobWriteOption.encryptionKey(BASE64_KEY), BlobWriteOption.doesNotExist())) { stringBytes = BLOB_STRING_CONTENT.getBytes(UTF_8); writer.write(ByteBuffer.wrap(BLOB_BYTE_CONTENT)); writer.write(ByteBuffer.wrap(stringBytes)); @@ -1197,7 +1110,7 @@ private void doTestReadAndWriteChannelsWithSize(int blobSize) throws IOException Random rnd = new Random(); byte[] bytes = new byte[blobSize]; rnd.nextBytes(bytes); - try (WriteChannel writer = storage.writer(blob)) { + try (WriteChannel writer = storage.writer(blob, BlobWriteOption.doesNotExist())) { writer.write(ByteBuffer.wrap(bytes)); } ByteArrayOutputStream output = new ByteArrayOutputStream(); @@ -1217,7 +1130,7 @@ public void testReadAndWriteCaptureChannels() throws IOException { String blobName = generator.randomObjectName(); BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).build(); byte[] stringBytes; - WriteChannel writer = storage.writer(blob); + WriteChannel writer = storage.writer(blob, BlobWriteOption.doesNotExist()); stringBytes = BLOB_STRING_CONTENT.getBytes(UTF_8); writer.write(ByteBuffer.wrap(BLOB_BYTE_CONTENT)); RestorableState writerState = writer.capture(); @@ -1386,12 +1299,18 @@ public void testAttemptObjectDeleteWithRetentionPolicy() public void testEnableDisableTemporaryHold() { String blobName = generator.randomObjectName(); BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).setTemporaryHold(true).build(); - Blob remoteBlob = storage.create(blobInfo); + Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist()); assertTrue(remoteBlob.getTemporaryHold()); remoteBlob = - storage.get(remoteBlob.getBlobId(), BlobGetOption.fields(BlobField.TEMPORARY_HOLD)); + storage.get( + remoteBlob.getBlobId(), + BlobGetOption.fields(BlobField.TEMPORARY_HOLD, BlobField.METAGENERATION)); assertTrue(remoteBlob.getTemporaryHold()); - remoteBlob = remoteBlob.toBuilder().setTemporaryHold(false).build().update(); + remoteBlob = + remoteBlob.toBuilder() + .setTemporaryHold(false) + .build() + .update(BlobTargetOption.metagenerationMatch()); assertFalse(remoteBlob.getTemporaryHold()); } @@ -1399,7 +1318,7 @@ public void testEnableDisableTemporaryHold() { public void testAttemptObjectDeleteWithEventBasedHold() { String blobName = generator.randomObjectName(); BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).setEventBasedHold(true).build(); - Blob remoteBlob = storage.create(blobInfo); + Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist()); assertTrue(remoteBlob.getEventBasedHold()); try { remoteBlob.delete(); @@ -1415,7 +1334,7 @@ public void testAttemptObjectDeleteWithEventBasedHold() { public void testAttemptDeletionObjectTemporaryHold() { String blobName = generator.randomObjectName(); BlobInfo blobInfo = BlobInfo.newBuilder(bucket, blobName).setTemporaryHold(true).build(); - Blob remoteBlob = storage.create(blobInfo); + Blob remoteBlob = storage.create(blobInfo, BlobTargetOption.doesNotExist()); assertTrue(remoteBlob.getTemporaryHold()); try { remoteBlob.delete(); @@ -1432,7 +1351,7 @@ public void testBlobReload() throws Exception { String blobName = generator.randomObjectName(); BlobId blobId = BlobId.of(bucket.getName(), blobName); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); - Blob blob = storage.create(blobInfo, new byte[] {0, 1, 2}); + Blob blob = storage.create(blobInfo, new byte[] {0, 1, 2}, BlobTargetOption.doesNotExist()); Blob blobUnchanged = blob.reload(); // gRPC and json have differing defaults on projections b/258835631 @@ -1463,7 +1382,9 @@ public void testUploadWithEncryption() throws Exception { BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); ByteArrayInputStream content = new ByteArrayInputStream(BLOB_BYTE_CONTENT); - Blob blob = storage.createFrom(blobInfo, content, BlobWriteOption.encryptionKey(KEY)); + Blob blob = + storage.createFrom( + blobInfo, content, BlobWriteOption.encryptionKey(KEY), BlobWriteOption.doesNotExist()); try { blob.getContent(); @@ -1482,18 +1403,25 @@ private Blob createBlob(String method, BlobInfo blobInfo, boolean detectType) th switch (method) { case "create": return detectType - ? storage.create(blobInfo, BlobTargetOption.detectContentType()) - : storage.create(blobInfo); + ? storage.create( + blobInfo, BlobTargetOption.detectContentType(), BlobTargetOption.doesNotExist()) + : storage.create(blobInfo, BlobTargetOption.doesNotExist()); case "createFrom": InputStream inputStream = new ByteArrayInputStream(BLOB_BYTE_CONTENT); return detectType - ? storage.createFrom(blobInfo, inputStream, BlobWriteOption.detectContentType()) - : storage.createFrom(blobInfo, inputStream); + ? storage.createFrom( + blobInfo, + inputStream, + BlobWriteOption.detectContentType(), + BlobWriteOption.doesNotExist()) + : storage.createFrom(blobInfo, inputStream, BlobWriteOption.doesNotExist()); case "writer": if (detectType) { - storage.writer(blobInfo, BlobWriteOption.detectContentType()).close(); + storage + .writer(blobInfo, BlobWriteOption.detectContentType(), BlobWriteOption.doesNotExist()) + .close(); } else { - storage.writer(blobInfo).close(); + storage.writer(blobInfo, BlobWriteOption.doesNotExist()).close(); } return storage.get(BlobId.of(blobInfo.getBucket(), blobInfo.getName())); default: @@ -1501,51 +1429,13 @@ private Blob createBlob(String method, BlobInfo blobInfo, boolean detectType) th } } - private void testAutoContentType(String method) throws IOException { - String[] names = { - generator.randomObjectName() + ".txt", - generator.randomObjectName() + "with space/Pic.Jpg", - generator.randomObjectName() + "no_extension" - }; - String[] types = {"text/plain", "image/jpeg", "application/octet-stream"}; - for (int i = 0; i < names.length; i++) { - BlobId blobId = BlobId.of(bucket.getName(), names[i]); - BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); - Blob blob_true = createBlob(method, blobInfo, true); - assertEquals(types[i], blob_true.getContentType()); - - Blob blob_false = createBlob(method, blobInfo, false); - assertThat(blob_false.getContentType()).isAnyOf("application/octet-stream", ""); - } - String customType = "custom/type"; - BlobId blobId = BlobId.of(bucket.getName(), names[0]); - BlobInfo blobInfo = BlobInfo.newBuilder(blobId).setContentType(customType).build(); - Blob blob = createBlob(method, blobInfo, true); - assertEquals(customType, blob.getContentType()); - } - - @Test - public void testAutoContentTypeCreate() throws IOException { - testAutoContentType("create"); - } - - @Test - public void testAutoContentTypeCreateFrom() throws IOException { - testAutoContentType("createFrom"); - } - - @Test - public void testAutoContentTypeWriter() throws IOException { - testAutoContentType("writer"); - } - @Test public void testBlobTimeStorageClassUpdated() { String blobName = generator.randomObjectName(); StorageClass storageClass = StorageClass.COLDLINE; BlobInfo blob = BlobInfo.newBuilder(bucket, blobName).setStorageClass(storageClass).build(); - Blob remoteBlob = storage.create(blob); + Blob remoteBlob = storage.create(blob, BlobTargetOption.doesNotExist()); assertThat(remoteBlob).isNotNull(); assertEquals(blob.getBucket(), remoteBlob.getBucket()); assertThat(remoteBlob.getName()).isEqualTo(blob.getName()); @@ -1559,7 +1449,9 @@ public void testBlobTimeStorageClassUpdated() { CopyRequest request = CopyRequest.newBuilder() .setSource(blobId) - .setTarget(BlobInfo.newBuilder(blobId).setStorageClass(StorageClass.STANDARD).build()) + .setTarget( + BlobInfo.newBuilder(blobId).setStorageClass(StorageClass.STANDARD).build(), + BlobTargetOption.generationMatch(remoteBlob.getGeneration())) .build(); Blob updatedBlob1 = storage.copy(request).getResult(); assertThat(updatedBlob1.getTimeStorageClassUpdated()).isNotNull(); @@ -1570,7 +1462,11 @@ public void testBlobTimeStorageClassUpdated() { // Updates the other properties of the blob's to check the difference between blob updateTime // and timeStorageClassUpdated. - Blob updatedBlob2 = updatedBlob1.toBuilder().setContentType(CONTENT_TYPE).build().update(); + Blob updatedBlob2 = + updatedBlob1.toBuilder() + .setContentType(CONTENT_TYPE) + .build() + .update(BlobTargetOption.metagenerationMatch()); assertThat(updatedBlob2.getUpdateTime()) .isGreaterThan(updatedBlob2.getTimeStorageClassUpdated()); assertThat(updatedBlob2.getTimeStorageClassUpdated()) @@ -1583,9 +1479,9 @@ public void testUpdateBlob_noModification() { BlobInfo info = BlobInfo.newBuilder(bucket, generator.randomObjectName()).build(); // in grpc, create will return acls but update does not. re-get the metadata with default fields - Blob gen1 = storage.create(info); + Blob gen1 = storage.create(info, BlobTargetOption.doesNotExist()); gen1 = storage.get(gen1.getBlobId()); - Blob gen2 = storage.update(gen1); + Blob gen2 = storage.update(gen1, BlobTargetOption.metagenerationMatch()); assertThat(gen2).isEqualTo(gen1); } @@ -1601,8 +1497,8 @@ public void blob_update() throws Exception { BlobInfo info2 = BlobInfo.newBuilder(versionedBucket, randomObjectName).setMetadata(meta2).build(); - BlobInfo gen1 = storage.create(info1); - BlobInfo gen2 = storage.create(info2); + BlobInfo gen1 = storage.create(info1, BlobTargetOption.doesNotExist()); + BlobInfo gen2 = storage.create(info2, BlobTargetOption.generationMatch(gen1.getGeneration())); BlobInfo update1 = gen1.toBuilder().setMetadata(meta3).build(); From 6eef1b0f587b9f32041ac4bcef1a16b1b0bc4bb3 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Wed, 8 Oct 2025 11:16:18 -0400 Subject: [PATCH 04/18] feat: add per-message checksum validation for gRPC ReadObject operations (#3336) --- .../storage/GapicDownloadSessionBuilder.java | 2 +- .../GapicUnbufferedReadableByteChannel.java | 113 ++++++++++-------- .../cloud/storage/GrpcBlobReadChannel.java | 2 +- ...apicUnbufferedReadableByteChannelTest.java | 13 +- 4 files changed, 73 insertions(+), 57 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java index 234326fa2..3bbba1d70 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicDownloadSessionBuilder.java @@ -67,7 +67,7 @@ private ReadableByteChannelSessionBuilder( this.read = read; this.retrier = retrier; this.resultRetryAlgorithm = resultRetryAlgorithm; - this.hasher = Hasher.noop(); + this.hasher = Hasher.defaultHasher(); this.autoGzipDecompression = false; } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java index ce41620d3..cef751213 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUnbufferedReadableByteChannel.java @@ -30,6 +30,7 @@ import com.google.cloud.storage.Conversions.Decoder; import com.google.cloud.storage.Crc32cValue.Crc32cLengthKnown; import com.google.cloud.storage.GrpcUtils.ZeroCopyServerStreamingCallable; +import com.google.cloud.storage.Hasher.UncheckedChecksumMismatchException; import com.google.cloud.storage.ResponseContentLifecycleHandle.ChildRef; import com.google.cloud.storage.Retrying.Retrier; import com.google.cloud.storage.UnbufferedReadableByteChannelSession.UnbufferedReadableByteChannel; @@ -104,7 +105,11 @@ public boolean shouldRetry( boolean isWatchdogTimeout = previousThrowable instanceof StorageException && previousThrowable.getCause() instanceof WatchdogTimeoutException; - boolean shouldRetry = isWatchdogTimeout || alg.shouldRetry(previousThrowable, null); + boolean isChecksumMismatch = + previousThrowable instanceof StorageException + && previousThrowable.getCause() instanceof UncheckedChecksumMismatchException; + boolean shouldRetry = + isWatchdogTimeout || isChecksumMismatch || alg.shouldRetry(previousThrowable, null); if (previousThrowable != null && !shouldRetry) { result.setException(previousThrowable); } @@ -146,6 +151,16 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { Thread.currentThread().interrupt(); throw new InterruptedIOException(); } + if (take instanceof IOException) { + IOException ioe = (IOException) take; + if (alg.shouldRetry(ioe, null)) { + readObjectObserver = null; + continue; + } else { + ioe.addSuppressed(new AsyncStorageTaskException()); + throw ioe; + } + } if (take instanceof Throwable) { Throwable throwable = (Throwable) take; BaseServiceException coalesce = StorageException.coalesce(throwable); @@ -153,6 +168,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { readObjectObserver = null; continue; } else { + close(); throw new IOException(coalesce); } } @@ -160,45 +176,13 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { complete = true; break; } - readObjectObserver.request(); - ReadObjectResponse resp = (ReadObjectResponse) take; - try (ResponseContentLifecycleHandle handle = - read.getResponseContentLifecycleManager().get(resp)) { - ReadObjectResponseChildRef ref = ReadObjectResponseChildRef.from(handle); - if (resp.hasMetadata()) { - Object respMetadata = resp.getMetadata(); - if (metadata == null) { - metadata = respMetadata; - } else if (metadata.getGeneration() != respMetadata.getGeneration()) { - throw closeWithError( - String.format( - Locale.US, - "Mismatch Generation between subsequent reads. Expected %d but received %d", - metadata.getGeneration(), - respMetadata.getGeneration())); - } - } - ChecksummedData checksummedData = resp.getChecksummedData(); - ByteString content = checksummedData.getContent(); - int contentSize = content.size(); - // Very important to know whether a crc32c value is set. Without checking, protobuf will - // happily return 0, which is a valid crc32c value. - if (checksummedData.hasCrc32C()) { - Crc32cLengthKnown expected = Crc32cValue.of(checksummedData.getCrc32C(), contentSize); - try { - hasher.validate(expected, content); - } catch (IOException e) { - close(); - throw e; - } - } - ref.copy(c, dsts, offset, length); - if (ref.hasRemaining()) { - leftovers = ref; - } else { - ref.close(); - } + ReadObjectResponseChildRef ref = (ReadObjectResponseChildRef) take; + ref.copy(c, dsts, offset, length); + if (ref.hasRemaining()) { + leftovers = ref; + } else { + ref.close(); } } long read = c.read(); @@ -321,11 +305,10 @@ private void ensureStreamOpen() { } } - private IOException closeWithError(String message) throws IOException { - close(); + private IOException createError(String message) throws IOException { StorageException cause = new StorageException(HttpStatusCodes.STATUS_CODE_PRECONDITION_FAILED, message); - throw new IOException(message, cause); + return new IOException(message, cause); } private final class ReadObjectObserver extends StateCheckingResponseObserver { @@ -335,10 +318,6 @@ private final class ReadObjectObserver extends StateCheckingResponseObserver handle = + read.getResponseContentLifecycleManager().get(response)) { + ChecksummedData checksummedData = response.getChecksummedData(); + ByteString content = checksummedData.getContent(); + int contentSize = content.size(); + // Very important to know whether a crc32c value is set. Without checking, protobuf will + // happily return 0, which is a valid crc32c value. + if (checksummedData.hasCrc32C()) { + Crc32cLengthKnown expected = Crc32cValue.of(checksummedData.getCrc32C(), contentSize); + try { + hasher.validateUnchecked(expected, content); + } catch (UncheckedChecksumMismatchException e) { + queue.offer(e); + return; + } + } + if (response.hasMetadata()) { + Object respMetadata = response.getMetadata(); + if (metadata == null) { + metadata = respMetadata; + } else if (metadata.getGeneration() != respMetadata.getGeneration()) { + IOException exception = + createError( + String.format( + Locale.US, + "Mismatch Generation between subsequent reads. Expected %d but received %d", + metadata.getGeneration(), + respMetadata.getGeneration())); + queue.offer(exception); + return; + } + } + queue.offer(ReadObjectResponseChildRef.from(handle)); + fetchOffset.addAndGet(contentSize); if (response.hasMetadata() && !result.isDone()) { result.set(response.getMetadata()); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw Code.ABORTED.toStatus().withCause(e).asRuntimeException(); + } catch (IOException e) { + throw new RuntimeException(e); } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java index 5c31cb533..9113af1e0 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcBlobReadChannel.java @@ -62,7 +62,7 @@ protected LazyReadChannel newLazyReadChannel() { ResumableMedia.gapic() .read() .byteChannel(read, retrier, resultRetryAlgorithm) - .setHasher(Hasher.noop()) + .setHasher(Hasher.defaultHasher()) .setAutoGzipDecompression(autoGzipDecompression); BufferHandle bufferHandle = getBufferHandle(); // because we're erasing the specific type of channel, we need to declare it here. diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java index 8006d2b53..1e1c05915 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedReadableByteChannelTest.java @@ -18,6 +18,7 @@ import static com.google.cloud.storage.TestUtils.apiException; import static com.google.cloud.storage.TestUtils.getChecksummedData; +import static com.google.cloud.storage.TestUtils.xxd; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; @@ -236,7 +237,7 @@ public void readObject( } @Test - public void ioException_if_crc32c_mismatch_individual_message() + public void ifCrc32cMismatchIndividualMessage_restartFromCorrectOffset() throws IOException, InterruptedException { StorageGrpc.StorageImplBase fakeStorage = new StorageGrpc.StorageImplBase() { @@ -245,10 +246,12 @@ public void readObject( ReadObjectRequest request, StreamObserver responseObserver) { if (request.equals(req1)) { responseObserver.onNext(resp1); - ReadObjectResponse.Builder b = resp2.toBuilder(); + responseObserver.onNext(resp2); + ReadObjectResponse.Builder b = resp3.toBuilder(); // set a bad checksum value b.getChecksummedDataBuilder().setCrc32C(1); responseObserver.onNext(b.build()); + } else if (request.equals(req2)) { responseObserver.onNext(resp3); responseObserver.onNext(resp4); responseObserver.onCompleted(); @@ -276,10 +279,10 @@ public void readObject( retryOnly(DataLossException.class))); byte[] actualBytes = new byte[40]; try (UnbufferedReadableByteChannel c = session.open()) { - IOException ioException = - assertThrows(IOException.class, () -> c.read(ByteBuffer.wrap(actualBytes))); + int read = c.read(ByteBuffer.wrap(actualBytes)); - assertThat(ioException).hasMessageThat().contains("Mismatch checksum"); + assertThat(read).isEqualTo(40); + assertThat(xxd(actualBytes)).isEqualTo(xxd(bytes)); } } } From 5cdd72f6ba7edc7cf0d6c737e4561d0621e419ae Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 8 Oct 2025 22:29:04 +0100 Subject: [PATCH 05/18] build(deps): update dependency org.codehaus.mojo:exec-maven-plugin to v3.6.1 (#3328) --- google-cloud-storage/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index c4ff68a86..dbc03a519 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -487,7 +487,7 @@ org.codehaus.mojo exec-maven-plugin - 3.6.0 + 3.6.1 From 3c395e46d7eb9aa4f1856efd85baeaec1d62750d Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Wed, 8 Oct 2025 22:31:01 +0100 Subject: [PATCH 06/18] chore(deps): update storage release dependencies to v2.58.1 (#3332) --- samples/install-without-bom/pom.xml | 6 +++--- samples/snippets/pom.xml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index d9e5315b3..a2fe523ec 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -30,12 +30,12 @@ com.google.cloud google-cloud-storage - 2.58.0 + 2.58.1 com.google.cloud google-cloud-storage-control - 2.58.0 + 2.58.1 @@ -78,7 +78,7 @@ com.google.cloud google-cloud-storage - 2.58.0 + 2.58.1 tests test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 1740ae08b..c5d2382ce 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -99,7 +99,7 @@ com.google.cloud google-cloud-storage - 2.58.0 + 2.58.1 tests test From 73f263fb2d48161de5a98612a01deba2100eddb4 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Thu, 9 Oct 2025 03:02:55 +0530 Subject: [PATCH 07/18] samples: add/modify samples for ranged reads, tail reads of an object and appendable uploads (#3311) --- ...va => CreateAndWriteAppendableObject.java} | 17 ++- .../object/OpenMultipleObjectsRangedRead.java | 87 +++++++++++ ...java => OpenObjectMultipleRangedRead.java} | 26 +++- ...ect.java => OpenObjectReadFullObject.java} | 14 +- ...d.java => OpenObjectSingleRangedRead.java} | 19 ++- ...PauseAndResumeAppendableObjectUpload.java} | 72 ++++++---- .../object/ReadAppendableObjectTail.java | 135 ++++++++++++++++++ 7 files changed, 325 insertions(+), 45 deletions(-) rename samples/snippets/src/main/java/com/example/storage/object/{StartAppendableObjectUpload.java => CreateAndWriteAppendableObject.java} (78%) create mode 100644 samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java rename samples/snippets/src/main/java/com/example/storage/object/{AppendableObjectMultipleRangedRead.java => OpenObjectMultipleRangedRead.java} (78%) rename samples/snippets/src/main/java/com/example/storage/object/{AppendableObjectReadFullObject.java => OpenObjectReadFullObject.java} (84%) rename samples/snippets/src/main/java/com/example/storage/object/{AppendableObjectSingleRangedRead.java => OpenObjectSingleRangedRead.java} (79%) rename samples/snippets/src/main/java/com/example/storage/object/{ResumeAppendableObjectUpload.java => PauseAndResumeAppendableObjectUpload.java} (51%) create mode 100644 samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java diff --git a/samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java b/samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java similarity index 78% rename from samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java rename to samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java index cda8b7c89..29495d0ec 100644 --- a/samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java +++ b/samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java @@ -16,7 +16,7 @@ package com.example.storage.object; -// [START storage_start_appendable_object_upload] +// [START storage_create_and_write_appendable_object_upload] import com.google.cloud.storage.BlobAppendableUpload; import com.google.cloud.storage.BlobAppendableUpload.AppendableUploadWriteableByteChannel; @@ -24,6 +24,7 @@ import com.google.cloud.storage.BlobAppendableUploadConfig.CloseAction; import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.FlushPolicy; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import com.google.common.io.ByteStreams; @@ -33,8 +34,8 @@ import java.nio.file.Paths; import java.util.Locale; -public class StartAppendableObjectUpload { - public static void startAppendableObjectUpload( +public class CreateAndWriteAppendableObject { + public static void createAndWriteAppendableObject( String bucketName, String objectName, String filePath) throws Exception { // The ID of your GCS bucket // String bucketName = "your-unique-bucket-name"; @@ -49,12 +50,18 @@ public static void startAppendableObjectUpload( BlobId blobId = BlobId.of(bucketName, objectName); BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); + int flushSize = 64 * 1000; + FlushPolicy.MaxFlushSizeFlushPolicy flushPolicy = FlushPolicy.maxFlushSize(flushSize); BlobAppendableUploadConfig config = - BlobAppendableUploadConfig.of().withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING); + BlobAppendableUploadConfig.of() + .withCloseAction(CloseAction.FINALIZE_WHEN_CLOSING) + .withFlushPolicy(flushPolicy); BlobAppendableUpload uploadSession = storage.blobAppendableUpload(blobInfo, config); try (AppendableUploadWriteableByteChannel channel = uploadSession.open(); ReadableByteChannel readableByteChannel = FileChannel.open(Paths.get(filePath))) { ByteStreams.copy(readableByteChannel, channel); + // Since the channel is in a try-with-resources block, channel.close() + // will be implicitly called here, which triggers the finalization. } catch (IOException ex) { throw new IOException("Failed to upload to object " + blobId.toGsUtilUri(), ex); } @@ -67,4 +74,4 @@ public static void startAppendableObjectUpload( } } -// [END storage_start_appendable_object_upload] +// [END storage_create_and_write_appendable_object_upload] diff --git a/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java b/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java new file mode 100644 index 000000000..33dfb916b --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java @@ -0,0 +1,87 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +package com.example.storage.object; + +// [START storage_open_multiple_objects_ranged_read] + +import com.google.api.core.ApiFuture; +import com.google.api.core.ApiFutures; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobReadSession; +import com.google.cloud.storage.RangeSpec; +import com.google.cloud.storage.ReadAsFutureBytes; +import com.google.cloud.storage.ReadProjectionConfigs; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class OpenMultipleObjectsRangedRead { + public static void multipleObjectsSingleRangedRead( + String bucketName, List objectNames, long startOffset, int length) throws Exception { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS objects to read + // List objectName = Arrays.asList("object-1", "object-2", "object-3"); + + RangeSpec singleRange = RangeSpec.of(startOffset, length); + ReadAsFutureBytes rangeConfig = + ReadProjectionConfigs.asFutureBytes().withRangeSpec(singleRange); + + try (Storage storage = StorageOptions.grpc().build().getService()) { + List> futuresToWaitOn = new ArrayList<>(); + + System.out.printf( + "Initiating single ranged read [%d, %d] on %d objects...%n", + startOffset, startOffset + length - 1, objectNames.size()); + + for (String objectName : objectNames) { + BlobId blobId = BlobId.of(bucketName, objectName); + ApiFuture futureReadSession = storage.blobReadSession(blobId); + + ApiFuture readAndCloseFuture = + ApiFutures.transformAsync( + futureReadSession, + (BlobReadSession session) -> { + ApiFuture readFuture = session.readAs(rangeConfig); + + readFuture.addListener( + () -> { + try { + session.close(); + } catch (java.io.IOException e) { + System.err.println( + "WARN: Background error while closing session: " + e.getMessage()); + } + }, + MoreExecutors.directExecutor()); + return readFuture; + }, + MoreExecutors.directExecutor()); + + futuresToWaitOn.add(readAndCloseFuture); + } + ApiFutures.allAsList(futuresToWaitOn).get(30, TimeUnit.SECONDS); + + System.out.println("All concurrent single-ranged read operations are complete."); + } + } +} +// [END storage_open_multiple_objects_ranged_read] diff --git a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java similarity index 78% rename from samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java rename to samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java index ce36771ad..f3e823341 100644 --- a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java +++ b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java @@ -16,7 +16,7 @@ package com.example.storage.object; -// [START storage_read_appendable_object_multiple_ranges] +// [START storage_open_object_multiple_ranged_read] import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; @@ -30,10 +30,28 @@ import java.util.List; import java.util.concurrent.TimeUnit; -public class AppendableObjectMultipleRangedRead { - public static void appendableObjectMultipleRangedRead( +public class OpenObjectMultipleRangedRead { + public static void openObjectMultipleRangedRead( String bucketName, String objectName, long offset1, int length1, long offset2, int length2) throws Exception { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS object + // String objectName = "your-object-name"; + + // The beginning of the range 1 + // long offset = 0 + + // The maximum number of bytes to read in range 1 + // int length = 16; + + // The beginning of the range 2 + // long offset = 16 + + // The maximum number of bytes to read in range 2 + // int length = 32; + try (Storage storage = StorageOptions.grpc().build().getService()) { BlobId blobId = BlobId.of(bucketName, objectName); ApiFuture futureBlobReadSession = storage.blobReadSession(blobId); @@ -62,4 +80,4 @@ public static void appendableObjectMultipleRangedRead( } } -// [END storage_read_appendable_object_multiple_ranges] +// [END storage_open_object_multiple_ranged_read] diff --git a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java similarity index 84% rename from samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java rename to samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java index 0b24051ca..a1a22ffd2 100644 --- a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java +++ b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java @@ -16,7 +16,7 @@ package com.example.storage.object; -// [START storage_read_appendable_object_full] +// [START storage_open_object_read_full_object] import com.google.api.core.ApiFuture; import com.google.cloud.storage.BlobId; @@ -30,9 +30,15 @@ import java.util.Locale; import java.util.concurrent.TimeUnit; -public class AppendableObjectReadFullObject { - public static void appendableObjectReadFullObject(String bucketName, String objectName) +public class OpenObjectReadFullObject { + public static void openObjectReadFullObject(String bucketName, String objectName) throws Exception { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS object to read + // String objectName = "your-object-name"; + try (Storage storage = StorageOptions.grpc().build().getService()) { BlobId blobId = BlobId.of(bucketName, objectName); ApiFuture futureBlobReadSession = storage.blobReadSession(blobId); @@ -60,4 +66,4 @@ public static void appendableObjectReadFullObject(String bucketName, String obje } } } -// [END storage_read_appendable_object_full] +// [END storage_open_object_read_full_object] diff --git a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java similarity index 79% rename from samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java rename to samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java index 437f47203..55446ea26 100644 --- a/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java +++ b/samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java @@ -16,7 +16,7 @@ package com.example.storage.object; -// [START storage_read_appendable_object_single_range] +// [START storage_open_object_single_ranged_read] import com.google.api.core.ApiFuture; import com.google.cloud.storage.BlobId; @@ -27,9 +27,20 @@ import com.google.cloud.storage.StorageOptions; import java.util.concurrent.TimeUnit; -public class AppendableObjectSingleRangedRead { - public static void appendableObjectSingleRangedRead( +public class OpenObjectSingleRangedRead { + public static void openObjectSingleRangedRead( String bucketName, String objectName, long offset, int length) throws Exception { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS object + // String objectName = "your-object-name"; + + // The beginning of the range + // long offset = 0 + + // The maximum number of bytes to read from the object. + // int length = 64; try (Storage storage = StorageOptions.grpc().build().getService()) { BlobId blobId = BlobId.of(bucketName, objectName); @@ -55,4 +66,4 @@ public static void appendableObjectSingleRangedRead( } } } -// [END storage_read_appendable_object_single_range] +// [END storage_open_object_single_ranged_read] diff --git a/samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java b/samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java similarity index 51% rename from samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java rename to samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java index 8852b585d..c364ee093 100644 --- a/samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java +++ b/samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java @@ -5,7 +5,7 @@ * 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 + * 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, @@ -16,7 +16,7 @@ package com.example.storage.object; -// [START storage_resume_appendable_object_upload] +// [START storage_pause_and_resume_appendable_object_upload] import com.google.cloud.storage.Blob; import com.google.cloud.storage.BlobAppendableUpload; @@ -26,20 +26,23 @@ import com.google.cloud.storage.BlobId; import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageChannelUtils; import com.google.cloud.storage.StorageOptions; import com.google.common.io.ByteStreams; import java.io.IOException; +import java.nio.ByteBuffer; import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; import java.nio.file.Paths; import java.util.Locale; -public class ResumeAppendableObjectUpload { - public static void resumeAppendableObjectUpload( +public class PauseAndResumeAppendableObjectUpload { + public static void pauseAndResumeAppendableObjectUpload( String bucketName, String objectName, String filePath) throws Exception { // The ID of your GCS bucket // String bucketName = "your-unique-bucket-name"; - // The ID of your GCS unfinalized appendable object + // The ID of your GCS object // String objectName = "your-object-name"; // The path to the file to upload @@ -47,45 +50,58 @@ public static void resumeAppendableObjectUpload( try (Storage storage = StorageOptions.grpc().build().getService()) { BlobId blobId = BlobId.of(bucketName, objectName); - Blob existingBlob = storage.get(blobId); - BlobInfo blobInfoForTakeover = BlobInfo.newBuilder(existingBlob.getBlobId()).build(); + BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); + + // --- Step 1: Initial string write (PAUSE) --- + // Default close action will be CLOSE_WITHOUT_FINALIZING + BlobAppendableUploadConfig initialConfig = BlobAppendableUploadConfig.of(); + BlobAppendableUpload initialUploadSession = + storage.blobAppendableUpload(blobInfo, initialConfig); + + try (AppendableUploadWriteableByteChannel channel = initialUploadSession.open()) { + String initialData = "Initial data segment.\n"; + ByteBuffer buffer = ByteBuffer.wrap(initialData.getBytes(StandardCharsets.UTF_8)); + long totalBytesWritten = StorageChannelUtils.blockingEmptyTo(buffer, channel); + channel.flush(); + + System.out.printf( + Locale.US, "Wrote %d bytes (initial string) in first segment.\n", totalBytesWritten); + } catch (IOException ex) { + throw new IOException("Failed initial upload to object " + blobId.toGsUtilUri(), ex); + } + Blob existingBlob = storage.get(blobId); long currentObjectSize = existingBlob.getSize(); System.out.printf( Locale.US, - "Resuming upload for %s. Currently uploaded size: %d bytes\n", - blobId.toGsUtilUri(), + "Initial upload paused. Currently uploaded size: %d bytes\n", currentObjectSize); - BlobAppendableUploadConfig config = - BlobAppendableUploadConfig.of().withCloseAction(CloseAction.CLOSE_WITHOUT_FINALIZING); + // --- Step 2: Resume upload with file content and finalize --- + // Use FINALIZE_WHEN_CLOSING to ensure the object is finalized on channel closure. + BlobAppendableUploadConfig resumeConfig = + BlobAppendableUploadConfig.of().withCloseAction(CloseAction.FINALIZE_WHEN_CLOSING); BlobAppendableUpload resumeUploadSession = - storage.blobAppendableUpload(blobInfoForTakeover, config); + storage.blobAppendableUpload(existingBlob.toBuilder().build(), resumeConfig); + try (FileChannel fileChannel = FileChannel.open(Paths.get(filePath)); AppendableUploadWriteableByteChannel channel = resumeUploadSession.open()) { + long bytesToAppend = fileChannel.size(); + System.out.printf( + Locale.US, + "Appending the entire file (%d bytes) after the initial string.\n", + bytesToAppend); - if (fileChannel.size() < currentObjectSize) { - throw new IOException( - "Local file is smaller than the already uploaded data. File size: " - + fileChannel.size() - + ", Uploaded size: " - + currentObjectSize); - } else if (fileChannel.size() == currentObjectSize) { - System.out.println("No more data to upload."); - } else { - fileChannel.position(currentObjectSize); - System.out.printf( - Locale.US, "Appending %d bytes\n", fileChannel.size() - currentObjectSize); - ByteStreams.copy(fileChannel, channel); - } + ByteStreams.copy(fileChannel, channel); } + BlobInfo result = storage.get(blobId); System.out.printf( Locale.US, - "Object %s successfully resumed. Total size: %d\n", + "\nObject %s successfully resumed and finalized. Total size: %d bytes\n", result.getBlobId().toGsUtilUriWithGeneration(), result.getSize()); } } } -// [END storage_resume_appendable_object_upload] +// [END storage_pause_and_resume_appendable_object_upload] diff --git a/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java b/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java new file mode 100644 index 000000000..98cf31e96 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java @@ -0,0 +1,135 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +package com.example.storage.object; + +// [START storage_read_appendable_object_tail] + +import com.google.api.core.ApiFuture; +import com.google.cloud.storage.BlobAppendableUpload; +import com.google.cloud.storage.BlobAppendableUpload.AppendableUploadWriteableByteChannel; +import com.google.cloud.storage.BlobAppendableUploadConfig; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.BlobReadSession; +import com.google.cloud.storage.FlushPolicy; +import com.google.cloud.storage.RangeSpec; +import com.google.cloud.storage.ReadProjectionConfigs; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageChannelUtils; +import com.google.cloud.storage.StorageOptions; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.TimeUnit; + +public class ReadAppendableObjectTail { + public static void readAppendableObjectTail(String bucketName, String objectName) + throws Exception { + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS object + // String objectName = "your-object-name"; + + try (Storage storage = StorageOptions.grpc().build().getService()) { + BlobId blobId = BlobId.of(bucketName, objectName); + BlobInfo info = BlobInfo.newBuilder(blobId).build(); + int totalToWrite = 64 * 1000; + // Define our flush policy to flush small increments + // This is useful for demonstration purposes, but you should use more appropriate values for + // your workload. + int flushSize = totalToWrite / 8; + FlushPolicy.MinFlushSizeFlushPolicy flushPolicy = + FlushPolicy.minFlushSize(flushSize).withMaxPendingBytes(flushSize); + BlobAppendableUploadConfig appendableUploadConfig = + BlobAppendableUploadConfig.of().withFlushPolicy(flushPolicy); + BlobAppendableUpload upload = + storage.blobAppendableUpload( + info, appendableUploadConfig, Storage.BlobWriteOption.doesNotExist()); + // Create the object, we'll takeover to write for our example. + upload.open().closeWithoutFinalizing(); + BlobInfo gen1 = upload.getResult().get(); + BlobAppendableUpload takeover = storage.blobAppendableUpload(gen1, appendableUploadConfig); + + try (AppendableUploadWriteableByteChannel channel = takeover.open()) { + // Start a background thread to write some data on a periodic basis + // In reality, you're application would probably be doing thing in another scope + Thread writeThread = startWriteThread(totalToWrite, channel, flushPolicy); + try (BlobReadSession readSession = + storage.blobReadSession(gen1.getBlobId()).get(10, TimeUnit.SECONDS)) { + int zeroCnt = 0; + long read = 0; + while (read < totalToWrite) { + if (zeroCnt >= 30 && !channel.isOpen()) { + System.out.println("breaking"); + break; + } + ApiFuture future = + readSession.readAs( + ReadProjectionConfigs.asFutureBytes() + .withRangeSpec(RangeSpec.of(read, flushPolicy.getMinFlushSize()))); + byte[] bytes = future.get(20, TimeUnit.SECONDS); + + read += bytes.length; + long defaultSleep = 1_500L; + if (bytes.length == 0) { + zeroCnt++; + long millis = defaultSleep * zeroCnt; + System.out.println("millis = " + millis); + Thread.sleep(millis); + } else { + zeroCnt = 0; + System.out.println("bytes.length = " + bytes.length + " read = " + read); + Thread.sleep(defaultSleep); + } + } + assert read == totalToWrite : "not enough bytes"; + } + writeThread.join(); + } + } + } + + private static Thread startWriteThread( + int totalToWrite, + AppendableUploadWriteableByteChannel channel, + FlushPolicy.MinFlushSizeFlushPolicy flushPolicy) { + Thread writeThread = + new Thread( + () -> { + try { + for (long written = 0; written < totalToWrite; ) { + byte alphaOffset = (byte) (written % 0x1a); + + ByteBuffer buf = ByteBuffer.wrap(new byte[] {(byte) (0x41 + alphaOffset)}); + int w = StorageChannelUtils.blockingEmptyTo(buf, channel); + written += w; + if (written % flushPolicy.getMinFlushSize() == 0) { + channel.flush(); + Thread.sleep(40); + } + } + channel.closeWithoutFinalizing(); + + } catch (IOException | InterruptedException e) { + throw new RuntimeException(e); + } + }); + writeThread.start(); + return writeThread; + } +} +// [END storage_read_appendable_object_tail] From 54bc2c12f2d0e8c164e4ddcaa1a61d2de3911131 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Thu, 9 Oct 2025 03:04:09 +0530 Subject: [PATCH 08/18] fix: add case insensitive check for X-Goog-Content-SHA256 in SignatureInfo (#3337) --- .../google/cloud/storage/SignatureInfo.java | 8 +++- .../cloud/storage/SignatureInfoTest.java | 37 +++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index 03630c61a..979949635 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -170,7 +170,13 @@ private String constructV4CanonicalRequestHash() { .append(serializer.serializeHeaderNames(canonicalizedExtensionHeaders)) .append(COMPONENT_SEPARATOR); - String userProvidedHash = canonicalizedExtensionHeaders.get("X-Goog-Content-SHA256"); + String userProvidedHash = null; + for (Map.Entry entry : canonicalizedExtensionHeaders.entrySet()) { + if ("X-Goog-Content-SHA256".equalsIgnoreCase(entry.getKey())) { + userProvidedHash = entry.getValue(); + break; + } + } canonicalRequest.append(userProvidedHash == null ? "UNSIGNED-PAYLOAD" : userProvidedHash); return Hashing.sha256() diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java index 7b146a713..711a84d04 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -20,7 +20,9 @@ import static org.junit.Assert.assertTrue; import com.google.cloud.storage.SignatureInfo.Builder; +import com.google.common.hash.Hashing; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import org.junit.Test; @@ -98,4 +100,39 @@ public void constructV4QueryString() { + "auto%2Fstorage%2Fgoog4_request&X-Goog-Date=20010909T014640Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host", queryString); } + + @Test + public void constructV4UnsignedPayloadWithContentSha256Header() { + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 10L, URI.create(RESOURCE)); + builder.setSignatureVersion(Storage.SignUrlOption.SignatureVersion.V4); + builder.setAccountEmail("me@google.com"); + builder.setTimestamp(1000000000000L); + + Map extensionHeaders = new HashMap<>(); + // Add the header with a lowercase key, which triggers the bug. + String contentSha256 = "sha256"; + extensionHeaders.put("X-goog-content-sha256", contentSha256); + builder.setCanonicalizedExtensionHeaders(extensionHeaders); + + // This is the payload hash that SHOULD be generated + String correctCanonicalRequest = + "PUT\n" + + "/bucketName/blobName\n" + + "X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=me%40google.com%2F20010909%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20010909T014640Z&X-Goog-Expires=10&X-Goog-SignedHeaders=host%3Bx-goog-content-sha256\n" + + "host:storage.googleapis.com\n" + + "x-goog-content-sha256:" + + contentSha256 + + "\n" + + "\n" + + "host;x-goog-content-sha256\n" + + contentSha256; + String expectedPayloadHash = + Hashing.sha256().hashString(correctCanonicalRequest, StandardCharsets.UTF_8).toString(); + + String unsignedPayload = builder.build().constructUnsignedPayload(); + String[] parts = unsignedPayload.split("\n"); + String generatedPayloadHash = parts[parts.length - 1]; + + assertEquals(expectedPayloadHash, generatedPayloadHash); + } } From 792ea71e49e183c8e72da70a2382bb2ff0984cf3 Mon Sep 17 00:00:00 2001 From: cloud-java-bot <122572305+cloud-java-bot@users.noreply.github.com> Date: Thu, 9 Oct 2025 12:00:29 -0400 Subject: [PATCH 09/18] chore: Update generation configuration at Thu Oct 9 02:27:00 UTC 2025 (#3333) --- README.md | 16 +++++++++------- generation_config.yaml | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index dd03e0c6f..64a76e3ae 100644 --- a/README.md +++ b/README.md @@ -46,12 +46,12 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-storage - 2.58.0 + 2.58.1 com.google.cloud google-cloud-storage-control - 2.58.0 + 2.58.1 ``` @@ -321,9 +321,6 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/ | Get Managed Folder | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/GetManagedFolder.java) | | List Managed Folders | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/managedfolders/ListManagedFolders.java) | | Add Blob Owner | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AddBlobOwner.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AddBlobOwner.java) | -| Appendable Object Multiple Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AppendableObjectMultipleRangedRead.java) | -| Appendable Object Read Full Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AppendableObjectReadFullObject.java) | -| Appendable Object Single Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AppendableObjectSingleRangedRead.java) | | Atomic Move Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/AtomicMoveObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/AtomicMoveObject.java) | | Batch Set Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/BatchSetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/BatchSetObjectMetadata.java) | | Change Object Csek To Kms | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ChangeObjectCsekToKms.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ChangeObjectCsekToKms.java) | @@ -332,6 +329,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/ | Copy Delete Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CopyDeleteObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CopyDeleteObject.java) | | Copy Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CopyObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CopyObject.java) | | Copy Old Version Of Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CopyOldVersionOfObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CopyOldVersionOfObject.java) | +| Create And Write Appendable Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/CreateAndWriteAppendableObject.java) | | Delete Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/DeleteObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/DeleteObject.java) | | Delete Old Version Of Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/DeleteOldVersionOfObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/DeleteOldVersionOfObject.java) | | Download Byte Range | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/DownloadByteRange.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/DownloadByteRange.java) | @@ -351,19 +349,23 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/ | List Soft Deleted Objects | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedObjects.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedObjects.java) | | List Soft Deleted Versions Of Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedVersionsOfObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListSoftDeletedVersionsOfObject.java) | | Make Object Public | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/MakeObjectPublic.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/MakeObjectPublic.java) | +| Open Multiple Objects Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenMultipleObjectsRangedRead.java) | +| Open Object Multiple Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenObjectMultipleRangedRead.java) | +| Open Object Read Full Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenObjectReadFullObject.java) | +| Open Object Single Ranged Read | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/OpenObjectSingleRangedRead.java) | +| Pause And Resume Appendable Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/PauseAndResumeAppendableObjectUpload.java) | | Print Blob Acl | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/PrintBlobAcl.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/PrintBlobAcl.java) | | Print Blob Acl For User | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/PrintBlobAclForUser.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/PrintBlobAclForUser.java) | +| Read Appendable Object Tail | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ReadAppendableObjectTail.java) | | Release Event Based Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ReleaseEventBasedHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ReleaseEventBasedHold.java) | | Release Temporary Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ReleaseTemporaryHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ReleaseTemporaryHold.java) | | Remove Blob Owner | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RemoveBlobOwner.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RemoveBlobOwner.java) | | Restore Soft Deleted Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RestoreSoftDeletedObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RestoreSoftDeletedObject.java) | -| Resume Appendable Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ResumeAppendableObjectUpload.java) | | Rotate Object Encryption Key | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RotateObjectEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RotateObjectEncryptionKey.java) | | Set Event Based Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetEventBasedHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetEventBasedHold.java) | | Set Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectMetadata.java) | | Set Object Retention Policy | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectRetentionPolicy.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectRetentionPolicy.java) | | Set Temporary Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetTemporaryHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetTemporaryHold.java) | -| Start Appendable Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/StartAppendableObjectUpload.java) | | Stream Object Download | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/StreamObjectDownload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/StreamObjectDownload.java) | | Stream Object Upload | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/StreamObjectUpload.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/StreamObjectUpload.java) | | Upload Encrypted Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/UploadEncryptedObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/UploadEncryptedObject.java) | diff --git a/generation_config.yaml b/generation_config.yaml index 56e27ee04..f9ad81523 100644 --- a/generation_config.yaml +++ b/generation_config.yaml @@ -1,5 +1,5 @@ gapic_generator_version: 2.62.3 -googleapis_commitish: 2193a2bfcecb92b92aad7a4d81baa428cafd7dfd +googleapis_commitish: 7b2b58ff4fb3eee3c0923af35fdee90134fabe3b libraries_bom_version: 26.69.0 libraries: - api_shortname: storage From 7e42c2fbca53ca6b1266f784e58cee00cfed7d62 Mon Sep 17 00:00:00 2001 From: Lawrence Qiu Date: Thu, 9 Oct 2025 16:01:25 +0000 Subject: [PATCH 10/18] fix: migrate away from GoogleCredentials.fromStream() usages (#3339) --- .../storage/testing/RemoteStorageHelper.java | 60 +++++++++++++++---- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java index da4d96a11..606ed1226 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java @@ -16,6 +16,7 @@ package com.google.cloud.storage.testing; +import com.google.api.core.ObsoleteApi; import com.google.api.gax.paging.Page; import com.google.api.gax.retrying.RetrySettings; import com.google.auth.oauth2.GoogleCredentials; @@ -186,7 +187,27 @@ public static String generateBucketName() { } /** - * Creates a {@code RemoteStorageHelper} object for the given project id and JSON key input + * This method is obsolete because of a potential security risk. Use the {@link #create(String, + * GoogleCredentials)} method instead. + * + *

If you know that you will be loading credential configurations of a specific type, it is + * recommended to use a credential-type-specific `fromStream()` method. This will ensure that an + * unexpected credential type with potential for malicious intent is not loaded unintentionally. + * You might still have to do validation for certain credential types. Please follow the + * recommendation for that method. + * + *

If you are loading your credential configuration from an untrusted source and have not + * mitigated the risks (e.g. by validating the configuration yourself), make these changes as soon + * as possible to prevent security risks to your environment. + * + *

Regardless of the method used, it is always your responsibility to validate configurations + * received from external sources. + * + *

See the {@see documentation} + * for more details. + * + *

Creates a {@code RemoteStorageHelper} object for the given project id and JSON key input * stream. * * @param projectId id of the project to be used for running the tests @@ -195,21 +216,12 @@ public static String generateBucketName() { * @throws com.google.cloud.storage.testing.RemoteStorageHelper.StorageHelperException if {@code * keyStream} is not a valid JSON key stream */ + @ObsoleteApi( + "This method is obsolete because of a potential security risk. Use the create() variant with Credential parameter instead") public static RemoteStorageHelper create(String projectId, InputStream keyStream) throws StorageHelperException { try { - HttpTransportOptions transportOptions = - HttpStorageOptions.defaults().getDefaultTransportOptions(); - transportOptions = - transportOptions.toBuilder().setConnectTimeout(60000).setReadTimeout(60000).build(); - StorageOptions storageOptions = - StorageOptions.http() - .setCredentials(GoogleCredentials.fromStream(keyStream)) - .setProjectId(projectId) - .setRetrySettings(retrySettings()) - .setTransportOptions(transportOptions) - .build(); - return new RemoteStorageHelper(storageOptions); + return create(projectId, GoogleCredentials.fromStream(keyStream)); } catch (IOException ex) { if (log.isLoggable(Level.WARNING)) { log.log(Level.WARNING, ex.getMessage()); @@ -218,6 +230,28 @@ public static RemoteStorageHelper create(String projectId, InputStream keyStream } } + /** + * Creates a {@code RemoteStorageHelper} object for the given project id and Credential. + * + * @param projectId id of the project to be used for running the tests + * @param credentials GoogleCredential to set to StorageOptions + * @return A {@code RemoteStorageHelper} object for the provided options + */ + public static RemoteStorageHelper create(String projectId, GoogleCredentials credentials) { + HttpTransportOptions transportOptions = + HttpStorageOptions.defaults().getDefaultTransportOptions(); + transportOptions = + transportOptions.toBuilder().setConnectTimeout(60000).setReadTimeout(60000).build(); + StorageOptions storageOptions = + StorageOptions.http() + .setCredentials(credentials) + .setProjectId(projectId) + .setRetrySettings(retrySettings()) + .setTransportOptions(transportOptions) + .build(); + return new RemoteStorageHelper(storageOptions); + } + /** * Creates a {@code RemoteStorageHelper} object using default project id and authentication * credentials. From 9dcce11a7f5197efc984c9cbdfe1fbcb25d6a159 Mon Sep 17 00:00:00 2001 From: Nidhi Date: Fri, 10 Oct 2025 03:58:31 +0530 Subject: [PATCH 11/18] samples: add samples for object contexts (#3329) --- .../storage/object/GetObjectContexts.java | 65 +++++++++++++ .../storage/object/ListObjectContexts.java | 64 +++++++++++++ .../storage/object/SetObjectContexts.java | 91 +++++++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java create mode 100644 samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java create mode 100644 samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java diff --git a/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java b/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java new file mode 100644 index 000000000..a0ab37776 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java @@ -0,0 +1,65 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +package com.example.storage.object; + +// [START storage_get_object_contexts] + +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobInfo.ObjectContexts; +import com.google.cloud.storage.BlobInfo.ObjectCustomContextPayload; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import java.util.Map; + +public class GetObjectContexts { + public static void getObjectContexts(String projectId, String bucketName, String objectName) + throws Exception { + // The ID of your GCP project + // String projectId = "your-project-id"; + + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS object + // String objectName = "your-object-name"; + + try (Storage storage = + StorageOptions.newBuilder().setProjectId(projectId).build().getService()) { + + Blob blob = storage.get(bucketName, objectName); + if (blob == null) { + System.out.println("The object " + objectName + " was not found in " + bucketName); + return; + } + ObjectContexts objectContexts = blob.getContexts(); + + if (objectContexts != null) { + Map customContexts = objectContexts.getCustom(); + if (customContexts == null) { + System.out.println("No custom contexts found for object: " + objectName); + return; + } + // Print blob's object contexts + System.out.println("\nCustom Contexts:"); + for (Map.Entry custom : customContexts.entrySet()) { + System.out.println(custom.getKey() + "=" + custom.getValue()); + } + } + } + } +} +// [END storage_get_object_contexts] diff --git a/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java b/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java new file mode 100644 index 000000000..3becd448a --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java @@ -0,0 +1,64 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +package com.example.storage.object; + +// [START storage_list_object_contexts] + +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; + +public class ListObjectContexts { + public static void listObjectContexts(String projectId, String bucketName, String key) + throws Exception { + // The ID of your GCP project + // String projectId = "your-project-id"; + + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The context key you want to filter + // String key = "your-context-key"; + + try (Storage storage = + StorageOptions.newBuilder().setProjectId(projectId).build().getService()) { + /* + * List any object that has a context with the specified key attached + * String filter = "contexts.\"KEY\":*"; + * + * List any object that that does not have a context with the specified key attached + * String filter = "NOT contexts.\"KEY\":*"; + * + * List any object that has a context with the specified key and value attached + * String filter = "contexts.\"KEY\"=\"VALUE\""; + * + * List any object that does not have a context with the specified key and value attached + * String filter = "NOT contexts.\"KEY\"=\"VALUE\""; + */ + + String filter = "contexts.\"" + key + "\":*"; + + System.out.println("Listing objects for bucket: " + bucketName + "with context key: " + key); + Page blobs = storage.list(bucketName, Storage.BlobListOption.filter(filter)); + for (Blob blob : blobs.iterateAll()) { + System.out.println(blob.getBlobId().toGsUtilUri()); + } + } + } +} +// [END storage_list_object_contexts] diff --git a/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java b/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java new file mode 100644 index 000000000..169399a71 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java @@ -0,0 +1,91 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +package com.example.storage.object; + +// [START storage_set_object_contexts] + +import com.google.cloud.storage.Blob; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.BlobInfo.ObjectContexts; +import com.google.cloud.storage.BlobInfo.ObjectCustomContextPayload; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.google.common.collect.Maps; +import java.util.Map; + +public class SetObjectContexts { + public static void setObjectContexts( + String projectId, String bucketName, String objectName, String key, String value) + throws Exception { + // The ID of your GCP project + // String projectId = "your-project-id"; + + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The ID of your GCS object + // String objectName = "your-object-name"; + + // The context key-value you want to add + // String key = "your-context-key"; + // String value = "your-context-value"; + + try (Storage storage = + StorageOptions.newBuilder().setProjectId(projectId).build().getService()) { + BlobId blobId = BlobId.of(bucketName, objectName); + Blob blob = storage.get(blobId); + if (blob == null) { + System.out.println("The object " + objectName + " was not found in " + bucketName); + return; + } + + // Recommended: Set a generation-match precondition to avoid potential race + // conditions and data corruptions. The request to update returns a 412 error if + // the object's generation number does not match your precondition. + Storage.BlobTargetOption precondition = Storage.BlobTargetOption.generationMatch(); + + // This section demonstrates how to upsert, delete all, and delete a specific context. + + // To upsert a context (if the key already exists, its value is replaced; + // otherwise, a new key-value pair is added): + ObjectCustomContextPayload payload = + ObjectCustomContextPayload.newBuilder().setValue(value).build(); + Map custom = Maps.newHashMap(); + custom.put(key, payload); + ObjectContexts contexts = ObjectContexts.newBuilder().setCustom(custom).build(); + + /* + * To delete all existing contexts: + * ObjectContexts contexts = ObjectContexts.newBuilder().setCustom(null).build(); + */ + + /* + * To delete a specific key from the context: + * Map custom = Maps.newHashMap(); + * custom.put(key, null); + * ObjectContexts contexts = ObjectContexts.newBuilder().setCustom(custom).build(); + */ + BlobInfo pendingUpdate = blob.toBuilder().setContexts(contexts).build(); + storage.update(pendingUpdate, precondition); + + System.out.println( + "Updated custom contexts for object " + objectName + " in bucket " + bucketName); + } + } +} +// [END storage_set_object_contexts] From 9fbf2d8b1cbb8fd635b2c3c28640f5dc9b3f5450 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 10 Oct 2025 17:18:38 +0100 Subject: [PATCH 12/18] test(deps): update dependency io.github.classgraph:classgraph to v4.8.184 (#3343) --- google-cloud-storage/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index dbc03a519..1b8cb3e8f 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -331,7 +331,7 @@ io.github.classgraph classgraph - 4.8.181 + 4.8.184 test From e94c4e69d04c994e693ad231119daf6fe4ec1431 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Fri, 10 Oct 2025 17:20:53 +0100 Subject: [PATCH 13/18] chore(deps): update dependency com.google.cloud:libraries-bom to v26.70.0 (#3341) --- samples/snippets/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index c5d2382ce..51b4c27a1 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -31,7 +31,7 @@ com.google.cloud libraries-bom - 26.69.0 + 26.70.0 pom import From be7744fea6b8e5e1b5bf1e3132ab4db21977a0e8 Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Mon, 20 Oct 2025 18:19:44 +0100 Subject: [PATCH 14/18] test(deps): update cross product test dependencies (#3334) --- google-cloud-storage/pom.xml | 6 +++--- pom.xml | 2 +- samples/install-without-bom/pom.xml | 4 ++-- samples/snapshot/pom.xml | 4 ++-- samples/snippets/pom.xml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index 1b8cb3e8f..d161cbb0c 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -16,7 +16,7 @@ google-cloud-storage - 1.123.5 + 1.124.0 @@ -239,14 +239,14 @@ com.google.api.grpc proto-google-cloud-kms-v1 - 0.169.0 + 0.171.0 test com.google.cloud google-cloud-kms - 2.78.0 + 2.80.0 test diff --git a/pom.xml b/pom.xml index fa0d915d3..6836a0f77 100644 --- a/pom.xml +++ b/pom.xml @@ -92,7 +92,7 @@ com.google.cloud google-cloud-pubsub - 1.141.5 + 1.142.0 test diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index a2fe523ec..b77249bb4 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -66,13 +66,13 @@ com.google.cloud google-cloud-pubsub - 1.141.5 + 1.142.0 test com.google.cloud google-cloud-kms - 2.78.0 + 2.80.0 test diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 87150d879..b0ad09d38 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -58,13 +58,13 @@ com.google.cloud google-cloud-pubsub - 1.141.5 + 1.142.0 test com.google.cloud google-cloud-kms - 2.78.0 + 2.80.0 test diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 51b4c27a1..36544329c 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -76,13 +76,13 @@ com.google.cloud google-cloud-pubsub - 1.141.5 + 1.142.0 test com.google.cloud google-cloud-kms - 2.78.0 + 2.80.0 test From e64565ab674f586ea4850408a3f30544997f4b1b Mon Sep 17 00:00:00 2001 From: Mend Renovate Date: Tue, 21 Oct 2025 16:53:13 +0100 Subject: [PATCH 15/18] deps: update dependency com.google.cloud:sdk-platform-java-config to v3.53.0 (#3351) --- .github/workflows/unmanaged_dependency_check.yaml | 2 +- google-cloud-storage-bom/pom.xml | 2 +- pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/unmanaged_dependency_check.yaml b/.github/workflows/unmanaged_dependency_check.yaml index b45989f80..756c7dfc7 100644 --- a/.github/workflows/unmanaged_dependency_check.yaml +++ b/.github/workflows/unmanaged_dependency_check.yaml @@ -17,6 +17,6 @@ jobs: # repository .kokoro/build.sh - name: Unmanaged dependency check - uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.52.3 + uses: googleapis/sdk-platform-java/java-shared-dependencies/unmanaged-dependency-check@google-cloud-shared-dependencies/v3.53.0 with: bom-path: google-cloud-storage-bom/pom.xml diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 286175ffc..0e212eead 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -24,7 +24,7 @@ com.google.cloud sdk-platform-java-config - 3.52.3 + 3.53.0 diff --git a/pom.xml b/pom.xml index 6836a0f77..63f3aa18a 100644 --- a/pom.xml +++ b/pom.xml @@ -14,7 +14,7 @@ com.google.cloud sdk-platform-java-config - 3.52.3 + 3.53.0 From 9f0a93eb4c6bb8aab13915ca1cb40ba9e229a2f9 Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 21 Oct 2025 14:44:09 -0400 Subject: [PATCH 16/18] fix: update BlobReadSession channels to not implicitly close once EOF is observed (#3344) --- .../BaseObjectReadSessionStreamRead.java | 18 +-- .../com/google/cloud/storage/GrpcUtils.java | 3 + .../ObjectReadSessionStreamReadTest.java | 28 +---- .../it/ITReadableByteChannelBehaviorTest.java | 104 ++++++++++++++++++ 4 files changed, 121 insertions(+), 32 deletions(-) create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java index 3ad80a198..dc71350d7 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BaseObjectReadSessionStreamRead.java @@ -343,12 +343,8 @@ public boolean canShareStreamWith(ObjectReadSessionStreamRead other) { @Override public void internalClose() throws IOException { if (!closed) { - retryContext.reset(); closed = true; - if (leftovers != null) { - leftovers.ref.close(); - } - GrpcUtils.closeAll(queue); + internalCleanup(); } } @@ -378,7 +374,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { throw new ClosedChannelException(); } if (complete) { - close(); + internalCleanup(); return -1; } @@ -406,7 +402,7 @@ public long read(ByteBuffer[] dsts, int offset, int length) throws IOException { } else if (poll == EofMarker.INSTANCE) { complete = true; if (read == 0) { - close(); + internalCleanup(); return -1; } break; @@ -442,6 +438,14 @@ private void offer(Closeable offer) throws InterruptedIOException { } } + private void internalCleanup() throws IOException { + retryContext.reset(); + if (leftovers != null) { + leftovers.ref.close(); + } + GrpcUtils.closeAll(queue); + } + /** * The queue items are added to is a queue of {@link Closeable}. This class smuggles a Throwable * in a no-op Closable, such that the throwable can be in the queue. diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java index 771797201..cbf5d3172 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcUtils.java @@ -70,6 +70,9 @@ static GrpcCallContext contextWithBucketName(String bucketName, GrpcCallContext * them all as suppressed exceptions on the first occurrence. */ static void closeAll(Collection closeables) throws IOException { + if (closeables.isEmpty()) { + return; + } IOException ioException = closeables.stream() .filter(Objects::nonNull) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java index 3e70f9c04..9db08fd63 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ObjectReadSessionStreamReadTest.java @@ -356,31 +356,9 @@ public void streamingRead_eofShouldBeReturnedIfNoOtherBytesRead() throws Excepti 1, RangeSpec.of(0, 137), Hasher.enabled(), RetryContext.neverRetry())) { read.eof(); assertThat(read.read(ByteBuffer.allocate(1))).isEqualTo(-1); - - assertAll( - () -> assertThrows(ClosedChannelException.class, () -> read.read((ByteBuffer) null)), - () -> assertThat(read.isOpen()).isFalse()); - } - } - - @Test - public void streamingRead_closedOnceEofIsRead() throws Exception { - try (StreamingRead read = - ObjectReadSessionStreamRead.streamingRead( - 1, RangeSpec.of(0, 137), Hasher.enabled(), RetryContext.neverRetry())) { - ByteString bytes1 = ByteString.copyFrom(DataGenerator.base64Characters().genBytes(62)); - try (ResponseContentLifecycleHandle handle = noopContentHandle(bytes1)) { - read.accept(handle.borrow(Function.identity())); - } - - ByteBuffer buf = ByteBuffer.allocate(512); - read.read(buf); - read.eof(); - assertThat(read.read(buf)).isEqualTo(-1); - - assertAll( - () -> assertThrows(ClosedChannelException.class, () -> read.read(buf)), - () -> assertThat(read.isOpen()).isFalse()); + assertThat(read.isOpen()).isTrue(); + read.close(); + assertThat(read.isOpen()).isFalse(); } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java new file mode 100644 index 000000000..b4082ed4f --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITReadableByteChannelBehaviorTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2025 Google LLC + * + * 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. + */ + +package com.google.cloud.storage.it; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import com.google.cloud.ReadChannel; +import com.google.cloud.storage.BlobId; +import com.google.cloud.storage.BlobReadSession; +import com.google.cloud.storage.BucketInfo; +import com.google.cloud.storage.ReadProjectionConfig; +import com.google.cloud.storage.ReadProjectionConfigs; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.TransportCompatibility.Transport; +import com.google.cloud.storage.it.runner.StorageITRunner; +import com.google.cloud.storage.it.runner.annotations.Backend; +import com.google.cloud.storage.it.runner.annotations.CrossRun; +import com.google.cloud.storage.it.runner.annotations.Inject; +import com.google.cloud.storage.it.runner.registry.Generator; +import com.google.cloud.storage.it.runner.registry.ObjectsFixture; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(StorageITRunner.class) +@CrossRun( + backends = {Backend.PROD}, + transports = {Transport.HTTP, Transport.GRPC}) +public final class ITReadableByteChannelBehaviorTest { + + @Inject public Storage storage; + @Inject public BucketInfo bucket; + @Inject public Generator generator; + @Inject public ObjectsFixture objectsFixture; + + @Test + public void eofReturnedMultipleTimes_reader() throws IOException { + BlobId id = objectsFixture.getObj512KiB().getInfo().getBlobId(); + + try (ReadChannel reader = storage.reader(id)) { + eofReturnedMultipleTimes_doTest(reader); + } + } + + @Test + @CrossRun.Exclude(transports = Transport.HTTP) + public void eofReturnedMultipleTimes_blobReadSession_channel() + throws ExecutionException, InterruptedException, TimeoutException, IOException { + eofReturnedMultipleTimes_doTestBlobReadSession(ReadProjectionConfigs.asChannel()); + } + + @Test + @CrossRun.Exclude(transports = Transport.HTTP) + public void eofReturnedMultipleTimes_blobReadSession_seekableChannel() + throws ExecutionException, InterruptedException, TimeoutException, IOException { + eofReturnedMultipleTimes_doTestBlobReadSession(ReadProjectionConfigs.asSeekableChannel()); + } + + private void eofReturnedMultipleTimes_doTestBlobReadSession( + ReadProjectionConfig config) + throws IOException, ExecutionException, InterruptedException, TimeoutException { + BlobId id = objectsFixture.getObj512KiB().getInfo().getBlobId(); + + try (BlobReadSession session = storage.blobReadSession(id).get(3, TimeUnit.SECONDS)) { + try (ReadableByteChannel c = session.readAs(config)) { + eofReturnedMultipleTimes_doTest(c); + } + } + } + + private void eofReturnedMultipleTimes_doTest(ReadableByteChannel c) throws IOException { + long copy = ByteStreams.copy(c, Channels.newChannel(ByteStreams.nullOutputStream())); + assertThat(copy).isEqualTo(objectsFixture.getObj512KiB().getInfo().getSize()); + + ByteBuffer buf = ByteBuffer.allocate(8); + int i = ThreadLocalRandom.current().nextInt(3, 10); + for (int j = 0; j < i; j++) { + assertWithMessage("expected EOF " + j).that(c.read(buf)).isEqualTo(-1); + } + } +} From 6eb33311d8dd7344e30ddcb92334fd52c7c63b4d Mon Sep 17 00:00:00 2001 From: BenWhitehead Date: Tue, 21 Oct 2025 14:44:29 -0400 Subject: [PATCH 17/18] fix: update retry logic for grpc start resumable upload to properly handle client side deadline_exceeded (#3354) --- .../storage/GapicUploadSessionBuilder.java | 54 ++++++++++++------- .../google/cloud/storage/GrpcStorageImpl.java | 18 +++---- .../cloud/storage/GrpcStorageOptions.java | 20 +++++-- .../com/google/cloud/storage/FakeServer.java | 2 +- .../GapicUploadSessionBuilderSyntaxTest.java | 22 +++++--- ...apicUnbufferedWritableByteChannelTest.java | 45 ++++++++++++++++ .../ITUnbufferedResumableUploadTest.java | 4 +- 7 files changed, 120 insertions(+), 45 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java index 3ae9b8bc2..1e4001fca 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GapicUploadSessionBuilder.java @@ -17,13 +17,14 @@ package com.google.cloud.storage; import com.google.api.core.ApiFuture; -import com.google.api.core.ApiFutures; +import com.google.api.core.SettableApiFuture; import com.google.api.gax.rpc.BidiStreamingCallable; import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.cloud.storage.Conversions.Decoder; +import com.google.cloud.storage.Retrying.RetrierWithAlg; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; import com.google.cloud.storage.UnifiedOpts.Opts; -import com.google.common.util.concurrent.MoreExecutors; import com.google.storage.v2.BidiWriteObjectRequest; import com.google.storage.v2.BidiWriteObjectResponse; import com.google.storage.v2.StartResumableWriteRequest; @@ -53,7 +54,8 @@ GapicBidiWritableByteChannelSessionBuilder bidiByteChannel( ApiFuture resumableWrite( UnaryCallable callable, WriteObjectRequest writeObjectRequest, - Opts opts) { + Opts opts, + RetrierWithAlg retrier) { StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder(); if (writeObjectRequest.hasWriteObjectSpec()) { b.setWriteObjectSpec(writeObjectRequest.getWriteObjectSpec()); @@ -68,23 +70,27 @@ ApiFuture resumableWrite( Function f = uploadId -> writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build(); - ApiFuture futureResumableWrite = - ApiFutures.transform( - callable.futureCall(req), - (resp) -> new ResumableWrite(req, resp, f), - MoreExecutors.directExecutor()); - // make sure we wrap any failure as a storage exception - return ApiFutures.catchingAsync( - futureResumableWrite, - Throwable.class, - throwable -> ApiFutures.immediateFailedFuture(StorageException.coalesce(throwable)), - MoreExecutors.directExecutor()); + SettableApiFuture future = SettableApiFuture.create(); + try { + ResumableWrite resumableWrite = + retrier.run( + () -> { + StartResumableWriteResponse resp = callable.call(req); + return new ResumableWrite(req, resp, f); + }, + Decoder.identity()); + future.set(resumableWrite); + } catch (StorageException e) { + future.setException(e); + } + return future; } ApiFuture bidiResumableWrite( UnaryCallable x, BidiWriteObjectRequest writeObjectRequest, - Opts opts) { + Opts opts, + RetrierWithAlg retrier) { StartResumableWriteRequest.Builder b = StartResumableWriteRequest.newBuilder(); if (writeObjectRequest.hasWriteObjectSpec()) { b.setWriteObjectSpec(writeObjectRequest.getWriteObjectSpec()); @@ -99,9 +105,19 @@ ApiFuture bidiResumableWrite( Function f = uploadId -> writeObjectRequest.toBuilder().clearWriteObjectSpec().setUploadId(uploadId).build(); - return ApiFutures.transform( - x.futureCall(req), - (resp) -> new BidiResumableWrite(req, resp, f), - MoreExecutors.directExecutor()); + SettableApiFuture future = SettableApiFuture.create(); + try { + BidiResumableWrite resumableWrite = + retrier.run( + () -> { + StartResumableWriteResponse resp = x.call(req); + return new BidiResumableWrite(req, resp, f); + }, + Decoder.identity()); + future.set(resumableWrite); + } catch (StorageException e) { + future.setException(e); + } + return future; } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java index ce3e6c62d..d79b30e1f 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageImpl.java @@ -21,7 +21,6 @@ import static com.google.cloud.storage.ByteSizeConstants._256KiB; import static com.google.cloud.storage.CrossTransportUtils.fmtMethodName; import static com.google.cloud.storage.CrossTransportUtils.throwHttpJsonOnly; -import static com.google.cloud.storage.GrpcToHttpStatusCodeTranslation.resultRetryAlgorithmToCodes; import static com.google.cloud.storage.StorageV2ProtoUtils.bucketAclEntityOrAltEq; import static com.google.cloud.storage.StorageV2ProtoUtils.objectAclEntityOrAltEq; import static com.google.cloud.storage.Utils.bucketNameCodec; @@ -42,7 +41,6 @@ import com.google.api.gax.rpc.ApiExceptions; import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.NotFoundException; -import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.rpc.UnaryCallable; import com.google.cloud.BaseService; import com.google.cloud.Policy; @@ -1831,30 +1829,26 @@ private UnbufferedReadableByteChannelSession unbufferedReadSession( @VisibleForTesting ApiFuture startResumableWrite( GrpcCallContext grpcCallContext, WriteObjectRequest req, Opts opts) { - Set codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(req)); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); return ResumableMedia.gapic() .write() .resumableWrite( - storageClient - .startResumableWriteCallable() - .withDefaultCallContext(merge.withRetryableCodes(codes)), + storageClient.startResumableWriteCallable().withDefaultCallContext(merge), req, - opts); + opts, + retrier.withAlg(retryAlgorithmManager.getFor(req))); } ApiFuture startResumableWrite( GrpcCallContext grpcCallContext, BidiWriteObjectRequest req, Opts opts) { - Set codes = resultRetryAlgorithmToCodes(retryAlgorithmManager.getFor(req)); GrpcCallContext merge = Utils.merge(grpcCallContext, Retrying.newCallContext()); return ResumableMedia.gapic() .write() .bidiResumableWrite( - storageClient - .startResumableWriteCallable() - .withDefaultCallContext(merge.withRetryableCodes(codes)), + storageClient.startResumableWriteCallable().withDefaultCallContext(merge), req, - opts); + opts, + retrier.withAlg(retryAlgorithmManager.getFor(req))); } private SourceObject sourceObjectEncode(SourceBlob from) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java index a102125e9..54dd12519 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/GrpcStorageOptions.java @@ -45,7 +45,6 @@ import com.google.api.gax.rpc.RequestParamsBuilder; import com.google.api.gax.rpc.RequestParamsExtractor; import com.google.api.gax.rpc.ServerStreamingCallable; -import com.google.api.gax.rpc.StatusCode.Code; import com.google.api.gax.rpc.internal.QuotaProjectIdHidingCredentials; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.pathtemplate.PathTemplate; @@ -109,6 +108,7 @@ import java.net.URI; import java.nio.ByteBuffer; import java.time.Clock; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -360,8 +360,6 @@ private Tuple> resolveSettingsAndOpts() throw .setLogicalTimeout(java.time.Duration.ofDays(28)) .build(); java.time.Duration totalTimeout = baseRetrySettings.getTotalTimeoutDuration(); - Set startResumableWriteRetryableCodes = - builder.startResumableWriteSettings().getRetryableCodes(); // retries for unary methods are generally handled at a different level, except // StartResumableWrite @@ -372,10 +370,22 @@ private Tuple> resolveSettingsAndOpts() throw }); // configure the settings for StartResumableWrite + Duration startResumableTimeoutDuration; + // the default for initialRpcTimeout is the same as totalTimeout. This is not good, because it + // will prevent our retries from even happening. + // If the default values is used, set our per-rpc timeout to 20 seconds to allow our retries + // a chance. + if (baseRetrySettings + .getInitialRpcTimeoutDuration() + .equals(getDefaultRetrySettings().getInitialRpcTimeoutDuration())) { + startResumableTimeoutDuration = Duration.ofSeconds(20); + } else { + startResumableTimeoutDuration = baseRetrySettings.getInitialRpcTimeoutDuration(); + } builder .startResumableWriteSettings() - .setRetrySettings(baseRetrySettings) - .setRetryableCodes(startResumableWriteRetryableCodes); + // set this lower, to allow our retries a chance instead of it being totalTimeout + .setSimpleTimeoutNoRetriesDuration(startResumableTimeoutDuration); // for ReadObject disable retries and move the total timeout to the idle timeout builder .readObjectSettings() diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeServer.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeServer.java index d4ba66360..0481f2b06 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeServer.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/FakeServer.java @@ -75,7 +75,7 @@ static FakeServer of(StorageGrpc.StorageImplBase service) throws IOException { .setRetryDelayMultiplier(1.2) .setMaxRetryDelayDuration(Duration.ofSeconds(16)) .setMaxAttempts(6) - .setInitialRpcTimeoutDuration(Duration.ofSeconds(25)) + .setInitialRpcTimeoutDuration(Duration.ofSeconds(1)) .setRpcTimeoutMultiplier(1.0) .setMaxRpcTimeoutDuration(Duration.ofSeconds(25)) .build()) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicUploadSessionBuilderSyntaxTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicUploadSessionBuilderSyntaxTest.java index e35278b5b..dddd5b8e1 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicUploadSessionBuilderSyntaxTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/GapicUploadSessionBuilderSyntaxTest.java @@ -20,9 +20,9 @@ import static org.mockito.Mockito.when; import com.google.api.core.ApiFuture; -import com.google.api.core.ApiFutures; import com.google.api.gax.rpc.ClientStreamingCallable; import com.google.api.gax.rpc.UnaryCallable; +import com.google.cloud.storage.Retrying.RetrierWithAlg; import com.google.cloud.storage.UnifiedOpts.Opts; import com.google.storage.v2.StartResumableWriteRequest; import com.google.storage.v2.StartResumableWriteResponse; @@ -60,8 +60,8 @@ public final class GapicUploadSessionBuilderSyntaxTest { @Before public void setUp() throws Exception { - when(startResumableWrite.futureCall(any())) - .thenReturn(ApiFutures.immediateFuture(StartResumableWriteResponse.getDefaultInstance())); + when(startResumableWrite.call(any())) + .thenReturn(StartResumableWriteResponse.getDefaultInstance()); } @Test @@ -95,7 +95,9 @@ public void syntax_directBuffered_fluent() { @Test public void syntax_resumableUnbuffered_fluent() { ApiFuture startAsync = - ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty()); + ResumableMedia.gapic() + .write() + .resumableWrite(startResumableWrite, req, Opts.empty(), RetrierWithAlg.attemptOnce()); UnbufferedWritableByteChannelSession session = ResumableMedia.gapic() .write() @@ -111,7 +113,9 @@ public void syntax_resumableUnbuffered_fluent() { @Test public void syntax_resumableBuffered_fluent() { ApiFuture startAsync = - ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty()); + ResumableMedia.gapic() + .write() + .resumableWrite(startResumableWrite, req, Opts.empty(), RetrierWithAlg.attemptOnce()); BufferedWritableByteChannelSession session = ResumableMedia.gapic() .write() @@ -151,7 +155,9 @@ public void syntax_directBuffered_incremental() { @Test public void syntax_resumableUnbuffered_incremental() { ApiFuture startAsync = - ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty()); + ResumableMedia.gapic() + .write() + .resumableWrite(startResumableWrite, req, Opts.empty(), RetrierWithAlg.attemptOnce()); GapicWritableByteChannelSessionBuilder b1 = ResumableMedia.gapic() .write() @@ -165,7 +171,9 @@ public void syntax_resumableUnbuffered_incremental() { @Test public void syntax_resumableBuffered_incremental() { ApiFuture startAsync = - ResumableMedia.gapic().write().resumableWrite(startResumableWrite, req, Opts.empty()); + ResumableMedia.gapic() + .write() + .resumableWrite(startResumableWrite, req, Opts.empty(), RetrierWithAlg.attemptOnce()); GapicWritableByteChannelSessionBuilder b1 = ResumableMedia.gapic() .write() diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java index e23b2a57f..3aa567a4d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITGapicUnbufferedWritableByteChannelTest.java @@ -20,9 +20,12 @@ import static com.google.cloud.storage.TestUtils.getChecksummedData; import static com.google.common.truth.Truth.assertThat; +import com.google.api.core.ApiFuture; import com.google.api.core.SettableApiFuture; +import com.google.api.gax.grpc.GrpcCallContext; import com.google.api.gax.rpc.PermissionDeniedException; import com.google.cloud.storage.Retrying.RetrierWithAlg; +import com.google.cloud.storage.UnifiedOpts.Opts; import com.google.cloud.storage.WriteCtx.SimpleWriteObjectRequestBuilderFactory; import com.google.cloud.storage.WriteCtx.WriteObjectRequestBuilderFactory; import com.google.common.collect.ImmutableList; @@ -47,7 +50,10 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; @@ -210,6 +216,45 @@ public void resumableUpload() throws IOException, InterruptedException, Executio } } + @Test + public void startResumableUpload_deadlineExceeded_isRetried() + throws IOException, InterruptedException, ExecutionException, TimeoutException { + + String uploadId = UUID.randomUUID().toString(); + AtomicInteger callCount = new AtomicInteger(0); + StorageImplBase service = + new StorageImplBase() { + @Override + public void startResumableWrite( + StartResumableWriteRequest req, StreamObserver respond) { + if (callCount.getAndIncrement() > 0) { + respond.onNext( + StartResumableWriteResponse.newBuilder().setUploadId(uploadId).build()); + respond.onCompleted(); + } + } + }; + try (FakeServer fake = FakeServer.of(service)) { + GrpcStorageImpl gsi = (GrpcStorageImpl) fake.getGrpcStorageOptions().getService(); + ApiFuture f = + gsi.startResumableWrite( + GrpcCallContext.createDefault(), + WriteObjectRequest.newBuilder() + .setWriteObjectSpec( + WriteObjectSpec.newBuilder() + .setResource( + Object.newBuilder().setBucket("bucket").setName("name").build()) + .setIfGenerationMatch(0) + .build()) + .build(), + Opts.empty()); + + ResumableWrite resumableWrite = f.get(2, TimeUnit.MINUTES); + assertThat(callCount.get()).isEqualTo(2); + assertThat(resumableWrite.newBuilder().build().getUploadId()).isEqualTo(uploadId); + } + } + @Test public void resumableUpload_chunkAutomaticRetry() throws IOException, InterruptedException, ExecutionException { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITUnbufferedResumableUploadTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITUnbufferedResumableUploadTest.java index 69b876ad3..59f694fe3 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/ITUnbufferedResumableUploadTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/ITUnbufferedResumableUploadTest.java @@ -24,6 +24,7 @@ import com.google.api.services.storage.model.StorageObject; import com.google.cloud.storage.ITUnbufferedResumableUploadTest.ObjectSizes; import com.google.cloud.storage.Retrying.Retrier; +import com.google.cloud.storage.Retrying.RetrierWithAlg; import com.google.cloud.storage.TransportCompatibility.Transport; import com.google.cloud.storage.UnbufferedWritableByteChannelSession.UnbufferedWritableByteChannel; import com.google.cloud.storage.UnifiedOpts.ObjectTargetOpt; @@ -241,7 +242,8 @@ private UnbufferedWritableByteChannelSession grpcSession() .resumableWrite( storageClient.startResumableWriteCallable().withDefaultCallContext(merge), request, - opts); + opts, + RetrierWithAlg.attemptOnce()); return ResumableMedia.gapic() .write() From 5e7bc060c78a371ce2158b99e7306fae741d2e29 Mon Sep 17 00:00:00 2001 From: "release-please[bot]" <55107282+release-please[bot]@users.noreply.github.com> Date: Tue, 21 Oct 2025 15:20:56 -0400 Subject: [PATCH 18/18] chore(main): release 2.59.0 (#3335) Co-authored-by: release-please[bot] <55107282+release-please[bot]@users.noreply.github.com> Co-authored-by: cloud-java-bot --- CHANGELOG.md | 21 +++++++++++++++++++ README.md | 11 ++++++---- gapic-google-cloud-storage-v2/pom.xml | 4 ++-- google-cloud-storage-bom/pom.xml | 16 +++++++------- google-cloud-storage-control/pom.xml | 4 ++-- google-cloud-storage/pom.xml | 4 ++-- .../storage/testing/RemoteStorageHelper.java | 3 ++- grpc-google-cloud-storage-control-v2/pom.xml | 4 ++-- grpc-google-cloud-storage-v2/pom.xml | 4 ++-- pom.xml | 16 +++++++------- proto-google-cloud-storage-control-v2/pom.xml | 4 ++-- proto-google-cloud-storage-v2/pom.xml | 4 ++-- samples/snapshot/pom.xml | 6 +++--- storage-shared-benchmarking/pom.xml | 4 ++-- versions.txt | 14 ++++++------- 15 files changed, 72 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eadeca9fa..2766dc4e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ # Changelog +## [2.59.0](https://github.com/googleapis/java-storage/compare/v2.58.1...v2.59.0) (2025-10-21) + + +### Features + +* Add per-message checksum validation for gRPC ReadObject operations ([#3336](https://github.com/googleapis/java-storage/issues/3336)) ([6eef1b0](https://github.com/googleapis/java-storage/commit/6eef1b0f587b9f32041ac4bcef1a16b1b0bc4bb3)) + + +### Bug Fixes + +* Add case insensitive check for X-Goog-Content-SHA256 in SignatureInfo ([#3337](https://github.com/googleapis/java-storage/issues/3337)) ([54bc2c1](https://github.com/googleapis/java-storage/commit/54bc2c12f2d0e8c164e4ddcaa1a61d2de3911131)) +* Migrate away from GoogleCredentials.fromStream() usages ([#3339](https://github.com/googleapis/java-storage/issues/3339)) ([7e42c2f](https://github.com/googleapis/java-storage/commit/7e42c2fbca53ca6b1266f784e58cee00cfed7d62)) +* Update BlobReadSession channels to not implicitly close once EOF is observed ([#3344](https://github.com/googleapis/java-storage/issues/3344)) ([9f0a93e](https://github.com/googleapis/java-storage/commit/9f0a93eb4c6bb8aab13915ca1cb40ba9e229a2f9)) +* Update grpc single-shot uploads to attach the callers stracktrace as suppressed exception if an error happens in the background ([#3330](https://github.com/googleapis/java-storage/issues/3330)) ([64e2b2e](https://github.com/googleapis/java-storage/commit/64e2b2ef839e69da0605b9e53989c1f5a2b09e66)) +* Update retry logic for grpc start resumable upload to properly handle client side deadline_exceeded ([#3354](https://github.com/googleapis/java-storage/issues/3354)) ([6eb3331](https://github.com/googleapis/java-storage/commit/6eb33311d8dd7344e30ddcb92334fd52c7c63b4d)) + + +### Dependencies + +* Update dependency com.google.cloud:sdk-platform-java-config to v3.53.0 ([#3351](https://github.com/googleapis/java-storage/issues/3351)) ([e64565a](https://github.com/googleapis/java-storage/commit/e64565ab674f586ea4850408a3f30544997f4b1b)) + ## [2.58.1](https://github.com/googleapis/java-storage/compare/v2.58.0...v2.58.1) (2025-10-06) diff --git a/README.md b/README.md index 64a76e3ae..78d10b82b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud libraries-bom - 26.69.0 + 26.70.0 pom import @@ -66,13 +66,13 @@ implementation 'com.google.cloud:google-cloud-storage' If you are using Gradle without BOM, add this to your dependencies: ```Groovy -implementation 'com.google.cloud:google-cloud-storage:2.58.1' +implementation 'com.google.cloud:google-cloud-storage:2.59.0' ``` If you are using SBT, add this to your dependencies: ```Scala -libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.58.1" +libraryDependencies += "com.google.cloud" % "google-cloud-storage" % "2.59.0" ``` ## Authentication @@ -342,7 +342,9 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/ | Generate Encryption Key | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/GenerateEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/GenerateEncryptionKey.java) | | Generate V4 Get Object Signed Url | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/GenerateV4GetObjectSignedUrl.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/GenerateV4GetObjectSignedUrl.java) | | Generate V4 Put Object Signed Url | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/GenerateV4PutObjectSignedUrl.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/GenerateV4PutObjectSignedUrl.java) | +| Get Object Contexts | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/GetObjectContexts.java) | | Get Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/GetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/GetObjectMetadata.java) | +| List Object Contexts | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListObjectContexts.java) | | List Objects | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListObjects.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListObjects.java) | | List Objects With Old Versions | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListObjectsWithOldVersions.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListObjectsWithOldVersions.java) | | List Objects With Prefix | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/ListObjectsWithPrefix.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/ListObjectsWithPrefix.java) | @@ -363,6 +365,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-storage/tree/ | Restore Soft Deleted Object | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RestoreSoftDeletedObject.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RestoreSoftDeletedObject.java) | | Rotate Object Encryption Key | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/RotateObjectEncryptionKey.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/RotateObjectEncryptionKey.java) | | Set Event Based Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetEventBasedHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetEventBasedHold.java) | +| Set Object Contexts | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectContexts.java) | | Set Object Metadata | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectMetadata.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectMetadata.java) | | Set Object Retention Policy | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetObjectRetentionPolicy.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetObjectRetentionPolicy.java) | | Set Temporary Hold | [source code](https://github.com/googleapis/java-storage/blob/main/samples/snippets/src/main/java/com/example/storage/object/SetTemporaryHold.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-storage&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/storage/object/SetTemporaryHold.java) | @@ -481,7 +484,7 @@ Java is a registered trademark of Oracle and/or its affiliates. [kokoro-badge-link-5]: http://storage.googleapis.com/cloud-devrel-public/java/badges/java-storage/java11.html [stability-image]: https://img.shields.io/badge/stability-stable-green [maven-version-image]: https://img.shields.io/maven-central/v/com.google.cloud/google-cloud-storage.svg -[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.58.1 +[maven-version-link]: https://central.sonatype.com/artifact/com.google.cloud/google-cloud-storage/2.59.0 [authentication]: https://github.com/googleapis/google-cloud-java#authentication [auth-scopes]: https://developers.google.com/identity/protocols/oauth2/scopes [predefined-iam-roles]: https://cloud.google.com/iam/docs/understanding-roles#predefined_roles diff --git a/gapic-google-cloud-storage-v2/pom.xml b/gapic-google-cloud-storage-v2/pom.xml index bebf7a572..d391400bd 100644 --- a/gapic-google-cloud-storage-v2/pom.xml +++ b/gapic-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 gapic-google-cloud-storage-v2 GRPC library for gapic-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/google-cloud-storage-bom/pom.xml b/google-cloud-storage-bom/pom.xml index 0e212eead..4d5e1276d 100644 --- a/google-cloud-storage-bom/pom.xml +++ b/google-cloud-storage-bom/pom.xml @@ -19,7 +19,7 @@ 4.0.0 com.google.cloud google-cloud-storage-bom - 2.58.2-SNAPSHOT + 2.59.0 pom com.google.cloud @@ -69,37 +69,37 @@ com.google.cloud google-cloud-storage - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.cloud google-cloud-storage-control - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/google-cloud-storage-control/pom.xml b/google-cloud-storage-control/pom.xml index 36fa826cf..932f08ab6 100644 --- a/google-cloud-storage-control/pom.xml +++ b/google-cloud-storage-control/pom.xml @@ -5,13 +5,13 @@ 4.0.0 com.google.cloud google-cloud-storage-control - 2.58.2-SNAPSHOT + 2.59.0 google-cloud-storage-control GRPC library for google-cloud-storage-control com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index d161cbb0c..bfa8010af 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -2,7 +2,7 @@ 4.0.0 google-cloud-storage - 2.58.2-SNAPSHOT + 2.59.0 jar Google Cloud Storage https://github.com/googleapis/java-storage @@ -12,7 +12,7 @@ com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 google-cloud-storage diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java index 606ed1226..404564174 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/RemoteStorageHelper.java @@ -217,7 +217,8 @@ public static String generateBucketName() { * keyStream} is not a valid JSON key stream */ @ObsoleteApi( - "This method is obsolete because of a potential security risk. Use the create() variant with Credential parameter instead") + "This method is obsolete because of a potential security risk. Use the create() variant with" + + " Credential parameter instead") public static RemoteStorageHelper create(String projectId, InputStream keyStream) throws StorageHelperException { try { diff --git a/grpc-google-cloud-storage-control-v2/pom.xml b/grpc-google-cloud-storage-control-v2/pom.xml index a1d12db85..9d25ee46c 100644 --- a/grpc-google-cloud-storage-control-v2/pom.xml +++ b/grpc-google-cloud-storage-control-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.58.2-SNAPSHOT + 2.59.0 grpc-google-cloud-storage-control-v2 GRPC library for google-cloud-storage com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/grpc-google-cloud-storage-v2/pom.xml b/grpc-google-cloud-storage-v2/pom.xml index 67fa24cff..9d5fb6468 100644 --- a/grpc-google-cloud-storage-v2/pom.xml +++ b/grpc-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 grpc-google-cloud-storage-v2 GRPC library for grpc-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/pom.xml b/pom.xml index 63f3aa18a..a9ef21368 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.google.cloud google-cloud-storage-parent pom - 2.58.2-SNAPSHOT + 2.59.0 Storage Parent https://github.com/googleapis/java-storage @@ -82,7 +82,7 @@ com.google.cloud google-cloud-storage - 2.58.2-SNAPSHOT + 2.59.0 com.google.apis @@ -104,32 +104,32 @@ com.google.api.grpc proto-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc grpc-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc gapic-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc grpc-google-cloud-storage-control-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.58.2-SNAPSHOT + 2.59.0 com.google.cloud google-cloud-storage-control - 2.58.2-SNAPSHOT + 2.59.0 com.google.cloud diff --git a/proto-google-cloud-storage-control-v2/pom.xml b/proto-google-cloud-storage-control-v2/pom.xml index 2038a1286..a258e6b24 100644 --- a/proto-google-cloud-storage-control-v2/pom.xml +++ b/proto-google-cloud-storage-control-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-control-v2 - 2.58.2-SNAPSHOT + 2.59.0 proto-google-cloud-storage-control-v2 Proto library for proto-google-cloud-storage-control-v2 com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/proto-google-cloud-storage-v2/pom.xml b/proto-google-cloud-storage-v2/pom.xml index b7630f746..7788c504e 100644 --- a/proto-google-cloud-storage-v2/pom.xml +++ b/proto-google-cloud-storage-v2/pom.xml @@ -4,13 +4,13 @@ 4.0.0 com.google.api.grpc proto-google-cloud-storage-v2 - 2.58.2-SNAPSHOT + 2.59.0 proto-google-cloud-storage-v2 PROTO library for proto-google-cloud-storage-v2 com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index b0ad09d38..2ac6fe76f 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -28,12 +28,12 @@ com.google.cloud google-cloud-storage - 2.58.2-SNAPSHOT + 2.59.0 com.google.cloud google-cloud-storage-control - 2.58.2-SNAPSHOT + 2.59.0 compile @@ -70,7 +70,7 @@ com.google.cloud google-cloud-storage - 2.58.2-SNAPSHOT + 2.59.0 tests test diff --git a/storage-shared-benchmarking/pom.xml b/storage-shared-benchmarking/pom.xml index 0f68aa834..2379fec2f 100644 --- a/storage-shared-benchmarking/pom.xml +++ b/storage-shared-benchmarking/pom.xml @@ -10,7 +10,7 @@ com.google.cloud google-cloud-storage-parent - 2.58.2-SNAPSHOT + 2.59.0 @@ -31,7 +31,7 @@ com.google.cloud google-cloud-storage - 2.58.2-SNAPSHOT + 2.59.0 tests diff --git a/versions.txt b/versions.txt index 353146a92..e73ee03ed 100644 --- a/versions.txt +++ b/versions.txt @@ -1,10 +1,10 @@ # Format: # module:released-version:current-version -google-cloud-storage:2.58.1:2.58.2-SNAPSHOT -gapic-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT -grpc-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT -proto-google-cloud-storage-v2:2.58.1:2.58.2-SNAPSHOT -google-cloud-storage-control:2.58.1:2.58.2-SNAPSHOT -proto-google-cloud-storage-control-v2:2.58.1:2.58.2-SNAPSHOT -grpc-google-cloud-storage-control-v2:2.58.1:2.58.2-SNAPSHOT +google-cloud-storage:2.59.0:2.59.0 +gapic-google-cloud-storage-v2:2.59.0:2.59.0 +grpc-google-cloud-storage-v2:2.59.0:2.59.0 +proto-google-cloud-storage-v2:2.59.0:2.59.0 +google-cloud-storage-control:2.59.0:2.59.0 +proto-google-cloud-storage-control-v2:2.59.0:2.59.0 +grpc-google-cloud-storage-control-v2:2.59.0:2.59.0