first commit
This commit is contained in:
commit
7584207f76
72 changed files with 12801 additions and 0 deletions
323
bib/internal/ux/validation.go
Normal file
323
bib/internal/ux/validation.go
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
package ux
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Validator provides input validation functionality
|
||||
type Validator struct {
|
||||
verbose bool
|
||||
}
|
||||
|
||||
// NewValidator creates a new validator
|
||||
func NewValidator(verbose bool) *Validator {
|
||||
return &Validator{verbose: verbose}
|
||||
}
|
||||
|
||||
// ValidationResult represents the result of a validation
|
||||
type ValidationResult struct {
|
||||
Valid bool
|
||||
Message string
|
||||
Suggestions []string
|
||||
}
|
||||
|
||||
// ValidateImageReference validates a container image reference
|
||||
func (v *Validator) ValidateImageReference(imageRef string) *ValidationResult {
|
||||
if imageRef == "" {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: "Image reference cannot be empty",
|
||||
Suggestions: []string{
|
||||
"Provide a valid container image reference",
|
||||
"Example: git.raines.xyz/particle-os/debian-bootc:latest",
|
||||
"Example: docker.io/debian:bookworm-slim",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Basic validation for image reference format
|
||||
// Should contain at least one colon or slash
|
||||
if !strings.Contains(imageRef, ":") && !strings.Contains(imageRef, "/") {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: "Invalid image reference format",
|
||||
Suggestions: []string{
|
||||
"Image reference should contain registry, namespace, and tag",
|
||||
"Example: registry.example.com/namespace/image:tag",
|
||||
"Example: image:tag",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check for valid characters (allow hyphens in addition to other valid chars)
|
||||
validChars := regexp.MustCompile(`^[a-zA-Z0-9._/:+-]+$`)
|
||||
if !validChars.MatchString(imageRef) {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: "Image reference contains invalid characters",
|
||||
Suggestions: []string{
|
||||
"Use only alphanumeric characters, dots, underscores, slashes, hyphens, and colons",
|
||||
"Example: git.raines.xyz/particle-os/debian-bootc:latest",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// ValidateImageTypes validates image type specifications
|
||||
func (v *Validator) ValidateImageTypes(imageTypes []string) *ValidationResult {
|
||||
if len(imageTypes) == 0 {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: "At least one image type must be specified",
|
||||
Suggestions: []string{
|
||||
"Specify one or more image types: qcow2, ami, vmdk, raw, debian-installer, calamares",
|
||||
"Example: --type qcow2 --type ami",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
validTypes := map[string]bool{
|
||||
"qcow2": true,
|
||||
"ami": true,
|
||||
"vmdk": true,
|
||||
"raw": true,
|
||||
"debian-installer": true,
|
||||
"calamares": true,
|
||||
}
|
||||
|
||||
var invalidTypes []string
|
||||
for _, imgType := range imageTypes {
|
||||
if !validTypes[imgType] {
|
||||
invalidTypes = append(invalidTypes, imgType)
|
||||
}
|
||||
}
|
||||
|
||||
if len(invalidTypes) > 0 {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Invalid image types: %s", strings.Join(invalidTypes, ", ")),
|
||||
Suggestions: []string{
|
||||
"Supported image types: qcow2, ami, vmdk, raw, debian-installer, calamares",
|
||||
"Check spelling and case sensitivity",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// ValidateArchitecture validates target architecture
|
||||
func (v *Validator) ValidateArchitecture(arch string) *ValidationResult {
|
||||
if arch == "" {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: "Target architecture cannot be empty",
|
||||
Suggestions: []string{
|
||||
"Specify a valid architecture: amd64, arm64, armhf, ppc64el, s390x",
|
||||
"Example: --target-arch amd64",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
validArchs := map[string]bool{
|
||||
"amd64": true,
|
||||
"arm64": true,
|
||||
"armhf": true,
|
||||
"ppc64el": true,
|
||||
"s390x": true,
|
||||
}
|
||||
|
||||
if !validArchs[arch] {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Unsupported architecture: %s", arch),
|
||||
Suggestions: []string{
|
||||
"Supported architectures: amd64, arm64, armhf, ppc64el, s390x",
|
||||
"Use --target-arch amd64 for x86_64 systems",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// ValidateRootfsType validates root filesystem type
|
||||
func (v *Validator) ValidateRootfsType(rootfsType string) *ValidationResult {
|
||||
if rootfsType == "" {
|
||||
// Empty is valid (will use default)
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
validTypes := map[string]bool{
|
||||
"ext4": true,
|
||||
"xfs": true,
|
||||
"btrfs": true,
|
||||
}
|
||||
|
||||
if !validTypes[rootfsType] {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Unsupported root filesystem type: %s", rootfsType),
|
||||
Suggestions: []string{
|
||||
"Supported filesystem types: ext4, xfs, btrfs",
|
||||
"Leave empty to use default (ext4)",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// ValidateDirectory validates a directory path
|
||||
func (v *Validator) ValidateDirectory(path, purpose string) *ValidationResult {
|
||||
if path == "" {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("%s path cannot be empty", purpose),
|
||||
Suggestions: []string{
|
||||
fmt.Sprintf("Specify a valid directory path for %s", purpose),
|
||||
"Use absolute or relative paths",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check if path exists and is a directory
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Try to create the directory
|
||||
if err := os.MkdirAll(path, 0755); err != nil {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Cannot create %s directory: %v", purpose, err),
|
||||
Suggestions: []string{
|
||||
"Check parent directory permissions",
|
||||
"Use a different directory path",
|
||||
"Run with appropriate permissions",
|
||||
},
|
||||
}
|
||||
}
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Cannot access %s directory: %v", purpose, err),
|
||||
Suggestions: []string{
|
||||
"Check directory permissions",
|
||||
"Ensure the path is accessible",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if !info.IsDir() {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("%s path is not a directory: %s", purpose, path),
|
||||
Suggestions: []string{
|
||||
"Specify a directory path, not a file",
|
||||
"Check the path exists and is a directory",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// ValidateConfigFile validates a configuration file path
|
||||
func (v *Validator) ValidateConfigFile(configPath string) *ValidationResult {
|
||||
if configPath == "" {
|
||||
// Empty is valid (will use default)
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// Check if file exists
|
||||
info, err := os.Stat(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Configuration file not found: %s", configPath),
|
||||
Suggestions: []string{
|
||||
"Check the file path is correct",
|
||||
"Create a configuration file at the specified path",
|
||||
"Leave empty to use default configuration",
|
||||
},
|
||||
}
|
||||
}
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Cannot access configuration file: %v", err),
|
||||
Suggestions: []string{
|
||||
"Check file permissions",
|
||||
"Ensure the file is accessible",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Configuration path is a directory, not a file: %s", configPath),
|
||||
Suggestions: []string{
|
||||
"Specify a file path, not a directory",
|
||||
"Example: .config/registry.yaml",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Check file extension
|
||||
ext := strings.ToLower(filepath.Ext(configPath))
|
||||
if ext != ".yaml" && ext != ".yml" {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: fmt.Sprintf("Configuration file should be YAML format: %s", configPath),
|
||||
Suggestions: []string{
|
||||
"Use .yaml or .yml extension",
|
||||
"Example: .config/registry.yaml",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &ValidationResult{Valid: true}
|
||||
}
|
||||
|
||||
// ValidateDistroDefPath validates distribution definition path
|
||||
func (v *Validator) ValidateDistroDefPath(path string) *ValidationResult {
|
||||
if path == "" {
|
||||
return &ValidationResult{
|
||||
Valid: false,
|
||||
Message: "Distribution definition path cannot be empty",
|
||||
Suggestions: []string{
|
||||
"Specify a valid directory path containing package definitions",
|
||||
"Example: ./data/defs",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return v.ValidateDirectory(path, "distribution definition")
|
||||
}
|
||||
|
||||
// PrintValidationResult prints validation results with suggestions
|
||||
func (v *Validator) PrintValidationResult(result *ValidationResult) {
|
||||
if result.Valid {
|
||||
if v.verbose {
|
||||
PrintInfo(os.Stdout, "Validation passed")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
PrintError(os.Stdout, result.Message)
|
||||
|
||||
if len(result.Suggestions) > 0 {
|
||||
fmt.Println("💡 Suggestions:")
|
||||
for _, suggestion := range result.Suggestions {
|
||||
fmt.Printf(" • %s\n", suggestion)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue