diff --git a/storage/api/Storage.Samples.Tests/ListSoftDeletedObjectsTest.cs b/storage/api/Storage.Samples.Tests/ListSoftDeletedObjectsTest.cs new file mode 100644 index 00000000000..372c7c9cc81 --- /dev/null +++ b/storage/api/Storage.Samples.Tests/ListSoftDeletedObjectsTest.cs @@ -0,0 +1,63 @@ +// 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.Linq; +using Xunit; + +[Collection(nameof(StorageFixture))] +public class ListSoftDeletedObjectsTest +{ + private readonly StorageFixture _fixture; + + public ListSoftDeletedObjectsTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ListSoftDeletedObjects() + { + ListSoftDeletedObjectsSample listSoftDeletedObjects = new ListSoftDeletedObjectsSample(); + UploadObjectFromMemorySample uploadObjectFromMemory = new UploadObjectFromMemorySample(); + var bucketName = _fixture.GenerateBucketName(); + _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true); + var objectNameOne = _fixture.GenerateName(); + var objectOneContent = _fixture.GenerateContent(); + var objectNameTwo = _fixture.GenerateName(); + var objectTwoContent = _fixture.GenerateContent(); + uploadObjectFromMemory.UploadObjectFromMemory(bucketName, objectNameOne, objectOneContent); + uploadObjectFromMemory.UploadObjectFromMemory(bucketName, objectNameTwo, objectTwoContent); + var preSoftDeleteObjects = _fixture.Client.ListObjects(bucketName); + int preSoftDeleteObjectsCount = preSoftDeleteObjects.Count(); + _fixture.Client.DeleteObject(bucketName, objectNameOne); + _fixture.Client.DeleteObject(bucketName, objectNameTwo); + var softDeletedObjects = listSoftDeletedObjects.ListSoftDeletedObjects(bucketName); + int softDeletedObjectsCount = softDeletedObjects.Count(); + Assert.Equal(preSoftDeleteObjectsCount, softDeletedObjectsCount); + Assert.All(softDeletedObjects, AssertSoftDeletedObject); + Assert.Multiple( + () => Assert.Contains(softDeletedObjects, softDeletedObject => softDeletedObject.Name == objectNameOne), + () => Assert.Contains(softDeletedObjects, softDeletedObject => softDeletedObject.Name == objectNameTwo) + ); + } + + // Validates that the given object is soft-deleted. + private void AssertSoftDeletedObject(Object o) + { + Assert.NotNull(o.Generation); + Assert.NotNull(o.HardDeleteTimeDateTimeOffset); + Assert.NotNull(o.SoftDeleteTimeDateTimeOffset); + } +} diff --git a/storage/api/Storage.Samples.Tests/ListSoftDeletedVersionsOfObjectTest.cs b/storage/api/Storage.Samples.Tests/ListSoftDeletedVersionsOfObjectTest.cs new file mode 100644 index 00000000000..17f4b168a2f --- /dev/null +++ b/storage/api/Storage.Samples.Tests/ListSoftDeletedVersionsOfObjectTest.cs @@ -0,0 +1,71 @@ +// 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.Collections.Generic; +using System.Linq; +using Xunit; + +[Collection(nameof(StorageFixture))] +public class ListSoftDeletedVersionsOfObjectTest +{ + private readonly StorageFixture _fixture; + private IList _softDeleteObjectGenerations { get; } = new List(); + + public ListSoftDeletedVersionsOfObjectTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void ListSoftDeletedVersionsOfObject() + { + int i = 3; + int preSoftDeleteObjectVersionsCount = i; + ListSoftDeletedVersionsOfObjectSample listSoftDeletedVersionOfObject = new ListSoftDeletedVersionsOfObjectSample(); + UploadObjectFromMemorySample uploadObjectFromMemory = new UploadObjectFromMemorySample(); + RestoreSoftDeletedObjectSample restoreSoftDeletedObjectSample = new RestoreSoftDeletedObjectSample(); + GetMetadataSample getMetadataSample = new GetMetadataSample(); + var bucketName = _fixture.GenerateBucketName(); + _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true); + var objectName = _fixture.GenerateName(); + var objectContent = _fixture.GenerateContent(); + uploadObjectFromMemory.UploadObjectFromMemory(bucketName, objectName, objectContent); + while (i >= 1) + { + var objectMetaData = getMetadataSample.GetMetadata(bucketName, objectName); + _softDeleteObjectGenerations.Add(objectMetaData.Generation.Value); + _fixture.Client.DeleteObject(bucketName, objectName); + var restoredObject = restoreSoftDeletedObjectSample.RestoreSoftDeletedObject(bucketName, objectName, objectMetaData.Generation.Value); + i--; + } + var softDeletedObjectVersions = listSoftDeletedVersionOfObject.ListSoftDeletedVersionsOfObject(bucketName, objectName); + int softDeletedObjectVersionsCount = softDeletedObjectVersions.Count(); + Assert.Equal(preSoftDeleteObjectVersionsCount, softDeletedObjectVersionsCount); + Assert.All(softDeletedObjectVersions, AssertSoftDeletedVersionsOfObject); + Assert.Multiple( + () => Assert.Contains(softDeletedObjectVersions, softDeletedObject => softDeletedObject.Name == objectName && softDeletedObject.Generation == _softDeleteObjectGenerations[_softDeleteObjectGenerations.Count - 1]), + () => Assert.Contains(softDeletedObjectVersions, softDeletedObject => softDeletedObject.Name == objectName && softDeletedObject.Generation == _softDeleteObjectGenerations[_softDeleteObjectGenerations.Count - 2]), + () => Assert.Contains(softDeletedObjectVersions, softDeletedObject => softDeletedObject.Name == objectName && softDeletedObject.Generation == _softDeleteObjectGenerations[_softDeleteObjectGenerations.Count - 3]) + ); + _fixture.Client.DeleteObject(bucketName, objectName); + } + + private void AssertSoftDeletedVersionsOfObject(Object o) + { + Assert.NotNull(o.Generation); + Assert.NotNull(o.HardDeleteTimeDateTimeOffset); + Assert.NotNull(o.SoftDeleteTimeDateTimeOffset); + } +} diff --git a/storage/api/Storage.Samples.Tests/RestoreSoftDeletedObjectTest.cs b/storage/api/Storage.Samples.Tests/RestoreSoftDeletedObjectTest.cs new file mode 100644 index 00000000000..f2a8d9dc62d --- /dev/null +++ b/storage/api/Storage.Samples.Tests/RestoreSoftDeletedObjectTest.cs @@ -0,0 +1,46 @@ +// 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 Xunit; + +[Collection(nameof(StorageFixture))] +public class RestoreSoftDeletedObjectTest +{ + private readonly StorageFixture _fixture; + + public RestoreSoftDeletedObjectTest(StorageFixture fixture) + { + _fixture = fixture; + } + + [Fact] + public void RestoreSoftDeletedObject() + { + RestoreSoftDeletedObjectSample restoreSoftDeletedObjectSample = new RestoreSoftDeletedObjectSample(); + UploadObjectFromMemorySample uploadObjectFromMemory = new UploadObjectFromMemorySample(); + GetMetadataSample getMetadataSample = new GetMetadataSample(); + var bucketName = _fixture.GenerateBucketName(); + _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: false, registerForDeletion: true); + var objectName = _fixture.GenerateName(); + var objectContent = _fixture.GenerateContent(); + uploadObjectFromMemory.UploadObjectFromMemory(bucketName, objectName, objectContent); + var objectMetaData = getMetadataSample.GetMetadata(bucketName, objectName); + _fixture.Client.DeleteObject(bucketName, objectName); + var restoredObject = restoreSoftDeletedObjectSample.RestoreSoftDeletedObject(bucketName, objectName, objectMetaData.Generation.Value); + Assert.Equal(objectName, restoredObject.Name); + Assert.Equal(objectMetaData.Size, restoredObject.Size); + Assert.Equal(objectMetaData.Md5Hash, restoredObject.Md5Hash); + _fixture.Client.DeleteObject(bucketName, objectName); + } +} diff --git a/storage/api/Storage.Samples.Tests/StorageFixture.cs b/storage/api/Storage.Samples.Tests/StorageFixture.cs index 9bcc2903b58..f159b5843c8 100644 --- a/storage/api/Storage.Samples.Tests/StorageFixture.cs +++ b/storage/api/Storage.Samples.Tests/StorageFixture.cs @@ -239,6 +239,18 @@ internal Bucket CreateBucket(string name, bool multiVersion, bool softDelete = f internal string GenerateBucketName() => Guid.NewGuid().ToString(); + /// + /// Generate the name of the object. + /// + /// The objectName. + internal string GenerateName() => Guid.NewGuid().ToString(); + + /// + /// Generate the content of the object. + /// + /// The objectContent. + internal string GenerateContent() => 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/ListSoftDeletedObjects.cs b/storage/api/Storage.Samples/ListSoftDeletedObjects.cs new file mode 100644 index 00000000000..505126145ba --- /dev/null +++ b/storage/api/Storage.Samples/ListSoftDeletedObjects.cs @@ -0,0 +1,39 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// [START storage_list_soft_deleted_objects] + +using Google.Cloud.Storage.V1; +using System; +using System.Collections.Generic; + +public class ListSoftDeletedObjectsSample +{ + /// + /// List all soft-deleted objects in the bucket. + /// + /// The name of the bucket. + public IEnumerable ListSoftDeletedObjects(string bucketName = "your-unique-bucket-name") + { + var storage = StorageClient.Create(); + var objects = storage.ListObjects(bucketName, prefix: null, new ListObjectsOptions { SoftDeletedOnly = true }); + Console.WriteLine($"The Names of the Soft Deleted Objects in the Bucket (Bucket Name: {bucketName}) are as follows:"); + foreach (var obj in objects) + { + Console.WriteLine($"Object Name: {obj.Name}"); + } + return objects; + } +} +// [END storage_list_soft_deleted_objects] diff --git a/storage/api/Storage.Samples/ListSoftDeletedVersionsOfObject.cs b/storage/api/Storage.Samples/ListSoftDeletedVersionsOfObject.cs new file mode 100644 index 00000000000..31b6ca359d3 --- /dev/null +++ b/storage/api/Storage.Samples/ListSoftDeletedVersionsOfObject.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 +// +// 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_object_versions] + +using Google.Cloud.Storage.V1; +using System; +using System.Collections.Generic; + +public class ListSoftDeletedVersionsOfObjectSample +{ + /// + /// List all soft-deleted versions of the object in the bucket. + /// + /// The name of the bucket. + /// The name of the object. + public IEnumerable ListSoftDeletedVersionsOfObject(string bucketName = "your-unique-bucket-name", + string objectName = "your-object-name") + { + var storage = StorageClient.Create(); + var objects = storage.ListObjects(bucketName, prefix: null, new ListObjectsOptions { SoftDeletedOnly = true, MatchGlob = objectName }); + Console.WriteLine($"The Name and Versions of the Soft Deleted Object in the Bucket (Bucket Name: {bucketName}) are as follows:"); + foreach (var obj in objects) + { + Console.WriteLine($"Object Name: {obj.Name} and Version: {obj.Generation}"); + } + return objects; + } +} +// [END storage_list_soft_deleted_object_versions] diff --git a/storage/api/Storage.Samples/RestoreSoftDeletedObject.cs b/storage/api/Storage.Samples/RestoreSoftDeletedObject.cs new file mode 100644 index 00000000000..bab812cbe02 --- /dev/null +++ b/storage/api/Storage.Samples/RestoreSoftDeletedObject.cs @@ -0,0 +1,39 @@ +// 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_object] + +using Google.Cloud.Storage.V1; +using System; + +public class RestoreSoftDeletedObjectSample +{ + /// + /// Restores a soft-deleted object. + /// + /// The name of the bucket. + /// The name of the soft-deleted object. + /// The generation of the soft-deleted object. + public Google.Apis.Storage.v1.Data.Object RestoreSoftDeletedObject( + string bucketName = "your-unique-bucket-name", + string objectName = "your-object-name", + long generation = 1579287380533984) + { + var client = StorageClient.Create(); + var restoredObject = client.RestoreObject(bucketName, objectName, generation); + Console.WriteLine($"The Name of the Restored Object which was previously Soft-deleted is : {restoredObject.Name}"); + return restoredObject; + } +} +// [END storage_restore_object]