Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ linters:
- github.com/spf13/cobra
- github.com/spf13/afero
- github.com/stretchr/testify/assert
- github.com/stretchr/testify/mock
- github.com/stretchr/testify/require
- gopkg.in/yaml.v3
- backup-rsync/backup/internal
Expand Down
17 changes: 17 additions & 0 deletions .mockery.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
all: false
dir: '{{.InterfaceDir}}/test'
filename: mock_{{.InterfaceName | lower}}_test.go
force-file-write: true
formatter: goimports
generate: true
include-auto-generated: false
log-level: info
structname: 'Mock{{.InterfaceName}}'
pkgname: 'internal_test'
recursive: false
template: testify
packages:
backup-rsync/backup/internal:
interfaces:
Exec:
JobCommand:
8 changes: 8 additions & 0 deletions backup/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ var (
ErrOverlappingPath = errors.New("overlapping path detected")
)

// Config represents the overall backup configuration.
type Config struct {
Sources []Path `yaml:"sources"`
Targets []Path `yaml:"targets"`
Variables map[string]string `yaml:"variables"`
Jobs []Job `yaml:"jobs"`
}

func (cfg Config) String() string {
out, _ := yaml.Marshal(cfg)

Expand Down
9 changes: 4 additions & 5 deletions backup/internal/job_runner.go → backup/internal/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ import (
"strings"
)

// JobRunner interface for executing commands.
type JobRunner interface {
type Exec interface {
Execute(name string, args ...string) ([]byte, error)
}

// RealSync implements JobRunner using actual os/exec.
type RealSync struct{}
// OsExec implements Exec using actual os/exec.
type OsExec struct{}

// Execute runs the actual command.
func (r *RealSync) Execute(name string, args ...string) ([]byte, error) {
func (r *OsExec) Execute(name string, args ...string) ([]byte, error) {
ctx := context.Background()
cmd := exec.CommandContext(ctx, name, args...)

Expand Down
6 changes: 6 additions & 0 deletions backup/internal/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import (
"time"
)

// Path represents a source or target path with optional exclusions.
type Path struct {
Path string `yaml:"path"`
Exclusions []string `yaml:"exclusions"`
}

func NormalizePath(path string) string {
return strings.TrimSuffix(strings.ReplaceAll(path, "//", "/"), "/")
}
Expand Down
16 changes: 14 additions & 2 deletions backup/internal/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@ import (
"gopkg.in/yaml.v3"
)

// Job represents a backup job configuration for a source/target pair.
//
//nolint:recvcheck // UnmarshalYAML requires pointer receiver while Apply uses value receiver
type Job struct {
Name string `yaml:"name"`
Source string `yaml:"source"`
Target string `yaml:"target"`
Delete bool `yaml:"delete"`
Enabled bool `yaml:"enabled"`
Exclusions []string `yaml:"exclusions,omitempty"`
}

// JobYAML is a helper struct for proper YAML unmarshaling with defaults.
type JobYAML struct {
Name string `yaml:"name"`
Expand All @@ -16,9 +28,9 @@ type JobYAML struct {
Exclusions []string `yaml:"exclusions,omitempty"`
}

func (job Job) Apply(rsync JobCommand) string {
func (job Job) Apply(rsync JobCommand) JobStatus {
if !job.Enabled {
return "SKIPPED"
return Skipped
}

return rsync.Run(job)
Expand Down
14 changes: 14 additions & 0 deletions backup/internal/job_command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package internal

type JobStatus string

const (
Success JobStatus = "SUCCESS"
Failure JobStatus = "FAILURE"
Skipped JobStatus = "SKIPPED"
)

type JobCommand interface {
Run(job Job) JobStatus
GetVersionInfo() (string, string, error)
}
86 changes: 14 additions & 72 deletions backup/internal/rsync.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,99 +10,41 @@ import (
var ErrInvalidRsyncVersion = errors.New("invalid rsync version output")
var ErrInvalidRsyncPath = errors.New("rsync path must be an absolute path")

type SyncCommand struct {
const RsyncVersionFlag = "--version"

type SharedCommand struct {
BinPath string
BaseLogPath string

Executor JobRunner
}

func NewSyncCommand(binPath string, logPath string) SyncCommand {
return SyncCommand{
BinPath: binPath,
BaseLogPath: logPath,
Executor: &RealSync{},
}
Shell Exec
}

func (command SyncCommand) Run(job Job) string {
logPath := fmt.Sprintf("%s/job-%s.log", command.BaseLogPath, job.Name)

args := ArgumentsForJob(job, logPath, false)

return command.RunWithArgs(job, args)
}

func (command SyncCommand) PrintArgs(job Job, args []string) {
func (c SharedCommand) PrintArgs(job Job, args []string) {
fmt.Printf("Job: %s\n", job.Name)
fmt.Printf("Command: %s %s\n", command.BinPath, strings.Join(args, " "))
fmt.Printf("Command: %s %s\n", c.BinPath, strings.Join(args, " "))
}

func (command SyncCommand) RunWithArgs(job Job, args []string) string {
command.PrintArgs(job, args)
func (c SharedCommand) RunWithArgs(job Job, args []string) JobStatus {
c.PrintArgs(job, args)

out, err := command.Executor.Execute(command.BinPath, args...)
out, err := c.Shell.Execute(c.BinPath, args...)
fmt.Printf("Output:\n%s\n", string(out))

if err != nil {
return "FAILURE"
return Failure
}

return "SUCCESS"
}

type SimulateCommand struct {
SyncCommand
}

func NewSimulateCommand(binPath string, logPath string) SimulateCommand {
return SimulateCommand{
SyncCommand: SyncCommand{
BinPath: binPath,
BaseLogPath: logPath,
Executor: &RealSync{},
},
}
}

func (command SimulateCommand) Run(job Job) string {
logPath := fmt.Sprintf("%s/job-%s.log", command.BaseLogPath, job.Name)

args := ArgumentsForJob(job, logPath, true)

return command.RunWithArgs(job, args)
}

type ListCommand struct {
SyncCommand
}

func NewListCommand(binPath string) ListCommand {
return ListCommand{
SyncCommand: SyncCommand{
BinPath: binPath,
BaseLogPath: "",
Executor: &RealSync{},
},
}
}
func (command ListCommand) Run(job Job) string {
logPath := fmt.Sprintf("%s/job-%s.log", command.BaseLogPath, job.Name)

args := ArgumentsForJob(job, logPath, false)
command.PrintArgs(job, args)

return "SUCCESS"
return Success
}

func (command SyncCommand) GetVersionInfo() (string, string, error) {
rsyncPath := command.BinPath
func (c SharedCommand) GetVersionInfo() (string, string, error) {
rsyncPath := c.BinPath

if !filepath.IsAbs(rsyncPath) {
return "", "", fmt.Errorf("%w: \"%s\"", ErrInvalidRsyncPath, rsyncPath)
}

output, err := command.Executor.Execute(command.BinPath, "--version")
output, err := c.Shell.Execute(c.BinPath, RsyncVersionFlag)
if err != nil {
return "", "", fmt.Errorf("error fetching rsync version: %w", err)
}
Expand Down
27 changes: 27 additions & 0 deletions backup/internal/rsync_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package internal

import (
"fmt"
)

type ListCommand struct {
SharedCommand
}

func NewListCommand(binPath string) ListCommand {
return ListCommand{
SharedCommand: SharedCommand{
BinPath: binPath,
BaseLogPath: "",
Shell: &OsExec{},
},
}
}
func (c ListCommand) Run(job Job) JobStatus {
logPath := fmt.Sprintf("%s/job-%s.log", c.BaseLogPath, job.Name)
args := ArgumentsForJob(job, logPath, false)

c.PrintArgs(job, args)

return Success
}
26 changes: 26 additions & 0 deletions backup/internal/rsync_simulate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package internal

import (
"fmt"
)

type SimulateCommand struct {
SharedCommand
}

func NewSimulateCommand(binPath string, logPath string) SimulateCommand {
return SimulateCommand{
SharedCommand: SharedCommand{
BinPath: binPath,
BaseLogPath: logPath,
Shell: &OsExec{},
},
}
}

func (c SimulateCommand) Run(job Job) JobStatus {
logPath := fmt.Sprintf("%s/job-%s.log", c.BaseLogPath, job.Name)
args := ArgumentsForJob(job, logPath, true)

return c.RunWithArgs(job, args)
}
26 changes: 26 additions & 0 deletions backup/internal/rsync_sync.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package internal

import (
"fmt"
)

type SyncCommand struct {
SharedCommand
}

func NewSyncCommand(binPath string, logPath string) SyncCommand {
return SyncCommand{
SharedCommand: SharedCommand{
BinPath: binPath,
BaseLogPath: logPath,
Shell: &OsExec{},
},
}
}

func (c SyncCommand) Run(job Job) JobStatus {
logPath := fmt.Sprintf("%s/job-%s.log", c.BaseLogPath, job.Name)
args := ArgumentsForJob(job, logPath, false)

return c.RunWithArgs(job, args)
}
Loading