From 1bdac8c90089e811a76ffcd8efc2589d9e3957a0 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:05 +0100 Subject: [PATCH 01/12] stdin backup support --- pkg/cli/cli.go | 79 +++++++++++++++++++++++++++++++++++++++--------- pkg/cli/types.go | 14 ++++++--- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 1878e64..3e59168 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -2,6 +2,7 @@ package cli import ( "bufio" + "bytes" "compress/gzip" "context" "fmt" @@ -20,7 +21,7 @@ import ( const flagTag = "flag" const gzipType = "application/x-gzip" -// includeFlag returns an string slice of [, ], or [] +// includeFlag returns a string slice of [, ], or [] func includeFlag(flag, val string) []string { var cmd []string if flag != "" { @@ -188,33 +189,83 @@ func ParseCommandLine(cmd CommandType) []string { } // RunWithTimeout executes the given binary within a max execution time -func RunWithTimeout(runContext context.Context, cmd CommandType, timeout time.Duration) ([]byte, error) { +func RunWithTimeout(runContext context.Context, cmd *CommandType, outputToPipe bool, timeout time.Duration) ([]byte, error) { ctx, cancel := context.WithTimeout(runContext, timeout) defer cancel() - return Run(ctx, cmd) + return Run(ctx, cmd, outputToPipe) } // Run executes the given binary -func Run(ctx context.Context, cmd CommandType) ([]byte, error) { - var out []byte - var err error - commandLine := ParseCommandLine(cmd) +func Run(ctx context.Context, cmd *CommandType, outputToPipe bool) ([]byte, error) { + if outputToPipe && cmd.Pipe != nil { + return nil, errors.New("output is supposed to be used but Pipe is already not nil") + } + var cmdExec *exec.Cmd + var outBuffer bytes.Buffer + var stdin io.WriteCloser + var err, pipeErr error + commandLine := ParseCommandLine(*cmd) log.WithField("command", strings.Join(commandLine, " ")).Debug("executing command") - if ctx != nil { - out, err = exec.CommandContext(ctx, commandLine[0], commandLine[1:]...).CombinedOutput() //nolint: gosec - if ctx.Err() != nil { - return out, fmt.Errorf("failed to execute command: timed out or canceled") + // TODO: Assure that --stdin and --stdin-filename are set if the backup from STDIN is enabled + if ctx == nil { + ctx = context.Background() + } + cCtx, cancelFunc := context.WithCancel(ctx) + defer cancelFunc() + cmdExec = exec.CommandContext(cCtx, commandLine[0], commandLine[1:]...) //nolint: gosec + if outputToPipe { + cmd.Pipe, err = cmdExec.StdoutPipe() + if err != nil { + return nil, errors.Wrapf(err, "error while getting STDOUT pipe for command: %s", strings.Join(commandLine, " ")) } + cmd.ReadingDone = make(chan bool, 1) } else { - out, err = exec.Command(commandLine[0], commandLine[1:]...).CombinedOutput() //nolint: gosec + cmdExec.Stdout = &outBuffer + } + cmdExec.Stderr = &outBuffer + if !outputToPipe && cmd.Pipe != nil { + stdin, err = cmdExec.StdinPipe() + if err != nil { + return nil, errors.Wrapf(err, "error while getting STDIN pipe for command: %s", strings.Join(commandLine, " ")) + } + } + + err = cmdExec.Start() + if err != nil { + return nil, errors.Wrapf(err, "error while getting starting restic command: %s", strings.Join(commandLine, " ")) + } + if outputToPipe { + for done := range cmd.ReadingDone { + if done { + cancelFunc() + break + } + } + } else if cmd.Pipe != nil { + _, pipeErr = io.Copy(stdin, cmd.Pipe) + cmd.ReadingDone <- true + close(cmd.ReadingDone) + if pipeErr != nil { + cancelFunc() + } + _ = stdin.Close() + } + err = cmdExec.Wait() + cancelFunc() + if ctx != nil && ctx.Err() != nil { + return outBuffer.Bytes(), fmt.Errorf("failed to execute command: timed out or canceled") + } + + if pipeErr != nil { + return outBuffer.Bytes(), fmt.Errorf("failed to pipe data into STDIN for command: %s", err) } if err != nil { - return out, fmt.Errorf("failed to execute command: %s", err) + return outBuffer.Bytes(), fmt.Errorf("failed to execute command: %s", err) } log.WithField("command", strings.Join(commandLine, " ")).Debug("successfully executed command") - return out, nil + return outBuffer.Bytes(), nil } // GzipFile compresses a file with gzip and returns the path of the created archive diff --git a/pkg/cli/types.go b/pkg/cli/types.go index 04de247..37d2e90 100644 --- a/pkg/cli/types.go +++ b/pkg/cli/types.go @@ -1,13 +1,17 @@ package cli +import "io" + const GzipSuffix = ".gz" type CommandType struct { - Binary string - Command string - Args []string - Nice *int // https://linux.die.net/man/1/nice - IONice *int // https://linux.die.net/man/1/ionice + Binary string + Command string + Args []string + Pipe io.Reader + ReadingDone chan bool + Nice *int // https://linux.die.net/man/1/nice + IONice *int // https://linux.die.net/man/1/ionice } type PipedCommandsPids struct { From 6bdb7b8e98031f0d48448a9709637a0a8ce98ba3 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:02:49 +0100 Subject: [PATCH 02/12] postgres tests for stdin backups --- test/pkg/source/internal/testcommons.go | 38 ++++++ test/pkg/source/postgrestest/postgres_test.go | 115 ++++++++++++++---- 2 files changed, 129 insertions(+), 24 deletions(-) diff --git a/test/pkg/source/internal/testcommons.go b/test/pkg/source/internal/testcommons.go index f13edc0..aff1c1a 100644 --- a/test/pkg/source/internal/testcommons.go +++ b/test/pkg/source/internal/testcommons.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "strings" + "time" "github.com/pkg/errors" "github.com/spf13/viper" @@ -68,6 +69,43 @@ func TestSetup() { os.Setenv("RESTIC_PASSWORD", ResticPassword) } +// GetProgramVersion tries to run the given program with the given version argument to determine its version. +// Leave the version string empty to use "--version". +func GetProgramVersion(program, versionArg string) (string, error) { + ctx, cancelFunc := context.WithTimeout(context.Background(), time.Second*10) + defer cancelFunc() + if versionArg == "" { + versionArg = "--version" + } + cmd := exec.CommandContext(ctx, program, versionArg) + version, err := cmd.Output() + if err != nil { + return "", errors.Wrapf(err, "error running '%s %s'", program, versionArg) + } + return string(version), nil +} + +// GetProgramsVersions does the same as GetProgramVersion but for multiple programs. Give the programs and their versions +// like this: "[program]", "[version]", "[program]", "[version]"... - Leave the version string empty to use "--version". +// Returns after all programs have been tested. +func GetProgramsVersions(programsAndVersions ...string) (versions []string, err error) { + versions = make([]string, 0, len(programsAndVersions)) + errs := make([]string, 0, len(programsAndVersions)) + for i := 0; i < len(programsAndVersions); i += 2 { + v, err := GetProgramVersion(programsAndVersions[i], programsAndVersions[i+1]) + versions = append(versions, v) + if err != nil { + errs = append(errs, err.Error()) + } + } + err = nil + if len(errs) > 0 { + err = errors.Errorf("got error(s) while determining the versions of required programsAndVersions for testing:\n\t%s", + strings.Join(errs, "\n\t")) + } + return +} + // DoResticRestore pulls the given backup from the given restic repo func DoResticRestore(ctx context.Context, resticContainer TestContainerSetup, dataDir string) error { cmd := exec.CommandContext(ctx, "restic", "restore", "-r", // nolint: gosec diff --git a/test/pkg/source/postgrestest/postgres_test.go b/test/pkg/source/postgrestest/postgres_test.go index c98d7f2..34a0077 100644 --- a/test/pkg/source/postgrestest/postgres_test.go +++ b/test/pkg/source/postgrestest/postgres_test.go @@ -6,6 +6,7 @@ import ( "database/sql" "fmt" "os" + "path" "testing" "time" @@ -42,9 +43,19 @@ const plainKind = "plain" type PGDumpAndRestoreTestSuite struct { suite.Suite + resticExists bool } func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) SetupTest() { + _, err := commons.GetProgramsVersions("pg_dump", "", "pg_restore", "", "psql", "") + if err != nil { + panic(err) + } + _, err = commons.GetProgramVersion("restic", "version") + pgDumpAndRestoreTestSuite.resticExists = err == nil + if !pgDumpAndRestoreTestSuite.resticExists { + pgDumpAndRestoreTestSuite.T().Logf("can't determine restics version: %v", err) + } commons.TestSetup() } @@ -80,7 +91,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "tar", backupPath, + "tar", backupPath, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -91,7 +102,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "tar", backupPath, + "tar", backupPath, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -104,7 +115,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "plain", backupPathPlain, + "plain", backupPathPlain, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -115,7 +126,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "plain", backupPathPlain, + "plain", backupPathPlain, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -149,7 +160,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "tar", backupPathZip, + "tar", backupPathZip, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -160,7 +171,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "tar", backupPathZip, + "tar", backupPathZip, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -173,7 +184,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "plain", backupPathPlainZip, + "plain", backupPathPlainZip, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -184,7 +195,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe Port: "", Address: "", }, - "plain", backupPathPlainZip, + "plain", backupPathPlainZip, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -193,6 +204,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe // TestPGDumpRestic performs an integration test for brudi pgdump with restic func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreRestic() { + pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") ctx := context.Background() // remove backup files after test @@ -218,7 +230,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore var testData []testStruct testData, err = pgDoBackup( ctx, true, resticContainer, - "tar", backupPath, + "tar", backupPath, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -226,7 +238,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore var restoreResult []testStruct restoreResult, err = pgDoRestore( ctx, true, resticContainer, - "tar", backupPath, + "tar", backupPath, false, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -235,6 +247,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore // TestPGDumpResticGzip performs an integration test for brudi pgdump with restic and gzip func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreResticGzip() { + pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") ctx := context.Background() // remove backup files after test @@ -260,7 +273,51 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore var testData []testStruct testData, err = pgDoBackup( ctx, true, resticContainer, - "tar", backupPathZip, + "tar", backupPathZip, false, + ) + pgDumpAndRestoreTestSuite.Require().NoError(err) + + // restore test data with brudi and retrieve it from the db for verification + var restoreResult []testStruct + restoreResult, err = pgDoRestore( + ctx, true, resticContainer, + "tar", backupPathZip, false, + ) + pgDumpAndRestoreTestSuite.Require().NoError(err) + + assert.DeepEqual(pgDumpAndRestoreTestSuite.T(), testData, restoreResult) +} + +// TestPGDumpResticStdin performs an integration test for brudi pgdump with restic over STDIN +func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreResticStdin() { + pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + ctx := context.Background() + backupPath := path.Base(backupPath) + + // remove backup files after test + defer func() { + // delete folder with backup file + removeErr := os.RemoveAll(backupPath) + if removeErr != nil { + log.WithError(removeErr).Error("failed to remove pgdump backup files") + } + }() + + // setup a container running the restic rest-server + resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) + pgDumpAndRestoreTestSuite.Require().NoError(err) + defer func() { + resticErr := resticContainer.Container.Terminate(ctx) + if resticErr != nil { + log.WithError(resticErr).Error("failed to terminate pgdump restic container") + } + }() + + // backup test data with brudi and retain test data for verification + var testData []testStruct + testData, err = pgDoBackup( + ctx, true, resticContainer, + "tar", backupPath, true, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -268,7 +325,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore var restoreResult []testStruct restoreResult, err = pgDoRestore( ctx, true, resticContainer, - "tar", backupPathZip, + "tar", backupPath, true, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -282,7 +339,7 @@ func TestPGDumpAndRestoreTestSuite(t *testing.T) { // pgDoBackup populates a database with data and performs a backup, optionally with restic func pgDoBackup( ctx context.Context, useRestic bool, - resticContainer commons.TestContainerSetup, format, path string, + resticContainer commons.TestContainerSetup, format, path string, doStdinBackup bool, ) ([]testStruct, error) { // create a postgres container to test backup function pgBackupTarget, err := commons.NewTestContainerSetup(ctx, &pgRequest, pgPort) @@ -328,7 +385,7 @@ func pgDoBackup( } // create a brudi config for pgdump - testPGConfig := createPGConfig(pgBackupTarget, useRestic, resticContainer.Address, resticContainer.Port, format, path) + testPGConfig := createPGConfig(pgBackupTarget, useRestic, resticContainer.Address, resticContainer.Port, format, path, doStdinBackup) err = viper.ReadConfig(bytes.NewBuffer(testPGConfig)) if err != nil { return []testStruct{}, err @@ -346,7 +403,7 @@ func pgDoBackup( // pgDoRestore restores data from backup and retrieves it for verification, optionally using restic func pgDoRestore( ctx context.Context, useRestic bool, resticContainer commons.TestContainerSetup, - format, path string, + format, path string, backuppedWithStdin bool, ) ([]testStruct, error) { // setup second postgres container to test if correct data is restored @@ -362,7 +419,7 @@ func pgDoRestore( }() // create a brudi configuration for pgrestore, depending on backup format - restorePGConfig := createPGConfig(pgRestoreTarget, useRestic, resticContainer.Address, resticContainer.Port, format, path) + restorePGConfig := createPGConfig(pgRestoreTarget, useRestic, resticContainer.Address, resticContainer.Port, format, path, backuppedWithStdin) err = viper.ReadConfig(bytes.NewBuffer(restorePGConfig)) if err != nil { return []testStruct{}, err @@ -427,7 +484,7 @@ func pgDoRestore( } // createPGConfig creates a brudi config for the pgdump and the correct restoration command based on format -func createPGConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, format, path string) []byte { +func createPGConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, format, filepath string, doStdinBackup bool) []byte { var restoreConfig string if format != plainKind { restoreConfig = fmt.Sprintf( @@ -441,7 +498,7 @@ func createPGConfig(container commons.TestContainerSetup, useRestic bool, restic dbname: %s additionalArgs: [] sourcefile: %s -`, hostName, container.Port, postgresPW, postgresUser, postgresDB, path, +`, hostName, container.Port, postgresPW, postgresUser, postgresDB, filepath, ) } else { restoreConfig = fmt.Sprintf( @@ -455,14 +512,19 @@ func createPGConfig(container commons.TestContainerSetup, useRestic bool, restic dbname: %s additionalArgs: [] sourcefile: %s -`, hostName, container.Port, postgresUser, postgresPW, postgresDB, path, +`, hostName, container.Port, postgresUser, postgresPW, postgresDB, filepath, ) } var resticConfig string if useRestic { + restoreTarget := "/" + if doStdinBackup { + restoreTarget = path.Join(restoreTarget, filepath) + } resticConfig = fmt.Sprintf( - `restic: + `doPipingBackup: %t +restic: global: flags: repo: rest:http://%s:%s/ @@ -476,12 +538,17 @@ func createPGConfig(container commons.TestContainerSetup, useRestic bool, restic keepYearly: 0 restore: flags: - target: "/" + target: "%s" id: "latest" -`, resticIP, resticPort, +`, doStdinBackup, resticIP, resticPort, restoreTarget, ) } + filenameKey := "file" + if doStdinBackup { + filenameKey = "stdinFilename" + } + result := []byte(fmt.Sprintf( ` pgdump: @@ -492,13 +559,13 @@ pgdump: password: %s username: %s dbName: %s - file: %s + %s: %s format: %s additionalArgs: [] %s %s -`, hostName, container.Port, postgresPW, postgresUser, postgresDB, path, format, restoreConfig, resticConfig, +`, hostName, container.Port, postgresPW, postgresUser, postgresDB, filenameKey, filepath, format, restoreConfig, resticConfig, )) return result } From 19d96492824a40798b41b99c240ff0287e364a61 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:02:58 +0100 Subject: [PATCH 03/12] program checks before every test --- test/pkg/source/internal/testcommons.go | 26 +++++++++++++++++++ test/pkg/source/mongodbtest/mongodb_test.go | 11 +++++++- test/pkg/source/mysqltest/mysql_test.go | 15 ++++++++++- test/pkg/source/postgrestest/postgres_test.go | 24 ++++++++++------- test/pkg/source/redisdump/redisdump_test.go | 15 ++++++++++- test/pkg/source/tar/tar_test.go | 7 ++++- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/test/pkg/source/internal/testcommons.go b/test/pkg/source/internal/testcommons.go index aff1c1a..760178d 100644 --- a/test/pkg/source/internal/testcommons.go +++ b/test/pkg/source/internal/testcommons.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "strings" + "testing" "time" "github.com/pkg/errors" @@ -69,6 +70,24 @@ func TestSetup() { os.Setenv("RESTIC_PASSWORD", ResticPassword) } +// CheckProgramsAndRestic determines the given programs versions (see GetProgramsVersions) and evaluates restics existence. +// Returns the determined program versions and a bool that describes if restic exists. +// Lets the test fail if the program versions check wasn't successful. +func CheckProgramsAndRestic(t *testing.T, programsAndVersions ...string) (versions []string, resticExists bool) { + var err error + versions, err = GetProgramsVersions(programsAndVersions...) + if err != nil { + t.Error(err) + t.FailNow() + } + _, err = GetProgramVersion("restic", "version") + resticExists = err == nil + if !resticExists { + t.Logf("can't determine restics version: %v", err) + } + return +} + // GetProgramVersion tries to run the given program with the given version argument to determine its version. // Leave the version string empty to use "--version". func GetProgramVersion(program, versionArg string) (string, error) { @@ -90,6 +109,13 @@ func GetProgramVersion(program, versionArg string) (string, error) { // Returns after all programs have been tested. func GetProgramsVersions(programsAndVersions ...string) (versions []string, err error) { versions = make([]string, 0, len(programsAndVersions)) + if len(programsAndVersions) == 0 { + return versions, nil + } + if len(programsAndVersions)%2 != 0 { + return versions, errors.New("the count of the given programs and their version arguments aren't a multiple of 2") + } + errs := make([]string, 0, len(programsAndVersions)) for i := 0; i < len(programsAndVersions); i += 2 { v, err := GetProgramVersion(programsAndVersions[i], programsAndVersions[i+1]) diff --git a/test/pkg/source/mongodbtest/mongodb_test.go b/test/pkg/source/mongodbtest/mongodb_test.go index 41326cc..4c9a77d 100644 --- a/test/pkg/source/mongodbtest/mongodb_test.go +++ b/test/pkg/source/mongodbtest/mongodb_test.go @@ -34,6 +34,7 @@ const logString = "Waiting for connections" type MongoDumpAndRestoreTestSuite struct { suite.Suite + resticExists bool } func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) SetupTest() { @@ -82,6 +83,10 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo // TestBasicMongoDBDumpRestic performs an integration test for the `mongodump` command with restic support func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreRestic() { + mongoDumpAndRestoreTestSuite.True(mongoDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + if !mongoDumpAndRestoreTestSuite.resticExists { + return + } ctx := context.Background() // remove files after test is done @@ -114,7 +119,11 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo } func TestMongoDumpAndRestoreTestSuite(t *testing.T) { - suite.Run(t, new(MongoDumpAndRestoreTestSuite)) + _, resticExists := commons.CheckProgramsAndRestic(t, "mongodump", "", "mongorestore", "") + testSuite := &MongoDumpAndRestoreTestSuite{ + resticExists: resticExists, + } + suite.Run(t, testSuite) } // mongoDoBackup performs a mongodump and returns the test data that was used for verification purposes diff --git a/test/pkg/source/mysqltest/mysql_test.go b/test/pkg/source/mysqltest/mysql_test.go index 2d8a0a3..3bf3ce7 100644 --- a/test/pkg/source/mysqltest/mysql_test.go +++ b/test/pkg/source/mysqltest/mysql_test.go @@ -41,6 +41,7 @@ const mysqlImage = "docker.io/bitnami/mysql:latest" type MySQLDumpAndRestoreTestSuite struct { suite.Suite + resticExists bool } // struct for test data @@ -129,6 +130,10 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestBasicMySQL // TestMySQLDumpRestic performs an integration test for mysqldump with restic func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpAndRestoreRestic() { + mySQLDumpAndRestoreTestSuite.True(mySQLDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + if !mySQLDumpAndRestoreTestSuite.resticExists { + return + } ctx := context.Background() defer func() { @@ -163,6 +168,10 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpA // TestMySQLDumpResticGzip performs an integration test for mysqldump with restic and gzip func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpAndRestoreResticGzip() { + mySQLDumpAndRestoreTestSuite.True(mySQLDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + if !mySQLDumpAndRestoreTestSuite.resticExists { + return + } ctx := context.Background() defer func() { @@ -196,7 +205,11 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpA } func TestMySQLDumpAndRestoreTestSuite(t *testing.T) { - suite.Run(t, new(MySQLDumpAndRestoreTestSuite)) + _, resticExists := commons.CheckProgramsAndRestic(t, "mysqldump", "", "mysql", "") + testSuite := &MySQLDumpAndRestoreTestSuite{ + resticExists: resticExists, + } + suite.Run(t, testSuite) } // mySQLDoBackup inserts test data into the given database and then executes brudi's `mysqldump` diff --git a/test/pkg/source/postgrestest/postgres_test.go b/test/pkg/source/postgrestest/postgres_test.go index 34a0077..105779d 100644 --- a/test/pkg/source/postgrestest/postgres_test.go +++ b/test/pkg/source/postgrestest/postgres_test.go @@ -47,15 +47,6 @@ type PGDumpAndRestoreTestSuite struct { } func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) SetupTest() { - _, err := commons.GetProgramsVersions("pg_dump", "", "pg_restore", "", "psql", "") - if err != nil { - panic(err) - } - _, err = commons.GetProgramVersion("restic", "version") - pgDumpAndRestoreTestSuite.resticExists = err == nil - if !pgDumpAndRestoreTestSuite.resticExists { - pgDumpAndRestoreTestSuite.T().Logf("can't determine restics version: %v", err) - } commons.TestSetup() } @@ -205,6 +196,9 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe // TestPGDumpRestic performs an integration test for brudi pgdump with restic func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreRestic() { pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + if !pgDumpAndRestoreTestSuite.resticExists { + return + } ctx := context.Background() // remove backup files after test @@ -248,6 +242,9 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore // TestPGDumpResticGzip performs an integration test for brudi pgdump with restic and gzip func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreResticGzip() { pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + if !pgDumpAndRestoreTestSuite.resticExists { + return + } ctx := context.Background() // remove backup files after test @@ -291,6 +288,9 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore // TestPGDumpResticStdin performs an integration test for brudi pgdump with restic over STDIN func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreResticStdin() { pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") + if !pgDumpAndRestoreTestSuite.resticExists { + return + } ctx := context.Background() backupPath := path.Base(backupPath) @@ -333,7 +333,11 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore } func TestPGDumpAndRestoreTestSuite(t *testing.T) { - suite.Run(t, new(PGDumpAndRestoreTestSuite)) + _, resticExists := commons.CheckProgramsAndRestic(t, "pg_dump", "", "pg_restore", "", "psql", "") + testSuite := &PGDumpAndRestoreTestSuite{ + resticExists: resticExists, + } + suite.Run(t, testSuite) } // pgDoBackup populates a database with data and performs a backup, optionally with restic diff --git a/test/pkg/source/redisdump/redisdump_test.go b/test/pkg/source/redisdump/redisdump_test.go index be9b3b6..b19465f 100644 --- a/test/pkg/source/redisdump/redisdump_test.go +++ b/test/pkg/source/redisdump/redisdump_test.go @@ -37,6 +37,7 @@ const redisImage = "docker.io/bitnami/redis:latest" type RedisDumpTestSuite struct { suite.Suite + resticExists bool } func (redisDumpTestSuite *RedisDumpTestSuite) SetupTest() { @@ -118,6 +119,10 @@ func (redisDumpTestSuite *RedisDumpTestSuite) TestBasicRedisDumpGzip() { // TestBasicRedisDumpRestic performs an integration test for brudi's `redisdump` command with restic func (redisDumpTestSuite *RedisDumpTestSuite) TestRedisDumpRestic() { + redisDumpTestSuite.True(redisDumpTestSuite.resticExists, "can't use restic on this machine") + if !redisDumpTestSuite.resticExists { + return + } ctx := context.Background() // remove backup files after test @@ -154,6 +159,10 @@ func (redisDumpTestSuite *RedisDumpTestSuite) TestRedisDumpRestic() { // TestBasicRedisDumpRestic performs an integration test for brudi's `redisdump` command with restic and gzip func (redisDumpTestSuite *RedisDumpTestSuite) TestRedisDumpResticGzip() { + redisDumpTestSuite.True(redisDumpTestSuite.resticExists, "can't use restic on this machine") + if !redisDumpTestSuite.resticExists { + return + } ctx := context.Background() // remove backup files after test @@ -189,7 +198,11 @@ func (redisDumpTestSuite *RedisDumpTestSuite) TestRedisDumpResticGzip() { } func TestRedisDumpTestSuite(t *testing.T) { - suite.Run(t, new(RedisDumpTestSuite)) + _, resticExists := commons.CheckProgramsAndRestic(t, "tar", "--version") + testSuite := &RedisDumpTestSuite{ + resticExists: resticExists, + } + suite.Run(t, testSuite) } // redisDoBackup populates a database with test data and performs a backup diff --git a/test/pkg/source/tar/tar_test.go b/test/pkg/source/tar/tar_test.go index 60ec6d4..cbe8ac6 100644 --- a/test/pkg/source/tar/tar_test.go +++ b/test/pkg/source/tar/tar_test.go @@ -25,6 +25,7 @@ const extractedPath = "/tmp/testdata/tarTestFile.yaml" type TarTestSuite struct { suite.Suite + resticExists bool } func (tarTestSuite *TarTestSuite) SetupTest() { @@ -70,7 +71,11 @@ func (tarTestSuite *TarTestSuite) TestBasicTarDump() { } func TestTarTestSuite(t *testing.T) { - suite.Run(t, new(TarTestSuite)) + _, resticExists := commons.CheckProgramsAndRestic(t, "tar", "--version") + testSuite := &TarTestSuite{ + resticExists: resticExists, + } + suite.Run(t, testSuite) } // tarDoBackup uses brudi to compress a test file into a tar.gz archive and returns the uncompressed files md5 hash From eae6dc28a86add9ba81036ba1fd0faa7ee3faa86 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:00 +0100 Subject: [PATCH 04/12] stdin test for all possible sources --- test/pkg/source/mongodbtest/mongodb_test.go | 35 +++++-- test/pkg/source/mysqltest/mysql_test.go | 75 ++++++--------- test/pkg/source/postgrestest/postgres_test.go | 95 ++----------------- test/pkg/source/tar/tar_test.go | 9 +- 4 files changed, 68 insertions(+), 146 deletions(-) diff --git a/test/pkg/source/mongodbtest/mongodb_test.go b/test/pkg/source/mongodbtest/mongodb_test.go index 4c9a77d..b50db98 100644 --- a/test/pkg/source/mongodbtest/mongodb_test.go +++ b/test/pkg/source/mongodbtest/mongodb_test.go @@ -5,6 +5,7 @@ import ( "context" "fmt" "os" + "path" "testing" "github.com/mittwald/brudi/pkg/source" @@ -63,7 +64,7 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo ctx, false, commons.TestContainerSetup{ Port: "", Address: "", - }, + }, false, ) mongoDumpAndRestoreTestSuite.Require().NoError(err) @@ -81,8 +82,7 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo assert.DeepEqual(mongoDumpAndRestoreTestSuite.T(), testData, results) } -// TestBasicMongoDBDumpRestic performs an integration test for the `mongodump` command with restic support -func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreRestic() { +func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) basicMongoDBDumpAndRestoreRestic(backupPath string, useStdin bool) { mongoDumpAndRestoreTestSuite.True(mongoDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") if !mongoDumpAndRestoreTestSuite.resticExists { return @@ -108,7 +108,7 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo // backup database and retain test data for verification var testData []interface{} - testData, err = mongoDoBackup(ctx, true, resticContainer) + testData, err = mongoDoBackup(ctx, true, resticContainer, useStdin) mongoDumpAndRestoreTestSuite.Require().NoError(err) // restore database from backup and pull test data for verification @@ -118,6 +118,16 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo assert.DeepEqual(mongoDumpAndRestoreTestSuite.T(), testData, results) } +// TestBasicMongoDBDumpRestic performs an integration test for the `mongodump` command with restic support +func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreRestic() { + mongoDumpAndRestoreTestSuite.basicMongoDBDumpAndRestoreRestic(backupPath, false) +} + +// TestBasicMongoDBDumpResticStdin performs an integration test for the `mongodump` command with restic support using STDIN +func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreResticStdin() { + mongoDumpAndRestoreTestSuite.basicMongoDBDumpAndRestoreRestic(backupPath, true) +} + func TestMongoDumpAndRestoreTestSuite(t *testing.T) { _, resticExists := commons.CheckProgramsAndRestic(t, "mongodump", "", "mongorestore", "") testSuite := &MongoDumpAndRestoreTestSuite{ @@ -129,7 +139,7 @@ func TestMongoDumpAndRestoreTestSuite(t *testing.T) { // mongoDoBackup performs a mongodump and returns the test data that was used for verification purposes func mongoDoBackup( ctx context.Context, useRestic bool, - resticContainer commons.TestContainerSetup, + resticContainer commons.TestContainerSetup, doStdinBackup bool, ) ([]interface{}, error) { // create a mongodb-container to test backup function mongoBackupTarget, err := commons.NewTestContainerSetup(ctx, &mongoRequest, mongoPort) @@ -164,7 +174,7 @@ func mongoDoBackup( } // create brudi config for backup - backupMongoConfig := createMongoConfig(mongoBackupTarget, useRestic, resticContainer.Address, resticContainer.Port, dumpKind) + backupMongoConfig := createMongoConfig(mongoBackupTarget, useRestic, resticContainer.Address, resticContainer.Port, dumpKind, doStdinBackup) err = viper.ReadConfig(bytes.NewBuffer(backupMongoConfig)) if err != nil { return []interface{}{}, err @@ -197,7 +207,7 @@ func mongoDoRestore( }() // create brudi config for restoration - restoreMongoConfig := createMongoConfig(mongoRestoreTarget, useRestic, resticContainer.Address, resticContainer.Port, restoreKind) + restoreMongoConfig := createMongoConfig(mongoRestoreTarget, useRestic, resticContainer.Address, resticContainer.Port, restoreKind, false) err = viper.ReadConfig(bytes.NewBuffer(restoreMongoConfig)) if err != nil { return []interface{}{}, err @@ -286,7 +296,7 @@ func newMongoClient(target *commons.TestContainerSetup) (mongo.Client, error) { } // createMongoConfig creates a brudi config for the brudi command specified via kind -func createMongoConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, kind string) []byte { +func createMongoConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, kind string, doStdinBackup bool) []byte { if !useRestic { return []byte(fmt.Sprintf( ` @@ -303,8 +313,13 @@ func createMongoConfig(container commons.TestContainerSetup, useRestic bool, res `, kind, container.Address, container.Port, mongoUser, mongoPW, backupPath, )) } + restoreTarget := "/" + if doStdinBackup { + restoreTarget = path.Join(restoreTarget, backupPath) + } return []byte(fmt.Sprintf( ` + doPipingBackup: %t %s: options: flags: @@ -329,9 +344,9 @@ func createMongoConfig(container commons.TestContainerSetup, useRestic bool, res keepYearly: 0 restore: flags: - target: "/" + target: "%s" id: "latest" -`, kind, container.Address, container.Port, mongoUser, mongoPW, backupPath, resticIP, resticPort, +`, doStdinBackup, kind, container.Address, container.Port, mongoUser, mongoPW, backupPath, resticIP, resticPort, restoreTarget, )) } diff --git a/test/pkg/source/mysqltest/mysql_test.go b/test/pkg/source/mysqltest/mysql_test.go index 3bf3ce7..412ed15 100644 --- a/test/pkg/source/mysqltest/mysql_test.go +++ b/test/pkg/source/mysqltest/mysql_test.go @@ -6,6 +6,7 @@ import ( "database/sql" "fmt" "os" + "path" "testing" "time" @@ -77,7 +78,7 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestBasicMySQL ctx, false, commons.TestContainerSetup{ Port: "", Address: "", - }, backupPath, + }, backupPath, false, ) mySQLDumpAndRestoreTestSuite.Require().NoError(err) @@ -111,7 +112,7 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestBasicMySQL ctx, false, commons.TestContainerSetup{ Port: "", Address: "", - }, backupPathZip, + }, backupPathZip, false, ) mySQLDumpAndRestoreTestSuite.Require().NoError(err) @@ -128,8 +129,7 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestBasicMySQL assert.DeepEqual(mySQLDumpAndRestoreTestSuite.T(), testData, restoreResult) } -// TestMySQLDumpRestic performs an integration test for mysqldump with restic -func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpAndRestoreRestic() { +func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) mySQLDumpAndRestoreRestic(backupPath string, useStdin bool) { mySQLDumpAndRestoreTestSuite.True(mySQLDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") if !mySQLDumpAndRestoreTestSuite.resticExists { return @@ -155,7 +155,7 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpA // backup test data with brudi and retain test data for verification var testData []TestStruct - testData, err = mySQLDoBackup(ctx, true, resticContainer, backupPath) + testData, err = mySQLDoBackup(ctx, true, resticContainer, backupPath, useStdin) mySQLDumpAndRestoreTestSuite.Require().NoError(err) // restore database from backup and pull test data from it for verification @@ -166,42 +166,19 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpA assert.DeepEqual(mySQLDumpAndRestoreTestSuite.T(), testData, restoreResult) } +// TestMySQLDumpRestic performs an integration test for mysqldump with restic +func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpAndRestoreRestic() { + mySQLDumpAndRestoreTestSuite.mySQLDumpAndRestoreRestic(backupPath, false) +} + // TestMySQLDumpResticGzip performs an integration test for mysqldump with restic and gzip func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpAndRestoreResticGzip() { - mySQLDumpAndRestoreTestSuite.True(mySQLDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") - if !mySQLDumpAndRestoreTestSuite.resticExists { - return - } - ctx := context.Background() - - defer func() { - removeErr := os.Remove(backupPathZip) - if removeErr != nil { - log.WithError(removeErr).Error("failed to clean up mysql backup files") - } - }() - - // setup a container running the restic rest-server - resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) - mySQLDumpAndRestoreTestSuite.Require().NoError(err) - defer func() { - resticErr := resticContainer.Container.Terminate(ctx) - if resticErr != nil { - log.WithError(resticErr).Error("failed to terminate mysql restic container") - } - }() - - // backup test data with brudi and retain test data for verification - var testData []TestStruct - testData, err = mySQLDoBackup(ctx, true, resticContainer, backupPathZip) - mySQLDumpAndRestoreTestSuite.Require().NoError(err) - - // restore database from backup and pull test data from it for verification - var restoreResult []TestStruct - restoreResult, err = mySQLDoRestore(ctx, true, resticContainer, backupPathZip) - mySQLDumpAndRestoreTestSuite.Require().NoError(err) + mySQLDumpAndRestoreTestSuite.mySQLDumpAndRestoreRestic(backupPathZip, false) +} - assert.DeepEqual(mySQLDumpAndRestoreTestSuite.T(), testData, restoreResult) +// TestMySQLDumpResticStdin performs an integration test for mysqldump with restic using STDIN +func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) TestMySQLDumpAndRestoreResticStdin() { + mySQLDumpAndRestoreTestSuite.mySQLDumpAndRestoreRestic(backupPath, true) } func TestMySQLDumpAndRestoreTestSuite(t *testing.T) { @@ -215,7 +192,7 @@ func TestMySQLDumpAndRestoreTestSuite(t *testing.T) { // mySQLDoBackup inserts test data into the given database and then executes brudi's `mysqldump` func mySQLDoBackup( ctx context.Context, useRestic bool, - resticContainer commons.TestContainerSetup, path string, + resticContainer commons.TestContainerSetup, path string, useStdinBackup bool, ) ([]TestStruct, error) { // setup a mysql container to backup from mySQLBackupTarget, err := commons.NewTestContainerSetup(ctx, &mySQLRequest, sqlPort) @@ -261,7 +238,7 @@ func mySQLDoBackup( } // create brudi config for mysqldump - MySQLBackupConfig := createMySQLConfig(mySQLBackupTarget, useRestic, resticContainer.Address, resticContainer.Port, path) + MySQLBackupConfig := createMySQLConfig(mySQLBackupTarget, useRestic, resticContainer.Address, resticContainer.Port, path, useStdinBackup) err = viper.ReadConfig(bytes.NewBuffer(MySQLBackupConfig)) if err != nil { return []TestStruct{}, err @@ -293,7 +270,7 @@ func mySQLDoRestore( }() // create a brudi config for mysql restore - MySQLRestoreConfig := createMySQLConfig(mySQLRestoreTarget, useRestic, resticContainer.Address, resticContainer.Port, path) + MySQLRestoreConfig := createMySQLConfig(mySQLRestoreTarget, useRestic, resticContainer.Address, resticContainer.Port, path, false) err = viper.ReadConfig(bytes.NewBuffer(MySQLRestoreConfig)) if err != nil { return []TestStruct{}, err @@ -356,11 +333,15 @@ func mySQLDoRestore( } // createMySQLConfig creates a brudi config for mysqldump and mysqlrestore command. -func createMySQLConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, path string) []byte { +func createMySQLConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, filepath string, doStdinBackup bool) []byte { var resticConfig string if useRestic { + restoreTarget := "/" + if doStdinBackup { + restoreTarget = path.Join(restoreTarget, filepath) + } resticConfig = fmt.Sprintf( - ` + `doPipingBackup: %t restic: global: flags: @@ -375,9 +356,9 @@ restic: keepYearly: 0 restore: flags: - target: "/" + target: "%s" id: "latest" -`, resticIP, resticPort, +`, doStdinBackup, resticIP, resticPort, restoreTarget, ) } @@ -405,8 +386,8 @@ mysqlrestore: Database: %s additionalArgs: [] sourceFile: %s%s -`, hostName, container.Port, mySQLRootPW, mySQLRoot, path, - hostName, container.Port, mySQLRootPW, mySQLRoot, mySQLDatabase, path, +`, hostName, container.Port, mySQLRootPW, mySQLRoot, filepath, + hostName, container.Port, mySQLRootPW, mySQLRoot, mySQLDatabase, filepath, resticConfig, )) return result diff --git a/test/pkg/source/postgrestest/postgres_test.go b/test/pkg/source/postgrestest/postgres_test.go index 105779d..52f0d56 100644 --- a/test/pkg/source/postgrestest/postgres_test.go +++ b/test/pkg/source/postgrestest/postgres_test.go @@ -195,104 +195,25 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestBasicPGDumpAndRe // TestPGDumpRestic performs an integration test for brudi pgdump with restic func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreRestic() { - pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") - if !pgDumpAndRestoreTestSuite.resticExists { - return - } - ctx := context.Background() - - // remove backup files after test - defer func() { - // delete folder with backup file - removeErr := os.RemoveAll(backupPath) - if removeErr != nil { - log.WithError(removeErr).Error("failed to remove pgdump backup files") - } - }() - - // setup a container running the restic rest-server - resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) - pgDumpAndRestoreTestSuite.Require().NoError(err) - defer func() { - resticErr := resticContainer.Container.Terminate(ctx) - if resticErr != nil { - log.WithError(resticErr).Error("failed to terminate pgdump restic container") - } - }() - - // backup test data with brudi and retain test data for verification - var testData []testStruct - testData, err = pgDoBackup( - ctx, true, resticContainer, - "tar", backupPath, false, - ) - pgDumpAndRestoreTestSuite.Require().NoError(err) - - // restore test data with brudi and retrieve it from the db for verification - var restoreResult []testStruct - restoreResult, err = pgDoRestore( - ctx, true, resticContainer, - "tar", backupPath, false, - ) - pgDumpAndRestoreTestSuite.Require().NoError(err) - - assert.DeepEqual(pgDumpAndRestoreTestSuite.T(), testData, restoreResult) + pgDumpAndRestoreTestSuite.pgDumpAndRestoreRestic(backupPath, false) } // TestPGDumpResticGzip performs an integration test for brudi pgdump with restic and gzip func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreResticGzip() { - pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") - if !pgDumpAndRestoreTestSuite.resticExists { - return - } - ctx := context.Background() - - // remove backup files after test - defer func() { - // delete folder with backup file - removeErr := os.RemoveAll(backupPathZip) - if removeErr != nil { - log.WithError(removeErr).Error("failed to remove pgdump backup files") - } - }() - - // setup a container running the restic rest-server - resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) - pgDumpAndRestoreTestSuite.Require().NoError(err) - defer func() { - resticErr := resticContainer.Container.Terminate(ctx) - if resticErr != nil { - log.WithError(resticErr).Error("failed to terminate pgdump restic container") - } - }() - - // backup test data with brudi and retain test data for verification - var testData []testStruct - testData, err = pgDoBackup( - ctx, true, resticContainer, - "tar", backupPathZip, false, - ) - pgDumpAndRestoreTestSuite.Require().NoError(err) - - // restore test data with brudi and retrieve it from the db for verification - var restoreResult []testStruct - restoreResult, err = pgDoRestore( - ctx, true, resticContainer, - "tar", backupPathZip, false, - ) - pgDumpAndRestoreTestSuite.Require().NoError(err) - - assert.DeepEqual(pgDumpAndRestoreTestSuite.T(), testData, restoreResult) + pgDumpAndRestoreTestSuite.pgDumpAndRestoreRestic(backupPathZip, false) } // TestPGDumpResticStdin performs an integration test for brudi pgdump with restic over STDIN func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestoreResticStdin() { + pgDumpAndRestoreTestSuite.pgDumpAndRestoreRestic(backupPath, true) +} + +func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) pgDumpAndRestoreRestic(backupPath string, useStdin bool) { pgDumpAndRestoreTestSuite.True(pgDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") if !pgDumpAndRestoreTestSuite.resticExists { return } ctx := context.Background() - backupPath := path.Base(backupPath) // remove backup files after test defer func() { @@ -317,7 +238,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore var testData []testStruct testData, err = pgDoBackup( ctx, true, resticContainer, - "tar", backupPath, true, + "tar", backupPath, useStdin, ) pgDumpAndRestoreTestSuite.Require().NoError(err) @@ -325,7 +246,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) TestPGDumpAndRestore var restoreResult []testStruct restoreResult, err = pgDoRestore( ctx, true, resticContainer, - "tar", backupPath, true, + "tar", backupPath, useStdin, ) pgDumpAndRestoreTestSuite.Require().NoError(err) diff --git a/test/pkg/source/tar/tar_test.go b/test/pkg/source/tar/tar_test.go index cbe8ac6..ec3a8e2 100644 --- a/test/pkg/source/tar/tar_test.go +++ b/test/pkg/source/tar/tar_test.go @@ -126,7 +126,12 @@ func hashFile(filename string) (string, error) { // createTarConfig creates a brudi config for the tar commands func createTarConfig() []byte { + restoreTarget := "/tmp" + /*if doStdinBackup { + restoreTarget = path.Join(restoreTarget, targetPath) + }*/ return []byte(fmt.Sprintf( + //doPipingBackup: %t ` tar: options: @@ -144,9 +149,9 @@ tarrestore: extract: true gzip: true file: %s - target: "/tmp" + target: "%s" additionalArgs: [] hostName: autoGeneratedIfEmpty -`, targetPath, backupPath, targetPath, +`, targetPath, backupPath, targetPath, restoreTarget, )) } From 5a6bb8f833ebb9f8cbbc116f0576471bc2ff05f3 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:02 +0100 Subject: [PATCH 05/12] stdin-from-command backups - only available in restic:latest --- cmd/pgdump.go | 1 - pkg/cli/cli.go | 3 ++ pkg/cli/types.go | 1 + pkg/restic/client.go | 9 +++- pkg/restic/commands.go | 14 +++++- pkg/source/backup.go | 50 ++++++++++++-------- pkg/source/mongodump/backend_config_based.go | 16 +++++-- pkg/source/mysqldump/backend_config_based.go | 16 +++++-- pkg/source/pgdump/backend_config_based.go | 16 +++++-- pkg/source/psql/backend_config_based.go | 4 ++ pkg/source/redisdump/backend_config_based.go | 19 ++++++-- pkg/source/tar/backend_config_based.go | 16 +++++-- pkg/source/types.go | 2 + 13 files changed, 122 insertions(+), 45 deletions(-) diff --git a/cmd/pgdump.go b/cmd/pgdump.go index af8bb85..860a53f 100644 --- a/cmd/pgdump.go +++ b/cmd/pgdump.go @@ -2,7 +2,6 @@ package cmd import ( "context" - "github.com/mittwald/brudi/pkg/source/pgdump" "github.com/spf13/cobra" diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 3e59168..c378a6d 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -108,6 +108,9 @@ func StructToCLI(optionStruct interface{}) []string { if flag == "-" { continue } + if fieldVal == nil { + continue + } switch t := fieldVal.(type) { case int: diff --git a/pkg/cli/types.go b/pkg/cli/types.go index 37d2e90..3a553d9 100644 --- a/pkg/cli/types.go +++ b/pkg/cli/types.go @@ -3,6 +3,7 @@ package cli import "io" const GzipSuffix = ".gz" +const DoStdinBackupKey = "doPipingBackup" type CommandType struct { Binary string diff --git a/pkg/restic/client.go b/pkg/restic/client.go index 362c842..70b7124 100644 --- a/pkg/restic/client.go +++ b/pkg/restic/client.go @@ -3,6 +3,8 @@ package restic import ( "context" "fmt" + "github.com/mittwald/brudi/pkg/cli" + "github.com/spf13/viper" "github.com/pkg/errors" log "github.com/sirupsen/logrus" @@ -36,6 +38,9 @@ func NewResticClient(logger *log.Entry, hostname string, backupPaths ...string) if err != nil { return nil, errors.WithStack(err) } + if viper.GetBool(cli.DoStdinBackupKey) { + conf.Backup.Paths = nil + } if (conf.Backup.Flags.Host) == "" { conf.Backup.Flags.Host = hostname @@ -51,7 +56,7 @@ func NewResticClient(logger *log.Entry, hostname string, backupPaths ...string) }, nil } -func (c *Client) DoResticBackup(ctx context.Context) error { +func (c *Client) DoResticBackup(ctx context.Context, backupDataCmd *cli.CommandType) error { c.Logger.Info("running 'restic backup'") _, err := initBackup(ctx, c.Config.Global) @@ -64,7 +69,7 @@ func (c *Client) DoResticBackup(ctx context.Context) error { } var out []byte - _, out, err = CreateBackup(ctx, c.Config.Global, c.Config.Backup, true) + _, out, err = CreateBackup(ctx, c.Config.Global, c.Config.Backup, true, backupDataCmd) if err != nil { return errors.WithStack(fmt.Errorf("error while running restic backup: %s - %s", err.Error(), out)) } diff --git a/pkg/restic/commands.go b/pkg/restic/commands.go index 9c82f81..56c3428 100644 --- a/pkg/restic/commands.go +++ b/pkg/restic/commands.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "path" "strings" "time" @@ -80,7 +81,7 @@ func parseSnapshotOut(jsonLog []byte) (BackupResult, error) { } // CreateBackup executes "restic backup" and returns the parent snapshot id (if available) and the snapshot id -func CreateBackup(ctx context.Context, globalOpts *GlobalOptions, backupOpts *BackupOptions, unlock bool) (BackupResult, []byte, error) { +func CreateBackup(ctx context.Context, globalOpts *GlobalOptions, backupOpts *BackupOptions, unlock bool, backupDataCmd *cli.CommandType) (BackupResult, []byte, error) { var out []byte var err error @@ -99,6 +100,17 @@ func CreateBackup(ctx context.Context, globalOpts *GlobalOptions, backupOpts *Ba var args []string args = cli.StructToCLI(globalOpts) args = append(args, cli.StructToCLI(backupOpts)...) + if backupDataCmd != nil { + if backupOpts.Flags.StdinFilename == "" { + binName := path.Base(backupDataCmd.Binary) + if binName == "." || binName == "/" { + args = append(args, "--stdin-filename", "stdin-backup-file") + } else { + args = append(args, "--stdin-filename", fmt.Sprintf("%s-file", binName)) + } + } + args = append(args, "--stdin-from-command", strings.Join(cli.ParseCommandLine(*backupDataCmd), " ")) + } cmd := newCommand("backup", args...) diff --git a/pkg/source/backup.go b/pkg/source/backup.go index 70e20ad..72c46e0 100644 --- a/pkg/source/backup.go +++ b/pkg/source/backup.go @@ -3,6 +3,8 @@ package source import ( "context" "fmt" + "github.com/mittwald/brudi/pkg/cli" + "github.com/spf13/viper" "github.com/mittwald/brudi/pkg/restic" @@ -46,28 +48,34 @@ func DoBackupForKind(ctx context.Context, kind string, cleanup, useRestic, useRe return err } - err = backend.CreateBackup(ctx) - if err != nil { - return err - } + var backupCmd *cli.CommandType = nil + if viper.GetBool(cli.DoStdinBackupKey) { + bc := backend.GetBackupCommand() + backupCmd = &bc + } else { + err = backend.CreateBackup(ctx) + if err != nil { + return err + } - if cleanup { - defer func() { - cleanupLogger := logKind.WithFields( - log.Fields{ - "path": backend.GetBackupPath(), - "cmd": "cleanup", - }, - ) - if err = backend.CleanUp(); err != nil { - cleanupLogger.WithError(err).Warn("failed to cleanup backup") - } else { - cleanupLogger.Info("successfully cleaned up backup") - } - }() - } + if cleanup { + defer func() { + cleanupLogger := logKind.WithFields( + log.Fields{ + "path": backend.GetBackupPath(), + "cmd": "cleanup", + }, + ) + if err = backend.CleanUp(); err != nil { + cleanupLogger.WithError(err).Warn("failed to cleanup backup") + } else { + cleanupLogger.Info("successfully cleaned up backup") + } + }() + } - logKind.Info("finished backing up") + logKind.Info("finished backing up") + } if !useRestic { return nil @@ -87,7 +95,7 @@ func DoBackupForKind(ctx context.Context, kind string, cleanup, useRestic, useRe resticClient.Config.Forget.Flags.Prune = false } - if doBackupErr := resticClient.DoResticBackup(ctx); doBackupErr != nil { + if doBackupErr := resticClient.DoResticBackup(ctx, backupCmd); doBackupErr != nil { return doBackupErr } diff --git a/pkg/source/mongodump/backend_config_based.go b/pkg/source/mongodump/backend_config_based.go index 1e6bff0..439ff19 100644 --- a/pkg/source/mongodump/backend_config_based.go +++ b/pkg/source/mongodump/backend_config_based.go @@ -3,6 +3,7 @@ package mongodump import ( "context" "fmt" + "github.com/spf13/viper" "os" "github.com/pkg/errors" @@ -27,15 +28,15 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if err != nil { return nil, err } + if viper.GetBool(cli.DoStdinBackupKey) { + config.Options.Flags.Archive = "" + } return &ConfigBasedBackend{cfg: config}, nil } func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { - cmd := cli.CommandType{ - Binary: binary, - Args: cli.StructToCLI(b.cfg.Options), - } + cmd := b.GetBackupCommand() out, err := cli.Run(ctx, cmd) if err != nil { @@ -45,6 +46,13 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { return nil } +func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { + return cli.CommandType{ + Binary: binary, + Args: cli.StructToCLI(b.cfg.Options), + } +} + func (b *ConfigBasedBackend) GetBackupPath() string { if b.cfg.Options.Flags.Archive != "" { return b.cfg.Options.Flags.Archive diff --git a/pkg/source/mysqldump/backend_config_based.go b/pkg/source/mysqldump/backend_config_based.go index 8edb214..448fbb2 100644 --- a/pkg/source/mysqldump/backend_config_based.go +++ b/pkg/source/mysqldump/backend_config_based.go @@ -3,6 +3,7 @@ package mysqldump import ( "context" "fmt" + "github.com/spf13/viper" "os" "strings" @@ -27,6 +28,9 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if err != nil { return nil, err } + if viper.GetBool(cli.DoStdinBackupKey) { + config.Options.Flags.ResultFile = "" + } return &ConfigBasedBackend{cfg: config}, nil } @@ -39,10 +43,7 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { gzip = true } - cmd := cli.CommandType{ - Binary: binary, - Args: cli.StructToCLI(b.cfg.Options), - } + cmd := b.GetBackupCommand() out, err := cli.Run(ctx, cmd) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) @@ -59,6 +60,13 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { return nil } +func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { + return cli.CommandType{ + Binary: binary, + Args: cli.StructToCLI(b.cfg.Options), + } +} + func (b *ConfigBasedBackend) GetBackupPath() string { return b.cfg.Options.Flags.ResultFile } diff --git a/pkg/source/pgdump/backend_config_based.go b/pkg/source/pgdump/backend_config_based.go index f5c4d02..bb6d838 100644 --- a/pkg/source/pgdump/backend_config_based.go +++ b/pkg/source/pgdump/backend_config_based.go @@ -3,6 +3,7 @@ package pgdump import ( "context" "fmt" + "github.com/spf13/viper" "os" "strings" @@ -27,6 +28,9 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if err != nil { return nil, err } + if viper.GetBool(cli.DoStdinBackupKey) { + config.Options.Flags.File = "" + } return &ConfigBasedBackend{cfg: config}, nil } @@ -38,10 +42,7 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { b.cfg.Options.Flags.File = strings.TrimSuffix(b.cfg.Options.Flags.File, cli.GzipSuffix) gzip = true } - cmd := cli.CommandType{ - Binary: binary, - Args: cli.StructToCLI(b.cfg.Options), - } + cmd := b.GetBackupCommand() out, err := cli.Run(ctx, cmd) if err != nil { @@ -59,6 +60,13 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { return nil } +func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { + return cli.CommandType{ + Binary: binary, + Args: cli.StructToCLI(b.cfg.Options), + } +} + func (b *ConfigBasedBackend) GetBackupPath() string { return b.cfg.Options.Flags.File } diff --git a/pkg/source/psql/backend_config_based.go b/pkg/source/psql/backend_config_based.go index 5a7e9af..f698d17 100644 --- a/pkg/source/psql/backend_config_based.go +++ b/pkg/source/psql/backend_config_based.go @@ -3,6 +3,7 @@ package psql import ( "context" "fmt" + "github.com/spf13/viper" "os" "github.com/mittwald/brudi/pkg/cli" @@ -27,6 +28,9 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if err != nil { return nil, err } + if viper.GetBool(cli.DoStdinBackupKey) { + config.Options.Flags.Output = "" + } return &ConfigBasedBackend{cfg: config}, nil } diff --git a/pkg/source/redisdump/backend_config_based.go b/pkg/source/redisdump/backend_config_based.go index dfcf040..184f20b 100644 --- a/pkg/source/redisdump/backend_config_based.go +++ b/pkg/source/redisdump/backend_config_based.go @@ -3,6 +3,7 @@ package redisdump import ( "context" "fmt" + "github.com/spf13/viper" "os" "strings" @@ -16,6 +17,12 @@ type ConfigBasedBackend struct { } func NewConfigBasedBackend() (*ConfigBasedBackend, error) { + if viper.GetBool(cli.DoStdinBackupKey) { + //config.Options.Flags.Pipe = true + // TODO: Is this error correct? + return nil, errors.Errorf("can't do a backup to STDOUT with redisdump but %s is set", cli.DoStdinBackupKey) + } + config := &Config{ &Options{ Flags: &Flags{}, @@ -40,10 +47,7 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { b.cfg.Options.Flags.Rdb = strings.TrimSuffix(b.cfg.Options.Flags.Rdb, cli.GzipSuffix) gzip = true } - cmd := cli.CommandType{ - Binary: binary, - Args: cli.StructToCLI(b.cfg.Options), - } + cmd := b.GetBackupCommand() out, err := cli.Run(ctx, cmd) if err != nil { @@ -61,6 +65,13 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { return nil } +func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { + return cli.CommandType{ + Binary: binary, + Args: cli.StructToCLI(b.cfg.Options), + } +} + func (b *ConfigBasedBackend) GetBackupPath() string { return b.cfg.Options.Flags.Rdb } diff --git a/pkg/source/tar/backend_config_based.go b/pkg/source/tar/backend_config_based.go index 62f740c..16ac883 100644 --- a/pkg/source/tar/backend_config_based.go +++ b/pkg/source/tar/backend_config_based.go @@ -3,6 +3,7 @@ package tar import ( "context" "fmt" + "github.com/spf13/viper" "os" "github.com/pkg/errors" @@ -27,15 +28,15 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if err != nil { return nil, err } + if viper.GetBool(cli.DoStdinBackupKey) { + config.Options.Flags.File = "-" + } return &ConfigBasedBackend{cfg: config}, nil } func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { - cmd := cli.CommandType{ - Binary: binary, - Args: cli.StructToCLI(b.cfg.Options), - } + cmd := b.GetBackupCommand() out, err := cli.Run(ctx, cmd) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) @@ -44,6 +45,13 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { return nil } +func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { + return cli.CommandType{ + Binary: binary, + Args: cli.StructToCLI(b.cfg.Options), + } +} + func (b *ConfigBasedBackend) GetBackupPath() string { return b.cfg.Options.Flags.File } diff --git a/pkg/source/types.go b/pkg/source/types.go index c8d2377..75f5b1f 100644 --- a/pkg/source/types.go +++ b/pkg/source/types.go @@ -2,10 +2,12 @@ package source import ( "context" + "github.com/mittwald/brudi/pkg/cli" ) type Generic interface { CreateBackup(ctx context.Context) error + GetBackupCommand() cli.CommandType GetBackupPath() string GetHostname() string CleanUp() error From 2e6705577b49a73aff2a245c8a545daf3ac9732b Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:04 +0100 Subject: [PATCH 06/12] additional config check for pgdump --- pkg/source/pgdump/backend_config_based.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/source/pgdump/backend_config_based.go b/pkg/source/pgdump/backend_config_based.go index bb6d838..9093a8b 100644 --- a/pkg/source/pgdump/backend_config_based.go +++ b/pkg/source/pgdump/backend_config_based.go @@ -29,6 +29,10 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { return nil, err } if viper.GetBool(cli.DoStdinBackupKey) { + if strings.TrimSpace(strings.ToLower(config.Options.Flags.Format)) == "directory" { + return nil, errors.New("the output format of pgdump is set to directory but doPipingBackup " + + "is also active. Use 'plain', 'custom' or 'tar' instead") + } config.Options.Flags.File = "" } From ac2a72b33dab587eca1c83960a3604d80c715d5a Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:07 +0100 Subject: [PATCH 07/12] combined stdin backup with pipe and --stdin-command --- pkg/cli/cli.go | 7 +++- pkg/cli/types.go | 14 ++++--- pkg/restic/commands.go | 41 +++++++++++-------- pkg/source/backup.go | 19 +++++---- pkg/source/mongodump/backend_config_based.go | 28 +++++++++++-- .../mongorestore/backend_config_based.go | 4 +- pkg/source/mysqldump/backend_config_based.go | 32 ++++++++++++--- .../mysqlrestore/backend_config_based.go | 4 +- pkg/source/pgdump/backend_config_based.go | 30 +++++++++++--- pkg/source/pgrestore/backend_config_based.go | 4 +- pkg/source/psql/backend_config_based.go | 4 +- pkg/source/redisdump/backend_config_based.go | 12 +++--- pkg/source/tar/backend_config_based.go | 29 +++++++++++-- pkg/source/tarrestore/backend_config_based.go | 4 +- pkg/source/types.go | 2 +- 15 files changed, 173 insertions(+), 61 deletions(-) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index c378a6d..d726773 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -210,7 +210,6 @@ func Run(ctx context.Context, cmd *CommandType, outputToPipe bool) ([]byte, erro var err, pipeErr error commandLine := ParseCommandLine(*cmd) log.WithField("command", strings.Join(commandLine, " ")).Debug("executing command") - // TODO: Assure that --stdin and --stdin-filename are set if the backup from STDIN is enabled if ctx == nil { ctx = context.Background() } @@ -218,16 +217,20 @@ func Run(ctx context.Context, cmd *CommandType, outputToPipe bool) ([]byte, erro defer cancelFunc() cmdExec = exec.CommandContext(cCtx, commandLine[0], commandLine[1:]...) //nolint: gosec if outputToPipe { + cmd.PipeReady.L.Lock() cmd.Pipe, err = cmdExec.StdoutPipe() + cmd.PipeReady.L.Unlock() if err != nil { + defer cmd.PipeReady.Broadcast() return nil, errors.Wrapf(err, "error while getting STDOUT pipe for command: %s", strings.Join(commandLine, " ")) } cmd.ReadingDone = make(chan bool, 1) + cmd.PipeReady.Broadcast() } else { cmdExec.Stdout = &outBuffer } cmdExec.Stderr = &outBuffer - if !outputToPipe && cmd.Pipe != nil { + if cmd.Pipe != nil { stdin, err = cmdExec.StdinPipe() if err != nil { return nil, errors.Wrapf(err, "error while getting STDIN pipe for command: %s", strings.Join(commandLine, " ")) diff --git a/pkg/cli/types.go b/pkg/cli/types.go index 3a553d9..ceeda69 100644 --- a/pkg/cli/types.go +++ b/pkg/cli/types.go @@ -1,6 +1,9 @@ package cli -import "io" +import ( + "io" + "sync" +) const GzipSuffix = ".gz" const DoStdinBackupKey = "doPipingBackup" @@ -9,10 +12,11 @@ type CommandType struct { Binary string Command string Args []string - Pipe io.Reader - ReadingDone chan bool - Nice *int // https://linux.die.net/man/1/nice - IONice *int // https://linux.die.net/man/1/ionice + Pipe io.Reader // TODO: Remove when --stdin-command was added to restic + PipeReady *sync.Cond // TODO: Remove when --stdin-command was added to restic + ReadingDone chan bool // TODO: Remove when --stdin-command was added to restic + Nice *int // https://linux.die.net/man/1/nice + IONice *int // https://linux.die.net/man/1/ionice } type PipedCommandsPids struct { diff --git a/pkg/restic/commands.go b/pkg/restic/commands.go index 56c3428..32417c6 100644 --- a/pkg/restic/commands.go +++ b/pkg/restic/commands.go @@ -32,7 +32,7 @@ var ( func initBackup(ctx context.Context, globalOpts *GlobalOptions) ([]byte, error) { cmd := newCommand("init", cli.StructToCLI(globalOpts)...) - out, err := cli.RunWithTimeout(ctx, cmd, cmdTimeout) + out, err := cli.RunWithTimeout(ctx, &cmd, false, cmdTimeout) if err != nil { // s3 init-check if strings.Contains(string(out), "config already initialized") { @@ -99,22 +99,29 @@ func CreateBackup(ctx context.Context, globalOpts *GlobalOptions, backupOpts *Ba var args []string args = cli.StructToCLI(globalOpts) - args = append(args, cli.StructToCLI(backupOpts)...) if backupDataCmd != nil { + backupOpts.Paths = nil if backupOpts.Flags.StdinFilename == "" { binName := path.Base(backupDataCmd.Binary) if binName == "." || binName == "/" { - args = append(args, "--stdin-filename", "stdin-backup-file") + backupOpts.Flags.StdinFilename = "stdin-backup-file" } else { - args = append(args, "--stdin-filename", fmt.Sprintf("%s-file", binName)) + backupOpts.Flags.StdinFilename = fmt.Sprintf("%s-file", binName) } } - args = append(args, "--stdin-from-command", strings.Join(cli.ParseCommandLine(*backupDataCmd), " ")) + // TODO: Change back when --stdin-command was added to restic + // args = append(args, "--stdin-from-command", strings.Join(cli.ParseCommandLine(*backupDataCmd), " ")) + backupOpts.Flags.Stdin = true } + args = append(args, cli.StructToCLI(backupOpts)...) cmd := newCommand("backup", args...) + if backupDataCmd != nil { + cmd.Pipe = backupDataCmd.Pipe + cmd.ReadingDone = backupDataCmd.ReadingDone + } - out, err = cli.RunWithTimeout(ctx, cmd, cmdTimeout) + out, err = cli.RunWithTimeout(ctx, &cmd, false, cmdTimeout) if err != nil { return BackupResult{}, out, err } @@ -153,7 +160,7 @@ func Ls(ctx context.Context, glob *GlobalOptions, opts *LsOptions) ([]LsResult, args = append(args, cli.StructToCLI(opts)...) cmd := newCommand("ls", args...) - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return nil, err } @@ -238,7 +245,7 @@ func GetSnapshotSize(ctx context.Context, snapshotIDs []string) (size uint64) { } cmd := newCommand("stats", cli.StructToCLI(&opts)...) - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return } @@ -279,7 +286,7 @@ func GetSnapshotSizeByPath(ctx context.Context, snapshotID, path string) (size u func ListSnapshots(ctx context.Context, opts *SnapshotOptions) ([]Snapshot, error) { cmd := newCommand("snapshots", cli.StructToCLI(&opts)...) - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return nil, err } @@ -294,7 +301,7 @@ func ListSnapshots(ctx context.Context, opts *SnapshotOptions) ([]Snapshot, erro // Find executes "restic find" func Find(ctx context.Context, opts *FindOptions) ([]FindResult, error) { cmd := newCommand("find", cli.StructToCLI(&opts)...) - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return nil, err } @@ -310,7 +317,7 @@ func Find(ctx context.Context, opts *FindOptions) ([]FindResult, error) { // Check executes "restic check" func Check(ctx context.Context, flags *CheckFlags) ([]byte, error) { cmd := newCommand("check", cli.StructToCLI(flags)...) - return cli.Run(ctx, cmd) + return cli.Run(ctx, &cmd, false) } // Forget executes "restic forget" @@ -332,7 +339,7 @@ func Forget( Args: args, } - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return nil, out, err } @@ -361,7 +368,7 @@ func Forget( func Prune(ctx context.Context, globalOpts *GlobalOptions) ([]byte, error) { cmd := newCommand("prune", cli.StructToCLI(globalOpts)...) - return cli.Run(ctx, cmd) + return cli.Run(ctx, &cmd, false) } // RebuildIndex executes "restic rebuild-index" @@ -375,7 +382,7 @@ func RebuildIndex(ctx context.Context) ([]byte, error) { Nice: &nice, IONice: &ionice, } - return cli.Run(ctx, cmd) + return cli.Run(ctx, &cmd, false) } // RestoreBackup executes "restic restore" @@ -397,7 +404,7 @@ func RestoreBackup(ctx context.Context, glob *GlobalOptions, opts *RestoreOption cmd := newCommand("restore", args...) - return cli.Run(ctx, cmd) + return cli.Run(ctx, &cmd, false) } // Unlock executes "restic unlock" @@ -407,12 +414,12 @@ func Unlock(ctx context.Context, globalOpts *GlobalOptions, unlockOpts *UnlockOp args = append(args, cli.StructToCLI(unlockOpts)...) cmd := newCommand("unlock", args...) - return cli.Run(ctx, cmd) + return cli.Run(ctx, &cmd, false) } // Tag executes "restic tag" func Tag(ctx context.Context, opts *TagOptions) ([]byte, error) { cmd := newCommand("tag", cli.StructToCLI(opts)...) - return cli.Run(ctx, cmd) + return cli.Run(ctx, &cmd, false) } diff --git a/pkg/source/backup.go b/pkg/source/backup.go index 72c46e0..4893713 100644 --- a/pkg/source/backup.go +++ b/pkg/source/backup.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "github.com/mittwald/brudi/pkg/cli" + "github.com/pkg/errors" "github.com/spf13/viper" "github.com/mittwald/brudi/pkg/restic" @@ -37,6 +38,9 @@ func getGenericBackendForKind(kind string) (Generic, error) { } func DoBackupForKind(ctx context.Context, kind string, cleanup, useRestic, useResticForget, useResticPrune bool) error { + if viper.GetBool(cli.DoStdinBackupKey) && !useRestic { + return errors.New("doStdinBackup is enabled but restic is disabled") + } logKind := log.WithFields( log.Fields{ "kind": kind, @@ -48,16 +52,18 @@ func DoBackupForKind(ctx context.Context, kind string, cleanup, useRestic, useRe return err } - var backupCmd *cli.CommandType = nil + // TODO: Re-activate when --stdin-command was added to restic + /*var backupCmd *cli.CommandType = nil if viper.GetBool(cli.DoStdinBackupKey) { bc := backend.GetBackupCommand() backupCmd = &bc - } else { - err = backend.CreateBackup(ctx) - if err != nil { - return err - } + } else {*/ + backupCmd, err := backend.CreateBackup(ctx) + if err != nil { + return err + } + if !viper.GetBool(cli.DoStdinBackupKey) { if cleanup { defer func() { cleanupLogger := logKind.WithFields( @@ -73,7 +79,6 @@ func DoBackupForKind(ctx context.Context, kind string, cleanup, useRestic, useRe } }() } - logKind.Info("finished backing up") } diff --git a/pkg/source/mongodump/backend_config_based.go b/pkg/source/mongodump/backend_config_based.go index 439ff19..223759c 100644 --- a/pkg/source/mongodump/backend_config_based.go +++ b/pkg/source/mongodump/backend_config_based.go @@ -3,8 +3,10 @@ package mongodump import ( "context" "fmt" + log "github.com/sirupsen/logrus" "github.com/spf13/viper" "os" + "sync" "github.com/pkg/errors" @@ -12,6 +14,8 @@ import ( "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.Generic = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -35,15 +39,31 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { return &ConfigBasedBackend{cfg: config}, nil } -func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { +func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType, error) { cmd := b.GetBackupCommand() - out, err := cli.Run(ctx, cmd) + var out []byte + var err error = nil + if viper.GetBool(cli.DoStdinBackupKey) { + cmd.PipeReady = &sync.Cond{L: &sync.Mutex{}} + go func() { + _, err = cli.Run(ctx, &cmd, true) + if err != nil { + log.Errorf("error while running backup program: %v", err) + } + }() + cmd.PipeReady.L.Lock() + cmd.PipeReady.Wait() + cmd.PipeReady.L.Unlock() + return &cmd, err + } else { + out, err = cli.Run(ctx, &cmd, false) + } if err != nil { - return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) + return nil, errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } - return nil + return nil, nil } func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { diff --git a/pkg/source/mongorestore/backend_config_based.go b/pkg/source/mongorestore/backend_config_based.go index 8cee522..47bf3a3 100644 --- a/pkg/source/mongorestore/backend_config_based.go +++ b/pkg/source/mongorestore/backend_config_based.go @@ -11,6 +11,8 @@ import ( "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.GenericRestore = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -36,7 +38,7 @@ func (b *ConfigBasedBackend) RestoreBackup(ctx context.Context) error { Binary: binary, Args: cli.StructToCLI(b.cfg.Options), } - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } diff --git a/pkg/source/mysqldump/backend_config_based.go b/pkg/source/mysqldump/backend_config_based.go index 448fbb2..9418d40 100644 --- a/pkg/source/mysqldump/backend_config_based.go +++ b/pkg/source/mysqldump/backend_config_based.go @@ -3,15 +3,19 @@ package mysqldump import ( "context" "fmt" + log "github.com/sirupsen/logrus" "github.com/spf13/viper" "os" "strings" + "sync" "github.com/pkg/errors" "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.Generic = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -35,29 +39,45 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { return &ConfigBasedBackend{cfg: config}, nil } -func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { +func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType, error) { gzip := false // create temporary, unzipped backup first, thus trim '.gz' extension if strings.HasSuffix(b.cfg.Options.Flags.ResultFile, cli.GzipSuffix) { b.cfg.Options.Flags.ResultFile = strings.TrimSuffix(b.cfg.Options.Flags.ResultFile, cli.GzipSuffix) gzip = true } - cmd := b.GetBackupCommand() - out, err := cli.Run(ctx, cmd) + + var out []byte + var err error = nil + if viper.GetBool(cli.DoStdinBackupKey) { + cmd.PipeReady = &sync.Cond{L: &sync.Mutex{}} + go func() { + _, err = cli.Run(ctx, &cmd, true) + if err != nil { + log.Errorf("error while running backup program: %v", err) + } + }() + cmd.PipeReady.L.Lock() + cmd.PipeReady.Wait() + cmd.PipeReady.L.Unlock() + return &cmd, err + } else { + out, err = cli.Run(ctx, &cmd, false) + } if err != nil { - return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) + return nil, errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } // zip backup, update flag with the name returned by GzipFile for correct handover to restic if gzip { b.cfg.Options.Flags.ResultFile, err = cli.GzipFile(b.cfg.Options.Flags.ResultFile) if err != nil { - return err + return nil, err } } - return nil + return nil, nil } func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { diff --git a/pkg/source/mysqlrestore/backend_config_based.go b/pkg/source/mysqlrestore/backend_config_based.go index 16befde..936889a 100644 --- a/pkg/source/mysqlrestore/backend_config_based.go +++ b/pkg/source/mysqlrestore/backend_config_based.go @@ -10,6 +10,8 @@ import ( "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.GenericRestore = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -44,7 +46,7 @@ func (b *ConfigBasedBackend) RestoreBackup(ctx context.Context) error { Args: args, } var out []byte - out, err = cli.Run(ctx, cmd) + out, err = cli.Run(ctx, &cmd, false) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } diff --git a/pkg/source/pgdump/backend_config_based.go b/pkg/source/pgdump/backend_config_based.go index 9093a8b..bb152c6 100644 --- a/pkg/source/pgdump/backend_config_based.go +++ b/pkg/source/pgdump/backend_config_based.go @@ -6,12 +6,16 @@ import ( "github.com/spf13/viper" "os" "strings" + "sync" "github.com/pkg/errors" + log "github.com/sirupsen/logrus" "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.Generic = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -39,7 +43,7 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { return &ConfigBasedBackend{cfg: config}, nil } -func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { +func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType, error) { gzip := false // create temporary, unzipped backup first, thus trim '.gz' extension if strings.HasSuffix(b.cfg.Options.Flags.File, cli.GzipSuffix) { @@ -48,20 +52,36 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { } cmd := b.GetBackupCommand() - out, err := cli.Run(ctx, cmd) + var out []byte + var err error = nil + if viper.GetBool(cli.DoStdinBackupKey) { + cmd.PipeReady = &sync.Cond{L: &sync.Mutex{}} + go func() { + _, err = cli.Run(ctx, &cmd, true) + if err != nil { + log.Errorf("error while running backup program: %v", err) + } + }() + cmd.PipeReady.L.Lock() + cmd.PipeReady.Wait() + cmd.PipeReady.L.Unlock() + return &cmd, err + } else { + out, err = cli.Run(ctx, &cmd, false) + } if err != nil { - return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) + return nil, errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } // zip backup, update flag with the name returned by GzipFile for correct handover to restic if gzip { b.cfg.Options.Flags.File, err = cli.GzipFile(b.cfg.Options.Flags.File) if err != nil { - return err + return nil, err } } - return nil + return nil, nil } func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { diff --git a/pkg/source/pgrestore/backend_config_based.go b/pkg/source/pgrestore/backend_config_based.go index 96445b4..183b5f4 100644 --- a/pkg/source/pgrestore/backend_config_based.go +++ b/pkg/source/pgrestore/backend_config_based.go @@ -10,6 +10,8 @@ import ( "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.GenericRestore = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -44,7 +46,7 @@ func (b *ConfigBasedBackend) RestoreBackup(ctx context.Context) error { Args: args, } var out []byte - out, err = cli.Run(ctx, cmd) + out, err = cli.Run(ctx, &cmd, false) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } diff --git a/pkg/source/psql/backend_config_based.go b/pkg/source/psql/backend_config_based.go index f698d17..4b7493f 100644 --- a/pkg/source/psql/backend_config_based.go +++ b/pkg/source/psql/backend_config_based.go @@ -11,6 +11,8 @@ import ( "github.com/pkg/errors" ) +//var _ source.GenericRestore = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -49,7 +51,7 @@ func (b *ConfigBasedBackend) RestoreBackup(ctx context.Context) error { Args: args, } var out []byte - out, err = cli.Run(ctx, cmd) + out, err = cli.Run(ctx, &cmd, false) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } diff --git a/pkg/source/redisdump/backend_config_based.go b/pkg/source/redisdump/backend_config_based.go index 184f20b..7e8ba1d 100644 --- a/pkg/source/redisdump/backend_config_based.go +++ b/pkg/source/redisdump/backend_config_based.go @@ -12,6 +12,8 @@ import ( "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.Generic = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -40,7 +42,7 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { } // Do a bgsave of the given redis instance -func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { +func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType, error) { gzip := false // create temporary, unzipped backup first, thus trim '.gz' extension if strings.HasSuffix(b.cfg.Options.Flags.Rdb, cli.GzipSuffix) { @@ -49,20 +51,20 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { } cmd := b.GetBackupCommand() - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { - return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) + return nil, errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } // zip backup, update flag with the name returned by GzipFile for correct handover to restic if gzip { b.cfg.Options.Flags.Rdb, err = cli.GzipFile(b.cfg.Options.Flags.Rdb) if err != nil { - return err + return nil, err } } - return nil + return nil, nil } func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { diff --git a/pkg/source/tar/backend_config_based.go b/pkg/source/tar/backend_config_based.go index 16ac883..2826ef4 100644 --- a/pkg/source/tar/backend_config_based.go +++ b/pkg/source/tar/backend_config_based.go @@ -3,14 +3,18 @@ package tar import ( "context" "fmt" + log "github.com/sirupsen/logrus" "github.com/spf13/viper" "os" + "sync" "github.com/pkg/errors" "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.Generic = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -35,14 +39,31 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { return &ConfigBasedBackend{cfg: config}, nil } -func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) error { +func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType, error) { cmd := b.GetBackupCommand() - out, err := cli.Run(ctx, cmd) + + var out []byte + var err error = nil + if viper.GetBool(cli.DoStdinBackupKey) { + cmd.PipeReady = &sync.Cond{L: &sync.Mutex{}} + go func() { + _, err = cli.Run(ctx, &cmd, true) + if err != nil { + log.Errorf("error while running backup program: %v", err) + } + }() + cmd.PipeReady.L.Lock() + cmd.PipeReady.Wait() + cmd.PipeReady.L.Unlock() + return &cmd, err + } else { + out, err = cli.Run(ctx, &cmd, false) + } if err != nil { - return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) + return nil, errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } - return nil + return nil, nil } func (b *ConfigBasedBackend) GetBackupCommand() cli.CommandType { diff --git a/pkg/source/tarrestore/backend_config_based.go b/pkg/source/tarrestore/backend_config_based.go index 3aa5d02..aa3e5ba 100644 --- a/pkg/source/tarrestore/backend_config_based.go +++ b/pkg/source/tarrestore/backend_config_based.go @@ -10,6 +10,8 @@ import ( "github.com/mittwald/brudi/pkg/cli" ) +//var _ source.GenericRestore = &ConfigBasedBackend{} + type ConfigBasedBackend struct { cfg *Config } @@ -36,7 +38,7 @@ func (b *ConfigBasedBackend) RestoreBackup(ctx context.Context) error { Binary: binary, Args: cli.StructToCLI(b.cfg.Options), } - out, err := cli.Run(ctx, cmd) + out, err := cli.Run(ctx, &cmd, false) if err != nil { return errors.WithStack(fmt.Errorf("%+v - %s", err, out)) } diff --git a/pkg/source/types.go b/pkg/source/types.go index 75f5b1f..1b61176 100644 --- a/pkg/source/types.go +++ b/pkg/source/types.go @@ -6,7 +6,7 @@ import ( ) type Generic interface { - CreateBackup(ctx context.Context) error + CreateBackup(ctx context.Context) (*cli.CommandType, error) // TODO: Remove *cli.CommandType when --stdin-command was added to restic GetBackupCommand() cli.CommandType GetBackupPath() string GetHostname() string From 448452b8eaed066b739d557f191409edb76110f7 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:08 +0100 Subject: [PATCH 08/12] fixed all tests and the MongoDB stdin backup config --- pkg/source/mongodump/backend_config_based.go | 15 ++++- test/pkg/cli/cli_test.go | 2 +- test/pkg/source/mongodbtest/mongodb_test.go | 36 ++++++----- test/pkg/source/mysqltest/mysql_test.go | 64 +++++++++++++------ test/pkg/source/postgrestest/postgres_test.go | 24 +++---- test/pkg/source/redisdump/redisdump_test.go | 8 ++- test/pkg/source/tar/tar_test.go | 13 ++-- 7 files changed, 104 insertions(+), 58 deletions(-) diff --git a/pkg/source/mongodump/backend_config_based.go b/pkg/source/mongodump/backend_config_based.go index 223759c..78c6c3c 100644 --- a/pkg/source/mongodump/backend_config_based.go +++ b/pkg/source/mongodump/backend_config_based.go @@ -17,7 +17,8 @@ import ( //var _ source.Generic = &ConfigBasedBackend{} type ConfigBasedBackend struct { - cfg *Config + cfg *Config + outputAsArchive bool } func NewConfigBasedBackend() (*ConfigBasedBackend, error) { @@ -32,11 +33,18 @@ func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if err != nil { return nil, err } + outputAsArchive := false if viper.GetBool(cli.DoStdinBackupKey) { + if config.Options.Flags.Archive != "" { + outputAsArchive = true + } config.Options.Flags.Archive = "" + if config.Options.Flags.Out != "" { + config.Options.Flags.Out = "-" + } } - return &ConfigBasedBackend{cfg: config}, nil + return &ConfigBasedBackend{cfg: config, outputAsArchive: outputAsArchive}, nil } func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType, error) { @@ -46,6 +54,9 @@ func (b *ConfigBasedBackend) CreateBackup(ctx context.Context) (*cli.CommandType var err error = nil if viper.GetBool(cli.DoStdinBackupKey) { cmd.PipeReady = &sync.Cond{L: &sync.Mutex{}} + if b.outputAsArchive { + cmd.Args = append(cmd.Args, "--archive") + } go func() { _, err = cli.Run(ctx, &cmd, true) if err != nil { diff --git a/test/pkg/cli/cli_test.go b/test/pkg/cli/cli_test.go index 80bb5e8..7bb448e 100644 --- a/test/pkg/cli/cli_test.go +++ b/test/pkg/cli/cli_test.go @@ -93,7 +93,7 @@ func (cliTestSuite *CliTestSuite) TestGzipFile() { Binary: binary, Args: []string{"-t", fileName}, } - _, err = cli.Run(context.TODO(), cmd) + _, err = cli.Run(context.TODO(), &cmd, false) cliTestSuite.Require().NoError(err) } diff --git a/test/pkg/source/mongodbtest/mongodb_test.go b/test/pkg/source/mongodbtest/mongodb_test.go index b50db98..69be977 100644 --- a/test/pkg/source/mongodbtest/mongodb_test.go +++ b/test/pkg/source/mongodbtest/mongodb_test.go @@ -5,7 +5,6 @@ import ( "context" "fmt" "os" - "path" "testing" "github.com/mittwald/brudi/pkg/source" @@ -82,6 +81,16 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongo assert.DeepEqual(mongoDumpAndRestoreTestSuite.T(), testData, results) } +// TestBasicMongoDBDumpRestic performs an integration test for the `mongodump` command with restic support +func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreRestic() { + mongoDumpAndRestoreTestSuite.basicMongoDBDumpAndRestoreRestic(backupPath, false) +} + +// TestBasicMongoDBDumpResticStdin performs an integration test for the `mongodump` command with restic support using STDIN +func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreResticStdin() { + mongoDumpAndRestoreTestSuite.basicMongoDBDumpAndRestoreRestic(backupPath, true) +} + func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) basicMongoDBDumpAndRestoreRestic(backupPath string, useStdin bool) { mongoDumpAndRestoreTestSuite.True(mongoDumpAndRestoreTestSuite.resticExists, "can't use restic on this machine") if !mongoDumpAndRestoreTestSuite.resticExists { @@ -114,20 +123,11 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) basicMongoDBDu // restore database from backup and pull test data for verification var results []interface{} results, err = mongoDoRestore(ctx, true, resticContainer) + mongoDumpAndRestoreTestSuite.Require().NoError(err) assert.DeepEqual(mongoDumpAndRestoreTestSuite.T(), testData, results) } -// TestBasicMongoDBDumpRestic performs an integration test for the `mongodump` command with restic support -func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreRestic() { - mongoDumpAndRestoreTestSuite.basicMongoDBDumpAndRestoreRestic(backupPath, false) -} - -// TestBasicMongoDBDumpResticStdin performs an integration test for the `mongodump` command with restic support using STDIN -func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) TestBasicMongoDBDumpAndRestoreResticStdin() { - mongoDumpAndRestoreTestSuite.basicMongoDBDumpAndRestoreRestic(backupPath, true) -} - func TestMongoDumpAndRestoreTestSuite(t *testing.T) { _, resticExists := commons.CheckProgramsAndRestic(t, "mongodump", "", "mongorestore", "") testSuite := &MongoDumpAndRestoreTestSuite{ @@ -313,9 +313,13 @@ func createMongoConfig(container commons.TestContainerSetup, useRestic bool, res `, kind, container.Address, container.Port, mongoUser, mongoPW, backupPath, )) } - restoreTarget := "/" + //restoreTarget := "/" + stdinFilename := "" if doStdinBackup { - restoreTarget = path.Join(restoreTarget, backupPath) + stdinFilename = fmt.Sprintf(" backup:\n flags:\n stdinFilename: %s\n", + backupPath) + //restoreTarget = path.Join(restoreTarget, backupPath) + } return []byte(fmt.Sprintf( ` @@ -334,7 +338,7 @@ func createMongoConfig(container commons.TestContainerSetup, useRestic bool, res global: flags: repo: rest:http://%s:%s/ - forget: +%s forget: flags: keepLast: 1 keepHourly: 0 @@ -344,9 +348,9 @@ func createMongoConfig(container commons.TestContainerSetup, useRestic bool, res keepYearly: 0 restore: flags: - target: "%s" + target: "/" id: "latest" -`, doStdinBackup, kind, container.Address, container.Port, mongoUser, mongoPW, backupPath, resticIP, resticPort, restoreTarget, +`, doStdinBackup, kind, container.Address, container.Port, mongoUser, mongoPW, backupPath, resticIP, resticPort, stdinFilename, )) } diff --git a/test/pkg/source/mysqltest/mysql_test.go b/test/pkg/source/mysqltest/mysql_test.go index 412ed15..35d7e9c 100644 --- a/test/pkg/source/mysqltest/mysql_test.go +++ b/test/pkg/source/mysqltest/mysql_test.go @@ -5,8 +5,8 @@ import ( "context" "database/sql" "fmt" + "github.com/pkg/errors" "os" - "path" "testing" "time" @@ -222,8 +222,10 @@ func mySQLDoBackup( log.WithError(dbErr).Error("failed to close connection to mysql backup database") } }() - // sleep to give mysql server time to get ready - time.Sleep(1 * time.Second) + err = waitForDb(db) + if err != nil { + return []TestStruct{}, err + } // create table for test data _, err = db.Exec(fmt.Sprintf("CREATE TABLE %s(id INT NOT NULL AUTO_INCREMENT, name VARCHAR(100) NOT NULL, PRIMARY KEY ( id ));", tableName)) @@ -276,16 +278,7 @@ func mySQLDoRestore( return []TestStruct{}, err } - // sleep to give mysql time to get ready - time.Sleep(1 * time.Second) - - // restore server from mysqldump - err = source.DoBackupForKind(ctx, dumpKind, false, useRestic, false, false) - if err != nil { - return []TestStruct{}, err - } - - // establish connection for retrieving restored data + // establish connection to be able to wait for the DB and retrieving restored data restoreConnectionString := fmt.Sprintf( "%s:%s@tcp(%s:%s)/%s?tls=skip-verify", mySQLRoot, mySQLRootPW, mySQLRestoreTarget.Address, mySQLRestoreTarget.Port, mySQLDatabase, @@ -298,9 +291,19 @@ func mySQLDoRestore( defer func() { dbErr := dbRestore.Close() if dbErr != nil { - log.WithError(dbErr).Error("failed to close connection to mysql restore database") + log.WithError(dbErr).Error("failed to close connection to mysql backup database") } }() + err = waitForDb(dbRestore) + if err != nil { + return []TestStruct{}, err + } + + // restore server from mysqldump + err = source.DoRestoreForKind(ctx, restoreKind, false, useRestic) + if err != nil { + return []TestStruct{}, err + } // attempt to retrieve test data from database var result *sql.Rows @@ -336,9 +339,11 @@ func mySQLDoRestore( func createMySQLConfig(container commons.TestContainerSetup, useRestic bool, resticIP, resticPort, filepath string, doStdinBackup bool) []byte { var resticConfig string if useRestic { - restoreTarget := "/" + //restoreTarget := "/" + stdinFilename := "" if doStdinBackup { - restoreTarget = path.Join(restoreTarget, filepath) + stdinFilename = fmt.Sprintf(" backup:\n flags:\n stdinFilename: %s\n", filepath) + //restoreTarget = path.Join(restoreTarget, filepath) } resticConfig = fmt.Sprintf( `doPipingBackup: %t @@ -346,7 +351,7 @@ restic: global: flags: repo: rest:http://%s:%s/ - forget: +%s forget: flags: keepLast: 1 keepHourly: 0 @@ -356,9 +361,9 @@ restic: keepYearly: 0 restore: flags: - target: "%s" + target: "/" id: "latest" -`, doStdinBackup, resticIP, resticPort, restoreTarget, +`, doStdinBackup, resticIP, resticPort, stdinFilename, ) } @@ -385,7 +390,8 @@ mysqlrestore: user: %s Database: %s additionalArgs: [] - sourceFile: %s%s + sourceFile: %s +%s `, hostName, container.Port, mySQLRootPW, mySQLRoot, filepath, hostName, container.Port, mySQLRootPW, mySQLRoot, mySQLDatabase, filepath, resticConfig, @@ -393,6 +399,24 @@ mysqlrestore: return result } +func waitForDb(db *sql.DB) error { + var err error + // sleep to give mysql server time to get ready + time.Sleep(10 * time.Second) + // Ping until ready or 30 seconds are over + for i := 0; i < 20; i++ { + err = db.Ping() + if err == nil { + break + } + time.Sleep(time.Second) + } + if err != nil { + return errors.Wrap(err, "can't ping database after 30 seconds") + } + return nil +} + // prepareTestData creates test data and inserts it into the given database func prepareTestData(database *sql.DB) ([]TestStruct, error) { var err error diff --git a/test/pkg/source/postgrestest/postgres_test.go b/test/pkg/source/postgrestest/postgres_test.go index 52f0d56..7120672 100644 --- a/test/pkg/source/postgrestest/postgres_test.go +++ b/test/pkg/source/postgrestest/postgres_test.go @@ -6,7 +6,6 @@ import ( "database/sql" "fmt" "os" - "path" "testing" "time" @@ -443,9 +442,11 @@ func createPGConfig(container commons.TestContainerSetup, useRestic bool, restic var resticConfig string if useRestic { - restoreTarget := "/" + stdinFilename := "" + //restoreTarget := "/" if doStdinBackup { - restoreTarget = path.Join(restoreTarget, filepath) + stdinFilename = fmt.Sprintf(" backup:\n flags:\n stdinFilename: %s\n", filepath) + //restoreTarget = path.Join(restoreTarget, filepath) } resticConfig = fmt.Sprintf( `doPipingBackup: %t @@ -453,7 +454,7 @@ restic: global: flags: repo: rest:http://%s:%s/ - forget: +%s forget: flags: keepLast: 1 keepHourly: 0 @@ -463,15 +464,15 @@ restic: keepYearly: 0 restore: flags: - target: "%s" + target: "/" id: "latest" -`, doStdinBackup, resticIP, resticPort, restoreTarget, +`, doStdinBackup, resticIP, resticPort, stdinFilename, ) } - filenameKey := "file" - if doStdinBackup { - filenameKey = "stdinFilename" + filename := "" + if !doStdinBackup { + filename = fmt.Sprintf(" file: %s\n", filepath) } result := []byte(fmt.Sprintf( @@ -484,13 +485,12 @@ pgdump: password: %s username: %s dbName: %s - %s: %s - format: %s +%s format: %s additionalArgs: [] %s %s -`, hostName, container.Port, postgresPW, postgresUser, postgresDB, filenameKey, filepath, format, restoreConfig, resticConfig, +`, hostName, container.Port, postgresPW, postgresUser, postgresDB, filename, format, restoreConfig, resticConfig, )) return result } diff --git a/test/pkg/source/redisdump/redisdump_test.go b/test/pkg/source/redisdump/redisdump_test.go index b19465f..45fd3a9 100644 --- a/test/pkg/source/redisdump/redisdump_test.go +++ b/test/pkg/source/redisdump/redisdump_test.go @@ -198,7 +198,7 @@ func (redisDumpTestSuite *RedisDumpTestSuite) TestRedisDumpResticGzip() { } func TestRedisDumpTestSuite(t *testing.T) { - _, resticExists := commons.CheckProgramsAndRestic(t, "tar", "--version") + _, resticExists := commons.CheckProgramsAndRestic(t, "redis-cli", "--version", "tar", "--version") testSuite := &RedisDumpTestSuite{ resticExists: resticExists, } @@ -298,7 +298,11 @@ func redisDoRestore( // pull data from restic repository if needed if useRestic { - err = commons.DoResticRestore(ctx, resticContainer, backupPath) + err = os.Remove(path) + if err != nil { + return testStruct{}, errors.Wrapf(err, "error while removing redis dump before restoring it") + } + err = commons.DoResticRestore(ctx, resticContainer, path) if err != nil { return testStruct{}, errors.WithStack(err) } diff --git a/test/pkg/source/tar/tar_test.go b/test/pkg/source/tar/tar_test.go index ec3a8e2..f2177f9 100644 --- a/test/pkg/source/tar/tar_test.go +++ b/test/pkg/source/tar/tar_test.go @@ -126,9 +126,12 @@ func hashFile(filename string) (string, error) { // createTarConfig creates a brudi config for the tar commands func createTarConfig() []byte { - restoreTarget := "/tmp" - /*if doStdinBackup { - restoreTarget = path.Join(restoreTarget, targetPath) + //restoreTarget := "/tmp" + /*stdinFilename := "" + if doStdinBackup { + stdinFilename = fmt.Sprintf(" backup:\n flags:\n stdinFilename: %s\n", + backupPath) + //restoreTarget = path.Join(restoreTarget, targetPath) }*/ return []byte(fmt.Sprintf( //doPipingBackup: %t @@ -149,9 +152,9 @@ tarrestore: extract: true gzip: true file: %s - target: "%s" + target: "/tmp" additionalArgs: [] hostName: autoGeneratedIfEmpty -`, targetPath, backupPath, targetPath, restoreTarget, +`, targetPath, backupPath, targetPath, )) } From 47732ebd5b2ff3f21290f5922e4a80a462c6f5e5 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 10:03:10 +0100 Subject: [PATCH 09/12] removed a todo --- pkg/source/redisdump/backend_config_based.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/source/redisdump/backend_config_based.go b/pkg/source/redisdump/backend_config_based.go index 7e8ba1d..ae6ef36 100644 --- a/pkg/source/redisdump/backend_config_based.go +++ b/pkg/source/redisdump/backend_config_based.go @@ -21,7 +21,6 @@ type ConfigBasedBackend struct { func NewConfigBasedBackend() (*ConfigBasedBackend, error) { if viper.GetBool(cli.DoStdinBackupKey) { //config.Options.Flags.Pipe = true - // TODO: Is this error correct? return nil, errors.Errorf("can't do a backup to STDOUT with redisdump but %s is set", cli.DoStdinBackupKey) } From 627e9b315463f862e4e9c5ce9f26aa2dc7ce1457 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 11:13:38 +0100 Subject: [PATCH 10/12] added helpful parameters to testing errors and let ci update postgres tools --- .github/workflows/build.yaml | 5 +++++ test/pkg/source/mongodbtest/mongodb_test.go | 6 +++--- test/pkg/source/mysqltest/mysql_test.go | 6 +++--- test/pkg/source/postgrestest/postgres_test.go | 6 +++--- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0b33ea1..4eba8e5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -41,6 +41,11 @@ jobs: - name: install redis-cli run: sudo apt-get install redis-tools + - name: Update postgres tools + run: | + sudo apt update + sudo apt install -y postgresql-client + - name: Test run: go test -v ./... env: diff --git a/test/pkg/source/mongodbtest/mongodb_test.go b/test/pkg/source/mongodbtest/mongodb_test.go index 69be977..f0249ab 100644 --- a/test/pkg/source/mongodbtest/mongodb_test.go +++ b/test/pkg/source/mongodbtest/mongodb_test.go @@ -107,7 +107,7 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) basicMongoDBDu }() // create a container running the restic rest-server resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) - mongoDumpAndRestoreTestSuite.Require().NoError(err) + mongoDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) defer func() { resticErr := resticContainer.Container.Terminate(ctx) if resticErr != nil { @@ -118,12 +118,12 @@ func (mongoDumpAndRestoreTestSuite *MongoDumpAndRestoreTestSuite) basicMongoDBDu // backup database and retain test data for verification var testData []interface{} testData, err = mongoDoBackup(ctx, true, resticContainer, useStdin) - mongoDumpAndRestoreTestSuite.Require().NoError(err) + mongoDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) // restore database from backup and pull test data for verification var results []interface{} results, err = mongoDoRestore(ctx, true, resticContainer) - mongoDumpAndRestoreTestSuite.Require().NoError(err) + mongoDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) assert.DeepEqual(mongoDumpAndRestoreTestSuite.T(), testData, results) } diff --git a/test/pkg/source/mysqltest/mysql_test.go b/test/pkg/source/mysqltest/mysql_test.go index 35d7e9c..4c16de0 100644 --- a/test/pkg/source/mysqltest/mysql_test.go +++ b/test/pkg/source/mysqltest/mysql_test.go @@ -145,7 +145,7 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) mySQLDumpAndRe // setup a container running the restic rest-server resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) - mySQLDumpAndRestoreTestSuite.Require().NoError(err) + mySQLDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) defer func() { resticErr := resticContainer.Container.Terminate(ctx) if resticErr != nil { @@ -156,12 +156,12 @@ func (mySQLDumpAndRestoreTestSuite *MySQLDumpAndRestoreTestSuite) mySQLDumpAndRe // backup test data with brudi and retain test data for verification var testData []TestStruct testData, err = mySQLDoBackup(ctx, true, resticContainer, backupPath, useStdin) - mySQLDumpAndRestoreTestSuite.Require().NoError(err) + mySQLDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) // restore database from backup and pull test data from it for verification var restoreResult []TestStruct restoreResult, err = mySQLDoRestore(ctx, true, resticContainer, backupPath) - mySQLDumpAndRestoreTestSuite.Require().NoError(err) + mySQLDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) assert.DeepEqual(mySQLDumpAndRestoreTestSuite.T(), testData, restoreResult) } diff --git a/test/pkg/source/postgrestest/postgres_test.go b/test/pkg/source/postgrestest/postgres_test.go index 7120672..dac26e8 100644 --- a/test/pkg/source/postgrestest/postgres_test.go +++ b/test/pkg/source/postgrestest/postgres_test.go @@ -225,7 +225,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) pgDumpAndRestoreRest // setup a container running the restic rest-server resticContainer, err := commons.NewTestContainerSetup(ctx, &commons.ResticReq, commons.ResticPort) - pgDumpAndRestoreTestSuite.Require().NoError(err) + pgDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) defer func() { resticErr := resticContainer.Container.Terminate(ctx) if resticErr != nil { @@ -239,7 +239,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) pgDumpAndRestoreRest ctx, true, resticContainer, "tar", backupPath, useStdin, ) - pgDumpAndRestoreTestSuite.Require().NoError(err) + pgDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) // restore test data with brudi and retrieve it from the db for verification var restoreResult []testStruct @@ -247,7 +247,7 @@ func (pgDumpAndRestoreTestSuite *PGDumpAndRestoreTestSuite) pgDumpAndRestoreRest ctx, true, resticContainer, "tar", backupPath, useStdin, ) - pgDumpAndRestoreTestSuite.Require().NoError(err) + pgDumpAndRestoreTestSuite.Require().NoErrorf(err, "backupPath: '%s', useStdin: '%t'", backupPath, useStdin) assert.DeepEqual(pgDumpAndRestoreTestSuite.T(), testData, restoreResult) } From 21b4a4adc02a89ff2f9e508a0be97d184c1c1189 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 11:27:47 +0100 Subject: [PATCH 11/12] let ci install postgres-client-16 --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4eba8e5..a2bd125 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -43,8 +43,8 @@ jobs: - name: Update postgres tools run: | - sudo apt update - sudo apt install -y postgresql-client + sudo apt-get update + sudo apt-get install -y postgresql-client-16 - name: Test run: go test -v ./... From 56cabe99e3adeb409b77114625bfd411c257d722 Mon Sep 17 00:00:00 2001 From: "A. Grellmann" <116557655+a-grellmann@users.noreply.github.com> Date: Wed, 17 Jan 2024 11:34:11 +0100 Subject: [PATCH 12/12] further postgres-client-16 ci changes --- .github/workflows/build.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index a2bd125..e74a002 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -43,8 +43,10 @@ jobs: - name: Update postgres tools run: | + sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - sudo apt-get update - sudo apt-get install -y postgresql-client-16 + sudo apt-get install -y postgresql-client - name: Test run: go test -v ./...