Skip to content

Commit 51f2687

Browse files
committed
feat: support request headers as S3 object metadata in PUT templates
Add RequestHeaders (http.Header) to PutInput so that PUT metadata and storage class templates can access incoming request headers via .Input.RequestHeaders.Get. The lookup is case-insensitive, matching Go's standard http.Header semantics. Multi-value headers can be accessed with index and combined using sprig's join function.
1 parent fc9628f commit 51f2687

File tree

5 files changed

+111
-16
lines changed

5 files changed

+111
-16
lines changed

docs/configuration/example.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,11 @@ targets:
397397
# # Values can be templated. Empty values will be flushed.
398398
# metadata:
399399
# key: value
400+
# # Request headers can be forwarded as S3 object metadata using .Input.RequestHeaders.Get.
401+
# # The header lookup is case-insensitive
402+
# # (e.g. "x-my-header" and "X-My-Header" both work).
403+
# uploaded-by: '{{ .Input.RequestHeaders.Get "X-Uploaded-By" }}'
404+
# correlation-id: '{{ .Input.RequestHeaders.Get "X-Correlation-Id" }}'
400405
# # System Metadata cases.
401406
# # Values can be templated. Empty values will be flushed.
402407
# systemMetadata:

docs/feature-guide/templates.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,13 @@ These are the properties available:
298298

299299
### PutInput
300300

301-
| Name | Type | Description |
302-
| ----------- | ------- | ---------------------------- |
303-
| RequestPath | String | Request path |
304-
| Filename | String | Filename used for upload |
305-
| ContentType | String | File content type for upload |
306-
| ContentSize | Integer | File content size for upload |
301+
| Name | Type | Description |
302+
| -------------- | ----------------- | ---------------------------- |
303+
| RequestPath | String | Request path |
304+
| Filename | String | Filename used for upload |
305+
| ContentType | String | File content type for upload |
306+
| ContentSize | Integer | File content size for upload |
307+
| RequestHeaders | [http.Header](https://golang.org/pkg/net/http/#Header) | Incoming request headers |
307308

308309
### PutData
309310

pkg/s3-proxy/bucket/bucket-req-impl_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,6 +1354,92 @@ func Test_requestContext_Put(t *testing.T) {
13541354
times: 1,
13551355
},
13561356
},
1357+
{
1358+
name: "should be ok to do templating on metadata using request headers",
1359+
fields: fields{
1360+
targetCfg: &config.TargetConfig{
1361+
Name: "name",
1362+
Bucket: &config.BucketConfig{Prefix: "/"},
1363+
Actions: &config.ActionsConfig{
1364+
PUT: &config.PutActionConfig{
1365+
Config: &config.PutActionConfigConfig{
1366+
Metadata: map[string]string{
1367+
"first-header-value-only-key": `{{ .Input.RequestHeaders.Get "X-Custom-Header" }}`,
1368+
"all-header-values-key": `{{ join "," (index .Input.RequestHeaders "X-Custom-Header") }}`,
1369+
"header-normalised-test-key": `{{ .Input.RequestHeaders.Get "x-CASE-is-NORMALISED" }}`,
1370+
},
1371+
AllowOverride: true,
1372+
},
1373+
},
1374+
},
1375+
},
1376+
mountPath: "/mount",
1377+
},
1378+
args: args{
1379+
inp: &PutInput{
1380+
RequestPath: "/test",
1381+
Filename: "file",
1382+
Body: nil,
1383+
ContentType: "content-type",
1384+
RequestHeaders: func() http.Header {
1385+
h := http.Header{}
1386+
h["X-Custom-Header"] = []string{"first-value", "second-value"}
1387+
h["X-Case-Is-Normalised"] = []string{"test-value"}
1388+
return h
1389+
}(),
1390+
},
1391+
},
1392+
s3ClientHeadObjectMockResult: s3ClientHeadObjectMockResult{
1393+
times: 0,
1394+
},
1395+
s3ClientPutObjectMockResult: s3ClientPutObjectMockResult{
1396+
input2: &s3client.PutInput{
1397+
Key: "/test/file",
1398+
ContentType: "content-type",
1399+
Metadata: map[string]string{
1400+
"first-header-value-only-key": "first-value",
1401+
"all-header-values-key": "first-value,second-value",
1402+
"header-normalised-test-key": "test-value",
1403+
},
1404+
},
1405+
res: &s3client.ResultInfo{
1406+
Bucket: "bucket",
1407+
Key: "key",
1408+
Region: "region",
1409+
S3Endpoint: "s3endpoint",
1410+
},
1411+
times: 1,
1412+
},
1413+
webhookManagerManagePutHooksMockResult: webhookManagerManagePutHooksMockResult{
1414+
input2: "name",
1415+
input3: "/test",
1416+
input4: &webhook.PutInputMetadata{
1417+
Filename: "file",
1418+
ContentType: "content-type",
1419+
},
1420+
input5: &webhook.S3Metadata{
1421+
Bucket: "bucket",
1422+
Key: "key",
1423+
Region: "region",
1424+
S3Endpoint: "s3endpoint",
1425+
},
1426+
times: 1,
1427+
},
1428+
s3clManagerClientForTargetMockInput: "name",
1429+
responseHandlerPutMockResultTimes: responseHandlerPutMockResult{
1430+
input: &responsehandlermodels.PutInput{
1431+
Key: "/test/file",
1432+
Filename: "file",
1433+
ContentType: "content-type",
1434+
Metadata: map[string]string{
1435+
"first-header-value-only-key": "first-value",
1436+
"all-header-values-key": "first-value,second-value",
1437+
"header-normalised-test-key": "test-value",
1438+
},
1439+
},
1440+
times: 1,
1441+
},
1442+
},
13571443
}
13581444
for _, tt := range tests {
13591445
t.Run(tt.name, func(t *testing.T) {

pkg/s3-proxy/bucket/client.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bucket
33
import (
44
"context"
55
"io"
6+
"net/http"
67
"time"
78

89
"emperror.dev/errors"
@@ -44,11 +45,12 @@ type GetInput struct {
4445

4546
// PutInput represents Put input.
4647
type PutInput struct {
47-
RequestPath string
48-
Filename string
49-
Body io.ReadSeeker
50-
ContentType string
51-
ContentSize int64
48+
RequestPath string
49+
Filename string
50+
Body io.ReadSeeker
51+
ContentType string
52+
ContentSize int64
53+
RequestHeaders http.Header
5254
}
5355

5456
// PutData Put Data represents a put data structure used in put templates rendering.

pkg/s3-proxy/server/server.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,12 @@ func (svr *Server) generateRouter() (http.Handler, error) {
500500

501501
// Create input for put request
502502
inp := &bucket.PutInput{
503-
RequestPath: requestPath,
504-
Filename: fileHeader.Filename,
505-
Body: file,
506-
ContentType: fileHeader.Header.Get("Content-Type"),
507-
ContentSize: fileHeader.Size,
503+
RequestPath: requestPath,
504+
Filename: fileHeader.Filename,
505+
Body: file,
506+
ContentType: fileHeader.Header.Get("Content-Type"),
507+
ContentSize: fileHeader.Size,
508+
RequestHeaders: req.Header,
508509
}
509510
// Action
510511
brctx.Put(req.Context(), inp)

0 commit comments

Comments
 (0)