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 29, 2023
1 parent 5a4861b commit 247b01d
Show file tree
Hide file tree
Showing 8 changed files with 123 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 renetion 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,8 @@ internal void ModifyRequest(BucketsResource.InsertRequest request)
{
request.Projection = GaxPreconditions.CheckEnumValue((ProjectionEnum) Projection, nameof(Projection));
}

request.EnableObjectRetention = EnableObjectRetention;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ private ObjectsResource.UpdateRequest CreateUpdateObjectRequest(Object obj, Upda
GaxPreconditions.CheckArgument(obj.Bucket != null, nameof(obj), "The Bucket property of the object to update is null");
GaxPreconditions.CheckArgument(obj.Name != null, nameof(obj), "The Name property of the object to update is null");
var request = Service.Objects.Update(obj, obj.Bucket, obj.Name);

if (options?.RetentionMode != null || options?.RetainUntilTime != null)
{
obj.Retention = new Object.RetentionData
{
Mode = options.RetentionMode,
RetainUntilTimeDateTimeOffset = options.RetainUntilTime
};
}
else
{
obj.Retention = null;
}
options?.ModifyRequest(request, obj);
RetryOptions retryOptions = options?.RetryOptions ?? RetryOptions.MaybeIdempotent(options?.IfMetagenerationMatch);
MarkAsRetriable(request, retryOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@ public override ObjectsResource.InsertMediaUpload CreateObjectUploader(
{
ValidateObject(destination, nameof(destination));
GaxPreconditions.CheckNotNull(source, nameof(source));

if (options?.RetentionMode != null || options?.RetainUntilTime != null)
{
destination.Retention = new Object.RetentionData();
if (options.RetentionMode != null)
{
destination.Retention.Mode = options.RetentionMode.ToString();
}
if (options.RetainUntilTime != null)
{
destination.Retention.RetainUntilTimeDateTimeOffset = options.RetainUntilTime;
}
}
var mediaUpload = new CustomMediaUpload(Service, destination, destination.Bucket, source, destination.ContentType);
options?.ModifyMediaUpload(mediaUpload);
ApplyEncryptionKey(options?.EncryptionKey, options?.KmsKeyName, mediaUpload);
Expand Down
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,10 @@ internal void ModifyRequest(UpdateRequest request, Object obj)
{
request.UserProject = UserProject;
}
if (OverrideUnlockedRetention != null)
{
request.OverrideUnlockedRetention = OverrideUnlockedRetention;
}
}
}
}
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

0 comments on commit 247b01d

Please sign in to comment.