From e31bd14a078e334916aa0b4e27a0cca1b5048221 Mon Sep 17 00:00:00 2001 From: adlerhurst <27845747+adlerhurst@users.noreply.github.com> Date: Wed, 5 Mar 2025 00:02:17 +0100 Subject: [PATCH] number 5 --- backend/cmd/configure/bla2/primitive.go | 48 +++++++ backend/cmd/configure/bla2/prompt.go | 61 +++++++++ backend/cmd/configure/bla2/struct.go | 112 ++++++++++++++++ backend/cmd/configure/bla2/tag.go | 53 ++++++++ backend/cmd/configure/bla2/update_config5.go | 132 +++++++++++++++---- backend/cmd/prepare/config.go | 8 +- backend/cmd/test.yaml | 4 +- 7 files changed, 385 insertions(+), 33 deletions(-) create mode 100644 backend/cmd/configure/bla2/primitive.go create mode 100644 backend/cmd/configure/bla2/prompt.go create mode 100644 backend/cmd/configure/bla2/struct.go create mode 100644 backend/cmd/configure/bla2/tag.go diff --git a/backend/cmd/configure/bla2/primitive.go b/backend/cmd/configure/bla2/primitive.go new file mode 100644 index 0000000000..425d604e87 --- /dev/null +++ b/backend/cmd/configure/bla2/primitive.go @@ -0,0 +1,48 @@ +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 +} diff --git a/backend/cmd/configure/bla2/prompt.go b/backend/cmd/configure/bla2/prompt.go new file mode 100644 index 0000000000..79642b8d63 --- /dev/null +++ b/backend/cmd/configure/bla2/prompt.go @@ -0,0 +1,61 @@ +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 +} diff --git a/backend/cmd/configure/bla2/struct.go b/backend/cmd/configure/bla2/struct.go new file mode 100644 index 0000000000..b174390e72 --- /dev/null +++ b/backend/cmd/configure/bla2/struct.go @@ -0,0 +1,112 @@ +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 +} diff --git a/backend/cmd/configure/bla2/tag.go b/backend/cmd/configure/bla2/tag.go new file mode 100644 index 0000000000..0a93e1e0a8 --- /dev/null +++ b/backend/cmd/configure/bla2/tag.go @@ -0,0 +1,53 @@ +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 +} diff --git a/backend/cmd/configure/bla2/update_config5.go b/backend/cmd/configure/bla2/update_config5.go index 2b6c97c068..c14416c85d 100644 --- a/backend/cmd/configure/bla2/update_config5.go +++ b/backend/cmd/configure/bla2/update_config5.go @@ -1,7 +1,11 @@ package bla2 import ( - "github.com/manifoldco/promptui" + "fmt" + "os" + "reflect" + "strconv" + "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -33,38 +37,110 @@ type Config interface { func Update(v *viper.Viper, config any) func(cmd *cobra.Command, args []string) { return func(cmd *cobra.Command, args []string) { - // promptui.Select - // 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) - // } + 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) + } } } -const ( - tagName = "configure" - defaultKey = "default" - oneOfKey = "oneof" -) - type Field struct { - Name string - DefaultValue any - Prompt prompt + // Either Prompt or Sub must be set + Prompt prompt + Sub []*Field + Set func() } -type prompt interface { - Run() +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 } -var ( - _ prompt = (*promptui.Select)(nil) - _ prompt = (*promptui.SelectWithAdd)(nil) - _ prompt = (*promptui.Prompt)(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 +} diff --git a/backend/cmd/prepare/config.go b/backend/cmd/prepare/config.go index 7be5cc9e72..a7e9c34d15 100644 --- a/backend/cmd/prepare/config.go +++ b/backend/cmd/prepare/config.go @@ -6,7 +6,7 @@ import ( "github.com/zitadel/zitadel/backend/cmd/config" "github.com/zitadel/zitadel/backend/cmd/configure" - "github.com/zitadel/zitadel/backend/cmd/configure/bla" + "github.com/zitadel/zitadel/backend/cmd/configure/bla2" step001 "github.com/zitadel/zitadel/backend/cmd/prepare/001" "github.com/zitadel/zitadel/backend/storage/database" "github.com/zitadel/zitadel/backend/storage/database/dialect" @@ -41,7 +41,9 @@ var ( // "Writes the configuration for the prepare command", // configuration.Fields(), // ), - Run: bla.Update(viper.GetViper(), &configuration), + Run: func(cmd *cobra.Command, args []string) { + bla2.Update(viper.GetViper(), &configuration)(cmd, args) + }, PreRun: configure.ReadConfigPreRun(viper.GetViper(), &configuration), } ) @@ -49,7 +51,7 @@ var ( type Config struct { config.Config `mapstructure:",squash" configure:"-"` - Database dialect.Config // `configure:"-"` + Database dialect.Config `configure:"-"` Step001 step001.Step001 // runtime config diff --git a/backend/cmd/test.yaml b/backend/cmd/test.yaml index 77e75d0ef4..65b25d458a 100644 --- a/backend/cmd/test.yaml +++ b/backend/cmd/test.yaml @@ -2,5 +2,5 @@ configuredversion: 2025.2.23 database: postgres: host=local step001: - databasename: asdf - username: asdf + databasename: zx;lvkj + username: z.;nv.,mvnzx