155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
var secretNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]*$`)
|
|
|
|
type EnvLookup func(string) (string, bool)
|
|
|
|
type Environment struct {
|
|
values map[string]string
|
|
lookup EnvLookup
|
|
}
|
|
|
|
type SecretConflict struct {
|
|
Name string
|
|
}
|
|
|
|
type SecretLoadResult struct {
|
|
Environment Environment
|
|
Conflicts []SecretConflict
|
|
}
|
|
|
|
type ResolvedCredentials struct {
|
|
AccessKeyID string
|
|
SecretAccessKey string
|
|
}
|
|
|
|
func ProcessEnvironment() Environment {
|
|
return NewEnvironment(nil, os.LookupEnv)
|
|
}
|
|
|
|
func NewEnvironment(values map[string]string, lookup EnvLookup) Environment {
|
|
copied := make(map[string]string, len(values))
|
|
for key, value := range values {
|
|
copied[key] = value
|
|
}
|
|
if lookup == nil {
|
|
lookup = os.LookupEnv
|
|
}
|
|
return Environment{values: copied, lookup: lookup}
|
|
}
|
|
|
|
func (e Environment) Lookup(name string) (string, bool) {
|
|
if e.lookup != nil {
|
|
if value, ok := e.lookup(name); ok {
|
|
return value, true
|
|
}
|
|
}
|
|
value, ok := e.values[name]
|
|
return value, ok
|
|
}
|
|
|
|
func (e Environment) ResolveCredentials(creds Credentials) (ResolvedCredentials, error) {
|
|
var resolved ResolvedCredentials
|
|
var err error
|
|
if creds.AccessKeyIDEnv != "" {
|
|
resolved.AccessKeyID, err = e.required(creds.AccessKeyIDEnv)
|
|
if err != nil {
|
|
return ResolvedCredentials{}, err
|
|
}
|
|
}
|
|
if creds.SecretAccessKeyEnv != "" {
|
|
resolved.SecretAccessKey, err = e.required(creds.SecretAccessKeyEnv)
|
|
if err != nil {
|
|
return ResolvedCredentials{}, err
|
|
}
|
|
}
|
|
return resolved, nil
|
|
}
|
|
|
|
func (e Environment) required(name string) (string, error) {
|
|
value, ok := e.Lookup(name)
|
|
if !ok {
|
|
return "", fmt.Errorf("credential environment variable %s is not set", name)
|
|
}
|
|
if value == "" {
|
|
return "", fmt.Errorf("credential environment variable %s is empty", name)
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
func LoadSecretEnvironment(directory string, lookup EnvLookup) (SecretLoadResult, error) {
|
|
if directory == "" {
|
|
return SecretLoadResult{Environment: NewEnvironment(nil, lookup)}, nil
|
|
}
|
|
values, err := loadSecretValues(directory)
|
|
if err != nil {
|
|
return SecretLoadResult{}, err
|
|
}
|
|
var conflicts []SecretConflict
|
|
for name, value := range values {
|
|
if processValue, ok := lookupValue(lookup, name); ok && processValue != value {
|
|
conflicts = append(conflicts, SecretConflict{Name: name})
|
|
}
|
|
}
|
|
sort.Slice(conflicts, func(i, j int) bool {
|
|
return conflicts[i].Name < conflicts[j].Name
|
|
})
|
|
return SecretLoadResult{
|
|
Environment: NewEnvironment(values, lookup),
|
|
Conflicts: conflicts,
|
|
}, nil
|
|
}
|
|
|
|
func loadSecretValues(directory string) (map[string]string, error) {
|
|
entries, err := os.ReadDir(directory)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("load secrets directory %q: %w", directory, err)
|
|
}
|
|
values := make(map[string]string)
|
|
for _, entry := range entries {
|
|
path := filepath.Join(directory, entry.Name())
|
|
info, err := os.Stat(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("inspect secret file %q: %w", entry.Name(), err)
|
|
}
|
|
if info.IsDir() || !info.Mode().IsRegular() {
|
|
continue
|
|
}
|
|
if !secretNamePattern.MatchString(entry.Name()) {
|
|
return nil, fmt.Errorf("secret filename %q is invalid", entry.Name())
|
|
}
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read secret file %q: %w", entry.Name(), err)
|
|
}
|
|
values[entry.Name()] = trimOneTrailingLineEnding(string(data))
|
|
}
|
|
return values, nil
|
|
}
|
|
|
|
func trimOneTrailingLineEnding(value string) string {
|
|
if strings.HasSuffix(value, "\r\n") {
|
|
return strings.TrimSuffix(value, "\r\n")
|
|
}
|
|
if strings.HasSuffix(value, "\n") {
|
|
return strings.TrimSuffix(value, "\n")
|
|
}
|
|
return value
|
|
}
|
|
|
|
func lookupValue(lookup EnvLookup, name string) (string, bool) {
|
|
if lookup == nil {
|
|
lookup = os.LookupEnv
|
|
}
|
|
return lookup(name)
|
|
}
|