Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impv: weave config structure and migration #136

Open
wants to merge 5 commits into
base: main
Choose a base branch
from

Conversation

Benzbeeb
Copy link
Collaborator

@Benzbeeb Benzbeeb commented Feb 5, 2025

Description

Closes: #XXXX


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow-up issues.

I have...

  • included the correct type prefix in the PR title, you can find examples of the prefixes below:
  • confirmed ! in the type prefix if API or client breaking change
  • targeted the correct branch
  • provided a link to the relevant issue or specification
  • reviewed "Files changed" and left comments if necessary
  • included the necessary unit and integration tests
  • updated the relevant documentation or specification, including comments for documenting Go code
  • confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.

I have...

  • confirmed the correct type prefix in the PR title
  • confirmed all author checklist items have been addressed
  • reviewed state machine logic, API design and naming, documentation is accurate, tests and test coverage

Summary by CodeRabbit

  • New Features
    • Introduced configuration versioning and a migration process with a new default template, ensuring smoother upgrades and enhanced backward compatibility.
    • Enhanced configuration management for more reliable operations during setup and runtime.
  • Refactor
    • Streamlined configuration keys for features like gas station and analytics by removing redundant prefixes.
    • Revised key file naming conventions to promote clarity and consistency.

Copy link
Contributor

coderabbitai bot commented Feb 5, 2025

Walkthrough

This pull request revises the configuration key naming conventions across the codebase. The "common." prefix has been removed from the gas station configuration keys in several files, and the key used for referencing the gas station mnemonic in JSON tests has been updated. Additionally, key file constants in the common module have been renamed, and the configuration management system has been enhanced with version migration functions and updated analytics keys. No public API declarations were altered.

Changes

File(s) Change Summary
cmd/gas_station_integration_test.go Updated JSON key for gas station mnemonic from "common.gas_station.mnemonic" to "gas_station.mnemonic".
common/constants.go Renamed key file path constants from "/weave.keys.json" to "/weave.keyfile.json" (including Hermes key file path).
config/config.go Enhanced config management: added MigrateConfigV1 and GetConfigVersion, updated analytics keys from "common.analytics_*" to "analytics.*", and changed gas station key references.
models/initialize.go, models/minitia/launch.go Modified gas station configuration key from "common.gas_station" to "gas_station" in initialization routines.
models/minitia/launch_test.go Updated test configuration key from "common.gas_station" to "gas_station".

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Config as Config Module
    App->>Config: LoadConfig()
    Config->>Config: Check configuration version
    alt Migration required
        Config->>Config: MigrateConfigV1()
    end
    Config->>App: Return updated configuration
Loading

Possibly related PRs

Suggested reviewers

  • traviolus
  • WasinWatt

Poem

I hopped through the code in a joyful race,
Changing keys with precision and grace.
No more "common."—the config's now bright,
Reflecting a clever, clean new light.
With every line, my heart does sing,
A bunny's cheer for each refreshing spring! 🐰
Hooray for changes that make our code zing!


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c1a02ef and 233a90d.

📒 Files selected for processing (1)
  • config/config.go (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run Integration Tests on Ubuntu
🔇 Additional comments (2)
config/config.go (2)

66-74: LGTM! Good error handling for config migration.

The migration is correctly placed after loading the config and includes proper error handling.


183-234: LGTM! Well-structured migration logic.

The migration function is well-implemented with:

  • Proper version checking
  • Data preservation during migration
  • Clean config structure reset
  • Good error handling
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

config/config.go Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (2)
models/minitia/launch.go (2)

2899-2923: Refactor log streaming logic into a reusable function.

The log streaming logic is duplicated for stdout and stderr. Consider extracting it into a reusable function to reduce code duplication.

+func streamLogs(reader io.Reader, streamingLogs *[]string) {
+	scanner := bufio.NewScanner(reader)
+	for scanner.Scan() {
+		line := scanner.Text()
+		if !isJSONLog(line) {
+			*streamingLogs = append(*streamingLogs, line)
+			if len(*streamingLogs) > 10 {
+				*streamingLogs = (*streamingLogs)[1:]
+			}
+		}
+	}
+}

-		go func() {
-			scanner := bufio.NewScanner(stdout)
-			for scanner.Scan() {
-				line := scanner.Text()
-				if !isJSONLog(line) {
-					*streamingLogs = append(*streamingLogs, line)
-					if len(*streamingLogs) > 10 {
-						*streamingLogs = (*streamingLogs)[1:]
-					}
-				}
-			}
-		}()
-
-		go func() {
-			scanner := bufio.NewScanner(stderr)
-			for scanner.Scan() {
-				line := scanner.Text()
-				if !isJSONLog(line) {
-					*streamingLogs = append(*streamingLogs, line)
-					if len(*streamingLogs) > 10 {
-						*streamingLogs = (*streamingLogs)[1:]
-					}
-				}
-			}
-		}()
+		go streamLogs(stdout, streamingLogs)
+		go streamLogs(stderr, streamingLogs)

2787-2788: Add mutex for thread-safe log handling.

The streamingLogs slice is accessed from multiple goroutines without synchronization. This could lead to race conditions.

 type LaunchingNewMinitiaLoading struct {
 	ui.Loading
 	weavecontext.BaseModel
+	logsMutex sync.Mutex
 	streamingLogs *[]string
 }

And update the log handling:

+func (m *LaunchingNewMinitiaLoading) appendLog(line string) {
+	m.logsMutex.Lock()
+	defer m.logsMutex.Unlock()
+	*m.streamingLogs = append(*m.streamingLogs, line)
+	if len(*m.streamingLogs) > 10 {
+		*m.streamingLogs = (*m.streamingLogs)[1:]
+	}
+}
🧹 Nitpick comments (5)
config/config.go (2)

124-125: Avoid silently discarding the SetConfig error.
Capturing and handling the return value from SetConfig("analytics.opt_out", false) can help detect file I/O or permission issues.


133-135: Consider error handling for SetConfig.
Ignoring the error returned from SetConfig("analytics.device_id", deviceID) could hide potential failures in saving the device ID.

models/minitia/launch.go (3)

1290-1297: Improve error handling consistency.

The error handling pattern here could be improved. The first error is handled with m.HandlePanic, but the second error is returned directly. Consider using consistent error handling patterns throughout the code.

-		gasStationKey, err := config.RecoverGasStationKey(input.Text)
-		if err != nil {
-			return m, m.HandlePanic(err)
-		}
-		err = config.SetConfig("gas_station", gasStationKey)
-		if err != nil {
-			return m, m.HandlePanic(err)
-		}
+		if gasStationKey, err := config.RecoverGasStationKey(input.Text); err != nil {
+			return m, m.HandlePanic(fmt.Errorf("failed to recover gas station key: %w", err))
+		} else if err = config.SetConfig("gas_station", gasStationKey); err != nil {
+			return m, m.HandlePanic(fmt.Errorf("failed to set gas station config: %w", err))
+		}

2874-2878: Ensure secure file permissions for sensitive data.

The code sets file permissions to 0600 for the config file, which is good. However, consider adding a check to verify that the parent directory also has secure permissions to prevent unauthorized access.

 			configFilePath = filepath.Join(userHome, common.WeaveDataDirectory, LaunchConfigFilename)
+			// Ensure parent directory has secure permissions
+			if err = os.Chmod(filepath.Dir(configFilePath), 0700); err != nil {
+				return ui.NonRetryableErrorLoading{Err: fmt.Errorf("failed to set directory permissions: %v", err)}
+			}
 			if err = os.WriteFile(configFilePath, configBz, 0600); err != nil {
 				return ui.NonRetryableErrorLoading{Err: fmt.Errorf("failed to write config file: %v", err)}
 			}

2885-2893: Extract command execution logic to a separate function.

The command execution setup logic is mixed with the business logic. Consider extracting it to a separate function for better maintainability and reusability.

+func setupCommandWithPipes(cmd *exec.Cmd) (stdout, stderr io.ReadCloser, err error) {
+	if stdout, err = cmd.StdoutPipe(); err != nil {
+		return nil, nil, fmt.Errorf("failed to capture stdout: %v", err)
+	}
+	if stderr, err = cmd.StderrPipe(); err != nil {
+		return nil, nil, fmt.Errorf("failed to capture stderr: %v", err)
+	}
+	return stdout, stderr, nil
+}

 		launchCmd := exec.Command(state.binaryPath, "launch", "--with-config", configFilePath, "--home", minitiaHome)
-		stdout, err := launchCmd.StdoutPipe()
-		if err != nil {
-			return ui.NonRetryableErrorLoading{Err: fmt.Errorf("failed to capture stdout: %v", err)}
-		}
-		stderr, err := launchCmd.StderrPipe()
-		if err != nil {
-			return ui.NonRetryableErrorLoading{Err: fmt.Errorf("failed to capture stderr: %v", err)}
-		}
+		stdout, stderr, err := setupCommandWithPipes(launchCmd)
+		if err != nil {
+			return ui.NonRetryableErrorLoading{Err: err}
+		}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce82e16 and 87064b3.

📒 Files selected for processing (6)
  • cmd/gas_station_integration_test.go (1 hunks)
  • common/constants.go (1 hunks)
  • config/config.go (5 hunks)
  • models/initialize.go (1 hunks)
  • models/minitia/launch.go (1 hunks)
  • models/minitia/launch_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • common/constants.go
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run Integration Tests on Ubuntu
🔇 Additional comments (11)
config/config.go (7)

13-13: No concerns with the new import.
No issues spotted; the package might be necessary for the recently introduced I/O operations.


71-74: Well-structured call to the new migration function.
Invoking MigrateConfigV1 immediately after reading the config clearly separates reading from migration.


95-95: Possible edge case in first-time setup check.
Relying solely on "gas_station" being nil may misclassify certain scenarios (e.g., if the user or a script clears the key).


103-103: Ensure JSON-serializable data.
Calling json.Marshal on data retrieved via GetConfig("gas_station") could fail if the value is stored in a non-serializable format.


143-143: Good practice returning SetConfig error.
Unlike the earlier occurrences, here the function returns the error, ensuring proper error handling.


146-149: Clear default configuration template.
Providing a version field and initializing analytics is beneficial for forward compatibility.


174-219: 🛠️ Refactor suggestion

Robust migration function with minor type safety concern.
While this approach correctly preserves and restructures config data, a direct float64 type assertion may cause panics if a corrupted or non-numeric version is stored. Also, be mindful of potential concurrency issues if more than one process can trigger migrations.

cmd/gas_station_integration_test.go (1)

41-41: Test aligned with the updated key.
Switching the key to "gas_station.mnemonic" maintains consistency with the newly migrated config structure.

models/initialize.go (1)

325-325: Key rename to “gas_station” aligns with the migration.
Ensuring all references to the old "common.gas_station" key are replaced helps maintain consistency.

models/minitia/launch_test.go (1)

1038-1038: LGTM! Configuration key change is consistent.

The removal of the "common." prefix from the gas station configuration key aligns with the broader changes in the codebase.

models/minitia/launch.go (1)

1294-1294: Configuration key has been simplified.

The configuration key has been changed from "common.gas_station" to "gas_station", aligning with the changes mentioned in the AI summary. This change appears to be part of a broader effort to simplify configuration key naming conventions.

config/config.go Show resolved Hide resolved
config/config.go Show resolved Hide resolved
models/minitia/launch_test.go Show resolved Hide resolved
@Benzbeeb Benzbeeb requested a review from traviolus February 5, 2025 11:16
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
config/config.go (1)

129-129: ⚠️ Potential issue

Add type assertion safety checks.

The code contains unsafe type assertions that could panic if the config values are of unexpected types.

Apply these changes to add safety checks:

-    return GetConfig("analytics.opt_out").(bool)
+    if val, ok := GetConfig("analytics.opt_out").(bool); ok {
+        return val
+    }
+    return false  // Default to false if type assertion fails

-    return GetConfig("analytics.device_id").(string)
+    if val, ok := GetConfig("analytics.device_id").(string); ok {
+        return val
+    }
+    deviceID := uuid.New().String()
+    _ = SetConfig("analytics.device_id", deviceID)
+    return deviceID

Also applies to: 139-139

🧹 Nitpick comments (3)
config/config.go (3)

146-149: Consider using a struct for DefaultConfigTemplate.

Instead of hardcoding the JSON template as a string, consider defining it as a struct and marshaling it to JSON. This would:

  • Make the structure type-safe
  • Allow compile-time validation
  • Make it easier to maintain and modify

Here's a suggested implementation:

-const DefaultConfigTemplate = `{
-    "version": 1,
-    "analytics": {}
-}`
+type Config struct {
+    Version   int                    `json:"version"`
+    Analytics map[string]interface{} `json:"analytics"`
+}
+
+var DefaultConfigTemplate = func() string {
+    config := Config{
+        Version:   1,
+        Analytics: make(map[string]interface{}),
+    }
+    bytes, _ := json.MarshalIndent(config, "", "  ")
+    return string(bytes)
+}()

66-76: Consider atomic config migration.

If migration fails, the config might be left in an inconsistent state. Consider:

  1. Backing up the config before migration
  2. Restoring the backup if migration fails

Here's a suggested implementation:

 func LoadConfig() error {
     if err := viper.ReadInConfig(); err != nil {
         return fmt.Errorf("failed to read config file: %v", err)
     }

+    // Backup current config
+    backup := viper.AllSettings()
+
     if err := MigrateConfigV1(); err != nil {
+        // Restore backup on failure
+        for k, v := range backup {
+            viper.Set(k, v)
+        }
         return err
     }

     return nil
 }

183-219: Enhance migration robustness and observability.

Consider adding:

  1. Validation of migrated data
  2. Logging of migration progress
  3. Backup of old config before migration

Here's a suggested implementation:

 func MigrateConfigV1() error {
     version := GetConfigVersion()
     if version == 1 {
         return nil // Already at latest version
     }

     // Migrate from version 0 to 1
     if version == 0 {
+        fmt.Printf("Migrating config from version 0 to 1...\n")
+
         // Preserve existing data
         gasStation := GetConfig("common.gas_station")
         analyticsOptOut := GetConfig("common.analytics_opt_out")
         analyticsDeviceID := GetConfig("common.analytics_device_id")

+        // Validate data before migration
+        if gasStation != nil {
+            if _, ok := gasStation.(map[string]interface{}); !ok {
+                return fmt.Errorf("invalid gas_station data format")
+            }
+        }
+
         // Clear the config
         viper.Set("version", 1)
         viper.Set("gas_station", map[string]interface{}{})
         viper.Set("analytics", map[string]interface{}{})
         viper.Set("common", nil)

         // Restore the data
         if gasStation != nil {
             viper.Set("gas_station", gasStation)
         }
         if analyticsOptOut != nil {
             viper.Set("analytics.opt_out", analyticsOptOut)
         }
         if analyticsDeviceID != nil {
             viper.Set("analytics.device_id", analyticsDeviceID)
         }

         if err := WriteConfig(); err != nil {
             return fmt.Errorf("failed to migrate config to version 1: %v", err)
         }
+
+        fmt.Printf("Successfully migrated config to version 1\n")
     }

     return nil
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87064b3 and 83ab2ca.

📒 Files selected for processing (1)
  • config/config.go (5 hunks)

config/config.go Outdated Show resolved Hide resolved
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
config/config.go (2)

103-113: Simplify JSON conversion.

The double JSON conversion (Marshal then Unmarshal) is inefficient. Consider using direct type assertion with a safety check.

-    data := GetConfig("gas_station")
-    jsonData, err := json.Marshal(data)
-    if err != nil {
-        return nil, fmt.Errorf("failed to marshal json: %v", err)
-    }
-
-    var gasKey GasStationKey
-    err = json.Unmarshal(jsonData, &gasKey)
-    if err != nil {
-        return nil, fmt.Errorf("failed to unmarshal json: %v", err)
-    }
+    data := GetConfig("gas_station")
+    if data == nil {
+        return nil, fmt.Errorf("gas station key not found")
+    }
+    
+    if mapData, ok := data.(map[string]interface{}); ok {
+        gasKey := GasStationKey{
+            InitiaAddress:   fmt.Sprint(mapData["initia_address"]),
+            CelestiaAddress: fmt.Sprint(mapData["celestia_address"]),
+            Mnemonic:        fmt.Sprint(mapData["mnemonic"]),
+        }
+        return &gasKey, nil
+    }
+    return nil, fmt.Errorf("invalid gas station key format")

183-219: Consider atomic updates for config migration.

While the migration logic is correct, a failure during migration could leave the config in an inconsistent state. Consider implementing a transaction-like behavior.

 func MigrateConfigV1() error {
     version := GetConfigVersion()
     if version == 1 {
         return nil // Already at latest version
     }

     // Migrate from version 0 to 1
     if version == 0 {
+        // Create a copy of the current config
+        tempConfig := make(map[string]interface{})
+        for k, v := range viper.AllSettings() {
+            tempConfig[k] = v
+        }
+
         // Preserve existing data
         gasStation := GetConfig("common.gas_station")
         analyticsOptOut := GetConfig("common.analytics_opt_out")
         analyticsDeviceID := GetConfig("common.analytics_device_id")

         // Clear the config
         viper.Set("version", 1)
         viper.Set("gas_station", map[string]interface{}{})
         viper.Set("analytics", map[string]interface{}{})
         viper.Set("common", nil)

         // Restore the data
         if gasStation != nil {
             viper.Set("gas_station", gasStation)
         }
         if analyticsOptOut != nil {
             viper.Set("analytics.opt_out", analyticsOptOut)
         }
         if analyticsDeviceID != nil {
             viper.Set("analytics.device_id", analyticsDeviceID)
         }

         if err := WriteConfig(); err != nil {
+            // Restore the original config on failure
+            for k, v := range tempConfig {
+                viper.Set(k, v)
+            }
+            _ = WriteConfig()  // Best effort to restore
             return fmt.Errorf("failed to migrate config to version 1: %v", err)
         }
     }

     return nil
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 83ab2ca and c1a02ef.

📒 Files selected for processing (1)
  • config/config.go (5 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Run Integration Tests on Ubuntu
🔇 Additional comments (4)
config/config.go (4)

70-73: LGTM! Migration is correctly placed.

The migration is appropriately called after loading the config and includes proper error handling.


129-129: Guard against unsafe type assertion.

Casting GetConfig("analytics.opt_out") to bool without a check may lead to a panic if the config is malformed.


139-139: Unsafe string assertion.

Casting GetConfig("analytics.device_id") to string could panic if someone modifies the config to an unexpected type.


146-149: LGTM! Template structure is well-defined.

The template correctly includes version and analytics fields, aligning with the new configuration structure.

config/config.go Show resolved Hide resolved
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 5, 2025
traviolus
traviolus previously approved these changes Feb 5, 2025
@Benzbeeb Benzbeeb dismissed stale reviews from traviolus and coderabbitai[bot] via 233a90d February 5, 2025 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants