mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-19 07:50:43 +01:00
Add ability for users to describe what kind of agent profile they need in natural language, and have AI generate a suggestion with name, description, config values, and rationale. - Add ProfileSuggestionHandler with schema-aware prompting - Add SuggestProfileModal component with example prompts - Update AgentProfilesPanel with suggest button and description field - Streamline ValidConfigKeys to only agent-supported settings - Update profile validation tests for simplified schema
318 lines
8.1 KiB
Go
318 lines
8.1 KiB
Go
package models
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// ConfigKeyDefinition defines a valid configuration key with its type and constraints.
|
|
type ConfigKeyDefinition struct {
|
|
Key string // Config key name
|
|
Type ConfigType // Expected value type
|
|
Description string // Human-readable description
|
|
Default interface{} // Default value (nil if required)
|
|
Required bool // Whether the key is required
|
|
Min *float64 // Minimum value for numbers
|
|
Max *float64 // Maximum value for numbers
|
|
Pattern string // Regex pattern for strings
|
|
Enum []string // Allowed values for enums
|
|
}
|
|
|
|
// ConfigType represents the type of a configuration value.
|
|
type ConfigType string
|
|
|
|
const (
|
|
ConfigTypeString ConfigType = "string"
|
|
ConfigTypeBool ConfigType = "bool"
|
|
ConfigTypeInt ConfigType = "int"
|
|
ConfigTypeFloat ConfigType = "float"
|
|
ConfigTypeDuration ConfigType = "duration"
|
|
ConfigTypeEnum ConfigType = "enum"
|
|
)
|
|
|
|
// ValidConfigKeys defines agent configuration keys that are actually applied by the agent.
|
|
// These match the keys handled in applyRemoteSettings() in cmd/pulse-agent/main.go.
|
|
var ValidConfigKeys = []ConfigKeyDefinition{
|
|
{
|
|
Key: "interval",
|
|
Type: ConfigTypeDuration,
|
|
Description: "Polling interval for metrics collection",
|
|
Default: "30s",
|
|
},
|
|
{
|
|
Key: "enable_host",
|
|
Type: ConfigTypeBool,
|
|
Description: "Enable host monitoring (metrics + command execution)",
|
|
Default: true,
|
|
},
|
|
{
|
|
Key: "enable_docker",
|
|
Type: ConfigTypeBool,
|
|
Description: "Enable Docker container monitoring",
|
|
Default: true,
|
|
},
|
|
{
|
|
Key: "enable_kubernetes",
|
|
Type: ConfigTypeBool,
|
|
Description: "Enable Kubernetes workload monitoring",
|
|
Default: false,
|
|
},
|
|
{
|
|
Key: "enable_proxmox",
|
|
Type: ConfigTypeBool,
|
|
Description: "Enable Proxmox mode for node registration",
|
|
Default: false,
|
|
},
|
|
{
|
|
Key: "proxmox_type",
|
|
Type: ConfigTypeEnum,
|
|
Description: "Proxmox type override (pve or pbs; auto-detect if unset)",
|
|
Default: "auto",
|
|
Enum: []string{"pve", "pbs", "auto"},
|
|
},
|
|
{
|
|
Key: "docker_runtime",
|
|
Type: ConfigTypeEnum,
|
|
Description: "Container runtime preference (auto, docker, podman)",
|
|
Default: "auto",
|
|
Enum: []string{"auto", "docker", "podman"},
|
|
},
|
|
{
|
|
Key: "disable_auto_update",
|
|
Type: ConfigTypeBool,
|
|
Description: "Disable automatic agent updates",
|
|
Default: false,
|
|
},
|
|
{
|
|
Key: "disable_docker_update_checks",
|
|
Type: ConfigTypeBool,
|
|
Description: "Disable Docker image update detection",
|
|
Default: false,
|
|
},
|
|
{
|
|
Key: "kube_include_all_pods",
|
|
Type: ConfigTypeBool,
|
|
Description: "Include all non-succeeded pods in Kubernetes reports",
|
|
Default: false,
|
|
},
|
|
{
|
|
Key: "kube_include_all_deployments",
|
|
Type: ConfigTypeBool,
|
|
Description: "Include all deployments in Kubernetes reports",
|
|
Default: false,
|
|
},
|
|
{
|
|
Key: "log_level",
|
|
Type: ConfigTypeEnum,
|
|
Description: "Agent log verbosity level",
|
|
Default: "info",
|
|
Enum: []string{"debug", "info", "warn", "error"},
|
|
},
|
|
{
|
|
Key: "report_ip",
|
|
Type: ConfigTypeString,
|
|
Description: "Override the reported IP address for the agent",
|
|
Default: "",
|
|
},
|
|
{
|
|
Key: "disable_ceph",
|
|
Type: ConfigTypeBool,
|
|
Description: "Disable local Ceph status polling",
|
|
Default: false,
|
|
},
|
|
}
|
|
|
|
// ValidationError represents a validation error for a config key.
|
|
type ValidationError struct {
|
|
Key string
|
|
Message string
|
|
}
|
|
|
|
func (e ValidationError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Key, e.Message)
|
|
}
|
|
|
|
// ValidationResult holds the result of config validation.
|
|
type ValidationResult struct {
|
|
Valid bool
|
|
Errors []ValidationError
|
|
Warnings []ValidationError
|
|
}
|
|
|
|
// ProfileValidator validates agent profile configurations.
|
|
type ProfileValidator struct {
|
|
keyDefs map[string]ConfigKeyDefinition
|
|
}
|
|
|
|
// NewProfileValidator creates a new profile validator.
|
|
func NewProfileValidator() *ProfileValidator {
|
|
keyDefs := make(map[string]ConfigKeyDefinition)
|
|
for _, def := range ValidConfigKeys {
|
|
keyDefs[def.Key] = def
|
|
}
|
|
return &ProfileValidator{keyDefs: keyDefs}
|
|
}
|
|
|
|
// Validate validates an agent profile configuration.
|
|
func (v *ProfileValidator) Validate(config AgentConfigMap) ValidationResult {
|
|
result := ValidationResult{Valid: true}
|
|
|
|
// Check for unknown keys
|
|
for key := range config {
|
|
if _, ok := v.keyDefs[key]; !ok {
|
|
result.Warnings = append(result.Warnings, ValidationError{
|
|
Key: key,
|
|
Message: "Unknown configuration key (will be ignored by agent)",
|
|
})
|
|
}
|
|
}
|
|
|
|
// Validate known keys
|
|
for key, value := range config {
|
|
def, ok := v.keyDefs[key]
|
|
if !ok {
|
|
continue // Skip unknown keys (already warned)
|
|
}
|
|
|
|
if err := v.validateValue(def, value); err != nil {
|
|
result.Valid = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
Key: key,
|
|
Message: err.Error(),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Check for required keys
|
|
for _, def := range v.keyDefs {
|
|
if def.Required {
|
|
if _, ok := config[def.Key]; !ok {
|
|
result.Valid = false
|
|
result.Errors = append(result.Errors, ValidationError{
|
|
Key: def.Key,
|
|
Message: "Required configuration key is missing",
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// validateValue validates a single configuration value against its definition.
|
|
func (v *ProfileValidator) validateValue(def ConfigKeyDefinition, value interface{}) error {
|
|
if value == nil {
|
|
if def.Required {
|
|
return fmt.Errorf("value cannot be null")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
switch def.Type {
|
|
case ConfigTypeString:
|
|
s, ok := value.(string)
|
|
if !ok {
|
|
return fmt.Errorf("expected string, got %T", value)
|
|
}
|
|
if def.Pattern != "" {
|
|
re, err := regexp.Compile(def.Pattern)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid pattern in definition: %v", err)
|
|
}
|
|
if !re.MatchString(s) {
|
|
return fmt.Errorf("value does not match pattern %s", def.Pattern)
|
|
}
|
|
}
|
|
|
|
case ConfigTypeBool:
|
|
if _, ok := value.(bool); !ok {
|
|
return fmt.Errorf("expected boolean, got %T", value)
|
|
}
|
|
|
|
case ConfigTypeInt:
|
|
var num float64
|
|
switch n := value.(type) {
|
|
case int:
|
|
num = float64(n)
|
|
case int64:
|
|
num = float64(n)
|
|
case float64:
|
|
if n != float64(int64(n)) {
|
|
return fmt.Errorf("expected integer, got float")
|
|
}
|
|
num = n
|
|
default:
|
|
return fmt.Errorf("expected integer, got %T", value)
|
|
}
|
|
if def.Min != nil && num < *def.Min {
|
|
return fmt.Errorf("value %v is below minimum %v", num, *def.Min)
|
|
}
|
|
if def.Max != nil && num > *def.Max {
|
|
return fmt.Errorf("value %v exceeds maximum %v", num, *def.Max)
|
|
}
|
|
|
|
case ConfigTypeFloat:
|
|
var num float64
|
|
switch n := value.(type) {
|
|
case int:
|
|
num = float64(n)
|
|
case int64:
|
|
num = float64(n)
|
|
case float64:
|
|
num = n
|
|
default:
|
|
return fmt.Errorf("expected number, got %T", value)
|
|
}
|
|
if def.Min != nil && num < *def.Min {
|
|
return fmt.Errorf("value %v is below minimum %v", num, *def.Min)
|
|
}
|
|
if def.Max != nil && num > *def.Max {
|
|
return fmt.Errorf("value %v exceeds maximum %v", num, *def.Max)
|
|
}
|
|
|
|
case ConfigTypeDuration:
|
|
s, ok := value.(string)
|
|
if !ok {
|
|
return fmt.Errorf("expected duration string, got %T", value)
|
|
}
|
|
if _, err := time.ParseDuration(s); err != nil {
|
|
return fmt.Errorf("invalid duration format: %v", err)
|
|
}
|
|
|
|
case ConfigTypeEnum:
|
|
s, ok := value.(string)
|
|
if !ok {
|
|
return fmt.Errorf("expected string, got %T", value)
|
|
}
|
|
found := false
|
|
for _, allowed := range def.Enum {
|
|
if strings.EqualFold(s, allowed) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
return fmt.Errorf("value must be one of: %s", strings.Join(def.Enum, ", "))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetConfigKeyDefinitions returns all valid configuration key definitions.
|
|
func GetConfigKeyDefinitions() []ConfigKeyDefinition {
|
|
return ValidConfigKeys
|
|
}
|
|
|
|
// GetConfigKeyDefinition returns the definition for a specific key.
|
|
func GetConfigKeyDefinition(key string) (ConfigKeyDefinition, bool) {
|
|
for _, def := range ValidConfigKeys {
|
|
if def.Key == key {
|
|
return def, true
|
|
}
|
|
}
|
|
return ConfigKeyDefinition{}, false
|
|
}
|