2 changed files with 405 additions and 0 deletions
@ -0,0 +1,260 @@ |
|||
package retention |
|||
|
|||
import ( |
|||
"context" |
|||
"strings" |
|||
"testing" |
|||
"time" |
|||
|
|||
"github.com/aws/aws-sdk-go-v2/aws" |
|||
"github.com/aws/aws-sdk-go-v2/service/s3" |
|||
"github.com/aws/aws-sdk-go-v2/service/s3/types" |
|||
"github.com/stretchr/testify/assert" |
|||
"github.com/stretchr/testify/require" |
|||
) |
|||
|
|||
// TestBucketDeletionWithObjectLock tests that buckets with object lock enabled
|
|||
// cannot be deleted if they contain objects with active retention or legal hold
|
|||
func TestBucketDeletionWithObjectLock(t *testing.T) { |
|||
client := getS3Client(t) |
|||
bucketName := getNewBucketName() |
|||
|
|||
// Create bucket with object lock enabled
|
|||
createBucketWithObjectLock(t, client, bucketName) |
|||
|
|||
// Test 1: Bucket deletion with active compliance retention should fail
|
|||
t.Run("CannotDeleteBucketWithComplianceRetention", func(t *testing.T) { |
|||
key := "test-compliance-retention" |
|||
content := "test content for compliance retention" |
|||
retainUntilDate := time.Now().Add(10 * time.Second) // 10 seconds in future
|
|||
|
|||
// Upload object with compliance retention
|
|||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
Body: strings.NewReader(content), |
|||
ObjectLockMode: types.ObjectLockModeCompliance, |
|||
ObjectLockRetainUntilDate: aws.Time(retainUntilDate), |
|||
}) |
|||
require.NoError(t, err, "PutObject with compliance retention should succeed") |
|||
|
|||
// Try to delete bucket - should fail because object has active retention
|
|||
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.Error(t, err, "DeleteBucket should fail when objects have active retention") |
|||
assert.Contains(t, err.Error(), "BucketNotEmpty", "Error should be BucketNotEmpty") |
|||
t.Logf("Expected error: %v", err) |
|||
|
|||
// Wait for retention to expire
|
|||
t.Logf("Waiting for compliance retention to expire...") |
|||
time.Sleep(11 * time.Second) |
|||
|
|||
// Delete the object
|
|||
_, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
}) |
|||
require.NoError(t, err, "DeleteObject should succeed after retention expires") |
|||
|
|||
// Clean up versions
|
|||
deleteAllObjectVersions(t, client, bucketName) |
|||
}) |
|||
|
|||
// Test 2: Bucket deletion with active governance retention should fail
|
|||
t.Run("CannotDeleteBucketWithGovernanceRetention", func(t *testing.T) { |
|||
key := "test-governance-retention" |
|||
content := "test content for governance retention" |
|||
retainUntilDate := time.Now().Add(10 * time.Second) // 10 seconds in future
|
|||
|
|||
// Upload object with governance retention
|
|||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
Body: strings.NewReader(content), |
|||
ObjectLockMode: types.ObjectLockModeGovernance, |
|||
ObjectLockRetainUntilDate: aws.Time(retainUntilDate), |
|||
}) |
|||
require.NoError(t, err, "PutObject with governance retention should succeed") |
|||
|
|||
// Try to delete bucket - should fail because object has active retention
|
|||
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.Error(t, err, "DeleteBucket should fail when objects have active retention") |
|||
assert.Contains(t, err.Error(), "BucketNotEmpty", "Error should be BucketNotEmpty") |
|||
t.Logf("Expected error: %v", err) |
|||
|
|||
// Wait for retention to expire
|
|||
t.Logf("Waiting for governance retention to expire...") |
|||
time.Sleep(11 * time.Second) |
|||
|
|||
// Delete the object
|
|||
_, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
}) |
|||
require.NoError(t, err, "DeleteObject should succeed after retention expires") |
|||
|
|||
// Clean up versions
|
|||
deleteAllObjectVersions(t, client, bucketName) |
|||
}) |
|||
|
|||
// Test 3: Bucket deletion with legal hold should fail
|
|||
t.Run("CannotDeleteBucketWithLegalHold", func(t *testing.T) { |
|||
key := "test-legal-hold" |
|||
content := "test content for legal hold" |
|||
|
|||
// Upload object first
|
|||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
Body: strings.NewReader(content), |
|||
}) |
|||
require.NoError(t, err, "PutObject should succeed") |
|||
|
|||
// Set legal hold on the object
|
|||
_, err = client.PutObjectLegalHold(context.TODO(), &s3.PutObjectLegalHoldInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
LegalHold: &types.ObjectLockLegalHold{Status: types.ObjectLockLegalHoldStatusOn}, |
|||
}) |
|||
require.NoError(t, err, "PutObjectLegalHold should succeed") |
|||
|
|||
// Try to delete bucket - should fail because object has active legal hold
|
|||
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.Error(t, err, "DeleteBucket should fail when objects have active legal hold") |
|||
assert.Contains(t, err.Error(), "BucketNotEmpty", "Error should be BucketNotEmpty") |
|||
t.Logf("Expected error: %v", err) |
|||
|
|||
// Remove legal hold
|
|||
_, err = client.PutObjectLegalHold(context.TODO(), &s3.PutObjectLegalHoldInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
LegalHold: &types.ObjectLockLegalHold{Status: types.ObjectLockLegalHoldStatusOff}, |
|||
}) |
|||
require.NoError(t, err, "Removing legal hold should succeed") |
|||
|
|||
// Delete the object
|
|||
_, err = client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
}) |
|||
require.NoError(t, err, "DeleteObject should succeed after legal hold is removed") |
|||
|
|||
// Clean up versions
|
|||
deleteAllObjectVersions(t, client, bucketName) |
|||
}) |
|||
|
|||
// Test 4: Bucket deletion should succeed when no objects have active locks
|
|||
t.Run("CanDeleteBucketWithoutActiveLocks", func(t *testing.T) { |
|||
// Make sure all objects are deleted
|
|||
deleteAllObjectVersions(t, client, bucketName) |
|||
|
|||
// Wait for eventual consistency
|
|||
time.Sleep(500 * time.Millisecond) |
|||
|
|||
// Now delete bucket should succeed
|
|||
_, err := client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.NoError(t, err, "DeleteBucket should succeed when no objects have active locks") |
|||
t.Logf("Successfully deleted bucket without active locks") |
|||
}) |
|||
} |
|||
|
|||
// TestBucketDeletionWithVersionedLocks tests deletion with versioned objects under lock
|
|||
func TestBucketDeletionWithVersionedLocks(t *testing.T) { |
|||
client := getS3Client(t) |
|||
bucketName := getNewBucketName() |
|||
|
|||
// Create bucket with object lock enabled
|
|||
createBucketWithObjectLock(t, client, bucketName) |
|||
defer deleteBucket(t, client, bucketName) // Best effort cleanup
|
|||
|
|||
key := "test-versioned-locks" |
|||
content1 := "version 1 content" |
|||
content2 := "version 2 content" |
|||
retainUntilDate := time.Now().Add(10 * time.Second) |
|||
|
|||
// Upload first version with retention
|
|||
putResp1, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
Body: strings.NewReader(content1), |
|||
ObjectLockMode: types.ObjectLockModeGovernance, |
|||
ObjectLockRetainUntilDate: aws.Time(retainUntilDate), |
|||
}) |
|||
require.NoError(t, err) |
|||
version1 := *putResp1.VersionId |
|||
|
|||
// Upload second version with retention
|
|||
putResp2, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(key), |
|||
Body: strings.NewReader(content2), |
|||
ObjectLockMode: types.ObjectLockModeGovernance, |
|||
ObjectLockRetainUntilDate: aws.Time(retainUntilDate), |
|||
}) |
|||
require.NoError(t, err) |
|||
version2 := *putResp2.VersionId |
|||
|
|||
t.Logf("Created two versions: %s, %s", version1, version2) |
|||
|
|||
// Try to delete bucket - should fail because versions have active retention
|
|||
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.Error(t, err, "DeleteBucket should fail when object versions have active retention") |
|||
assert.Contains(t, err.Error(), "BucketNotEmpty", "Error should be BucketNotEmpty") |
|||
t.Logf("Expected error: %v", err) |
|||
|
|||
// Wait for retention to expire
|
|||
t.Logf("Waiting for retention to expire on all versions...") |
|||
time.Sleep(11 * time.Second) |
|||
|
|||
// Clean up all versions
|
|||
deleteAllObjectVersions(t, client, bucketName) |
|||
|
|||
// Wait for eventual consistency
|
|||
time.Sleep(500 * time.Millisecond) |
|||
|
|||
// Now delete bucket should succeed
|
|||
_, err = client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.NoError(t, err, "DeleteBucket should succeed after all locks expire") |
|||
t.Logf("Successfully deleted bucket after locks expired") |
|||
} |
|||
|
|||
// TestBucketDeletionWithoutObjectLock tests that buckets without object lock can be deleted normally
|
|||
func TestBucketDeletionWithoutObjectLock(t *testing.T) { |
|||
client := getS3Client(t) |
|||
bucketName := getNewBucketName() |
|||
|
|||
// Create regular bucket without object lock
|
|||
createBucket(t, client, bucketName) |
|||
|
|||
// Upload some objects
|
|||
for i := 0; i < 3; i++ { |
|||
_, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ |
|||
Bucket: aws.String(bucketName), |
|||
Key: aws.String(strings.ReplaceAll("test-object-{}", "{}", string(rune('0'+i)))), |
|||
Body: strings.NewReader("test content"), |
|||
}) |
|||
require.NoError(t, err) |
|||
} |
|||
|
|||
// Delete all objects
|
|||
deleteAllObjectVersions(t, client, bucketName) |
|||
|
|||
// Delete bucket should succeed
|
|||
_, err := client.DeleteBucket(context.TODO(), &s3.DeleteBucketInput{ |
|||
Bucket: aws.String(bucketName), |
|||
}) |
|||
require.NoError(t, err, "DeleteBucket should succeed for regular bucket") |
|||
t.Logf("Successfully deleted regular bucket without object lock") |
|||
} |
|||
|
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue