Pulse/cmd/pulse/mock.go
rcourtman 9e339957c6 fix: Update runtime config when toggling Docker update actions setting
The DisableDockerUpdateActions setting was being saved to disk but not
updated in h.config, causing the UI toggle to appear to revert on page
refresh since the API returned the stale runtime value.

Related to #1023
2026-01-03 11:14:17 +00:00

244 lines
6.3 KiB
Go

package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var mockCmd = &cobra.Command{
Use: "mock",
Short: "Manage mock/demo mode for development and demos",
Long: `Enable or disable mock mode to run Pulse with simulated data instead of real infrastructure.`,
}
var mockEnableCmd = &cobra.Command{
Use: "enable",
Short: "Enable mock mode with simulated infrastructure data",
Long: `Enable mock mode to run Pulse with simulated data.
This creates/updates the mock.env file and requires a service restart.
Mock mode is useful for:
- Demos without real infrastructure
- Development and testing
- Showcasing AI patrol features
Example:
pulse mock enable
sudo systemctl restart pulse`,
Run: func(cmd *cobra.Command, args []string) {
if err := setMockMode(true); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
osExit(1)
return
}
fmt.Println("✓ Mock mode enabled")
fmt.Println("")
fmt.Println("Restart Pulse to apply changes:")
fmt.Println(" sudo systemctl restart pulse")
},
}
var mockDisableCmd = &cobra.Command{
Use: "disable",
Short: "Disable mock mode and use real infrastructure",
Long: `Disable mock mode to reconnect to real infrastructure.
This updates the mock.env file and requires a service restart.
Example:
pulse mock disable
sudo systemctl restart pulse`,
Run: func(cmd *cobra.Command, args []string) {
if err := setMockMode(false); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
osExit(1)
return
}
fmt.Println("✓ Mock mode disabled")
fmt.Println("")
fmt.Println("Restart Pulse to apply changes:")
fmt.Println(" sudo systemctl restart pulse")
},
}
var mockStatusCmd = &cobra.Command{
Use: "status",
Short: "Show current mock mode status",
Run: func(cmd *cobra.Command, args []string) {
enabled, config := getMockStatus()
if enabled {
fmt.Println("Mock mode: ENABLED")
fmt.Println("")
fmt.Println("Configuration:")
for _, line := range config {
fmt.Printf(" %s\n", line)
}
} else {
fmt.Println("Mock mode: DISABLED")
fmt.Println("")
fmt.Println("Run 'pulse mock enable' to enable mock mode")
}
},
}
func init() {
mockCmd.AddCommand(mockEnableCmd)
mockCmd.AddCommand(mockDisableCmd)
mockCmd.AddCommand(mockStatusCmd)
rootCmd.AddCommand(mockCmd)
}
// getMockEnvPath returns the path to mock.env
func getMockEnvPath() string {
// Check PULSE_DATA_DIR first, then fall back to /opt/pulse
dataDir := os.Getenv("PULSE_DATA_DIR")
if dataDir == "" {
// Check if we're in development (running from /opt/pulse)
if _, err := os.Stat("/opt/pulse/mock.env"); err == nil {
return "/opt/pulse/mock.env"
}
dataDir = "/opt/pulse"
}
return filepath.Join(dataDir, "mock.env")
}
// setMockMode enables or disables mock mode by updating mock.env
func setMockMode(enable bool) error {
envPath := getMockEnvPath()
// Read existing config or create default
config := getDefaultMockConfig()
// Try to preserve existing config
if data, err := os.ReadFile(envPath); err == nil {
existing := parseMockEnv(string(data))
for k, v := range existing {
if k != "PULSE_MOCK_MODE" {
config[k] = v
}
}
}
// Set the mode
if enable {
config["PULSE_MOCK_MODE"] = "true"
} else {
config["PULSE_MOCK_MODE"] = "false"
}
// Write the file
return writeMockEnv(envPath, config)
}
// getDefaultMockConfig returns the default mock configuration
func getDefaultMockConfig() map[string]string {
return map[string]string{
"PULSE_MOCK_MODE": "false",
"PULSE_MOCK_NODES": "7",
"PULSE_MOCK_VMS_PER_NODE": "5",
"PULSE_MOCK_LXCS_PER_NODE": "8",
"PULSE_MOCK_DOCKER_HOSTS": "3",
"PULSE_MOCK_DOCKER_CONTAINERS": "12",
"PULSE_MOCK_GENERIC_HOSTS": "4",
"PULSE_MOCK_K8S_CLUSTERS": "2",
"PULSE_MOCK_K8S_NODES": "4",
"PULSE_MOCK_K8S_PODS": "30",
"PULSE_MOCK_K8S_DEPLOYMENTS": "12",
"PULSE_MOCK_RANDOM_METRICS": "true",
"PULSE_MOCK_STOPPED_PERCENT": "20",
}
}
// parseMockEnv parses a mock.env file into a map
func parseMockEnv(content string) map[string]string {
result := make(map[string]string)
for _, line := range strings.Split(content, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
result[parts[0]] = parts[1]
}
}
return result
}
// writeMockEnv writes the mock.env file
func writeMockEnv(path string, config map[string]string) error {
// Order keys for consistent output
keys := []string{
"PULSE_MOCK_MODE",
"PULSE_MOCK_NODES",
"PULSE_MOCK_VMS_PER_NODE",
"PULSE_MOCK_LXCS_PER_NODE",
"PULSE_MOCK_DOCKER_HOSTS",
"PULSE_MOCK_DOCKER_CONTAINERS",
"PULSE_MOCK_GENERIC_HOSTS",
"PULSE_MOCK_K8S_CLUSTERS",
"PULSE_MOCK_K8S_NODES",
"PULSE_MOCK_K8S_PODS",
"PULSE_MOCK_K8S_DEPLOYMENTS",
"PULSE_MOCK_RANDOM_METRICS",
"PULSE_MOCK_STOPPED_PERCENT",
}
var lines []string
lines = append(lines, "# Pulse Mock Mode Configuration")
lines = append(lines, "# Enable with: pulse mock enable")
lines = append(lines, "# Disable with: pulse mock disable")
lines = append(lines, "")
for _, key := range keys {
if val, ok := config[key]; ok {
lines = append(lines, fmt.Sprintf("%s=%s", key, val))
}
}
// Add any extra keys not in our ordered list
for k, v := range config {
found := false
for _, key := range keys {
if k == key {
found = true
break
}
}
if !found {
lines = append(lines, fmt.Sprintf("%s=%s", k, v))
}
}
content := strings.Join(lines, "\n") + "\n"
return os.WriteFile(path, []byte(content), 0644)
}
// getMockStatus returns the current mock mode status and config
func getMockStatus() (enabled bool, config []string) {
envPath := getMockEnvPath()
data, err := os.ReadFile(envPath)
if err != nil {
return false, nil
}
parsed := parseMockEnv(string(data))
enabled = parsed["PULSE_MOCK_MODE"] == "true"
if enabled {
config = []string{
fmt.Sprintf("Nodes: %s", parsed["PULSE_MOCK_NODES"]),
fmt.Sprintf("VMs per node: %s", parsed["PULSE_MOCK_VMS_PER_NODE"]),
fmt.Sprintf("Containers per node: %s", parsed["PULSE_MOCK_LXCS_PER_NODE"]),
fmt.Sprintf("Docker hosts: %s", parsed["PULSE_MOCK_DOCKER_HOSTS"]),
fmt.Sprintf("K8s clusters: %s", parsed["PULSE_MOCK_K8S_CLUSTERS"]),
}
}
return enabled, config
}