优化
This commit is contained in:
@@ -118,6 +118,9 @@ func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json
|
||||
nextAdmin.Email.Secret = value.Email.Secret
|
||||
}
|
||||
}
|
||||
if err := refreshDatabaseSources(nextData); err != nil {
|
||||
return err
|
||||
}
|
||||
nextAdmin.ConfigPath = currentAdmin.ConfigPath
|
||||
dataRaw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(nextData)
|
||||
if err != nil {
|
||||
@@ -151,6 +154,46 @@ func cloneDataConfig(value *conf.Data) *conf.Data {
|
||||
return proto.Clone(value).(*conf.Data)
|
||||
}
|
||||
|
||||
func refreshDatabaseSources(value *conf.Data) error {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
if err := refreshDatabaseSource(value.Database); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, database := range value.DatabaseList {
|
||||
if database == nil || database.Disable {
|
||||
continue
|
||||
}
|
||||
if err := refreshDatabaseSource(database); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func refreshDatabaseSource(database *conf.Data_Database) error {
|
||||
if database == nil {
|
||||
return nil
|
||||
}
|
||||
hasStructuredConfig := database.Host != "" || database.Port != "" || database.User != "" ||
|
||||
database.Password != "" || database.Name != "" || database.Config != "" || database.Path != ""
|
||||
if !hasStructuredConfig {
|
||||
// A source-only configuration is an intentional escape hatch for custom
|
||||
// driver DSNs; do not reinterpret it as the structured form.
|
||||
return nil
|
||||
}
|
||||
previousSource := database.Source
|
||||
database.Source = ""
|
||||
source, err := databaseDSN(database, "")
|
||||
if err != nil {
|
||||
database.Source = previousSource
|
||||
return err
|
||||
}
|
||||
database.Source = source
|
||||
return nil
|
||||
}
|
||||
|
||||
func durationString(value *durationpb.Duration) string {
|
||||
if value == nil {
|
||||
return "0s"
|
||||
|
||||
@@ -15,7 +15,10 @@ import (
|
||||
)
|
||||
|
||||
func protoMap(message proto.Message) (map[string]any, error) {
|
||||
raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(message)
|
||||
raw, err := (protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
EmitDefaultValues: true,
|
||||
}).Marshal(message)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -44,7 +47,7 @@ func setYAMLMapping(node *yaml.Node, key string, value any) error {
|
||||
}
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
if node.Content[i].Value == key {
|
||||
node.Content[i+1] = replacement.Content[0]
|
||||
mergeYAMLNode(node.Content[i+1], replacement.Content[0])
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -52,6 +55,35 @@ func setYAMLMapping(node *yaml.Node, key string, value any) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// mergeYAMLNode updates values produced from the runtime configuration while
|
||||
// retaining keys that are not represented by the protobuf and comments that
|
||||
// were already present in the user's config file. Replacing an entire mapping
|
||||
// with protojson output would discard configuration hints and extension keys.
|
||||
func mergeYAMLNode(dst, src *yaml.Node) {
|
||||
if dst.Kind == yaml.MappingNode && src.Kind == yaml.MappingNode {
|
||||
for i := 0; i+1 < len(src.Content); i += 2 {
|
||||
key := src.Content[i].Value
|
||||
found := false
|
||||
for j := 0; j+1 < len(dst.Content); j += 2 {
|
||||
if dst.Content[j].Value == key {
|
||||
mergeYAMLNode(dst.Content[j+1], src.Content[i+1])
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
dst.Content = append(dst.Content, src.Content[i], src.Content[i+1])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Keep comments attached to a scalar/sequence node when its value changes.
|
||||
head, line, foot := dst.HeadComment, dst.LineComment, dst.FootComment
|
||||
*dst = *src
|
||||
dst.HeadComment, dst.LineComment, dst.FootComment = head, line, foot
|
||||
}
|
||||
|
||||
func (d *Data) persistConfig() error {
|
||||
dataConfig, adminConfig := d.runtime.Values()
|
||||
return d.persistConfigValues(dataConfig, adminConfig)
|
||||
@@ -90,7 +122,57 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
|
||||
if err = setYAMLMapping(&document, "admin", adminValue); err != nil {
|
||||
return err
|
||||
}
|
||||
output, err := yaml.Marshal(&document)
|
||||
return writeConfigDocument(configPath, &document)
|
||||
}
|
||||
|
||||
// persistDatabaseConfig writes only the database selected on the init page.
|
||||
// Initialization must not serialize the partially populated runtime config
|
||||
// back over the template: doing so removes all omitted/default settings from
|
||||
// data and admin and leaves the next startup with no visible configuration.
|
||||
func (d *Data) persistDatabaseConfig(database *conf.Data_Database) error {
|
||||
d.configMu.Lock()
|
||||
defer d.configMu.Unlock()
|
||||
|
||||
configPath := d.runtime.ConfigPath()
|
||||
if configPath == "" {
|
||||
return nil
|
||||
}
|
||||
raw, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var document yaml.Node
|
||||
if err = yaml.Unmarshal(raw, &document); err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := databaseDSN(database, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Do not marshal the init request through protojson here. Proto3 omits
|
||||
// empty and zero values, which makes a merge retain stale values from the
|
||||
// template (an empty database password is the common example). Update the
|
||||
// fields owned by the init page explicitly and leave advanced database,
|
||||
// Redis, Mongo, admin and server settings untouched.
|
||||
databaseValue := map[string]any{
|
||||
"driver": database.Driver,
|
||||
"source": source,
|
||||
"host": database.Host,
|
||||
"port": database.Port,
|
||||
"user": database.User,
|
||||
"password": database.Password,
|
||||
"name": database.Name,
|
||||
"config": database.Config,
|
||||
"path": database.Path,
|
||||
}
|
||||
if err = setYAMLMapping(&document, "data", map[string]any{"database": databaseValue}); err != nil {
|
||||
return err
|
||||
}
|
||||
return writeConfigDocument(configPath, &document)
|
||||
}
|
||||
|
||||
func writeConfigDocument(configPath string, document *yaml.Node) error {
|
||||
output, err := yaml.Marshal(document)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -68,7 +68,24 @@ func (r *initializationRepo) IsInitialized(ctx context.Context) (bool, error) {
|
||||
}
|
||||
|
||||
func (r *initializationRepo) Initialize(ctx context.Context, input *biz.DatabaseConfig) error {
|
||||
config := &conf.Data_Database{Driver: input.Driver, Host: input.Host, Port: input.Port, User: input.User, Password: input.Password, Name: input.Name, Path: input.Path, Config: input.Config}
|
||||
config := &conf.Data_Database{}
|
||||
if current := r.data.runtime.Data(); current != nil && current.Database != nil {
|
||||
config = proto.Clone(current.Database).(*conf.Data_Database)
|
||||
}
|
||||
config.Driver = input.Driver
|
||||
config.Host = input.Host
|
||||
config.Port = input.Port
|
||||
config.User = input.User
|
||||
config.Password = input.Password
|
||||
config.Name = input.Name
|
||||
config.Path = input.Path
|
||||
config.Config = input.Config
|
||||
config.Source = ""
|
||||
source, err := databaseDSN(config, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
config.Source = source
|
||||
r.data.initMu.Lock()
|
||||
defer r.data.initMu.Unlock()
|
||||
initialized, err := r.IsInitialized(ctx)
|
||||
@@ -197,10 +214,7 @@ func (r *initializationRepo) Initialize(ctx context.Context, input *biz.Database
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
currentData, currentAdmin := r.data.runtime.Values()
|
||||
nextData := proto.Clone(currentData).(*conf.Data)
|
||||
nextData.Database = config
|
||||
if err := r.data.persistConfigValues(nextData, currentAdmin); err != nil {
|
||||
if err := r.data.persistDatabaseConfig(config); err != nil {
|
||||
return fmt.Errorf("persist database configuration: %w", err)
|
||||
}
|
||||
r.data.activateDatabase(candidate, config)
|
||||
|
||||
Reference in New Issue
Block a user