diff --git a/storage/api/Storage.Samples.Tests/GetSoftDeleteBucketTest.cs b/storage/api/Storage.Samples.Tests/GetSoftDeleteBucketTest.cs new file mode 100644 index 00000000000..4e5006e7880 --- /dev/null +++ b/storage/api/Storage.Samples.Tests/GetSoftDeleteBucketTest.cs @@ -0,0 +1,43 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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. + +using Google.Cloud.Storage.V1; +using System.Threading.Tasks; +using Xunit; + +[Collection(nameof(StorageFixture))] +public class GetSoftDeleteBucketTest +{ + private readonly StorageFixture _fixture; + + public GetSoftDeleteBucketTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task GetSoftDeleteBucket() + { + GetSoftDeletedBucketSample getSoftDeletedBucketSample = new GetSoftDeletedBucketSample(); + var bucketName = _fixture.GenerateBucketName(); + var softDeleteBucket = _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: true, registerForDeletion: true); + await _fixture.Client.DeleteBucketAsync(softDeleteBucket.Name); + + var softDeleted = getSoftDeletedBucketSample.GetSoftDeletedBucket(softDeleteBucket.Name, softDeleteBucket.Generation); + Assert.Equal(softDeleteBucket.Name, softDeleted.Name); + Assert.Equal(softDeleteBucket.Generation, softDeleted.Generation); + Assert.NotNull(softDeleted.SoftDeleteTimeDateTimeOffset); + Assert.NotNull(softDeleted.HardDeleteTimeDateTimeOffset); + } +} diff --git a/storage/api/Storage.Samples.Tests/ListSoftDeleteBucketsTest.cs b/storage/api/Storage.Samples.Tests/ListSoftDeleteBucketsTest.cs new file mode 100644 index 00000000000..834a741b105 --- /dev/null +++ b/storage/api/Storage.Samples.Tests/ListSoftDeleteBucketsTest.cs @@ -0,0 +1,51 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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. + +using Google.Apis.Storage.v1.Data; +using System.Threading.Tasks; +using Xunit; + +[Collection(nameof(StorageFixture))] +public class ListSoftDeleteBucketsTest +{ + private readonly StorageFixture _fixture; + + public ListSoftDeleteBucketsTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task ListSoftDeleteBuckets() + { + ListSoftDeletedBucketsSample listSoftDeletedBucketsSample = new ListSoftDeletedBucketsSample(); + var bucketName = _fixture.GenerateBucketName(); + var softDeleteBucket = _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: true, registerForDeletion: true); + await _fixture.Client.DeleteBucketAsync(softDeleteBucket.Name); + + var actualBuckets = listSoftDeletedBucketsSample.ListSoftDeletedBuckets(_fixture.ProjectId); + // Check the list contains the bucket we just soft-deleted. + Assert.Contains(actualBuckets, bucket => bucket.Name == softDeleteBucket.Name && bucket.Generation == softDeleteBucket.Generation); + // Check all the buckets in the list are soft-deleted buckets. + Assert.All(actualBuckets, AssertSoftDeletedBucket); + } + + // Validates that the given bucket is soft-deleted. + private void AssertSoftDeletedBucket(Bucket b) + { + Assert.NotNull(b.Generation); + Assert.NotNull(b.HardDeleteTimeDateTimeOffset); + Assert.NotNull(b.SoftDeleteTimeDateTimeOffset); + } +} diff --git a/storage/api/Storage.Samples.Tests/RestoreSoftDeleteBucketTest.cs b/storage/api/Storage.Samples.Tests/RestoreSoftDeleteBucketTest.cs new file mode 100644 index 00000000000..a3217287952 --- /dev/null +++ b/storage/api/Storage.Samples.Tests/RestoreSoftDeleteBucketTest.cs @@ -0,0 +1,41 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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. + +using Google.Cloud.Storage.V1; +using System.Threading.Tasks; +using Xunit; + +[Collection(nameof(StorageFixture))] +public class RestoreSoftDeleteBucketTest +{ + private readonly StorageFixture _fixture; + + public RestoreSoftDeleteBucketTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public async Task RestoreSoftDeleteBucket() + { + RestoreSoftDeletedBucketSample restoreSoftDeletedBucketSample = new RestoreSoftDeletedBucketSample(); + var bucketName = _fixture.GenerateBucketName(); + var softDeleteBucket = _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: true, registerForDeletion: true); + await _fixture.Client.DeleteBucketAsync(softDeleteBucket.Name); + + var restoredBucket = restoreSoftDeletedBucketSample.RestoreSoftDeletedBucket(softDeleteBucket.Name, softDeleteBucket.Generation.Value); + Assert.Equal(softDeleteBucket.Name, restoredBucket.Name); + Assert.Equal(softDeleteBucket.Generation, restoredBucket.Generation); + } +} diff --git a/storage/api/Storage.Samples.Tests/StorageFixture.cs b/storage/api/Storage.Samples.Tests/StorageFixture.cs index 263b83cea30..9bcc2903b58 100644 --- a/storage/api/Storage.Samples.Tests/StorageFixture.cs +++ b/storage/api/Storage.Samples.Tests/StorageFixture.cs @@ -1,4 +1,4 @@ -// Copyright 2020 Google Inc. +// Copyright 2020 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -47,6 +47,7 @@ public class StorageFixture : IDisposable, ICollectionFixture public string KmsKeyLocation { get; } = "us-west1"; public string ServiceAccountEmail { get; } = "gcs-iam-acl-test@dotnet-docs-samples-tests.iam.gserviceaccount.com"; public List TempTopicNames { get; } = new List(); + public StorageClient Client { get; } public RetryRobot HmacChangesPropagated { get; } = new RetryRobot { @@ -64,6 +65,7 @@ public StorageFixture() { throw new Exception("You need to set the Environment variable 'GOOGLE_PROJECT_ID' with your Google Cloud Project's project id."); } + Client = StorageClient.Create(); // create simple bucket CreateBucket(BucketNameGeneric); @@ -217,6 +219,26 @@ public void CreateBucket(string bucketName, string location = null, AutoclassDat TempBucketNames.Add(bucketName); } + internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = false, bool registerForDeletion = true) + { + var bucket = Client.CreateBucket(ProjectId, + new Bucket + { + Name = name, + Versioning = new Bucket.VersioningData { Enabled = multiVersion }, + // The minimum allowed for soft delete is 7 days. + SoftDeletePolicy = softDelete ? new Bucket.SoftDeletePolicyData { RetentionDurationSeconds = (int) TimeSpan.FromDays(7).TotalSeconds } : null, + }); + SleepAfterBucketCreateUpdateDelete(); + if (registerForDeletion) + { + TempBucketNames.Add(name); + } + return bucket; + } + + internal string GenerateBucketName() => Guid.NewGuid().ToString(); + /// /// Bucket creation/update/deletion is rate-limited. To avoid making the tests flaky, we sleep after each operation. /// diff --git a/storage/api/Storage.Samples/GetSoftDeletedBucket.cs b/storage/api/Storage.Samples/GetSoftDeletedBucket.cs new file mode 100644 index 00000000000..75f68d0b311 --- /dev/null +++ b/storage/api/Storage.Samples/GetSoftDeletedBucket.cs @@ -0,0 +1,45 @@ +// Copyright 2025 Google Inc. +// +// 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. + +// [START storage_get_soft_deleted_bucket] + +using Google.Apis.Storage.v1.Data; +using Google.Cloud.Storage.V1; +using System; + +public class GetSoftDeletedBucketSample +{ + /// + /// Get a soft deleted bucket. + /// + /// The name of the bucket. + /// The generation of the bucket. + public Bucket GetSoftDeletedBucket( + string bucketName = "your-unique-bucket-name", + long? generation = 123456789) + { + var client = StorageClient.Create(); + var bucket = client.GetBucket(bucketName, new GetBucketOptions + { + SoftDeleted = true, + Generation = generation + }); + Console.WriteLine($"Bucket:\t{bucket.Name}"); + Console.WriteLine($"Bucket Generation:\t{bucket.Generation}"); + Console.WriteLine($"Bucket SoftDelete Time:\t{bucket.SoftDeleteTimeDateTimeOffset}"); + Console.WriteLine($"Bucket HardDelete Time:\t{bucket.HardDeleteTimeDateTimeOffset}"); + return bucket; + } +} +// [END storage_get_soft_deleted_bucket] diff --git a/storage/api/Storage.Samples/ListSoftDeletedBuckets.cs b/storage/api/Storage.Samples/ListSoftDeletedBuckets.cs new file mode 100644 index 00000000000..06ff7b38bd3 --- /dev/null +++ b/storage/api/Storage.Samples/ListSoftDeletedBuckets.cs @@ -0,0 +1,40 @@ +// Copyright 2025 Google Inc. +// +// 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. + +// [START storage_list_soft_deleted_buckets] + +using Google.Apis.Storage.v1.Data; +using Google.Cloud.Storage.V1; +using System; +using System.Collections.Generic; + +public class ListSoftDeletedBucketsSample +{ + /// + /// List soft deleted buckets. + /// + /// The ID of the project to list soft deleted buckets. + public IEnumerable ListSoftDeletedBuckets(string projectId = "your-project-id") + { + var storage = StorageClient.Create(); + var buckets = storage.ListBuckets(projectId, new ListBucketsOptions { SoftDeletedOnly = true }); + Console.WriteLine("Soft Deleted Buckets are as follows:"); + foreach (var bucket in buckets) + { + Console.WriteLine(bucket.Name); + } + return buckets; + } +} +// [END storage_list_soft_deleted_buckets] diff --git a/storage/api/Storage.Samples/RestoreSoftDeletedBucket.cs b/storage/api/Storage.Samples/RestoreSoftDeletedBucket.cs new file mode 100644 index 00000000000..870a01ea5cb --- /dev/null +++ b/storage/api/Storage.Samples/RestoreSoftDeletedBucket.cs @@ -0,0 +1,38 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"). +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://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. + +// [START storage_restore_bucket] + +using Google.Apis.Storage.v1.Data; +using Google.Cloud.Storage.V1; +using System; +public class RestoreSoftDeletedBucketSample +{ + /// + /// Restores a soft deleted bucket. + /// + /// The name of the bucket to restore. + /// The generation of the bucket. + public Bucket RestoreSoftDeletedBucket( + string bucketName = "your-unique-bucket-name", + long generation = 123456789) + { + var client = StorageClient.Create(); + var restored = client.RestoreBucket(bucketName, generation); + Console.WriteLine($"Bucket Name:\t {restored.Name}"); + Console.WriteLine($"Bucket Generation:\t {restored.Generation}"); + return restored; + } +} +// [END storage_restore_bucket] diff --git a/storage/api/Storage.Samples/Storage.Samples.csproj b/storage/api/Storage.Samples/Storage.Samples.csproj index 7674b9deaf5..9d1dbdcfc5e 100644 --- a/storage/api/Storage.Samples/Storage.Samples.csproj +++ b/storage/api/Storage.Samples/Storage.Samples.csproj @@ -1,4 +1,4 @@ - + netstandard2.1