This commit is contained in:
adlerhurst
2025-03-06 17:12:23 +01:00
parent 594152581c
commit 67b22ef9c4
10 changed files with 582 additions and 180 deletions

View File

@@ -1,7 +1,12 @@
package database
import "context"
import (
"context"
"github.com/zitadel/zitadel/backend/cmd/configure/bla4"
)
type Connector interface {
Connect(ctx context.Context) (Pool, error)
bla4.Configurer
}

View File

@@ -5,23 +5,20 @@ import (
"errors"
"reflect"
"github.com/manifoldco/promptui"
"github.com/mitchellh/mapstructure"
"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/bla"
"github.com/zitadel/zitadel/backend/cmd/configure/bla4"
"github.com/zitadel/zitadel/backend/storage/database"
"github.com/zitadel/zitadel/backend/storage/database/dialect/gosql"
"github.com/zitadel/zitadel/backend/storage/database/dialect/postgres"
)
type Hook struct {
Match func(string) bool
Decode func(name string, config any) (database.Connector, error)
Decode func(config any) (database.Connector, error)
Name string
Field configure.Updater
Constructor func() any
Constructor func() database.Connector
}
var hooks = []Hook{
@@ -29,24 +26,65 @@ var hooks = []Hook{
Match: postgres.NameMatcher,
Decode: postgres.DecodeConfig,
Name: postgres.Name,
Field: postgres.Field,
Constructor: func() any { return new(postgres.Config) },
},
{
Match: gosql.NameMatcher,
Decode: gosql.DecodeConfig,
Name: gosql.Name,
Field: gosql.Field,
Constructor: func() any { return new(gosql.Config) },
Constructor: func() database.Connector { return new(postgres.Config) },
},
// {
// Match: gosql.NameMatcher,
// Decode: gosql.DecodeConfig,
// Name: gosql.Name,
// Constructor: func() database.Connector { return new(gosql.Config) },
// },
}
type Config struct {
Dialects dialects `mapstructure:",remain"`
Dialects map[string]any `mapstructure:",remain" yaml:",inline"`
connector database.Connector
}
// Configure implements [configure.Configurer].
func (c *Config) Configure() (any, error) {
possibilities := make([]string, len(hooks))
var cursor int
for i, hook := range hooks {
if _, ok := c.Dialects[hook.Name]; ok {
cursor = i
}
possibilities[i] = hook.Name
}
prompt := promptui.Select{
Label: "Select a dialect",
Items: possibilities,
CursorPos: cursor,
}
i, _, err := prompt.Run()
if err != nil {
return nil, err
}
var config bla4.Configurer
if dialect, ok := c.Dialects[hooks[i].Name]; ok {
config, err = hooks[i].Decode(dialect)
if err != nil {
return nil, err
}
} else {
clear(c.Dialects)
config = hooks[i].Constructor()
}
if c.Dialects == nil {
c.Dialects = make(map[string]any)
}
c.Dialects[hooks[i].Name], err = config.Configure()
if err != nil {
return nil, err
}
return c, nil
}
func (c Config) Connect(ctx context.Context) (database.Pool, error) {
if len(c.Dialects) != 1 {
return nil, errors.New("Exactly one dialect must be configured")
@@ -62,12 +100,6 @@ func (c Config) Hooks() []viper.DecoderConfigOption {
}
}
// var _ configure.StructUpdater = (*Config)(nil)
func (c Config) Configure(v *viper.Viper, currentVersion config.Version) Config {
return c
}
func (c *Config) decodeDialect() error {
for _, hook := range hooks {
for name, config := range c.Dialects {
@@ -75,7 +107,7 @@ func (c *Config) decodeDialect() error {
continue
}
connector, err := hook.Decode(name, config)
connector, err := hook.Decode(config)
if err != nil {
return err
}
@@ -87,7 +119,7 @@ func (c *Config) decodeDialect() error {
return errors.New("no dialect found")
}
func decodeHook(from, to reflect.Value) (_ interface{}, err error) {
func decodeHook(from, to reflect.Value) (_ any, err error) {
if to.Type() != reflect.TypeOf(Config{}) {
return from.Interface(), nil
}
@@ -103,21 +135,3 @@ func decodeHook(from, to reflect.Value) (_ interface{}, err error) {
return config, nil
}
type dialects map[string]any
// ConfigForIndex implements [bla.OneOfField].
func (d dialects) ConfigForIndex(i int) any {
return hooks[i].Constructor()
}
// Possibilities implements [bla.OneOfField].
func (d dialects) Possibilities() []string {
possibilities := make([]string, len(hooks))
for i, hook := range hooks {
possibilities[i] = hook.Name
}
return possibilities
}
var _ bla.OneOfField = (dialects)(nil)

View File

@@ -6,25 +6,13 @@ import (
"errors"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/zitadel/zitadel/backend/cmd/configure"
"github.com/zitadel/zitadel/backend/storage/database"
)
var (
_ database.Connector = (*Config)(nil)
Name = "gosql"
Field = &configure.Field[string]{
Description: "Connection string",
Version: semver.MustParse("v3"),
Validate: func(s string) error {
_, err := pgxpool.ParseConfig(s)
return err
},
}
Name = "gosql"
)
type Config struct {

View File

@@ -6,117 +6,70 @@ import (
"slices"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/manifoldco/promptui"
"github.com/mitchellh/mapstructure"
"github.com/zitadel/zitadel/backend/cmd/configure"
"github.com/zitadel/zitadel/backend/cmd/configure/bla"
"github.com/zitadel/zitadel/backend/storage/database"
)
var (
_ database.Connector = (*Config)(nil)
Name = "postgres"
Field = &configure.OneOf{
Description: "Configuring postgres using one of the following options",
SubFields: []configure.Updater{
&configure.Field[string]{
Description: "Connection string",
Version: semver.MustParse("v3"),
Validate: func(s string) error {
_, err := pgxpool.ParseConfig(s)
return err
},
},
&configure.Struct{
Description: "Configuration for the connection",
SubFields: []configure.Updater{
&configure.Field[string]{
FieldName: "host",
Value: "localhost",
Description: "The host to connect to",
Version: semver.MustParse("3"),
},
&configure.Field[uint32]{
FieldName: "port",
Value: 5432,
Description: "The port to connect to",
Version: semver.MustParse("3"),
},
&configure.Field[string]{
FieldName: "database",
Value: "zitadel",
Description: "The database to connect to",
Version: semver.MustParse("3"),
},
&configure.Field[string]{
FieldName: "user",
Description: "The user to connect as",
Value: "zitadel",
Version: semver.MustParse("3"),
},
&configure.Field[string]{
FieldName: "password",
Description: "The password to connect with",
Version: semver.MustParse("3"),
HideInput: true,
},
&configure.OneOf{
FieldName: "sslMode",
Description: "The SSL mode to use",
SubFields: []configure.Updater{
&configure.Constant[string]{
Description: "Disable",
Constant: "disable",
Version: semver.MustParse("3"),
},
&configure.Constant[string]{
Description: "Require",
Constant: "require",
Version: semver.MustParse("3"),
},
&configure.Constant[string]{
Description: "Verify CA",
Constant: "verify-ca",
Version: semver.MustParse("3"),
},
&configure.Constant[string]{
Description: "Verify Full",
Constant: "verify-full",
Version: semver.MustParse("3"),
},
},
},
},
},
},
}
)
type Config struct{ pgxpool.Config }
type Config struct {
config *pgxpool.Config
// ConfigForIndex implements bla.OneOfField.
func (c Config) ConfigForIndex(i int) any {
switch i {
case 0:
return new(string)
case 1:
return &c.Config
// Host string
// Port int32
// Database string
// EventPushConnRatio float64
// MaxOpenConns uint32
// MaxIdleConns uint32
// MaxConnLifetime time.Duration
// MaxConnIdleTime time.Duration
// User User
// Admin AdminUser
// // Additional options to be appended as options=<Options>
// // The value will be taken as is. Multiple options are space separated.
// Options string
}
// Configure implements bla3.Custom.
func (c *Config) Configure() (value any, err error) {
typeSelect := promptui.Select{
Label: "Configure the database connection",
Items: []string{"connection string", "fields"},
}
i, _, err := typeSelect.Run()
if err != nil {
return nil, err
}
if i > 0 {
return nil, nil
}
return nil
}
// Possibilities implements bla.OneOfField.
func (c Config) Possibilities() []string {
return []string{"connection string", "fields"}
}
if c.config == nil {
c.config, _ = pgxpool.ParseConfig("host=localhost user=zitadel password= dbname=zitadel sslmode=disable")
}
var _ bla.OneOfField = (*Config)(nil)
prompt := promptui.Prompt{
Label: "Connection string",
Default: c.config.ConnString(),
AllowEdit: c.config.ConnString() != "",
Validate: func(input string) error {
_, err := pgxpool.ParseConfig(input)
return err
},
}
return prompt.Run()
}
// Connect implements [database.Connector].
func (c *Config) Connect(ctx context.Context) (database.Pool, error) {
pool, err := pgxpool.NewWithConfig(ctx, &c.Config)
pool, err := pgxpool.NewWithConfig(ctx, c.config)
if err != nil {
return nil, err
}
@@ -130,17 +83,29 @@ func NameMatcher(name string) bool {
return slices.Contains([]string{"postgres", "pg"}, strings.ToLower(name))
}
func DecodeConfig(_ string, config any) (database.Connector, error) {
switch c := config.(type) {
func DecodeConfig(input any) (database.Connector, error) {
switch c := input.(type) {
case string:
config, err := pgxpool.ParseConfig(c)
if err != nil {
return nil, err
}
return &Config{Config: *config}, nil
return &Config{config: config}, nil
case map[string]any:
connector := new(Config)
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
WeaklyTypedInput: true,
Result: connector,
})
if err != nil {
return nil, err
}
if err = decoder.Decode(c); err != nil {
return nil, err
}
return &Config{
Config: pgxpool.Config{},
config: &pgxpool.Config{},
}, nil
}
return nil, errors.New("invalid configuration")