mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:37:30 +00:00
remove unused tests
This commit is contained in:
@@ -1,74 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Version Version
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) Hooks() []viper.DecoderConfigOption {
|
|
||||||
return []viper.DecoderConfigOption{
|
|
||||||
viper.DecodeHook(decodeVersion),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) CurrentVersion() Version {
|
|
||||||
return c.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
var Path string
|
|
||||||
|
|
||||||
// InitConfig reads in config file and ENV variables if set.
|
|
||||||
func InitConfig() {
|
|
||||||
if Path != "" {
|
|
||||||
// Use config file from the flag.
|
|
||||||
viper.SetConfigFile(Path)
|
|
||||||
} else {
|
|
||||||
// Find home directory.
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
cobra.CheckErr(err)
|
|
||||||
|
|
||||||
// Search config in home directory with name ".zitadel" (without extension).
|
|
||||||
viper.AddConfigPath(home)
|
|
||||||
viper.SetConfigType("yaml")
|
|
||||||
viper.SetConfigName(".zitadel")
|
|
||||||
}
|
|
||||||
|
|
||||||
viper.AutomaticEnv() // read in environment variables that match
|
|
||||||
viper.SetEnvPrefix("ZITADEL")
|
|
||||||
|
|
||||||
// If a config file is found, read it in.
|
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
|
||||||
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Version uint8
|
|
||||||
|
|
||||||
const (
|
|
||||||
VersionUnknown Version = iota
|
|
||||||
V3
|
|
||||||
)
|
|
||||||
|
|
||||||
func decodeVersion(from, to reflect.Value) (_ interface{}, err error) {
|
|
||||||
if to.Type() != reflect.TypeOf(Version(0)) {
|
|
||||||
return from.Interface(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
switch from.Interface().(string) {
|
|
||||||
case "":
|
|
||||||
return VersionUnknown, nil
|
|
||||||
case "v3":
|
|
||||||
return V3, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported version: %v", from.Interface())
|
|
||||||
}
|
|
@@ -1,278 +0,0 @@
|
|||||||
package bla
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestConfig struct {
|
|
||||||
API APIConfig `configure:""`
|
|
||||||
Database DatabaseOneOf `configure:"type=oneof"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIConfig struct {
|
|
||||||
Host string `configure:""`
|
|
||||||
Port uint16 `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseOneOf struct {
|
|
||||||
ConnectionString *string `configure:""`
|
|
||||||
Config *DatabaseConfig `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
Host string `configure:""`
|
|
||||||
Port uint16 `configure:""`
|
|
||||||
SSLMode string `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config interface {
|
|
||||||
Hooks() []viper.DecoderConfigOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func Update(v *viper.Viper, config any) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
err := handleStruct(v, reflect.ValueOf(config), configuration{})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err = v.WriteConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagName = "configure"
|
|
||||||
defaultKey = "default"
|
|
||||||
oneOfKey = "oneof"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handleStruct(v *viper.Viper, val reflect.Value, config configuration) error {
|
|
||||||
val = reflect.Indirect(val)
|
|
||||||
if val.Type().Kind() != reflect.Struct {
|
|
||||||
return errors.New("object is not a struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range val.NumField() {
|
|
||||||
if err := handleField(v, val.Type().Field(i), val.Field(i)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handlePrimitive(field reflect.StructField, primitive reflect.Value, config configuration) error {
|
|
||||||
if config.customPrimitiveHandler != nil {
|
|
||||||
return config.customPrimitiveHandler(config.defaultValue)
|
|
||||||
}
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: fmt.Sprintf("%s: %s", field.Name, config.description),
|
|
||||||
Validate: func(s string) error {
|
|
||||||
_, err := mapValue(primitive.Type(), s)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if config.defaultValue != nil {
|
|
||||||
prompt.Default = fmt.Sprintf("%v", config.defaultValue)
|
|
||||||
}
|
|
||||||
res, err := prompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
value, _ := mapValue(primitive.Type(), res)
|
|
||||||
primitive.Set(reflect.ValueOf(value))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleField(v *viper.Viper, field reflect.StructField, value reflect.Value) error {
|
|
||||||
fmt.Println(field.Name)
|
|
||||||
fieldConfig, err := fieldConfiguration(field, v.Get(field.Name))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if fieldConfig.skip {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if potential pointer implements [OneOfField]
|
|
||||||
if value.Type().Implements(reflect.TypeFor[OneOfField]()) {
|
|
||||||
return handleOneOf(v, field, value, fieldConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
value = reflect.Indirect(value)
|
|
||||||
|
|
||||||
if value.IsZero() {
|
|
||||||
value = reflect.New(value.Type()).Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if potential non pointer value implements [OneOfField]
|
|
||||||
if value.Type().Implements(reflect.TypeFor[OneOfField]()) {
|
|
||||||
return handleOneOf(v, field, value, fieldConfig)
|
|
||||||
}
|
|
||||||
//nolint: exhaustive
|
|
||||||
// Default are all primitive types
|
|
||||||
switch value.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
if v.Get(field.Name) == nil {
|
|
||||||
defaultValue := fieldConfig.defaultValue
|
|
||||||
if defaultValue == nil {
|
|
||||||
defaultValue = map[string]any{}
|
|
||||||
}
|
|
||||||
v.Set(field.Name, defaultValue)
|
|
||||||
}
|
|
||||||
sub := v.Sub(field.Name)
|
|
||||||
if err := handleStruct(sub, value, fieldConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set(field.Name, sub.AllSettings())
|
|
||||||
return nil
|
|
||||||
case reflect.Uintptr, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Slice, reflect.UnsafePointer:
|
|
||||||
return fmt.Errorf("unsupported kind: %s", value.Type().Kind().String())
|
|
||||||
default:
|
|
||||||
if err := handlePrimitive(field, value, fieldConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set(field.Name, value.Interface())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleOneOf(v *viper.Viper, field reflect.StructField, value reflect.Value, config configuration) error {
|
|
||||||
possibilities := value.MethodByName("Possibilities").Call(nil)[0].Interface().([]string)
|
|
||||||
prompt := promptui.Select{
|
|
||||||
Label: config.description,
|
|
||||||
Items: possibilities,
|
|
||||||
}
|
|
||||||
|
|
||||||
idx, _, err := prompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedValue := value.MethodByName("ConfigForIndex").Call([]reflect.Value{reflect.ValueOf(idx)})[0].Interface()
|
|
||||||
return handleField(v, field, reflect.ValueOf(selectedValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneOfField interface {
|
|
||||||
Possibilities() []string
|
|
||||||
// ConfigForIndex returns a pointer to the field that was selected
|
|
||||||
ConfigForIndex(int) any
|
|
||||||
}
|
|
||||||
|
|
||||||
type configuration struct {
|
|
||||||
skip bool
|
|
||||||
defaultValue any
|
|
||||||
description string
|
|
||||||
|
|
||||||
isOneOf bool
|
|
||||||
oneOfValues []any
|
|
||||||
|
|
||||||
customPrimitiveHandler func(current any) error
|
|
||||||
customStructHandler func(*viper.Viper) error
|
|
||||||
customRepeatedHandler func(*viper.Viper) error
|
|
||||||
customOneOfHandler func(*viper.Viper) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func fieldConfiguration(field reflect.StructField, current any) (config configuration, err error) {
|
|
||||||
value, ok := field.Tag.Lookup(tagName)
|
|
||||||
if !ok {
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
if value == "-" {
|
|
||||||
config.skip = true
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(value, ",")
|
|
||||||
for _, f := range fields {
|
|
||||||
configSplit := strings.Split(f, "=")
|
|
||||||
switch strings.ToLower(configSplit[0]) {
|
|
||||||
case defaultKey:
|
|
||||||
config.defaultValue, err = mapValue(field.Type, configSplit[1])
|
|
||||||
if err != nil {
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
case oneOfKey:
|
|
||||||
config.isOneOf = true
|
|
||||||
oneOfValues := strings.Split(configSplit[1], "|")
|
|
||||||
for _, oneOfValue := range oneOfValues {
|
|
||||||
value, err := mapValue(field.Type, oneOfValue)
|
|
||||||
if err != nil {
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
config.oneOfValues = append(config.oneOfValues, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current != nil {
|
|
||||||
config.defaultValue = current
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapValue(typ reflect.Type, value string) (_ any, err error) {
|
|
||||||
var val any
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
val = value
|
|
||||||
case reflect.Bool:
|
|
||||||
val, err = strconv.ParseBool(value)
|
|
||||||
case reflect.Int:
|
|
||||||
val, err = strconv.Atoi(value)
|
|
||||||
case reflect.Int8:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 8)
|
|
||||||
val = int8(val.(int64))
|
|
||||||
case reflect.Int16:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 16)
|
|
||||||
val = int16(val.(int64))
|
|
||||||
case reflect.Int32:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 32)
|
|
||||||
val = int32(val.(int64))
|
|
||||||
case reflect.Int64:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case reflect.Uint:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 0)
|
|
||||||
val = uint(val.(uint64))
|
|
||||||
case reflect.Uint8:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 8)
|
|
||||||
val = uint8(val.(uint64))
|
|
||||||
case reflect.Uint16:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 16)
|
|
||||||
val = uint16(val.(uint64))
|
|
||||||
case reflect.Uint32:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 32)
|
|
||||||
val = uint32(val.(uint64))
|
|
||||||
case reflect.Uint64:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 64)
|
|
||||||
case reflect.Float32:
|
|
||||||
val, err = strconv.ParseFloat(value, 32)
|
|
||||||
val = float32(val.(float64))
|
|
||||||
case reflect.Float64:
|
|
||||||
val, err = strconv.ParseFloat(value, 64)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported type: %s", typ.Kind().String())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := reflect.ValueOf(val)
|
|
||||||
if !res.CanConvert(typ) {
|
|
||||||
return nil, fmt.Errorf("cannot convert %T to %s", val, typ.Kind().String())
|
|
||||||
}
|
|
||||||
return res.Convert(typ).Interface(), nil
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
package bla2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type primitive struct {
|
|
||||||
typ reflect.Type
|
|
||||||
|
|
||||||
tag fieldTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *primitive) defaultValue() any {
|
|
||||||
if p.tag.currentValue != nil {
|
|
||||||
return p.tag.currentValue
|
|
||||||
}
|
|
||||||
return reflect.Zero(p.typ).Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *primitive) label() string {
|
|
||||||
if p.tag.description == "" {
|
|
||||||
return p.tag.fieldName
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (%s)", p.tag.fieldName, p.tag.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *primitive) toPrompt() prompt {
|
|
||||||
return &linePrompt{
|
|
||||||
Prompt: promptui.Prompt{
|
|
||||||
Label: p.label(),
|
|
||||||
Default: fmt.Sprintf("%v", p.defaultValue()),
|
|
||||||
Validate: p.validateInput,
|
|
||||||
IsConfirm: p.typ.Kind() == reflect.Bool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func promptFromPrimitive(p *primitive) prompt {
|
|
||||||
return p.toPrompt()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *primitive) validateInput(s string) error {
|
|
||||||
_, err := mapValue(p.typ, s)
|
|
||||||
return err
|
|
||||||
}
|
|
@@ -1,61 +0,0 @@
|
|||||||
package bla2
|
|
||||||
|
|
||||||
import "github.com/manifoldco/promptui"
|
|
||||||
|
|
||||||
type prompt interface {
|
|
||||||
Run() (err error)
|
|
||||||
Result() string
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ prompt = (*selectPrompt)(nil)
|
|
||||||
_ prompt = (*selectWithAddPrompt)(nil)
|
|
||||||
_ prompt = (*linePrompt)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
type selectPrompt struct {
|
|
||||||
promptui.Select
|
|
||||||
|
|
||||||
selectedIndex int
|
|
||||||
selectedValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *selectPrompt) Run() (err error) {
|
|
||||||
p.selectedIndex, p.selectedValue, err = p.Select.Run()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *selectPrompt) Result() string {
|
|
||||||
return p.selectedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
type selectWithAddPrompt struct {
|
|
||||||
promptui.SelectWithAdd
|
|
||||||
|
|
||||||
selectedIndex int
|
|
||||||
selectedValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *selectWithAddPrompt) Run() (err error) {
|
|
||||||
p.selectedIndex, p.selectedValue, err = p.SelectWithAdd.Run()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *selectWithAddPrompt) Result() string {
|
|
||||||
return p.selectedValue
|
|
||||||
}
|
|
||||||
|
|
||||||
type linePrompt struct {
|
|
||||||
promptui.Prompt
|
|
||||||
|
|
||||||
selectedValue string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *linePrompt) Run() (err error) {
|
|
||||||
p.selectedValue, err = p.Prompt.Run()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *linePrompt) Result() string {
|
|
||||||
return p.selectedValue
|
|
||||||
}
|
|
@@ -1,112 +0,0 @@
|
|||||||
package bla2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type object struct {
|
|
||||||
value reflect.Value
|
|
||||||
viper *viper.Viper
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *object) fields() ([]*Field, error) {
|
|
||||||
fields := make([]*Field, 0, o.value.NumField())
|
|
||||||
for i := range o.value.NumField() {
|
|
||||||
if !o.value.CanSet() {
|
|
||||||
slog.Info("skipping field because it is not settable", slog.String("field", o.value.Type().Field(i).Name))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var currentValue any
|
|
||||||
if !o.value.Field(i).IsZero() {
|
|
||||||
currentValue = o.value.Field(i).Interface()
|
|
||||||
}
|
|
||||||
tag, err := newFieldTag(o.value.Type().Field(i), currentValue)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag.skip {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
field, err := o.field(o.value.Field(i), tag)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// field can be nil if the fields kind is not supported
|
|
||||||
if field != nil {
|
|
||||||
fields = append(fields, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fields, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *object) field(field reflect.Value, tag fieldTag) (*Field, error) {
|
|
||||||
switch field.Kind() {
|
|
||||||
case reflect.Bool,
|
|
||||||
reflect.Int,
|
|
||||||
reflect.Int8,
|
|
||||||
reflect.Int16,
|
|
||||||
reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint,
|
|
||||||
reflect.Uint8,
|
|
||||||
reflect.Uint16,
|
|
||||||
reflect.Uint32,
|
|
||||||
reflect.Uint64,
|
|
||||||
reflect.Uintptr,
|
|
||||||
reflect.Float32,
|
|
||||||
reflect.Float64,
|
|
||||||
reflect.Complex64,
|
|
||||||
reflect.Complex128,
|
|
||||||
reflect.String:
|
|
||||||
|
|
||||||
prompt := promptFromPrimitive(&primitive{
|
|
||||||
typ: field.Type(),
|
|
||||||
tag: tag,
|
|
||||||
})
|
|
||||||
return &Field{
|
|
||||||
Prompt: prompt,
|
|
||||||
Set: func() {
|
|
||||||
o.viper.Set(tag.fieldName, prompt.Result())
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
case reflect.Struct:
|
|
||||||
if o.viper.Get(tag.fieldName) == nil {
|
|
||||||
o.viper.Set(tag.fieldName, make(map[string]any))
|
|
||||||
}
|
|
||||||
sub := &object{
|
|
||||||
value: reflect.Indirect(field),
|
|
||||||
viper: o.viper.Sub(tag.fieldName),
|
|
||||||
}
|
|
||||||
subFields, err := sub.fields()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Field{
|
|
||||||
Sub: subFields,
|
|
||||||
Set: func() {
|
|
||||||
o.viper.Set(tag.fieldName, sub.viper.AllSettings())
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
case reflect.Pointer:
|
|
||||||
return o.field(field.Elem(), tag)
|
|
||||||
case reflect.Array, reflect.Interface, reflect.Map, reflect.Slice:
|
|
||||||
slog.Info(
|
|
||||||
"skipping field because type is not implemented",
|
|
||||||
slog.String("field", tag.fieldName),
|
|
||||||
slog.String("kind", field.Kind().String()),
|
|
||||||
)
|
|
||||||
case reflect.Chan, reflect.Func, reflect.UnsafePointer, reflect.Invalid:
|
|
||||||
slog.Info(
|
|
||||||
"skipping field because field type is invalid, add `configure=\"-\"` to skip",
|
|
||||||
slog.String("field", tag.fieldName),
|
|
||||||
slog.String("kind", field.Kind().String()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
@@ -1,53 +0,0 @@
|
|||||||
package bla2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldTag struct {
|
|
||||||
skip bool
|
|
||||||
|
|
||||||
fieldName string
|
|
||||||
description string
|
|
||||||
|
|
||||||
currentValue any
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagName = "configure"
|
|
||||||
defaultKey = "default"
|
|
||||||
descriptionKey = "description"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newFieldTag(field reflect.StructField, current any) (config fieldTag, err error) {
|
|
||||||
config.fieldName = field.Name
|
|
||||||
|
|
||||||
value, ok := field.Tag.Lookup(tagName)
|
|
||||||
if !ok {
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
if value == "-" {
|
|
||||||
config.skip = true
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(value, ",")
|
|
||||||
for _, f := range fields {
|
|
||||||
configSplit := strings.Split(f, "=")
|
|
||||||
switch strings.ToLower(configSplit[0]) {
|
|
||||||
case defaultKey:
|
|
||||||
config.currentValue, err = mapValue(field.Type, configSplit[1])
|
|
||||||
if err != nil {
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
case descriptionKey:
|
|
||||||
config.description = configSplit[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if current != nil {
|
|
||||||
config.currentValue = current
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
@@ -1,146 +0,0 @@
|
|||||||
package bla2
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestConfig struct {
|
|
||||||
API APIConfig `configure:""`
|
|
||||||
Database DatabaseOneOf `configure:"type=oneof"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIConfig struct {
|
|
||||||
Host string `configure:""`
|
|
||||||
Port uint16 `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseOneOf struct {
|
|
||||||
ConnectionString *string `configure:""`
|
|
||||||
Config *DatabaseConfig `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
Host string `configure:""`
|
|
||||||
Port uint16 `configure:""`
|
|
||||||
SSLMode string `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config interface {
|
|
||||||
Hooks() []viper.DecoderConfigOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func Update(v *viper.Viper, config any) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
s := object{
|
|
||||||
value: reflect.Indirect(reflect.ValueOf(config)),
|
|
||||||
viper: v,
|
|
||||||
}
|
|
||||||
fields, err := s.fields()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
for _, field := range fields {
|
|
||||||
err = field.execute()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = v.WriteConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Field struct {
|
|
||||||
// Either Prompt or Sub must be set
|
|
||||||
Prompt prompt
|
|
||||||
Sub []*Field
|
|
||||||
Set func()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Field) execute() error {
|
|
||||||
if f.Prompt != nil {
|
|
||||||
if err := f.Prompt.Run(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.Set()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for _, sub := range f.Sub {
|
|
||||||
err := sub.execute()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sub.Set()
|
|
||||||
}
|
|
||||||
f.Set()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapValue(typ reflect.Type, value string) (v reflect.Value, err error) {
|
|
||||||
var val any
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
val = value
|
|
||||||
case reflect.Bool:
|
|
||||||
val, err = strconv.ParseBool(value)
|
|
||||||
case reflect.Int:
|
|
||||||
val, err = strconv.Atoi(value)
|
|
||||||
case reflect.Int8:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 8)
|
|
||||||
val = int8(val.(int64))
|
|
||||||
case reflect.Int16:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 16)
|
|
||||||
val = int16(val.(int64))
|
|
||||||
case reflect.Int32:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 32)
|
|
||||||
val = int32(val.(int64))
|
|
||||||
case reflect.Int64:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case reflect.Uint:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 0)
|
|
||||||
val = uint(val.(uint64))
|
|
||||||
case reflect.Uint8:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 8)
|
|
||||||
val = uint8(val.(uint64))
|
|
||||||
case reflect.Uint16:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 16)
|
|
||||||
val = uint16(val.(uint64))
|
|
||||||
case reflect.Uint32:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 32)
|
|
||||||
val = uint32(val.(uint64))
|
|
||||||
case reflect.Uint64:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 64)
|
|
||||||
case reflect.Float32:
|
|
||||||
val, err = strconv.ParseFloat(value, 32)
|
|
||||||
val = float32(val.(float64))
|
|
||||||
case reflect.Float64:
|
|
||||||
val, err = strconv.ParseFloat(value, 64)
|
|
||||||
case reflect.Complex64:
|
|
||||||
val, err = strconv.ParseComplex(value, 64)
|
|
||||||
val = complex64(val.(complex128))
|
|
||||||
case reflect.Complex128:
|
|
||||||
val, err = strconv.ParseComplex(value, 128)
|
|
||||||
default:
|
|
||||||
return v, fmt.Errorf("unsupported type: %s", typ.Kind().String())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := reflect.ValueOf(val)
|
|
||||||
if !res.CanConvert(typ) {
|
|
||||||
return v, fmt.Errorf("cannot convert %T to %s", val, typ.Kind().String())
|
|
||||||
}
|
|
||||||
return res.Convert(typ), nil
|
|
||||||
}
|
|
@@ -1,183 +0,0 @@
|
|||||||
package bla3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var customType = reflect.TypeFor[Custom]()
|
|
||||||
|
|
||||||
type Custom interface {
|
|
||||||
Configure() (value any, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Object struct {
|
|
||||||
value reflect.Value
|
|
||||||
|
|
||||||
tag fieldTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewObject(value any) Object {
|
|
||||||
return Object{
|
|
||||||
value: reflect.Indirect(reflect.ValueOf(value)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Object) Configure(v *viper.Viper) error {
|
|
||||||
if o.tag.label() != "" {
|
|
||||||
fmt.Println("\n", o.tag.label())
|
|
||||||
}
|
|
||||||
for i := range o.value.NumField() {
|
|
||||||
if !o.value.Type().Field(i).IsExported() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
structField := o.value.Field(i)
|
|
||||||
|
|
||||||
f := Field{
|
|
||||||
value: structField,
|
|
||||||
structField: o.value.Type().Field(i),
|
|
||||||
}
|
|
||||||
err := f.Configure(v)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Field struct {
|
|
||||||
value reflect.Value
|
|
||||||
|
|
||||||
structField reflect.StructField
|
|
||||||
|
|
||||||
tag fieldTag
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewField(value any) Field {
|
|
||||||
return Field{
|
|
||||||
value: reflect.ValueOf(value),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Field) validate(input string) (err error) {
|
|
||||||
configuredValue, err := mapValue(f.value.Type(), input)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.value.Set(configuredValue)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Field) callCustom(v *viper.Viper) (ok bool, err error) {
|
|
||||||
if !f.value.Type().Implements(customType) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
custom := f.value.Interface().(Custom)
|
|
||||||
value, err := custom.Configure()
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
switch val := value.(type) {
|
|
||||||
case Field:
|
|
||||||
val.tag, err = newFieldTag(f.structField, val.value.Interface())
|
|
||||||
val.structField = f.structField
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
return true, val.Configure(v)
|
|
||||||
default:
|
|
||||||
v.Set(f.tag.fieldName, val)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *Field) Configure(v *viper.Viper) error {
|
|
||||||
if f.value.IsNil() {
|
|
||||||
f.value.Set(reflect.New(f.value.Type().Elem()))
|
|
||||||
}
|
|
||||||
|
|
||||||
tag, err := newFieldTag(f.structField, f.value.Interface())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if tag.skip {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := f.callCustom(v); ok || err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
f.value = reflect.Indirect(f.value)
|
|
||||||
if ok, err := f.callCustom(v); ok || err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
kind := f.value.Kind()
|
|
||||||
switch kind {
|
|
||||||
case reflect.Bool:
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: f.tag.label(),
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
_, err := prompt.Run()
|
|
||||||
selected := true
|
|
||||||
if err != nil {
|
|
||||||
if err.Error() != "" {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
selected = false
|
|
||||||
}
|
|
||||||
f.value.SetBool(selected)
|
|
||||||
v.Set(f.tag.fieldName, f.value.Interface())
|
|
||||||
return nil
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
|
|
||||||
reflect.Float32, reflect.Float64, reflect.Complex64, reflect.Complex128, reflect.String:
|
|
||||||
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: f.tag.label(),
|
|
||||||
Validate: f.validate,
|
|
||||||
Default: f.tag.defaultValue(),
|
|
||||||
}
|
|
||||||
_, err := prompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set(f.tag.fieldName, f.value.Interface())
|
|
||||||
return nil
|
|
||||||
case reflect.Struct:
|
|
||||||
o := Object{
|
|
||||||
value: f.value,
|
|
||||||
tag: f.tag,
|
|
||||||
}
|
|
||||||
if !v.IsSet(f.tag.fieldName) {
|
|
||||||
v.Set(f.tag.fieldName, map[string]any{})
|
|
||||||
}
|
|
||||||
sub := v.Sub(f.tag.fieldName)
|
|
||||||
err := o.Configure(sub)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set(f.tag.fieldName, sub.AllSettings())
|
|
||||||
return nil
|
|
||||||
case reflect.Pointer:
|
|
||||||
if f.value.IsNil() {
|
|
||||||
f.value = reflect.New(f.value.Type().Elem())
|
|
||||||
}
|
|
||||||
f.value = f.value.Elem()
|
|
||||||
return f.Configure(v)
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Map:
|
|
||||||
slog.Warn("skipping because kind is unimplemented", slog.String("field", f.tag.fieldName), slog.String("kind", kind.String()))
|
|
||||||
return nil
|
|
||||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer, reflect.Invalid:
|
|
||||||
slog.Error("skipping because kind is unsupported", slog.String("field", f.tag.fieldName), slog.String("kind", kind.String()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,81 +0,0 @@
|
|||||||
package bla3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type fieldTag struct {
|
|
||||||
skip bool
|
|
||||||
|
|
||||||
fieldName string
|
|
||||||
description string
|
|
||||||
|
|
||||||
currentValue any
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagName = "configure"
|
|
||||||
defaultKey = "default"
|
|
||||||
descriptionKey = "description"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newFieldTag(field reflect.StructField, current any) (config fieldTag, err error) {
|
|
||||||
config.fieldName = field.Name
|
|
||||||
if current != nil {
|
|
||||||
config.currentValue = current
|
|
||||||
}
|
|
||||||
|
|
||||||
value, ok := field.Tag.Lookup(tagName)
|
|
||||||
if !ok {
|
|
||||||
if config.currentValue == nil {
|
|
||||||
config.currentValue = reflect.New(field.Type).Elem().Interface()
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
if value == "-" {
|
|
||||||
config.skip = true
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(value, ",")
|
|
||||||
for _, f := range fields {
|
|
||||||
configSplit := strings.Split(f, "=")
|
|
||||||
switch strings.ToLower(configSplit[0]) {
|
|
||||||
case defaultKey:
|
|
||||||
config.currentValue, err = mapValue(field.Type, configSplit[1])
|
|
||||||
if err != nil {
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
case descriptionKey:
|
|
||||||
config.description = configSplit[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if config.currentValue == nil {
|
|
||||||
config.currentValue = reflect.New(field.Type).Elem().Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tag fieldTag) label() string {
|
|
||||||
if tag.fieldName == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
valueType := reflect.TypeOf(tag.currentValue)
|
|
||||||
typ := valueType.Kind().String()
|
|
||||||
if typeParsers[valueType] != nil {
|
|
||||||
typ = valueType.Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
label := fmt.Sprintf("%s (%s)", tag.fieldName, typ)
|
|
||||||
if tag.description == "" {
|
|
||||||
return label
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (%s)", label, tag.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tag fieldTag) defaultValue() string {
|
|
||||||
return fmt.Sprintf("%v", tag.currentValue)
|
|
||||||
}
|
|
@@ -1,140 +0,0 @@
|
|||||||
package bla3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TestConfig struct {
|
|
||||||
API APIConfig `configure:""`
|
|
||||||
Database DatabaseOneOf `configure:"type=oneof"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type APIConfig struct {
|
|
||||||
Host string `configure:""`
|
|
||||||
Port uint16 `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseOneOf struct {
|
|
||||||
ConnectionString *string `configure:""`
|
|
||||||
Config *DatabaseConfig `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
Host string `configure:""`
|
|
||||||
Port uint16 `configure:""`
|
|
||||||
SSLMode string `configure:""`
|
|
||||||
}
|
|
||||||
|
|
||||||
func Update(v *viper.Viper, config any) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
s := NewObject(config)
|
|
||||||
if err := s.Configure(v); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
err := v.WriteConfig()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeParsers = map[reflect.Type]func(string) (any, error){
|
|
||||||
reflect.TypeFor[time.Duration](): func(value string) (any, error) {
|
|
||||||
return time.ParseDuration(value)
|
|
||||||
},
|
|
||||||
reflect.TypeFor[time.Time](): func(value string) (any, error) {
|
|
||||||
if t, err := time.Parse(time.DateTime, value); err == nil {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
if t, err := time.Parse(time.DateOnly, value); err == nil {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
if t, err := time.Parse(time.TimeOnly, value); err == nil {
|
|
||||||
return t, nil
|
|
||||||
}
|
|
||||||
return time.Parse(time.RFC3339, value)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetTypeParser[T any](fn func(string) (any, error)) {
|
|
||||||
typeParsers[reflect.TypeFor[T]()] = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapValue(typ reflect.Type, value string) (v reflect.Value, err error) {
|
|
||||||
if fn, ok := typeParsers[typ]; ok {
|
|
||||||
mappedValue, err := fn(value)
|
|
||||||
if err != nil {
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
res := reflect.ValueOf(mappedValue)
|
|
||||||
if !res.CanConvert(typ) {
|
|
||||||
return v, fmt.Errorf("cannot convert %T to %s", mappedValue, typ.Kind().String())
|
|
||||||
}
|
|
||||||
return res.Convert(typ), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var val any
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.String:
|
|
||||||
val = value
|
|
||||||
case reflect.Bool:
|
|
||||||
val, err = strconv.ParseBool(value)
|
|
||||||
case reflect.Int:
|
|
||||||
val, err = strconv.Atoi(value)
|
|
||||||
case reflect.Int8:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 8)
|
|
||||||
val = int8(val.(int64))
|
|
||||||
case reflect.Int16:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 16)
|
|
||||||
val = int16(val.(int64))
|
|
||||||
case reflect.Int32:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 32)
|
|
||||||
val = int32(val.(int64))
|
|
||||||
case reflect.Int64:
|
|
||||||
val, err = strconv.ParseInt(value, 10, 64)
|
|
||||||
case reflect.Uint:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 0)
|
|
||||||
val = uint(val.(uint64))
|
|
||||||
case reflect.Uint8:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 8)
|
|
||||||
val = uint8(val.(uint64))
|
|
||||||
case reflect.Uint16:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 16)
|
|
||||||
val = uint16(val.(uint64))
|
|
||||||
case reflect.Uint32:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 32)
|
|
||||||
val = uint32(val.(uint64))
|
|
||||||
case reflect.Uint64:
|
|
||||||
val, err = strconv.ParseUint(value, 10, 64)
|
|
||||||
case reflect.Float32:
|
|
||||||
val, err = strconv.ParseFloat(value, 32)
|
|
||||||
val = float32(val.(float64))
|
|
||||||
case reflect.Float64:
|
|
||||||
val, err = strconv.ParseFloat(value, 64)
|
|
||||||
case reflect.Complex64:
|
|
||||||
val, err = strconv.ParseComplex(value, 64)
|
|
||||||
val = complex64(val.(complex128))
|
|
||||||
case reflect.Complex128:
|
|
||||||
val, err = strconv.ParseComplex(value, 128)
|
|
||||||
default:
|
|
||||||
return v, fmt.Errorf("unsupported type: %s", typ.Kind().String())
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
|
|
||||||
res := reflect.ValueOf(val)
|
|
||||||
if !res.CanConvert(typ) {
|
|
||||||
return v, fmt.Errorf("cannot convert %T to %s", val, typ.Kind().String())
|
|
||||||
}
|
|
||||||
return res.Convert(typ), nil
|
|
||||||
}
|
|
@@ -1,115 +0,0 @@
|
|||||||
package bla4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type field struct {
|
|
||||||
info reflect.StructField
|
|
||||||
viper *viper.Viper
|
|
||||||
|
|
||||||
tag
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *field) label() string {
|
|
||||||
var builder strings.Builder
|
|
||||||
builder.WriteString(f.info.Name)
|
|
||||||
builder.WriteString(" (")
|
|
||||||
builder.WriteString(f.info.Type.Kind().String())
|
|
||||||
builder.WriteString(")")
|
|
||||||
if f.description != "" {
|
|
||||||
builder.WriteString(": ")
|
|
||||||
builder.WriteString(f.description)
|
|
||||||
}
|
|
||||||
return builder.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *field) sub() *viper.Viper {
|
|
||||||
if !f.viper.IsSet(f.fieldName) {
|
|
||||||
f.viper.Set(f.fieldName, map[string]any{})
|
|
||||||
}
|
|
||||||
return f.viper.Sub(f.fieldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *field) printStructInfo() {
|
|
||||||
var builder strings.Builder
|
|
||||||
builder.WriteString("------- ")
|
|
||||||
builder.WriteString(f.info.Name)
|
|
||||||
builder.WriteString(" -------")
|
|
||||||
if f.description != "" {
|
|
||||||
builder.WriteString(": ")
|
|
||||||
builder.WriteString(f.description)
|
|
||||||
}
|
|
||||||
fmt.Println(builder.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
type tag struct {
|
|
||||||
skip bool
|
|
||||||
|
|
||||||
fieldName string
|
|
||||||
description string
|
|
||||||
|
|
||||||
value reflect.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
tagName = "configure"
|
|
||||||
defaultKey = "default"
|
|
||||||
descriptionKey = "description"
|
|
||||||
)
|
|
||||||
|
|
||||||
func newTag(field reflect.StructField, current reflect.Value) (config tag, err error) {
|
|
||||||
config.fieldName = field.Name
|
|
||||||
defer func() {
|
|
||||||
if !config.value.IsValid() {
|
|
||||||
if field.Type.Kind() == reflect.Pointer {
|
|
||||||
config.value = reflect.New(field.Type.Elem())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
config.value = reflect.New(field.Type).Elem()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
if !current.IsZero() {
|
|
||||||
config.value = current
|
|
||||||
}
|
|
||||||
|
|
||||||
value, ok := field.Tag.Lookup(tagName)
|
|
||||||
if !ok {
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
if value == "-" {
|
|
||||||
config.skip = true
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := strings.Split(value, ",")
|
|
||||||
for _, f := range fields {
|
|
||||||
configSplit := strings.Split(f, "=")
|
|
||||||
switch strings.ToLower(configSplit[0]) {
|
|
||||||
case defaultKey:
|
|
||||||
if !config.value.IsZero() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
value, err := kindMapper(field.Type.Kind())(configSplit[1])
|
|
||||||
if err != nil {
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
config.value.Set(reflect.ValueOf(value))
|
|
||||||
case descriptionKey:
|
|
||||||
config.description = configSplit[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tag tag) defaultValue() string {
|
|
||||||
if tag.value.IsZero() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%v", tag.value.Interface())
|
|
||||||
}
|
|
@@ -1,70 +0,0 @@
|
|||||||
package bla4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Iterator interface {
|
|
||||||
// NextField returns the name of the next field to configure.
|
|
||||||
// If it returns an empty string, the configuration is complete.
|
|
||||||
NextField() string
|
|
||||||
// FinishAllowed returns true if the configuration is complete and the user can skip the remaining fields.
|
|
||||||
FinishAllowed() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func fieldFunctionByIterator(l *slog.Logger, f *field) Configure {
|
|
||||||
if !f.value.Type().Implements(reflect.TypeFor[Iterator]()) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return func() (value any, err error) {
|
|
||||||
iter := f.value.Interface().(Iterator)
|
|
||||||
sub := f.sub()
|
|
||||||
for {
|
|
||||||
fieldName := iter.NextField()
|
|
||||||
if fieldName == "" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if iter.FinishAllowed() {
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: fmt.Sprintf("Finish configuration of %s", f.info.Name),
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
_, err := prompt.Run()
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
info, ok := f.value.Type().FieldByName(fieldName)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("field %s not found", fieldName)
|
|
||||||
}
|
|
||||||
tag, err := newTag(info, f.value.FieldByName(fieldName))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if tag.skip {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := fieldFunction(
|
|
||||||
l.With(slog.String("field", fieldName)),
|
|
||||||
&field{
|
|
||||||
info: info,
|
|
||||||
viper: sub, //TODO: sub of sub
|
|
||||||
tag: tag,
|
|
||||||
},
|
|
||||||
)()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
reflect.Indirect(f.value).FieldByName(fieldName).Set(reflect.ValueOf(res))
|
|
||||||
}
|
|
||||||
f.viper.Set(f.tag.fieldName, sub.AllSettings())
|
|
||||||
return value, err
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,121 +0,0 @@
|
|||||||
package bla4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var writeMu sync.RWMutex
|
|
||||||
|
|
||||||
func SetTypeMapper(typ reflect.Type, mapper func(input string) (any, error)) {
|
|
||||||
writeMu.Lock()
|
|
||||||
defer writeMu.Unlock()
|
|
||||||
|
|
||||||
typeMappers[typ] = mapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetTypeMapperFor[T any](mapper func(input string) (any, error)) {
|
|
||||||
writeMu.Lock()
|
|
||||||
defer writeMu.Unlock()
|
|
||||||
|
|
||||||
typeMappers[reflect.TypeFor[T]()] = mapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeMapper(typ reflect.Type) func(input string) (any, error) {
|
|
||||||
writeMu.RLock()
|
|
||||||
defer writeMu.RUnlock()
|
|
||||||
|
|
||||||
return typeMappers[typ]
|
|
||||||
}
|
|
||||||
|
|
||||||
var typeMappers = map[reflect.Type]func(string) (any, error){
|
|
||||||
reflect.TypeFor[time.Duration](): func(input string) (any, error) {
|
|
||||||
return time.ParseDuration(input)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetKindMapper overwrites the mapper for the given kind.
|
|
||||||
func SetKindMapper(kind reflect.Kind, mapper func(input string) (any, error)) {
|
|
||||||
writeMu.Lock()
|
|
||||||
defer writeMu.Unlock()
|
|
||||||
|
|
||||||
kindMappers[kind] = mapper
|
|
||||||
}
|
|
||||||
|
|
||||||
func kindMapper(kind reflect.Kind) func(input string) (any, error) {
|
|
||||||
writeMu.RLock()
|
|
||||||
defer writeMu.RUnlock()
|
|
||||||
|
|
||||||
return kindMappers[kind]
|
|
||||||
}
|
|
||||||
|
|
||||||
var kindMappers = map[reflect.Kind]func(input string) (any, error){
|
|
||||||
reflect.String: func(input string) (any, error) {
|
|
||||||
return input, nil
|
|
||||||
},
|
|
||||||
reflect.Bool: func(input string) (any, error) {
|
|
||||||
return strconv.ParseBool(input)
|
|
||||||
},
|
|
||||||
reflect.Int: func(input string) (any, error) {
|
|
||||||
return strconv.Atoi(input)
|
|
||||||
},
|
|
||||||
reflect.Int8: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseInt(input, 10, 8)
|
|
||||||
val = int8(val.(int64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Int16: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseInt(input, 10, 16)
|
|
||||||
val = int16(val.(int64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Int32: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseInt(input, 10, 32)
|
|
||||||
val = int32(val.(int64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Int64: func(input string) (any, error) {
|
|
||||||
return strconv.ParseInt(input, 10, 64)
|
|
||||||
},
|
|
||||||
reflect.Uint: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseUint(input, 10, 0)
|
|
||||||
val = uint(val.(uint64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Uint8: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseUint(input, 10, 8)
|
|
||||||
val = uint8(val.(uint64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Uint16: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseUint(input, 10, 16)
|
|
||||||
val = uint16(val.(uint64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Uint32: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseUint(input, 10, 32)
|
|
||||||
val = uint32(val.(uint64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Uint64: func(input string) (any, error) {
|
|
||||||
return strconv.ParseUint(input, 10, 64)
|
|
||||||
},
|
|
||||||
reflect.Float32: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseFloat(input, 32)
|
|
||||||
val = float32(val.(float64))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Float64: func(input string) (any, error) {
|
|
||||||
return strconv.ParseFloat(input, 64)
|
|
||||||
},
|
|
||||||
reflect.Complex64: func(input string) (val any, err error) {
|
|
||||||
val, err = strconv.ParseComplex(input, 64)
|
|
||||||
val = complex64(val.(complex128))
|
|
||||||
return val, err
|
|
||||||
},
|
|
||||||
reflect.Complex128: func(input string) (any, error) {
|
|
||||||
return strconv.ParseComplex(input, 128)
|
|
||||||
},
|
|
||||||
}
|
|
@@ -1,189 +0,0 @@
|
|||||||
package bla4
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
||||||
AddSource: true,
|
|
||||||
Level: slog.LevelDebug,
|
|
||||||
}))
|
|
||||||
|
|
||||||
type Configure func() (value any, err error)
|
|
||||||
|
|
||||||
type Configurer interface {
|
|
||||||
// Configure is called to configure the value.
|
|
||||||
// It must return the same type as itself. Otherwise [Update] will panic because it is not able to set the value.
|
|
||||||
Configure() (value any, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Update(v *viper.Viper, config any) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
value := reflect.ValueOf(config)
|
|
||||||
structConfigures := structToConfigureMap(Logger, v, value)
|
|
||||||
for key, configure := range structConfigures {
|
|
||||||
result, err := configure()
|
|
||||||
if err != nil {
|
|
||||||
Logger.Error("error configuring field", slog.String("field", key), slog.Any("cause", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
value.Elem().FieldByName(key).Set(reflect.ValueOf(result))
|
|
||||||
}
|
|
||||||
|
|
||||||
err := v.WriteConfig()
|
|
||||||
if err != nil {
|
|
||||||
Logger.Error("error writing config", slog.Any("cause", err))
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func structToConfigureMap(l *slog.Logger, v *viper.Viper, object reflect.Value) map[string]Configure {
|
|
||||||
if object.Kind() == reflect.Pointer {
|
|
||||||
if object.IsNil() {
|
|
||||||
l.Debug("initialize object")
|
|
||||||
object = reflect.New(object.Type().Elem())
|
|
||||||
}
|
|
||||||
return structToConfigureMap(l, v, object.Elem())
|
|
||||||
}
|
|
||||||
if object.Kind() != reflect.Struct {
|
|
||||||
panic("config must be a struct")
|
|
||||||
}
|
|
||||||
|
|
||||||
fields := make(map[string]Configure, object.NumField())
|
|
||||||
|
|
||||||
for i := range object.NumField() {
|
|
||||||
if !object.Type().Field(i).IsExported() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tag, err := newTag(object.Type().Field(i), object.Field(i))
|
|
||||||
if err != nil {
|
|
||||||
l.Error("failed to parse field tag", slog.Any("cause", err))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tag.skip {
|
|
||||||
l.Debug("skipping field", slog.String("field", object.Type().Field(i).Name))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fields[object.Type().Field(i).Name] = fieldFunction(
|
|
||||||
l.With(slog.String("field", object.Type().Field(i).Name)),
|
|
||||||
&field{
|
|
||||||
info: object.Type().Field(i),
|
|
||||||
tag: tag,
|
|
||||||
viper: v,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
func fieldFunction(l *slog.Logger, f *field) (configure Configure) {
|
|
||||||
for _, mapper := range []func(*slog.Logger, *field) Configure{
|
|
||||||
fieldFunctionByConfigurer,
|
|
||||||
fieldFunctionByIterator,
|
|
||||||
fieldFunctionByReflection,
|
|
||||||
} {
|
|
||||||
if configure = mapper(l, f); configure != nil {
|
|
||||||
return configure
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("unsupported field type: %s", f.info.Type.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
func fieldFunctionByConfigurer(l *slog.Logger, f *field) Configure {
|
|
||||||
if f.value.Type().Implements(reflect.TypeFor[Configurer]()) {
|
|
||||||
return func() (value any, err error) {
|
|
||||||
f.printStructInfo()
|
|
||||||
res, err := f.value.Interface().(Configurer).Configure()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.viper.Set(f.tag.fieldName, res)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func fieldFunctionByReflection(l *slog.Logger, f *field) Configure {
|
|
||||||
kind := f.value.Kind()
|
|
||||||
|
|
||||||
//nolint:exhaustive // only types that require special treatment are covered
|
|
||||||
switch kind {
|
|
||||||
case reflect.Pointer:
|
|
||||||
if f.value.IsNil() {
|
|
||||||
f.value.Set(reflect.New(f.value.Type().Elem()))
|
|
||||||
}
|
|
||||||
sub := f.sub()
|
|
||||||
m := structToConfigureMap(l, sub, f.value)
|
|
||||||
return func() (value any, err error) {
|
|
||||||
f.printStructInfo()
|
|
||||||
for key, configure := range m {
|
|
||||||
value, err = configure()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.value.Elem().FieldByName(key).Set(reflect.ValueOf(value))
|
|
||||||
}
|
|
||||||
f.viper.Set(f.tag.fieldName, sub.AllSettings())
|
|
||||||
return f.value.Interface(), nil
|
|
||||||
}
|
|
||||||
case reflect.Struct:
|
|
||||||
sub := f.sub()
|
|
||||||
m := structToConfigureMap(l, sub, f.value)
|
|
||||||
return func() (value any, err error) {
|
|
||||||
f.printStructInfo()
|
|
||||||
for key, configure := range m {
|
|
||||||
value, err = configure()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.value.FieldByName(key).Set(reflect.ValueOf(value))
|
|
||||||
}
|
|
||||||
f.viper.Set(f.tag.fieldName, sub.AllSettings())
|
|
||||||
return f.value.Interface(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Map:
|
|
||||||
l.Warn("skipping because kind is unimplemented", slog.String("kind", kind.String()))
|
|
||||||
return nil
|
|
||||||
case reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer, reflect.Invalid:
|
|
||||||
slog.Error("skipping because kind is unsupported", slog.String("kind", kind.String()))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
mapper := typeMapper(f.info.Type)
|
|
||||||
if mapper == nil {
|
|
||||||
mapper = kindMapper(kind)
|
|
||||||
}
|
|
||||||
if mapper == nil {
|
|
||||||
l.Error("unsupported kind", slog.String("kind", kind.String()))
|
|
||||||
panic(fmt.Sprintf("unsupported kind: %s", kind.String()))
|
|
||||||
}
|
|
||||||
|
|
||||||
return func() (value any, err error) {
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: f.label(),
|
|
||||||
Validate: func(input string) error {
|
|
||||||
value, err = mapper(input)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
Default: f.defaultValue(),
|
|
||||||
}
|
|
||||||
_, err = prompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
f.viper.Set(f.tag.fieldName, value)
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
package bla5
|
|
@@ -1,75 +0,0 @@
|
|||||||
package bla5
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
|
|
||||||
AddSource: true,
|
|
||||||
Level: slog.LevelDebug,
|
|
||||||
}))
|
|
||||||
|
|
||||||
type Configure func() (value any, err error)
|
|
||||||
|
|
||||||
// Configurer should be implemented by each field of the configuration struct.
|
|
||||||
// It is used to configure the value of the field.
|
|
||||||
type Configurer interface {
|
|
||||||
// Configure is called to configure the value.
|
|
||||||
// It must return the same type as itself. Otherwise [Update] will panic because it is not able to set the value.
|
|
||||||
Configure() (value any, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Update(v *viper.Viper, config any) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
// value := reflect.ValueOf(config)
|
|
||||||
// structConfigures := structToConfigureMap(Logger, v, value)
|
|
||||||
// for key, configure := range structConfigures {
|
|
||||||
// result, err := configure()
|
|
||||||
// if err != nil {
|
|
||||||
// Logger.Error("error configuring field", slog.String("field", key), slog.Any("cause", err))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// value.Elem().FieldByName(key).Set(reflect.ValueOf(result))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// err := v.WriteConfig()
|
|
||||||
// if err != nil {
|
|
||||||
// Logger.Error("error writing config", slog.Any("cause", err))
|
|
||||||
// os.Exit(1)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// func objectToFlow(l *slog.Logger, value reflect.Value) {
|
|
||||||
// if value.Kind() == reflect.Pointer {
|
|
||||||
// if value.IsNil() {
|
|
||||||
// value = reflect.New(value.Type().Elem())
|
|
||||||
// }
|
|
||||||
// value = value.Elem()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// typeOfValue := value.Type()
|
|
||||||
// for i := 0; i < value.NumField(); i++ {
|
|
||||||
// fieldValue := value.Field(i)
|
|
||||||
// fieldType := typeOfValue.Field(i)
|
|
||||||
|
|
||||||
// l.Info("Processing field", slog.String("field", fieldType.Name))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type field struct {
|
|
||||||
// set func(value reflect.Value) error
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type structField struct {
|
|
||||||
// name string
|
|
||||||
|
|
||||||
// fields []field
|
|
||||||
// }
|
|
||||||
|
|
||||||
// type primitiveField struct {
|
|
||||||
// }
|
|
@@ -1,111 +0,0 @@
|
|||||||
package configure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ConfigureCmd represents the config command
|
|
||||||
ConfigureCmd = &cobra.Command{
|
|
||||||
Use: "configure",
|
|
||||||
Short: "Guides you through configuring Zitadel for the specified command",
|
|
||||||
// Long: `A longer description that spans multiple lines and likely contains examples
|
|
||||||
// and usage of using your command. For example:
|
|
||||||
|
|
||||||
// Cobra is a CLI library for Go that empowers applications.
|
|
||||||
// This application is a tool to generate the needed files
|
|
||||||
// to quickly create a Cobra application.`,
|
|
||||||
// Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
// fmt.Println("config called")
|
|
||||||
// // fmt.Println(viper.AllSettings())
|
|
||||||
// // fmt.Println(viper.Sub("database").AllSettings())
|
|
||||||
// // pool, err := config.Database.Connect(cmd.Context())
|
|
||||||
// // _, _ = pool, err
|
|
||||||
// },
|
|
||||||
PersistentPreRun: configurePreRun,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
t := new(test)
|
|
||||||
// Update2(*t)
|
|
||||||
Update("test", "test", t.Fields())(cmd, args)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
configuration Config
|
|
||||||
)
|
|
||||||
|
|
||||||
func configurePreRun(cmd *cobra.Command, args []string) {
|
|
||||||
// cmd.InheritedFlags().Lookup("config").Hidden = true
|
|
||||||
ReadConfigPreRun(viper.GetViper(), &configuration)(cmd, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
ConfigureCmd.PersistentFlags().BoolVarP(&configuration.upgrade, "upgrade", "u", false, "Only changed configuration values since the previously used version will be asked for")
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// configureCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
upgrade bool `mapstructure:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Hooks() []viper.DecoderConfigOption {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type sub struct {
|
|
||||||
F1 string
|
|
||||||
F2 int
|
|
||||||
F3 *bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s sub) Fields() []Updater {
|
|
||||||
return []Updater{
|
|
||||||
Field[string]{
|
|
||||||
FieldName: "f1",
|
|
||||||
Value: "",
|
|
||||||
Description: "field 1",
|
|
||||||
Version: semver.MustParse("3"),
|
|
||||||
},
|
|
||||||
Field[int]{
|
|
||||||
FieldName: "f2",
|
|
||||||
Value: 0,
|
|
||||||
Description: "field 2",
|
|
||||||
Version: semver.MustParse("3"),
|
|
||||||
},
|
|
||||||
Field[*bool]{
|
|
||||||
FieldName: "f3",
|
|
||||||
Value: nil,
|
|
||||||
Description: "field 3",
|
|
||||||
Version: semver.MustParse("3"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type test struct {
|
|
||||||
F1 string
|
|
||||||
Sub sub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t test) Fields() []Updater {
|
|
||||||
return []Updater{
|
|
||||||
Field[string]{
|
|
||||||
FieldName: "f1",
|
|
||||||
Value: "",
|
|
||||||
Description: "field 1",
|
|
||||||
Version: semver.MustParse("3"),
|
|
||||||
},
|
|
||||||
Struct{
|
|
||||||
FieldName: "sub",
|
|
||||||
Description: "sub field",
|
|
||||||
SubFields: t.Sub.Fields(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
package configure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Unmarshaller interface {
|
|
||||||
Hooks() []viper.DecoderConfigOption
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadConfigPreRun[C Unmarshaller](v *viper.Viper, config C) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
if err := v.Unmarshal(config, config.Hooks()...); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ReadConfig[C Unmarshaller](v *viper.Viper) (config C, err error) {
|
|
||||||
if err := v.Unmarshal(&config, config.Hooks()...); err != nil {
|
|
||||||
return config, err
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
@@ -1,449 +0,0 @@
|
|||||||
package configure
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
"github.com/manifoldco/promptui"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/info"
|
|
||||||
)
|
|
||||||
|
|
||||||
var lastConfiguredVersion *semver.Version
|
|
||||||
|
|
||||||
func Update(name, description string, fields []Updater) func(cmd *cobra.Command, args []string) {
|
|
||||||
return func(cmd *cobra.Command, args []string) {
|
|
||||||
configVersion := viper.GetString("configuredVersion")
|
|
||||||
if configVersion != "" {
|
|
||||||
lastConfiguredVersion, _ = semver.NewVersion(configVersion)
|
|
||||||
}
|
|
||||||
for _, field := range fields {
|
|
||||||
if err := setField(field, "", 1); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("Using config file:", viper.ConfigFileUsed(), "fields:", fields)
|
|
||||||
fmt.Println(viper.AllSettings())
|
|
||||||
viper.Set("configuredVersion", info.Version().String())
|
|
||||||
// viper.MergeConfigMap(fields)
|
|
||||||
// if err := viper.WriteConfig(); err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
if err := viper.WriteConfig(); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setField(field Updater, parentPath string, depth int) error {
|
|
||||||
if !field.ShouldUpdate(lastConfiguredVersion) {
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: field.Name() + " did not change since last configure, skip?",
|
|
||||||
IsConfirm: true,
|
|
||||||
}
|
|
||||||
if _, err := prompt.Run(); err == nil {
|
|
||||||
fmt.Println("skip")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fieldPath := path(parentPath, field.Name())
|
|
||||||
switch f := field.(type) {
|
|
||||||
case StructUpdater:
|
|
||||||
fmt.Printf("\n%.*s %s: %s\n\n", depth*2, "-", f.Name(), f.Describe())
|
|
||||||
for _, subField := range f.Fields() {
|
|
||||||
err := setField(subField, fieldPath, depth+1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case FieldUpdater:
|
|
||||||
value := viper.Get(fieldPath)
|
|
||||||
if value == nil {
|
|
||||||
value = f.ValueOrDefault()
|
|
||||||
}
|
|
||||||
prompt := promptui.Prompt{
|
|
||||||
Label: fmt.Sprintf("%s (%s) (%T)", f.Name(), f.Describe(), f.ValueOrDefault()),
|
|
||||||
Default: fmt.Sprintf("%v", value),
|
|
||||||
Validate: func(s string) error {
|
|
||||||
if isConfirm(reflect.TypeOf(value)) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return f.Set(s)
|
|
||||||
},
|
|
||||||
HideEntered: f.ShouldHide(),
|
|
||||||
IsConfirm: isConfirm(reflect.TypeOf(value)),
|
|
||||||
}
|
|
||||||
_, err := prompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
viper.Set(fieldPath, f.ValueOrDefault())
|
|
||||||
case OneOfUpdater:
|
|
||||||
var possibilities []string
|
|
||||||
for _, subField := range f.Fields() {
|
|
||||||
possibility := subField.Name()
|
|
||||||
if possibility == "" {
|
|
||||||
possibility = subField.Describe()
|
|
||||||
}
|
|
||||||
possibilities = append(possibilities, possibility)
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt := promptui.Select{
|
|
||||||
Label: fmt.Sprintf("Select one of %s: (%s)", f.Name(), f.Describe()),
|
|
||||||
Items: possibilities,
|
|
||||||
}
|
|
||||||
i, _, err := prompt.Run()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("panic", err)
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
setField(f.Fields()[i], fieldPath, depth-1)
|
|
||||||
// TODO: fv is result of decoder (postgres.config in this case)
|
|
||||||
case ConstantUpdater:
|
|
||||||
viper.Set(fieldPath, f.Value())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func path(parent, field string) string {
|
|
||||||
if parent == "" {
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(parent, field) {
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
if field == "" {
|
|
||||||
return parent
|
|
||||||
}
|
|
||||||
return parent + "." + field
|
|
||||||
}
|
|
||||||
|
|
||||||
func isConfirm(t reflect.Type) bool {
|
|
||||||
if t == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if t.Kind() == reflect.Ptr {
|
|
||||||
return isConfirm(t.Elem())
|
|
||||||
}
|
|
||||||
return t.Kind() == reflect.Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Updater interface {
|
|
||||||
Name() string
|
|
||||||
Describe() string
|
|
||||||
ShouldUpdate(version *semver.Version) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type FieldUpdater interface {
|
|
||||||
Updater
|
|
||||||
// DefaultValue() any
|
|
||||||
Set(value any) error
|
|
||||||
ValueOrDefault() any
|
|
||||||
_field()
|
|
||||||
ShouldHide() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type StructUpdater interface {
|
|
||||||
Updater
|
|
||||||
Fields() []Updater
|
|
||||||
_struct()
|
|
||||||
}
|
|
||||||
|
|
||||||
type OneOfUpdater interface {
|
|
||||||
Updater
|
|
||||||
Fields() []Updater
|
|
||||||
_oneOf()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConstantUpdater interface {
|
|
||||||
Updater
|
|
||||||
Value() any
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ FieldUpdater = (*Field[string])(nil)
|
|
||||||
|
|
||||||
type Field[T any] struct {
|
|
||||||
FieldName string
|
|
||||||
Value T
|
|
||||||
HideInput bool
|
|
||||||
Description string
|
|
||||||
Version *semver.Version
|
|
||||||
Validate func(T) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe implements [FieldUpdater].
|
|
||||||
func (f Field[T]) Describe() string {
|
|
||||||
return f.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set implements [FieldUpdater].
|
|
||||||
func (f *Field[T]) Set(value any) (err error) {
|
|
||||||
switch v := value.(type) {
|
|
||||||
case string:
|
|
||||||
f.Value, err = mapString[T](v)
|
|
||||||
case map[string]any:
|
|
||||||
f.Value, err = mapMap[T](v)
|
|
||||||
case T:
|
|
||||||
f.Value = v
|
|
||||||
default:
|
|
||||||
err = fmt.Errorf("unsupported type %T", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil || f.Validate == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f.Validate(f.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field[T]) ValueOrDefault() any {
|
|
||||||
return f.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapMap[T any](value map[string]any) (t T, err error) {
|
|
||||||
jsonValue, err := json.Marshal(value)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
return t, json.Unmarshal(jsonValue, &t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapString[T any](value string) (t T, err error) {
|
|
||||||
var v any
|
|
||||||
switch reflect.TypeFor[T]().Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
v, err = strconv.ParseBool(value)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
case reflect.Int:
|
|
||||||
i, err := strconv.ParseInt(value, 10, 0)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = int(i)
|
|
||||||
case reflect.Int8:
|
|
||||||
i, err := strconv.ParseInt(value, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = int8(i)
|
|
||||||
case reflect.Int16:
|
|
||||||
i, err := strconv.ParseInt(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = int16(i)
|
|
||||||
case reflect.Int32:
|
|
||||||
i, err := strconv.ParseInt(value, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = int32(i)
|
|
||||||
case reflect.Int64:
|
|
||||||
i, err := strconv.ParseInt(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = int64(i)
|
|
||||||
case reflect.Uint:
|
|
||||||
i, err := strconv.ParseUint(value, 10, 0)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = uint(i)
|
|
||||||
case reflect.Uint8:
|
|
||||||
i, err := strconv.ParseUint(value, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = uint8(i)
|
|
||||||
case reflect.Uint16:
|
|
||||||
i, err := strconv.ParseUint(value, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = uint16(i)
|
|
||||||
case reflect.Uint32:
|
|
||||||
i, err := strconv.ParseUint(value, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = uint32(i)
|
|
||||||
case reflect.Uint64:
|
|
||||||
i, err := strconv.ParseUint(value, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = uint64(i)
|
|
||||||
case reflect.Float32:
|
|
||||||
i, err := strconv.ParseFloat(value, 32)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = float32(i)
|
|
||||||
case reflect.Float64:
|
|
||||||
i, err := strconv.ParseFloat(value, 64)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = float64(i)
|
|
||||||
case reflect.Complex64:
|
|
||||||
i, err := strconv.ParseComplex(value, 64)
|
|
||||||
if err != nil {
|
|
||||||
return t, err
|
|
||||||
}
|
|
||||||
v = complex64(i)
|
|
||||||
case reflect.Complex128:
|
|
||||||
v, err = strconv.ParseComplex(value, 128)
|
|
||||||
case reflect.String:
|
|
||||||
v = value
|
|
||||||
default:
|
|
||||||
k := reflect.TypeFor[T]().Kind()
|
|
||||||
_ = k
|
|
||||||
return t, errors.New("not implemented")
|
|
||||||
}
|
|
||||||
return v.(T), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Field implements [FieldUpdater].
|
|
||||||
func (f Field[T]) Name() string {
|
|
||||||
return f.FieldName
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldUpdate implements [FieldUpdater].
|
|
||||||
func (f Field[T]) ShouldUpdate(version *semver.Version) bool {
|
|
||||||
if version == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return f.Version.GreaterThan(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldHide implements [FieldUpdater].
|
|
||||||
func (f Field[T]) ShouldHide() bool {
|
|
||||||
return f.HideInput
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f Field[T]) _field() {}
|
|
||||||
|
|
||||||
var _ StructUpdater = (*Struct)(nil)
|
|
||||||
|
|
||||||
type Struct struct {
|
|
||||||
FieldName string
|
|
||||||
Description string
|
|
||||||
SubFields []Updater
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe implements [StructUpdater].
|
|
||||||
func (s Struct) Describe() string {
|
|
||||||
return s.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Struct) Name() string {
|
|
||||||
return s.FieldName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Struct) Fields() []Updater {
|
|
||||||
return s.SubFields
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Struct) ShouldUpdate(version *semver.Version) bool {
|
|
||||||
if version == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
for _, field := range s.SubFields {
|
|
||||||
if !field.ShouldUpdate(version) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Struct) _struct() {}
|
|
||||||
|
|
||||||
type OneOf struct {
|
|
||||||
FieldName string
|
|
||||||
Description string
|
|
||||||
SubFields []Updater
|
|
||||||
}
|
|
||||||
|
|
||||||
// Describe implements [OneOfUpdater].
|
|
||||||
func (o OneOf) Describe() string {
|
|
||||||
return o.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields implements [OneOfUpdater].
|
|
||||||
func (o OneOf) Fields() []Updater {
|
|
||||||
return o.SubFields
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements [FieldUpdater].
|
|
||||||
func (o OneOf) Name() string {
|
|
||||||
return o.FieldName
|
|
||||||
}
|
|
||||||
|
|
||||||
func (OneOf) _oneOf() {}
|
|
||||||
|
|
||||||
// ShouldUpdate implements [OneOfUpdater].
|
|
||||||
func (o OneOf) ShouldUpdate(version *semver.Version) bool {
|
|
||||||
if version == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, field := range o.SubFields {
|
|
||||||
if !field.ShouldUpdate(version) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ OneOfUpdater = (*OneOf)(nil)
|
|
||||||
|
|
||||||
func FieldName(parent, field string) string {
|
|
||||||
if parent == "" {
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
return parent + "." + field
|
|
||||||
}
|
|
||||||
|
|
||||||
type Constant[T any] struct {
|
|
||||||
Description string
|
|
||||||
Constant T
|
|
||||||
Version *semver.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ ConstantUpdater = (*Constant[any])(nil)
|
|
||||||
|
|
||||||
// Describe implements [ConstantUpdater].
|
|
||||||
func (c Constant[T]) Describe() string {
|
|
||||||
return c.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements [ConstantUpdater].
|
|
||||||
func (c Constant[T]) Name() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldUpdate implements [ConstantUpdater].
|
|
||||||
func (c Constant[T]) ShouldUpdate(version *semver.Version) bool {
|
|
||||||
if version == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return c.Version.GreaterThan(version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Value implements [ConstantUpdater].
|
|
||||||
func (c Constant[T]) Value() any {
|
|
||||||
return c.Constant
|
|
||||||
}
|
|
@@ -1,36 +0,0 @@
|
|||||||
package info
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
version string
|
|
||||||
commit string
|
|
||||||
date string
|
|
||||||
)
|
|
||||||
|
|
||||||
func Version() *semver.Version {
|
|
||||||
v, _ := semver.NewVersion(version)
|
|
||||||
if v != nil {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return semver.New(uint64(Date().Year()), uint64(Date().Month()), uint64(Date().Day()), "", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func Commit() string {
|
|
||||||
return commit
|
|
||||||
}
|
|
||||||
|
|
||||||
func Date() time.Time {
|
|
||||||
if date == "" {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
d, err := time.Parse(time.RFC3339, date)
|
|
||||||
if err != nil {
|
|
||||||
return time.Now()
|
|
||||||
}
|
|
||||||
return d
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
CREATE USER IF NOT EXISTS {{ .Username}};
|
|
@@ -1 +0,0 @@
|
|||||||
CREATE DATABASE IF NOT EXISTS {{ .DatabaseName }} WITH OWNER {{ .Username }};
|
|
@@ -1,67 +0,0 @@
|
|||||||
package step001
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/configure"
|
|
||||||
"github.com/zitadel/zitadel/backend/storage/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
//go:embed sql/*.sql
|
|
||||||
migrations embed.FS
|
|
||||||
)
|
|
||||||
|
|
||||||
type Step001 struct {
|
|
||||||
Database database.Pool `mapstructure:"-" configure:"-"`
|
|
||||||
|
|
||||||
DatabaseName string `configure:"default=zitadel"`
|
|
||||||
Username string `configure:"default=zitadel"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fields implements configure.StructUpdater.
|
|
||||||
func (v Step001) Fields() []configure.Updater {
|
|
||||||
return []configure.Updater{
|
|
||||||
&configure.Field[string]{
|
|
||||||
FieldName: "databaseName",
|
|
||||||
Value: "zitadel",
|
|
||||||
Description: "The name of the database Zitadel will store its data in",
|
|
||||||
Version: semver.MustParse("3"),
|
|
||||||
},
|
|
||||||
&configure.Field[string]{
|
|
||||||
FieldName: "username",
|
|
||||||
Value: "zitadel",
|
|
||||||
Description: "The username Zitadel will use to connect to the database",
|
|
||||||
Version: semver.MustParse("3"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name implements configure.StructUpdater.
|
|
||||||
func (v *Step001) Name() string {
|
|
||||||
return "step001"
|
|
||||||
}
|
|
||||||
|
|
||||||
// var _ configure.StructUpdater = (*Step001)(nil)
|
|
||||||
|
|
||||||
func (v *Step001) Migrate(ctx context.Context) error {
|
|
||||||
files, err := migrations.ReadDir("sql")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
fmt.Println(file.Name())
|
|
||||||
fmt.Println(migrations.ReadFile(file.Name()))
|
|
||||||
}
|
|
||||||
conn, err := v.Database.Acquire(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer conn.Release(ctx)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,83 +0,0 @@
|
|||||||
package prepare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/config"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/configure"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/configure/bla4"
|
|
||||||
step001 "github.com/zitadel/zitadel/backend/cmd/prepare/001"
|
|
||||||
"github.com/zitadel/zitadel/backend/storage/database"
|
|
||||||
"github.com/zitadel/zitadel/backend/storage/database/dialect"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
configuration Config
|
|
||||||
|
|
||||||
// configurePrepare represents the prepare command
|
|
||||||
configurePrepare = &cobra.Command{
|
|
||||||
Use: "prepare",
|
|
||||||
Short: "Writes the configuration for the prepare command",
|
|
||||||
// Long: `A longer description that spans multiple lines and likely contains examples
|
|
||||||
// and usage of using your command. For example:
|
|
||||||
|
|
||||||
// Cobra is a CLI library for Go that empowers applications.
|
|
||||||
// This application is a tool to generate the needed files
|
|
||||||
// to quickly create a Cobra application.`,
|
|
||||||
// Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
// var err error
|
|
||||||
// config.Client, err = config.Database.Connect(cmd.Context())
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// defer config.Client.Close(cmd.Context())
|
|
||||||
// if err := (&step001.Step001{Database: config.Client}).Migrate(cmd.Context()); err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// Run: configure.Update(
|
|
||||||
// "prepare",
|
|
||||||
// "Writes the configuration for the prepare command",
|
|
||||||
// configuration.Fields(),
|
|
||||||
// ),
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
bla4.Update(viper.GetViper(), &configuration)(cmd, args)
|
|
||||||
},
|
|
||||||
PreRun: configure.ReadConfigPreRun(viper.GetViper(), &configuration),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
config.Config `mapstructure:",squash" configure:"-"`
|
|
||||||
|
|
||||||
Database *dialect.Config // `configure:"-"`
|
|
||||||
Step001 step001.Step001
|
|
||||||
Step002 *step001.Step001
|
|
||||||
|
|
||||||
// runtime config
|
|
||||||
Client database.Pool `mapstructure:"-" configure:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Config) Hooks() (decoders []viper.DecoderConfigOption) {
|
|
||||||
// for _, hooks := range []configure.Unmarshaller{
|
|
||||||
// c.Config,
|
|
||||||
// c.Database,
|
|
||||||
// } {
|
|
||||||
// decoders = append(decoders, hooks.Hooks()...)
|
|
||||||
// }
|
|
||||||
return decoders
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
configure.ConfigureCmd.AddCommand(configurePrepare)
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// prepareCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// prepareCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
@@ -1,35 +0,0 @@
|
|||||||
package prepare
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
step001 "github.com/zitadel/zitadel/backend/cmd/prepare/001"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// PrepareCmd represents the prepare command
|
|
||||||
PrepareCmd = &cobra.Command{
|
|
||||||
Use: "prepare",
|
|
||||||
Short: "Prepares external services before starting Zitadel",
|
|
||||||
// Long: `A longer description that spans multiple lines and likely contains examples
|
|
||||||
// and usage of using your command. For example:
|
|
||||||
|
|
||||||
// Cobra is a CLI library for Go that empowers applications.
|
|
||||||
// This application is a tool to generate the needed files
|
|
||||||
// to quickly create a Cobra application.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
// var err error
|
|
||||||
// configuration.Client, err = configuration.Database.Connect(cmd.Context())
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
defer configuration.Client.Close(cmd.Context())
|
|
||||||
if err := (&step001.Step001{Database: configuration.Client}).Migrate(cmd.Context()); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type Migration interface {
|
|
||||||
}
|
|
@@ -1,52 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/config"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/configure"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/prepare"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/start"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/upgrade"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RootCmd represents the base command when called without any subcommands
|
|
||||||
var RootCmd = &cobra.Command{
|
|
||||||
Use: "zitadel [subcommand]",
|
|
||||||
Short: "A brief description of your application",
|
|
||||||
Long: `zitadel`,
|
|
||||||
// Uncomment the following line if your bare application
|
|
||||||
// has an action associated with it:
|
|
||||||
// Run: func(cmd *cobra.Command, args []string) { },
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
|
||||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
|
||||||
func Execute() {
|
|
||||||
err := RootCmd.Execute()
|
|
||||||
if err != nil {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
RootCmd.AddCommand(
|
|
||||||
configure.ConfigureCmd,
|
|
||||||
prepare.PrepareCmd,
|
|
||||||
start.StartCmd,
|
|
||||||
upgrade.UpgradeCmd,
|
|
||||||
)
|
|
||||||
|
|
||||||
cobra.OnInitialize(config.InitConfig)
|
|
||||||
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
// Cobra supports persistent flags, which, if defined here,
|
|
||||||
// will be global for your application.
|
|
||||||
|
|
||||||
RootCmd.PersistentFlags().StringVar(&config.Path, "config", "", "config file (default is $HOME/.zitadel.yaml)")
|
|
||||||
|
|
||||||
// Cobra also supports local flags, which will only run
|
|
||||||
// when this action is called directly.
|
|
||||||
RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
package start
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/backend/storage/database/dialect"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Database dialect.Config `version:"v3"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c Config) Hooks() []viper.DecoderConfigOption {
|
|
||||||
return c.Database.Hooks()
|
|
||||||
}
|
|
@@ -1,41 +0,0 @@
|
|||||||
package start
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"github.com/zitadel/zitadel/backend/cmd/configure"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// StartCmd represents the start command
|
|
||||||
StartCmd = &cobra.Command{
|
|
||||||
Use: "start",
|
|
||||||
Short: "Starts the Zitadel server",
|
|
||||||
// Long: `A longer description that spans multiple lines and likely contains examples
|
|
||||||
// and usage of using your command. For example:
|
|
||||||
|
|
||||||
// Cobra is a CLI library for Go that empowers applications.
|
|
||||||
// This application is a tool to generate the needed files
|
|
||||||
// to quickly create a Cobra application.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("start called")
|
|
||||||
},
|
|
||||||
PreRun: configure.ReadConfigPreRun(viper.GetViper(), &config),
|
|
||||||
}
|
|
||||||
|
|
||||||
config Config
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// startCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// startCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
@@ -1,8 +0,0 @@
|
|||||||
database:
|
|
||||||
postgres: host=localhost user=zitadel password= dbname=zitadel sslmode=disable test=test
|
|
||||||
step001:
|
|
||||||
databasename: qwer
|
|
||||||
username: asdf
|
|
||||||
step002:
|
|
||||||
databasename: yuio
|
|
||||||
username: hjkl
|
|
@@ -1,34 +0,0 @@
|
|||||||
package upgrade
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UpgradeCmd represents the upgrade command
|
|
||||||
var UpgradeCmd = &cobra.Command{
|
|
||||||
Use: "upgrade",
|
|
||||||
Short: "Upgrades Zitadel from a previous version",
|
|
||||||
// Long: `A longer description that spans multiple lines and likely contains examples
|
|
||||||
// and usage of using your command. For example:
|
|
||||||
|
|
||||||
// Cobra is a CLI library for Go that empowers applications.
|
|
||||||
// This application is a tool to generate the needed files
|
|
||||||
// to quickly create a Cobra application.`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
fmt.Println("upgrade called")
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Here you will define your flags and configuration settings.
|
|
||||||
|
|
||||||
// Cobra supports Persistent Flags which will work for this command
|
|
||||||
// and all subcommands, e.g.:
|
|
||||||
// upgradeCmd.PersistentFlags().String("foo", "", "A help for foo")
|
|
||||||
|
|
||||||
// Cobra supports local flags which will only run when this command
|
|
||||||
// is called directly, e.g.:
|
|
||||||
// upgradeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
|
||||||
}
|
|
Reference in New Issue
Block a user