Merge pull request #135 from jkaninda/refactor

Refactor
This commit is contained in:
2024-11-19 04:24:16 +01:00
committed by GitHub
12 changed files with 669 additions and 23 deletions

16
go.mod
View File

@@ -3,31 +3,29 @@ module github.com/jkaninda/pg-bkup
go 1.23.2
require (
github.com/ProtonMail/gopenpgp/v2 v2.7.5
github.com/aws/aws-sdk-go v1.55.5
github.com/bramvdbogaerde/go-scp v1.5.0
github.com/go-mail/mail v2.3.1+incompatible
github.com/jkaninda/encryptor v0.0.0-20241013064832-ed4bd6a1b221
github.com/jkaninda/go-storage v0.1.1
github.com/jlaffaye/ftp v0.2.0
github.com/robfig/cron/v3 v3.0.1
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.28.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/ProtonMail/go-crypto v1.0.0 // indirect
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect
github.com/ProtonMail/gopenpgp/v2 v2.7.5 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/bramvdbogaerde/go-scp v1.5.0 // indirect
github.com/cloudflare/circl v1.5.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jlaffaye/ftp v0.2.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/crypto v0.29.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.20.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 // indirect
)

6
go.sum
View File

@@ -56,6 +56,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -78,6 +80,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@@ -93,6 +97,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -3,15 +3,14 @@ package internal
import (
"fmt"
"github.com/jkaninda/encryptor"
"github.com/jkaninda/go-storage/pkg/ftp"
"github.com/jkaninda/go-storage/pkg/local"
"github.com/jkaninda/go-storage/pkg/s3"
"github.com/jkaninda/go-storage/pkg/ssh"
"github.com/jkaninda/pg-bkup/pkg/logger"
"github.com/jkaninda/pg-bkup/pkg/storage/ftp"
"github.com/jkaninda/pg-bkup/pkg/storage/local"
"github.com/jkaninda/pg-bkup/pkg/storage/s3"
"github.com/jkaninda/pg-bkup/pkg/storage/ssh"
"github.com/jkaninda/pg-bkup/utils"
"github.com/robfig/cron/v3"
"github.com/spf13/cobra"
"log"
"os"
"os/exec"
@@ -51,7 +50,7 @@ func scheduledMode(db *dbConfig, config *BackupConfig) {
// Test backup
logger.Info("Testing backup configurations...")
BackupTask(db, config)
testDatabaseConnection(db)
logger.Info("Testing backup configurations...done")
logger.Info("Creating backup job...")
// Create a new cron instance
@@ -116,6 +115,9 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
if conf.CronExpression != "" {
bkConfig.cronExpression = conf.CronExpression
}
if len(conf.Databases) == 0 {
logger.Fatal("No databases found")
}
// Check if cronExpression is defined
if bkConfig.cronExpression == "" {
multiBackupTask(conf.Databases, bkConfig)
@@ -129,7 +131,9 @@ func startMultiBackup(bkConfig *BackupConfig, configFile string) {
// Test backup
logger.Info("Testing backup configurations...")
multiBackupTask(conf.Databases, bkConfig)
for _, db := range conf.Databases {
testDatabaseConnection(getDatabase(db))
}
logger.Info("Testing backup configurations...done")
logger.Info("Creating backup job...")
// Create a new cron instance

View File

@@ -20,8 +20,8 @@ import (
)
func intro() {
logger.Info("Starting PostgreSQL Backup...")
logger.Info("Copyright (c) 2024 Jonas Kaninda ")
fmt.Println("Starting PostgreSQL Backup...")
fmt.Println("Copyright (c) 2024 Jonas Kaninda ")
}
// copyToTmp copy file to temporary directory

View File

@@ -9,15 +9,15 @@ package internal
import (
"github.com/jkaninda/pg-bkup/pkg/logger"
"github.com/jkaninda/pg-bkup/pkg/storage/ftp"
"github.com/jkaninda/pg-bkup/pkg/storage/local"
"github.com/jkaninda/pg-bkup/pkg/storage/s3"
"github.com/jkaninda/pg-bkup/pkg/storage/ssh"
"os"
"os/exec"
"path/filepath"
"github.com/jkaninda/encryptor"
"github.com/jkaninda/go-storage/pkg/ftp"
"github.com/jkaninda/go-storage/pkg/local"
"github.com/jkaninda/go-storage/pkg/s3"
"github.com/jkaninda/go-storage/pkg/ssh"
"github.com/jkaninda/pg-bkup/utils"
"github.com/spf13/cobra"
)

142
pkg/storage/ftp/ftp.go Normal file
View File

@@ -0,0 +1,142 @@
package ftp
import (
"fmt"
pkg "github.com/jkaninda/pg-bkup/pkg/storage"
"github.com/jlaffaye/ftp"
"io"
"os"
"path/filepath"
"time"
)
type ftpStorage struct {
*pkg.Backend
client *ftp.ServerConn
}
// Config holds the SSH connection details
type Config struct {
Host string
User string
Password string
Port string
LocalPath string
RemotePath string
}
// createClient creates FTP Client
func createClient(conf Config) (*ftp.ServerConn, error) {
ftpClient, err := ftp.Dial(fmt.Sprintf("%s:%s", conf.Host, conf.Port), ftp.DialWithTimeout(5*time.Second))
if err != nil {
return nil, fmt.Errorf("failed to connect to FTP: %w", err)
}
err = ftpClient.Login(conf.User, conf.Password)
if err != nil {
return nil, fmt.Errorf("failed to log in to FTP: %w", err)
}
return ftpClient, nil
}
// NewStorage creates new Storage
func NewStorage(conf Config) (pkg.Storage, error) {
client, err := createClient(conf)
if err != nil {
return nil, err
}
return &ftpStorage{
client: client,
Backend: &pkg.Backend{
RemotePath: conf.RemotePath,
LocalPath: conf.LocalPath,
},
}, nil
}
// Copy copies file to the remote server
func (s ftpStorage) Copy(fileName string) error {
ftpClient := s.client
defer func(ftpClient *ftp.ServerConn) {
err := ftpClient.Quit()
if err != nil {
return
}
}(ftpClient)
filePath := filepath.Join(s.LocalPath, fileName)
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", fileName, err)
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
return
}
}(file)
remoteFilePath := filepath.Join(s.RemotePath, fileName)
err = ftpClient.Stor(remoteFilePath, file)
if err != nil {
return fmt.Errorf("failed to upload file %s: %w", filepath.Join(s.LocalPath, fileName), err)
}
return nil
}
// CopyFrom copies a file from the remote server to local storage
func (s ftpStorage) CopyFrom(fileName string) error {
ftpClient := s.client
defer func(ftpClient *ftp.ServerConn) {
err := ftpClient.Quit()
if err != nil {
return
}
}(ftpClient)
remoteFilePath := filepath.Join(s.RemotePath, fileName)
r, err := ftpClient.Retr(remoteFilePath)
if err != nil {
return fmt.Errorf("failed to retrieve file %s: %w", fileName, err)
}
defer func(r *ftp.Response) {
err := r.Close()
if err != nil {
return
}
}(r)
localFilePath := filepath.Join(s.LocalPath, fileName)
outFile, err := os.Create(localFilePath)
if err != nil {
return fmt.Errorf("failed to create local file %s: %w", fileName, err)
}
defer func(outFile *os.File) {
err := outFile.Close()
if err != nil {
return
}
}(outFile)
_, err = io.Copy(outFile, r)
if err != nil {
return fmt.Errorf("failed to copy data to local file %s: %w", fileName, err)
}
return nil
}
// Prune deletes old backup created more than specified days
func (s ftpStorage) Prune(retentionDays int) error {
fmt.Println("Deleting old backup from a remote server is not implemented yet")
return nil
}
// Name returns the storage name
func (s ftpStorage) Name() string {
return "ftp"
}

116
pkg/storage/local/local.go Normal file
View File

@@ -0,0 +1,116 @@
package local
import (
pkg "github.com/jkaninda/pg-bkup/pkg/storage"
"io"
"os"
"path/filepath"
"time"
)
type localStorage struct {
*pkg.Backend
}
type Config struct {
LocalPath string
RemotePath string
}
// NewStorage creates new Storage
func NewStorage(conf Config) pkg.Storage {
return &localStorage{
Backend: &pkg.Backend{
LocalPath: conf.LocalPath,
RemotePath: conf.RemotePath,
},
}
}
// Copy copies file to the local destination path
func (l localStorage) Copy(file string) error {
if _, err := os.Stat(filepath.Join(l.LocalPath, file)); os.IsNotExist(err) {
return err
}
err := copyFile(filepath.Join(l.LocalPath, file), filepath.Join(l.RemotePath, file))
if err != nil {
return err
}
return nil
}
// CopyFrom copies file from a Path to local path
func (l localStorage) CopyFrom(file string) error {
if _, err := os.Stat(filepath.Join(l.RemotePath, file)); os.IsNotExist(err) {
return err
}
err := copyFile(filepath.Join(l.RemotePath, file), filepath.Join(l.LocalPath, file))
if err != nil {
return err
}
return nil
}
// Prune deletes old backup created more than specified days
func (l localStorage) Prune(retentionDays int) error {
currentTime := time.Now()
// Delete file
deleteFile := func(filePath string) error {
err := os.Remove(filePath)
return err
}
// Walk through the directory and delete files modified more than specified days ago
err := filepath.Walk(l.RemotePath, func(filePath string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}
// Check if it's a regular file and if it was modified more than specified days ago
if fileInfo.Mode().IsRegular() {
timeDiff := currentTime.Sub(fileInfo.ModTime())
if timeDiff.Hours() > 24*float64(retentionDays) {
err := deleteFile(filePath)
if err != nil {
return err
}
}
}
return nil
})
if err != nil {
return err
}
return nil
}
// Name returns the storage name
func (l localStorage) Name() string {
return "local"
}
// copyFile copies file
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer func(in *os.File) {
err := in.Close()
if err != nil {
return
}
}(in)
out, err := os.Create(dst)
if err != nil {
return err
}
_, err = io.Copy(out, in)
if err != nil {
err := out.Close()
if err != nil {
return err
}
return err
}
return out.Close()
}

View File

@@ -0,0 +1,66 @@
package local
import (
"fmt"
"os"
"path/filepath"
"testing"
)
const content = "Lorem ipsum dolor sit amet. Eum eius voluptas sit vitae vitae aut sequi molestias hic accusamus consequatur"
const inputFile = "file.txt"
const localPath = "./tests/local"
const RemotePath = "./tests/remote"
func TestCopy(t *testing.T) {
err := os.MkdirAll(localPath, 0777)
if err != nil {
t.Error(err)
}
err = os.MkdirAll(RemotePath, 0777)
if err != nil {
t.Error(err)
}
_, err = createFile(filepath.Join(localPath, inputFile), content)
if err != nil {
t.Error(err)
}
l := NewStorage(Config{
LocalPath: "./tests/local",
RemotePath: "./tests/remote",
})
err = l.Copy(inputFile)
if err != nil {
t.Error(err)
}
fmt.Printf("File copied to %s\n", filepath.Join(RemotePath, inputFile))
}
func createFile(fileName, content string) ([]byte, error) {
// Create a file named hello.txt
file, err := os.Create(fileName)
if err != nil {
fmt.Println("Error creating file:", err)
return nil, err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
fmt.Println("Error closing file:", err)
return
}
}(file)
// Write the message to the file
_, err = file.WriteString(content)
if err != nil {
fmt.Println("Error writing to file:", err)
return nil, err
}
fmt.Printf("Successfully wrote to %s\n", fileName)
fileBytes, err := os.ReadFile(fileName)
return fileBytes, err
}

176
pkg/storage/s3/s3.go Normal file
View File

@@ -0,0 +1,176 @@
package s3
import (
"bytes"
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
pkg "github.com/jkaninda/pg-bkup/pkg/storage"
"net/http"
"os"
"path/filepath"
"time"
)
type s3Storage struct {
*pkg.Backend
client *session.Session
bucket string
}
// Config holds the AWS S3 config
type Config struct {
Endpoint string
Bucket string
AccessKey string
SecretKey string
Region string
DisableSsl bool
ForcePathStyle bool
LocalPath string
RemotePath string
}
// CreateSession creates a new AWS session
func createSession(conf Config) (*session.Session, error) {
s3Config := &aws.Config{
Credentials: credentials.NewStaticCredentials(conf.AccessKey, conf.SecretKey, ""),
Endpoint: aws.String(conf.Endpoint),
Region: aws.String(conf.Region),
DisableSSL: aws.Bool(conf.DisableSsl),
S3ForcePathStyle: aws.Bool(conf.ForcePathStyle),
}
return session.NewSession(s3Config)
}
// NewStorage creates new Storage
func NewStorage(conf Config) (pkg.Storage, error) {
sess, err := createSession(conf)
if err != nil {
return nil, err
}
return &s3Storage{
client: sess,
bucket: conf.Bucket,
Backend: &pkg.Backend{
RemotePath: conf.RemotePath,
LocalPath: conf.LocalPath,
},
}, nil
}
// Copy copies file to S3 storage
func (s s3Storage) Copy(fileName string) error {
svc := s3.New(s.client)
file, err := os.Open(filepath.Join(s.LocalPath, fileName))
if err != nil {
return err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
return
}
}(file)
fileInfo, err := file.Stat()
if err != nil {
return err
}
objectKey := filepath.Join(s.RemotePath, fileName)
buffer := make([]byte, fileInfo.Size())
_, err = file.Read(buffer)
if err != nil {
return err
}
fileBytes := bytes.NewReader(buffer)
fileType := http.DetectContentType(buffer)
_, err = svc.PutObject(&s3.PutObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(objectKey),
Body: fileBytes,
ContentLength: aws.Int64(fileInfo.Size()),
ContentType: aws.String(fileType),
})
if err != nil {
return err
}
return nil
}
// CopyFrom copies a file from S3 to local storage
func (s s3Storage) CopyFrom(fileName string) error {
file, err := os.Create(filepath.Join(s.LocalPath, fileName))
if err != nil {
return err
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
fmt.Printf("Error closing file: %v\n", err)
return
}
}(file)
objectKey := filepath.Join(s.RemotePath, fileName)
downloader := s3manager.NewDownloader(s.client)
_, err = downloader.Download(file,
&s3.GetObjectInput{
Bucket: aws.String(s.bucket),
Key: aws.String(objectKey),
})
if err != nil {
return err
}
return nil
}
// Prune deletes old backup created more than specified days
func (s s3Storage) Prune(retentionDays int) error {
svc := s3.New(s.client)
// Get the current time
now := time.Now()
backupRetentionDays := now.AddDate(0, 0, -retentionDays)
// List objects in the bucket
listObjectsInput := &s3.ListObjectsV2Input{
Bucket: aws.String(s.bucket),
Prefix: aws.String(s.RemotePath),
}
err := svc.ListObjectsV2Pages(listObjectsInput, func(page *s3.ListObjectsV2Output, lastPage bool) bool {
for _, object := range page.Contents {
if object.LastModified.Before(backupRetentionDays) {
// Object is older than retention days, delete it
_, err := svc.DeleteObject(&s3.DeleteObjectInput{
Bucket: aws.String(s.bucket),
Key: object.Key,
})
if err != nil {
fmt.Printf("failed to delete object %s: %v", *object.Key, err)
} else {
fmt.Printf("Deleted object %s", *object.Key)
}
}
}
return !lastPage
})
if err != nil {
return fmt.Errorf("failed to list objects: %v", err)
}
return nil
}
// Name returns the storage name
func (s s3Storage) Name() string {
return "s3"
}

124
pkg/storage/ssh/ssh.go Normal file
View File

@@ -0,0 +1,124 @@
package ssh
import (
"context"
"errors"
"fmt"
"github.com/bramvdbogaerde/go-scp"
"github.com/bramvdbogaerde/go-scp/auth"
pkg "github.com/jkaninda/pg-bkup/pkg/storage"
"golang.org/x/crypto/ssh"
"os"
"path/filepath"
)
type sshStorage struct {
*pkg.Backend
client scp.Client
}
// Config holds the SSH connection details
type Config struct {
Host string
User string
Password string
Port string
IdentifyFile string
LocalPath string
RemotePath string
}
// createClient creates SSH Client
func createClient(conf Config) (scp.Client, error) {
if _, err := os.Stat(conf.IdentifyFile); os.IsNotExist(err) {
clientConfig, err := auth.PrivateKey(conf.User, conf.IdentifyFile, ssh.InsecureIgnoreHostKey())
return scp.NewClient(fmt.Sprintf("%s:%s", conf.Host, conf.Port), &clientConfig), err
} else {
if conf.Password == "" {
return scp.Client{}, errors.New("ssh password required")
}
clientConfig, err := auth.PasswordKey(conf.User, conf.Password, ssh.InsecureIgnoreHostKey())
return scp.NewClient(fmt.Sprintf("%s:%s", conf.Host, conf.Port), &clientConfig), err
}
}
// NewStorage creates new Storage
func NewStorage(conf Config) (pkg.Storage, error) {
client, err := createClient(conf)
if err != nil {
return nil, err
}
return &sshStorage{
client: client,
Backend: &pkg.Backend{
RemotePath: conf.RemotePath,
LocalPath: conf.LocalPath,
},
}, nil
}
// Copy copies file to the remote server
func (s sshStorage) Copy(fileName string) error {
client := s.client
// Connect to the remote server
err := client.Connect()
if err != nil {
return errors.New("couldn't establish a connection to the remote server")
}
// Open the local file
filePath := filepath.Join(s.LocalPath, fileName)
file, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("failed to open file %s: %w", filePath, err)
}
defer client.Close()
// Copy file to the remote server
err = client.CopyFromFile(context.Background(), *file, filepath.Join(s.RemotePath, fileName), "0655")
if err != nil {
return fmt.Errorf("failed to copy file to remote server: %w", err)
}
return nil
}
// CopyFrom copies a file from the remote server to local storage
func (s sshStorage) CopyFrom(fileName string) error {
// Create a new SCP client
client := s.client
// Connect to the remote server
err := client.Connect()
if err != nil {
return errors.New("couldn't establish a connection to the remote server")
}
// Close client connection after the file has been copied
defer client.Close()
file, err := os.OpenFile(filepath.Join(s.LocalPath, fileName), os.O_RDWR|os.O_CREATE, 0777)
if err != nil {
return errors.New("couldn't open the output file")
}
defer func(file *os.File) {
err := file.Close()
if err != nil {
return
}
}(file)
err = client.CopyFromRemote(context.Background(), file, filepath.Join(s.RemotePath, fileName))
if err != nil {
return err
}
return nil
}
// Prune deletes old backup created more than specified days
func (s sshStorage) Prune(retentionDays int) error {
fmt.Println("Deleting old backup from a remote server is not implemented yet")
return nil
}
// Name returns the storage name
func (s sshStorage) Name() string {
return "ssh"
}

14
pkg/storage/storage.go Normal file
View File

@@ -0,0 +1,14 @@
package pkg
type Storage interface {
Copy(fileName string) error
CopyFrom(fileName string) error
Prune(retentionDays int) error
Name() string
}
type Backend struct {
// Local Path
LocalPath string
// Remote path or Destination path
RemotePath string
}

View File

@@ -1,4 +1,4 @@
[✅ Database Backup Notification {{.Database}}
✅ Database Backup Notification {{.Database}}
Hi,
Backup of the {{.Database}} database has been successfully completed on {{.EndTime}}.