|
2 | 2 | package deps
|
3 | 3 |
|
4 | 4 | import (
|
| 5 | + "bytes" |
5 | 6 | "context"
|
6 | 7 | "crypto/rand"
|
7 | 8 | "database/sql"
|
@@ -429,6 +430,11 @@ func LoadConfigWithUpgrades(text string, curioConfigWithDefaults *config.CurioCo
|
429 | 430 | return meta, err
|
430 | 431 | }
|
431 | 432 | func GetConfig(ctx context.Context, layers []string, db *harmonydb.DB) (*config.CurioConfig, error) {
|
| 433 | + err := updateBaseLayer(ctx, db) |
| 434 | + if err != nil { |
| 435 | + return nil, err |
| 436 | + } |
| 437 | + |
432 | 438 | curioConfig := config.DefaultCurioConfig()
|
433 | 439 | have := []string{}
|
434 | 440 | layers = append([]string{"base"}, layers...) // Always stack on top of "base" layer
|
@@ -462,6 +468,131 @@ func GetConfig(ctx context.Context, layers []string, db *harmonydb.DB) (*config.
|
462 | 468 | return curioConfig, nil
|
463 | 469 | }
|
464 | 470 |
|
| 471 | +func updateBaseLayer(ctx context.Context, db *harmonydb.DB) error { |
| 472 | + _, err := db.BeginTransaction(ctx, func(tx *harmonydb.Tx) (commit bool, err error) { |
| 473 | + // Get existing base from DB |
| 474 | + text := "" |
| 475 | + err = tx.QueryRow(`SELECT config FROM harmony_config WHERE title=$1`, "base").Scan(&text) |
| 476 | + if err != nil { |
| 477 | + if strings.Contains(err.Error(), sql.ErrNoRows.Error()) { |
| 478 | + return false, fmt.Errorf("missing layer 'base' ") |
| 479 | + } |
| 480 | + return false, fmt.Errorf("could not read layer 'base': %w", err) |
| 481 | + } |
| 482 | + |
| 483 | + // Load the existing configuration |
| 484 | + cfg := config.DefaultCurioConfig() |
| 485 | + metadata, err := LoadConfigWithUpgrades(text, cfg) |
| 486 | + if err != nil { |
| 487 | + return false, fmt.Errorf("could not read base layer, bad toml %s: %w", text, err) |
| 488 | + } |
| 489 | + |
| 490 | + // Capture unknown fields |
| 491 | + keys := removeUnknownEntries(metadata.Keys(), metadata.Undecoded()) |
| 492 | + unrecognizedFields := extractUnknownFields(keys, text) |
| 493 | + |
| 494 | + // Convert the updated config back to TOML string |
| 495 | + cb, err := config.ConfigUpdate(cfg, config.DefaultCurioConfig(), config.Commented(true), config.DefaultKeepUncommented(), config.NoEnv()) |
| 496 | + if err != nil { |
| 497 | + return false, xerrors.Errorf("cannot update base config: %w", err) |
| 498 | + } |
| 499 | + |
| 500 | + // Merge unknown fields back into the updated config |
| 501 | + finalConfig, err := mergeUnknownFields(string(cb), unrecognizedFields) |
| 502 | + if err != nil { |
| 503 | + return false, xerrors.Errorf("cannot merge unknown fields: %w", err) |
| 504 | + } |
| 505 | + |
| 506 | + // Check if we need to update the DB |
| 507 | + if text == finalConfig { |
| 508 | + return false, nil |
| 509 | + } |
| 510 | + |
| 511 | + // Save the updated base with merged comments |
| 512 | + _, err = tx.Exec("UPDATE harmony_config SET config=$1 WHERE title='base'", finalConfig) |
| 513 | + if err != nil { |
| 514 | + return false, xerrors.Errorf("cannot update base config: %w", err) |
| 515 | + } |
| 516 | + |
| 517 | + return true, nil |
| 518 | + }, harmonydb.OptionRetry()) |
| 519 | + |
| 520 | + if err != nil { |
| 521 | + return err |
| 522 | + } |
| 523 | + |
| 524 | + return nil |
| 525 | +} |
| 526 | + |
| 527 | +func extractUnknownFields(knownKeys []toml.Key, originalConfig string) map[string]interface{} { |
| 528 | + // Parse the original config into a raw map |
| 529 | + var rawConfig map[string]interface{} |
| 530 | + err := toml.Unmarshal([]byte(originalConfig), &rawConfig) |
| 531 | + if err != nil { |
| 532 | + log.Warnw("Failed to parse original config for unknown fields", "error", err) |
| 533 | + return nil |
| 534 | + } |
| 535 | + |
| 536 | + // Collect all recognized keys |
| 537 | + recognizedKeys := map[string]struct{}{} |
| 538 | + for _, key := range knownKeys { |
| 539 | + recognizedKeys[strings.Join(key, ".")] = struct{}{} |
| 540 | + } |
| 541 | + |
| 542 | + // Identify unrecognized fields |
| 543 | + unrecognizedFields := map[string]interface{}{} |
| 544 | + for key, value := range rawConfig { |
| 545 | + if _, recognized := recognizedKeys[key]; !recognized { |
| 546 | + unrecognizedFields[key] = value |
| 547 | + } |
| 548 | + } |
| 549 | + return unrecognizedFields |
| 550 | +} |
| 551 | + |
| 552 | +func removeUnknownEntries(array1, array2 []toml.Key) []toml.Key { |
| 553 | + // Create a set from array2 for fast lookup |
| 554 | + toRemove := make(map[string]struct{}, len(array2)) |
| 555 | + for _, key := range array2 { |
| 556 | + toRemove[key.String()] = struct{}{} |
| 557 | + } |
| 558 | + |
| 559 | + // Filter array1, keeping only elements not in toRemove |
| 560 | + var result []toml.Key |
| 561 | + for _, key := range array1 { |
| 562 | + if _, exists := toRemove[key.String()]; !exists { |
| 563 | + result = append(result, key) |
| 564 | + } |
| 565 | + } |
| 566 | + |
| 567 | + return result |
| 568 | +} |
| 569 | + |
| 570 | +func mergeUnknownFields(updatedConfig string, unrecognizedFields map[string]interface{}) (string, error) { |
| 571 | + // Parse the updated config into a raw map |
| 572 | + var updatedConfigMap map[string]interface{} |
| 573 | + err := toml.Unmarshal([]byte(updatedConfig), &updatedConfigMap) |
| 574 | + if err != nil { |
| 575 | + return "", fmt.Errorf("failed to parse updated config: %w", err) |
| 576 | + } |
| 577 | + |
| 578 | + // Merge unrecognized fields |
| 579 | + for key, value := range unrecognizedFields { |
| 580 | + if _, exists := updatedConfigMap[key]; !exists { |
| 581 | + updatedConfigMap[key] = value |
| 582 | + } |
| 583 | + } |
| 584 | + |
| 585 | + // Convert back into TOML |
| 586 | + b := new(bytes.Buffer) |
| 587 | + encoder := toml.NewEncoder(b) |
| 588 | + err = encoder.Encode(updatedConfigMap) |
| 589 | + if err != nil { |
| 590 | + return "", fmt.Errorf("failed to marshal final config: %w", err) |
| 591 | + } |
| 592 | + |
| 593 | + return b.String(), nil |
| 594 | +} |
| 595 | + |
465 | 596 | func GetDefaultConfig(comment bool) (string, error) {
|
466 | 597 | c := config.DefaultCurioConfig()
|
467 | 598 | cb, err := config.ConfigUpdate(c, nil, config.Commented(comment), config.DefaultKeepUncommented(), config.NoEnv())
|
|
0 commit comments