deb-bootc-image-builder-new/bib/internal/ux/troubleshooting.go
2025-09-05 07:10:12 -07:00

411 lines
12 KiB
Go

package ux
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"syscall"
)
// TroubleshootingGuide provides troubleshooting information and diagnostics
type TroubleshootingGuide struct {
verbose bool
}
// NewTroubleshootingGuide creates a new troubleshooting guide
func NewTroubleshootingGuide(verbose bool) *TroubleshootingGuide {
return &TroubleshootingGuide{verbose: verbose}
}
// DiagnosticResult represents the result of a diagnostic check
type DiagnosticResult struct {
Check string
Status string
Message string
Details string
Fix string
Critical bool
}
// RunDiagnostics runs comprehensive system diagnostics
func (t *TroubleshootingGuide) RunDiagnostics() []DiagnosticResult {
var results []DiagnosticResult
// Check required tools
results = append(results, t.checkRequiredTools()...)
// Check system resources
results = append(results, t.checkSystemResources()...)
// Check permissions
results = append(results, t.checkPermissions()...)
// Check network connectivity
results = append(results, t.checkNetworkConnectivity()...)
// Check container runtime
results = append(results, t.checkContainerRuntime()...)
return results
}
// checkRequiredTools checks for required system tools
func (t *TroubleshootingGuide) checkRequiredTools() []DiagnosticResult {
var results []DiagnosticResult
requiredTools := map[string]string{
"apt-cache": "Required for package dependency resolution",
"podman": "Required for container operations",
"qemu-img": "Required for image format conversion",
"file": "Required for file type detection",
}
for tool, description := range requiredTools {
result := t.checkTool(tool, description)
results = append(results, result)
}
return results
}
// checkTool checks if a specific tool is available
func (t *TroubleshootingGuide) checkTool(tool, description string) DiagnosticResult {
_, err := exec.LookPath(tool)
if err != nil {
return DiagnosticResult{
Check: fmt.Sprintf("Tool: %s", tool),
Status: "❌ Missing",
Message: fmt.Sprintf("%s is not installed or not in PATH", tool),
Details: description,
Fix: fmt.Sprintf("Install %s: sudo apt install %s", tool, t.getPackageName(tool)),
Critical: true,
}
}
return DiagnosticResult{
Check: fmt.Sprintf("Tool: %s", tool),
Status: "✅ Available",
Message: fmt.Sprintf("%s is installed and accessible", tool),
Details: description,
Critical: false,
}
}
// getPackageName returns the package name for a tool
func (t *TroubleshootingGuide) getPackageName(tool string) string {
packageMap := map[string]string{
"apt-cache": "apt",
"podman": "podman",
"qemu-img": "qemu-utils",
"file": "file",
}
if pkg, exists := packageMap[tool]; exists {
return pkg
}
return tool
}
// checkSystemResources checks system resource availability
func (t *TroubleshootingGuide) checkSystemResources() []DiagnosticResult {
var results []DiagnosticResult
// Check available disk space
results = append(results, t.checkDiskSpace())
// Check available memory
results = append(results, t.checkMemory())
return results
}
// checkDiskSpace checks available disk space
func (t *TroubleshootingGuide) checkDiskSpace() DiagnosticResult {
// Check current directory disk space
var stat syscall.Statfs_t
err := syscall.Statfs(".", &stat)
if err != nil {
return DiagnosticResult{
Check: "Disk Space",
Status: "⚠️ Unknown",
Message: "Cannot determine available disk space",
Details: "Failed to get filesystem statistics",
Fix: "Check disk space manually: df -h",
Critical: false,
}
}
// Calculate available space in GB
availableBytes := stat.Bavail * uint64(stat.Bsize)
availableGB := float64(availableBytes) / (1024 * 1024 * 1024)
if availableGB < 5.0 {
return DiagnosticResult{
Check: "Disk Space",
Status: "❌ Low",
Message: fmt.Sprintf("Only %.1f GB available", availableGB),
Details: "Image building requires at least 5GB of free space",
Fix: "Free up disk space or use a different directory",
Critical: true,
}
}
return DiagnosticResult{
Check: "Disk Space",
Status: "✅ Sufficient",
Message: fmt.Sprintf("%.1f GB available", availableGB),
Details: "Adequate disk space for image building",
Critical: false,
}
}
// checkMemory checks available system memory
func (t *TroubleshootingGuide) checkMemory() DiagnosticResult {
// Read /proc/meminfo for memory information
data, err := os.ReadFile("/proc/meminfo")
if err != nil {
return DiagnosticResult{
Check: "Memory",
Status: "⚠️ Unknown",
Message: "Cannot determine available memory",
Details: "Failed to read memory information",
Fix: "Check memory manually: free -h",
Critical: false,
}
}
// Parse available memory (simplified)
lines := strings.Split(string(data), "\n")
var memTotal, memAvailable int64
for _, line := range lines {
if strings.HasPrefix(line, "MemTotal:") {
fmt.Sscanf(line, "MemTotal: %d kB", &memTotal)
} else if strings.HasPrefix(line, "MemAvailable:") {
fmt.Sscanf(line, "MemAvailable: %d kB", &memAvailable)
}
}
if memTotal == 0 {
return DiagnosticResult{
Check: "Memory",
Status: "⚠️ Unknown",
Message: "Cannot parse memory information",
Details: "Failed to parse /proc/meminfo",
Fix: "Check memory manually: free -h",
Critical: false,
}
}
memTotalGB := float64(memTotal) / (1024 * 1024)
memAvailableGB := float64(memAvailable) / (1024 * 1024)
if memTotalGB < 2.0 {
return DiagnosticResult{
Check: "Memory",
Status: "❌ Insufficient",
Message: fmt.Sprintf("Only %.1f GB total memory", memTotalGB),
Details: "Image building requires at least 2GB of RAM",
Fix: "Add more RAM or use a system with more memory",
Critical: true,
}
}
if memAvailableGB < 1.0 {
return DiagnosticResult{
Check: "Memory",
Status: "⚠️ Low",
Message: fmt.Sprintf("Only %.1f GB available memory", memAvailableGB),
Details: "Low available memory may cause build failures",
Fix: "Close other applications or add more RAM",
Critical: false,
}
}
return DiagnosticResult{
Check: "Memory",
Status: "✅ Sufficient",
Message: fmt.Sprintf("%.1f GB total, %.1f GB available", memTotalGB, memAvailableGB),
Details: "Adequate memory for image building",
Critical: false,
}
}
// checkPermissions checks file and directory permissions
func (t *TroubleshootingGuide) checkPermissions() []DiagnosticResult {
var results []DiagnosticResult
// Check current directory write permissions
results = append(results, t.checkWritePermission(".", "Current directory"))
// Check /tmp write permissions
results = append(results, t.checkWritePermission("/tmp", "Temporary directory"))
return results
}
// checkWritePermission checks write permission for a directory
func (t *TroubleshootingGuide) checkWritePermission(path, description string) DiagnosticResult {
// Try to create a test file
testFile := filepath.Join(path, ".debian-bootc-image-builder-test")
err := os.WriteFile(testFile, []byte("test"), 0644)
if err != nil {
// Clean up if file was created
os.Remove(testFile)
return DiagnosticResult{
Check: fmt.Sprintf("Write Permission: %s", description),
Status: "❌ Denied",
Message: fmt.Sprintf("Cannot write to %s", path),
Details: fmt.Sprintf("Permission denied: %v", err),
Fix: fmt.Sprintf("Check permissions: ls -la %s", path),
Critical: true,
}
}
// Clean up test file
os.Remove(testFile)
return DiagnosticResult{
Check: fmt.Sprintf("Write Permission: %s", description),
Status: "✅ Allowed",
Message: fmt.Sprintf("Can write to %s", path),
Details: "Write permissions are sufficient",
Critical: false,
}
}
// checkNetworkConnectivity checks network connectivity for required services
func (t *TroubleshootingGuide) checkNetworkConnectivity() []DiagnosticResult {
var results []DiagnosticResult
// Check basic internet connectivity
results = append(results, t.checkConnectivity("8.8.8.8", "Internet connectivity"))
// Check Debian repository connectivity
results = append(results, t.checkConnectivity("deb.debian.org", "Debian repositories"))
return results
}
// checkConnectivity checks network connectivity to a host
func (t *TroubleshootingGuide) checkConnectivity(host, description string) DiagnosticResult {
cmd := exec.Command("ping", "-c", "1", "-W", "5", host)
err := cmd.Run()
if err != nil {
return DiagnosticResult{
Check: fmt.Sprintf("Network: %s", description),
Status: "❌ Failed",
Message: fmt.Sprintf("Cannot reach %s", host),
Details: "Network connectivity issue",
Fix: "Check network connection and firewall settings",
Critical: false, // Not critical for basic functionality
}
}
return DiagnosticResult{
Check: fmt.Sprintf("Network: %s", description),
Status: "✅ Connected",
Message: fmt.Sprintf("Can reach %s", host),
Details: "Network connectivity is working",
Critical: false,
}
}
// checkContainerRuntime checks container runtime availability
func (t *TroubleshootingGuide) checkContainerRuntime() []DiagnosticResult {
var results []DiagnosticResult
// Check if podman is running
cmd := exec.Command("podman", "version")
err := cmd.Run()
if err != nil {
results = append(results, DiagnosticResult{
Check: "Container Runtime",
Status: "❌ Failed",
Message: "Podman is not working properly",
Details: "Cannot execute podman version command",
Fix: "Check podman installation and configuration",
Critical: true,
})
} else {
results = append(results, DiagnosticResult{
Check: "Container Runtime",
Status: "✅ Working",
Message: "Podman is working properly",
Details: "Container runtime is functional",
Critical: false,
})
}
return results
}
// PrintDiagnostics prints diagnostic results
func (t *TroubleshootingGuide) PrintDiagnostics(results []DiagnosticResult) {
fmt.Println("🔍 System Diagnostics:")
fmt.Println()
criticalIssues := 0
warnings := 0
for _, result := range results {
fmt.Printf("%s %s: %s\n", result.Status, result.Check, result.Message)
if t.verbose && result.Details != "" {
fmt.Printf(" Details: %s\n", result.Details)
}
if result.Fix != "" {
fmt.Printf(" Fix: %s\n", result.Fix)
}
if result.Critical {
criticalIssues++
} else if strings.Contains(result.Status, "⚠️") {
warnings++
}
fmt.Println()
}
// Summary
if criticalIssues > 0 {
fmt.Printf("❌ Found %d critical issues that must be resolved\n", criticalIssues)
} else if warnings > 0 {
fmt.Printf("⚠️ Found %d warnings (non-critical)\n", warnings)
} else {
fmt.Println("✅ All diagnostics passed - system is ready")
}
}
// GetCommonSolutions returns common solutions for frequent issues
func (t *TroubleshootingGuide) GetCommonSolutions() map[string][]string {
return map[string][]string{
"apt-cache failed": {
"Ensure apt-cache is installed: sudo apt install apt",
"Update package lists: sudo apt update",
"Check repository configuration: cat /etc/apt/sources.list",
},
"permission denied": {
"Check file/directory permissions: ls -la",
"Use appropriate user permissions",
"Try running with sudo if necessary",
},
"container not found": {
"Verify container image exists: podman images",
"Pull the container: podman pull <image>",
"Check image reference format",
},
"out of disk space": {
"Free up disk space: df -h",
"Remove unnecessary files",
"Use a different output directory",
},
"out of memory": {
"Close other applications",
"Add swap space if needed",
"Use a system with more RAM",
},
}
}