diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3caf605..c632f16 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,7 +10,11 @@ "eamodio.gitlens", "streetsidesoftware.code-spell-checker", "esbenp.prettier-vscode" - ] - } + ], + "settings": { + "terminal.integrated.cwd": "/workspaces/backup" + } + }, + "workspaceFolder": "/workspaces/backup" } } diff --git a/Makefile b/Makefile index 3e89421..0e570fe 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # Makefile to build the project and place the binary in the dist/ directory # Build command with common flags -BUILD_CMD = CGO_ENABLED=0 go build -ldflags="-s -w" +BUILD_CMD = CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -tags=prod PACKAGE = ./backup/main.go .PHONY: build clean test lint tidy checksums release sanity-check check-mod-tidy lint-config-check lint-fix format check-clean @@ -67,3 +67,16 @@ release: release-linux-amd64 release-darwin-amd64 release-windows-amd64 checksum printf "%-40s %-15s %-64s\n" "$$file" "Size: $$size bytes" "Checksum: $$checksum"; \ fi; \ done + +report-size: build + go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest + gsa --web --listen=":8910" --open dist/backup + +report-coverage: + @mkdir -p coverage + @go test ./... -count=1 -coverprofile=coverage/coverage.out -coverpkg=./backup/... + @echo + @echo "Coverage Summary:" + @go tool cover -func=coverage/coverage.out + @go tool cover -html=coverage/coverage.out -o coverage/coverage.html + @echo "Coverage report generated at coverage/coverage.html" diff --git a/backup/cmd/backup.go b/backup/cmd/backup.go deleted file mode 100644 index 445e925..0000000 --- a/backup/cmd/backup.go +++ /dev/null @@ -1,92 +0,0 @@ -package cmd - -import ( - "fmt" - "log" - "os" - "time" - - "backup-rsync/backup/internal" - - "github.com/spf13/cobra" -) - -const filePermission = 0644 -const logDirPermission = 0755 - -func getLogPath(create bool) string { - logPath := "logs/sync-" + time.Now().Format("2006-01-02T15-04-05") - if create { - err := os.MkdirAll(logPath, logDirPermission) - if err != nil { - log.Fatalf("Failed to create log directory: %v", err) - } - } - - return logPath -} - -func executeSyncJobs(cfg internal.Config, simulate bool) { - logPath := getLogPath(true) - - overallLogPath := logPath + "/summary.log" - - overallLogFile, err := os.OpenFile(overallLogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, filePermission) - if err != nil { - log.Fatalf("Failed to open overall log file: %v", err) - } - defer overallLogFile.Close() - - overallLogger := log.New(overallLogFile, "", log.LstdFlags) - - for _, job := range cfg.Jobs { - jobLogPath := fmt.Sprintf("%s/job-%s.log", logPath, job.Name) - status := internal.ExecuteJob(job, simulate, false, jobLogPath) - overallLogger.Printf("STATUS [%s]: %s", job.Name, status) - fmt.Printf("Status [%s]: %s\n", job.Name, status) - } -} - -func listCommands(cfg internal.Config) { - logPath := getLogPath(false) - for _, job := range cfg.Jobs { - jobLogPath := fmt.Sprintf("%s/job-%s.log", logPath, job.Name) - internal.ExecuteJob(job, false, true, jobLogPath) - } -} - -func AddBackupCommands(rootCmd *cobra.Command) { - var runCmd = &cobra.Command{ - Use: "run", - Short: "Execute the sync jobs", - Run: func(cmd *cobra.Command, args []string) { - configPath, _ := cmd.Flags().GetString("config") - cfg := internal.LoadResolvedConfig(configPath) - executeSyncJobs(cfg, false) - }, - } - - var simulateCmd = &cobra.Command{ - Use: "simulate", - Short: "Simulate the sync jobs", - Run: func(cmd *cobra.Command, args []string) { - configPath, _ := cmd.Flags().GetString("config") - cfg := internal.LoadResolvedConfig(configPath) - executeSyncJobs(cfg, true) - }, - } - - var listCmd = &cobra.Command{ - Use: "list", - Short: "List the commands that will be executed", - Run: func(cmd *cobra.Command, args []string) { - configPath, _ := cmd.Flags().GetString("config") - cfg := internal.LoadResolvedConfig(configPath) - listCommands(cfg) - }, - } - - rootCmd.AddCommand(runCmd) - rootCmd.AddCommand(simulateCmd) - rootCmd.AddCommand(listCmd) -} diff --git a/backup/cmd/check.go b/backup/cmd/check-coverage.go similarity index 82% rename from backup/cmd/check.go rename to backup/cmd/check-coverage.go index e83b644..b7f7738 100644 --- a/backup/cmd/check.go +++ b/backup/cmd/check-coverage.go @@ -9,10 +9,10 @@ import ( "github.com/spf13/cobra" ) -func AddCheckCommands(rootCmd *cobra.Command) { +func buildCheckCoverageCommand() *cobra.Command { var fs = afero.NewOsFs() - var checkCmd = &cobra.Command{ + return &cobra.Command{ Use: "check-coverage", Short: "Check path coverage", Run: func(cmd *cobra.Command, args []string) { @@ -27,6 +27,4 @@ func AddCheckCommands(rootCmd *cobra.Command) { } }, } - - rootCmd.AddCommand(checkCmd) } diff --git a/backup/cmd/config.go b/backup/cmd/config.go index 05bff0e..2920799 100644 --- a/backup/cmd/config.go +++ b/backup/cmd/config.go @@ -9,9 +9,7 @@ import ( "gopkg.in/yaml.v3" ) -// AddConfigCommands binds the config command and its subcommands to the root command. -func AddConfigCommands(rootCmd *cobra.Command) { - // configCmd represents the config command. +func buildConfigCommand() *cobra.Command { var configCmd = &cobra.Command{ Use: "config", Short: "Manage configuration", @@ -21,8 +19,7 @@ func AddConfigCommands(rootCmd *cobra.Command) { }, } - // Extend the config subcommand with the show verb. - var showCmd = &cobra.Command{ + var showVerb = &cobra.Command{ Use: "show", Short: "Show resolved configuration", Run: func(cmd *cobra.Command, args []string) { @@ -38,8 +35,7 @@ func AddConfigCommands(rootCmd *cobra.Command) { }, } - // Extend the config subcommand with the validate verb. - var validateCmd = &cobra.Command{ + var validateVerb = &cobra.Command{ Use: "validate", Short: "Validate configuration", Run: func(cmd *cobra.Command, args []string) { @@ -49,7 +45,8 @@ func AddConfigCommands(rootCmd *cobra.Command) { }, } - rootCmd.AddCommand(configCmd) - configCmd.AddCommand(showCmd) - configCmd.AddCommand(validateCmd) + configCmd.AddCommand(showVerb) + configCmd.AddCommand(validateVerb) + + return configCmd } diff --git a/backup/cmd/list.go b/backup/cmd/list.go new file mode 100644 index 0000000..6ad9bb2 --- /dev/null +++ b/backup/cmd/list.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "fmt" + + "backup-rsync/backup/internal" + + "github.com/spf13/cobra" +) + +func listCommands(cfg internal.Config) { + logPath := internal.GetLogPath(false) + for _, job := range cfg.Jobs { + jobLogPath := fmt.Sprintf("%s/job-%s.log", logPath, job.Name) + internal.ExecuteJob(job, false, true, jobLogPath) + } +} + +func buildListCommand() *cobra.Command { + return &cobra.Command{ + Use: "list", + Short: "List the commands that will be executed", + Run: func(cmd *cobra.Command, args []string) { + configPath, _ := cmd.Flags().GetString("config") + cfg := internal.LoadResolvedConfig(configPath) + listCommands(cfg) + }, + } +} diff --git a/backup/cmd/root.go b/backup/cmd/root.go index 8dfcfca..504505c 100644 --- a/backup/cmd/root.go +++ b/backup/cmd/root.go @@ -17,9 +17,11 @@ func Execute() { rootCmd.PersistentFlags().String("config", "config.yaml", "Path to the configuration file") - AddConfigCommands(rootCmd) - AddBackupCommands(rootCmd) - AddCheckCommands(rootCmd) + rootCmd.AddCommand(buildListCommand()) + rootCmd.AddCommand(buildRunCommand()) + rootCmd.AddCommand(buildSimulateCommand()) + rootCmd.AddCommand(buildConfigCommand()) + rootCmd.AddCommand(buildCheckCoverageCommand()) err := rootCmd.Execute() if err != nil { diff --git a/backup/cmd/run.go b/backup/cmd/run.go new file mode 100644 index 0000000..3c27ba2 --- /dev/null +++ b/backup/cmd/run.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "backup-rsync/backup/internal" + + "github.com/spf13/cobra" +) + +func buildRunCommand() *cobra.Command { + return &cobra.Command{ + Use: "run", + Short: "Execute the sync jobs", + Run: func(cmd *cobra.Command, args []string) { + configPath, _ := cmd.Flags().GetString("config") + cfg := internal.LoadResolvedConfig(configPath) + internal.ExecuteSyncJobs(cfg, false) + }, + } +} diff --git a/backup/cmd/simulate.go b/backup/cmd/simulate.go new file mode 100644 index 0000000..522c924 --- /dev/null +++ b/backup/cmd/simulate.go @@ -0,0 +1,19 @@ +package cmd + +import ( + "backup-rsync/backup/internal" + + "github.com/spf13/cobra" +) + +func buildSimulateCommand() *cobra.Command { + return &cobra.Command{ + Use: "simulate", + Short: "Simulate the sync jobs", + Run: func(cmd *cobra.Command, args []string) { + configPath, _ := cmd.Flags().GetString("config") + cfg := internal.LoadResolvedConfig(configPath) + internal.ExecuteSyncJobs(cfg, true) + }, + } +} diff --git a/backup/internal/helper.go b/backup/internal/helper.go index 2089042..7b3fd6f 100644 --- a/backup/internal/helper.go +++ b/backup/internal/helper.go @@ -1,8 +1,56 @@ // Package internal provides helper functions for internal use within the application. package internal -import "strings" +import ( + "fmt" + "log" + "os" + "strings" + "time" +) func NormalizePath(path string) string { return strings.TrimSuffix(strings.ReplaceAll(path, "//", "/"), "/") } + +const FilePermission = 0644 +const LogDirPermission = 0755 + +func GetLogPath(create bool) string { + logPath := "logs/sync-" + time.Now().Format("2006-01-02T15-04-05") + if create { + err := os.MkdirAll(logPath, LogDirPermission) + if err != nil { + log.Fatalf("Failed to create log directory: %v", err) + } + } + + return logPath +} + +func ExecuteSyncJobs(cfg Config, simulate bool) { + logPath := GetLogPath(true) + + overallLogPath := logPath + "/summary.log" + + overallLogFile, err := os.OpenFile(overallLogPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, FilePermission) + if err != nil { + log.Fatalf("Failed to open overall log file: %v", err) + } + + defer func() { + err := overallLogFile.Close() + if err != nil { + log.Fatalf("Failed to close overall log file: %v", err) + } + }() + + overallLogger := log.New(overallLogFile, "", log.LstdFlags) + + for _, job := range cfg.Jobs { + jobLogPath := fmt.Sprintf("%s/job-%s.log", logPath, job.Name) + status := ExecuteJob(job, simulate, false, jobLogPath) + overallLogger.Printf("STATUS [%s]: %s", job.Name, status) + fmt.Printf("Status [%s]: %s\n", job.Name, status) + } +} diff --git a/backup/internal/check_test.go b/backup/internal/test/check_test.go similarity index 100% rename from backup/internal/check_test.go rename to backup/internal/test/check_test.go diff --git a/backup/internal/config_test.go b/backup/internal/test/config_test.go similarity index 100% rename from backup/internal/config_test.go rename to backup/internal/test/config_test.go diff --git a/backup/internal/helper_test.go b/backup/internal/test/helper_test.go similarity index 100% rename from backup/internal/helper_test.go rename to backup/internal/test/helper_test.go diff --git a/backup/internal/job_test.go b/backup/internal/test/job_test.go similarity index 100% rename from backup/internal/job_test.go rename to backup/internal/test/job_test.go