diff --git a/.golangci.yml b/.golangci.yml index 9dafa72..90c2bb3 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,43 +1,105 @@ --- linters: enable: + - deadcode + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + - asciicheck + - bidichk - bodyclose - - dogsled + - containedctx + - contextcheck + - cyclop + - decorder + - depguard - dupl + - durationcheck + - errchkjson + - errname + - errorlint - exhaustive + - exhaustivestruct + - exportloopref + - forcetypeassert + - funlen + - gci - gochecknoinits - gocognit + - goconst - gocritic - - gocyclo - - goerr113 - - gofumpt + # - gocyclo # Using cyclop + - godot + # - goerr113 # Using errorlint + - gofmt + - goheader - goimports - gomnd + - gomoddirectives + - gomodguard - goprintffuncname - # - gosec - # - ifshort - - interfacer - - maligned + - gosec + - grouper + - ifshort + - importas + # - ireturn + - lll + - maintidx + - makezero - misspell - nakedret - nestif + - nilerr + - nilnil - nlreturn - noctx + - nolintlint + - paralleltest + - prealloc + - predeclared + # - promlinter # Not common enough + - revive + - rowserrcheck + - sqlclosecheck + # - stylecheck # Using revive + - tagliatelle + - tenv + - testpackage + - thelper + - tparallel + - unconvert - unparam + - varnamelen + - wastedassign + - whitespace + - wrapcheck - wsl - # - errorlint + disable: - gochecknoglobals + - godox + - forbidigo + # Deprecated + - golint + - interfacer + - maligned + - scopelint linters-settings: - gosec: - excludes: - - G204 -# gomnd: -# settings: -# mnd: -# ignored-functions: math.* + # gosec: + # excludes: + # - G204 + gomnd: + settings: + mnd: + ignored-functions: math.* issues: exclude-rules: @@ -45,4 +107,5 @@ issues: linters: - errcheck - gosec - - maligned + # Enable autofix + fix: true diff --git a/go.mod b/go.mod index fbbc236..2142995 100644 --- a/go.mod +++ b/go.mod @@ -2,11 +2,12 @@ module git.iamthefij.com/iamthefij/restic-scheduler go 1.17 +require github.com/hashicorp/hcl/v2 v2.11.1 + require ( github.com/agext/levenshtein v1.2.1 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/google/go-cmp v0.3.1 // indirect - github.com/hashicorp/hcl/v2 v2.11.1 // indirect github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/zclconf/go-cty v1.8.0 // indirect golang.org/x/text v0.3.5 // indirect diff --git a/go.sum b/go.sum index 3e0d664..5e58289 100644 --- a/go.sum +++ b/go.sum @@ -5,7 +5,9 @@ github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2 github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -14,13 +16,17 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc= github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM= github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= diff --git a/job.go b/job.go index b013163..912dc82 100644 --- a/job.go +++ b/job.go @@ -16,7 +16,7 @@ type TaskConfig struct { Restic *ResticCmd } -// ResticConfig is all configuration to be sent to Restic +// ResticConfig is all configuration to be sent to Restic. type ResticConfig struct { Repo string `hcl:"repo"` Passphrase string `hcl:"passphrase,optional"` @@ -24,14 +24,14 @@ type ResticConfig struct { GlobalOpts *ResticGlobalOpts `hcl:"options,block"` } -// ExecutableTask is a task to be run before or after backup/retore +// ExecutableTask is a task to be run before or after backup/retore. type ExecutableTask interface { RunBackup(cfg TaskConfig) error RunRestore(cfg TaskConfig) error Name() string } -// JobTaskScript is a sript to be executed as part of a job task +// JobTaskScript is a sript to be executed as part of a job task. type JobTaskScript struct { OnBackup string `hcl:"on_backup,optional"` OnRestore string `hcl:"on_restore,optional"` @@ -40,7 +40,7 @@ type JobTaskScript struct { name string } -// RunBackup runs script on backup +// RunBackup runs script on backup. func (t JobTaskScript) RunBackup(cfg TaskConfig) error { env := MergeEnv(cfg.Env, t.env) if env == nil { @@ -61,7 +61,7 @@ func (t JobTaskScript) RunBackup(cfg TaskConfig) error { return nil } -// RunRestore script on restore +// RunRestore script on restore. func (t JobTaskScript) RunRestore(cfg TaskConfig) error { env := MergeEnv(cfg.Env, t.env) if env == nil { @@ -90,7 +90,7 @@ func (t *JobTaskScript) SetName(name string) { t.name = name } -// JobTaskMySQL is a sqlite backup task that performs required pre and post tasks +// JobTaskMySQL is a sqlite backup task that performs required pre and post tasks. type JobTaskMySQL struct { Name string `hcl:"name,label"` Hostname string `hcl:"hostname,optional"` @@ -102,6 +102,7 @@ type JobTaskMySQL struct { func (t JobTaskMySQL) GetPreTask() ExecutableTask { return JobTaskScript{ name: t.Name, + env: nil, OnBackup: fmt.Sprintf( "mysqldump -h '%s' -u '%s' -p '%s' '%s' > './%s.sql'", t.Hostname, @@ -110,13 +111,16 @@ func (t JobTaskMySQL) GetPreTask() ExecutableTask { t.Database, t.Name, ), + OnRestore: "", FromJobDir: true, } } func (t JobTaskMySQL) GetPostTask() ExecutableTask { return JobTaskScript{ - name: t.Name, + name: t.Name, + env: nil, + OnBackup: "", OnRestore: fmt.Sprintf( "mysql -h '%s' -u '%s' -p '%s' '%s' << './%s.sql'", t.Hostname, @@ -129,7 +133,7 @@ func (t JobTaskMySQL) GetPostTask() ExecutableTask { } } -// JobTaskSqlite is a sqlite backup task that performs required pre and post tasks +// JobTaskSqlite is a sqlite backup task that performs required pre and post tasks. type JobTaskSqlite struct { Name string `hcl:"name,label"` Path string `hcl:"path"` @@ -138,17 +142,23 @@ type JobTaskSqlite struct { func (t JobTaskSqlite) GetPreTask() ExecutableTask { return JobTaskScript{ name: t.Name, + env: nil, OnBackup: fmt.Sprintf( "sqlite3 %s '.backup $RESTIC_JOB_DIR/%s.bak'", t.Path, t.Name, ), + OnRestore: "", + FromJobDir: false, } } func (t JobTaskSqlite) GetPostTask() ExecutableTask { return JobTaskScript{ - name: t.Name, - OnRestore: fmt.Sprintf("cp '$RESTIC_JOB_DIR/%s.bak' '%s'", t.Name, t.Path), + name: t.Name, + env: nil, + OnBackup: "", + OnRestore: fmt.Sprintf("cp '$RESTIC_JOB_DIR/%s.bak' '%s'", t.Name, t.Path), + FromJobDir: false, } } @@ -189,7 +199,7 @@ func (t *BackupFilesTask) SetName(name string) { t.name = name } -// JobTask represents a single task within a backup job +// JobTask represents a single task within a backup job. type JobTask struct { Name string `hcl:"name,label"` Scripts []JobTaskScript `hcl:"script,block"` @@ -213,7 +223,7 @@ func (t JobTask) GetTasks() []ExecutableTask { } // Job contains all configuration required to construct and run a backup -// and restore job +// and restore job. type Job struct { Name string `hcl:"name,label"` Schedule string `hcl:"schedule"` @@ -223,7 +233,7 @@ type Job struct { Forget *ForgetOpts `hcl:"forget,block"` // Meta Tasks - MySql []JobTaskMySQL `hcl:"mysql,block"` + MySQL []JobTaskMySQL `hcl:"mysql,block"` Sqlite []JobTaskSqlite `hcl:"sqlite,block"` } @@ -231,7 +241,7 @@ func (j Job) AllTasks() []ExecutableTask { allTasks := []ExecutableTask{} // Pre tasks - for _, mysql := range j.MySql { + for _, mysql := range j.MySQL { allTasks = append(allTasks, mysql.GetPreTask()) } @@ -245,7 +255,7 @@ func (j Job) AllTasks() []ExecutableTask { } // Post tasks - for _, mysql := range j.MySql { + for _, mysql := range j.MySQL { allTasks = append(allTasks, mysql.GetPreTask()) } @@ -277,6 +287,7 @@ func (j Job) RunTasks() error { JobDir: jobDir, Logger: GetChildLogger(logger, exTask.Name()), Restic: restic, + Env: nil, } if err := exTask.RunBackup(taskCfg); err != nil { @@ -285,7 +296,9 @@ func (j Job) RunTasks() error { } if j.Forget != nil { - restic.Forget(j.Forget) + if err := restic.Forget(j.Forget); err != nil { + return fmt.Errorf("failed forgetting and pruning job %s: %w", j.Name, err) + } } return nil @@ -298,6 +311,7 @@ func (j Job) NewRestic() *ResticCmd { Env: j.Config.Env, Passphrase: j.Config.Passphrase, GlobalOpts: j.Config.GlobalOpts, + Cwd: "", } } diff --git a/main.go b/main.go index cacfae8..71ff109 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( ) var ( - // version of restic-scheduler being run + // version of restic-scheduler being run. version = "dev" ) diff --git a/restic.go b/restic.go index d50e6eb..36c3618 100644 --- a/restic.go +++ b/restic.go @@ -15,7 +15,7 @@ type CommandOptions interface { type NoOpts struct{} -func (_ NoOpts) ToArgs() []string { +func (NoOpts) ToArgs() []string { return []string{} } @@ -23,7 +23,7 @@ type ResticGlobalOpts struct { CaCertFile string `hcl:"CaCertFile,optional"` CacheDir string `hcl:"CacheDir,optional"` PasswordFile string `hcl:"PasswordFile,optional"` - TlsClientCertFile string `hcl:"TlsClientCertFile,optional"` + TLSClientCertFile string `hcl:"TlsClientCertFile,optional"` LimitDownload int `hcl:"LimitDownload,optional"` LimitUpload int `hcl:"LimitUpload,optional"` VerboseLevel int `hcl:"VerboseLevel,optional"` @@ -32,6 +32,7 @@ type ResticGlobalOpts struct { NoLock bool `hcl:"NoLock,optional"` } +// nolint:cyclop func (glo ResticGlobalOpts) ToArgs() (args []string) { if glo.CaCertFile != "" { args = append(args, "--cacert", glo.CaCertFile) @@ -65,8 +66,8 @@ func (glo ResticGlobalOpts) ToArgs() (args []string) { args = append(args, "--password-file", glo.PasswordFile) } - if glo.TlsClientCertFile != "" { - args = append(args, "--tls-client-cert", glo.TlsClientCertFile) + if glo.TLSClientCertFile != "" { + args = append(args, "--tls-client-cert", glo.TLSClientCertFile) } if glo.VerboseLevel > 0 { @@ -120,9 +121,11 @@ func (rcmd ResticCmd) RunRestic(command string, options CommandOptions, commandA cmd.Env = rcmd.BuildEnv() cmd.Dir = rcmd.Cwd - err := cmd.Run() + if err := cmd.Run(); err != nil { + return fmt.Errorf("error running restic: %w", err) + } - return err + return nil } type BackupOpts struct { @@ -146,7 +149,7 @@ func (bo BackupOpts) ToArgs() (args []string) { func (rcmd ResticCmd) Backup(files []string, options *BackupOpts) error { if options == nil { - options = &BackupOpts{} + options = &BackupOpts{} // nolint:exhaustivestruct } err := rcmd.RunRestic("backup", options, files...) @@ -198,7 +201,7 @@ func (ro RestoreOpts) ToArgs() (args []string) { func (rcmd ResticCmd) Restore(snapshot string, opts *RestoreOpts) error { if opts == nil { - opts = &RestoreOpts{} + opts = &RestoreOpts{} // nolint:exhaustivestruct } err := rcmd.RunRestic("restore", opts, snapshot) @@ -227,6 +230,7 @@ type ForgetOpts struct { Prune bool `hcl:"Prune,optional"` } +// nolint:funlen,cyclop func (fo ForgetOpts) ToArgs() (args []string) { // Add keep-* if fo.KeepLast > 0 { @@ -298,7 +302,7 @@ func (fo ForgetOpts) ToArgs() (args []string) { func (rcmd ResticCmd) Forget(forgetOpts *ForgetOpts) error { if forgetOpts == nil { - forgetOpts = &ForgetOpts{} + forgetOpts = &ForgetOpts{} // nolint:exhaustivestruct } err := rcmd.RunRestic("forget", forgetOpts) diff --git a/shell.go b/shell.go index 6e1efe3..3b6c2b5 100644 --- a/shell.go +++ b/shell.go @@ -30,25 +30,25 @@ func GetChildLogger(parent *log.Logger, name string) *log.Logger { return GetLogger(childName) } -type logWriter struct { +type LogWriter struct { logger *log.Logger } -func NewLogWriter(logger *log.Logger) *logWriter { - return &logWriter{logger} +func NewLogWriter(logger *log.Logger) *LogWriter { + return &LogWriter{logger} } -func (w logWriter) Write(p []byte) (n int, err error) { - message := fmt.Sprintf("%s", p) +func (w LogWriter) Write(content []byte) (n int, err error) { + message := string(content) for _, line := range strings.Split(message, "\n") { w.logger.Printf(" %s", line) } - return len(p), nil + return len(content), nil } func RunShell(script string, cwd string, env map[string]string, logger *log.Logger) error { - cmd := exec.Command("sh", "-c", strings.TrimSpace(script)) + cmd := exec.Command("sh", "-c", strings.TrimSpace(script)) // nolint:gosec // Make both stderr and stdout go to logger // fmt.Println("LOGGER PREFIX", logger.Prefix()) diff --git a/utils.go b/utils.go index c551b19..af82978 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,8 @@ package main -func MergeEnv(parent, child map[string]string) (result map[string]string) { +func MergeEnv(parent, child map[string]string) map[string]string { + result := map[string]string{} + for key, value := range parent { result[key] = value } @@ -9,5 +11,5 @@ func MergeEnv(parent, child map[string]string) (result map[string]string) { result[key] = value } - return + return result }