mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-22 16:28:30 +02:00
feat(yaml): extend environment variable substitution to all YAML files
- returns error for unset environment variables
This commit is contained in:
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -216,23 +215,10 @@ func (cfg *Config) StartServers(opts ...*StartServersOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY}
|
|
||||||
var readFile = os.ReadFile
|
|
||||||
|
|
||||||
func (cfg *Config) readConfigFile() ([]byte, error) {
|
|
||||||
data, err := readFile(common.ConfigPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return envRegex.ReplaceAllFunc(data, func(match []byte) []byte {
|
|
||||||
return strconv.AppendQuote(nil, os.Getenv(string(match[2:len(match)-1])))
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cfg *Config) load() gperr.Error {
|
func (cfg *Config) load() gperr.Error {
|
||||||
const errMsg = "config load error"
|
const errMsg = "config load error"
|
||||||
|
|
||||||
data, err := cfg.readConfigFile()
|
data, err := os.ReadFile(common.ConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Warn().Msg("config file not found, using default config")
|
log.Warn().Msg("config file not found, using default config")
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestConfigEnvSubstitution(t *testing.T) {
|
|
||||||
os.Setenv("CLOUDFLARE_AUTH_TOKEN", "test")
|
|
||||||
readFile = func(_ string) ([]byte, error) {
|
|
||||||
return []byte(`
|
|
||||||
---
|
|
||||||
autocert:
|
|
||||||
email: "test@test.com"
|
|
||||||
domains:
|
|
||||||
- "*.test.com"
|
|
||||||
provider: cloudflare
|
|
||||||
options:
|
|
||||||
auth_token: ${CLOUDFLARE_AUTH_TOKEN}
|
|
||||||
`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var cfg Config
|
|
||||||
out, err := cfg.readConfigFile()
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, `
|
|
||||||
---
|
|
||||||
autocert:
|
|
||||||
email: "test@test.com"
|
|
||||||
domains:
|
|
||||||
- "*.test.com"
|
|
||||||
provider: cloudflare
|
|
||||||
options:
|
|
||||||
auth_token: "test"
|
|
||||||
`, string(out))
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -517,7 +518,22 @@ func ConvertString(src string, dst reflect.Value) (convertible bool, convErr gpe
|
|||||||
return true, Convert(reflect.ValueOf(tmp), dst, true)
|
return true, Convert(reflect.ValueOf(tmp), dst, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var envRegex = regexp.MustCompile(`\$\{([^}]+)\}`) // e.g. ${CLOUDFLARE_API_KEY}
|
||||||
|
|
||||||
func UnmarshalValidateYAML[T any](data []byte, target *T) gperr.Error {
|
func UnmarshalValidateYAML[T any](data []byte, target *T) gperr.Error {
|
||||||
|
envError := gperr.NewBuilder("env substitution error")
|
||||||
|
data = envRegex.ReplaceAllFunc(data, func(match []byte) []byte {
|
||||||
|
varName := string(match[2 : len(match)-1])
|
||||||
|
env, ok := os.LookupEnv(varName)
|
||||||
|
if !ok {
|
||||||
|
envError.Addf("%s is not set", varName)
|
||||||
|
}
|
||||||
|
return strconv.AppendQuote(nil, env)
|
||||||
|
})
|
||||||
|
if envError.HasError() {
|
||||||
|
return envError.Error()
|
||||||
|
}
|
||||||
|
|
||||||
m := make(map[string]any)
|
m := make(map[string]any)
|
||||||
if err := yaml.Unmarshal(data, &m); err != nil {
|
if err := yaml.Unmarshal(data, &m); err != nil {
|
||||||
return gperr.Wrap(err)
|
return gperr.Wrap(err)
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package serialization
|
package serialization
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -314,6 +316,26 @@ func TestStringToStruct(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigEnvSubstitution(t *testing.T) {
|
||||||
|
os.Setenv("CLOUDFLARE_AUTH_TOKEN", "test")
|
||||||
|
data := []byte(`
|
||||||
|
---
|
||||||
|
autocert:
|
||||||
|
options:
|
||||||
|
auth_token: ${CLOUDFLARE_AUTH_TOKEN}
|
||||||
|
`)
|
||||||
|
|
||||||
|
var cfg struct {
|
||||||
|
Autocert struct {
|
||||||
|
Options struct {
|
||||||
|
AuthToken string `yaml:"auth_token"`
|
||||||
|
} `yaml:"options"`
|
||||||
|
} `yaml:"autocert"`
|
||||||
|
}
|
||||||
|
require.NoError(t, UnmarshalValidateYAML(data, &cfg))
|
||||||
|
require.Equal(t, "test", cfg.Autocert.Options.AuthToken)
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkStringToStruct(b *testing.B) {
|
func BenchmarkStringToStruct(b *testing.B) {
|
||||||
for range b.N {
|
for range b.N {
|
||||||
dst := struct {
|
dst := struct {
|
||||||
|
|||||||
Reference in New Issue
Block a user