diff --git a/job.go b/job.go index fc68b89..fc17300 100644 --- a/job.go +++ b/job.go @@ -14,10 +14,11 @@ import ( const WorkDirPerms = 0o666 var ( - ErrNoJobsFound = errors.New("no jobs found and at least one job is required") - ErrMissingField = errors.New("missing config field") - ErrMissingBlock = errors.New("missing config block") - ErrMutuallyExclusive = errors.New("mutually exclusive values not valid") + ErrNoJobsFound = errors.New("no jobs found and at least one job is required") + ErrMissingField = errors.New("missing config field") + ErrMissingBlock = errors.New("missing config block") + ErrMutuallyExclusive = errors.New("mutually exclusive values not valid") + ErrInvalidConfigValue = errors.New("invalid config value") ) type TaskConfig struct { @@ -128,11 +129,20 @@ func (t JobTaskMySQL) Filename() string { func (t JobTaskMySQL) Validate() error { if invalidChars := "'\";"; strings.ContainsAny(t.Name, invalidChars) { - return fmt.Errorf("mysql task %s has an invalid name. The name may not contain %s", t.Name, invalidChars) + return fmt.Errorf( + "mysql task %s has an invalid name. The name may not contain %s: %w", + t.Name, + invalidChars, + ErrInvalidConfigValue, + ) } if len(t.Tables) > 0 && t.Database == "" { - return fmt.Errorf("mysql task %s is invalid. Must specify a database to use tables: %w", t.Name, ErrMissingField) + return fmt.Errorf( + "mysql task %s is invalid. Must specify a database to use tables: %w", + t.Name, + ErrMissingField, + ) } return nil @@ -206,7 +216,12 @@ func (t JobTaskSqlite) Filename() string { func (t JobTaskSqlite) Validate() error { if invalidChars := "'\";"; strings.ContainsAny(t.Name, invalidChars) { - return fmt.Errorf("sqlite task %s has an invalid name. The name may not contain %s", t.Name, invalidChars) + return fmt.Errorf( + "sqlite task %s has an invalid name. The name may not contain %s: %w", + t.Name, + invalidChars, + ErrInvalidConfigValue, + ) } return nil @@ -217,7 +232,7 @@ func (t JobTaskSqlite) GetPreTask() ExecutableTask { name: t.Name, env: nil, OnBackup: fmt.Sprintf( - "sqlite3 %s '.backup $RESTIC_JOB_DIR/%s'", + "sqlite3 '%s' '.backup $RESTIC_JOB_DIR/%s'", t.Path, t.Filename(), ), OnRestore: "", @@ -429,17 +444,6 @@ func (j Job) JobDir() string { return cwd } -/* - * func NewTaskConfig(jobDir string, jobLogger *log.Logger, restic *ResticCmd, taskName string) TaskConfig { - * return TaskConfig{ - * JobDir: jobDir, - * Logger: GetChildLogger(jobLogger, taskName), - * Restic: restic, - * Env: nil, - * } - * } - */ - func (j Job) RunBackup() error { logger := GetLogger(j.Name) restic := j.NewRestic() @@ -524,37 +528,3 @@ func (c Config) Validate() error { return nil } - -/*** - -job "My App" { - schedule = "* * * * *" - config { - repo = "s3://..." - passphrase = "foo" - } - - task "Dump mysql" { - mysql { - hostname = "foo" - username = "bar" - } - } - - task "Create biz file" { - on_backup { - body = < /biz.txt - EOF - } - } - - task "Backup data files" { - files = [ - "/foo/bar", - "/biz.txt", - ] - } -} - -***/ diff --git a/job_test.go b/job_test.go index 8875484..bb3210e 100644 --- a/job_test.go +++ b/job_test.go @@ -173,3 +173,125 @@ func TestJobTaskScript(t *testing.T) { }) } } + +func TestJobTaskMySQL(t *testing.T) { + t.Parallel() + + type TaskGenerator interface { + Validate() error + GetPreTask() main.ExecutableTask + GetPostTask() main.ExecutableTask + } + + cases := []struct { + name string + task TaskGenerator + validationErr error + preBackup string + postBackup string + preRestore string + postRestore string + }{ + { + name: "mysql simple", + // nolint:exhaustivestruct + task: main.JobTaskMySQL{Name: "simple"}, + validationErr: nil, + preBackup: "mysqldump --result-file './simple.sql'", + postBackup: "", + preRestore: "", + postRestore: "mysql < './simple.sql'", + }, + { + name: "mysql invalid name", + // nolint:exhaustivestruct + task: main.JobTaskMySQL{Name: "it's invalid;"}, + validationErr: main.ErrInvalidConfigValue, + preBackup: "", + postBackup: "", + preRestore: "", + postRestore: "", + }, + { + name: "mysql tables no database", + // nolint:exhaustivestruct + task: main.JobTaskMySQL{ + Name: "name", + Tables: []string{"table1", "table2"}, + }, + validationErr: main.ErrMissingField, + preBackup: "", + postBackup: "", + preRestore: "", + postRestore: "", + }, + { + name: "mysql all options", + task: main.JobTaskMySQL{ + Name: "simple", + Hostname: "host", + Username: "user", + Password: "pass", + Database: "db", + Tables: []string{"table1", "table2"}, + }, + validationErr: nil, + preBackup: "mysqldump --result-file './simple.sql' --host host --user user --password pass db table1 table2", + postBackup: "", + preRestore: "", + postRestore: "mysql --host host --user user --password pass < './simple.sql'", + }, + // Sqlite + { + name: "sqlite simple", + + task: main.JobTaskSqlite{Name: "simple", Path: "database.db"}, + validationErr: nil, + preBackup: "sqlite3 'database.db' '.backup $RESTIC_JOB_DIR/simple.db.bak'", + postBackup: "", + preRestore: "", + postRestore: "cp '$RESTIC_JOB_DIR/simple.db.bak' 'database.db'", + }, + { + name: "sqlite invalid name", + + task: main.JobTaskSqlite{Name: "it's invalid;", Path: "database.db"}, + validationErr: main.ErrInvalidConfigValue, + preBackup: "", + postBackup: "", + preRestore: "", + postRestore: "", + }, + } + + for _, c := range cases { + testCase := c + + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + validateErr := testCase.task.Validate() + if !errors.Is(validateErr, testCase.validationErr) { + t.Errorf("unexpected validation result. expected: %v, actual: %v", testCase.validationErr, validateErr) + } + + if validateErr != nil { + return + } + + if preTask, ok := testCase.task.GetPreTask().(main.JobTaskScript); ok { + AssertEqual(t, "incorrect pre-backup", testCase.preBackup, preTask.OnBackup) + AssertEqual(t, "incorrect pre-restore", testCase.preRestore, preTask.OnRestore) + } else { + t.Error("pre task was not a JobTaskScript") + } + + if postTask, ok := testCase.task.GetPostTask().(main.JobTaskScript); ok { + AssertEqual(t, "incorrect post-backup", testCase.postBackup, postTask.OnBackup) + AssertEqual(t, "incorrect post-restore", testCase.postRestore, postTask.OnRestore) + } else { + t.Error("post task was not a JobTaskScript") + } + }) + } +} diff --git a/utils_test.go b/utils_test.go index c84f7fe..0fc0895 100644 --- a/utils_test.go +++ b/utils_test.go @@ -7,6 +7,16 @@ import ( "github.com/go-test/deep" ) +func AssertEqual(t *testing.T, message string, expected, actual interface{}) bool { + t.Helper() + + if expected != actual { + t.Errorf("%s. expected: %v, actual: %v", message, expected, actual) + } + + return true +} + func TestMergeEnvMap(t *testing.T) { t.Parallel()