148 lines
4.1 KiB
Go
148 lines
4.1 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
var environmentPattern = regexp.MustCompile(`\$\{([A-Z_][A-Z0-9_]*)\}`)
|
|
|
|
type Resolver interface {
|
|
LookupEnv(string) (string, bool)
|
|
ReadFile(string) ([]byte, error)
|
|
}
|
|
|
|
type OSResolver struct{}
|
|
|
|
func (OSResolver) LookupEnv(name string) (string, bool) { return os.LookupEnv(name) }
|
|
func (OSResolver) ReadFile(path string) ([]byte, error) { return os.ReadFile(path) }
|
|
|
|
func LoadResolved(reader io.Reader, resolver Resolver) (*Config, error) {
|
|
if resolver == nil {
|
|
return nil, fmt.Errorf("resolve configuration: resolver is required")
|
|
}
|
|
cfg, err := decode(reader)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := expandValue(reflect.ValueOf(cfg).Elem(), resolver.LookupEnv); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := resolveSecretFiles(cfg, resolver); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := Validate(cfg); err != nil {
|
|
return nil, err
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func resolveSecretFiles(cfg *Config, resolver Resolver) error {
|
|
for _, auth := range []*Auth{&cfg.Gateway.Auth, &cfg.Distribution.Auth, &cfg.Admin.Auth} {
|
|
if err := resolveSecret(&auth.Password, &auth.PasswordFile, resolver); err != nil {
|
|
return err
|
|
}
|
|
if err := resolveSecret(&auth.Token, &auth.TokenFile, resolver); err != nil {
|
|
return err
|
|
}
|
|
for index := range auth.Methods {
|
|
method := &auth.Methods[index]
|
|
if err := resolveSecret(&method.Password, &method.PasswordFile, resolver); err != nil {
|
|
return err
|
|
}
|
|
if err := resolveSecret(&method.Value, &method.ValueFile, resolver); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
for name, upstream := range cfg.Upstreams {
|
|
if err := resolveSecret(&upstream.API.Auth.Password, &upstream.API.Auth.PasswordFile, resolver); err != nil {
|
|
return fmt.Errorf("resolve upstream %q api password: %w", name, err)
|
|
}
|
|
if err := resolveSecret(&upstream.API.Auth.Token, &upstream.API.Auth.TokenFile, resolver); err != nil {
|
|
return fmt.Errorf("resolve upstream %q api token: %w", name, err)
|
|
}
|
|
if err := resolveSecret(&upstream.API.Auth.Value, &upstream.API.Auth.ValueFile, resolver); err != nil {
|
|
return fmt.Errorf("resolve upstream %q api key: %w", name, err)
|
|
}
|
|
if err := resolveSecret(&upstream.ProxyAuth.Password, &upstream.ProxyAuth.PasswordFile, resolver); err != nil {
|
|
return fmt.Errorf("resolve upstream %q proxy password: %w", name, err)
|
|
}
|
|
cfg.Upstreams[name] = upstream
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resolveSecret(value, fileRef *string, resolver Resolver) error {
|
|
if *value != "" {
|
|
*fileRef = ""
|
|
return nil
|
|
}
|
|
if *fileRef == "" {
|
|
return nil
|
|
}
|
|
content, err := resolver.ReadFile(*fileRef)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resolved := strings.TrimRight(string(content), "\r\n")
|
|
if resolved == "" {
|
|
return fmt.Errorf("secret file %q is empty", *fileRef)
|
|
}
|
|
*value = resolved
|
|
*fileRef = ""
|
|
return nil
|
|
}
|
|
|
|
func expandValue(value reflect.Value, lookup func(string) (string, bool)) error {
|
|
switch value.Kind() {
|
|
case reflect.String:
|
|
expanded, err := expandEnvironment(value.String(), lookup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
value.SetString(expanded)
|
|
case reflect.Struct:
|
|
for index := 0; index < value.NumField(); index++ {
|
|
if err := expandValue(value.Field(index), lookup); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case reflect.Slice:
|
|
for index := 0; index < value.Len(); index++ {
|
|
if err := expandValue(value.Index(index), lookup); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
case reflect.Map:
|
|
iterator := value.MapRange()
|
|
for iterator.Next() {
|
|
item := reflect.New(value.Type().Elem()).Elem()
|
|
item.Set(iterator.Value())
|
|
if err := expandValue(item, lookup); err != nil {
|
|
return err
|
|
}
|
|
value.SetMapIndex(iterator.Key(), item)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func expandEnvironment(value string, lookup func(string) (string, bool)) (string, error) {
|
|
var resolveErr error
|
|
expanded := environmentPattern.ReplaceAllStringFunc(value, func(match string) string {
|
|
name := environmentPattern.FindStringSubmatch(match)[1]
|
|
resolved, ok := lookup(name)
|
|
if !ok {
|
|
resolveErr = fmt.Errorf("resolve configuration: environment variable %s is not set", name)
|
|
return match
|
|
}
|
|
return resolved
|
|
})
|
|
return expanded, resolveErr
|
|
}
|