Initial commit: Database Backup Tool v1.1.0
- PostgreSQL and MySQL support - Interactive TUI with fixed menu navigation - Line-by-line progress display - CPU-aware parallel processing - Cross-platform build support - Configuration settings menu - Silent mode for TUI operations
This commit is contained in:
129
cmd/backup.go
Normal file
129
cmd/backup.go
Normal file
@ -0,0 +1,129 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// backupCmd represents the backup command
|
||||
var backupCmd = &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Create database backups",
|
||||
Long: `Create database backups with support for various modes:
|
||||
|
||||
Backup Modes:
|
||||
cluster - Full cluster backup (all databases + globals) [PostgreSQL only]
|
||||
single - Single database backup
|
||||
sample - Sample database backup (reduced dataset)
|
||||
|
||||
Examples:
|
||||
# Full cluster backup (PostgreSQL)
|
||||
dbbackup backup cluster --db-type postgres
|
||||
|
||||
# Single database backup
|
||||
dbbackup backup single mydb --db-type postgres
|
||||
dbbackup backup single mydb --db-type mysql
|
||||
|
||||
# Sample database backup
|
||||
dbbackup backup sample mydb --sample-ratio 10 --db-type postgres`,
|
||||
}
|
||||
|
||||
var clusterCmd = &cobra.Command{
|
||||
Use: "cluster",
|
||||
Short: "Create full cluster backup (PostgreSQL only)",
|
||||
Long: `Create a complete backup of the entire PostgreSQL cluster including all databases and global objects (roles, tablespaces, etc.)`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runClusterBackup(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
var singleCmd = &cobra.Command{
|
||||
Use: "single [database]",
|
||||
Short: "Create single database backup",
|
||||
Long: `Create a backup of a single database with all its data and schema`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
dbName := ""
|
||||
if len(args) > 0 {
|
||||
dbName = args[0]
|
||||
} else if cfg.SingleDBName != "" {
|
||||
dbName = cfg.SingleDBName
|
||||
} else {
|
||||
return fmt.Errorf("database name required (provide as argument or set SINGLE_DB_NAME)")
|
||||
}
|
||||
|
||||
return runSingleBackup(cmd.Context(), dbName)
|
||||
},
|
||||
}
|
||||
|
||||
var sampleCmd = &cobra.Command{
|
||||
Use: "sample [database]",
|
||||
Short: "Create sample database backup",
|
||||
Long: `Create a sample database backup with reduced dataset for testing/development.
|
||||
|
||||
Sampling Strategies:
|
||||
--sample-ratio N - Take every Nth record (e.g., 10 = every 10th record)
|
||||
--sample-percent N - Take N% of records (e.g., 20 = 20% of data)
|
||||
--sample-count N - Take first N records from each table
|
||||
|
||||
Warning: Sample backups may break referential integrity due to sampling!`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
dbName := ""
|
||||
if len(args) > 0 {
|
||||
dbName = args[0]
|
||||
} else if cfg.SingleDBName != "" {
|
||||
dbName = cfg.SingleDBName
|
||||
} else {
|
||||
return fmt.Errorf("database name required (provide as argument or set SAMPLE_DB_NAME)")
|
||||
}
|
||||
|
||||
return runSampleBackup(cmd.Context(), dbName)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Add backup subcommands
|
||||
backupCmd.AddCommand(clusterCmd)
|
||||
backupCmd.AddCommand(singleCmd)
|
||||
backupCmd.AddCommand(sampleCmd)
|
||||
|
||||
// Sample backup flags - use local variables to avoid cfg access during init
|
||||
var sampleStrategy string
|
||||
var sampleValue int
|
||||
var sampleRatio int
|
||||
var samplePercent int
|
||||
var sampleCount int
|
||||
|
||||
sampleCmd.Flags().StringVar(&sampleStrategy, "sample-strategy", "ratio", "Sampling strategy (ratio|percent|count)")
|
||||
sampleCmd.Flags().IntVar(&sampleValue, "sample-value", 10, "Sampling value")
|
||||
sampleCmd.Flags().IntVar(&sampleRatio, "sample-ratio", 0, "Take every Nth record")
|
||||
sampleCmd.Flags().IntVar(&samplePercent, "sample-percent", 0, "Take N% of records")
|
||||
sampleCmd.Flags().IntVar(&sampleCount, "sample-count", 0, "Take first N records")
|
||||
|
||||
// Set up pre-run hook to handle convenience flags and update cfg
|
||||
sampleCmd.PreRunE = func(cmd *cobra.Command, args []string) error {
|
||||
// Update cfg with flag values
|
||||
if cmd.Flags().Changed("sample-ratio") && sampleRatio > 0 {
|
||||
cfg.SampleStrategy = "ratio"
|
||||
cfg.SampleValue = sampleRatio
|
||||
} else if cmd.Flags().Changed("sample-percent") && samplePercent > 0 {
|
||||
cfg.SampleStrategy = "percent"
|
||||
cfg.SampleValue = samplePercent
|
||||
} else if cmd.Flags().Changed("sample-count") && sampleCount > 0 {
|
||||
cfg.SampleStrategy = "count"
|
||||
cfg.SampleValue = sampleCount
|
||||
} else if cmd.Flags().Changed("sample-strategy") {
|
||||
cfg.SampleStrategy = sampleStrategy
|
||||
}
|
||||
if cmd.Flags().Changed("sample-value") {
|
||||
cfg.SampleValue = sampleValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Mark the strategy flags as mutually exclusive
|
||||
sampleCmd.MarkFlagsMutuallyExclusive("sample-ratio", "sample-percent", "sample-count")
|
||||
}
|
||||
159
cmd/backup_impl.go
Normal file
159
cmd/backup_impl.go
Normal file
@ -0,0 +1,159 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"dbbackup/internal/backup"
|
||||
"dbbackup/internal/database"
|
||||
)
|
||||
|
||||
// runClusterBackup performs a full cluster backup
|
||||
func runClusterBackup(ctx context.Context) error {
|
||||
if !cfg.IsPostgreSQL() {
|
||||
return fmt.Errorf("cluster backup is only supported for PostgreSQL")
|
||||
}
|
||||
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Starting cluster backup",
|
||||
"host", cfg.Host,
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Create backup engine
|
||||
engine := backup.New(cfg, log, db)
|
||||
|
||||
// Perform cluster backup
|
||||
return engine.BackupCluster(ctx)
|
||||
}
|
||||
|
||||
// runSingleBackup performs a single database backup
|
||||
func runSingleBackup(ctx context.Context, databaseName string) error {
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Starting single database backup",
|
||||
"database", databaseName,
|
||||
"db_type", cfg.DatabaseType,
|
||||
"host", cfg.Host,
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Verify database exists
|
||||
exists, err := db.DatabaseExists(ctx, databaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if database exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("database '%s' does not exist", databaseName)
|
||||
}
|
||||
|
||||
// Create backup engine
|
||||
engine := backup.New(cfg, log, db)
|
||||
|
||||
// Perform single database backup
|
||||
return engine.BackupSingle(ctx, databaseName)
|
||||
}
|
||||
|
||||
// runSampleBackup performs a sample database backup
|
||||
func runSampleBackup(ctx context.Context, databaseName string) error {
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
// Validate sample parameters
|
||||
if cfg.SampleValue <= 0 {
|
||||
return fmt.Errorf("sample value must be greater than 0")
|
||||
}
|
||||
|
||||
switch cfg.SampleStrategy {
|
||||
case "percent":
|
||||
if cfg.SampleValue > 100 {
|
||||
return fmt.Errorf("percentage cannot exceed 100")
|
||||
}
|
||||
case "ratio":
|
||||
if cfg.SampleValue < 2 {
|
||||
return fmt.Errorf("ratio must be at least 2")
|
||||
}
|
||||
case "count":
|
||||
// Any positive count is valid
|
||||
default:
|
||||
return fmt.Errorf("invalid sampling strategy: %s (must be ratio, percent, or count)", cfg.SampleStrategy)
|
||||
}
|
||||
|
||||
log.Info("Starting sample database backup",
|
||||
"database", databaseName,
|
||||
"db_type", cfg.DatabaseType,
|
||||
"strategy", cfg.SampleStrategy,
|
||||
"value", cfg.SampleValue,
|
||||
"host", cfg.Host,
|
||||
"port", cfg.Port,
|
||||
"backup_dir", cfg.BackupDir)
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create database instance: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Connect to database
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
return fmt.Errorf("failed to connect to database: %w", err)
|
||||
}
|
||||
|
||||
// Verify database exists
|
||||
exists, err := db.DatabaseExists(ctx, databaseName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check if database exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("database '%s' does not exist", databaseName)
|
||||
}
|
||||
|
||||
// Create backup engine
|
||||
engine := backup.New(cfg, log, db)
|
||||
|
||||
// Perform sample database backup
|
||||
return engine.BackupSample(ctx, databaseName)
|
||||
}
|
||||
76
cmd/cpu.go
Normal file
76
cmd/cpu.go
Normal file
@ -0,0 +1,76 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var cpuCmd = &cobra.Command{
|
||||
Use: "cpu",
|
||||
Short: "Show CPU information and optimization settings",
|
||||
Long: `Display detailed CPU information and current parallelism configuration.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runCPUInfo(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
func runCPUInfo(ctx context.Context) error {
|
||||
log.Info("Detecting CPU information...")
|
||||
|
||||
// Optimize CPU settings if auto-detect is enabled
|
||||
if cfg.AutoDetectCores {
|
||||
if err := cfg.OptimizeForCPU(); err != nil {
|
||||
log.Warn("CPU optimization failed", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get CPU information
|
||||
cpuInfo, err := cfg.GetCPUInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to detect CPU: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("=== CPU Information ===")
|
||||
fmt.Print(cpuInfo.FormatCPUInfo())
|
||||
|
||||
fmt.Println("\n=== Current Configuration ===")
|
||||
fmt.Printf("Auto-detect cores: %t\n", cfg.AutoDetectCores)
|
||||
fmt.Printf("CPU workload type: %s\n", cfg.CPUWorkloadType)
|
||||
fmt.Printf("Parallel jobs (restore): %d\n", cfg.Jobs)
|
||||
fmt.Printf("Dump jobs (backup): %d\n", cfg.DumpJobs)
|
||||
fmt.Printf("Maximum cores limit: %d\n", cfg.MaxCores)
|
||||
|
||||
// Show optimization recommendations
|
||||
fmt.Println("\n=== Optimization Recommendations ===")
|
||||
if cpuInfo.PhysicalCores > 1 {
|
||||
if cfg.CPUWorkloadType == "balanced" {
|
||||
optimal, _ := cfg.CPUDetector.CalculateOptimalJobs("balanced", cfg.MaxCores)
|
||||
fmt.Printf("Recommended jobs (balanced): %d\n", optimal)
|
||||
}
|
||||
if cfg.CPUWorkloadType == "io-intensive" {
|
||||
optimal, _ := cfg.CPUDetector.CalculateOptimalJobs("io-intensive", cfg.MaxCores)
|
||||
fmt.Printf("Recommended jobs (I/O intensive): %d\n", optimal)
|
||||
}
|
||||
if cfg.CPUWorkloadType == "cpu-intensive" {
|
||||
optimal, _ := cfg.CPUDetector.CalculateOptimalJobs("cpu-intensive", cfg.MaxCores)
|
||||
fmt.Printf("Recommended jobs (CPU intensive): %d\n", optimal)
|
||||
}
|
||||
}
|
||||
|
||||
// Show current vs optimal
|
||||
if cfg.AutoDetectCores {
|
||||
fmt.Println("\n✅ CPU optimization is enabled")
|
||||
fmt.Println("Job counts are automatically optimized based on detected hardware")
|
||||
} else {
|
||||
fmt.Println("\n⚠️ CPU optimization is disabled")
|
||||
fmt.Println("Consider enabling --auto-detect-cores for better performance")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cpuCmd)
|
||||
}
|
||||
70
cmd/placeholder.go
Normal file
70
cmd/placeholder.go
Normal file
@ -0,0 +1,70 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
"dbbackup/internal/tui"
|
||||
)
|
||||
|
||||
// Create placeholder commands for the other subcommands
|
||||
|
||||
var restoreCmd = &cobra.Command{
|
||||
Use: "restore [archive]",
|
||||
Short: "Restore from backup archive",
|
||||
Long: `Restore database from backup archive. Auto-detects archive format.`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.Info("Restore command called - not yet implemented")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var verifyCmd = &cobra.Command{
|
||||
Use: "verify [archive]",
|
||||
Short: "Verify backup archive integrity",
|
||||
Long: `Verify the integrity of backup archives.`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.Info("Verify command called - not yet implemented")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var listCmd = &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List available backups and databases",
|
||||
Long: `List available backup archives and database information.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.Info("List command called - not yet implemented")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
var interactiveCmd = &cobra.Command{
|
||||
Use: "interactive",
|
||||
Short: "Start interactive menu mode",
|
||||
Long: `Start the interactive menu system for guided backup operations.`,
|
||||
Aliases: []string{"menu", "ui"},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Start the interactive TUI
|
||||
return tui.RunInteractiveMenu(cfg, log)
|
||||
},
|
||||
}
|
||||
|
||||
var statusCmd = &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Show connection status and configuration",
|
||||
Long: `Display current configuration and test database connectivity.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runStatus(cmd.Context())
|
||||
},
|
||||
}
|
||||
|
||||
var preflightCmd = &cobra.Command{
|
||||
Use: "preflight",
|
||||
Short: "Run preflight checks",
|
||||
Long: `Run connectivity and dependency checks before backup operations.`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
log.Info("Preflight command called - not yet implemented")
|
||||
return nil
|
||||
},
|
||||
}
|
||||
79
cmd/root.go
Normal file
79
cmd/root.go
Normal file
@ -0,0 +1,79 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"dbbackup/internal/config"
|
||||
"dbbackup/internal/logger"
|
||||
)
|
||||
|
||||
var (
|
||||
cfg *config.Config
|
||||
log logger.Logger
|
||||
)
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "dbbackup",
|
||||
Short: "Multi-database backup and restore tool",
|
||||
Long: `A comprehensive database backup and restore solution supporting both PostgreSQL and MySQL.
|
||||
|
||||
Features:
|
||||
- CPU-aware parallel processing
|
||||
- Multiple backup modes (cluster, single database, sample)
|
||||
- Interactive UI and CLI modes
|
||||
- Archive verification and restore
|
||||
- Progress indicators and timing summaries
|
||||
- Robust error handling and logging
|
||||
|
||||
Database Support:
|
||||
- PostgreSQL (via pg_dump/pg_restore)
|
||||
- MySQL (via mysqldump/mysql)
|
||||
|
||||
For help with specific commands, use: dbbackup [command] --help`,
|
||||
Version: "",
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
func Execute(ctx context.Context, config *config.Config, logger logger.Logger) error {
|
||||
cfg = config
|
||||
log = logger
|
||||
|
||||
// Set version info
|
||||
rootCmd.Version = fmt.Sprintf("%s (built: %s, commit: %s)",
|
||||
cfg.Version, cfg.BuildTime, cfg.GitCommit)
|
||||
|
||||
// Add persistent flags
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Host, "host", cfg.Host, "Database host")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.Port, "port", cfg.Port, "Database port")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.User, "user", cfg.User, "Database user")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Database, "database", cfg.Database, "Database name")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Password, "password", cfg.Password, "Database password")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.DatabaseType, "db-type", cfg.DatabaseType, "Database type (postgres|mysql)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.BackupDir, "backup-dir", cfg.BackupDir, "Backup directory")
|
||||
rootCmd.PersistentFlags().BoolVar(&cfg.NoColor, "no-color", cfg.NoColor, "Disable colored output")
|
||||
rootCmd.PersistentFlags().BoolVar(&cfg.Debug, "debug", cfg.Debug, "Enable debug logging")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.Jobs, "jobs", cfg.Jobs, "Number of parallel jobs")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.DumpJobs, "dump-jobs", cfg.DumpJobs, "Number of parallel dump jobs")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.MaxCores, "max-cores", cfg.MaxCores, "Maximum CPU cores to use")
|
||||
rootCmd.PersistentFlags().BoolVar(&cfg.AutoDetectCores, "auto-detect-cores", cfg.AutoDetectCores, "Auto-detect CPU cores")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.CPUWorkloadType, "cpu-workload", cfg.CPUWorkloadType, "CPU workload type (cpu-intensive|io-intensive|balanced)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.SSLMode, "ssl-mode", cfg.SSLMode, "SSL mode for connections")
|
||||
rootCmd.PersistentFlags().BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "Disable SSL (shortcut for --ssl-mode=disable)")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.CompressionLevel, "compression", cfg.CompressionLevel, "Compression level (0-9)")
|
||||
|
||||
return rootCmd.ExecuteContext(ctx)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Register subcommands
|
||||
rootCmd.AddCommand(backupCmd)
|
||||
rootCmd.AddCommand(restoreCmd)
|
||||
rootCmd.AddCommand(verifyCmd)
|
||||
rootCmd.AddCommand(listCmd)
|
||||
rootCmd.AddCommand(interactiveCmd)
|
||||
rootCmd.AddCommand(statusCmd)
|
||||
rootCmd.AddCommand(preflightCmd)
|
||||
}
|
||||
173
cmd/status.go
Normal file
173
cmd/status.go
Normal file
@ -0,0 +1,173 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"dbbackup/internal/database"
|
||||
"dbbackup/internal/progress"
|
||||
)
|
||||
|
||||
// runStatus displays configuration and tests connectivity
|
||||
func runStatus(ctx context.Context) error {
|
||||
// Update config from environment
|
||||
cfg.UpdateFromEnvironment()
|
||||
|
||||
// Validate configuration
|
||||
if err := cfg.Validate(); err != nil {
|
||||
return fmt.Errorf("configuration error: %w", err)
|
||||
}
|
||||
|
||||
// Display header
|
||||
displayHeader()
|
||||
|
||||
// Display configuration
|
||||
displayConfiguration()
|
||||
|
||||
// Test database connection
|
||||
return testConnection(ctx)
|
||||
}
|
||||
|
||||
// displayHeader shows the application header
|
||||
func displayHeader() {
|
||||
if cfg.NoColor {
|
||||
fmt.Println("==============================================================")
|
||||
fmt.Println(" Database Backup & Recovery Tool")
|
||||
fmt.Println("==============================================================")
|
||||
} else {
|
||||
fmt.Println("\033[1;34m==============================================================\033[0m")
|
||||
fmt.Println("\033[1;37m Database Backup & Recovery Tool\033[0m")
|
||||
fmt.Println("\033[1;34m==============================================================\033[0m")
|
||||
}
|
||||
|
||||
fmt.Printf("Version: %s (built: %s, commit: %s)\n", cfg.Version, cfg.BuildTime, cfg.GitCommit)
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// displayConfiguration shows current configuration
|
||||
func displayConfiguration() {
|
||||
fmt.Println("Configuration:")
|
||||
fmt.Printf(" Database Type: %s\n", cfg.DatabaseType)
|
||||
fmt.Printf(" Host: %s:%d\n", cfg.Host, cfg.Port)
|
||||
fmt.Printf(" User: %s\n", cfg.User)
|
||||
fmt.Printf(" Database: %s\n", cfg.Database)
|
||||
|
||||
if cfg.Password != "" {
|
||||
fmt.Printf(" Password: ****** (set)\n")
|
||||
} else {
|
||||
fmt.Printf(" Password: (not set)\n")
|
||||
}
|
||||
|
||||
fmt.Printf(" SSL Mode: %s\n", cfg.SSLMode)
|
||||
if cfg.Insecure {
|
||||
fmt.Printf(" SSL: disabled\n")
|
||||
}
|
||||
|
||||
fmt.Printf(" Backup Dir: %s\n", cfg.BackupDir)
|
||||
fmt.Printf(" Compression: %d\n", cfg.CompressionLevel)
|
||||
fmt.Printf(" Jobs: %d\n", cfg.Jobs)
|
||||
fmt.Printf(" Dump Jobs: %d\n", cfg.DumpJobs)
|
||||
fmt.Printf(" Max Cores: %d\n", cfg.MaxCores)
|
||||
fmt.Printf(" Auto Detect: %v\n", cfg.AutoDetectCores)
|
||||
|
||||
// System information
|
||||
fmt.Println()
|
||||
fmt.Println("System Information:")
|
||||
fmt.Printf(" OS: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
fmt.Printf(" CPU Cores: %d\n", runtime.NumCPU())
|
||||
fmt.Printf(" Go Version: %s\n", runtime.Version())
|
||||
|
||||
// Check if backup directory exists
|
||||
if info, err := os.Stat(cfg.BackupDir); err != nil {
|
||||
fmt.Printf(" Backup Dir: %s (does not exist - will be created)\n", cfg.BackupDir)
|
||||
} else if info.IsDir() {
|
||||
fmt.Printf(" Backup Dir: %s (exists, writable)\n", cfg.BackupDir)
|
||||
} else {
|
||||
fmt.Printf(" Backup Dir: %s (exists but not a directory!)\n", cfg.BackupDir)
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// testConnection tests database connectivity
|
||||
func testConnection(ctx context.Context) error {
|
||||
// Create progress indicator
|
||||
indicator := progress.NewIndicator(true, "spinner")
|
||||
|
||||
// Create database instance
|
||||
db, err := database.New(cfg, log)
|
||||
if err != nil {
|
||||
indicator.Fail(fmt.Sprintf("Failed to create database instance: %v", err))
|
||||
return err
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
// Test tool availability
|
||||
indicator.Start("Checking required tools...")
|
||||
if err := db.ValidateBackupTools(); err != nil {
|
||||
indicator.Fail(fmt.Sprintf("Tool validation failed: %v", err))
|
||||
return err
|
||||
}
|
||||
indicator.Complete("Required tools available")
|
||||
|
||||
// Test connection
|
||||
indicator.Start(fmt.Sprintf("Connecting to %s...", cfg.DatabaseType))
|
||||
if err := db.Connect(ctx); err != nil {
|
||||
indicator.Fail(fmt.Sprintf("Connection failed: %v", err))
|
||||
return err
|
||||
}
|
||||
indicator.Complete("Connected successfully")
|
||||
|
||||
// Test basic operations
|
||||
indicator.Start("Testing database operations...")
|
||||
|
||||
// Get version
|
||||
version, err := db.GetVersion(ctx)
|
||||
if err != nil {
|
||||
indicator.Fail(fmt.Sprintf("Failed to get database version: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
// List databases
|
||||
databases, err := db.ListDatabases(ctx)
|
||||
if err != nil {
|
||||
indicator.Fail(fmt.Sprintf("Failed to list databases: %v", err))
|
||||
return err
|
||||
}
|
||||
|
||||
indicator.Complete("Database operations successful")
|
||||
|
||||
// Display results
|
||||
fmt.Println("Connection Test Results:")
|
||||
fmt.Printf(" Status: Connected ✅\n")
|
||||
fmt.Printf(" Version: %s\n", version)
|
||||
fmt.Printf(" Databases: %d found\n", len(databases))
|
||||
|
||||
if len(databases) > 0 {
|
||||
fmt.Printf(" Database List: ")
|
||||
if len(databases) <= 5 {
|
||||
for i, db := range databases {
|
||||
if i > 0 {
|
||||
fmt.Print(", ")
|
||||
}
|
||||
fmt.Print(db)
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < 3; i++ {
|
||||
if i > 0 {
|
||||
fmt.Print(", ")
|
||||
}
|
||||
fmt.Print(databases[i])
|
||||
}
|
||||
fmt.Printf(", ... (%d more)", len(databases)-3)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("✅ Status check completed successfully!")
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user