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 @@ -23,6 +23,7 @@ linters:
- os
- path/filepath
- sort
- slices
- strings
- testing
- time
Expand Down
25 changes: 9 additions & 16 deletions backup/internal/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"log"
"path/filepath"
"sort"
"slices"
"strings"

"github.com/spf13/afero"
Expand Down Expand Up @@ -40,20 +40,17 @@ func (c *CoverageChecker) ListUncoveredPaths(cfg Config) []string {
c.checkPath(source.Path, cfg, &result, seen)
}

sort.Strings(result) // Ensure consistent ordering for test comparison
slices.Sort(result) // Ensure consistent ordering for test comparison

return result
}

func (c *CoverageChecker) isExcluded(path string, job Job) bool {
for _, exclusion := range job.Exclusions {
exclusionPath := filepath.Join(job.Source, exclusion)
if strings.HasPrefix(NormalizePath(path), exclusionPath) {
return true
}
}
normalized := NormalizePath(path)

return false
return slices.ContainsFunc(job.Exclusions, func(exclusion string) bool {
return strings.HasPrefix(normalized, filepath.Join(job.Source, exclusion))
})
}

func (c *CoverageChecker) isCoveredByJob(path string, job Job) bool {
Expand All @@ -73,13 +70,9 @@ func (c *CoverageChecker) isCoveredByJob(path string, job Job) bool {
}

func (c *CoverageChecker) isCovered(path string, jobs []Job) bool {
for _, job := range jobs {
if c.isCoveredByJob(path, job) {
return true
}
}

return false
return slices.ContainsFunc(jobs, func(job Job) bool {
return c.isCoveredByJob(path, job)
})
}

func (c *CoverageChecker) checkPath(path string, cfg Config, result *[]string, seen map[string]bool) {
Expand Down
59 changes: 19 additions & 40 deletions backup/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"os"
"path/filepath"
"slices"
"strings"

"gopkg.in/yaml.v3"
Expand Down Expand Up @@ -47,29 +48,22 @@ func (cfg Config) Apply(rsync JobCommand, logger *log.Logger, output io.Writer)
logger.Printf("Rsync Version Info: %s", versionInfo)
}

var succeeded, failed, skipped int
counts := make(map[JobStatus]int)

for _, job := range cfg.Jobs {
status := job.Apply(rsync)
logger.Printf("STATUS [%s]: %s", job.Name, status)
fmt.Fprintf(output, "Status [%s]: %s\n", job.Name, status)

switch status {
case Success:
succeeded++
case Failure:
failed++
case Skipped:
skipped++
}
counts[status]++
}

summary := fmt.Sprintf("Summary: %d succeeded, %d failed, %d skipped", succeeded, failed, skipped)
summary := fmt.Sprintf("Summary: %d succeeded, %d failed, %d skipped",
counts[Success], counts[Failure], counts[Skipped])
logger.Print(summary)
fmt.Fprintln(output, summary)

if failed > 0 {
return fmt.Errorf("%w: %d of %d jobs", ErrJobFailure, failed, len(cfg.Jobs))
if counts[Failure] > 0 {
return fmt.Errorf("%w: %d of %d jobs", ErrJobFailure, counts[Failure], len(cfg.Jobs))
}

return nil
Expand All @@ -89,12 +83,12 @@ func LoadConfig(reader io.Reader) (Config, error) {
}

func SubstituteVariables(input string, variables map[string]string) string {
oldnew := make([]string, 0, len(variables)*2) //nolint:mnd // 2 entries per variable: key placeholder + value
for key, value := range variables {
placeholder := fmt.Sprintf("${%s}", key)
input = strings.ReplaceAll(input, placeholder, value)
oldnew = append(oldnew, "${"+key+"}", value)
}

return input
return strings.NewReplacer(oldnew...).Replace(input)
}

func ResolveConfig(cfg Config) Config {
Expand All @@ -108,7 +102,8 @@ func ResolveConfig(cfg Config) Config {
}

func ValidateJobNames(jobs []Job) error {
invalidNames := []string{}
var invalidNames []string

nameSet := make(map[string]bool)

for _, job := range jobs {
Expand All @@ -118,12 +113,8 @@ func ValidateJobNames(jobs []Job) error {
nameSet[job.Name] = true
}

for _, r := range job.Name {
if r > 127 || r == ' ' {
invalidNames = append(invalidNames, "invalid characters in job name: "+job.Name)

break
}
if strings.ContainsFunc(job.Name, func(r rune) bool { return r > 127 || r == ' ' }) {
invalidNames = append(invalidNames, "invalid characters in job name: "+job.Name)
}
}

Expand All @@ -135,10 +126,8 @@ func ValidateJobNames(jobs []Job) error {
}

func ValidatePath(jobPath string, paths []Path, pathType string, jobName string) error {
for _, path := range paths {
if strings.HasPrefix(jobPath, path.Path) {
return nil
}
if slices.ContainsFunc(paths, func(p Path) bool { return strings.HasPrefix(jobPath, p.Path) }) {
return nil
}

return fmt.Errorf("%w for job '%s': %s %s", ErrInvalidPath, jobName, pathType, jobPath)
Expand Down Expand Up @@ -172,19 +161,9 @@ func validateJobPaths(jobs []Job, pathType string, getPath func(job Job) string)
if i != j {
path1, path2 := NormalizePath(getPath(job1)), NormalizePath(getPath(job2))

// Check if path2 is part of job1's exclusions
excluded := false

if pathType == "source" {
for _, exclusion := range job2.Exclusions {
exclusionPath := NormalizePath(filepath.Join(job2.Source, exclusion))
if strings.HasPrefix(path1, exclusionPath) {
excluded = true

break
}
}
}
excluded := pathType == "source" && slices.ContainsFunc(job2.Exclusions, func(exclusion string) bool {
return strings.HasPrefix(path1, NormalizePath(filepath.Join(job2.Source, exclusion)))
})

if !excluded && strings.HasPrefix(path1, path2) {
return fmt.Errorf("%w: job '%s' has a %s path overlapping with job '%s'",
Expand Down
24 changes: 10 additions & 14 deletions backup/internal/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ func (job Job) Apply(rsync JobCommand) JobStatus {
return rsync.Run(job)
}

func boolDefault(ptr *bool, defaultVal bool) bool {
if ptr != nil {
return *ptr
}

return defaultVal
}

// UnmarshalYAML implements custom YAML unmarshaling to handle defaults properly.
func (job *Job) UnmarshalYAML(node *yaml.Node) error {
var jobYAML JobYAML
Expand All @@ -45,24 +53,12 @@ func (job *Job) UnmarshalYAML(node *yaml.Node) error {
return fmt.Errorf("failed to decode YAML node: %w", err)
}

// Copy basic fields
job.Name = jobYAML.Name
job.Source = jobYAML.Source
job.Target = jobYAML.Target
job.Exclusions = jobYAML.Exclusions

// Handle boolean fields with defaults
if jobYAML.Delete != nil {
job.Delete = *jobYAML.Delete
} else {
job.Delete = true // default value
}

if jobYAML.Enabled != nil {
job.Enabled = *jobYAML.Enabled
} else {
job.Enabled = true // default value
}
job.Delete = boolDefault(jobYAML.Delete, true)
job.Enabled = boolDefault(jobYAML.Enabled, true)

return nil
}
Loading