From 7e223b3baaef68d6e6f99e28f162362c81deffba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Erik=20Pedersen?= Date: Tue, 24 Nov 2020 14:11:42 +0100 Subject: [PATCH] Allow setting the delimiter used for setting config via OS env, e.g. HUGO_ Fixes #7829 --- common/maps/params.go | 2 +- .../en/getting-started/configuration.md | 2 + hugolib/config.go | 62 ++++++++++++------- hugolib/config_test.go | 11 ++++ 4 files changed, 53 insertions(+), 24 deletions(-) diff --git a/common/maps/params.go b/common/maps/params.go index 1f0856598..5e973051b 100644 --- a/common/maps/params.go +++ b/common/maps/params.go @@ -84,7 +84,7 @@ func GetNestedParam(keyStr, separator string, candidates ...Params) (interface{} } func GetNestedParamFn(keyStr, separator string, lookupFn func(key string) interface{}) (interface{}, string, map[string]interface{}, error) { - keySegments := strings.Split(strings.ToLower(keyStr), separator) + keySegments := strings.Split(keyStr, separator) if len(keySegments) == 0 { return nil, "", nil, nil } diff --git a/docs/content/en/getting-started/configuration.md b/docs/content/en/getting-started/configuration.md index fda7e2327..d12ecdf3b 100644 --- a/docs/content/en/getting-started/configuration.md +++ b/docs/content/en/getting-started/configuration.md @@ -427,6 +427,8 @@ Names must be prefixed with `HUGO_` and the configuration key must be set in upp To set config params, prefix the name with `HUGO_PARAMS_` {{% /note %}} +{{< new-in "0.79.0" >}} If you are using snake_cased variable names, the above will not work, so since Hugo 0.79.0 Hugo determines the delimiter to use by the first character after `HUGO`. This allows you to define environment variables on the form `HUGOxPARAMSxAPI_KEY=abcdefgh`, using any [allowed](https://stackoverflow.com/questions/2821043/allowed-characters-in-linux-environment-variable-names#:~:text=So%20names%20may%20contain%20any,not%20begin%20with%20a%20digit.) delimiter. + {{< todo >}} Test and document setting params via JSON env var. {{< /todo >}} diff --git a/hugolib/config.go b/hugolib/config.go index 72b51272b..9acb7d701 100644 --- a/hugolib/config.go +++ b/hugolib/config.go @@ -18,6 +18,8 @@ import ( "path/filepath" "strings" + "github.com/gohugoio/hugo/common/types" + "github.com/gobwas/glob" hglob "github.com/gohugoio/hugo/hugofs/glob" @@ -166,45 +168,59 @@ func LoadConfig(d ConfigSourceDescriptor, doWithConfig ...func(cfg config.Provid } } + const delim = "__env__delim" + // Apply environment overrides if len(d.Environ) > 0 { - // Extract all that start with the HUGO_ prefix - const hugoEnvPrefix = "HUGO_" - var hugoEnv []string + // Extract all that start with the HUGO prefix. + // The delimiter is the following rune, usually "_". + const hugoEnvPrefix = "HUGO" + var hugoEnv []types.KeyValueStr for _, v := range d.Environ { key, val := config.SplitEnvVar(v) if strings.HasPrefix(key, hugoEnvPrefix) { - hugoEnv = append(hugoEnv, strings.ToLower(strings.TrimPrefix(key, hugoEnvPrefix)), val) + delimiterAndKey := strings.TrimPrefix(key, hugoEnvPrefix) + if len(delimiterAndKey) < 2 { + continue + } + // Allow delimiters to be case sensitive. + // It turns out there isn't that many allowed special + // chars in environment variables when used in Bash and similar, + // so variables on the form HUGOxPARAMSxFOO=bar is one option. + key := strings.ReplaceAll(delimiterAndKey[1:], delimiterAndKey[:1], delim) + key = strings.ToLower(key) + hugoEnv = append(hugoEnv, types.KeyValueStr{ + Key: key, + Value: val, + }) + } } - if len(hugoEnv) > 0 { - for i := 0; i < len(hugoEnv); i += 2 { - key, valStr := strings.ToLower(hugoEnv[i]), hugoEnv[i+1] + for _, env := range hugoEnv { + existing, nestedKey, owner, err := maps.GetNestedParamFn(env.Key, delim, v.Get) + if err != nil { + return v, configFiles, err + } - existing, nestedKey, owner, err := maps.GetNestedParamFn(key, "_", v.Get) + if existing != nil { + val, err := metadecoders.Default.UnmarshalStringTo(env.Value, existing) if err != nil { - return v, configFiles, err + continue } - if existing != nil { - val, err := metadecoders.Default.UnmarshalStringTo(valStr, existing) - if err != nil { - continue - } - - if owner != nil { - owner[nestedKey] = val - } else { - v.Set(key, val) - } - } else if nestedKey != "" { - owner[nestedKey] = valStr + if owner != nil { + owner[nestedKey] = val } else { - v.Set(key, valStr) + v.Set(env.Key, val) } + } else if nestedKey != "" { + owner[nestedKey] = env.Value + } else { + v.Set(env.Key, env.Value) } } + } // We made this a Glob pattern in Hugo 0.75, we don't need both. diff --git a/hugolib/config_test.go b/hugolib/config_test.go index cb9c1d8f6..e8dce331d 100644 --- a/hugolib/config_test.go +++ b/hugolib/config_test.go @@ -492,6 +492,11 @@ intSlice = [5,7,9] floatSlice = [3.14, 5.19] stringSlice = ["a", "b"] +[params] +[params.api_config] +api_key="default_key" +another_key="default another_key" + [imaging] anchor = "smart" quality = 75 @@ -508,6 +513,10 @@ quality = 75 "HUGO_STRINGSLICE", `["c", "d"]`, "HUGO_INTSLICE", `[5, 8, 9]`, "HUGO_FLOATSLICE", `[5.32]`, + // https://github.com/gohugoio/hugo/issues/7829 + "HUGOxPARAMSxAPI_CONFIGxAPI_KEY", "new_key", + // Delimiters are case sensitive. + "HUGOxPARAMSxAPI_CONFIGXANOTHER_KEY", "another_key", ) b.Build(BuildCfg{}) @@ -523,5 +532,7 @@ quality = 75 c.Assert(cfg.Get("stringSlice"), qt.DeepEquals, []interface{}{"c", "d"}) c.Assert(cfg.Get("floatSlice"), qt.DeepEquals, []interface{}{5.32}) c.Assert(cfg.Get("intSlice"), qt.DeepEquals, []interface{}{5, 8, 9}) + c.Assert(cfg.Get("params.api_config.api_key"), qt.Equals, "new_key") + c.Assert(cfg.Get("params.api_config.another_key"), qt.Equals, "default another_key") }