-
-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixes #31
- Loading branch information
Showing
14 changed files
with
564 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,52 @@ | ||
github.com/aws/aws-sdk-go v1.15.27 h1:i75BxN4Es/8rTVQbEKAP1WCiIhhz635xTNeDdZJRAXQ= | ||
github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780 h1:tFh1tRc4CA31yP6qDcu+Trax5wW5GuMxvkIba07qVLY= | ||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= | ||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= | ||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= | ||
github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo= | ||
github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= | ||
github.com/gopherjs/gopherjs v0.0.0-20181003052733-bf5fc7d38140 h1:xUNgm/qAe2iOwkqf230y9I1zOlqgq0gjOJ3RcIIJIoQ= | ||
github.com/gopherjs/gopherjs v0.0.0-20181003052733-bf5fc7d38140/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= | ||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= | ||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= | ||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= | ||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= | ||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= | ||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= | ||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86 h1:D6paGObi5Wud7xg83MaEFyjxQB1W5bz5d0IFppr+ymk= | ||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= | ||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab h1:eFXv9Nu1lGbrNbj619aWwZfVF5HBrm9Plte8aNptuTI= | ||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo= | ||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= | ||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= | ||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= | ||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo= | ||
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= | ||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= | ||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= | ||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= | ||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= | ||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4 h1:Vk3wNqEZwyGyei9yq5ekj7frek2u7HUfffJ1/opblzc= | ||
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= | ||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= | ||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||
golang.org/x/sys v0.0.0-20181003145944-af653ce8b74f h1:zAtpFwFDtnvBWPPelq8CSiqRN1wrIzMUk9dwzbpjpNM= | ||
golang.org/x/sys v0.0.0-20181003145944-af653ce8b74f/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= | ||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||
golang.org/x/tools v0.0.0-20181003164445-c930a8531daf h1:5wIhpNrul45Er8vIy8iqSCUsCEQH7G5YtJ4CfOi4Fk0= | ||
golang.org/x/tools v0.0.0-20181003164445-c930a8531daf/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= | ||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
// Copyright © 2018 Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>. | ||
// | ||
// Use of this source code is governed by an MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package lib | ||
|
||
import ( | ||
"errors" | ||
"path" | ||
"sort" | ||
"strings" | ||
"time" | ||
|
||
"github.com/aws/aws-sdk-go/aws" | ||
|
||
"github.com/aws/aws-sdk-go/aws/session" | ||
"github.com/aws/aws-sdk-go/service/cloudfront" | ||
) | ||
|
||
var _ remoteCDN = (*cloudFrontClient)(nil) | ||
|
||
type cloudFrontClient struct { | ||
// The CloudFront distribution ID | ||
distributionID string | ||
|
||
// Will invalidate the entire cache, e.g. "/*" | ||
force bool | ||
bucketPath string | ||
|
||
logger printer | ||
cf *cloudfront.CloudFront | ||
} | ||
|
||
func newCloudFrontClient( | ||
sess *session.Session, | ||
logger printer, | ||
cfg Config) (*cloudFrontClient, error) { | ||
if cfg.CDNDistributionID == "" { | ||
return nil, errors.New("must provide a distribution ID") | ||
} | ||
return &cloudFrontClient{ | ||
distributionID: cfg.CDNDistributionID, | ||
force: cfg.Force, | ||
bucketPath: cfg.BucketPath, | ||
logger: logger, | ||
cf: cloudfront.New(sess), | ||
}, nil | ||
} | ||
|
||
func (c *cloudFrontClient) InvalidateCDNCache(paths ...string) error { | ||
if len(paths) == 0 { | ||
return nil | ||
} | ||
|
||
dcfg, err := c.cf.GetDistribution(&cloudfront.GetDistributionInput{ | ||
Id: &c.distributionID, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
originPath := *dcfg.Distribution.DistributionConfig.Origins.Items[0].OriginPath | ||
var root string | ||
if originPath != "" || c.bucketPath != "" { | ||
bucket := strings.TrimPrefix(c.bucketPath, "/") | ||
origin := strings.TrimPrefix(originPath, "/") | ||
root = strings.TrimPrefix(bucket, origin) | ||
subPath := strings.TrimPrefix(origin, bucket) | ||
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 = 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) | ||
} | ||
|
||
in := &cloudfront.CreateInvalidationInput{ | ||
DistributionId: &c.distributionID, | ||
InvalidationBatch: c.pathsToInvalidationBatch(time.Now().Format("20060102150405"), paths...), | ||
} | ||
|
||
_, err = c.cf.CreateInvalidation( | ||
in, | ||
) | ||
|
||
return err | ||
} | ||
|
||
func (*cloudFrontClient) pathsToInvalidationBatch(ref string, paths ...string) *cloudfront.InvalidationBatch { | ||
batch := &cloudfront.InvalidationBatch{ | ||
CallerReference: &ref, | ||
} | ||
cfpaths := &cloudfront.Paths{} | ||
for _, p := range paths { | ||
cfpaths.Items = append(cfpaths.Items, aws.String(p)) | ||
} | ||
|
||
qty := int64(len(paths)) | ||
cfpaths.Quantity = &qty | ||
batch.SetPaths(cfpaths) | ||
return batch | ||
} | ||
|
||
// For path rules, see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/Invalidation.html | ||
func normalizeInvalidationPaths( | ||
root string, | ||
threshold int, | ||
force bool, | ||
paths ...string) []string { | ||
|
||
if !strings.HasPrefix(root, "/") { | ||
root = "/" + root | ||
} | ||
|
||
matchAll := path.Join(root, "*/") | ||
clearAll := []string{matchAll} | ||
|
||
if force { | ||
return clearAll | ||
} | ||
|
||
var normalized []string | ||
var maxlevels int | ||
|
||
for _, p := range paths { | ||
p = path.Clean(p) | ||
if !strings.HasPrefix(p, "/") { | ||
p = "/" + p | ||
} | ||
levels := strings.Count(p, "/") | ||
if levels > maxlevels { | ||
maxlevels = levels | ||
} | ||
|
||
if strings.HasSuffix(p, "index.html") { | ||
dir := path.Dir(p) | ||
if !strings.HasSuffix(dir, "/") { | ||
dir += "/" | ||
} | ||
normalized = append(normalized, dir) | ||
} else { | ||
normalized = append(normalized, p) | ||
} | ||
} | ||
|
||
normalized = uniqueStrings(uniqueStrings(normalized)) | ||
sort.Strings(normalized) | ||
|
||
if len(normalized) > threshold { | ||
normalized = uniqueStrings(normalized) | ||
if len(normalized) > threshold { | ||
for k := maxlevels; k > 0; k-- { | ||
for i, p := range normalized { | ||
if strings.Count(p, "/") > k { | ||
parts := strings.Split(strings.TrimPrefix(path.Dir(p), "/"), "/") | ||
normalized[i] = "/" + path.Join(parts[0:len(parts)-k+1]...) + "/*" | ||
} | ||
} | ||
normalized = uniqueStrings(normalized) | ||
if len(normalized) <= threshold { | ||
break | ||
} | ||
} | ||
|
||
if len(normalized) > threshold { | ||
// Give up. | ||
return clearAll | ||
} | ||
} | ||
} | ||
|
||
for _, pattern := range normalized { | ||
if pattern == matchAll { | ||
return clearAll | ||
} | ||
} | ||
|
||
return normalized | ||
} | ||
|
||
func uniqueStrings(s []string) []string { | ||
var unique []string | ||
set := map[string]interface{}{} | ||
for _, val := range s { | ||
if _, ok := set[val]; !ok { | ||
unique = append(unique, val) | ||
set[val] = val | ||
} | ||
} | ||
return unique | ||
} |
Oops, something went wrong.