From fc028a2c553a33144de6572ead284357e0fe661b Mon Sep 17 00:00:00 2001 From: Jonas Kaninda Date: Sun, 26 Jan 2025 13:43:39 +0100 Subject: [PATCH] feat: add multiple backup rescued mode for scheduled mode --- pkg/azure.go | 6 +++- pkg/backup.go | 59 +++++++++++++++++++++++++++++--------- pkg/config.go | 5 ++-- pkg/helper.go | 8 +++--- pkg/remote.go | 12 ++++++-- pkg/s3.go | 6 +++- pkg/var.go | 1 + templates/email-error.tmpl | 4 +-- 8 files changed, 75 insertions(+), 26 deletions(-) diff --git a/pkg/azure.go b/pkg/azure.go index 64f7048..7a0c602 100644 --- a/pkg/azure.go +++ b/pkg/azure.go @@ -39,7 +39,11 @@ func azureBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to Azure Blob Storage") // Backup database - BackupDatabase(db, config.backupFileName, disableCompression) + err := BackupDatabase(db, config.backupFileName, disableCompression) + if err != nil { + recoverMode(err, "Error backing up database") + return + } finalFileName := config.backupFileName if config.encryption { encryptBackup(config) diff --git a/pkg/backup.go b/pkg/backup.go index 0836021..b52c566 100644 --- a/pkg/backup.go +++ b/pkg/backup.go @@ -72,13 +72,17 @@ func scheduledMode(db *dbConfig, config *BackupConfig) { // Test backup utils.Info("Testing backup configurations...") - testDatabaseConnection(db) + err := testDatabaseConnection(db) + if err != nil { + utils.Error("Error connecting to database: %s", db.dbName) + utils.Fatal("Error: %s", err) + } utils.Info("Testing backup configurations...done") utils.Info("Creating backup job...") // Create a new cron instance c := cron.New() - _, err := c.AddFunc(config.cronExpression, func() { + _, err = c.AddFunc(config.cronExpression, func() { BackupTask(db, config) utils.Info("Next backup time is: %v", utils.CronNextTime(config.cronExpression).Format(timeFormat)) @@ -147,6 +151,7 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) { if bkConfig.cronExpression == "" { multiBackupTask(conf.Databases, bkConfig) } else { + backupRescueMode = conf.BackupRescueMode // Check if cronExpression is valid if utils.IsValidCronExpression(bkConfig.cronExpression) { utils.Info("Running backup in Scheduled mode") @@ -157,7 +162,11 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) { // Test backup utils.Info("Testing backup configurations...") for _, db := range conf.Databases { - testDatabaseConnection(getDatabase(db)) + err = testDatabaseConnection(getDatabase(db)) + if err != nil { + recoverMode(err, fmt.Sprintf("Error connecting to database: %s", db.Name)) + continue + } } utils.Info("Testing backup configurations...done") utils.Info("Creating backup job...") @@ -187,16 +196,19 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) { } // BackupDatabase backup database -func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) { +func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool) error { storagePath = os.Getenv("STORAGE_PATH") utils.Info("Starting database backup...") err := os.Setenv("MYSQL_PWD", db.dbPassword) if err != nil { - return + return fmt.Errorf("failed to set MYSQL_PWD environment variable: %v", err) + } + err = testDatabaseConnection(db) + if err != nil { + return fmt.Errorf(err.Error()) } - testDatabaseConnection(db) // Backup Database database utils.Info("Backing up database...") @@ -211,24 +223,24 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool ) output, err := cmd.Output() if err != nil { - utils.Fatal(err.Error()) + return fmt.Errorf("failed to backup database: %v", err) } // save output file, err := os.Create(filepath.Join(tmpPath, backupFileName)) if err != nil { - utils.Fatal(err.Error()) + return fmt.Errorf("failed to create backup file: %v", err) } defer func(file *os.File) { err := file.Close() if err != nil { - utils.Fatal(err.Error()) + return } }(file) _, err = file.Write(output) if err != nil { - utils.Fatal(err.Error()) + return err } utils.Info("Database has been backed up") @@ -237,14 +249,14 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool cmd := exec.Command("mysqldump", "-h", db.dbHost, "-P", db.dbPort, "-u", db.dbUserName, db.dbName) stdout, err := cmd.StdoutPipe() if err != nil { - log.Fatal(err) + return fmt.Errorf("failed to backup database: %v", err) } gzipCmd := exec.Command("gzip") gzipCmd.Stdin = stdout gzipCmd.Stdout, err = os.Create(filepath.Join(tmpPath, backupFileName)) err = gzipCmd.Start() if err != nil { - return + return fmt.Errorf("failed to backup database: %v", err) } if err := cmd.Run(); err != nil { log.Fatal(err) @@ -252,13 +264,18 @@ func BackupDatabase(db *dbConfig, backupFileName string, disableCompression bool if err := gzipCmd.Wait(); err != nil { log.Fatal(err) } - utils.Info("Database has been backed up") } + utils.Info("Database has been backed up") + return nil } func localBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to local storage") - BackupDatabase(db, config.backupFileName, disableCompression) + err := BackupDatabase(db, config.backupFileName, disableCompression) + if err != nil { + recoverMode(err, "Error backing up database") + return + } finalFileName := config.backupFileName if config.encryption { encryptBackup(config) @@ -333,3 +350,17 @@ func encryptBackup(config *BackupConfig) { } } +func recoverMode(err error, msg string) { + if err != nil { + if backupRescueMode { + utils.NotifyError(fmt.Sprintf("%s : %v", msg, err)) + utils.Error(msg) + utils.Error("Backup rescue mode is enabled") + utils.Error("Backup will continue") + } else { + utils.Error(msg) + utils.Fatal("Error: %v", err) + } + } + +} diff --git a/pkg/config.go b/pkg/config.go index 726a9b6..349d6c6 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -42,8 +42,9 @@ type Database struct { Path string `yaml:"path"` } type Config struct { - Databases []Database `yaml:"databases"` - CronExpression string `yaml:"cronExpression"` + CronExpression string `yaml:"cronExpression"` + BackupRescueMode bool `yaml:"backupRescueMode"` + Databases []Database `yaml:"databases"` } type dbConfig struct { diff --git a/pkg/helper.go b/pkg/helper.go index 0fc86e4..4ca5030 100644 --- a/pkg/helper.go +++ b/pkg/helper.go @@ -66,10 +66,10 @@ func deleteTemp() { } // TestDatabaseConnection tests the database connection -func testDatabaseConnection(db *dbConfig) { +func testDatabaseConnection(db *dbConfig) error { err := os.Setenv("MYSQL_PWD", db.dbPassword) if err != nil { - return + return fmt.Errorf("failed to set MYSQL_PWD environment variable: %v", err) } utils.Info("Connecting to %s database ...", db.dbName) // Set database name for notification error @@ -81,11 +81,11 @@ func testDatabaseConnection(db *dbConfig) { cmd.Stderr = &out err = cmd.Run() if err != nil { - utils.Fatal("Error testing database connection: %v\nOutput: %s", err, out.String()) + return fmt.Errorf("failed to connect to %s database: %v", db.dbName, err) } utils.Info("Successfully connected to %s database", db.dbName) - + return nil } // checkPubKeyFile checks gpg public key diff --git a/pkg/remote.go b/pkg/remote.go index 0e126a5..d01069d 100644 --- a/pkg/remote.go +++ b/pkg/remote.go @@ -39,7 +39,11 @@ import ( func sshBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to Remote server") // Backup database - BackupDatabase(db, config.backupFileName, disableCompression) + err := BackupDatabase(db, config.backupFileName, disableCompression) + if err != nil { + recoverMode(err, "Error backing up database") + return + } finalFileName := config.backupFileName if config.encryption { encryptBackup(config) @@ -156,7 +160,11 @@ func ftpBackup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to the remote FTP server") // Backup database - BackupDatabase(db, config.backupFileName, disableCompression) + err := BackupDatabase(db, config.backupFileName, disableCompression) + if err != nil { + recoverMode(err, "Error backing up database") + return + } finalFileName := config.backupFileName if config.encryption { encryptBackup(config) diff --git a/pkg/s3.go b/pkg/s3.go index 4f903a5..90822b9 100644 --- a/pkg/s3.go +++ b/pkg/s3.go @@ -39,7 +39,11 @@ func s3Backup(db *dbConfig, config *BackupConfig) { utils.Info("Backup database to s3 storage") // Backup database - BackupDatabase(db, config.backupFileName, disableCompression) + err := BackupDatabase(db, config.backupFileName, disableCompression) + if err != nil { + recoverMode(err, "Error backing up database") + return + } finalFileName := config.backupFileName if config.encryption { encryptBackup(config) diff --git a/pkg/var.go b/pkg/var.go index 4b8327d..4da2c8b 100644 --- a/pkg/var.go +++ b/pkg/var.go @@ -42,6 +42,7 @@ var ( usingKey = false backupSize int64 = 0 startTime = time.Now() + backupRescueMode = false ) // dbHVars Required environment variables for database diff --git a/templates/email-error.tmpl b/templates/email-error.tmpl index 8919241..7dc2fbb 100644 --- a/templates/email-error.tmpl +++ b/templates/email-error.tmpl @@ -60,10 +60,10 @@

We recommend investigating the issue as soon as possible to prevent potential data loss or service disruptions.

-

For more information, visit the pg-bkup documentation.

+

For more information, visit the mysql-bkup documentation.