From e5f2845a4242d6a2976b4d43cb8bc0cc31d16f40 Mon Sep 17 00:00:00 2001 From: Jesse Lovelace Date: Fri, 1 Nov 2024 15:24:40 -0700 Subject: [PATCH 1/2] feat: adds bucket restore support --- .../java/com/google/cloud/storage/Bucket.java | 18 +++ .../com/google/cloud/storage/BucketInfo.java | 68 ++++++++++- .../google/cloud/storage/GrpcStorageImpl.java | 5 + .../storage/HttpRetryAlgorithmManager.java | 4 + .../google/cloud/storage/JsonConversions.java | 6 + .../com/google/cloud/storage/Storage.java | 106 +++++++++++++++++- .../com/google/cloud/storage/StorageImpl.java | 17 +++ .../com/google/cloud/storage/UnifiedOpts.java | 35 +++++- .../cloud/storage/spi/v1/HttpStorageRpc.java | 35 +++++- .../storage/spi/v1/HttpStorageRpcSpans.java | 2 + .../cloud/storage/spi/v1/StorageRpc.java | 7 +- .../storage/testing/StorageRpcTestBase.java | 5 + .../google/cloud/storage/it/ITBucketTest.java | 30 +++++ .../runner/registry/AbstractStorageProxy.java | 5 + .../it/runner/registry/StorageInstance.java | 6 + 15 files changed, 340 insertions(+), 9 deletions(-) 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..cd5c3b7627 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..2969bd879c 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..2f5ef02992 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,10 @@ 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..6858990a85 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..3312f9ed3d 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,28 @@ 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 +932,23 @@ 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 +1782,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 +1864,14 @@ 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 +3283,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..6513b54be8 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,23 @@ 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..2e0e0b6830 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,7 @@ 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..229bb01eff 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,19 @@ public Bucket get(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_BUCKET); Scope scope = tracer.withSpan(span); try { - return storage + 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)) - .execute(); + .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 +601,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..961c052500 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,17 @@ 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..2f544920c2 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 @@ -67,6 +67,8 @@ import java.util.Map; import java.util.Optional; import java.util.stream.StreamSupport; + +import com.google.common.collect.Streams; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -688,6 +690,34 @@ 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"); From 351fe194ecf00cc93f5690ee58140e9d2b560e7e Mon Sep 17 00:00:00 2001 From: Jesse Lovelace Date: Wed, 20 Nov 2024 13:41:06 -0800 Subject: [PATCH 2/2] add samples, lint --- .../com/google/cloud/storage/BucketInfo.java | 6 +- .../google/cloud/storage/GrpcStorageImpl.java | 2 +- .../storage/HttpRetryAlgorithmManager.java | 3 +- .../google/cloud/storage/JsonConversions.java | 4 +- .../com/google/cloud/storage/Storage.java | 40 +++------- .../com/google/cloud/storage/StorageImpl.java | 14 ++-- .../com/google/cloud/storage/UnifiedOpts.java | 3 +- .../cloud/storage/spi/v1/HttpStorageRpc.java | 25 ++++--- .../cloud/storage/spi/v1/StorageRpc.java | 4 +- .../google/cloud/storage/it/ITBucketTest.java | 13 +++- .../com/example/storage/ConfigureRetries.java | 3 +- .../storage/bucket/DisableRequesterPays.java | 3 +- .../bucket/EnableLifecycleManagement.java | 3 +- .../EnableUniformBucketLevelAccess.java | 3 +- .../storage/bucket/GetSoftDeletedBucket.java | 55 ++++++++++++++ .../bucket/ListSoftDeletedBuckets.java | 38 ++++++++++ .../storage/bucket/MakeBucketPublic.java | 3 +- .../bucket/RestoreSoftDeletedBucket.java | 41 ++++++++++ .../storage/bucket/SetBucketAutoclass.java | 3 +- .../SetPublicAccessPreventionEnforced.java | 3 +- .../SetPublicAccessPreventionInherited.java | 3 +- .../storage/bucket/SetRetentionPolicy.java | 3 +- .../com/example/storage/ITBucketSnippets.java | 74 ++++++++++--------- .../com/example/storage/ITObjectSnippets.java | 4 +- 24 files changed, 235 insertions(+), 118 deletions(-) create mode 100644 samples/snippets/src/main/java/com/example/storage/bucket/GetSoftDeletedBucket.java create mode 100644 samples/snippets/src/main/java/com/example/storage/bucket/ListSoftDeletedBuckets.java create mode 100644 samples/snippets/src/main/java/com/example/storage/bucket/RestoreSoftDeletedBucket.java 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 cd5c3b7627..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 @@ -2381,7 +2381,7 @@ public Builder setHierarchicalNamespace(HierarchicalNamespace hierarchicalNamesp @Override Builder setGeneration(long generation) { - if(!Objects.equals(this.generation, generation)){ + if (!Objects.equals(this.generation, generation)) { modifiedFields.add(BucketField.GENERATION); } this.generation = generation; @@ -2390,7 +2390,7 @@ Builder setGeneration(long generation) { @Override Builder setSoftDeleteTime(OffsetDateTime softDeleteTime) { - if(!Objects.equals(this.softDeleteTime, softDeleteTime)){ + if (!Objects.equals(this.softDeleteTime, softDeleteTime)) { modifiedFields.add(BucketField.SOFT_DELETE_TIME); } this.softDeleteTime = softDeleteTime; @@ -2399,7 +2399,7 @@ Builder setSoftDeleteTime(OffsetDateTime softDeleteTime) { @Override Builder setHardDeleteTime(OffsetDateTime hardDeleteTime) { - if(!Objects.equals(this.hardDeleteTime, hardDeleteTime)){ + if (!Objects.equals(this.hardDeleteTime, hardDeleteTime)) { modifiedFields.add(BucketField.HARD_DELETE_TIME); } this.hardDeleteTime = hardDeleteTime; 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 2969bd879c..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 @@ -417,7 +417,7 @@ public Blob restore(BlobId blob, BlobRestoreOption... options) { @Override public void restore(String bucket, long generation, BucketRestoreOption... options) { - //todo: implement when grpc is available + // todo: implement when grpc is available } private Blob internalObjectRestore(BlobId blobId, Opts opts) { 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 2f5ef02992..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,7 +218,8 @@ public ResultRetryAlgorithm getForObjectsRestore( return retryStrategy.getIdempotentHandler(); } - public ResultRetryAlgorithm getForBucketRestore(String bucket, Map optionsMap) { + public ResultRetryAlgorithm getForBucketRestore( + String bucket, Map optionsMap) { return retryStrategy.getIdempotentHandler(); } 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 6858990a85..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 @@ -411,8 +411,8 @@ private Bucket bucketInfoEncode(BucketInfo from) { 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); + 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()); 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 3312f9ed3d..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 @@ -191,25 +191,13 @@ enum BucketField implements FieldSelector, NamedField { com.google.api.services.storage.model.Bucket.SoftDeletePolicy.class), @TransportCompatibility({Transport.HTTP, Transport.GRPC}) - GENERATION( - "generation", - "generation", - Long.class - ), + GENERATION("generation", "generation", Long.class), @TransportCompatibility({Transport.HTTP, Transport.GRPC}) - SOFT_DELETE_TIME( - "softDeleteTime", - "soft_delete_time", - DateTime.class - ), + SOFT_DELETE_TIME("softDeleteTime", "soft_delete_time", DateTime.class), @TransportCompatibility({Transport.HTTP, Transport.GRPC}) - HARD_DELETE_TIME( - "hardDeleteTime", - "hard_delete_time", - DateTime.class - ); + 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; @@ -933,19 +921,17 @@ public static BucketGetOption userProject(@NonNull String userProject) { } /** - * Returns an option for this bucket's generation. Should only be specified when - * getting a soft-deleted bucket + * 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. - */ + /** Returns an option that must be true if getting a soft-deleted bucket. */ @TransportCompatibility({Transport.HTTP, Transport.GRPC}) - public static BucketGetOption softDeleted(boolean softDeleted){ + public static BucketGetOption softDeleted(boolean softDeleted) { return new BucketGetOption(UnifiedOpts.softDeleted(softDeleted)); } @@ -1808,10 +1794,10 @@ public static BucketRestoreOption projection(String projection) { public static BucketRestoreOption fields(BucketField... fields) { requireNonNull(fields, "fields must be non null"); ImmutableSet set = - ImmutableSet.builder() - .addAll(BucketField.REQUIRED_FIELDS) - .add(fields) - .build(); + ImmutableSet.builder() + .addAll(BucketField.REQUIRED_FIELDS) + .add(fields) + .build(); return new BucketRestoreOption(UnifiedOpts.fields(set)); } @@ -1864,9 +1850,7 @@ public static BucketListOption userProject(@NonNull String userProject) { return new BucketListOption(UnifiedOpts.userProject(userProject)); } - /** - * Returns an option for whether to return soft-deleted buckets. - */ + /** 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)); 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 6513b54be8..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 @@ -447,19 +447,15 @@ public Page list(final String bucket, BlobListOption... options) { @Override public void restore(String bucket, long generation, BucketRestoreOption... options) { - ImmutableMap optionsMap = - Opts.unwrap(options).getRpcOptions(); + ImmutableMap optionsMap = Opts.unwrap(options).getRpcOptions(); final com.google.api.services.storage.model.Bucket bucketPb = - codecs.bucketInfo().encode(BucketInfo.of(bucket)).setGeneration(generation); + codecs.bucketInfo().encode(BucketInfo.of(bucket)).setGeneration(generation); - ResultRetryAlgorithm algorithm = retryAlgorithmManager.getForBucketRestore(bucket, optionsMap); + ResultRetryAlgorithm algorithm = + retryAlgorithmManager.getForBucketRestore(bucket, optionsMap); - run( - algorithm, - callable( - () -> storageRpc.restore(bucketPb, optionsMap)), - Function.identity()); + run(algorithm, callable(() -> storageRpc.restore(bucketPb, optionsMap)), Function.identity()); } private static Page listBuckets( 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 2e0e0b6830..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 @@ -1627,7 +1627,8 @@ public Mapper listBuckets() { } } - static final class Projection extends RpcOptVal implements BucketTargetOpt, BucketSourceOpt { + 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 229bb01eff..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 @@ -511,14 +511,15 @@ public Bucket get(Bucket bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_BUCKET); Scope scope = tracer.withSpan(span); try { - 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)); + 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)); @@ -607,11 +608,11 @@ public void restore(Bucket bucket, Map options) { Scope scope = tracer.withSpan(span); try { Storage.Buckets.Restore restore = - storage.buckets().restore(bucket.getName(), bucket.getGeneration()); + storage.buckets().restore(bucket.getName(), bucket.getGeneration()); restore - .setUserProject(Option.USER_PROJECT.getString(options)) - .setFields(Option.FIELDS.getString(options)) - .execute(); + .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); 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 961c052500..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 @@ -253,9 +253,7 @@ public int hashCode() { */ StorageObject restore(StorageObject object, Map options); - /** - * If a bucket has been soft-deleted, restores it. - */ + /** If a bucket has been soft-deleted, restores it. */ void restore(Bucket bucket, Map options); /** 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 2f544920c2..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; @@ -67,8 +68,6 @@ import java.util.Map; import java.util.Optional; import java.util.stream.StreamSupport; - -import com.google.common.collect.Streams; import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -702,13 +701,19 @@ public void testSoftDelete() { storage.delete(bucketName); assertNull(storage.get(bucketName)); - Bucket softDeleted = storage.get(bucketName, BucketGetOption.generation(generation), BucketGetOption.softDeleted(true)); + 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))); + assertTrue( + Streams.stream(storage.list(BucketListOption.softDeleted(true)).iterateAll()) + .anyMatch(b -> b.getName().equals(bucketName))); storage.restore(bucketName, generation); 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));