Skip to content

Commit

Permalink
Allow multiple distribution-id
Browse files Browse the repository at this point in the history
This allows multiple values in `distribution-id`; add by repeating the flag with new values e.g.:

```bash
s3deploy -bucket=mybucket -distribution-id=abcd -distribution-id=efgh
```

Fixes #142
  • Loading branch information
bep committed Feb 5, 2022
1 parent 4d7acf3 commit b66e5d2
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 58 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,15 @@ Note that `s3deploy` is a perfect tool to use with a continuous integration tool
## Use

```bash
Usage of ./s3deploy:
-V print version and exit
-acl string
provide an ACL for uploaded objects. to make objects public, set to 'public-read'. all possible values are listed here: https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl (default "private")
-bucket string
destination bucket name on AWS
-config string
optional config file (default ".s3deploy.yml")
-distribution-id string
optional CDN distribution ID for cache invalidation
-distribution-id value
optional CDN distribution ID for cache invalidation, repeat flag for multiple distributions
-force
upload even if the etags match
-h help
Expand Down
93 changes: 51 additions & 42 deletions lib/cloudfront.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (
var _ remoteCDN = (*cloudFrontClient)(nil)

type cloudFrontClient struct {
// The CloudFront distribution ID
distributionID string
// The CloudFront distribution IDs
distributionIDs Strings

// Will invalidate the entire cache, e.g. "/*"
force bool
Expand All @@ -36,15 +36,15 @@ func newCloudFrontClient(
sess *session.Session,
logger printer,
cfg Config) (*cloudFrontClient, error) {
if cfg.CDNDistributionID == "" {
return nil, errors.New("must provide a distribution ID")
if len(cfg.CDNDistributionIDs) == 0 {
return nil, errors.New("must provide one or more distribution ID")
}
return &cloudFrontClient{
distributionID: cfg.CDNDistributionID,
force: cfg.Force,
bucketPath: cfg.BucketPath,
logger: logger,
cf: cloudfront.New(sess),
distributionIDs: cfg.CDNDistributionIDs,
force: cfg.Force,
bucketPath: cfg.BucketPath,
logger: logger,
cf: cloudfront.New(sess),
}, nil
}

Expand All @@ -53,47 +53,57 @@ func (c *cloudFrontClient) InvalidateCDNCache(paths ...string) error {
return nil
}

dcfg, err := c.cf.GetDistribution(&cloudfront.GetDistributionInput{
Id: &c.distributionID,
})
if err != nil {
return err
}
invalidateForID := func(id string) error {
dcfg, err := c.cf.GetDistribution(&cloudfront.GetDistributionInput{
Id: &id,
})
if err != nil {
return err
}

originPath := *dcfg.Distribution.DistributionConfig.Origins.Items[0].OriginPath
var root string
if originPath != "" || c.bucketPath != "" {
var subPath string
root, subPath = c.determineRootAndSubPath(c.bucketPath, originPath)
if subPath != "" {
for i, p := range paths {
paths[i] = strings.TrimPrefix(p, subPath)
originPath := *dcfg.Distribution.DistributionConfig.Origins.Items[0].OriginPath
var root string
if originPath != "" || c.bucketPath != "" {
var subPath string
root, subPath = c.determineRootAndSubPath(c.bucketPath, originPath)
if subPath != "" {
for i, p := range paths {
paths[i] = strings.TrimPrefix(p, subPath)
}
}
}
}

// This will try to reduce the number of invaldation paths to maximum 8.
// If that isn't possible it will fall back to a full invalidation, e.g. "/*".
// CloudFront allows 1000 free invalidations per month. After that they
// cost money, so we want to keep this down.
paths = c.normalizeInvalidationPaths(root, 8, c.force, paths...)
// This will try to reduce the number of invaldation paths to maximum 8.
// If that isn't possible it will fall back to a full invalidation, e.g. "/*".
// CloudFront allows 1000 free invalidations per month. After that they
// cost money, so we want to keep this down.
paths = c.normalizeInvalidationPaths(root, 8, c.force, paths...)

if len(paths) > 10 {
c.logger.Printf("Create CloudFront invalidation request for %d paths", len(paths))
} else {
c.logger.Printf("Create CloudFront invalidation request for %v", paths)
}
if len(paths) > 10 {
c.logger.Printf("Create CloudFront invalidation request for %d paths", len(paths))
} else {
c.logger.Printf("Create CloudFront invalidation request for %v", paths)
}

in := &cloudfront.CreateInvalidationInput{
DistributionId: &id,
InvalidationBatch: c.pathsToInvalidationBatch(time.Now().Format("20060102150405"), paths...),
}

_, err = c.cf.CreateInvalidation(
in,
)

in := &cloudfront.CreateInvalidationInput{
DistributionId: &c.distributionID,
InvalidationBatch: c.pathsToInvalidationBatch(time.Now().Format("20060102150405"), paths...),
return err
}

_, err = c.cf.CreateInvalidation(
in,
)
for _, id := range c.distributionIDs {
if err := invalidateForID(id); err != nil {
return err
}
}

return err
return nil
}

func (*cloudFrontClient) pathsToInvalidationBatch(ref string, paths ...string) *cloudfront.InvalidationBatch {
Expand Down Expand Up @@ -136,7 +146,6 @@ func (c *cloudFrontClient) determineRootAndSubPath(bucketPath, originPath string
}

return

}

// For path rules, see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html
Expand Down
10 changes: 4 additions & 6 deletions lib/cloudfront_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ func TestReduceInvalidationPaths(t *testing.T) {
assert.Equal([]string{"/*"}, normalized)
normalized = client.normalizeInvalidationPaths("root", 5, true, rootPlusManyInDifferentFoldersNested...)
assert.Equal([]string{"/root/*"}, normalized)

}

func TestDetermineRootAndSubPath(t *testing.T) {
Expand All @@ -80,7 +79,6 @@ func TestDetermineRootAndSubPath(t *testing.T) {
check("/temp/forsale/", "temp", "/forsale", "temp")
check("root", "root", "/", "root")
check("root", "/root", "/", "root")

}

func TestPathsToInvalidationBatch(t *testing.T) {
Expand All @@ -99,13 +97,13 @@ func TestNewCloudFrontClient(t *testing.T) {
assert := require.New(t)
s := mock.Session
c, err := newCloudFrontClient(s, newPrinter(ioutil.Discard), Config{
CDNDistributionID: "12345",
Force: true,
BucketPath: "/mypath",
CDNDistributionIDs: Strings{"12345"},
Force: true,
BucketPath: "/mypath",
})
assert.NoError(err)
assert.NotNil(c)
assert.Equal("12345", c.distributionID)
assert.Equal("12345", c.distributionIDs[0])
assert.Equal("/mypath", c.bucketPath)
assert.Equal(true, c.force)
}
Expand Down
17 changes: 14 additions & 3 deletions lib/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,17 @@ import (
"strings"
)

type Strings []string

func (i *Strings) String() string {
return strings.Join(*i, ",")
}

func (i *Strings) Set(value string) error {
*i = append(*i, value)
return nil
}

// Config configures a deployment.
type Config struct {
conf fileConfig
Expand All @@ -29,8 +40,8 @@ type Config struct {
BucketPath string
RegionName string

// When set, will invalidate the CDN cache for the updated files.
CDNDistributionID string
// When set, will invalidate the CDN cache(s) for the updated files.
CDNDistributionIDs Strings

// Optional configFile
ConfigFile string
Expand Down Expand Up @@ -70,7 +81,7 @@ func flagsToConfig(f *flag.FlagSet) (*Config, error) {
f.StringVar(&cfg.BucketName, "bucket", "", "destination bucket name on AWS")
f.StringVar(&cfg.BucketPath, "path", "", "optional bucket sub path")
f.StringVar(&cfg.SourcePath, "source", ".", "path of files to upload")
f.StringVar(&cfg.CDNDistributionID, "distribution-id", "", "optional CDN distribution ID for cache invalidation")
f.Var(&cfg.CDNDistributionIDs, "distribution-id", "optional CDN distribution ID for cache invalidation, repeat flag for multiple distributions")
f.StringVar(&cfg.ConfigFile, "config", ".s3deploy.yml", "optional config file")
f.IntVar(&cfg.MaxDelete, "max-delete", 256, "maximum number of files to delete per deploy")
f.BoolVar(&cfg.PublicReadACL, "public-access", false, "DEPRECATED: please set -acl='public-read'")
Expand Down
2 changes: 1 addition & 1 deletion lib/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestFlagsToConfig(t *testing.T) {
assert.Equal("mysource", cfg.SourcePath)
assert.Equal(true, cfg.Try)
assert.Equal("myregion", cfg.RegionName)
assert.Equal("mydistro", cfg.CDNDistributionID)
assert.Equal(Strings{"mydistro"}, cfg.CDNDistributionIDs)
assert.Equal("^ignored-prefix.*", cfg.Ignore)
}

Expand Down
4 changes: 1 addition & 3 deletions lib/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func newRemoteStore(cfg Config, logger printer) (*s3Store, error) {
return nil, err
}

if cfg.CDNDistributionID != "" {
if len(cfg.CDNDistributionIDs) > 0 {
cfc, err = newCloudFrontClient(sess, logger, cfg)
if err != nil {
return nil, err
Expand All @@ -70,7 +70,6 @@ func newRemoteStore(cfg Config, logger printer) (*s3Store, error) {
s = &s3Store{svc: s3.New(sess), cfc: cfc, acl: acl, bucket: cfg.BucketName, r: cfg.conf.Routes, bucketPath: cfg.BucketPath}

return s, nil

}

func (s *s3Store) FileMap(opts ...opOption) (map[string]file, error) {
Expand All @@ -93,7 +92,6 @@ 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()

withHeaders := func(r *request.Request) {
Expand Down

0 comments on commit b66e5d2

Please sign in to comment.