Skip to content

Commit 73f2d9d

Browse files
satotakebep
authored andcommitted
Add route-scoped ACL configuration
Some users would like to configure ACL for objects. It is easier to implement it to s3deploy. Ref gohugoio/hugo#8432
1 parent 8bc0776 commit 73f2d9d

File tree

7 files changed

+114
-84
lines changed

7 files changed

+114
-84
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ routes:
107107
headers:
108108
Cache-Control: "max-age=630720000, no-transform, public"
109109
gzip: false
110+
# configure route-scoped ACL
111+
acl: public-read
110112
- route: "^.+\\.(html|xml|json)$"
111113
gzip: true
112114
```

lib/deployer.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,18 @@ func (d *Deployer) loadConfig() error {
348348
return err
349349
}
350350

351+
acl := "private"
352+
if d.cfg.ACL != "" {
353+
acl = d.cfg.ACL
354+
} else if d.cfg.PublicReadACL {
355+
acl = "public-read"
356+
}
357+
351358
for _, r := range conf.Routes {
359+
if r.ACL == "" {
360+
r.ACL = acl
361+
}
362+
352363
r.routerRE, err = regexp.Compile(r.Route)
353364

354365
if err != nil {

lib/deployer_test.go

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,13 @@ func TestDeploy(t *testing.T) {
2727
source := testSourcePath()
2828
configFile := filepath.Join(source, ".s3deploy.yml")
2929

30+
storeACL := "public-read"
3031
cfg := &Config{
3132
BucketName: "example.com",
3233
RegionName: "eu-west-1",
3334
ConfigFile: configFile,
3435
MaxDelete: 300,
35-
ACL: "public-read",
36+
ACL: storeACL,
3637
Silent: true,
3738
SourcePath: source,
3839
baseStore: store,
@@ -48,6 +49,10 @@ func TestDeploy(t *testing.T) {
4849
c.Assert(headers["Content-Encoding"], qt.Equals, "gzip")
4950
c.Assert(headers["Content-Type"], qt.Equals, "text/css; charset=utf-8")
5051
c.Assert(headers["Cache-Control"], qt.Equals, "max-age=630720000, no-transform, public")
52+
53+
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
54+
indexHtml := m["index.html"]
55+
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, storeACL)
5156
}
5257

5358
func TestDeployWithBucketPath(t *testing.T) {
@@ -281,6 +286,91 @@ func TestDeployMaxDelete(t *testing.T) {
281286
c.Assert(stats.Summary(), qt.Equals, "Deleted 42 of 200, uploaded 4, skipped 0 (100% changed)")
282287
}
283288

289+
func TestDeployNoACLProvided(t *testing.T) {
290+
c := qt.New(t)
291+
store, m := newTestStore(0, "")
292+
source := testSourcePath()
293+
configFile := filepath.Join(source, ".s3deploy.yml")
294+
295+
cfg := &Config{
296+
BucketName: "example.com",
297+
RegionName: "eu-west-1",
298+
ConfigFile: configFile,
299+
MaxDelete: 300,
300+
ACL: "",
301+
Silent: true,
302+
SourcePath: source,
303+
baseStore: store,
304+
}
305+
306+
stats, err := Deploy(cfg)
307+
c.Assert(err, qt.IsNil)
308+
c.Assert(stats.Summary(), qt.Equals, "Deleted 1 of 1, uploaded 3, skipped 1 (80% changed)")
309+
assertKeys(t, m, ".s3deploy.yml", "main.css", "index.html", "ab.txt")
310+
311+
mainCss := m["main.css"]
312+
indexHtml := m["index.html"]
313+
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
314+
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, "private") // default private
315+
}
316+
317+
func TestDeployOtherCannedACLProvided(t *testing.T) {
318+
c := qt.New(t)
319+
store, m := newTestStore(0, "")
320+
source := testSourcePath()
321+
configFile := filepath.Join(source, ".s3deploy.yml")
322+
323+
cfg := &Config{
324+
BucketName: "example.com",
325+
RegionName: "eu-west-1",
326+
ConfigFile: configFile,
327+
MaxDelete: 300,
328+
ACL: "bucket-owner-full-control",
329+
Silent: true,
330+
SourcePath: source,
331+
baseStore: store,
332+
}
333+
334+
stats, err := Deploy(cfg)
335+
c.Assert(err, qt.IsNil)
336+
c.Assert(stats.Summary(), qt.Equals, "Deleted 1 of 1, uploaded 3, skipped 1 (80% changed)")
337+
assertKeys(t, m, ".s3deploy.yml", "main.css", "index.html", "ab.txt")
338+
339+
mainCss := m["main.css"]
340+
indexHtml := m["index.html"]
341+
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
342+
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, "bucket-owner-full-control")
343+
}
344+
345+
func TestDeployDeprecatedPublicReadACLFlagProvided(t *testing.T) {
346+
c := qt.New(t)
347+
store, m := newTestStore(0, "")
348+
source := testSourcePath()
349+
configFile := filepath.Join(source, ".s3deploy.yml")
350+
351+
cfg := &Config{
352+
BucketName: "example.com",
353+
RegionName: "eu-west-1",
354+
ConfigFile: configFile,
355+
MaxDelete: 300,
356+
ACL: "",
357+
PublicReadACL: true,
358+
Silent: true,
359+
SourcePath: source,
360+
baseStore: store,
361+
}
362+
363+
stats, err := Deploy(cfg)
364+
c.Assert(err, qt.IsNil)
365+
c.Assert(stats.Summary(), qt.Equals, "Deleted 1 of 1, uploaded 3, skipped 1 (80% changed)")
366+
assertKeys(t, m, ".s3deploy.yml", "main.css", "index.html", "ab.txt")
367+
368+
mainCss := m["main.css"]
369+
indexHtml := m["index.html"]
370+
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
371+
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, "public-read")
372+
}
373+
284374
func testSourcePath() string {
285375
wd, _ := os.Getwd()
286376
return filepath.Join(wd, "testdata") + "/"

lib/files.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type localFile interface {
5050
Content() io.ReadSeeker
5151

5252
Headers() map[string]string
53+
ACL() string
5354
}
5455

5556
type osFile struct {
@@ -125,6 +126,10 @@ func (f *osFile) Headers() map[string]string {
125126
return headers
126127
}
127128

129+
func (f *osFile) ACL() string {
130+
return f.route.ACL
131+
}
132+
128133
func (f *osFile) initContentType(peek []byte) error {
129134
if f.route != nil {
130135
if contentType, found := f.route.Headers["Content-Type"]; found {
@@ -240,6 +245,7 @@ type route struct {
240245
Headers map[string]string `yaml:"headers"`
241246
Gzip bool `yaml:"gzip"`
242247
Ignore bool `yaml:"ignore"`
248+
ACL string `yaml:"acl"`
243249

244250
routerRE *regexp.Regexp // compiled version of Route
245251
}

lib/s3.go

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ type s3Store struct {
2424
bucketPath string
2525
r routes
2626
svc *s3.S3
27-
acl string
2827
cfc *cloudFrontClient
2928
}
3029

@@ -60,14 +59,7 @@ func newRemoteStore(cfg Config, logger printer) (*s3Store, error) {
6059
}
6160
}
6261

63-
acl := "private"
64-
if cfg.ACL != "" {
65-
acl = cfg.ACL
66-
} else if cfg.PublicReadACL {
67-
acl = "public-read"
68-
}
69-
70-
s = &s3Store{svc: s3.New(sess), cfc: cfc, acl: acl, bucket: cfg.BucketName, r: cfg.conf.Routes, bucketPath: cfg.BucketPath}
62+
s = &s3Store{svc: s3.New(sess), cfc: cfc, bucket: cfg.BucketName, r: cfg.conf.Routes, bucketPath: cfg.BucketPath}
7163

7264
return s, nil
7365
}
@@ -93,6 +85,7 @@ func (s *s3Store) FileMap(opts ...opOption) (map[string]file, error) {
9385

9486
func (s *s3Store) Put(ctx context.Context, f localFile, opts ...opOption) error {
9587
headers := f.Headers()
88+
acl := f.ACL()
9689

9790
withHeaders := func(r *request.Request) {
9891
for k, v := range headers {
@@ -104,7 +97,7 @@ func (s *s3Store) Put(ctx context.Context, f localFile, opts ...opOption) error
10497
Bucket: aws.String(s.bucket),
10598
Key: aws.String(f.Key()),
10699
Body: f.Content(),
107-
ACL: aws.String(s.acl),
100+
ACL: aws.String(acl),
108101
ContentLength: aws.Int64(f.Size()),
109102
}, withHeaders)
110103

lib/s3_test.go

Lines changed: 0 additions & 73 deletions
This file was deleted.

lib/testdata/.s3deploy.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ routes:
44
headers:
55
Cache-Control: "max-age=630720000, no-transform, public"
66
gzip: true
7+
acl: private
78
- route: "^.+\\.(png|jpg)$"
89
headers:
910
Cache-Control: "max-age=630720000, no-transform, public"

0 commit comments

Comments
 (0)