- Merge plugin defaults, sorted YAML, SUMMER_ env, overrides and Set - Persist atomically to env overrides; Reload clears runtime Set - Discover HasConfig before Register and print layered hello values
125 lines
2.7 KiB
Go
125 lines
2.7 KiB
Go
package compass
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"github.com/knadh/koanf/providers/env/v2"
|
|
"github.com/knadh/koanf/v2"
|
|
)
|
|
|
|
const envPrefix = "SUMMER_"
|
|
|
|
func loadEnvVars(k *koanf.Koanf, environ []string) error {
|
|
if k == nil {
|
|
return nil
|
|
}
|
|
return k.Load(env.Provider(".", env.Opt{
|
|
Prefix: envPrefix,
|
|
EnvironFunc: func() []string {
|
|
if environ == nil {
|
|
return []string{}
|
|
}
|
|
return environ
|
|
},
|
|
TransformFunc: transformSummerKey,
|
|
}), nil)
|
|
}
|
|
|
|
func transformSummerKey(key, value string) (string, any) {
|
|
if key == "SUMMER_ENV" {
|
|
return "", nil
|
|
}
|
|
rest, ok := strings.CutPrefix(key, envPrefix)
|
|
if !ok || rest == "" {
|
|
return "", nil
|
|
}
|
|
parts := strings.Split(rest, "__")
|
|
for i, part := range parts {
|
|
parts[i] = strings.ToLower(part)
|
|
}
|
|
return strings.Join(parts, "."), value
|
|
}
|
|
|
|
func readDotEnv(path string) ([]string, error) {
|
|
raw, err := os.ReadFile(path)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil, nil
|
|
}
|
|
return nil, fmt.Errorf("compass: read .env: %w", err)
|
|
}
|
|
var out []string
|
|
for _, line := range strings.Split(string(raw), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || strings.HasPrefix(line, "#") {
|
|
continue
|
|
}
|
|
if after, ok := strings.CutPrefix(line, "export "); ok {
|
|
line = strings.TrimSpace(after)
|
|
}
|
|
key, val, ok := strings.Cut(line, "=")
|
|
if !ok {
|
|
continue
|
|
}
|
|
key = strings.TrimSpace(key)
|
|
if key == "" {
|
|
continue
|
|
}
|
|
val = strings.TrimSpace(val)
|
|
if len(val) >= 2 {
|
|
if (val[0] == '"' && val[len(val)-1] == '"') || (val[0] == '\'' && val[len(val)-1] == '\'') {
|
|
val = val[1 : len(val)-1]
|
|
}
|
|
}
|
|
out = append(out, key+"="+val)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func mergeEnviron(real, dotenv []string) []string {
|
|
present := make(map[string]struct{}, len(real))
|
|
out := make([]string, 0, len(real)+len(dotenv))
|
|
for _, kv := range real {
|
|
key, _, _ := strings.Cut(kv, "=")
|
|
present[key] = struct{}{}
|
|
out = append(out, kv)
|
|
}
|
|
for _, kv := range dotenv {
|
|
key, _, _ := strings.Cut(kv, "=")
|
|
if _, ok := present[key]; ok {
|
|
continue
|
|
}
|
|
out = append(out, kv)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func lookupEnv(environ []string, key string) string {
|
|
for i := len(environ) - 1; i >= 0; i-- {
|
|
k, v, ok := strings.Cut(environ[i], "=")
|
|
if ok && k == key {
|
|
return v
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func sanitizeEnv(env string) (string, error) {
|
|
if env == "" {
|
|
return "", fmt.Errorf("compass: environment name is empty")
|
|
}
|
|
if env == "." || env == ".." || strings.Contains(env, "..") || strings.ContainsAny(env, `/\`) {
|
|
return "", fmt.Errorf("compass: invalid environment name")
|
|
}
|
|
for _, r := range env {
|
|
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '-' || r == '_' {
|
|
continue
|
|
}
|
|
return "", fmt.Errorf("compass: invalid environment name")
|
|
}
|
|
return env, nil
|
|
}
|