commands: Fix config environment handling

Fixes #6503
Fixes #6824
This commit is contained in:
Bjørn Erik Pedersen 2020-01-31 09:09:11 +01:00
parent 0792cfa9fa
commit 2bbc865f7b
13 changed files with 211 additions and 204 deletions

View File

@ -47,12 +47,12 @@ func (b *commandsBuilder) addAll() *commandsBuilder {
b.newServerCmd(),
newVersionCmd(),
newEnvCmd(),
newConfigCmd(),
b.newConfigCmd(),
newCheckCmd(),
newDeployCmd(),
newConvertCmd(),
b.newDeployCmd(),
b.newConvertCmd(),
b.newNewCmd(),
newListCmd(),
b.newListCmd(),
newImportCmd(),
newGenCmd(),
createReleaser(),
@ -111,6 +111,12 @@ func (b *commandsBuilder) newBuilderCmd(cmd *cobra.Command) *baseBuilderCmd {
return bcmd
}
func (b *commandsBuilder) newBuilderBasicCmd(cmd *cobra.Command) *baseBuilderCmd {
bcmd := &baseBuilderCmd{commandsBuilder: b, baseCmd: &baseCmd{cmd: cmd}}
bcmd.hugoBuilderCommon.handleCommonBuilderFlags(cmd)
return bcmd
}
func (c *baseCmd) flagsToConfig(cfg config.Provider) {
initializeFlags(c.cmd, cfg)
}

View File

@ -20,6 +20,12 @@ import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/htesting"
"github.com/spf13/afero"
"github.com/gohugoio/hugo/hugofs"
"github.com/gohugoio/hugo/common/types"
"github.com/spf13/cobra"
@ -32,18 +38,117 @@ func TestExecute(t *testing.T) {
c := qt.New(t)
dir, err := createSimpleTestSite(t, testSiteConfig{})
createSite := func(c *qt.C) (string, func()) {
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil)
return dir, clean
}
c.Run("hugo", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
resp := Execute([]string{"-s=" + dir})
c.Assert(resp.Err, qt.IsNil)
result := resp.Result
c.Assert(len(result.Sites) == 1, qt.Equals, true)
c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramproduction")
})
c.Run("hugo, set environment", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
resp := Execute([]string{"-s=" + dir, "-e=staging"})
c.Assert(resp.Err, qt.IsNil)
result := resp.Result
c.Assert(result.Sites[0].Info.Params()["myparam"], qt.Equals, "paramstaging")
})
c.Run("convert toJSON", func(c *qt.C) {
dir, clean := createSite(c)
output := filepath.Join(dir, "myjson")
defer clean()
resp := Execute([]string{"convert", "toJSON", "-s=" + dir, "-e=staging", "-o=" + output})
c.Assert(resp.Err, qt.IsNil)
converted := readFileFrom(c, filepath.Join(output, "content", "p1.md"))
c.Assert(converted, qt.Equals, "{\n \"title\": \"P1\",\n \"weight\": 1\n}\n\nContent\n\n", qt.Commentf(converted))
})
c.Run("config, set environment", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
out, err := captureStdout(func() error {
resp := Execute([]string{"config", "-s=" + dir, "-e=staging"})
return resp.Err
})
c.Assert(err, qt.IsNil)
c.Assert(out, qt.Contains, "params = map[myparam:paramstaging]", qt.Commentf(out))
})
c.Run("deploy, environment set", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
resp := Execute([]string{"deploy", "-s=" + dir, "-e=staging", "--target=mydeployment", "--dryRun"})
c.Assert(resp.Err, qt.Not(qt.IsNil))
c.Assert(resp.Err.Error(), qt.Contains, `no provider registered for "hugocloud"`)
})
c.Run("list", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
out, err := captureStdout(func() error {
resp := Execute([]string{"list", "all", "-s=" + dir, "-e=staging"})
return resp.Err
})
c.Assert(err, qt.IsNil)
c.Assert(out, qt.Contains, "p1.md")
})
c.Run("new theme", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
themesDir := filepath.Join(dir, "mythemes")
resp := Execute([]string{"new", "theme", "mytheme", "-s=" + dir, "-e=staging", "--themesDir=" + themesDir})
c.Assert(resp.Err, qt.IsNil)
themeTOML := readFileFrom(c, filepath.Join(themesDir, "mytheme", "theme.toml"))
c.Assert(themeTOML, qt.Contains, "name = \"Mytheme\"")
})
c.Run("new site", func(c *qt.C) {
dir, clean := createSite(c)
defer clean()
siteDir := filepath.Join(dir, "mysite")
resp := Execute([]string{"new", "site", siteDir, "-e=staging"})
c.Assert(resp.Err, qt.IsNil)
config := readFileFrom(c, filepath.Join(siteDir, "config.toml"))
c.Assert(config, qt.Contains, "baseURL = \"http://example.org/\"")
checkNewSiteInited(c, siteDir)
})
}
func checkNewSiteInited(c *qt.C, basepath string) {
paths := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
filepath.Join(basepath, "archetypes"),
filepath.Join(basepath, "static"),
filepath.Join(basepath, "data"),
filepath.Join(basepath, "config.toml"),
}
for _, path := range paths {
_, err := os.Stat(path)
c.Assert(err, qt.IsNil)
}
}
func readFileFrom(c *qt.C, filename string) string {
c.Helper()
filename = filepath.Clean(filename)
b, err := afero.ReadFile(hugofs.Os, filename)
c.Assert(err, qt.IsNil)
defer func() {
os.RemoveAll(dir)
}()
resp := Execute([]string{"-s=" + dir})
c.Assert(resp.Err, qt.IsNil)
result := resp.Result
c.Assert(len(result.Sites) == 1, qt.Equals, true)
c.Assert(len(result.Sites[0].RegularPages()) == 1, qt.Equals, true)
return string(b)
}
func TestCommandsPersistentFlags(t *testing.T) {
@ -146,16 +251,14 @@ func TestCommandsExecute(t *testing.T) {
c := qt.New(t)
dir, err := createSimpleTestSite(t, testSiteConfig{})
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
c.Assert(err, qt.IsNil)
dirOut, err := ioutil.TempDir("", "hugo-cli-out")
dirOut, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-cli-out")
c.Assert(err, qt.IsNil)
defer func() {
os.RemoveAll(dir)
os.RemoveAll(dirOut)
}()
defer clean()
defer clean2()
sourceFlag := fmt.Sprintf("-s=%s", dir)
@ -222,10 +325,10 @@ type testSiteConfig struct {
contentDir string
}
func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, error) {
d, e := ioutil.TempDir("", "hugo-cli")
func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, func(), error) {
d, clean, e := htesting.CreateTempDir(hugofs.Os, "hugo-cli")
if e != nil {
return "", e
return "", nil, e
}
cfgStr := `
@ -233,6 +336,7 @@ func createSimpleTestSite(t *testing.T, cfg testSiteConfig) (string, error) {
baseURL = "https://example.org"
title = "Hugo Commands"
`
contentDir := "content"
@ -244,8 +348,19 @@ title = "Hugo Commands"
contentDir = cfg.contentDir
}
os.MkdirAll(filepath.Join(d, "public"), 0777)
// Just the basic. These are for CLI tests, not site testing.
writeFile(t, filepath.Join(d, "config.toml"), cfgStr)
writeFile(t, filepath.Join(d, "config", "staging", "params.toml"), `myparam="paramstaging"`)
writeFile(t, filepath.Join(d, "config", "staging", "deployment.toml"), `
[[targets]]
name = "mydeployment"
URL = "hugocloud://hugotestbucket"
`)
writeFile(t, filepath.Join(d, "config", "testing", "params.toml"), `myparam="paramtesting"`)
writeFile(t, filepath.Join(d, "config", "production", "params.toml"), `myparam="paramproduction"`)
writeFile(t, filepath.Join(d, contentDir, "p1.md"), `
---
@ -270,7 +385,7 @@ Environment: {{ hugo.Environment }}
`)
return d, nil
return d, clean, nil
}

View File

@ -15,6 +15,7 @@ package commands
import (
"encoding/json"
"fmt"
"os"
"reflect"
"regexp"
@ -27,27 +28,23 @@ import (
"github.com/gohugoio/hugo/modules"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
)
var _ cmder = (*configCmd)(nil)
type configCmd struct {
hugoBuilderCommon
*baseCmd
*baseBuilderCmd
}
func newConfigCmd() *configCmd {
func (b *commandsBuilder) newConfigCmd() *configCmd {
cc := &configCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
cmd := &cobra.Command{
Use: "config",
Short: "Print the site configuration",
Long: `Print the site configuration, both default and custom settings.`,
RunE: cc.printConfig,
})
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
}
printMountsCmd := &cobra.Command{
Use: "mounts",
@ -55,7 +52,9 @@ func newConfigCmd() *configCmd {
RunE: cc.printMounts,
}
cc.cmd.AddCommand(printMountsCmd)
cmd.AddCommand(printMountsCmd)
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}
@ -105,9 +104,9 @@ func (c *configCmd) printConfig(cmd *cobra.Command, args []string) error {
for _, k := range keys {
kv := reflect.ValueOf(allSettings[k])
if kv.Kind() == reflect.String {
jww.FEEDBACK.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
fmt.Printf("%s%s\"%+v\"\n", k, separator, allSettings[k])
} else {
jww.FEEDBACK.Printf("%s%s%+v\n", k, separator, allSettings[k])
fmt.Printf("%s%s%+v\n", k, separator, allSettings[k])
}
}

View File

@ -44,27 +44,25 @@ var (
)
type convertCmd struct {
hugoBuilderCommon
outputDir string
unsafe bool
*baseCmd
*baseBuilderCmd
}
func newConvertCmd() *convertCmd {
func (b *commandsBuilder) newConvertCmd() *convertCmd {
cc := &convertCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
cmd := &cobra.Command{
Use: "convert",
Short: "Convert your content to different formats",
Long: `Convert your content (e.g. front matter) to different formats.
See convert's subcommands toJSON, toTOML and toYAML for more information.`,
RunE: nil,
})
}
cc.cmd.AddCommand(
cmd.AddCommand(
&cobra.Command{
Use: "toJSON",
Short: "Convert front matter to JSON",
@ -94,10 +92,10 @@ to use YAML for the front matter.`,
},
)
cc.cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cc.cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
cmd.PersistentFlags().StringVarP(&cc.outputDir, "output", "o", "", "filesystem path to write files to")
cmd.PersistentFlags().BoolVar(&cc.unsafe, "unsafe", false, "enable less safe operations, please backup first")
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}

View File

@ -24,8 +24,7 @@ var _ cmder = (*deployCmd)(nil)
// deployCmd supports deploying sites to Cloud providers.
type deployCmd struct {
hugoBuilderCommon
*baseCmd
*baseBuilderCmd
}
// TODO: In addition to the "deploy" command, consider adding a "--deploy"
@ -38,10 +37,10 @@ type deployCmd struct {
// run "hugo && hugo deploy" again and again and upload new stuff every time. Is
// this intended?
func newDeployCmd() *deployCmd {
func (b *commandsBuilder) newDeployCmd() *deployCmd {
cc := &deployCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
cmd := &cobra.Command{
Use: "deploy",
Short: "Deploy your site to a Cloud provider.",
Long: `Deploy your site to a Cloud provider.
@ -64,14 +63,16 @@ documentation.
}
return deployer.Deploy(context.Background())
},
})
}
cc.cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
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("force", false, "force upload of all files")
cc.cmd.Flags().Bool("invalidateCDN", true, "invalidate the CDN cache listed in the deployment target")
cc.cmd.Flags().Int("maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
cmd.Flags().String("target", "", "target deployment from deployments section in config file; defaults to the first one")
cmd.Flags().Bool("confirm", false, "ask for confirmation before making changes to the target")
cmd.Flags().Bool("dryRun", false, "dry run")
cmd.Flags().Bool("force", false, "force upload of all files")
cmd.Flags().Bool("invalidateCDN", true, "invalidate the CDN cache listed in the deployment target")
cmd.Flags().Int("maxDeletes", 256, "maximum # of files to delete, or -1 to disable")
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}

View File

@ -14,7 +14,6 @@
package commands
import (
"os"
"testing"
qt "github.com/frankban/quicktest"
@ -37,12 +36,9 @@ title = "Hugo Commands"
contentDir = "thisdoesnotexist"
`
dir, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
dir, clean, err := createSimpleTestSite(t, testSiteConfig{configTOML: cfgStr, contentDir: contentDir})
c.Assert(err, qt.IsNil)
defer func() {
os.RemoveAll(dir)
}()
defer clean()
cmd.SetArgs([]string{"-s=" + dir, "-c=" + contentDir})

View File

@ -29,8 +29,7 @@ import (
var _ cmder = (*listCmd)(nil)
type listCmd struct {
hugoBuilderCommon
*baseCmd
*baseBuilderCmd
}
func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites, error) {
@ -59,19 +58,19 @@ func (lc *listCmd) buildSites(config map[string]interface{}) (*hugolib.HugoSites
return sites, nil
}
func newListCmd() *listCmd {
func (b *commandsBuilder) newListCmd() *listCmd {
cc := &listCmd{}
cc.baseCmd = newBaseCmd(&cobra.Command{
cmd := &cobra.Command{
Use: "list",
Short: "Listing out various types of content",
Long: `Listing out various types of content.
List requires a subcommand, e.g. ` + "`hugo list drafts`.",
RunE: nil,
})
}
cc.cmd.AddCommand(
cmd.AddCommand(
&cobra.Command{
Use: "drafts",
Short: "List all drafts",
@ -202,8 +201,7 @@ List requires a subcommand, e.g. ` + "`hugo list drafts`.",
},
)
cc.cmd.PersistentFlags().StringVarP(&cc.source, "source", "s", "", "filesystem path to read files relative from")
cc.cmd.PersistentFlags().SetAnnotation("source", cobra.BashCompSubdirsInDir, []string{})
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return cc
}

View File

@ -10,31 +10,27 @@ import (
"testing"
qt "github.com/frankban/quicktest"
"github.com/spf13/cobra"
)
func captureStdout(f func() (*cobra.Command, error)) (string, error) {
func captureStdout(f func() error) (string, error) {
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
_, err := f()
if err != nil {
return "", err
}
err := f()
w.Close()
os.Stdout = old
var buf bytes.Buffer
io.Copy(&buf, r)
return buf.String(), nil
return buf.String(), err
}
func TestListAll(t *testing.T) {
c := qt.New(t)
dir, err := createSimpleTestSite(t, testSiteConfig{})
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
defer clean()
c.Assert(err, qt.IsNil)
@ -47,7 +43,10 @@ func TestListAll(t *testing.T) {
cmd.SetArgs([]string{"-s=" + dir, "list", "all"})
out, err := captureStdout(cmd.ExecuteC)
out, err := captureStdout(func() error {
_, err := cmd.ExecuteC()
return err
})
c.Assert(err, qt.IsNil)
r := csv.NewReader(strings.NewReader(out))

View File

@ -55,8 +55,8 @@ Ensure you run this within the root directory of your site.`,
cmd.Flags().StringVarP(&cc.contentType, "kind", "k", "", "content type to create")
cmd.Flags().StringVar(&cc.contentEditor, "editor", "", "edit new content with this editor, if provided")
cmd.AddCommand(newNewSiteCmd().getCommand())
cmd.AddCommand(newNewThemeCmd().getCommand())
cmd.AddCommand(b.newNewSiteCmd().getCommand())
cmd.AddCommand(b.newNewThemeCmd().getCommand())
cmd.RunE = cc.newContent

View File

@ -17,9 +17,6 @@ import (
"path/filepath"
"testing"
"github.com/gohugoio/hugo/hugofs"
"github.com/spf13/viper"
qt "github.com/frankban/quicktest"
)
@ -30,105 +27,3 @@ func TestNewContentPathSectionWithForwardSlashes(t *testing.T) {
c.Assert(p, qt.Equals, filepath.FromSlash("/post/new.md"))
c.Assert(s, qt.Equals, "post")
}
func checkNewSiteInited(fs *hugofs.Fs, basepath string, t *testing.T) {
c := qt.New(t)
paths := []string{
filepath.Join(basepath, "layouts"),
filepath.Join(basepath, "content"),
filepath.Join(basepath, "archetypes"),
filepath.Join(basepath, "static"),
filepath.Join(basepath, "data"),
filepath.Join(basepath, "config.toml"),
}
for _, path := range paths {
_, err := fs.Source.Stat(path)
c.Assert(err, qt.IsNil)
}
}
func TestDoNewSite(t *testing.T) {
c := qt.New(t)
n := newNewSiteCmd()
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
c.Assert(n.doNewSite(fs, basepath, false), qt.IsNil)
checkNewSiteInited(fs, basepath, t)
}
func TestDoNewSite_noerror_base_exists_but_empty(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
c.Assert(n.doNewSite(fs, basepath, false), qt.IsNil)
}
func TestDoNewSite_error_base_exists(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
_, err := fs.Source.Create(filepath.Join(basepath, "foo"))
c.Assert(err, qt.IsNil)
// Since the directory already exists and isn't empty, expect an error
c.Assert(n.doNewSite(fs, basepath, false), qt.Not(qt.IsNil))
}
func TestDoNewSite_force_empty_dir(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
c.Assert(n.doNewSite(fs, basepath, true), qt.IsNil)
checkNewSiteInited(fs, basepath, t)
}
func TestDoNewSite_error_force_dir_inside_exists(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
contentPath := filepath.Join(basepath, "content")
c.Assert(fs.Source.MkdirAll(contentPath, 0777), qt.IsNil)
c.Assert(n.doNewSite(fs, basepath, true), qt.Not(qt.IsNil))
}
func TestDoNewSite_error_force_config_inside_exists(t *testing.T) {
c := qt.New(t)
basepath := filepath.Join("base", "blog")
_, fs := newTestCfg()
n := newNewSiteCmd()
configPath := filepath.Join(basepath, "config.toml")
c.Assert(fs.Source.MkdirAll(basepath, 0777), qt.IsNil)
_, err := fs.Source.Create(configPath)
c.Assert(err, qt.IsNil)
c.Assert(n.doNewSite(fs, basepath, true), qt.Not(qt.IsNil))
}
func newTestCfg() (*viper.Viper, *hugofs.Fs) {
v := viper.New()
fs := hugofs.NewMem(v)
v.SetFs(fs.Source)
return v, fs
}

View File

@ -37,11 +37,11 @@ var _ cmder = (*newSiteCmd)(nil)
type newSiteCmd struct {
configFormat string
*baseCmd
*baseBuilderCmd
}
func newNewSiteCmd() *newSiteCmd {
ccmd := &newSiteCmd{}
func (b *commandsBuilder) newNewSiteCmd() *newSiteCmd {
cc := &newSiteCmd{}
cmd := &cobra.Command{
Use: "site [path]",
@ -49,15 +49,15 @@ func newNewSiteCmd() *newSiteCmd {
Long: `Create a new site in the provided directory.
The new site will have the correct structure, but no content or theme yet.
Use ` + "`hugo new [contentPath]`" + ` to create new content.`,
RunE: ccmd.newSite,
RunE: cc.newSite,
}
cmd.Flags().StringVarP(&ccmd.configFormat, "format", "f", "toml", "config & frontmatter format")
cmd.Flags().StringVarP(&cc.configFormat, "format", "f", "toml", "config & frontmatter format")
cmd.Flags().Bool("force", false, "init inside non-empty directory")
ccmd.baseCmd = newBaseCmd(cmd)
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return ccmd
return cc
}

View File

@ -29,12 +29,11 @@ import (
var _ cmder = (*newThemeCmd)(nil)
type newThemeCmd struct {
*baseCmd
hugoBuilderCommon
*baseBuilderCmd
}
func newNewThemeCmd() *newThemeCmd {
ccmd := &newThemeCmd{baseCmd: newBaseCmd(nil)}
func (b *commandsBuilder) newNewThemeCmd() *newThemeCmd {
cc := &newThemeCmd{}
cmd := &cobra.Command{
Use: "theme [name]",
@ -43,12 +42,12 @@ func newNewThemeCmd() *newThemeCmd {
New theme is a skeleton. Please add content to the touched files. Add your
name to the copyright line in the license and adjust the theme.toml file
as you see fit.`,
RunE: ccmd.newTheme,
RunE: cc.newTheme,
}
ccmd.cmd = cmd
cc.baseBuilderCmd = b.newBuilderBasicCmd(cmd)
return ccmd
return cc
}
// newTheme creates a new Hugo theme template

View File

@ -34,7 +34,8 @@ func TestServer(t *testing.T) {
t.Skip("Skip server test on appveyor")
}
c := qt.New(t)
dir, err := createSimpleTestSite(t, testSiteConfig{})
dir, clean, err := createSimpleTestSite(t, testSiteConfig{})
defer clean()
c.Assert(err, qt.IsNil)
// Let us hope that this port is available on all systems ...