feat: add storage class to put object flows input

A new glaicer mode for the archiving backend needs the storage
class supplied to the backend for put object, create multipart
upload, and browser post object input.
This commit is contained in:
Ben McClelland
2026-09-08 10:52:46 -07:00
parent 7f0a793150
commit 3b5c74ac16
7 changed files with 66 additions and 0 deletions
+2
View File
@@ -127,6 +127,7 @@ func (c S3ApiController) POSTObject(ctx fiber.Ctx) (*Response, error) {
cacheControl := parsed.Fields["cache-control"]
expires := parsed.Fields["expires"]
websiteRedirectLocation := parsed.Fields["x-amz-website-redirect-location"]
storageClass := parsed.Fields["x-amz-storage-class"]
key := parsed.Fields["key"]
@@ -233,6 +234,7 @@ func (c S3ApiController) POSTObject(ctx fiber.Ctx) (*Response, error) {
ContentLength: &parsed.ContentLength,
Tagging: &tagging,
Metadata: metadata,
StorageClass: types.StorageClass(storageClass),
ChecksumCRC32: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc32]),
ChecksumCRC32C: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc32c]),
ChecksumSHA1: utils.GetStringPtr(checksums[types.ChecksumAlgorithmSha1]),
+3
View File
@@ -567,6 +567,7 @@ func TestS3ApiController_POSTObject(t *testing.T) {
[]any{"eq", "$content-encoding", "gzip"},
[]any{"eq", "$content-language", "en-US"},
[]any{"eq", "$expires", "Fri, 21 Mar 2026 00:00:00 GMT"},
[]any{"eq", "$x-amz-storage-class", string(types.StorageClassGlacier)},
}),
"file": "ignored",
"x-amz-signature": "ignored",
@@ -580,6 +581,7 @@ func TestS3ApiController_POSTObject(t *testing.T) {
"content-encoding": "gzip",
"content-language": "en-US",
"expires": "Fri, 21 Mar 2026 00:00:00 GMT",
"x-amz-storage-class": string(types.StorageClassGlacier),
},
FileRdr: newMockFileReader("payload"),
ContentLength: int64(len("payload")),
@@ -719,6 +721,7 @@ func TestS3ApiController_POSTObject(t *testing.T) {
assert.Equal(t, "en-US", *putObjectInput.ContentLanguage)
assert.Equal(t, "max-age=60", *putObjectInput.CacheControl)
assert.Equal(t, "Fri, 21 Mar 2026 00:00:00 GMT", *putObjectInput.Expires)
assert.Equal(t, types.StorageClassGlacier, putObjectInput.StorageClass)
assert.Equal(t, int64(len("payload")), *putObjectInput.ContentLength)
assert.Equal(t, "project=alpha+team", *putObjectInput.Tagging)
assert.Equal(t, map[string]string{"owner": "alice"}, putObjectInput.Metadata)
+2
View File
@@ -151,6 +151,7 @@ func (c S3ApiController) CreateMultipartUpload(ctx fiber.Ctx) (*Response, error)
tagging := ctx.Get("X-Amz-Tagging")
expires := ctx.Get("Expires")
websiteRedirectLocation := ctx.Get("X-Amz-Website-Redirect-Location")
storageClass := ctx.Get("X-Amz-Storage-Class")
legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold")
lockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode")
objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date")
@@ -241,6 +242,7 @@ func (c S3ApiController) CreateMultipartUpload(ctx fiber.Ctx) (*Response, error)
ObjectLockMode: objLockState.ObjectLockMode,
ObjectLockLegalHoldStatus: objLockState.LegalHoldStatus,
Metadata: metadata,
StorageClass: types.StorageClass(storageClass),
ChecksumAlgorithm: checksumAlgorithm,
ChecksumType: checksumType,
})
+4
View File
@@ -312,6 +312,7 @@ func TestS3ApiController_CreateMultipartUpload(t *testing.T) {
headers: map[string]string{
"x-amz-checksum-algorithm": string(types.ChecksumAlgorithmCrc32),
"x-amz-checksum-type": string(types.ChecksumTypeComposite),
"X-Amz-Storage-Class": string(types.StorageClassGlacier),
},
},
output: testOutput{
@@ -332,6 +333,9 @@ func TestS3ApiController_CreateMultipartUpload(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
be := &BackendMock{
CreateMultipartUploadFunc: func(contextMoqParam context.Context, createMultipartUploadInput s3response.CreateMultipartUploadInput) (s3response.InitiateMultipartUploadResult, error) {
if tt.name == "successful response" && createMultipartUploadInput.StorageClass != types.StorageClassGlacier {
t.Fatalf("expected storage class %q, got %q", types.StorageClassGlacier, createMultipartUploadInput.StorageClass)
}
return tt.input.beRes.(s3response.InitiateMultipartUploadResult), tt.input.beErr
},
GetBucketPolicyFunc: func(contextMoqParam context.Context, bucket string) ([]byte, error) {
+2
View File
@@ -668,6 +668,7 @@ func (c S3ApiController) PutObject(ctx fiber.Ctx) (*Response, error) {
expires := ctx.Get("Expires")
websiteRedirectLocation := ctx.Get("X-Amz-Website-Redirect-Location")
tagging := ctx.Get("x-amz-tagging")
storageClass := ctx.Get("X-Amz-Storage-Class")
legalHoldHdr := ctx.Get("X-Amz-Object-Lock-Legal-Hold")
lockModeHdr := ctx.Get("X-Amz-Object-Lock-Mode")
objLockDate := ctx.Get("X-Amz-Object-Lock-Retain-Until-Date")
@@ -803,6 +804,7 @@ func (c S3ApiController) PutObject(ctx fiber.Ctx) (*Response, error) {
ObjectLockRetainUntilDate: &objLock.RetainUntilDate,
ObjectLockMode: objLock.ObjectLockMode,
ObjectLockLegalHoldStatus: objLock.LegalHoldStatus,
StorageClass: types.StorageClass(storageClass),
ChecksumAlgorithm: algorithm,
ChecksumCRC32: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc32]),
ChecksumCRC32C: utils.GetStringPtr(checksums[types.ChecksumAlgorithmCrc32c]),
+52
View File
@@ -1169,6 +1169,58 @@ func TestS3ApiController_PutObject(t *testing.T) {
emptyStringPtr := &str
objSize := int64(120)
t.Run("forwards storage class", func(t *testing.T) {
be := &BackendMock{
PutObjectFunc: func(_ context.Context, input s3response.PutObjectInput) (s3response.PutObjectOutput, error) {
if input.StorageClass != types.StorageClassGlacier {
t.Fatalf("expected storage class %q, got %q", types.StorageClassGlacier, input.StorageClass)
}
return s3response.PutObjectOutput{ETag: "etag", VersionID: "version-id"}, nil
},
GetBucketPolicyFunc: func(_ context.Context, _ string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrAccessDenied)
},
GetObjectLockConfigurationFunc: func(_ context.Context, _ string) ([]byte, error) {
return nil, s3err.GetAPIError(s3err.ErrObjectLockConfigurationNotFound)
},
GetBucketVersioningFunc: func(_ context.Context, _ string) (s3response.GetBucketVersioningOutput, error) {
return s3response.GetBucketVersioningOutput{}, s3err.GetAPIError(s3err.ErrNotImplemented)
},
}
ctrl := S3ApiController{be: be}
testController(t, ctrl.PutObject, &Response{
Headers: map[string]*string{
"ETag": utils.GetStringPtr("etag"),
"x-amz-checksum-crc32": nil,
"x-amz-checksum-crc32c": nil,
"x-amz-checksum-crc64nvme": nil,
"x-amz-checksum-sha1": nil,
"x-amz-checksum-sha256": nil,
"x-amz-checksum-sha512": nil,
"x-amz-checksum-md5": nil,
"x-amz-checksum-xxhash64": nil,
"x-amz-checksum-xxhash3": nil,
"x-amz-checksum-xxhash128": nil,
"x-amz-checksum-type": nil,
"x-amz-version-id": utils.GetStringPtr("version-id"),
"x-amz-object-size": nil,
},
MetaOpts: &MetaOptions{
BucketOwner: "root",
ObjectETag: utils.GetStringPtr("etag"),
ContentLength: 0,
ObjectSize: 0,
EventName: s3event.EventObjectCreatedPut,
},
}, nil, ctxInputs{
locals: defaultLocals,
headers: map[string]string{
"X-Amz-Storage-Class": string(types.StorageClassGlacier),
},
})
})
tests := []struct {
name string
input testInput
+1
View File
@@ -608,6 +608,7 @@ type PutObjectInput struct {
ObjectLockMode types.ObjectLockMode
ObjectLockLegalHoldStatus types.ObjectLockLegalHoldStatus
ChecksumAlgorithm types.ChecksumAlgorithm
StorageClass types.StorageClass
Metadata map[string]string
Body io.Reader