package utils import ( "fmt" "regexp" "strconv" "strings" ) const ( // VMIDMin is the minimum valid Proxmox VM ID VMIDMin = 100 // VMIDMax is the maximum valid Proxmox VM ID VMIDMax = 999999999 // VMMinMemoryMB is the minimum memory for a VM (128MB) VMMinMemoryMB = 128 // VMMaxMemoryMB is a reasonable maximum memory (2TB) VMMaxMemoryMB = 2 * 1024 * 1024 // VMMinDiskGB is the minimum disk size (1GB) VMMinDiskGB = 1 // VMMaxDiskGB is a reasonable maximum disk size (100TB) VMMaxDiskGB = 100 * 1024 ) // ValidateVMID validates that a VM ID is within valid Proxmox range func ValidateVMID(vmid int) error { if vmid < VMIDMin || vmid > VMIDMax { return fmt.Errorf("VMID %d is out of valid range (%d-%d)", vmid, VMIDMin, VMIDMax) } return nil } // ValidateVMName validates a VM name according to Proxmox restrictions // Proxmox VM names must: // - Be 1-100 characters long // - Only contain alphanumeric characters, hyphens, underscores, dots, and spaces // - Not start or end with spaces func ValidateVMName(name string) error { if len(name) == 0 { return fmt.Errorf("VM name cannot be empty") } if len(name) > 100 { return fmt.Errorf("VM name '%s' exceeds maximum length of 100 characters", name) } // Proxmox allows: alphanumeric, hyphen, underscore, dot, space // But spaces cannot be at start or end name = strings.TrimSpace(name) if len(name) != len(strings.TrimSpace(name)) { return fmt.Errorf("VM name cannot start or end with spaces") } // Valid characters: alphanumeric, hyphen, underscore, dot, space (but not at edges) validPattern := regexp.MustCompile(`^[a-zA-Z0-9._-]+( [a-zA-Z0-9._-]+)*$`) if !validPattern.MatchString(name) { return fmt.Errorf("VM name '%s' contains invalid characters. Allowed: alphanumeric, hyphen, underscore, dot, space", name) } return nil } // ValidateMemory validates memory specification func ValidateMemory(memory string) error { if memory == "" { return fmt.Errorf("memory cannot be empty") } memoryMB := ParseMemoryToMB(memory) if memoryMB < VMMinMemoryMB { return fmt.Errorf("memory %s (%d MB) is below minimum of %d MB", memory, memoryMB, VMMinMemoryMB) } if memoryMB > VMMaxMemoryMB { return fmt.Errorf("memory %s (%d MB) exceeds maximum of %d MB", memory, memoryMB, VMMaxMemoryMB) } return nil } // ValidateDisk validates disk specification func ValidateDisk(disk string) error { if disk == "" { return fmt.Errorf("disk cannot be empty") } diskGB := ParseDiskToGB(disk) if diskGB < VMMinDiskGB { return fmt.Errorf("disk %s (%d GB) is below minimum of %d GB", disk, diskGB, VMMinDiskGB) } if diskGB > VMMaxDiskGB { return fmt.Errorf("disk %s (%d GB) exceeds maximum of %d GB", disk, diskGB, VMMaxDiskGB) } return nil } // ValidateCPU validates CPU count func ValidateCPU(cpu int) error { if cpu < 1 { return fmt.Errorf("CPU count must be at least 1, got %d", cpu) } // Reasonable maximum: 1024 cores if cpu > 1024 { return fmt.Errorf("CPU count %d exceeds maximum of 1024", cpu) } return nil } // ValidateNetworkBridge validates network bridge name format // Network bridges typically follow vmbrX pattern or custom names func ValidateNetworkBridge(network string) error { if network == "" { return fmt.Errorf("network bridge cannot be empty") } // Basic validation: alphanumeric, hyphen, underscore (common bridge naming) validPattern := regexp.MustCompile(`^[a-zA-Z0-9_-]+$`) if !validPattern.MatchString(network) { return fmt.Errorf("network bridge name '%s' contains invalid characters", network) } return nil } // ValidateImageSpec validates image specification format // Images can be: // - Numeric VMID (for template cloning): "123" // - Volid format: "storage:path/to/image" // - Image name: "ubuntu-22.04-cloud" func ValidateImageSpec(image string) error { if image == "" { return fmt.Errorf("image cannot be empty") } // Check if it's a numeric VMID (template) if vmid, err := strconv.Atoi(image); err == nil { if err := ValidateVMID(vmid); err != nil { return fmt.Errorf("invalid template VMID: %w", err) } return nil } // Check if it's a volid format (storage:path) if strings.Contains(image, ":") { parts := strings.SplitN(image, ":", 2) if len(parts) != 2 || parts[0] == "" || parts[1] == "" { return fmt.Errorf("invalid volid format '%s', expected 'storage:path'", image) } return nil } // Otherwise assume it's an image name (validate basic format) if len(image) > 255 { return fmt.Errorf("image name '%s' exceeds maximum length of 255 characters", image) } return nil }