86 lines
2.6 KiB
Go
86 lines
2.6 KiB
Go
package config
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"reflect"
|
|
|
|
"github.com/mitchellh/mapstructure"
|
|
)
|
|
|
|
// SecretValue defines what types of secrets this secret container supports
|
|
type SecretValue interface {
|
|
~string | ~[]string | ~map[string]string
|
|
}
|
|
|
|
// SecretFilePath is the source to the file containing the secret.
|
|
type SecretFilePath string
|
|
|
|
const SecretPlaceholder = "[SECRET]"
|
|
|
|
// Secret encapsulates a secret value, which is loaded from a file, or directly from configuration / environment variable
|
|
type Secret[T SecretValue] struct {
|
|
value T
|
|
source SecretFilePath
|
|
}
|
|
|
|
// MarshalJSON implements the json.Marshaler interface, used to hide the actual secret value in logs etc.
|
|
func (s Secret[T]) MarshalJSON() ([]byte, error) {
|
|
return json.Marshal(map[string]string{"value": SecretPlaceholder, "source": string(s.source)})
|
|
}
|
|
|
|
// MarshalText implements the encoding.TextMarshaler interface, used to hide the actual secret value in logs etc.
|
|
func (s *Secret[T]) MarshalText() ([]byte, error) {
|
|
return []byte(SecretPlaceholder), nil
|
|
}
|
|
|
|
// UnmarshalText implements the encoding.TextUnmarshaler interface, used to load the secret from config files and env. values
|
|
func (s *Secret[T]) UnmarshalText(text []byte) error {
|
|
var val T
|
|
switch any(val).(type) {
|
|
case string:
|
|
s.value = any(string(text)).(T)
|
|
return nil
|
|
default:
|
|
return json.Unmarshal(text, &s.value)
|
|
}
|
|
}
|
|
|
|
func (s Secret[T]) Value() T {
|
|
return s.value
|
|
}
|
|
|
|
func SecretFromSecretPath[T SecretValue](path SecretFilePath) (Secret[T], error) {
|
|
contentBytes, err := os.ReadFile(string(path))
|
|
if err != nil {
|
|
return Secret[T]{}, err
|
|
}
|
|
secret := Secret[T]{source: path}
|
|
err = secret.UnmarshalText(bytes.TrimSpace(contentBytes))
|
|
return secret, err
|
|
}
|
|
|
|
func SecretFromValue[T SecretValue](value T) Secret[T] {
|
|
return Secret[T]{value: value}
|
|
}
|
|
|
|
// SecretFilePathUnmarshalHookFunc is a mapstructure.DecodeHookFunc that will convert a SecretFilePath to a Secret
|
|
func SecretFilePathUnmarshalHookFunc() mapstructure.DecodeHookFuncType {
|
|
return func(from, to reflect.Type, data interface{}) (interface{}, error) {
|
|
if from != reflect.TypeOf(SecretFilePath("")) {
|
|
return data, nil
|
|
}
|
|
// Reflection does not work with generics as of 1.20, so we have to do this manually
|
|
if to == reflect.TypeOf(Secret[map[string]string]{}) {
|
|
return SecretFromSecretPath[map[string]string](data.(SecretFilePath))
|
|
} else if to == reflect.TypeOf(Secret[[]string]{}) {
|
|
return SecretFromSecretPath[[]string](data.(SecretFilePath))
|
|
} else if to == reflect.TypeOf(Secret[string]{}) {
|
|
return SecretFromSecretPath[string](data.(SecretFilePath))
|
|
} else {
|
|
return data, nil
|
|
}
|
|
}
|
|
}
|