Skip to content

Commit

Permalink
feat: Storage object retention lock
Browse files Browse the repository at this point in the history
  • Loading branch information
anuragsrivstv committed Nov 30, 2023
1 parent 5a4861b commit 1397ed4
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,59 @@ public void LockRetentionPolicy()
Assert.True(bucket.RetentionPolicy.IsLocked);
}

/// <summary>
/// Tests for object retention policy.
/// </summary>
[Fact]
public void ObjectRetentionTests()
{
var client = _fixture.Client;
var bucketName = _fixture.GenerateBucketName();

// Create a bucket with object retention enabled.
var createBucketoptions = new CreateBucketOptions { EnableObjectRetention = true };
var bucket = _fixture.CreateBucket(bucketName, false, createBucketoptions);
bucket = client.GetBucket(bucketName);
Assert.Equal("Enabled", bucket.ObjectRetention?.Mode);

var baseDateTime = DateTimeOffset.UtcNow;

// Create an object, with retention configured.
var uploadObjectOptions = new UploadObjectOptions
{
RetainUntilTime = baseDateTime.AddDays(5),
RetentionMode = "Unlocked"
};
string objectName = "object.txt";
CreateObject(bucketName, objectName, uploadObjectOptions);
var obj = client.GetObject(bucketName, objectName);
Assert.Equal("Unlocked", obj.Retention?.Mode);
Assert.Equal(baseDateTime.AddDays(5).Date, obj.Retention.RetainUntilTimeDateTimeOffset.Value.Date);

Assert.Throws<GoogleApiException>(() => client.DeleteObject(bucketName, objectName));

// Update the retainUntilTime of the object
var updateObjectOptions = new UpdateObjectOptions
{
RetainUntilTime = baseDateTime.AddDays(2),
OverrideUnlockedRetention = true,
RetentionMode = "Unlocked"
};
client.UpdateObject(obj, updateObjectOptions);
var updatedObj = client.GetObject(bucketName, objectName);
Assert.Equal(baseDateTime.AddDays(2).Date, updatedObj.Retention?.RetainUntilTimeDateTimeOffset.Value.Date);

Assert.Throws<GoogleApiException>(() => client.DeleteObject(bucketName, objectName));

// Remove retention for existing object.
var removeobjectRetentionOption = new UpdateObjectOptions { OverrideUnlockedRetention = true, }; //RetainUntilTime and RetentionMode is kept null.
client.UpdateObject(updatedObj, removeobjectRetentionOption);
var retentionRemovedObj = client.GetObject(bucketName, objectName);
Assert.Null(retentionRemovedObj.Retention);

client.DeleteObject(bucketName, objectName);
}

/// <summary>
/// Test Set/Update temporary hold on an object and attempt to delete an object.
/// It should fail until temporaryHold is set to false.
Expand Down Expand Up @@ -200,11 +253,11 @@ public void DefaultEventBasedHold()
client.UpdateObject(obj);
}

private Object CreateObject(string bucketName, string objectName)
private Object CreateObject(string bucketName, string objectName, UploadObjectOptions options = null)
{
string text = $"This is the content of {objectName}";
var bytes = Encoding.UTF8.GetBytes(text);
return _fixture.Client.UploadObject(bucketName, objectName, "text/plain", new MemoryStream(bytes));
return _fixture.Client.UploadObject(bucketName, objectName, "text/plain", new MemoryStream(bytes), options);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -242,9 +242,9 @@ void CreateObject()

}

internal Bucket CreateBucket(string name, bool multiVersion)
internal Bucket CreateBucket(string name, bool multiVersion, CreateBucketOptions options = null)
{
var bucket = Client.CreateBucket(ProjectId, new Bucket { Name = name, Versioning = new Bucket.VersioningData { Enabled = multiVersion } });
var bucket = Client.CreateBucket(ProjectId, new Bucket { Name = name, Versioning = new Bucket.VersioningData { Enabled = multiVersion } }, options);
SleepAfterBucketCreateDelete();
RegisterBucketToDelete(name);
return bucket;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ public void ModifyRequest_AllOptions()
{
PredefinedAcl = PredefinedBucketAcl.AuthenticatedRead,
PredefinedDefaultObjectAcl = PredefinedObjectAcl.BucketOwnerFullControl,
Projection = Projection.Full
Projection = Projection.Full,
EnableObjectRetention = true
};
options.ModifyRequest(request);
Assert.Equal(PredefinedAclEnum.AuthenticatedRead, request.PredefinedAcl);
Assert.Equal(PredefinedDefaultObjectAclEnum.BucketOwnerFullControl, request.PredefinedDefaultObjectAcl);
Assert.Equal(ProjectionEnum.Full, request.Projection);
Assert.True(request.EnableObjectRetention);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ public sealed class CreateBucketOptions
/// </summary>
public RetryOptions RetryOptions { get; set; }

/// <summary>
/// When set to true, object retention is enabled for this bucket.
/// </summary>
public bool? EnableObjectRetention { get; set; }

internal void ModifyRequest(BucketsResource.InsertRequest request)
{
if (PredefinedAcl != null)
Expand All @@ -59,6 +64,10 @@ internal void ModifyRequest(BucketsResource.InsertRequest request)
{
request.Projection = GaxPreconditions.CheckEnumValue((ProjectionEnum) Projection, nameof(Projection));
}
if (EnableObjectRetention != null)
{
request.EnableObjectRetention = EnableObjectRetention;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,22 @@ public sealed class UpdateObjectOptions
/// </summary>
public RetryOptions RetryOptions { get; set; }

/// <summary>
/// Specifies the time that the object should be retained until.
/// </summary>
public DateTimeOffset? RetainUntilTime { get; set; }

/// <summary>
/// Must be true to remove the retention configuration, reduce its unlocked retention period, or change its
/// mode from unlocked to locked.
/// </summary>
public bool? OverrideUnlockedRetention { get; set; }

/// <summary>
/// The object's retention mode, can only be Unlocked or Locked.
/// </summary>
public string RetentionMode { get; set; }

private bool AnyExplicitPreconditions =>
IfGenerationMatch != null || IfGenerationNotMatch != null || IfMetagenerationMatch != null || IfMetagenerationNotMatch != null;

Expand Down Expand Up @@ -149,6 +165,27 @@ internal void ModifyRequest(UpdateRequest request, Object obj)
{
request.UserProject = UserProject;
}
if (OverrideUnlockedRetention != null)
{
request.OverrideUnlockedRetention = OverrideUnlockedRetention;
}
if (RetentionMode != null || RetainUntilTime != null)
{
var objRetention = obj.Retention ?? new Object.RetentionData();
if (RetentionMode != null)
{
objRetention.Mode = RetentionMode;
}
if (RetainUntilTime != null)
{
objRetention.RetainUntilTimeDateTimeOffset = RetainUntilTime;
}
obj.Retention = objRetention;
}
else
{
obj.Retention = null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ public int? ChunkSize
/// </summary>
public string Origin { get; set; }

/// <summary>
/// Specifies the time that the object should be retaind until.
/// </summary>
public DateTimeOffset? RetainUntilTime { get; set; }

/// <summary>
/// The object's retention mode, can only be Unlocked or Locked.
/// </summary>
public string RetentionMode { get; set; }

internal void ModifyMediaUpload(CustomMediaUpload upload)
{
// Note the use of ArgumentException here, as this will basically be the result of invalid
Expand Down Expand Up @@ -185,6 +195,18 @@ internal void ModifyMediaUpload(CustomMediaUpload upload)
{
upload.Options.ModifySessionInitiationRequest += message => message.Headers.Add("Origin", Origin);
}
if (RetentionMode != null || RetainUntilTime != null)
{
upload.Body.Retention = new Object.RetentionData();
if (RetentionMode != null)
{
upload.Body.Retention.Mode = RetentionMode;
}
if (RetainUntilTime != null)
{
upload.Body.Retention.RetainUntilTimeDateTimeOffset = RetainUntilTime;
}
}
}
}
}

0 comments on commit 1397ed4

Please sign in to comment.