From 1df1c46a964aa15f70b0c5268d5a0c37d9e50424 Mon Sep 17 00:00:00 2001 From: Jonas Kaninda Date: Wed, 9 Oct 2024 08:32:51 +0200 Subject: [PATCH] feat: add multi backup --- go.mod | 1 + pkg/backup.go | 77 ++++++++++++++++++++++++++++++++++++++++++++++----- pkg/config.go | 28 +++++++++++++++++++ pkg/helper.go | 44 +++++++++++++++++++++++++++++ pkg/var.go | 1 + 5 files changed, 144 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 8ae07ba..876dd6d 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/spf13/cobra v1.8.0 golang.org/x/crypto v0.28.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( diff --git a/pkg/backup.go b/pkg/backup.go index abca1ac..bb417f5 100644 --- a/pkg/backup.go +++ b/pkg/backup.go @@ -20,17 +20,22 @@ import ( func StartBackup(cmd *cobra.Command) { intro() - dbConf = initDbConfig(cmd) //Initialize backup configs config := initBackupConfig(cmd) - - if config.cronExpression == "" { - BackupTask(dbConf, config) + //Load backup configuration file + configFile, err := loadConfigFile() + if err == nil { + startMultiBackup(config, configFile) } else { - if utils.IsValidCronExpression(config.cronExpression) { - scheduledMode(dbConf, config) + dbConf = initDbConfig(cmd) + if config.cronExpression == "" { + BackupTask(dbConf, config) } else { - utils.Fatal("Cron expression is not valid: %s", config.cronExpression) + if utils.IsValidCronExpression(config.cronExpression) { + scheduledMode(dbConf, config) + } else { + utils.Fatal("Cron expression is not valid: %s", config.cronExpression) + } } } @@ -63,6 +68,15 @@ func scheduledMode(db *dbConfig, config *BackupConfig) { defer c.Stop() select {} } +func multiBackupTask(databases []Database, bkConfig *BackupConfig) { + for _, db := range databases { + //Check if path is defined in config file + if db.Path != "" { + bkConfig.remotePath = db.Path + } + BackupTask(getDatabase(db), bkConfig) + } +} func BackupTask(db *dbConfig, config *BackupConfig) { utils.Info("Starting backup task...") //Generate file name @@ -85,6 +99,55 @@ func BackupTask(db *dbConfig, config *BackupConfig) { localBackup(db, config) } } +func startMultiBackup(bkConfig *BackupConfig, configFile string) { + utils.Info("Starting multiple backup jobs...") + var conf = &Config{} + conf, err := readConf(configFile) + if err != nil { + utils.Fatal("Error reading config file: %s", err) + } + //Check if cronExpression is defined in config file + if conf.CronExpression != "" { + bkConfig.cronExpression = conf.CronExpression + } + // Check if cronExpression is defined + if bkConfig.cronExpression == "" { + multiBackupTask(conf.Databases, bkConfig) + } else { + // Check if cronExpression is valid + if utils.IsValidCronExpression(bkConfig.cronExpression) { + utils.Info("Running MultiBackup in Scheduled mode") + utils.Info("Backup cron expression: %s", bkConfig.cronExpression) + utils.Info("Storage type %s ", bkConfig.storage) + + //Test backup + utils.Info("Testing backup configurations...") + multiBackupTask(conf.Databases, bkConfig) + utils.Info("Testing backup configurations...done") + utils.Info("Creating multi backup job...") + // Create a new cron instance + c := cron.New() + + _, err := c.AddFunc(bkConfig.cronExpression, func() { + // Create a channel + multiBackupTask(conf.Databases, bkConfig) + }) + if err != nil { + return + } + // Start the cron scheduler + c.Start() + utils.Info("Creating multi backup job...done") + utils.Info("Backup job started") + defer c.Stop() + select {} + + } else { + utils.Fatal("Cron expression is not valid: %s", bkConfig.cronExpression) + } + } + +} func intro() { utils.Info("Starting PostgreSQL Backup...") utils.Info("Copyright (c) 2024 Jonas Kaninda ") diff --git a/pkg/config.go b/pkg/config.go index d898523..8cd457c 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -7,6 +7,7 @@ package pkg import ( + "errors" "fmt" "github.com/jkaninda/pg-bkup/utils" "github.com/spf13/cobra" @@ -14,7 +15,17 @@ import ( "strconv" ) +type Database struct { + Host string `yaml:"host"` + Port string `yaml:"port"` + Name string `yaml:"name"` + User string `yaml:"user"` + Password string `yaml:"password"` + Path string `yaml:"path"` +} type Config struct { + Databases []Database `yaml:"databases"` + CronExpression string `yaml:"cronExpression"` } type dbConfig struct { @@ -92,6 +103,16 @@ func initDbConfig(cmd *cobra.Command) *dbConfig { return &dConf } +func getDatabase(database Database) *dbConfig { + return &dbConfig{ + dbHost: database.Host, + dbPort: database.Port, + dbName: database.Name, + dbUserName: database.User, + dbPassword: database.Password, + } +} + // loadSSHConfig loads the SSH configuration from environment variables func loadSSHConfig() (*SSHConfig, error) { utils.GetEnvVariable("SSH_HOST", "SSH_HOST_NAME") @@ -245,3 +266,10 @@ func initTargetDbConfig() *targetDbConfig { } return &tdbConfig } +func loadConfigFile() (string, error) { + backupConfigFile, err := checkConfigFile(os.Getenv("BACKUP_CONFIG_FILE")) + if err == nil { + return backupConfigFile, nil + } + return "", errors.New("backup config file not found") +} diff --git a/pkg/helper.go b/pkg/helper.go index 4f236ee..deb98f7 100644 --- a/pkg/helper.go +++ b/pkg/helper.go @@ -10,6 +10,7 @@ import ( "bytes" "fmt" "github.com/jkaninda/pg-bkup/utils" + "gopkg.in/yaml.v3" "os" "os/exec" "path/filepath" @@ -139,6 +140,8 @@ func testDatabaseConnection(db *dbConfig) { utils.Info("Successfully connected to %s database", db.dbName) } + +// checkPubKeyFile checks gpg public key func checkPubKeyFile(pubKey string) (string, error) { // Define possible key file names keyFiles := []string{filepath.Join(gpgHome, "public_key.asc"), filepath.Join(gpgHome, "public_key.gpg"), pubKey} @@ -160,6 +163,8 @@ func checkPubKeyFile(pubKey string) (string, error) { // Return an error if neither file exists return "", fmt.Errorf("no public key file found") } + +// checkPrKeyFile checks private key func checkPrKeyFile(prKey string) (string, error) { // Define possible key file names keyFiles := []string{filepath.Join(gpgHome, "private_key.asc"), filepath.Join(gpgHome, "private_key.gpg"), prKey} @@ -181,3 +186,42 @@ func checkPrKeyFile(prKey string) (string, error) { // Return an error if neither file exists return "", fmt.Errorf("no public key file found") } +func readConf(configFile string) (*Config, error) { + //configFile := filepath.Join("./", filename) + if utils.FileExists(configFile) { + buf, err := os.ReadFile(configFile) + if err != nil { + return nil, err + } + + c := &Config{} + err = yaml.Unmarshal(buf, c) + if err != nil { + return nil, fmt.Errorf("in file %q: %w", configFile, err) + } + + return c, err + } + return nil, fmt.Errorf("config file %q not found", configFile) +} +func checkConfigFile(filePath string) (string, error) { + // Define possible config file names + configFiles := []string{filepath.Join(workingDir, "config.yaml"), filepath.Join(workingDir, "config.yml"), filePath} + + // Loop through config file names and check if they exist + for _, configFile := range configFiles { + if _, err := os.Stat(configFile); err == nil { + // File exists + return configFile, nil + } else if os.IsNotExist(err) { + // File does not exist, continue to the next one + continue + } else { + // An unexpected error occurred + return "", err + } + } + + // Return an error if neither file exists + return "", fmt.Errorf("no config file found") +} diff --git a/pkg/var.go b/pkg/var.go index 2702bd5..1541f94 100644 --- a/pkg/var.go +++ b/pkg/var.go @@ -16,6 +16,7 @@ var ( file = "" storagePath = "/backup" + workingDir = "/config" disableCompression = false encryption = false usingKey = false