commit 5b6f975d160bfd8e4c50e7d4dd7d9f4900a2cce6
parent eb753d2a89ba8b323904500b25c0c6a332772dcb
Author: Sean Enck <sean@ttypty.com>
Date: Tue, 8 Oct 2024 18:15:40 -0400
switch off go-envparse, cleanup config/shell input handling a bit
Diffstat:
10 files changed, 170 insertions(+), 50 deletions(-)
diff --git a/cmd/main.go b/cmd/main.go
@@ -2,7 +2,6 @@
package main
import (
- "bytes"
"errors"
"fmt"
"os"
@@ -10,8 +9,6 @@ import (
"strings"
"time"
- env "github.com/hashicorp/go-envparse"
-
"github.com/seanenck/lockbox/internal/app"
"github.com/seanenck/lockbox/internal/config"
"github.com/seanenck/lockbox/internal/platform"
@@ -48,35 +45,8 @@ func run() error {
if err != nil {
return err
}
- for _, useEnv := range paths {
- if !platform.PathExists(useEnv) {
- continue
- }
- b, err := os.ReadFile(useEnv)
- if err != nil {
- return err
- }
- r := bytes.NewReader(b)
- found, err := env.Parse(r)
- if err != nil {
- return err
- }
- result, err := config.ExpandParsed(found)
- if err != nil {
- return err
- }
- for k, v := range result {
- ok, err := config.IsUnset(k, v)
- if err != nil {
- return err
- }
- if !ok {
- if err := os.Setenv(k, v); err != nil {
- return err
- }
- }
- }
- break
+ if err := platform.LoadEnvConfigs(paths...); err != nil {
+ return err
}
args := os.Args
if len(args) < 2 {
diff --git a/go.mod b/go.mod
@@ -4,7 +4,6 @@ go 1.23.0
require (
github.com/aymanbagabas/go-osc52 v1.2.2
- github.com/hashicorp/go-envparse v0.1.0
github.com/pquerna/otp v1.4.0
github.com/tobischo/gokeepasslib/v3 v3.6.0
golang.org/x/text v0.19.0
diff --git a/go.sum b/go.sum
@@ -10,8 +10,6 @@ github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
-github.com/hashicorp/go-envparse v0.1.0 h1:bE++6bhIsNCPLvgDZkYqo3nA+/PFI51pkrHdmPSDFPY=
-github.com/hashicorp/go-envparse v0.1.0/go.mod h1:OHheN1GoygLlAkTlXLXvAdnXdZxy8JUweQ1rAXx1xnc=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
diff --git a/internal/app/core_test.go b/internal/app/core_test.go
@@ -13,7 +13,7 @@ func TestUsage(t *testing.T) {
t.Errorf("invalid usage, out of date? %d", len(u))
}
u, _ = app.Usage(true, "lb")
- if len(u) != 115 {
+ if len(u) != 116 {
t.Errorf("invalid verbose usage, out of date? %d", len(u))
}
for _, usage := range u {
diff --git a/internal/config/core.go b/internal/config/core.go
@@ -135,7 +135,11 @@ func (e environmentBase) Key() string {
// Get will get the boolean value for the setting
func (e EnvironmentBool) Get() (bool, error) {
- read := strings.ToLower(strings.TrimSpace(getExpand(e.Key())))
+ return parseStringYesNo(e, getExpand(e.Key()))
+}
+
+func parseStringYesNo(e EnvironmentBool, in string) (bool, error) {
+ read := strings.ToLower(strings.TrimSpace(in))
switch read {
case no:
return false, nil
@@ -361,6 +365,18 @@ func Environ() []string {
return results
}
+func parseConfigKeyEarly[T any](env interface {
+ Key() string
+ Get() (T, error)
+}, inputs map[string]string, conv func(string) (T, error),
+) (T, error) {
+ raw, ok := inputs[env.Key()]
+ if ok {
+ return conv(raw)
+ }
+ return env.Get()
+}
+
// ExpandParsed handles cycles of parsing configuration env inputs to resolve ALL variables
func ExpandParsed(inputs map[string]string) (map[string]string, error) {
if inputs == nil {
@@ -369,14 +385,13 @@ func ExpandParsed(inputs map[string]string) (map[string]string, error) {
if len(inputs) == 0 {
return inputs, nil
}
- var err error
- var cycles int
- possibleCycles, ok := inputs[envConfigExpands.Key()]
- if ok {
- cycles, err = strconv.Atoi(possibleCycles)
- } else {
- cycles, err = envConfigExpands.Get()
+ cycles, err := parseConfigKeyEarly(envConfigExpands, inputs, strconv.Atoi)
+ if err != nil {
+ return nil, err
}
+ quoted, err := parseConfigKeyEarly(envConfigQuoted, inputs, func(v string) (bool, error) {
+ return parseStringYesNo(envConfigQuoted, v)
+ })
if err != nil {
return nil, err
}
@@ -385,7 +400,7 @@ func ExpandParsed(inputs map[string]string) (map[string]string, error) {
}
result := inputs
for cycles > 0 {
- expanded := expandParsed(result)
+ expanded := expandParsed(result, quoted)
if len(expanded) == len(result) {
same := true
for k, v := range expanded {
@@ -409,12 +424,18 @@ func ExpandParsed(inputs map[string]string) (map[string]string, error) {
return nil, errors.New("reached maximum expand cycle count")
}
-func expandParsed(inputs map[string]string) map[string]string {
+func expandParsed(inputs map[string]string, quoted bool) map[string]string {
result := make(map[string]string)
for k, v := range inputs {
- result[k] = os.Expand(v, func(in string) string {
- if val, ok := inputs[in]; ok {
- return val
+ val := v
+ if quoted {
+ if strings.HasPrefix(val, "\"") && strings.HasSuffix(val, "\"") {
+ val = strings.TrimPrefix(strings.TrimSuffix(val, "\""), "\"")
+ }
+ }
+ result[k] = os.Expand(val, func(in string) string {
+ if i, ok := inputs[in]; ok {
+ return i
}
return os.Getenv(in)
})
diff --git a/internal/config/core_test.go b/internal/config/core_test.go
@@ -231,6 +231,33 @@ func TestExpandParsed(t *testing.T) {
if err != nil || len(r) != 2 || r["TEST"] != "$TEST_ABC" {
t.Errorf("invalid expand: %v", r)
}
+ ins["LOCKBOX_ENV_EXPANDS"] = "5"
+ ins["TEST"] = "\"abc\""
+ os.Setenv("LOCKBOX_ENV_QUOTED", "yes")
+ r, err = config.ExpandParsed(ins)
+ if err != nil || len(r) != 2 || r["TEST"] != "abc" {
+ t.Errorf("invalid expand: %v", r)
+ }
+ os.Setenv("LOCKBOX_ENV_QUOTED", "no")
+ ins["TEST"] = "\"abc\""
+ r, err = config.ExpandParsed(ins)
+ if err != nil || len(r) != 2 || r["TEST"] != "\"abc\"" {
+ t.Errorf("invalid expand: %v", r)
+ }
+ os.Unsetenv("LOCKBOX_ENV_QUOTED")
+ r, err = config.ExpandParsed(ins)
+ if err != nil || len(r) != 2 || r["TEST"] != "abc" {
+ t.Errorf("invalid expand: %v", r)
+ }
+ ins["LOCKBOX_ENV_QUOTED"] = "yes"
+ r, err = config.ExpandParsed(ins)
+ if err != nil || len(r) != 3 || r["TEST"] != "abc" {
+ t.Errorf("invalid expand: %v", r)
+ }
+ ins["LOCKBOX_ENV_QUOTED"] = "1"
+ if _, err = config.ExpandParsed(ins); err == nil || err.Error() != "invalid yes/no env value for LOCKBOX_ENV_QUOTED" {
+ t.Errorf("invalid error: %v", err)
+ }
}
func TestWrap(t *testing.T) {
diff --git a/internal/config/vars.go b/internal/config/vars.go
@@ -314,6 +314,14 @@ Set to '%s' to ignore the set key value`, noKeyMode, IgnoreKeyMode),
allowed: []string{commandArgsExample, "password"},
canDefault: false,
})
+ envConfigQuoted = environmentRegister(
+ EnvironmentBool{
+ environmentDefault: newDefaultedEnvironment(true,
+ environmentBase{
+ subKey: EnvConfig.subKey + "_QUOTED",
+ desc: "Enables removing prefix/suffix quotes from shell environment config settings\nwhen loaded through configuration file.",
+ }),
+ })
envConfigExpands = environmentRegister(
EnvironmentInt{
environmentDefault: newDefaultedEnvironment(20,
diff --git a/internal/config/vars_test.go b/internal/config/vars_test.go
@@ -105,7 +105,7 @@ func TestListVariables(t *testing.T) {
known[trim] = struct{}{}
}
l := len(known)
- if l != 33 {
+ if l != 34 {
t.Errorf("invalid env count, outdated? %d", l)
}
}
diff --git a/internal/platform/os.go b/internal/platform/os.go
@@ -9,6 +9,9 @@ import (
"os"
"strings"
"syscall"
+
+ "github.com/seanenck/lockbox/internal/config"
+ "mvdan.cc/sh/v3/expand"
)
func termEcho(on bool) {
@@ -138,3 +141,39 @@ func PathExists(file string) bool {
}
return true
}
+
+// LoadEnvConfigs load environment settings from configs
+func LoadEnvConfigs(paths ...string) error {
+ for _, useEnv := range paths {
+ if !PathExists(useEnv) {
+ continue
+ }
+ b, err := os.ReadFile(useEnv)
+ if err != nil {
+ return err
+ }
+ env := expand.ListEnviron(strings.Split(string(b), "\n")...)
+ found := make(map[string]string)
+ env.Each(func(name string, vr expand.Variable) bool {
+ found[name] = vr.String()
+ return true
+ })
+ result, err := config.ExpandParsed(found)
+ if err != nil {
+ return err
+ }
+ for k, v := range result {
+ ok, err := config.IsUnset(k, v)
+ if err != nil {
+ return err
+ }
+ if !ok {
+ if err := os.Setenv(k, v); err != nil {
+ return err
+ }
+ }
+ }
+ break
+ }
+ return nil
+}
diff --git a/internal/platform/os_test.go b/internal/platform/os_test.go
@@ -1,8 +1,10 @@
package platform_test
import (
+ "fmt"
"os"
"path/filepath"
+ "strings"
"testing"
"github.com/seanenck/lockbox/internal/platform"
@@ -19,3 +21,59 @@ func TestPathExist(t *testing.T) {
t.Error("test dir SHOULD exist")
}
}
+
+func TestLoadEnvConfigs(t *testing.T) {
+ os.Clearenv()
+ defer os.Clearenv()
+ files := []string{filepath.Join("testdata", "xyz"), filepath.Join("testdata", "abc")}
+ if err := platform.LoadEnvConfigs(files...); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ cfg := files[1]
+ os.WriteFile(cfg, []byte(`
+TEST_X=1
+TEST_Y=2
+TEST_Z="1
+TEST11=1"
+TEST_3="abc $HOME $X $TEST_X"`), 0o644)
+ if err := platform.LoadEnvConfigs(files...); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ env := fmt.Sprintf("%v", os.Environ())
+ verify := func(expects []string) {
+ for _, e := range expects {
+ if !strings.Contains(env, e) {
+ t.Errorf("invalid env: %s (missing '%s')", env, e)
+ }
+ }
+ }
+ verify([]string{"TEST_X=1", "TEST_Y=2", "TEST_3=abc 1", "TEST_Z=\"1", "TEST11=1\""})
+ os.Setenv("HOME", "a123")
+ if err := platform.LoadEnvConfigs(files...); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ env = fmt.Sprintf("%v", os.Environ())
+ verify([]string{"TEST_X=1", "TEST_Y=2", "TEST_3=abc a123 1", "TEST_Z=\"1", "TEST11=1\""})
+ os.Setenv("XYZ", "xyz")
+ os.Setenv("HOME", "$TEST4")
+ os.Setenv("TEST4", "$XYZ")
+ if err := platform.LoadEnvConfigs(files...); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ env = fmt.Sprintf("%v", os.Environ())
+ verify([]string{"TEST_X=1", "TEST_Y=2", "TEST_3=abc xyz 1", "TEST_Z=\"1", "TEST11=1\""})
+ final := filepath.Join("testdata", "zzz")
+ files = append(files, final)
+ os.Setenv("TEST4", "a")
+ os.WriteFile(final, []byte(`
+TEST_X=1
+TEST_Y=2
+TEST_Z="1
+TEST11=2"
+TEST_3="abc $HOME $X $TEST_X"`), 0o644)
+ if err := platform.LoadEnvConfigs(files...); err != nil {
+ t.Errorf("unexpected error: %v", err)
+ }
+ env = fmt.Sprintf("%v", os.Environ())
+ verify([]string{"TEST_X=1", "TEST_Y=2", "TEST_3=abc a 1", "TEST_Z=\"1", "TEST11=1\""})
+}