mirror of
https://github.com/gohugoio/hugo.git
synced 2025-04-26 13:40:38 +03:00
Avoid impporting deploy from config when nodeploy tag is set
Test: ``` go list -tags nodeploy ./... | grep deploy ``` Fixes #12009
This commit is contained in:
parent
a65622a13e
commit
0257eb50a4
8 changed files with 65 additions and 60 deletions
174
deploy/deployconfig/deployConfig.go
Normal file
174
deploy/deployconfig/deployConfig.go
Normal file
|
@ -0,0 +1,174 @@
|
|||
// Copyright 2024 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 deployconfig
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/gobwas/glob"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
hglob "github.com/gohugoio/hugo/hugofs/glob"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
const DeploymentConfigKey = "deployment"
|
||||
|
||||
// DeployConfig is the complete configuration for deployment.
|
||||
type DeployConfig struct {
|
||||
Targets []*Target
|
||||
Matchers []*Matcher
|
||||
Order []string
|
||||
|
||||
// Usually set via flags.
|
||||
// Target deployment Name; defaults to the first one.
|
||||
Target string
|
||||
// Show a confirm prompt before deploying.
|
||||
Confirm bool
|
||||
// DryRun will try the deployment without any remote changes.
|
||||
DryRun bool
|
||||
// Force will re-upload all files.
|
||||
Force bool
|
||||
// Invalidate the CDN cache listed in the deployment target.
|
||||
InvalidateCDN bool
|
||||
// MaxDeletes is the maximum number of files to delete.
|
||||
MaxDeletes int
|
||||
// Number of concurrent workers to use when uploading files.
|
||||
Workers int
|
||||
|
||||
Ordering []*regexp.Regexp `json:"-"` // compiled Order
|
||||
}
|
||||
|
||||
type Target struct {
|
||||
Name string
|
||||
URL string
|
||||
|
||||
CloudFrontDistributionID string
|
||||
|
||||
// GoogleCloudCDNOrigin specifies the Google Cloud project and CDN origin to
|
||||
// invalidate when deploying this target. It is specified as <project>/<origin>.
|
||||
GoogleCloudCDNOrigin string
|
||||
|
||||
// Optional patterns of files to include/exclude for this target.
|
||||
// Parsed using github.com/gobwas/glob.
|
||||
Include string
|
||||
Exclude string
|
||||
|
||||
// Parsed versions of Include/Exclude.
|
||||
IncludeGlob glob.Glob `json:"-"`
|
||||
ExcludeGlob glob.Glob `json:"-"`
|
||||
}
|
||||
|
||||
func (tgt *Target) ParseIncludeExclude() error {
|
||||
var err error
|
||||
if tgt.Include != "" {
|
||||
tgt.IncludeGlob, err = hglob.GetGlob(tgt.Include)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid deployment.target.include %q: %v", tgt.Include, err)
|
||||
}
|
||||
}
|
||||
if tgt.Exclude != "" {
|
||||
tgt.ExcludeGlob, err = hglob.GetGlob(tgt.Exclude)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid deployment.target.exclude %q: %v", tgt.Exclude, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Matcher represents configuration to be applied to files whose paths match
|
||||
// a specified pattern.
|
||||
type Matcher struct {
|
||||
// Pattern is the string pattern to match against paths.
|
||||
// Matching is done against paths converted to use / as the path separator.
|
||||
Pattern string
|
||||
|
||||
// CacheControl specifies caching attributes to use when serving the blob.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
|
||||
CacheControl string
|
||||
|
||||
// ContentEncoding specifies the encoding used for the blob's content, if any.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding
|
||||
ContentEncoding string
|
||||
|
||||
// ContentType specifies the MIME type of the blob being written.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type
|
||||
ContentType string
|
||||
|
||||
// Gzip determines whether the file should be gzipped before upload.
|
||||
// If so, the ContentEncoding field will automatically be set to "gzip".
|
||||
Gzip bool
|
||||
|
||||
// Force indicates that matching files should be re-uploaded. Useful when
|
||||
// other route-determined metadata (e.g., ContentType) has changed.
|
||||
Force bool
|
||||
|
||||
// Re is Pattern compiled.
|
||||
Re *regexp.Regexp `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Matcher) Matches(path string) bool {
|
||||
return m.Re.MatchString(path)
|
||||
}
|
||||
|
||||
var DefaultConfig = DeployConfig{
|
||||
Workers: 10,
|
||||
InvalidateCDN: true,
|
||||
MaxDeletes: 256,
|
||||
}
|
||||
|
||||
// DecodeConfig creates a config from a given Hugo configuration.
|
||||
func DecodeConfig(cfg config.Provider) (DeployConfig, error) {
|
||||
dcfg := DefaultConfig
|
||||
|
||||
if !cfg.IsSet(DeploymentConfigKey) {
|
||||
return dcfg, nil
|
||||
}
|
||||
if err := mapstructure.WeakDecode(cfg.GetStringMap(DeploymentConfigKey), &dcfg); err != nil {
|
||||
return dcfg, err
|
||||
}
|
||||
|
||||
if dcfg.Workers <= 0 {
|
||||
dcfg.Workers = 10
|
||||
}
|
||||
|
||||
for _, tgt := range dcfg.Targets {
|
||||
if *tgt == (Target{}) {
|
||||
return dcfg, errors.New("empty deployment target")
|
||||
}
|
||||
if err := tgt.ParseIncludeExclude(); err != nil {
|
||||
return dcfg, err
|
||||
}
|
||||
}
|
||||
var err error
|
||||
for _, m := range dcfg.Matchers {
|
||||
if *m == (Matcher{}) {
|
||||
return dcfg, errors.New("empty deployment matcher")
|
||||
}
|
||||
m.Re, err = regexp.Compile(m.Pattern)
|
||||
if err != nil {
|
||||
return dcfg, fmt.Errorf("invalid deployment.matchers.pattern: %v", err)
|
||||
}
|
||||
}
|
||||
for _, o := range dcfg.Order {
|
||||
re, err := regexp.Compile(o)
|
||||
if err != nil {
|
||||
return dcfg, fmt.Errorf("invalid deployment.orderings.pattern: %v", err)
|
||||
}
|
||||
dcfg.Ordering = append(dcfg.Ordering, re)
|
||||
}
|
||||
|
||||
return dcfg, nil
|
||||
}
|
199
deploy/deployconfig/deployConfig_test.go
Normal file
199
deploy/deployconfig/deployConfig_test.go
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Copyright 2024 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.
|
||||
|
||||
//go:build !nodeploy
|
||||
// +build !nodeploy
|
||||
|
||||
package deployconfig
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gohugoio/hugo/config"
|
||||
)
|
||||
|
||||
func TestDecodeConfigFromTOML(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
|
||||
someOtherValue = "foo"
|
||||
|
||||
[deployment]
|
||||
|
||||
order = ["o1", "o2"]
|
||||
|
||||
# All lowercase.
|
||||
[[deployment.targets]]
|
||||
name = "name0"
|
||||
url = "url0"
|
||||
cloudfrontdistributionid = "cdn0"
|
||||
include = "*.html"
|
||||
|
||||
# All uppercase.
|
||||
[[deployment.targets]]
|
||||
NAME = "name1"
|
||||
URL = "url1"
|
||||
CLOUDFRONTDISTRIBUTIONID = "cdn1"
|
||||
INCLUDE = "*.jpg"
|
||||
|
||||
# Camelcase.
|
||||
[[deployment.targets]]
|
||||
name = "name2"
|
||||
url = "url2"
|
||||
cloudFrontDistributionID = "cdn2"
|
||||
exclude = "*.png"
|
||||
|
||||
# All lowercase.
|
||||
[[deployment.matchers]]
|
||||
pattern = "^pattern0$"
|
||||
cachecontrol = "cachecontrol0"
|
||||
contentencoding = "contentencoding0"
|
||||
contenttype = "contenttype0"
|
||||
|
||||
# All uppercase.
|
||||
[[deployment.matchers]]
|
||||
PATTERN = "^pattern1$"
|
||||
CACHECONTROL = "cachecontrol1"
|
||||
CONTENTENCODING = "contentencoding1"
|
||||
CONTENTTYPE = "contenttype1"
|
||||
GZIP = true
|
||||
FORCE = true
|
||||
|
||||
# Camelcase.
|
||||
[[deployment.matchers]]
|
||||
pattern = "^pattern2$"
|
||||
cacheControl = "cachecontrol2"
|
||||
contentEncoding = "contentencoding2"
|
||||
contentType = "contenttype2"
|
||||
gzip = true
|
||||
force = true
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
dcfg, err := DecodeConfig(cfg)
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
// Order.
|
||||
c.Assert(len(dcfg.Order), qt.Equals, 2)
|
||||
c.Assert(dcfg.Order[0], qt.Equals, "o1")
|
||||
c.Assert(dcfg.Order[1], qt.Equals, "o2")
|
||||
c.Assert(len(dcfg.Ordering), qt.Equals, 2)
|
||||
|
||||
// Targets.
|
||||
c.Assert(len(dcfg.Targets), qt.Equals, 3)
|
||||
wantInclude := []string{"*.html", "*.jpg", ""}
|
||||
wantExclude := []string{"", "", "*.png"}
|
||||
for i := 0; i < 3; i++ {
|
||||
tgt := dcfg.Targets[i]
|
||||
c.Assert(tgt.Name, qt.Equals, fmt.Sprintf("name%d", i))
|
||||
c.Assert(tgt.URL, qt.Equals, fmt.Sprintf("url%d", i))
|
||||
c.Assert(tgt.CloudFrontDistributionID, qt.Equals, fmt.Sprintf("cdn%d", i))
|
||||
c.Assert(tgt.Include, qt.Equals, wantInclude[i])
|
||||
if wantInclude[i] != "" {
|
||||
c.Assert(tgt.IncludeGlob, qt.Not(qt.IsNil))
|
||||
}
|
||||
c.Assert(tgt.Exclude, qt.Equals, wantExclude[i])
|
||||
if wantExclude[i] != "" {
|
||||
c.Assert(tgt.ExcludeGlob, qt.Not(qt.IsNil))
|
||||
}
|
||||
}
|
||||
|
||||
// Matchers.
|
||||
c.Assert(len(dcfg.Matchers), qt.Equals, 3)
|
||||
for i := 0; i < 3; i++ {
|
||||
m := dcfg.Matchers[i]
|
||||
c.Assert(m.Pattern, qt.Equals, fmt.Sprintf("^pattern%d$", i))
|
||||
c.Assert(m.Re, qt.Not(qt.IsNil))
|
||||
c.Assert(m.CacheControl, qt.Equals, fmt.Sprintf("cachecontrol%d", i))
|
||||
c.Assert(m.ContentEncoding, qt.Equals, fmt.Sprintf("contentencoding%d", i))
|
||||
c.Assert(m.ContentType, qt.Equals, fmt.Sprintf("contenttype%d", i))
|
||||
c.Assert(m.Gzip, qt.Equals, i != 0)
|
||||
c.Assert(m.Force, qt.Equals, i != 0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvalidOrderingPattern(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
|
||||
someOtherValue = "foo"
|
||||
|
||||
[deployment]
|
||||
order = ["["] # invalid regular expression
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
_, err = DecodeConfig(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
func TestInvalidMatcherPattern(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
|
||||
someOtherValue = "foo"
|
||||
|
||||
[deployment]
|
||||
[[deployment.matchers]]
|
||||
Pattern = "[" # invalid regular expression
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
_, err = DecodeConfig(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
func TestDecodeConfigDefault(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
dcfg, err := DecodeConfig(config.New())
|
||||
c.Assert(err, qt.IsNil)
|
||||
c.Assert(len(dcfg.Targets), qt.Equals, 0)
|
||||
c.Assert(len(dcfg.Matchers), qt.Equals, 0)
|
||||
}
|
||||
|
||||
func TestEmptyTarget(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
[deployment]
|
||||
[[deployment.targets]]
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
_, err = DecodeConfig(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
||||
|
||||
func TestEmptyMatcher(t *testing.T) {
|
||||
c := qt.New(t)
|
||||
|
||||
tomlConfig := `
|
||||
[deployment]
|
||||
[[deployment.matchers]]
|
||||
`
|
||||
cfg, err := config.FromConfigString(tomlConfig, "toml")
|
||||
c.Assert(err, qt.IsNil)
|
||||
|
||||
_, err = DecodeConfig(cfg)
|
||||
c.Assert(err, qt.Not(qt.IsNil))
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue