Pulse/pkg/pulsecli/mock.go

220 lines
6.4 KiB
Go

package pulsecli
import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
mockruntime "github.com/rcourtman/pulse-go-rewrite/internal/mock"
"github.com/spf13/cobra"
)
func GetMockEnvPath(mock *MockDeps) string {
dataDir := strings.TrimSpace(os.Getenv("PULSE_DATA_DIR"))
if dataDir != "" {
return filepath.Join(dataDir, ".env")
}
if info, err := mockStat(mock, "tmp/dev-config"); err == nil && info.IsDir() {
return filepath.Join("tmp/dev-config", ".env")
}
probe := filepath.Join(mockDefaultEnvDir(mock), ".env")
if _, err := mockStat(mock, probe); err == nil {
return probe
}
return filepath.Join(config.ResolveRuntimeDataDir(""), ".env")
}
func newMockCmd(mock *MockDeps) *cobra.Command {
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.`,
}
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 runtime .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(mock, true); err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "Error: %v\n", err)
mockExit(mock, 1)
return
}
fmt.Fprintln(cmd.OutOrStdout(), "✓ Mock mode enabled")
fmt.Fprintln(cmd.OutOrStdout())
fmt.Fprintln(cmd.OutOrStdout(), "Restart Pulse to apply changes:")
fmt.Fprintln(cmd.OutOrStdout(), " sudo systemctl restart pulse")
},
}
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 runtime .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(mock, false); err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "Error: %v\n", err)
mockExit(mock, 1)
return
}
fmt.Fprintln(cmd.OutOrStdout(), "✓ Mock mode disabled")
fmt.Fprintln(cmd.OutOrStdout())
fmt.Fprintln(cmd.OutOrStdout(), "Restart Pulse to apply changes:")
fmt.Fprintln(cmd.OutOrStdout(), " sudo systemctl restart pulse")
},
}
mockStatusCmd := &cobra.Command{
Use: "status",
Short: "Show current mock mode status",
Run: func(cmd *cobra.Command, args []string) {
enabled, config := getMockStatus(mock)
if enabled {
fmt.Fprintln(cmd.OutOrStdout(), "Mock mode: ENABLED")
fmt.Fprintln(cmd.OutOrStdout())
fmt.Fprintln(cmd.OutOrStdout(), "Configuration:")
for _, line := range config {
fmt.Fprintf(cmd.OutOrStdout(), " %s\n", line)
}
return
}
fmt.Fprintln(cmd.OutOrStdout(), "Mock mode: DISABLED")
fmt.Fprintln(cmd.OutOrStdout())
fmt.Fprintln(cmd.OutOrStdout(), "Run 'pulse mock enable' to enable mock mode")
},
}
mockCmd.AddCommand(mockEnableCmd)
mockCmd.AddCommand(mockDisableCmd)
mockCmd.AddCommand(mockStatusCmd)
return mockCmd
}
func setMockMode(mock *MockDeps, enable bool) error {
envPath := GetMockEnvPath(mock)
config := getDefaultMockConfig()
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
}
}
}
if enable {
config["PULSE_MOCK_MODE"] = "true"
} else {
config["PULSE_MOCK_MODE"] = "false"
}
return writeMockEnv(envPath, config)
}
func getMockStatus(mock *MockDeps) (bool, []string) {
envPath := GetMockEnvPath(mock)
data, err := os.ReadFile(envPath)
if err != nil {
return false, nil
}
config := parseMockEnv(string(data))
enabled := strings.EqualFold(strings.TrimSpace(config["PULSE_MOCK_MODE"]), "true")
lines := make([]string, 0, len(config))
for key, value := range config {
lines = append(lines, fmt.Sprintf("%s=%s", key, value))
}
return enabled, lines
}
func getDefaultMockConfig() map[string]string {
cfg := mockruntime.DefaultConfig
return map[string]string{
"PULSE_MOCK_MODE": "false",
"PULSE_MOCK_NODES": strconv.Itoa(cfg.NodeCount),
"PULSE_MOCK_VMS_PER_NODE": strconv.Itoa(cfg.VMsPerNode),
"PULSE_MOCK_LXCS_PER_NODE": strconv.Itoa(cfg.LXCsPerNode),
"PULSE_MOCK_DOCKER_HOSTS": strconv.Itoa(cfg.DockerHostCount),
"PULSE_MOCK_DOCKER_CONTAINERS": strconv.Itoa(cfg.DockerContainersPerHost),
"PULSE_MOCK_GENERIC_HOSTS": strconv.Itoa(cfg.GenericHostCount),
"PULSE_MOCK_K8S_CLUSTERS": strconv.Itoa(cfg.K8sClusterCount),
"PULSE_MOCK_K8S_NODES": strconv.Itoa(cfg.K8sNodesPerCluster),
"PULSE_MOCK_K8S_PODS": strconv.Itoa(cfg.K8sPodsPerCluster),
"PULSE_MOCK_K8S_DEPLOYMENTS": strconv.Itoa(cfg.K8sDeploymentsPerCluster),
"PULSE_MOCK_RANDOM_METRICS": strconv.FormatBool(cfg.RandomMetrics),
"PULSE_MOCK_STOPPED_PERCENT": strconv.FormatFloat(cfg.StoppedPercent*100, 'f', -1, 64),
}
}
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
}
func writeMockEnv(envPath string, config map[string]string) error {
var b strings.Builder
b.WriteString("# Pulse Mock Mode Configuration\n")
b.WriteString("# Enable with: pulse mock enable\n")
b.WriteString("# Disable with: pulse mock disable\n\n")
for _, key := range []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",
} {
if value, ok := config[key]; ok {
fmt.Fprintf(&b, "%s=%s\n", key, value)
}
}
for key, value := range config {
if strings.HasPrefix(key, "PULSE_MOCK_") {
continue
}
fmt.Fprintf(&b, "%s=%s\n", key, value)
}
return os.WriteFile(envPath, []byte(b.String()), 0644)
}