Skip to content

Commit f773cad

Browse files
feat: Bucket soft delete
1 parent d85534f commit f773cad

File tree

13 files changed

+348
-5
lines changed

13 files changed

+348
-5
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"):
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Threading.Tasks;
17+
using Xunit;
18+
19+
namespace Google.Cloud.Storage.V1.IntegrationTests;
20+
21+
[Collection(nameof(StorageFixture))]
22+
public class GetBucketTest
23+
{
24+
private readonly StorageFixture _fixture;
25+
26+
public GetBucketTest(StorageFixture fixture)
27+
{
28+
_fixture = fixture;
29+
}
30+
31+
[Fact]
32+
public async Task SoftDeleted()
33+
{
34+
var bucketName = _fixture.GenerateBucketName();
35+
var softDeleteBucket = _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: true);
36+
await _fixture.Client.DeleteBucketAsync(softDeleteBucket.Name, new DeleteBucketOptions { DeleteObjects = true });
37+
38+
var softDeleted = await _fixture.Client.GetBucketAsync(softDeleteBucket.Name, new GetBucketOptions { SoftDeleted = true, Generation = softDeleteBucket.Generation });
39+
Assert.Equal(softDeleteBucket.Name, softDeleted.Name);
40+
Assert.Equal(softDeleteBucket.Generation, softDeleted.Generation);
41+
Assert.NotNull(softDeleted.SoftDeleteTimeDateTimeOffset);
42+
Assert.NotNull(softDeleted.HardDeleteTimeDateTimeOffset);
43+
}
44+
}

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.IntegrationTests/ListBucketsTest.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,32 @@ public void PartialResponses()
7575
}
7676
}
7777

78+
[Fact]
79+
public async Task SoftDeletedOnly()
80+
{
81+
var bucketName = _fixture.GenerateBucketName();
82+
var softDeleteBucket = _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: true);
83+
84+
await _fixture.Client.DeleteBucketAsync(softDeleteBucket.Name, new DeleteBucketOptions { DeleteObjects = true });
85+
86+
var actualBuckets = await _fixture.Client
87+
.ListBucketsAsync(_fixture.ProjectId, new ListBucketsOptions { SoftDeletedOnly = true })
88+
.ToListAsync();
89+
90+
// Check the list cotains the bucket we just soft-deleted.
91+
Assert.Contains(actualBuckets, bucket => bucket.Name == softDeleteBucket.Name && bucket.Generation == softDeleteBucket.Generation);
92+
// Check all the buckets in the list are soft-deleted buckets.
93+
Assert.All(actualBuckets, AssertSoftDeletedBucket);
94+
}
95+
96+
// Validates that the given bucket is soft-deleted.
97+
private void AssertSoftDeletedBucket(Bucket b)
98+
{
99+
Assert.NotNull(b.Generation);
100+
Assert.NotNull(b.HardDeleteTimeDateTimeOffset);
101+
Assert.NotNull(b.SoftDeleteTimeDateTimeOffset);
102+
}
103+
78104
// Fetches buckets using the given options in each possible way, validating that the expected bucket names are returned.
79105
private async Task AssertBuckets(ListBucketsOptions options, params string[] expectedBucketNames)
80106
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2024 Google Inc. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Threading.Tasks;
17+
using Xunit;
18+
19+
namespace Google.Cloud.Storage.V1.IntegrationTests;
20+
21+
[Collection(nameof(StorageFixture))]
22+
public class RestoreBucketTest
23+
{
24+
private readonly StorageFixture _fixture;
25+
26+
public RestoreBucketTest(StorageFixture fixture)
27+
{
28+
_fixture = fixture;
29+
}
30+
31+
[Fact]
32+
public async Task RestoreSoftDeletedBucket()
33+
{
34+
var bucketName = _fixture.GenerateBucketName();
35+
var softDeleteBucket = _fixture.CreateBucket(bucketName, multiVersion: false, softDelete: true);
36+
await _fixture.Client.DeleteBucketAsync(softDeleteBucket.Name, new DeleteBucketOptions { DeleteObjects = true });
37+
38+
var restoredBucket = await _fixture.Client.RestoreBucketAsync(softDeleteBucket.Name, softDeleteBucket.Generation.Value);
39+
Assert.Equal(softDeleteBucket.Name, restoredBucket.Name);
40+
Assert.Equal(softDeleteBucket.Generation, restoredBucket.Generation);
41+
}
42+
}

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/GetBucketOptionsTest.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public void ModifyRequest_DefaultOptions()
3131
Assert.Null(request.IfMetagenerationNotMatch);
3232
Assert.Null(request.Projection);
3333
Assert.Null(request.UserProject);
34+
Assert.Null(request.SoftDeleted);
35+
Assert.Null(request.Generation);
3436
}
3537

3638
[Fact]
@@ -41,13 +43,17 @@ public void ModifyRequest_PositiveMatchOptions()
4143
{
4244
IfMetagenerationMatch = 1L,
4345
Projection = Projection.Full,
44-
UserProject = "proj"
46+
UserProject = "proj",
47+
SoftDeleted = true,
48+
Generation = long.MaxValue
4549
};
4650
options.ModifyRequest(request);
4751
Assert.Equal(1L, request.IfMetagenerationMatch);
4852
Assert.Null(request.IfMetagenerationNotMatch);
4953
Assert.Equal(ProjectionEnum.Full, request.Projection);
5054
Assert.Equal("proj", request.UserProject);
55+
Assert.True(request.SoftDeleted);
56+
Assert.Equal(long.MaxValue, request.Generation);
5157
}
5258

5359
[Fact]

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.Tests/ListBucketsOptionsTest.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ public void ModifyRequest_DefaultOptions()
3030
Assert.Null(request.Prefix);
3131
Assert.Null(request.MaxResults);
3232
Assert.Null(request.PageToken);
33+
Assert.Null(request.SoftDeleted);
3334
}
3435

3536
[Fact]
@@ -42,14 +43,16 @@ public void ModifyRequest_AllOptions()
4243
Prefix = "prefix",
4344
Projection = Projection.Full,
4445
PageToken = "nextpage",
45-
Fields = "items(name),nextPageToken"
46+
Fields = "items(name),nextPageToken",
47+
SoftDeletedOnly = true,
4648
};
4749
options.ModifyRequest(request);
4850
Assert.Equal(10, request.MaxResults);
4951
Assert.Equal("prefix", request.Prefix);
5052
Assert.Equal(ProjectionEnum.Full, request.Projection);
5153
Assert.Equal("nextpage", request.PageToken);
5254
Assert.Equal("items(name),nextPageToken", request.Fields);
55+
Assert.True(request.SoftDeleted);
5356
}
5457
}
5558
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"):
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Xunit;
16+
using static Google.Apis.Storage.v1.BucketsResource;
17+
using static Google.Apis.Storage.v1.BucketsResource.RestoreRequest;
18+
19+
namespace Google.Cloud.Storage.V1.Tests;
20+
public class RestoreBucketOptionsTest
21+
{
22+
[Fact]
23+
public void ModifyRequest_DefaultOptions()
24+
{
25+
var request = new RestoreRequest(null, "bucket", 2L);
26+
var options = new RestoreBucketOptions();
27+
options.ModifyRequest(request);
28+
Assert.Null(request.Projection);
29+
Assert.Null(request.UserProject);
30+
}
31+
32+
[Fact]
33+
public void ModifyRequest_AllOptions()
34+
{
35+
var request = new RestoreRequest(null, "bucket", 2L);
36+
var options = new RestoreBucketOptions
37+
{
38+
Projection = Projection.Full,
39+
UserProject = "proj"
40+
};
41+
options.ModifyRequest(request);
42+
Assert.Equal(ProjectionEnum.Full, request.Projection);
43+
Assert.Equal("proj", request.UserProject);
44+
}
45+
}

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/GetBucketOptions.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ public sealed class GetBucketOptions
5252
/// </summary>
5353
public RetryOptions RetryOptions { get; set; }
5454

55+
/// <summary>
56+
/// The bucket generation to be retrieved. It must be set if <see ref="SoftDelete" /> is true.
57+
/// </summary>
58+
public long? Generation { get; set; }
59+
60+
/// <summary>
61+
/// If true, the soft-deleted version of the bucket will be retrieved.
62+
/// If true, <see ref="Generation" /> must be set.
63+
/// </summary>
64+
public bool? SoftDeleted { get; set; }
65+
5566
internal void ModifyRequest(GetRequest request)
5667
{
5768
if (IfMetagenerationMatch != null && IfMetagenerationNotMatch != null)
@@ -75,6 +86,15 @@ internal void ModifyRequest(GetRequest request)
7586
{
7687
request.UserProject = UserProject;
7788
}
89+
if (Generation != null)
90+
{
91+
request.Generation = Generation;
92+
}
93+
if (SoftDeleted != null)
94+
{
95+
request.SoftDeleted = SoftDeleted;
96+
}
97+
7898
}
7999
}
80100
}

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<ItemGroup>
1111
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="All" />
1212
<PackageReference Include="Google.Api.Gax.Rest" />
13-
<PackageReference Include="Google.Apis.Storage.v1" VersionOverride="[1.68.0.3431, 2.0.0.0)" />
13+
<PackageReference Include="Google.Apis.Storage.v1" VersionOverride="[1.68.0.3604, 2.0.0.0)" />
1414
</ItemGroup>
1515
<ItemGroup>
1616
<Compile Update="StorageClient.*.cs">
@@ -23,4 +23,4 @@
2323
<DependentUpon>UrlSigner.cs</DependentUpon>
2424
</Compile>
2525
</ItemGroup>
26-
</Project>
26+
</Project>

apis/Google.Cloud.Storage.V1/Google.Cloud.Storage.V1/ListBucketsOptions.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public sealed class ListBucketsOptions
6262
/// </summary>
6363
public RetryOptions RetryOptions { get; set; }
6464

65+
/// <summary>
66+
/// If true, only soft-deleted buckets will be listed. The default is false.
67+
/// </summary>
68+
public bool? SoftDeletedOnly { get; set; }
69+
6570
/// <summary>
6671
/// Modifies the specified request for all non-null properties of this options object.
6772
/// </summary>
@@ -88,6 +93,10 @@ internal void ModifyRequest(ListRequest request)
8893
{
8994
request.Fields = Fields;
9095
}
96+
if (SoftDeletedOnly != null)
97+
{
98+
request.SoftDeleted = SoftDeletedOnly;
99+
}
91100
}
92101
}
93102
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2024 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License"):
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Google.Api.Gax;
16+
using static Google.Apis.Storage.v1.BucketsResource;
17+
using static Google.Apis.Storage.v1.BucketsResource.RestoreRequest;
18+
19+
namespace Google.Cloud.Storage.V1;
20+
21+
/// <summary>
22+
/// Options for RestoreBucket operations.
23+
/// </summary>
24+
public sealed class RestoreBucketOptions
25+
{
26+
/// <summary>
27+
/// The projection of the restored bucket to return. Note the whole bucket will be restored,
28+
/// except for the bucket's access controls. This only affects
29+
/// what information is returned when restoration is successful.
30+
/// </summary>
31+
public Projection? Projection { get; set; }
32+
33+
/// <summary>
34+
/// If set, this is the ID of the project which will be billed for the request.
35+
/// The caller must have suitable permissions for the project being billed.
36+
/// </summary>
37+
public string UserProject { get; set; }
38+
39+
/// <summary>
40+
/// Options to pass custom retry configuration for each API request.
41+
/// </summary>
42+
public RetryOptions RetryOptions { get; set; }
43+
44+
internal void ModifyRequest(RestoreRequest request)
45+
{
46+
if (Projection != null)
47+
{
48+
request.Projection = GaxPreconditions.CheckEnumValue((ProjectionEnum) Projection, nameof(Projection));
49+
}
50+
if (UserProject != null)
51+
{
52+
request.UserProject = UserProject;
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)