mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
deploy: Support invalidating a CloudFront CDN cache
This commit is contained in:
parent
2838d58b1d
commit
f4956d9aae
7 changed files with 93 additions and 25 deletions
|
@ -68,6 +68,7 @@ func newDeployCmd() *deployCmd {
|
||||||
cc.cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
cc.cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
|
||||||
cc.cmd.Flags().Bool("dryRun", false, "dry run")
|
cc.cmd.Flags().Bool("dryRun", false, "dry run")
|
||||||
cc.cmd.Flags().Bool("force", false, "force upload of all files")
|
cc.cmd.Flags().Bool("force", false, "force upload of all files")
|
||||||
|
cc.cmd.Flags().Bool("invalidateCDN", true, "invalidate the CDN cache via the CloudFrontDistributionID listed in the deployment target")
|
||||||
cc.cmd.Flags().Int("maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
|
cc.cmd.Flags().Int("maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
|
||||||
|
|
||||||
return cc
|
return cc
|
||||||
|
|
|
@ -213,6 +213,7 @@ func initializeFlags(cmd *cobra.Command, cfg config.Provider) {
|
||||||
"force",
|
"force",
|
||||||
"gc",
|
"gc",
|
||||||
"i18n-warnings",
|
"i18n-warnings",
|
||||||
|
"invalidateCDN",
|
||||||
"layoutDir",
|
"layoutDir",
|
||||||
"logFile",
|
"logFile",
|
||||||
"maxDeletes",
|
"maxDeletes",
|
||||||
|
|
51
deploy/cloudfront.go
Normal file
51
deploy/cloudfront.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2019 The Hugo Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package deploy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
"github.com/aws/aws-sdk-go/aws/session"
|
||||||
|
"github.com/aws/aws-sdk-go/service/cloudfront"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InvalidateCloudFront invalidates the CloudFront cache for distributionID.
|
||||||
|
// It uses the default AWS credentials from the environment.
|
||||||
|
func InvalidateCloudFront(ctx context.Context, distributionID string) error {
|
||||||
|
// SharedConfigEnable enables loading "shared config (~/.aws/config) and
|
||||||
|
// shared credentials (~/.aws/credentials) files".
|
||||||
|
// See https://docs.aws.amazon.com/sdk-for-go/api/aws/session/ for more
|
||||||
|
// details.
|
||||||
|
// This is the same codepath used by Go CDK when creating an s3 URL.
|
||||||
|
// TODO: Update this to a Go CDK helper once available
|
||||||
|
// (https://github.com/google/go-cloud/issues/2003).
|
||||||
|
sess, err := session.NewSessionWithOptions(session.Options{SharedConfigState: session.SharedConfigEnable})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req := &cloudfront.CreateInvalidationInput{
|
||||||
|
DistributionId: aws.String(distributionID),
|
||||||
|
InvalidationBatch: &cloudfront.InvalidationBatch{
|
||||||
|
CallerReference: aws.String(time.Now().Format("20060102150405")),
|
||||||
|
Paths: &cloudfront.Paths{
|
||||||
|
Items: []*string{aws.String("/*")},
|
||||||
|
Quantity: aws.Int64(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, err = cloudfront.New(sess).CreateInvalidationWithContext(ctx, req)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -45,18 +45,19 @@ import (
|
||||||
type Deployer struct {
|
type Deployer struct {
|
||||||
localFs afero.Fs
|
localFs afero.Fs
|
||||||
|
|
||||||
targetURL string // the Go Cloud blob URL to deploy to
|
target *target // the target to deploy to
|
||||||
matchers []*matcher // matchers to apply to uploaded files
|
matchers []*matcher // matchers to apply to uploaded files
|
||||||
quiet bool // true reduces STDOUT
|
quiet bool // true reduces STDOUT
|
||||||
confirm bool // true enables confirmation before making changes
|
confirm bool // true enables confirmation before making changes
|
||||||
dryRun bool // true skips conformations and prints changes instead of applying them
|
dryRun bool // true skips conformations and prints changes instead of applying them
|
||||||
force bool // true forces upload of all files
|
force bool // true forces upload of all files
|
||||||
|
invalidateCDN bool // true enables invalidate CDN cache (if possible)
|
||||||
maxDeletes int // caps the # of files to delete; -1 to disable
|
maxDeletes int // caps the # of files to delete; -1 to disable
|
||||||
}
|
}
|
||||||
|
|
||||||
// New constructs a new *Deployer.
|
// New constructs a new *Deployer.
|
||||||
func New(cfg config.Provider, localFs afero.Fs) (*Deployer, error) {
|
func New(cfg config.Provider, localFs afero.Fs) (*Deployer, error) {
|
||||||
target := cfg.GetString("target")
|
targetName := cfg.GetString("target")
|
||||||
|
|
||||||
// Load the [deployment] section of the config.
|
// Load the [deployment] section of the config.
|
||||||
dcfg, err := decodeConfig(cfg)
|
dcfg, err := decodeConfig(cfg)
|
||||||
|
@ -65,23 +66,24 @@ func New(cfg config.Provider, localFs afero.Fs) (*Deployer, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the target to deploy to.
|
// Find the target to deploy to.
|
||||||
var targetURL string
|
var tgt *target
|
||||||
for _, t := range dcfg.Targets {
|
for _, t := range dcfg.Targets {
|
||||||
if t.Name == target {
|
if t.Name == targetName {
|
||||||
targetURL = t.URL
|
tgt = t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if targetURL == "" {
|
if tgt == nil {
|
||||||
return nil, fmt.Errorf("deployment target %q not found", target)
|
return nil, fmt.Errorf("deployment target %q not found", targetName)
|
||||||
}
|
}
|
||||||
return &Deployer{
|
return &Deployer{
|
||||||
localFs: localFs,
|
localFs: localFs,
|
||||||
targetURL: targetURL,
|
target: tgt,
|
||||||
matchers: dcfg.Matchers,
|
matchers: dcfg.Matchers,
|
||||||
quiet: cfg.GetBool("quiet"),
|
quiet: cfg.GetBool("quiet"),
|
||||||
confirm: cfg.GetBool("confirm"),
|
confirm: cfg.GetBool("confirm"),
|
||||||
dryRun: cfg.GetBool("dryRun"),
|
dryRun: cfg.GetBool("dryRun"),
|
||||||
force: cfg.GetBool("force"),
|
force: cfg.GetBool("force"),
|
||||||
|
invalidateCDN: cfg.GetBool("invalidateCDN"),
|
||||||
maxDeletes: cfg.GetInt("maxDeletes"),
|
maxDeletes: cfg.GetInt("maxDeletes"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -90,7 +92,7 @@ func New(cfg config.Provider, localFs afero.Fs) (*Deployer, error) {
|
||||||
func (d *Deployer) Deploy(ctx context.Context) error {
|
func (d *Deployer) Deploy(ctx context.Context) error {
|
||||||
// TODO: This opens the root path in the bucket/container.
|
// TODO: This opens the root path in the bucket/container.
|
||||||
// Consider adding support for targeting a subdirectory.
|
// Consider adding support for targeting a subdirectory.
|
||||||
bucket, err := blob.OpenBucket(ctx, d.targetURL)
|
bucket, err := blob.OpenBucket(ctx, d.target.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -203,9 +205,14 @@ func (d *Deployer) Deploy(ctx context.Context) error {
|
||||||
jww.FEEDBACK.Println("Success!")
|
jww.FEEDBACK.Println("Success!")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add support for CloudFront invalidation similar to s3deploy,
|
if d.invalidateCDN && d.target.CloudFrontDistributionID != "" {
|
||||||
// and possibly similar functionality for other providers.
|
jww.FEEDBACK.Println("Invalidating CloudFront CDN...")
|
||||||
|
if err := InvalidateCloudFront(ctx, d.target.CloudFrontDistributionID); err != nil {
|
||||||
|
jww.FEEDBACK.Printf("Failed to invalidate CloudFront CDN: %v\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jww.FEEDBACK.Println("Success!")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,8 @@ type deployConfig struct {
|
||||||
type target struct {
|
type target struct {
|
||||||
Name string
|
Name string
|
||||||
URL string
|
URL string
|
||||||
|
|
||||||
|
CloudFrontDistributionID string
|
||||||
}
|
}
|
||||||
|
|
||||||
// matcher represents configuration to be applied to files whose paths match
|
// matcher represents configuration to be applied to files whose paths match
|
||||||
|
|
|
@ -32,9 +32,12 @@ someOtherValue = "foo"
|
||||||
[[deployment.targets]]
|
[[deployment.targets]]
|
||||||
Name = "name1"
|
Name = "name1"
|
||||||
URL = "url1"
|
URL = "url1"
|
||||||
|
CloudFrontDistributionID = "cdn1"
|
||||||
|
|
||||||
[[deployment.targets]]
|
[[deployment.targets]]
|
||||||
name = "name2"
|
name = "name2"
|
||||||
url = "url2"
|
url = "url2"
|
||||||
|
cloudfrontdistributionid = "cdn2"
|
||||||
|
|
||||||
[[deployment.matchers]]
|
[[deployment.matchers]]
|
||||||
Pattern = "^pattern1$"
|
Pattern = "^pattern1$"
|
||||||
|
@ -59,8 +62,10 @@ content-type = "contenttype2"
|
||||||
assert.Equal(2, len(dcfg.Targets))
|
assert.Equal(2, len(dcfg.Targets))
|
||||||
assert.Equal("name1", dcfg.Targets[0].Name)
|
assert.Equal("name1", dcfg.Targets[0].Name)
|
||||||
assert.Equal("url1", dcfg.Targets[0].URL)
|
assert.Equal("url1", dcfg.Targets[0].URL)
|
||||||
|
assert.Equal("cdn1", dcfg.Targets[0].CloudFrontDistributionID)
|
||||||
assert.Equal("name2", dcfg.Targets[1].Name)
|
assert.Equal("name2", dcfg.Targets[1].Name)
|
||||||
assert.Equal("url2", dcfg.Targets[1].URL)
|
assert.Equal("url2", dcfg.Targets[1].URL)
|
||||||
|
assert.Equal("cdn2", dcfg.Targets[1].CloudFrontDistributionID)
|
||||||
|
|
||||||
assert.Equal(2, len(dcfg.Matchers))
|
assert.Equal(2, len(dcfg.Matchers))
|
||||||
assert.Equal("^pattern1$", dcfg.Matchers[0].Pattern)
|
assert.Equal("^pattern1$", dcfg.Matchers[0].Pattern)
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -8,6 +8,7 @@ require (
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
|
||||||
github.com/alecthomas/chroma v0.6.3
|
github.com/alecthomas/chroma v0.6.3
|
||||||
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect
|
github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1 // indirect
|
||||||
|
github.com/aws/aws-sdk-go v1.16.23
|
||||||
github.com/bep/debounce v1.2.0
|
github.com/bep/debounce v1.2.0
|
||||||
github.com/bep/gitmap v1.0.0
|
github.com/bep/gitmap v1.0.0
|
||||||
github.com/bep/go-tocss v0.6.0
|
github.com/bep/go-tocss v0.6.0
|
||||||
|
|
Loading…
Reference in a new issue