- Added lock file exclusions for pnpm in .gitignore. - Removed obsolete package-lock.json from the api and portal directories. - Enhanced Cloudflare adapter with additional interfaces for zones and tunnels. - Improved Proxmox adapter error handling and logging for API requests. - Updated Proxmox VM parameters with validation rules in the API schema. - Enhanced documentation for Proxmox VM specifications and examples.
160 lines
4.5 KiB
Go
160 lines
4.5 KiB
Go
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
|
|
}
|
|
|