all repos

mugit @ b8bdd407050773cc92e004b87079f42d6f0ad318

🐮 git server that your cow will love
3 files changed, 93 insertions(+), 1 deletions(-)
config: $env: and $file: notations
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-02-13 00:07:29 +0200
Change ID: kuwtpkzyrplzpzynozotqmukxwwpkzol
Parent: 8703591
M internal/config/config.go

@@ -5,11 +5,16 @@ "errors"

"fmt" "os" "path/filepath" + "strings" "gopkg.in/yaml.v2" ) -var ErrConfigNotFound = errors.New("no config file found") +var ( + ErrConfigNotFound = errors.New("no config file found") + ErrUnsetEnv = errors.New("environment variable is not set") + ErrFileNotFound = errors.New("provided file path is invalid") +) type ServerConfig struct { Host string `yaml:"host"`

@@ -66,6 +71,10 @@ }

config.ensureDefaults() + if perr := config.parseValues(); perr != nil { + return nil, perr + } + if verr := config.validate(); verr != nil { return nil, verr }

@@ -131,6 +140,49 @@

// mirroring if c.Mirror.Interval == "" { c.Mirror.Interval = "8h" + } +} + +func (c *Config) parseValues() error { + if c.Mirror.Enable { + ghToken, err := parseValue(c.Mirror.GithubToken) + if err != nil { + return err + } + c.Mirror.GithubToken = ghToken + } + return nil +} + +func parseValue(value string) (string, error) { + envPrefix := "$env:" + filePrefix := "$file:" + + switch { + case strings.HasPrefix(value, envPrefix): + env := os.Getenv(os.ExpandEnv(value[len(envPrefix):])) + if env == "" { + return "", ErrUnsetEnv + } + return env, nil + + case strings.HasPrefix(value, filePrefix): + // supports only absolute paths + + fpath := value[len(filePrefix):] + if !isFileExists(fpath) { + return "", ErrFileNotFound + } + + data, err := os.ReadFile(fpath) + if err != nil { + return "", err + } + + return strings.TrimSpace(string(data)), nil + + default: + return value, nil } }
M internal/config/config_test.go

@@ -8,6 +8,45 @@

"olexsmir.xyz/x/is" ) +func TestConfig_parseValue(t *testing.T) { + def := "qwerty123" + + t.Run("string", func(t *testing.T) { + r, err := parseValue(def) + is.Err(t, err, nil) + is.Equal(t, r, def) + }) + + t.Run("env var", func(t *testing.T) { + t.Setenv("secret_value", "123") + r, err := parseValue("$env:secret_value") + is.Err(t, err, nil) + is.Equal(t, r, "123") + }) + + t.Run("unset env var", func(t *testing.T) { + _, err := parseValue("$env:secret_password") + is.Err(t, err, ErrUnsetEnv) + }) + + t.Run("file", func(t *testing.T) { + fpath, _ := filepath.Abs("./testdata/file_value") + r, err := parseValue("$file:" + fpath) + is.Err(t, err, nil) + is.Equal(t, r, def) + }) + + t.Run("non existing file", func(t *testing.T) { + _, err := parseValue("$file:/not/exists") + is.Err(t, err, ErrFileNotFound) + }) + + t.Run("file, not set path", func(t *testing.T) { + _, err := parseValue("$file:") + is.Err(t, err, ErrFileNotFound) + }) +} + func TestPathOrDefaultWithCandidates(t *testing.T) { first := candidateFile(t, "first.yaml") second := candidateFile(t, "second.yaml")
A internal/config/testdata/file_value

@@ -0,0 +1,1 @@

+qwerty123