mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 20:47:32 +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