Skip to content

Commit

Permalink
Add route-scoped ACL configuration
Browse files Browse the repository at this point in the history
Some users would like to configure ACL for objects.
It is easier to implement it to s3deploy.

Ref gohugoio/hugo#8432
  • Loading branch information
satotake authored and bep committed Aug 5, 2022
1 parent 8bc0776 commit 73f2d9d
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 84 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -107,6 +107,8 @@ routes:
headers:
Cache-Control: "max-age=630720000, no-transform, public"
gzip: false
# configure route-scoped ACL
acl: public-read
- route: "^.+\\.(html|xml|json)$"
gzip: true
```
Expand Down
11 changes: 11 additions & 0 deletions lib/deployer.go
Expand Up @@ -348,7 +348,18 @@ func (d *Deployer) loadConfig() error {
return err
}

acl := "private"
if d.cfg.ACL != "" {
acl = d.cfg.ACL
} else if d.cfg.PublicReadACL {
acl = "public-read"
}

for _, r := range conf.Routes {
if r.ACL == "" {
r.ACL = acl
}

r.routerRE, err = regexp.Compile(r.Route)

if err != nil {
Expand Down
92 changes: 91 additions & 1 deletion lib/deployer_test.go
Expand Up @@ -27,12 +27,13 @@ func TestDeploy(t *testing.T) {
source := testSourcePath()
configFile := filepath.Join(source, ".s3deploy.yml")

storeACL := "public-read"
cfg := &Config{
BucketName: "example.com",
RegionName: "eu-west-1",
ConfigFile: configFile,
MaxDelete: 300,
ACL: "public-read",
ACL: storeACL,
Silent: true,
SourcePath: source,
baseStore: store,
Expand All @@ -48,6 +49,10 @@ func TestDeploy(t *testing.T) {
c.Assert(headers["Content-Encoding"], qt.Equals, "gzip")
c.Assert(headers["Content-Type"], qt.Equals, "text/css; charset=utf-8")
c.Assert(headers["Cache-Control"], qt.Equals, "max-age=630720000, no-transform, public")

c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
indexHtml := m["index.html"]
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, storeACL)
}

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

func TestDeployNoACLProvided(t *testing.T) {
c := qt.New(t)
store, m := newTestStore(0, "")
source := testSourcePath()
configFile := filepath.Join(source, ".s3deploy.yml")

cfg := &Config{
BucketName: "example.com",
RegionName: "eu-west-1",
ConfigFile: configFile,
MaxDelete: 300,
ACL: "",
Silent: true,
SourcePath: source,
baseStore: store,
}

stats, err := Deploy(cfg)
c.Assert(err, qt.IsNil)
c.Assert(stats.Summary(), qt.Equals, "Deleted 1 of 1, uploaded 3, skipped 1 (80% changed)")
assertKeys(t, m, ".s3deploy.yml", "main.css", "index.html", "ab.txt")

mainCss := m["main.css"]
indexHtml := m["index.html"]
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, "private") // default private
}

func TestDeployOtherCannedACLProvided(t *testing.T) {
c := qt.New(t)
store, m := newTestStore(0, "")
source := testSourcePath()
configFile := filepath.Join(source, ".s3deploy.yml")

cfg := &Config{
BucketName: "example.com",
RegionName: "eu-west-1",
ConfigFile: configFile,
MaxDelete: 300,
ACL: "bucket-owner-full-control",
Silent: true,
SourcePath: source,
baseStore: store,
}

stats, err := Deploy(cfg)
c.Assert(err, qt.IsNil)
c.Assert(stats.Summary(), qt.Equals, "Deleted 1 of 1, uploaded 3, skipped 1 (80% changed)")
assertKeys(t, m, ".s3deploy.yml", "main.css", "index.html", "ab.txt")

mainCss := m["main.css"]
indexHtml := m["index.html"]
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, "bucket-owner-full-control")
}

func TestDeployDeprecatedPublicReadACLFlagProvided(t *testing.T) {
c := qt.New(t)
store, m := newTestStore(0, "")
source := testSourcePath()
configFile := filepath.Join(source, ".s3deploy.yml")

cfg := &Config{
BucketName: "example.com",
RegionName: "eu-west-1",
ConfigFile: configFile,
MaxDelete: 300,
ACL: "",
PublicReadACL: true,
Silent: true,
SourcePath: source,
baseStore: store,
}

stats, err := Deploy(cfg)
c.Assert(err, qt.IsNil)
c.Assert(stats.Summary(), qt.Equals, "Deleted 1 of 1, uploaded 3, skipped 1 (80% changed)")
assertKeys(t, m, ".s3deploy.yml", "main.css", "index.html", "ab.txt")

mainCss := m["main.css"]
indexHtml := m["index.html"]
c.Assert(mainCss.(*osFile).ACL(), qt.Equals, "private") // route-configured
c.Assert(indexHtml.(*osFile).ACL(), qt.Equals, "public-read")
}

func testSourcePath() string {
wd, _ := os.Getwd()
return filepath.Join(wd, "testdata") + "/"
Expand Down
6 changes: 6 additions & 0 deletions lib/files.go
Expand Up @@ -50,6 +50,7 @@ type localFile interface {
Content() io.ReadSeeker

Headers() map[string]string
ACL() string
}

type osFile struct {
Expand Down Expand Up @@ -125,6 +126,10 @@ func (f *osFile) Headers() map[string]string {
return headers
}

func (f *osFile) ACL() string {
return f.route.ACL
}

func (f *osFile) initContentType(peek []byte) error {
if f.route != nil {
if contentType, found := f.route.Headers["Content-Type"]; found {
Expand Down Expand Up @@ -240,6 +245,7 @@ type route struct {
Headers map[string]string `yaml:"headers"`
Gzip bool `yaml:"gzip"`
Ignore bool `yaml:"ignore"`
ACL string `yaml:"acl"`

routerRE *regexp.Regexp // compiled version of Route
}
Expand Down
13 changes: 3 additions & 10 deletions lib/s3.go
Expand Up @@ -24,7 +24,6 @@ type s3Store struct {
bucketPath string
r routes
svc *s3.S3
acl string
cfc *cloudFrontClient
}

Expand Down Expand Up @@ -60,14 +59,7 @@ func newRemoteStore(cfg Config, logger printer) (*s3Store, error) {
}
}

acl := "private"
if cfg.ACL != "" {
acl = cfg.ACL
} else if cfg.PublicReadACL {
acl = "public-read"
}

s = &s3Store{svc: s3.New(sess), cfc: cfc, acl: acl, bucket: cfg.BucketName, r: cfg.conf.Routes, bucketPath: cfg.BucketPath}
s = &s3Store{svc: s3.New(sess), cfc: cfc, bucket: cfg.BucketName, r: cfg.conf.Routes, bucketPath: cfg.BucketPath}

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

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

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

Expand Down
73 changes: 0 additions & 73 deletions lib/s3_test.go

This file was deleted.

1 change: 1 addition & 0 deletions lib/testdata/.s3deploy.yml
Expand Up @@ -4,6 +4,7 @@ routes:
headers:
Cache-Control: "max-age=630720000, no-transform, public"
gzip: true
acl: private
- route: "^.+\\.(png|jpg)$"
headers:
Cache-Control: "max-age=630720000, no-transform, public"
Expand Down

0 comments on commit 73f2d9d

Please sign in to comment.