Skip to content

Commit

Permalink
Support Bucket Versioning Config
Browse files Browse the repository at this point in the history
  • Loading branch information
afranken committed May 30, 2024
1 parent d31aeaa commit 35188ac
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 17 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ Of these [operations of the Amazon S3 API](https://docs.aws.amazon.com/AmazonS3/
| [GetBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketReplication.html) | :x: | |
| [GetBucketRequestPayment](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketRequestPayment.html) | :x: | |
| [GetBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketTagging.html) | :x: | |
| [GetBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html) | :x: | |
| [GetBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html) | :white_check_mark: | |
| [GetBucketWebsite](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketWebsite.html) | :x: | |
| [GetObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html) | :white_check_mark: | |
| [GetObjectAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html) | :white_check_mark: | |
Expand Down Expand Up @@ -144,7 +144,7 @@ Of these [operations of the Amazon S3 API](https://docs.aws.amazon.com/AmazonS3/
| [PutBucketReplication](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketReplication.html) | :x: | |
| [PutBucketRequestPayment](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketRequestPayment.html) | :x: | |
| [PutBucketTagging](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketTagging.html) | :x: | |
| [PutBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketVersioning.html) | :x: | |
| [PutBucketVersioning](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketVersioning.html) | :white_check_mark: | |
| [PutBucketWebsite](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketWebsite.html) | :x: | |
| [PutObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html) | :white_check_mark: | |
| [PutObjectAcl](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html) | :white_check_mark: | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,24 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.AbortIncompleteMultipartUpload
import software.amazon.awssdk.services.s3.model.BucketLifecycleConfiguration
import software.amazon.awssdk.services.s3.model.BucketVersioningStatus
import software.amazon.awssdk.services.s3.model.CreateBucketRequest
import software.amazon.awssdk.services.s3.model.DeleteBucketLifecycleRequest
import software.amazon.awssdk.services.s3.model.DeleteBucketRequest
import software.amazon.awssdk.services.s3.model.ExpirationStatus
import software.amazon.awssdk.services.s3.model.GetBucketLifecycleConfigurationRequest
import software.amazon.awssdk.services.s3.model.GetBucketLocationRequest
import software.amazon.awssdk.services.s3.model.GetBucketVersioningRequest
import software.amazon.awssdk.services.s3.model.HeadBucketRequest
import software.amazon.awssdk.services.s3.model.LifecycleExpiration
import software.amazon.awssdk.services.s3.model.LifecycleRule
import software.amazon.awssdk.services.s3.model.LifecycleRuleFilter
import software.amazon.awssdk.services.s3.model.MFADelete
import software.amazon.awssdk.services.s3.model.MFADeleteStatus
import software.amazon.awssdk.services.s3.model.NoSuchBucketException
import software.amazon.awssdk.services.s3.model.PutBucketLifecycleConfigurationRequest
import software.amazon.awssdk.services.s3.model.PutBucketVersioningRequest
import software.amazon.awssdk.services.s3.model.VersioningConfiguration
import java.util.concurrent.TimeUnit

/**
Expand Down Expand Up @@ -79,6 +85,51 @@ internal class BucketV2IT : S3TestBase() {
assertThat(bucketLocation.locationConstraint().toString()).isEqualTo("eu-west-1")
}

@Test
@S3VerifiedTodo
fun getDefaultBucketVersioning(testInfo: TestInfo) {
val bucketName = givenBucketV2(testInfo)

s3ClientV2.getBucketVersioning(
GetBucketVersioningRequest
.builder()
.bucket(bucketName)
.build()
).also {
assertThat(it.status()).isNull()
assertThat(it.mfaDelete()).isNull()
}
}

@Test
@S3VerifiedTodo
fun putAndGetBucketVersioning(testInfo: TestInfo) {
val bucketName = givenBucketV2(testInfo)
s3ClientV2.putBucketVersioning(
PutBucketVersioningRequest
.builder()
.bucket(bucketName)
.versioningConfiguration(
VersioningConfiguration
.builder()
.status(BucketVersioningStatus.ENABLED)
.mfaDelete(MFADelete.ENABLED)
.build()
)
.build()
)

s3ClientV2.getBucketVersioning(
GetBucketVersioningRequest
.builder()
.bucket(bucketName)
.build()
).also {
assertThat(it.status()).isEqualTo(BucketVersioningStatus.ENABLED)
assertThat(it.mfaDelete()).isEqualTo(MFADeleteStatus.ENABLED)
}
}

@Test
@S3VerifiedSuccess(year = 2024)
fun duplicateBucketCreation(testInfo: TestInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@
import static com.adobe.testing.s3mock.util.AwsHttpParameters.NOT_LOCATION;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.NOT_OBJECT_LOCK;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.NOT_UPLOADS;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.NOT_VERSIONING;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.NOT_VERSIONS;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.OBJECT_LOCK;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.START_AFTER;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.VERSIONING;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.VERSIONS;
import static com.adobe.testing.s3mock.util.AwsHttpParameters.VERSION_ID_MARKER;
import static org.springframework.http.MediaType.APPLICATION_XML_VALUE;
Expand All @@ -44,6 +46,7 @@
import com.adobe.testing.s3mock.dto.ListVersionsResult;
import com.adobe.testing.s3mock.dto.LocationConstraint;
import com.adobe.testing.s3mock.dto.ObjectLockConfiguration;
import com.adobe.testing.s3mock.dto.VersioningConfiguration;
import com.adobe.testing.s3mock.service.BucketService;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
Expand Down Expand Up @@ -116,7 +119,8 @@ public ResponseEntity<ListAllMyBucketsResult> listBuckets() {
},
params = {
NOT_OBJECT_LOCK,
NOT_LIFECYCLE
NOT_LIFECYCLE,
NOT_VERSIONING
}
)
public ResponseEntity<Void> createBucket(@PathVariable final String bucketName,
Expand Down Expand Up @@ -180,6 +184,62 @@ public ResponseEntity<Void> deleteBucket(@PathVariable String bucketName) {
return ResponseEntity.noContent().build();
}

/**
* Get VersioningConfiguration of a bucket.
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html">API Reference</a>
*
* @param bucketName name of the Bucket.
*
* @return 200, VersioningConfiguration
*/
@GetMapping(
value = {
//AWS SDK V2 pattern
"/{bucketName:.+}",
//AWS SDK V1 pattern
"/{bucketName:.+}/"
},
params = {
VERSIONING,
NOT_LIST_TYPE
},
produces = APPLICATION_XML_VALUE
)
public ResponseEntity<VersioningConfiguration> getVersioningConfiguration(
@PathVariable String bucketName) {
bucketService.verifyBucketExists(bucketName);
var configuration = bucketService.getVersioningConfiguration(bucketName);
return ResponseEntity.ok(configuration);
}

/**
* Put VersioningConfiguration of a bucket.
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketVersioning.html">API Reference</a>
*
* @param bucketName name of the Bucket.
*
* @return 200
*/
@PutMapping(
value = {
//AWS SDK V2 pattern
"/{bucketName:.+}",
//AWS SDK V1 pattern
"/{bucketName:.+}/"
},
params = {
VERSIONING
},
consumes = APPLICATION_XML_VALUE
)
public ResponseEntity<Void> putVersioningConfiguration(
@PathVariable String bucketName,
@RequestBody VersioningConfiguration configuration) {
bucketService.verifyBucketExists(bucketName);
bucketService.setVersioningConfiguration(bucketName, configuration);
return ResponseEntity.ok().build();
}

/**
* Get ObjectLockConfiguration of a bucket.
* <a href="https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectLockConfiguration.html">API Reference</a>
Expand Down Expand Up @@ -362,7 +422,8 @@ public ResponseEntity<LocationConstraint> getBucketLocation(
NOT_LIST_TYPE,
NOT_LIFECYCLE,
NOT_LOCATION,
NOT_VERSIONS
NOT_VERSIONS,
NOT_VERSIONING
},
produces = APPLICATION_XML_VALUE
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import com.adobe.testing.s3mock.dto.ObjectVersion;
import com.adobe.testing.s3mock.dto.Prefix;
import com.adobe.testing.s3mock.dto.S3Object;
import com.adobe.testing.s3mock.dto.VersioningConfiguration;
import com.adobe.testing.s3mock.store.BucketStore;
import com.adobe.testing.s3mock.store.ObjectStore;
import java.util.ArrayList;
Expand Down Expand Up @@ -113,6 +114,21 @@ public boolean deleteBucket(String bucketName) {
return bucketStore.deleteBucket(bucketName);
}

public void setVersioningConfiguration(String bucketName, VersioningConfiguration configuration) {
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
bucketStore.storeVersioningConfiguration(bucketMetadata, configuration);
}

public VersioningConfiguration getVersioningConfiguration(String bucketName) {
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
var configuration = bucketMetadata.versioningConfiguration();
if (configuration != null) {
return configuration;
} else {
throw NOT_FOUND_BUCKET_OBJECT_LOCK;
}
}

public void setObjectLockConfiguration(String bucketName, ObjectLockConfiguration configuration) {
var bucketMetadata = bucketStore.getBucketMetadata(bucketName);
bucketStore.storeObjectLockConfiguration(bucketMetadata, configuration);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.adobe.testing.s3mock.dto.BucketLifecycleConfiguration;
import com.adobe.testing.s3mock.dto.ObjectLockConfiguration;
import com.adobe.testing.s3mock.dto.VersioningConfiguration;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -30,6 +31,7 @@
public record BucketMetadata(
String name,
String creationDate,
VersioningConfiguration versioningConfiguration,
ObjectLockConfiguration objectLockConfiguration,
BucketLifecycleConfiguration bucketLifecycleConfiguration,
ObjectOwnership objectOwnership,
Expand All @@ -38,30 +40,49 @@ public record BucketMetadata(
) {

public BucketMetadata(String name, String creationDate,
VersioningConfiguration versioningConfiguration,
ObjectLockConfiguration objectLockConfiguration,
BucketLifecycleConfiguration bucketLifecycleConfiguration,
ObjectOwnership objectOwnership,
Path path) {
this(name,
creationDate,
versioningConfiguration,
objectLockConfiguration,
bucketLifecycleConfiguration,
objectOwnership,
path,
new HashMap<>());
}

public BucketMetadata withVersioningConfiguration(
VersioningConfiguration versioningConfiguration) {
return new BucketMetadata(name(),
creationDate(),
versioningConfiguration,
objectLockConfiguration(),
bucketLifecycleConfiguration(),
objectOwnership(),
path());
}

public BucketMetadata withObjectLockConfiguration(
ObjectLockConfiguration objectLockConfiguration) {
return new BucketMetadata(name(), creationDate(), objectLockConfiguration,
return new BucketMetadata(name(),
creationDate(),
versioningConfiguration(),
objectLockConfiguration,
bucketLifecycleConfiguration(),
objectOwnership(),
path());
}

public BucketMetadata withBucketLifecycleConfiguration(
BucketLifecycleConfiguration bucketLifecycleConfiguration) {
return new BucketMetadata(name(), creationDate(), objectLockConfiguration(),
return new BucketMetadata(name(),
creationDate(),
versioningConfiguration(),
objectLockConfiguration(),
bucketLifecycleConfiguration,
objectOwnership(),
path());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.adobe.testing.s3mock.dto.BucketLifecycleConfiguration;
import com.adobe.testing.s3mock.dto.ObjectLockConfiguration;
import com.adobe.testing.s3mock.dto.ObjectLockEnabled;
import com.adobe.testing.s3mock.dto.VersioningConfiguration;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -193,6 +194,7 @@ public BucketMetadata createBucket(String bucketName,
var newBucketMetadata = new BucketMetadata(
bucketName,
s3ObjectDateFormat.format(LocalDateTime.now()),
new VersioningConfiguration(null, null, null),
objectLockEnabled
? new ObjectLockConfiguration(ObjectLockEnabled.ENABLED, null) : null,
null,
Expand Down Expand Up @@ -232,6 +234,13 @@ public void storeObjectLockConfiguration(BucketMetadata metadata,
}
}

public void storeVersioningConfiguration(BucketMetadata metadata,
VersioningConfiguration configuration) {
synchronized (lockStore.get(metadata.name())) {
writeToDisk(metadata.withVersioningConfiguration(configuration));
}
}

public void storeBucketLifecycleConfiguration(BucketMetadata metadata,
BucketLifecycleConfiguration configuration) {
synchronized (lockStore.get(metadata.name())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,30 @@
*/
public class AwsHttpParameters {

private static final String NOT = "!";

public static final String ACL = "acl";
public static final String NOT_ACL = NOT + ACL;
public static final String CONTINUATION_TOKEN = "continuation-token";
public static final String DELETE = "delete";
public static final String ENCODING_TYPE = "encoding-type";
public static final String KEY_MARKER = "key-marker";
public static final String VERSION_ID_MARKER = "version-id-marker";
public static final String LIST_TYPE_V2 = "list-type=2";
public static final String VERSIONS = "versions";
public static final String NOT_VERSIONS = "!versions";
public static final String NOT_LIST_TYPE = "!list-type";
public static final String MAX_KEYS = "max-keys";
public static final String PART_NUMBER = "partNumber";
public static final String START_AFTER = "start-after";
public static final String VERSION_ID = "versionId";

private static final String NOT = "!";

public static final String ACL = "acl";
public static final String NOT_ACL = NOT + ACL;
public static final String VERSIONS = "versions";
public static final String NOT_VERSIONS = NOT + VERSIONS;
public static final String TAGGING = "tagging";
public static final String NOT_TAGGING = NOT + TAGGING;
public static final String UPLOADS = "uploads";
public static final String NOT_UPLOADS = NOT + UPLOADS;

public static final String UPLOAD_ID = "uploadId";
public static final String NOT_UPLOAD_ID = NOT + UPLOAD_ID;

public static final String LEGAL_HOLD = "legal-hold";
public static final String NOT_LEGAL_HOLD = NOT + LEGAL_HOLD;
public static final String OBJECT_LOCK = "object-lock";
Expand All @@ -57,8 +57,9 @@ public class AwsHttpParameters {
public static final String NOT_ATTRIBUTES = NOT + ATTRIBUTES;
public static final String LOCATION = "location";
public static final String NOT_LOCATION = NOT + LOCATION;
public static final String VERSIONING = "versioning";
public static final String NOT_VERSIONING = NOT + VERSIONING;

public static final String VERSION_ID = "versionId";

private AwsHttpParameters() {
// private constructor for utility classes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ internal class MultipartServiceTest : ServiceTestBase() {
val uploadId = "uploadId"
val bucketName = "bucketName"
whenever(bucketStore.getBucketMetadata(bucketName))
.thenReturn(BucketMetadata(null, null, null, null, null, null))
.thenReturn(BucketMetadata(null, null, null, null, null, null, null))
whenever(
multipartStore.getMultipartUpload(
ArgumentMatchers.any(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ internal abstract class ServiceTestBase {
Date().toString(),
null,
null,
null,
BUCKET_OWNER_ENFORCED,
Files.createTempDirectory(bucketName)
)
Expand Down
Loading

0 comments on commit 35188ac

Please sign in to comment.