mirror of
https://github.com/hanzo-fi/stack.git
synced 2026-10-30 20:34:43 +00:00
386 lines
8.8 KiB
Go
386 lines
8.8 KiB
Go
package settings
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"reflect"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/formancehq/go-libs/pointer"
|
|
"github.com/formancehq/operator/api/formance.com/v1beta1"
|
|
"github.com/formancehq/operator/internal/core"
|
|
"github.com/pkg/errors"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
)
|
|
|
|
func Get(ctx core.Context, stack string, keys ...string) (*string, error) {
|
|
allSettingsTargetingStack := &v1beta1.SettingsList{}
|
|
if err := ctx.GetClient().List(ctx, allSettingsTargetingStack, client.MatchingFields{
|
|
"stack": stack,
|
|
"keylen": fmt.Sprint(len(keys)),
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "listings settings")
|
|
}
|
|
|
|
allSettingsTargetingAllStacks := &v1beta1.SettingsList{}
|
|
if err := ctx.GetClient().List(ctx, allSettingsTargetingAllStacks, client.MatchingFields{
|
|
"stack": "*",
|
|
"keylen": fmt.Sprint(len(keys)),
|
|
}); err != nil {
|
|
return nil, errors.Wrap(err, "listings settings")
|
|
}
|
|
|
|
return findMatchingSettings(append(allSettingsTargetingStack.Items, allSettingsTargetingAllStacks.Items...), keys...)
|
|
}
|
|
|
|
func GetString(ctx core.Context, stack string, keys ...string) (*string, error) {
|
|
return Get(ctx, stack, keys...)
|
|
}
|
|
|
|
func GetStringOrDefault(ctx core.Context, stack, defaultValue string, keys ...string) (string, error) {
|
|
value, err := GetString(ctx, stack, keys...)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if value == nil {
|
|
return defaultValue, nil
|
|
}
|
|
return *value, nil
|
|
}
|
|
|
|
func GetStringOrEmpty(ctx core.Context, stack string, keys ...string) (string, error) {
|
|
return GetStringOrDefault(ctx, stack, "", keys...)
|
|
}
|
|
|
|
func GetStringSlice(ctx core.Context, stack string, keys ...string) ([]string, error) {
|
|
value, err := GetString(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, err
|
|
}
|
|
return strings.Split(*value, ","), nil
|
|
}
|
|
|
|
func RequireString(ctx core.Context, stack string, keys ...string) (string, error) {
|
|
value, err := GetString(ctx, stack, keys...)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if value == nil {
|
|
return "", core.NewMissingSettingsError(fmt.Sprintf("settings '%s' not found for stack '%s'", strings.Join(keys, "."), stack))
|
|
}
|
|
return *value, nil
|
|
}
|
|
|
|
func GetURL(ctx core.Context, stack string, keys ...string) (*v1beta1.URI, error) {
|
|
value, err := GetString(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
return v1beta1.ParseURL(*value)
|
|
}
|
|
|
|
func RequireURL(ctx core.Context, stack string, keys ...string) (*v1beta1.URI, error) {
|
|
value, err := RequireString(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return v1beta1.ParseURL(value)
|
|
}
|
|
|
|
func GetInt64(ctx core.Context, stack string, keys ...string) (*int64, error) {
|
|
value, err := Get(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
intValue, err := strconv.ParseInt(*value, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &intValue, nil
|
|
}
|
|
|
|
func GetInt32(ctx core.Context, stack string, keys ...string) (*int32, error) {
|
|
value, err := Get(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
intValue, err := strconv.ParseInt(*value, 10, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pointer.For(int32(intValue)), nil
|
|
}
|
|
|
|
func GetUInt64(ctx core.Context, stack string, keys ...string) (*uint64, error) {
|
|
value, err := Get(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
intValue, err := strconv.ParseUint(*value, 10, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &intValue, nil
|
|
}
|
|
|
|
func GetUInt16(ctx core.Context, stack string, keys ...string) (*uint16, error) {
|
|
value, err := Get(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
intValue, err := strconv.ParseUint(*value, 10, 16)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return pointer.For(uint16(intValue)), nil
|
|
}
|
|
|
|
func GetInt(ctx core.Context, stack string, keys ...string) (*int, error) {
|
|
value, err := GetInt64(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
return pointer.For(int(*value)), nil
|
|
}
|
|
|
|
func GetUInt(ctx core.Context, stack string, keys ...string) (*uint, error) {
|
|
value, err := GetUInt64(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
return pointer.For(uint(*value)), nil
|
|
}
|
|
|
|
func GetIntOrDefault(ctx core.Context, stack string, defaultValue int, keys ...string) (int, error) {
|
|
value, err := GetInt(ctx, stack, keys...)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if value == nil {
|
|
return defaultValue, nil
|
|
}
|
|
return *value, nil
|
|
}
|
|
|
|
func GetUInt16OrDefault(ctx core.Context, stack string, defaultValue uint16, keys ...string) (uint16, error) {
|
|
value, err := GetUInt16(ctx, stack, keys...)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if value == nil {
|
|
return defaultValue, nil
|
|
}
|
|
return *value, nil
|
|
}
|
|
|
|
func GetInt32OrDefault(ctx core.Context, stack string, defaultValue int32, keys ...string) (int32, error) {
|
|
value, err := GetInt32(ctx, stack, keys...)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if value == nil {
|
|
return defaultValue, nil
|
|
}
|
|
return *value, nil
|
|
}
|
|
|
|
func GetBool(ctx core.Context, stack string, keys ...string) (*bool, error) {
|
|
value, err := Get(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
return pointer.For(*value == "true"), nil
|
|
}
|
|
|
|
func GetBoolOrDefault(ctx core.Context, stack string, defaultValue bool, keys ...string) (bool, error) {
|
|
value, err := GetBool(ctx, stack, keys...)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if value == nil {
|
|
return defaultValue, nil
|
|
}
|
|
return *value, nil
|
|
}
|
|
|
|
func GetBoolOrFalse(ctx core.Context, stack string, keys ...string) (bool, error) {
|
|
return GetBoolOrDefault(ctx, stack, false, keys...)
|
|
}
|
|
|
|
func GetBoolOrTrue(ctx core.Context, stack string, keys ...string) (bool, error) {
|
|
return GetBoolOrDefault(ctx, stack, true, keys...)
|
|
}
|
|
|
|
func GetMap(ctx core.Context, stack string, keys ...string) (map[string]string, error) {
|
|
value, err := GetString(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return nil, nil
|
|
}
|
|
ret := make(map[string]string)
|
|
parts := strings.Split(*value, ",")
|
|
for _, part := range parts {
|
|
part := strings.TrimSpace(part)
|
|
parts := strings.SplitN(part, "=", 2)
|
|
ret[parts[0]] = parts[1]
|
|
}
|
|
|
|
return ret, nil
|
|
}
|
|
|
|
// TODO(gfyrag): GetAs only allow to map to structure containing only strings.
|
|
// With a bit of reflection, we could be able to have a more smart mapping to structure with usefull types.
|
|
func GetAs[T any](ctx core.Context, stack string, keys ...string) (*T, error) {
|
|
m, err := GetMap(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var ret T
|
|
ret = reflect.New(reflect.TypeOf(ret)).Elem().Interface().(T)
|
|
if m == nil {
|
|
return &ret, nil
|
|
}
|
|
|
|
data, err := json.Marshal(m)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &ret); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &ret, nil
|
|
}
|
|
|
|
func GetMapOrEmpty(ctx core.Context, stack string, keys ...string) (map[string]string, error) {
|
|
value, err := GetMap(ctx, stack, keys...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if value == nil {
|
|
return map[string]string{}, nil
|
|
}
|
|
|
|
return value, nil
|
|
}
|
|
|
|
func findMatchingSettings(settings []v1beta1.Settings, flattenKeys ...string) (*string, error) {
|
|
|
|
// Keys can be passed as "a.b.c", instead of "a", "b", "c"
|
|
// Keys can be passed as "a.b.*", instead of "a", "b", "*"
|
|
// Keys can be passed as "a.*.c", instead of "a", "*", "c"
|
|
// Keys can be passed as "a."*.*".c," instead of "a", "b", "c"
|
|
slices.SortFunc(settings, sortSettingsByPriority)
|
|
|
|
for _, setting := range settings {
|
|
if matchSetting(setting, flattenKeys...) {
|
|
return &setting.Spec.Value, nil
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func matchSetting(setting v1beta1.Settings, keys ...string) bool {
|
|
settingKeyParts := SplitKeywordWithDot(setting.Spec.Key)
|
|
for i, settingKeyPart := range settingKeyParts {
|
|
if settingKeyPart == "*" {
|
|
continue
|
|
}
|
|
if settingKeyPart != keys[i] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func SplitKeywordWithDot(key string) []string {
|
|
segments := ""
|
|
needQuote := false
|
|
for _, v := range key {
|
|
switch v {
|
|
case '"':
|
|
needQuote = !needQuote
|
|
case '.':
|
|
if !needQuote {
|
|
segments += " "
|
|
continue
|
|
}
|
|
segments += string(v)
|
|
default:
|
|
segments += string(v)
|
|
}
|
|
}
|
|
|
|
return strings.Split(segments, " ")
|
|
}
|
|
|
|
func sortSettingsByPriority(a, b v1beta1.Settings) int {
|
|
switch {
|
|
case a.IsWildcard() && !b.IsWildcard():
|
|
return 1
|
|
case !a.IsWildcard() && b.IsWildcard():
|
|
return -1
|
|
}
|
|
aKeys := SplitKeywordWithDot(a.Spec.Key)
|
|
bKeys := SplitKeywordWithDot(b.Spec.Key)
|
|
|
|
for i := 0; i < len(aKeys); i++ {
|
|
if aKeys[i] == bKeys[i] {
|
|
continue
|
|
}
|
|
if aKeys[i] == "*" {
|
|
return 1
|
|
}
|
|
if bKeys[i] == "*" {
|
|
return -1
|
|
}
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
func IsTrue(v string) bool {
|
|
return strings.ToLower(v) == "true" || v == "1"
|
|
}
|