Update .gitignore, remove package-lock.json, and enhance Cloudflare and Proxmox adapters
- 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.
This commit is contained in:
88
crossplane-provider-proxmox/pkg/utils/parsing.go
Normal file
88
crossplane-provider-proxmox/pkg/utils/parsing.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseMemoryToMB parses a memory string and returns the value in MB
|
||||
// Supports: Gi, Mi, Ki, G, M, K (case-insensitive) or plain numbers (assumed MB)
|
||||
func ParseMemoryToMB(memory string) int {
|
||||
if len(memory) == 0 {
|
||||
return 4096 // Default: 4GB
|
||||
}
|
||||
|
||||
// Remove whitespace and convert to lowercase for case-insensitive parsing
|
||||
memory = strings.TrimSpace(strings.ToLower(memory))
|
||||
|
||||
// Check for unit suffix (case-insensitive)
|
||||
if strings.HasSuffix(memory, "gi") || strings.HasSuffix(memory, "g") {
|
||||
value, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimSuffix(memory, "gi"), "g"), 64)
|
||||
if err == nil {
|
||||
return int(value * 1024) // Convert GiB to MB
|
||||
}
|
||||
} else if strings.HasSuffix(memory, "mi") || strings.HasSuffix(memory, "m") {
|
||||
value, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimSuffix(memory, "mi"), "m"), 64)
|
||||
if err == nil {
|
||||
return int(value) // Already in MB
|
||||
}
|
||||
} else if strings.HasSuffix(memory, "ki") || strings.HasSuffix(memory, "k") {
|
||||
value, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimSuffix(memory, "ki"), "k"), 64)
|
||||
if err == nil {
|
||||
return int(value / 1024) // Convert KiB to MB
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing as number (assume MB)
|
||||
value, err := strconv.Atoi(memory)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
|
||||
return 4096 // Default if parsing fails
|
||||
}
|
||||
|
||||
// ParseMemoryToGB parses a memory string and returns the value in GB
|
||||
// Supports: Gi, Mi, Ki, G, M, K (case-insensitive) or plain numbers (assumed GB)
|
||||
func ParseMemoryToGB(memory string) int {
|
||||
memoryMB := ParseMemoryToMB(memory)
|
||||
return memoryMB / 1024 // Convert MB to GB
|
||||
}
|
||||
|
||||
// ParseDiskToGB parses a disk string and returns the value in GB
|
||||
// Supports: Ti, Gi, Mi, T, G, M (case-insensitive) or plain numbers (assumed GB)
|
||||
func ParseDiskToGB(disk string) int {
|
||||
if len(disk) == 0 {
|
||||
return 50 // Default: 50GB
|
||||
}
|
||||
|
||||
// Remove whitespace and convert to lowercase for case-insensitive parsing
|
||||
disk = strings.TrimSpace(strings.ToLower(disk))
|
||||
|
||||
// Check for unit suffix (case-insensitive)
|
||||
if strings.HasSuffix(disk, "ti") || strings.HasSuffix(disk, "t") {
|
||||
value, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimSuffix(disk, "ti"), "t"), 64)
|
||||
if err == nil {
|
||||
return int(value * 1024) // Convert TiB to GB
|
||||
}
|
||||
} else if strings.HasSuffix(disk, "gi") || strings.HasSuffix(disk, "g") {
|
||||
value, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimSuffix(disk, "gi"), "g"), 64)
|
||||
if err == nil {
|
||||
return int(value) // Already in GB
|
||||
}
|
||||
} else if strings.HasSuffix(disk, "mi") || strings.HasSuffix(disk, "m") {
|
||||
value, err := strconv.ParseFloat(strings.TrimSuffix(strings.TrimSuffix(disk, "mi"), "m"), 64)
|
||||
if err == nil {
|
||||
return int(value / 1024) // Convert MiB to GB
|
||||
}
|
||||
}
|
||||
|
||||
// Try parsing as number (assume GB)
|
||||
value, err := strconv.Atoi(disk)
|
||||
if err == nil {
|
||||
return value
|
||||
}
|
||||
|
||||
return 50 // Default if parsing fails
|
||||
}
|
||||
|
||||
184
crossplane-provider-proxmox/pkg/utils/parsing_test.go
Normal file
184
crossplane-provider-proxmox/pkg/utils/parsing_test.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestParseMemoryToMB(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected int
|
||||
}{
|
||||
// GiB format (case-insensitive)
|
||||
{"4Gi", "4Gi", 4 * 1024},
|
||||
{"4GI", "4GI", 4 * 1024},
|
||||
{"4gi", "4gi", 4 * 1024},
|
||||
{"4G", "4G", 4 * 1024},
|
||||
{"4g", "4g", 4 * 1024},
|
||||
{"8.5Gi", "8.5Gi", int(8.5 * 1024)},
|
||||
{"0.5Gi", "0.5Gi", int(0.5 * 1024)},
|
||||
|
||||
// MiB format (case-insensitive)
|
||||
{"4096Mi", "4096Mi", 4096},
|
||||
{"4096MI", "4096MI", 4096},
|
||||
{"4096mi", "4096mi", 4096},
|
||||
{"4096M", "4096M", 4096},
|
||||
{"4096m", "4096m", 4096},
|
||||
{"512Mi", "512Mi", 512},
|
||||
|
||||
// KiB format (case-insensitive)
|
||||
{"1024Ki", "1024Ki", 1},
|
||||
{"1024KI", "1024KI", 1},
|
||||
{"1024ki", "1024ki", 1},
|
||||
{"1024K", "1024K", 1},
|
||||
{"1024k", "1024k", 1},
|
||||
{"512Ki", "512Ki", 0}, // Rounds down
|
||||
|
||||
// Plain numbers (assumed MB)
|
||||
{"4096", "4096", 4096},
|
||||
{"8192", "8192", 8192},
|
||||
{"0", "0", 0},
|
||||
|
||||
// Empty string (default)
|
||||
{"empty", "", 4096},
|
||||
|
||||
// Whitespace handling
|
||||
{"with spaces", " 4096 ", 4096},
|
||||
{"with tabs", "\t8192\t", 8192},
|
||||
|
||||
// Edge cases
|
||||
{"large value", "1024Gi", 1024 * 1024},
|
||||
{"small value", "1Mi", 1},
|
||||
{"fractional MiB", "1.5Mi", 1}, // Truncates
|
||||
{"fractional KiB", "1536Ki", 1}, // 1536/1024 = 1.5, rounds down to 1
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ParseMemoryToMB(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ParseMemoryToMB(%q) = %d, want %d", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMemoryToGB(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected int
|
||||
}{
|
||||
{"4Gi to GB", "4Gi", 4},
|
||||
{"8Gi to GB", "8Gi", 8},
|
||||
{"4096Mi to GB", "4096Mi", 4},
|
||||
{"8192Mi to GB", "8192Mi", 8},
|
||||
{"1024MB to GB", "1024M", 1},
|
||||
{"plain number GB", "8", 0}, // 8 MB = 0 GB (truncates)
|
||||
{"plain number 8192MB", "8192", 8}, // 8192 MB = 8 GB
|
||||
{"empty default", "", 4}, // 4096 MB default = 4 GB
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ParseMemoryToGB(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ParseMemoryToGB(%q) = %d, want %d", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDiskToGB(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected int
|
||||
}{
|
||||
// TiB format (case-insensitive)
|
||||
{"1Ti", "1Ti", 1 * 1024},
|
||||
{"1TI", "1TI", 1 * 1024},
|
||||
{"1ti", "1ti", 1024},
|
||||
{"1T", "1T", 1024},
|
||||
{"1t", "1t", 1024},
|
||||
{"2.5Ti", "2.5Ti", int(2.5 * 1024)},
|
||||
|
||||
// GiB format (case-insensitive)
|
||||
{"50Gi", "50Gi", 50},
|
||||
{"50GI", "50GI", 50},
|
||||
{"50gi", "50gi", 50},
|
||||
{"50G", "50G", 50},
|
||||
{"50g", "50g", 50},
|
||||
{"100Gi", "100Gi", 100},
|
||||
{"8.5Gi", "8.5Gi", 8}, // Truncates
|
||||
|
||||
// MiB format (case-insensitive)
|
||||
{"51200Mi", "51200Mi", 50}, // 51200 MiB = 50 GB
|
||||
{"51200MI", "51200MI", 50},
|
||||
{"51200mi", "51200mi", 50},
|
||||
{"51200M", "51200M", 50},
|
||||
{"51200m", "51200m", 50},
|
||||
{"1024Mi", "1024Mi", 1},
|
||||
|
||||
// Plain numbers (assumed GB)
|
||||
{"50", "50", 50},
|
||||
{"100", "100", 100},
|
||||
{"0", "0", 0},
|
||||
|
||||
// Empty string (default)
|
||||
{"empty", "", 50},
|
||||
|
||||
// Whitespace handling
|
||||
{"with spaces", " 100 ", 100},
|
||||
{"with tabs", "\t50\t", 50},
|
||||
|
||||
// Edge cases
|
||||
{"large value", "10Ti", 10 * 1024},
|
||||
{"small value", "1Gi", 1},
|
||||
{"fractional GiB", "1.5Gi", 1}, // Truncates
|
||||
{"fractional MiB", "1536Mi", 1}, // 1536/1024 = 1.5, rounds down to 1
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := ParseDiskToGB(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("ParseDiskToGB(%q) = %d, want %d", tt.input, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseMemoryToMB_InvalidInput(t *testing.T) {
|
||||
// Invalid inputs should return default (4096 MB)
|
||||
invalidInputs := []string{
|
||||
"invalid",
|
||||
"abc123",
|
||||
"10.5.5Gi", // Invalid number format
|
||||
"10XX", // Invalid unit
|
||||
}
|
||||
|
||||
for _, input := range invalidInputs {
|
||||
result := ParseMemoryToMB(input)
|
||||
if result != 4096 {
|
||||
t.Errorf("ParseMemoryToMB(%q) with invalid input should return default 4096, got %d", input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseDiskToGB_InvalidInput(t *testing.T) {
|
||||
// Invalid inputs should return default (50 GB)
|
||||
invalidInputs := []string{
|
||||
"invalid",
|
||||
"abc123",
|
||||
"10.5.5Gi", // Invalid number format
|
||||
"10XX", // Invalid unit
|
||||
}
|
||||
|
||||
for _, input := range invalidInputs {
|
||||
result := ParseDiskToGB(input)
|
||||
if result != 50 {
|
||||
t.Errorf("ParseDiskToGB(%q) with invalid input should return default 50, got %d", input, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
159
crossplane-provider-proxmox/pkg/utils/validation.go
Normal file
159
crossplane-provider-proxmox/pkg/utils/validation.go
Normal file
@@ -0,0 +1,159 @@
|
||||
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
|
||||
}
|
||||
|
||||
239
crossplane-provider-proxmox/pkg/utils/validation_test.go
Normal file
239
crossplane-provider-proxmox/pkg/utils/validation_test.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateVMID(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
vmid int
|
||||
wantErr bool
|
||||
}{
|
||||
{"valid minimum", 100, false},
|
||||
{"valid maximum", 999999999, false},
|
||||
{"valid middle", 1000, false},
|
||||
{"too small", 99, true},
|
||||
{"zero", 0, true},
|
||||
{"negative", -1, true},
|
||||
{"too large", 1000000000, true},
|
||||
{"very large", 2000000000, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateVMID(tt.vmid)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateVMID(%d) error = %v, wantErr %v", tt.vmid, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateVMName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
vmName string
|
||||
wantErr bool
|
||||
}{
|
||||
// Valid names
|
||||
{"simple name", "vm-001", false},
|
||||
{"with underscore", "vm_001", false},
|
||||
{"with dot", "vm.001", false},
|
||||
{"with spaces", "my vm", false},
|
||||
{"alphanumeric", "vm001", false},
|
||||
{"mixed case", "MyVM", false},
|
||||
{"max length", string(make([]byte, 100)), false}, // 100 chars
|
||||
|
||||
// Invalid names
|
||||
{"empty", "", true},
|
||||
{"too long", string(make([]byte, 101)), true}, // 101 chars
|
||||
{"starts with space", " vm", true},
|
||||
{"ends with space", "vm ", true},
|
||||
{"invalid char @", "vm@001", true},
|
||||
{"invalid char #", "vm#001", true},
|
||||
{"invalid char $", "vm$001", true},
|
||||
{"invalid char %", "vm%001", true},
|
||||
{"only spaces", " ", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateVMName(tt.vmName)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateVMName(%q) error = %v, wantErr %v", tt.vmName, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMemory(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
memory string
|
||||
wantErr bool
|
||||
}{
|
||||
// Valid memory
|
||||
{"minimum", "128Mi", false},
|
||||
{"128MB", "128M", false},
|
||||
{"1Gi", "1Gi", false},
|
||||
{"4Gi", "4Gi", false},
|
||||
{"8Gi", "8Gi", false},
|
||||
{"16Gi", "16Gi", false},
|
||||
{"maximum", "2097152Mi", false}, // 2TB in MiB
|
||||
{"2TB in GiB", "2048Gi", false},
|
||||
|
||||
// Invalid memory
|
||||
{"empty", "", true},
|
||||
{"too small", "127Mi", true},
|
||||
{"too small MB", "127M", true},
|
||||
{"zero", "0", true},
|
||||
{"too large", "2097153Mi", true}, // Over 2TB
|
||||
{"too large GiB", "2049Gi", true},
|
||||
{"invalid format", "invalid", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateMemory(tt.memory)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateMemory(%q) error = %v, wantErr %v", tt.memory, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDisk(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
disk string
|
||||
wantErr bool
|
||||
}{
|
||||
// Valid disk
|
||||
{"minimum", "1Gi", false},
|
||||
{"1GB", "1G", false},
|
||||
{"10Gi", "10Gi", false},
|
||||
{"50Gi", "50Gi", false},
|
||||
{"100Gi", "100Gi", false},
|
||||
{"1Ti", "1Ti", false},
|
||||
{"maximum", "102400Gi", false}, // 100TB in GiB
|
||||
{"100TB in TiB", "100Ti", false},
|
||||
|
||||
// Invalid disk
|
||||
{"empty", "", true},
|
||||
{"too small", "0.5Gi", true}, // Less than 1GB
|
||||
{"zero", "0", true},
|
||||
{"too large", "102401Gi", true}, // Over 100TB
|
||||
{"too large TiB", "101Ti", true},
|
||||
{"invalid format", "invalid", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateDisk(tt.disk)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateDisk(%q) error = %v, wantErr %v", tt.disk, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCPU(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cpu int
|
||||
wantErr bool
|
||||
}{
|
||||
{"minimum", 1, false},
|
||||
{"valid", 2, false},
|
||||
{"valid", 4, false},
|
||||
{"valid", 8, false},
|
||||
{"maximum", 1024, false},
|
||||
{"zero", 0, true},
|
||||
{"negative", -1, true},
|
||||
{"too large", 1025, true},
|
||||
{"very large", 2048, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateCPU(tt.cpu)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateCPU(%d) error = %v, wantErr %v", tt.cpu, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateNetworkBridge(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
network string
|
||||
wantErr bool
|
||||
}{
|
||||
// Valid networks
|
||||
{"vmbr0", "vmbr0", false},
|
||||
{"vmbr1", "vmbr1", false},
|
||||
{"custom-bridge", "custom-bridge", false},
|
||||
{"custom_bridge", "custom_bridge", false},
|
||||
{"bridge01", "bridge01", false},
|
||||
{"BRIDGE", "BRIDGE", false},
|
||||
|
||||
// Invalid networks
|
||||
{"empty", "", true},
|
||||
{"with space", "vmbr 0", true},
|
||||
{"with @", "vmbr@0", true},
|
||||
{"with #", "vmbr#0", true},
|
||||
{"with $", "vmbr$0", true},
|
||||
{"with dot", "vmbr.0", true}, // Dots are typically not used in bridge names
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateNetworkBridge(tt.network)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateNetworkBridge(%q) error = %v, wantErr %v", tt.network, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImageSpec(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
image string
|
||||
wantErr bool
|
||||
}{
|
||||
// Valid template IDs
|
||||
{"valid template ID min", "100", false},
|
||||
{"valid template ID", "1000", false},
|
||||
{"valid template ID max", "999999999", false},
|
||||
|
||||
// Valid volid format
|
||||
{"valid volid", "local:iso/ubuntu-22.04.iso", false},
|
||||
{"valid volid with path", "storage:path/to/image.qcow2", false},
|
||||
|
||||
// Valid image names
|
||||
{"simple name", "ubuntu-22.04-cloud", false},
|
||||
{"with dots", "ubuntu.22.04.cloud", false},
|
||||
{"with hyphens", "ubuntu-22-04-cloud", false},
|
||||
{"with underscores", "ubuntu_22_04_cloud", false},
|
||||
{"max length", string(make([]byte, 255)), false}, // 255 chars
|
||||
|
||||
// Invalid
|
||||
{"empty", "", true},
|
||||
{"invalid template ID too small", "99", true},
|
||||
{"invalid template ID too large", "1000000000", true},
|
||||
{"invalid volid no storage", ":path", true},
|
||||
{"invalid volid no path", "storage:", true},
|
||||
{"too long name", string(make([]byte, 256)), true}, // 256 chars
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := ValidateImageSpec(tt.image)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ValidateImageSpec(%q) error = %v, wantErr %v", tt.image, err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user