diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java index 6aa7aaec9b..361de50c88 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Bucket.java @@ -744,6 +744,24 @@ public Builder setSoftDeletePolicy(SoftDeletePolicy softDeletePolicy) { return this; } + @Override + BucketInfo.Builder setGeneration(long generation) { + infoBuilder.setGeneration(generation); + return this; + } + + @Override + BucketInfo.Builder setSoftDeleteTime(OffsetDateTime softDeleteTime) { + infoBuilder.setSoftDeleteTime(softDeleteTime); + return this; + } + + @Override + BucketInfo.Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) { + infoBuilder.setHardDeleteTime(hardDeleteTime); + return this; + } + @Override public Builder setHierarchicalNamespace(HierarchicalNamespace hierarchicalNamespace) { infoBuilder.setHierarchicalNamespace(hierarchicalNamespace); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java index 3235559222..6f7920369c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/BucketInfo.java @@ -119,8 +119,10 @@ public class BucketInfo implements Serializable { private final CustomPlacementConfig customPlacementConfig; private final ObjectRetention objectRetention; private final HierarchicalNamespace hierarchicalNamespace; - private final SoftDeletePolicy softDeletePolicy; + private final long generation; + private final OffsetDateTime softDeleteTime; + private final OffsetDateTime hardDeleteTime; private final transient ImmutableSet modifiedFields; @@ -1841,6 +1843,12 @@ public Builder setRetentionPeriodDuration(Duration retentionPeriod) { public abstract Builder setSoftDeletePolicy(SoftDeletePolicy softDeletePolicy); + abstract Builder setGeneration(long generation); + + abstract Builder setSoftDeleteTime(OffsetDateTime softDeleteTime); + + abstract Builder setHardDeleteTime(OffsetDateTime hardDeleteTime); + /** Creates a {@code BucketInfo} object. */ public abstract BucketInfo build(); @@ -1939,9 +1947,11 @@ static final class BuilderImpl extends Builder { private Logging logging; private CustomPlacementConfig customPlacementConfig; private ObjectRetention objectRetention; - private SoftDeletePolicy softDeletePolicy; private HierarchicalNamespace hierarchicalNamespace; + private long generation; + private OffsetDateTime softDeleteTime; + private OffsetDateTime hardDeleteTime; private final ImmutableSet.Builder modifiedFields = ImmutableSet.builder(); BuilderImpl(String name) { @@ -1983,6 +1993,9 @@ static final class BuilderImpl extends Builder { objectRetention = bucketInfo.objectRetention; softDeletePolicy = bucketInfo.softDeletePolicy; hierarchicalNamespace = bucketInfo.hierarchicalNamespace; + generation = bucketInfo.getGeneration(); + softDeleteTime = bucketInfo.getSoftDeleteTime(); + hardDeleteTime = bucketInfo.getHardDeleteTime(); } @Override @@ -2366,6 +2379,33 @@ public Builder setHierarchicalNamespace(HierarchicalNamespace hierarchicalNamesp return this; } + @Override + Builder setGeneration(long generation) { + if (!Objects.equals(this.generation, generation)) { + modifiedFields.add(BucketField.GENERATION); + } + this.generation = generation; + return this; + } + + @Override + Builder setSoftDeleteTime(OffsetDateTime softDeleteTime) { + if (!Objects.equals(this.softDeleteTime, softDeleteTime)) { + modifiedFields.add(BucketField.SOFT_DELETE_TIME); + } + this.softDeleteTime = softDeleteTime; + return this; + } + + @Override + Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) { + if (!Objects.equals(this.hardDeleteTime, hardDeleteTime)) { + modifiedFields.add(BucketField.HARD_DELETE_TIME); + } + this.hardDeleteTime = hardDeleteTime; + return this; + } + @Override Builder setLocationType(String locationType) { if (!Objects.equals(this.locationType, locationType)) { @@ -2609,6 +2649,9 @@ private Builder clearDeleteLifecycleRules() { objectRetention = builder.objectRetention; softDeletePolicy = builder.softDeletePolicy; hierarchicalNamespace = builder.hierarchicalNamespace; + generation = builder.generation; + softDeleteTime = builder.softDeleteTime; + hardDeleteTime = builder.hardDeleteTime; modifiedFields = builder.modifiedFields.build(); } @@ -2959,6 +3002,21 @@ public HierarchicalNamespace getHierarchicalNamespace() { return hierarchicalNamespace; } + /** Returns the generation of this bucket */ + public long getGeneration() { + return generation; + } + + /** If this bucket is soft-deleted, returns the time it was soft-deleted */ + public OffsetDateTime getSoftDeleteTime() { + return softDeleteTime; + } + + /** If this bucket is soft-deleted, returns the time it will be hard-deleted */ + public OffsetDateTime getHardDeleteTime() { + return hardDeleteTime; + } + /** Returns a builder for the current bucket. */ public Builder toBuilder() { return new BuilderImpl(this); @@ -2998,6 +3056,9 @@ public int hashCode() { objectRetention, softDeletePolicy, hierarchicalNamespace, + generation, + softDeleteTime, + hardDeleteTime, logging); } @@ -3041,6 +3102,9 @@ public boolean equals(Object o) { && Objects.equals(objectRetention, that.objectRetention) && Objects.equals(softDeletePolicy, that.softDeletePolicy) && Objects.equals(hierarchicalNamespace, that.hierarchicalNamespace) + && Objects.equals(generation, that.getGeneration()) + && Objects.equals(softDeleteTime, that.getSoftDeleteTime()) + && Objects.equals(hardDeleteTime, that.getHardDeleteTime()) && Objects.equals(logging, that.logging); } 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 d0003c7119..9c409fba0f 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 @@ -415,6 +415,11 @@ public Blob restore(BlobId blob, BlobRestoreOption... options) { return internalObjectRestore(blob, unwrap); } + @Override + public void restore(String bucket, long generation, BucketRestoreOption... options) { + // todo: implement when grpc is available + } + private Blob internalObjectRestore(BlobId blobId, Opts opts) { Opts finalOpts = opts.prepend(defaultOpts).prepend(ALL_BLOB_FIELDS); GrpcCallContext grpcCallContext = diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java index b2315cd825..629c57dd03 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/HttpRetryAlgorithmManager.java @@ -218,6 +218,11 @@ public ResultRetryAlgorithm getForObjectsRestore( return retryStrategy.getIdempotentHandler(); } + public ResultRetryAlgorithm getForBucketRestore( + String bucket, Map optionsMap) { + return retryStrategy.getIdempotentHandler(); + } + public ResultRetryAlgorithm getForObjectsUpdate( StorageObject pb, Map optionsMap) { return optionsMap.containsKey(StorageRpc.Option.IF_METAGENERATION_MATCH) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonConversions.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonConversions.java index 12562cb020..e686cc0f08 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonConversions.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/JsonConversions.java @@ -410,6 +410,9 @@ private Bucket bucketInfoEncode(BucketInfo from) { ifNonNull(from.getStorageClass(), StorageClass::toString, to::setStorageClass); ifNonNull(from.getUpdateTimeOffsetDateTime(), dateTimeCodec::encode, to::setUpdated); ifNonNull(from.versioningEnabled(), b -> new Versioning().setEnabled(b), to::setVersioning); + ifNonNull(from.getGeneration(), to::setGeneration); + ifNonNull(from.getSoftDeleteTime(), dateTimeCodec::encode, to::setSoftDeleteTime); + ifNonNull(from.getHardDeleteTime(), dateTimeCodec::encode, to::setHardDeleteTime); to.setEtag(from.getEtag()); to.setId(from.getGeneratedId()); to.setName(from.getName()); @@ -527,6 +530,9 @@ private BucketInfo bucketInfoDecode(com.google.api.services.storage.model.Bucket to::setHierarchicalNamespace); ifNonNull(from.getObjectRetention(), this::objectRetentionDecode, to::setObjectRetention); ifNonNull(from.getSoftDeletePolicy(), this::softDeletePolicyDecode, to::setSoftDeletePolicy); + ifNonNull(from.getGeneration(), to::setGeneration); + ifNonNull(from.getSoftDeleteTime(), dateTimeCodec::decode, to::setSoftDeleteTime); + ifNonNull(from.getHardDeleteTime(), dateTimeCodec::decode, to::setHardDeleteTime); return to.build(); } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 8b719396a6..9bebf8fbb2 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -22,6 +22,7 @@ import static com.google.common.base.Preconditions.checkNotNull; import static java.util.Objects.requireNonNull; +import com.google.api.client.util.DateTime; import com.google.api.core.BetaApi; import com.google.api.core.InternalApi; import com.google.api.core.InternalExtensionOnly; @@ -187,7 +188,16 @@ enum BucketField implements FieldSelector, NamedField { SOFT_DELETE_POLICY( "softDeletePolicy", "soft_delete_policy", - com.google.api.services.storage.model.Bucket.SoftDeletePolicy.class); + com.google.api.services.storage.model.Bucket.SoftDeletePolicy.class), + + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + GENERATION("generation", "generation", Long.class), + + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + SOFT_DELETE_TIME("softDeleteTime", "soft_delete_time", DateTime.class), + + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + HARD_DELETE_TIME("hardDeleteTime", "hard_delete_time", DateTime.class); static final List REQUIRED_FIELDS = ImmutableList.of(NAME); private static final Map JSON_FIELD_NAME_INDEX; @@ -910,6 +920,21 @@ public static BucketGetOption userProject(@NonNull String userProject) { return new BucketGetOption(UnifiedOpts.userProject(userProject)); } + /** + * Returns an option for this bucket's generation. Should only be specified when getting a + * soft-deleted bucket + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketGetOption generation(long generation) { + return new BucketGetOption(UnifiedOpts.generation(generation)); + } + + /** Returns an option that must be true if getting a soft-deleted bucket. */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketGetOption softDeleted(boolean softDeleted) { + return new BucketGetOption(UnifiedOpts.softDeleted(softDeleted)); + } + /** * Returns an option to specify the bucket's fields to be returned by the RPC call. If this * option is not provided all bucket's fields are returned. {@code BucketGetOption.fields}) can @@ -1743,6 +1768,49 @@ public static BlobRestoreOption copySourceAcl(boolean copySourceAcl) { } } + class BucketRestoreOption extends Option { + private static final long serialVersionUID = 1422014464162702152L; + + BucketRestoreOption(BucketSourceOpt opt) { + super(opt); + } + + /** + * Returns an option to define the projection in the API request. In some cases this option may + * be needed to be set to `noAcl` to omit ACL data from the response. The default value is + * `full` + */ + public static BucketRestoreOption projection(String projection) { + return new BucketRestoreOption(UnifiedOpts.projection(projection)); + } + + /** + * Returns an option to specify the bucket's fields to be returned by the RPC call. If this + * option is not provided all bucket's fields are returned. {@code BucketGetOption.fields}) can + * be used to specify only the fields of interest. Bucket name is always returned, even if not + * specified. + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketRestoreOption fields(BucketField... fields) { + requireNonNull(fields, "fields must be non null"); + ImmutableSet set = + ImmutableSet.builder() + .addAll(BucketField.REQUIRED_FIELDS) + .add(fields) + .build(); + return new BucketRestoreOption(UnifiedOpts.fields(set)); + } + + /** + * Returns an option for bucket's billing user project. This option is only used by the buckets + * with 'requester_pays' flag. + */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketRestoreOption userProject(@NonNull String userProject) { + return new BucketRestoreOption(UnifiedOpts.userProject(userProject)); + } + } + /** Class for specifying bucket list options. */ class BucketListOption extends Option { @@ -1782,6 +1850,12 @@ public static BucketListOption userProject(@NonNull String userProject) { return new BucketListOption(UnifiedOpts.userProject(userProject)); } + /** Returns an option for whether to return soft-deleted buckets. */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + public static BucketListOption softDeleted(boolean softDeleted) { + return new BucketListOption(UnifiedOpts.softDeleted(softDeleted)); + } + /** * Returns an option to specify the bucket's fields to be returned by the RPC call. If this * option is not provided all bucket's fields are returned. {@code BucketListOption.fields}) can @@ -3193,6 +3267,20 @@ Blob createFrom( @TransportCompatibility({Transport.HTTP, Transport.GRPC}) Blob restore(BlobId blob, BlobRestoreOption... options); + /** + * Restores a soft-deleted bucket to full bucket status. + * + *

Example of restoring a bucket. + * + *

{@code
+   * String bucketName = "my-unique-bucket";
+   * long generation = 42;
+   * storage.restore(bucketName, generation);
+   * }
+ */ + @TransportCompatibility({Transport.HTTP, Transport.GRPC}) + void restore(String bucket, long generation, BucketRestoreOption... options); + /** * Lists the project's buckets. * diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index ee538bcde8..ff42670879 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -445,6 +445,19 @@ public Page list(final String bucket, BlobListOption... options) { return listBlobs(bucket, getOptions(), optionsMap); } + @Override + public void restore(String bucket, long generation, BucketRestoreOption... options) { + ImmutableMap optionsMap = Opts.unwrap(options).getRpcOptions(); + + final com.google.api.services.storage.model.Bucket bucketPb = + codecs.bucketInfo().encode(BucketInfo.of(bucket)).setGeneration(generation); + + ResultRetryAlgorithm algorithm = + retryAlgorithmManager.getForBucketRestore(bucket, optionsMap); + + run(algorithm, callable(() -> storageRpc.restore(bucketPb, optionsMap)), Function.identity()); + } + private static Page listBuckets( final HttpStorageOptions serviceOptions, final Map optionsMap) { ResultRetryAlgorithm algorithm = diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java index 1f51429d02..037ffbdd21 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/UnifiedOpts.java @@ -372,6 +372,10 @@ static GenerationMatch doesNotExist() { return new GenerationMatch(0); } + static Generation generation(long generation) { + return new Generation(generation); + } + static EncryptionKey encryptionKey(@NonNull String encryptionKey) { requireNonNull(encryptionKey, "encryptionKey must be non null"); return new EncryptionKey( @@ -675,7 +679,7 @@ public Mapper listObjects() { } static final class SoftDeleted extends RpcOptVal - implements ObjectListOpt, ObjectSourceOpt { + implements ObjectListOpt, ObjectSourceOpt, BucketListOpt, BucketSourceOpt { private static final long serialVersionUID = -8526951678111463350L; @@ -692,6 +696,12 @@ public Mapper listObjects() { public Mapper getObject() { return b -> b.setSoftDeleted(val); } + + /* todo: uncomment when grpc is available + @Override + public Mapper getBucket() { + return b -> b.setSoftDeleted(val); + }*/ } static final class CopySourceAcl extends RpcOptVal implements ObjectSourceOpt { @@ -1438,6 +1448,27 @@ public SourceMetagenerationNotMatch asSource() { } } + static final class Generation extends RpcOptVal<@NonNull Long> implements BucketSourceOpt { + + private static final long serialVersionUID = 5765651177835878761L; + + private Generation(long val) { + super(StorageRpc.Option.GENERATION, val); + } + + /* todo: uncomment when grpc is available + @Override + public Mapper getBucket() { + return b -> b.setGeneration(val); + } + + @Override + public Mapper restoreBucket() { + return b -> b.setGeneration(val); + } + */ + } + static final class PageSize extends RpcOptVal<@NonNull Long> implements BucketListOpt, ObjectListOpt, HmacKeyListOpt { private static final long serialVersionUID = -8184518840397826601L; @@ -1596,7 +1627,8 @@ public Mapper listBuckets() { } } - static final class Projection extends RpcOptVal implements BucketTargetOpt { + static final class Projection extends RpcOptVal + implements BucketTargetOpt, BucketSourceOpt { private static final long serialVersionUID = -7394684784418942133L; private Projection(String val) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index 5341051a25..3801bd33d9 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -429,6 +429,7 @@ public Tuple> list(Map options) { .setPageToken(Option.PAGE_TOKEN.getString(options)) .setFields(Option.FIELDS.getString(options)) .setUserProject(Option.USER_PROJECT.getString(options)) + .setSoftDeleted(Option.SOFT_DELETED.getBoolean(options)) .execute(); return Tuple.>of(buckets.getNextPageToken(), buckets.getItems()); } catch (IOException ex) { @@ -510,15 +511,20 @@ public Bucket get(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_BUCKET); Scope scope = tracer.withSpan(span); try { - return storage - .buckets() - .get(bucket.getName()) - .setProjection(DEFAULT_PROJECTION) - .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) - .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) - .setFields(Option.FIELDS.getString(options)) - .setUserProject(Option.USER_PROJECT.getString(options)) - .execute(); + Storage.Buckets.Get get = + storage + .buckets() + .get(bucket.getName()) + .setProjection(DEFAULT_PROJECTION) + .setIfMetagenerationMatch(Option.IF_METAGENERATION_MATCH.getLong(options)) + .setIfMetagenerationNotMatch(Option.IF_METAGENERATION_NOT_MATCH.getLong(options)) + .setFields(Option.FIELDS.getString(options)) + .setUserProject(Option.USER_PROJECT.getString(options)); + if (Boolean.TRUE.equals(Option.SOFT_DELETED.getBoolean(options))) { + get.setSoftDeleted(true); + get.setGeneration(Option.GENERATION.getLong(options)); + } + return get.execute(); } catch (IOException ex) { span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); StorageException serviceException = translate(ex); @@ -596,6 +602,30 @@ public StorageObject restore(StorageObject object, Map options) { } } + @Override + public void restore(Bucket bucket, Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_RESTORE_BUCKET); + Scope scope = tracer.withSpan(span); + try { + Storage.Buckets.Restore restore = + storage.buckets().restore(bucket.getName(), bucket.getGeneration()); + restore + .setUserProject(Option.USER_PROJECT.getString(options)) + .setFields(Option.FIELDS.getString(options)) + .execute(); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + StorageException serviceException = translate(ex); + if (serviceException.getCode() == HTTP_NOT_FOUND) { + return; + } + throw serviceException; + } finally { + scope.close(); + span.end(HttpStorageRpcSpans.END_SPAN_OPTIONS); + } + } + @Override public Bucket patch(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_PATCH_BUCKET); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java index dc4b05336c..0fd68a21ab 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java @@ -31,6 +31,8 @@ class HttpStorageRpcSpans { static final String SPAN_NAME_GET_BUCKET = getTraceSpanName("get(Bucket,Map)"); static final String SPAN_NAME_GET_OBJECT = getTraceSpanName("get(StorageObject,Map)"); static final String SPAN_NAME_RESTORE_OBJECT = getTraceSpanName("restore(StorageObject, Map)"); + + static final String SPAN_NAME_RESTORE_BUCKET = getTraceSpanName("restore(Bucket, Map)"); static final String SPAN_NAME_PATCH_BUCKET = getTraceSpanName("patch(Bucket,Map)"); static final String SPAN_NAME_PATCH_OBJECT = getTraceSpanName("patch(StorageObject,Map)"); static final String SPAN_NAME_DELETE_BUCKET = getTraceSpanName("delete(Bucket,Map)"); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java index 78747d42d6..47ccece6f9 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java @@ -247,12 +247,15 @@ public int hashCode() { StorageObject get(StorageObject object, Map options); /** - * If an object has been soft-deleted, restores it and returns the restored object.j + * If an object has been soft-deleted, restores it and returns the restored object. * * @throws StorageException upon failure */ StorageObject restore(StorageObject object, Map options); + /** If a bucket has been soft-deleted, restores it. */ + void restore(Bucket bucket, Map options); + /** * Updates bucket information. * diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java index 6686cb925e..54ad244e6c 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/StorageRpcTestBase.java @@ -76,6 +76,11 @@ public StorageObject restore(StorageObject object, Map options) { throw new UnsupportedOperationException("Not implemented yet"); } + @Override + public void restore(Bucket bucket, Map options) { + throw new UnsupportedOperationException("Not implemented yet"); + } + @Override public Bucket patch(Bucket bucket, Map options) { throw new UnsupportedOperationException("Not implemented yet"); 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 97ab8ad3ec..5255c69243 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 @@ -58,6 +58,7 @@ import com.google.cloud.storage.spi.v1.HttpStorageRpc; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Streams; import java.time.Duration; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; @@ -688,6 +689,40 @@ public void testListObjectsWithFolders() throws Exception { } } + @Test + @CrossRun.Exclude(transports = {Transport.GRPC}) + public void testSoftDelete() { + String bucketName = generator.randomBucketName(); + Bucket softDelBucket = storage.create(BucketInfo.of(bucketName)); + try { + long generation = softDelBucket.getGeneration(); + assertNull(softDelBucket.getSoftDeleteTime()); + assertNull(softDelBucket.getHardDeleteTime()); + storage.delete(bucketName); + + assertNull(storage.get(bucketName)); + Bucket softDeleted = + storage.get( + bucketName, + BucketGetOption.generation(generation), + BucketGetOption.softDeleted(true)); + assertNotNull(softDeleted); + assertNotNull(softDeleted.getSoftDeleteTime()); + assertNotNull(softDeleted.getHardDeleteTime()); + + storage.list().iterateAll().forEach(b -> assertNotEquals(bucketName, b.getName())); + assertTrue( + Streams.stream(storage.list(BucketListOption.softDeleted(true)).iterateAll()) + .anyMatch(b -> b.getName().equals(bucketName))); + + storage.restore(bucketName, generation); + + assertNotNull(storage.get(bucketName)); + } finally { + storage.delete(bucketName); + } + } + private void unsetRequesterPays() { Bucket remoteBucket = storage.get( diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/AbstractStorageProxy.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/AbstractStorageProxy.java index 9e5e9691e9..64429d0e18 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/AbstractStorageProxy.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/AbstractStorageProxy.java @@ -144,6 +144,11 @@ public Blob restore(BlobId blob, BlobRestoreOption... options) { return delegate.restore(blob, options); } + @Override + public void restore(String bucket, long generation, BucketRestoreOption... options) { + delegate.restore(bucket, generation, options); + } + @Override public Page list(BucketListOption... options) { return delegate.list(options); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/StorageInstance.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/StorageInstance.java index b653dea056..7c9908cadc 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/StorageInstance.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/runner/registry/StorageInstance.java @@ -167,6 +167,12 @@ public Bucket lockRetentionPolicy(BucketInfo bucket, BucketTargetOption... optio return super.lockRetentionPolicy(bucket, options); } + @Override + public void restore(String bucket, long generation, BucketRestoreOption... options) { + checkBucketProtected(bucket); + super.restore(bucket, generation, options); + } + @Override public void close() throws Exception { throw new VetoException("Called #close() on global Storage instance"); diff --git a/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java b/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java index e1db27ab02..5cdf01b1b6 100644 --- a/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java +++ b/samples/snippets/src/main/java/com/example/storage/ConfigureRetries.java @@ -35,8 +35,7 @@ public static void main(String[] args) { static void deleteBlob(String bucketName, String blobName) { // Customize retry behavior RetrySettings retrySettings = - StorageOptions.getDefaultRetrySettings() - .toBuilder() + StorageOptions.getDefaultRetrySettings().toBuilder() // Set the max number of attempts to 10 (initial attempt plus 9 retries) .setMaxAttempts(10) // Set the backoff multiplier to 3.0 diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/DisableRequesterPays.java b/samples/snippets/src/main/java/com/example/storage/bucket/DisableRequesterPays.java index 9cb40b1b65..aaa9694c9a 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/DisableRequesterPays.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/DisableRequesterPays.java @@ -31,8 +31,7 @@ public static void disableRequesterPays(String projectId, String bucketName) { Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService(); Bucket bucket = storage.get(bucketName, Storage.BucketGetOption.userProject(projectId)); - bucket - .toBuilder() + bucket.toBuilder() .setRequesterPays(false) .build() .update(Storage.BucketTargetOption.userProject(projectId)); diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/EnableLifecycleManagement.java b/samples/snippets/src/main/java/com/example/storage/bucket/EnableLifecycleManagement.java index 5c8699f5ee..bf2767321e 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/EnableLifecycleManagement.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/EnableLifecycleManagement.java @@ -40,8 +40,7 @@ public static void enableLifecycleManagement(String projectId, String bucketName // See the LifecycleRule documentation for additional info on what you can do with lifecycle // management rules. This one deletes objects that are over 100 days old. // https://googleapis.dev/java/google-cloud-clients/latest/com/google/cloud/storage/BucketInfo.LifecycleRule.html - bucket - .toBuilder() + bucket.toBuilder() .setLifecycleRules( ImmutableList.of( new LifecycleRule( diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/EnableUniformBucketLevelAccess.java b/samples/snippets/src/main/java/com/example/storage/bucket/EnableUniformBucketLevelAccess.java index 4c70b7b2c7..a8ae606bd0 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/EnableUniformBucketLevelAccess.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/EnableUniformBucketLevelAccess.java @@ -43,8 +43,7 @@ public static void enableUniformBucketLevelAccess(String projectId, String bucke BucketInfo.IamConfiguration.newBuilder().setIsUniformBucketLevelAccessEnabled(true).build(); storage.update( - bucket - .toBuilder() + bucket.toBuilder() .setIamConfiguration(iamConfiguration) .setAcl(null) .setDefaultAcl(null) diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/GetSoftDeletedBucket.java b/samples/snippets/src/main/java/com/example/storage/bucket/GetSoftDeletedBucket.java new file mode 100644 index 0000000000..61fd1aeb15 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/bucket/GetSoftDeletedBucket.java @@ -0,0 +1,55 @@ +/* + * Copyright 2020 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.bucket; + +// [START storage_get_soft_deleted_bucket] +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; + +public class GetSoftDeletedBucket { + public static void getSoftDeletedBucket(String projectId, String bucketName, long generation) { + // The ID of your GCP project + // String projectId = "your-project-id"; + + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The generation of the bucket to restore + // long generation = 123456789; + + Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService(); + Bucket bucket = + storage.get( + bucketName, + Storage.BucketGetOption.softDeleted(true), + Storage.BucketGetOption.generation(generation)); + + // The following fields are only set for soft-deleted buckets + String softDeleteTime = bucket.getSoftDeleteTime().toString(); + String hardDeleteTime = bucket.getHardDeleteTime().toString(); + + System.out.println( + "The bucket " + + bucketName + + " was soft-deleted at " + + softDeleteTime + + " and will be fully deleted at " + + hardDeleteTime); + } +} +// [END storage_get_soft_deleted_bucket] diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/ListSoftDeletedBuckets.java b/samples/snippets/src/main/java/com/example/storage/bucket/ListSoftDeletedBuckets.java new file mode 100644 index 0000000000..ce7482abb2 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/bucket/ListSoftDeletedBuckets.java @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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.bucket; + +// [START storage_list_soft_deleted_buckets] +import com.google.api.gax.paging.Page; +import com.google.cloud.storage.Bucket; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; + +public class ListSoftDeletedBuckets { + public static void listSoftDeletedBuckets(String projectId) { + // The ID of your GCP project + // String projectId = "your-project-id"; + + Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService(); + Page buckets = storage.list(Storage.BucketListOption.softDeleted(true)); + + for (Bucket bucket : buckets.iterateAll()) { + System.out.println(bucket.getName()); + } + } +} +// [END storage_list_soft_deleted_buckets] diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/MakeBucketPublic.java b/samples/snippets/src/main/java/com/example/storage/bucket/MakeBucketPublic.java index a77a1d99cf..09e9b32074 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/MakeBucketPublic.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/MakeBucketPublic.java @@ -35,8 +35,7 @@ public static void makeBucketPublic(String projectId, String bucketName) { Policy originalPolicy = storage.getIamPolicy(bucketName); storage.setIamPolicy( bucketName, - originalPolicy - .toBuilder() + originalPolicy.toBuilder() .addIdentity(StorageRoles.objectViewer(), Identity.allUsers()) // All users can view .build()); diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/RestoreSoftDeletedBucket.java b/samples/snippets/src/main/java/com/example/storage/bucket/RestoreSoftDeletedBucket.java new file mode 100644 index 0000000000..1265f55761 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/storage/bucket/RestoreSoftDeletedBucket.java @@ -0,0 +1,41 @@ +/* + * Copyright 2020 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.bucket; + +// [START storage_restore_soft_deleted_bucket] +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; + +public class RestoreSoftDeletedBucket { + public static void restoreSoftDeletedBucket( + String projectId, String bucketName, long generation) { + // The ID of your GCP project + // String projectId = "your-project-id"; + + // The ID of your GCS bucket + // String bucketName = "your-unique-bucket-name"; + + // The generation of the bucket to restore + // long generation = 123456789; + + Storage storage = StorageOptions.newBuilder().setProjectId(projectId).build().getService(); + storage.restore(bucketName, generation); + + System.out.println("Soft deleted bucket " + bucketName + " was restored."); + } +} +// [END storage_restore_soft_deleted_bucket] diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/SetBucketAutoclass.java b/samples/snippets/src/main/java/com/example/storage/bucket/SetBucketAutoclass.java index 0f9ae0f4ef..395acc023f 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/SetBucketAutoclass.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/SetBucketAutoclass.java @@ -49,8 +49,7 @@ public static void setBucketAutoclass( Bucket bucket = storage.get(bucketName); Bucket toUpdate = - bucket - .toBuilder() + bucket.toBuilder() .setAutoclass( Autoclass.newBuilder() .setEnabled(enabled) diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionEnforced.java b/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionEnforced.java index 8d679838a5..c959dce210 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionEnforced.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionEnforced.java @@ -34,8 +34,7 @@ public static void setPublicAccessPreventionEnforced(String projectId, String bu Bucket bucket = storage.get(bucketName); // Enforces public access prevention for the bucket - bucket - .toBuilder() + bucket.toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED) diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionInherited.java b/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionInherited.java index 57938b8c48..0208f70824 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionInherited.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/SetPublicAccessPreventionInherited.java @@ -34,8 +34,7 @@ public static void setPublicAccessPreventionInherited(String projectId, String b Bucket bucket = storage.get(bucketName); // Sets public access prevention to 'inherited' for the bucket - bucket - .toBuilder() + bucket.toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.INHERITED) diff --git a/samples/snippets/src/main/java/com/example/storage/bucket/SetRetentionPolicy.java b/samples/snippets/src/main/java/com/example/storage/bucket/SetRetentionPolicy.java index eca89fbb44..4491ed3897 100644 --- a/samples/snippets/src/main/java/com/example/storage/bucket/SetRetentionPolicy.java +++ b/samples/snippets/src/main/java/com/example/storage/bucket/SetRetentionPolicy.java @@ -43,8 +43,7 @@ public static void setRetentionPolicy( Bucket bucket = storage.get(bucketName); Bucket bucketWithRetentionPolicy = storage.update( - bucket - .toBuilder() + bucket.toBuilder() .setRetentionPeriodDuration(Duration.ofSeconds(retentionPeriodSeconds)) .build(), BucketTargetOption.metagenerationMatch()); diff --git a/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java b/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java index 2c10e71ff7..8b4263d5ae 100644 --- a/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java +++ b/samples/snippets/src/test/java/com/example/storage/ITBucketSnippets.java @@ -49,9 +49,11 @@ import com.example.storage.bucket.GetDefaultEventBasedHold; import com.example.storage.bucket.GetPublicAccessPrevention; import com.example.storage.bucket.GetRetentionPolicy; +import com.example.storage.bucket.GetSoftDeletedBucket; import com.example.storage.bucket.GetUniformBucketLevelAccess; import com.example.storage.bucket.ListBucketIamMembers; import com.example.storage.bucket.ListBuckets; +import com.example.storage.bucket.ListSoftDeletedBuckets; import com.example.storage.bucket.LockRetentionPolicy; import com.example.storage.bucket.MakeBucketPublic; import com.example.storage.bucket.RemoveBucketCors; @@ -60,6 +62,7 @@ import com.example.storage.bucket.RemoveBucketIamMember; import com.example.storage.bucket.RemoveBucketLabel; import com.example.storage.bucket.RemoveRetentionPolicy; +import com.example.storage.bucket.RestoreSoftDeletedBucket; import com.example.storage.bucket.SetAsyncTurboRpo; import com.example.storage.bucket.SetBucketDefaultKmsKey; import com.example.storage.bucket.SetBucketWebsiteInfo; @@ -132,14 +135,9 @@ public class ITBucketSnippets { public static void beforeClass() { RemoteStorageHelper helper = RemoteStorageHelper.create(); storage = - helper - .getOptions() - .toBuilder() + helper.getOptions().toBuilder() .setRetrySettings( - helper - .getOptions() - .getRetrySettings() - .toBuilder() + helper.getOptions().getRetrySettings().toBuilder() .setRetryDelayMultiplier(3.0) .build()) .build() @@ -223,8 +221,7 @@ public void testGetBucketMetadata() { Bucket bucket = storage.get(BUCKET, Storage.BucketGetOption.fields(Storage.BucketField.values())); bucket = - bucket - .toBuilder() + bucket.toBuilder() .setLabels(ImmutableMap.of("k", "v")) .setLifecycleRules( ImmutableList.of( @@ -291,9 +288,7 @@ public void testEnableLifecycleManagement() throws Throwable { @Test public void testDisableLifecycleManagement() throws Throwable { - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setLifecycleRules( ImmutableList.of( new BucketInfo.LifecycleRule( @@ -313,9 +308,7 @@ public void testGetPublicAccessPrevention() throws Throwable { try { // By default a bucket PAP state is INHERITED and we are changing the state to validate // non-default state. - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED) @@ -332,9 +325,7 @@ public void testGetPublicAccessPrevention() throws Throwable { assertTrue(snippetOutput.contains("enforced")); } finally { // No matter what happens make sure test set bucket back to INHERITED - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.INHERITED) @@ -356,9 +347,7 @@ public void testSetPublicAccessPreventionEnforced() throws Throwable { BucketInfo.PublicAccessPrevention.ENFORCED)); } finally { // No matter what happens make sure test set bucket back to INHERITED - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.INHERITED) @@ -371,9 +360,7 @@ public void testSetPublicAccessPreventionEnforced() throws Throwable { @Test public void testSetPublicAccessPreventionInherited() throws Throwable { try { - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.ENFORCED) @@ -395,9 +382,7 @@ public void testSetPublicAccessPreventionInherited() throws Throwable { BucketInfo.PublicAccessPrevention.INHERITED)); } finally { // No matter what happens make sure test set bucket back to INHERITED - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setIamConfiguration( BucketInfo.IamConfiguration.newBuilder() .setPublicAccessPrevention(BucketInfo.PublicAccessPrevention.INHERITED) @@ -456,9 +441,7 @@ public void testMakeBucketPublic() throws Throwable { @Test public void deleteBucketDefaultKmsKey() throws Throwable { - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setDefaultKmsKeyName( "projects/cloud-java-ci-sample/locations/us/keyRings/" + "gcs_test_kms_key_ring/cryptoKeys/gcs_kms_key_one") @@ -517,9 +500,7 @@ public void testConfigureBucketCors() throws Throwable { @Test public void testRemoveBucketCors() throws Throwable { - storage - .get(BUCKET) - .toBuilder() + storage.get(BUCKET).toBuilder() .setCors( ImmutableList.of( Cors.newBuilder() @@ -697,4 +678,31 @@ public void testCreateBucketWithObjectRetention() { storage.delete(tempBucket); } } + + @Test + public void testBucketSoftDelete() { + String bucketName = RemoteStorageHelper.generateBucketName(); + Bucket softDelBucket = storage.create(BucketInfo.of(bucketName)); + try { + long generation = softDelBucket.getGeneration(); + storage.delete(bucketName); + + GetSoftDeletedBucket.getSoftDeletedBucket(PROJECT_ID, bucketName, generation); + String snippetOutput = stdOutCaptureRule.getCapturedOutputAsUtf8String(); + // a 'Z' is printed with a DateTime, so verifying there are two Zs means a soft delete time + // and hard delete time + // were printed + assertEquals(2, snippetOutput.chars().filter(c -> c == 'Z').count()); + + ListSoftDeletedBuckets.listSoftDeletedBuckets(PROJECT_ID); + snippetOutput = stdOutCaptureRule.getCapturedOutputAsUtf8String(); + assertTrue(snippetOutput.contains(bucketName)); + + RestoreSoftDeletedBucket.restoreSoftDeletedBucket(PROJECT_ID, bucketName, generation); + + assertNotNull(storage.get(bucketName)); + } finally { + storage.delete(bucketName); + } + } } diff --git a/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java b/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java index 3fdb0cfe88..c864be6d6b 100644 --- a/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java +++ b/samples/snippets/src/test/java/com/example/storage/ITObjectSnippets.java @@ -441,9 +441,7 @@ public void testSetObjectRetentionPolicy() { assertNotNull(storage.get(tempBucket, retentionBlob).getRetention()); } finally { - storage - .get(tempBucket, retentionBlob) - .toBuilder() + storage.get(tempBucket, retentionBlob).toBuilder() .setRetention(null) .build() .update(Storage.BlobTargetOption.overrideUnlockedRetention(true));