First commit of golang CLI.
This commit is contained in:
249
wild-cli/cmd/wild/util/backup.go
Normal file
249
wild-cli/cmd/wild/util/backup.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/wild-cloud/wild-cli/internal/config"
|
||||
"github.com/wild-cloud/wild-cli/internal/environment"
|
||||
"github.com/wild-cloud/wild-cli/internal/external"
|
||||
"github.com/wild-cloud/wild-cli/internal/output"
|
||||
)
|
||||
|
||||
var (
|
||||
backupAll bool
|
||||
)
|
||||
|
||||
func NewBackupCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Backup Wild Cloud system",
|
||||
Long: `Backup the entire Wild Cloud system including applications and data.
|
||||
|
||||
This command performs a comprehensive backup of your Wild Cloud system using restic,
|
||||
including WC_HOME directory and all application data.
|
||||
|
||||
Examples:
|
||||
wild backup
|
||||
wild backup --all`,
|
||||
RunE: runBackup,
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&backupAll, "all", true, "backup all applications")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runBackup(cmd *cobra.Command, args []string) error {
|
||||
output.Header("Wild Cloud System Backup")
|
||||
|
||||
// Initialize environment
|
||||
env := environment.New()
|
||||
if err := env.RequiresProject(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check external tools
|
||||
toolManager := external.NewManager()
|
||||
if err := toolManager.CheckTools(cmd.Context(), []string{"restic"}); err != nil {
|
||||
return fmt.Errorf("required tools not available: %w", err)
|
||||
}
|
||||
|
||||
// Load configuration
|
||||
configMgr := config.NewManager(env.ConfigPath(), env.SecretsPath())
|
||||
|
||||
// Get backup configuration
|
||||
backupRoot, err := configMgr.Get("cloud.backup.root")
|
||||
if err != nil || backupRoot == nil {
|
||||
return fmt.Errorf("backup root not configured. Set cloud.backup.root in config.yaml")
|
||||
}
|
||||
|
||||
backupPassword, err := configMgr.GetSecret("cloud.backupPassword")
|
||||
if err != nil || backupPassword == nil {
|
||||
return fmt.Errorf("backup password not configured. Set cloud.backupPassword in secrets.yaml")
|
||||
}
|
||||
|
||||
stagingDir, err := configMgr.Get("cloud.backup.staging")
|
||||
if err != nil || stagingDir == nil {
|
||||
return fmt.Errorf("backup staging directory not configured. Set cloud.backup.staging in config.yaml")
|
||||
}
|
||||
|
||||
repository := fmt.Sprintf("%v", backupRoot)
|
||||
password := fmt.Sprintf("%v", backupPassword)
|
||||
staging := fmt.Sprintf("%v", stagingDir)
|
||||
|
||||
output.Info("Backup repository: " + repository)
|
||||
|
||||
// Initialize restic tool
|
||||
restic := toolManager.Restic()
|
||||
restic.SetRepository(repository)
|
||||
restic.SetPassword(password)
|
||||
|
||||
// Check if repository exists, initialize if needed
|
||||
output.Info("Checking if restic repository exists...")
|
||||
if err := checkOrInitializeRepository(cmd.Context(), restic); err != nil {
|
||||
return fmt.Errorf("repository initialization failed: %w", err)
|
||||
}
|
||||
|
||||
// Create staging directory
|
||||
if err := os.MkdirAll(staging, 0755); err != nil {
|
||||
return fmt.Errorf("creating staging directory: %w", err)
|
||||
}
|
||||
|
||||
// Generate backup tags
|
||||
today := time.Now().Format("2006-01-02")
|
||||
tags := []string{"wild-cloud", "wc-home", today}
|
||||
|
||||
// Backup entire WC_HOME
|
||||
output.Info("Backing up WC_HOME directory...")
|
||||
wcHome := env.WCHome()
|
||||
if wcHome == "" {
|
||||
wcHome = env.WildCloudDir()
|
||||
}
|
||||
|
||||
if err := restic.Backup(cmd.Context(), []string{wcHome}, []string{".wildcloud/cache"}, tags); err != nil {
|
||||
return fmt.Errorf("backing up WC_HOME: %w", err)
|
||||
}
|
||||
|
||||
output.Success("WC_HOME backup completed")
|
||||
|
||||
// Backup applications if requested
|
||||
if backupAll {
|
||||
output.Info("Running backup for all applications...")
|
||||
if err := backupAllApplications(cmd.Context(), env, configMgr, restic, staging, today); err != nil {
|
||||
return fmt.Errorf("application backup failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Future enhancements
|
||||
// - Backup Kubernetes resources (kubectl get all -A -o yaml)
|
||||
// - Backup persistent volumes
|
||||
// - Backup secrets and configmaps
|
||||
|
||||
output.Success("Wild Cloud system backup completed successfully!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkOrInitializeRepository checks if restic repository exists and initializes if needed
|
||||
func checkOrInitializeRepository(ctx context.Context, restic *external.ResticTool) error {
|
||||
// Try to check repository
|
||||
if err := restic.Check(ctx); err != nil {
|
||||
output.Warning("No existing backup repository found. Initializing restic repository...")
|
||||
if err := restic.InitRepository(ctx); err != nil {
|
||||
return fmt.Errorf("initializing repository: %w", err)
|
||||
}
|
||||
output.Success("Repository initialized successfully")
|
||||
} else {
|
||||
output.Info("Using existing backup repository")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// backupAllApplications backs up all applications using the app backup functionality
|
||||
func backupAllApplications(ctx context.Context, env *environment.Environment, configMgr *config.Manager, restic *external.ResticTool, staging, dateTag string) error {
|
||||
// Get list of applications
|
||||
appsDir := env.AppsDir()
|
||||
if _, err := os.Stat(appsDir); os.IsNotExist(err) {
|
||||
output.Warning("No apps directory found, skipping application backups")
|
||||
return nil
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(appsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("reading apps directory: %w", err)
|
||||
}
|
||||
|
||||
var apps []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
apps = append(apps, entry.Name())
|
||||
}
|
||||
}
|
||||
|
||||
if len(apps) == 0 {
|
||||
output.Warning("No applications found, skipping application backups")
|
||||
return nil
|
||||
}
|
||||
|
||||
output.Info(fmt.Sprintf("Found %d applications to backup: %v", len(apps), apps))
|
||||
|
||||
// For now, we'll use the existing bash script for application backups
|
||||
// This maintains compatibility with the existing backup infrastructure
|
||||
wcRoot := env.WCRoot()
|
||||
if wcRoot == "" {
|
||||
output.Warning("WC_ROOT not set, skipping application-specific backups")
|
||||
return nil
|
||||
}
|
||||
|
||||
appBackupScript := filepath.Join(wcRoot, "bin", "wild-app-backup")
|
||||
if _, err := os.Stat(appBackupScript); os.IsNotExist(err) {
|
||||
output.Warning("App backup script not found, skipping application backups")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute the app backup script
|
||||
bashTool := external.NewBaseTool("bash", "bash")
|
||||
|
||||
// Set environment variables needed by the script
|
||||
oldWCRoot := os.Getenv("WC_ROOT")
|
||||
oldWCHome := os.Getenv("WC_HOME")
|
||||
defer func() {
|
||||
if oldWCRoot != "" {
|
||||
_ = os.Setenv("WC_ROOT", oldWCRoot)
|
||||
}
|
||||
if oldWCHome != "" {
|
||||
_ = os.Setenv("WC_HOME", oldWCHome)
|
||||
}
|
||||
}()
|
||||
|
||||
_ = os.Setenv("WC_ROOT", wcRoot)
|
||||
_ = os.Setenv("WC_HOME", env.WCHome())
|
||||
|
||||
output.Info("Running application backup script...")
|
||||
if _, err := bashTool.Execute(ctx, appBackupScript, "--all"); err != nil {
|
||||
output.Warning(fmt.Sprintf("Application backup script failed: %v", err))
|
||||
return nil // Don't fail the entire backup for app backup issues
|
||||
}
|
||||
|
||||
output.Success("Application backup script completed")
|
||||
|
||||
// Upload each app's backup to restic individually
|
||||
stagingAppsDir := filepath.Join(staging, "apps")
|
||||
if _, err := os.Stat(stagingAppsDir); err != nil {
|
||||
output.Warning("No app staging directory found, skipping app backup uploads")
|
||||
return nil
|
||||
}
|
||||
|
||||
entries, err = os.ReadDir(stagingAppsDir)
|
||||
if err != nil {
|
||||
output.Warning(fmt.Sprintf("Reading app staging directory failed: %v", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
appName := entry.Name()
|
||||
appBackupDir := filepath.Join(stagingAppsDir, appName)
|
||||
|
||||
output.Info(fmt.Sprintf("Uploading backup for app: %s", appName))
|
||||
|
||||
tags := []string{"wild-cloud", appName, dateTag}
|
||||
if err := restic.Backup(ctx, []string{appBackupDir}, []string{}, tags); err != nil {
|
||||
output.Warning(fmt.Sprintf("Failed to backup app %s: %v", appName, err))
|
||||
continue
|
||||
}
|
||||
|
||||
output.Success(fmt.Sprintf("Backup for app '%s' completed", appName))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user