Skip to content

Commit

Permalink
reorganize configuration loading
Browse files Browse the repository at this point in the history
  • Loading branch information
choffmeister committed Jun 9, 2022
1 parent 9143b91 commit 4eaa278
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 128 deletions.
5 changes: 1 addition & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ run:
go run . -v backup

test:
go test $(shell go list ./... | grep -v e2e) -v

test-e2e:
go test $(shell go list ./... | grep e2e) -v -timeout=60m
go test ./... -v

test-cover:
go test -coverprofile=coverage.out ./...
Expand Down
9 changes: 2 additions & 7 deletions cmd/backup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ var (
backupCmd = &cobra.Command{
Use: "backup",
RunE: func(cmd *cobra.Command, args []string) error {
config := internal.Config{}
if err := config.LoadFromFile(""); err != nil {
return err
}

for _, target := range config.Targets {
if err := internal.Restic("backup", target); err != nil {
for _, target := range rootContext.Config.Targets {
if err := internal.Restic(rootContext, "backup", target); err != nil {
return err
}
}
Expand Down
30 changes: 13 additions & 17 deletions cmd/cron.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,30 @@ var (
cronCmd = &cobra.Command{
Use: "cron",
RunE: func(cmd *cobra.Command, args []string) error {
config := internal.Config{}
if err := config.LoadFromFile(""); err != nil {
return err
}

if err := backupCmd.RunE(cmd, []string{}); err != nil {
return err
}

if config.Cron.Cleanup.Enabled {
cleanup := rootContext.Config.Cron.Cleanup
if cleanup.Enabled {
forgetArgs := []string{"forget", "--prune"}
if config.Cron.Cleanup.Keep.Last > 0 {
forgetArgs = append(forgetArgs, "--keep-last", strconv.Itoa(config.Cron.Cleanup.Keep.Last))
if cleanup.Keep.Last > 0 {
forgetArgs = append(forgetArgs, "--keep-last", strconv.Itoa(cleanup.Keep.Last))
}
if config.Cron.Cleanup.Keep.Daily > 0 {
forgetArgs = append(forgetArgs, "--keep-daily", strconv.Itoa(config.Cron.Cleanup.Keep.Daily))
if cleanup.Keep.Daily > 0 {
forgetArgs = append(forgetArgs, "--keep-daily", strconv.Itoa(cleanup.Keep.Daily))
}
if config.Cron.Cleanup.Keep.Weekly > 0 {
forgetArgs = append(forgetArgs, "--keep-weekly", strconv.Itoa(config.Cron.Cleanup.Keep.Weekly))
if cleanup.Keep.Weekly > 0 {
forgetArgs = append(forgetArgs, "--keep-weekly", strconv.Itoa(cleanup.Keep.Weekly))
}
if config.Cron.Cleanup.Keep.Monthly > 0 {
forgetArgs = append(forgetArgs, "--keep-monthly", strconv.Itoa(config.Cron.Cleanup.Keep.Monthly))
if cleanup.Keep.Monthly > 0 {
forgetArgs = append(forgetArgs, "--keep-monthly", strconv.Itoa(cleanup.Keep.Monthly))
}
if config.Cron.Cleanup.Keep.Yearly > 0 {
forgetArgs = append(forgetArgs, "--keep-yearly", strconv.Itoa(config.Cron.Cleanup.Keep.Yearly))
if cleanup.Keep.Yearly > 0 {
forgetArgs = append(forgetArgs, "--keep-yearly", strconv.Itoa(cleanup.Keep.Yearly))
}

if err := internal.Restic(forgetArgs...); err != nil {
if err := internal.Restic(rootContext, forgetArgs...); err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/raw.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var (
rawCmd = &cobra.Command{
Use: "raw",
RunE: func(cmd *cobra.Command, args []string) error {
if err := internal.Restic(args...); err != nil {
if err := internal.Restic(rootContext, args...); err != nil {
return err
}

Expand Down
20 changes: 15 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,33 @@ import (
)

var (
verbose bool
rootCmd = &cobra.Command{
rootCmdVerbose bool
rootCmdConfig string
rootCmd = &cobra.Command{
Use: "restic-plus",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if !verbose {
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if !rootCmdVerbose {
internal.Debug = log.New(ioutil.Discard, "", log.LstdFlags)
}
context, err := internal.NewContext(rootCmdConfig)
if err != nil {
return err
}
rootContext = context
return nil
},
}
rootContext *internal.Context
)

func Execute() error {
return rootCmd.Execute()
}

func init() {
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "")
rootCmd.PersistentFlags().BoolVarP(&rootCmdVerbose, "verbose", "v", false, "")
rootCmd.PersistentFlags().StringVarP(&rootCmdConfig, "config", "c", "restic-plus.yaml", "")

rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(rawCmd)

Expand Down
2 changes: 1 addition & 1 deletion cmd/snapshots.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ var (
snapshotsCmd = &cobra.Command{
Use: "snapshots",
RunE: func(cmd *cobra.Command, args []string) error {
if err := internal.Restic("snapshots"); err != nil {
if err := internal.Restic(rootContext, "snapshots"); err != nil {
return err
}

Expand Down
31 changes: 0 additions & 31 deletions internal/config.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
package internal

import (
"fmt"
"io/ioutil"
"os"
"path"

"gopkg.in/yaml.v3"
)

type Config struct {
Targets []string `yaml:"targets"`
Restic ConfigRestic `yaml:"restic"`
Expand Down Expand Up @@ -48,25 +39,3 @@ type ConfigBandwidth struct {
Download int `yaml:"download"`
Upload int `yaml:"upload"`
}

func (c *Config) LoadFromFile(dir string) error {
if dir == "" {
cwd, err := os.Getwd()
if err != nil {
return err
}
dir = cwd
}

file := path.Join(dir, "restic-plus.yaml")
bytes, err := ioutil.ReadFile(file)
if err != nil {
return fmt.Errorf("unable to read config: %w", err)
}

if err := yaml.Unmarshal(bytes, c); err != nil {
return fmt.Errorf("unable to unmarshall config: %w", err)
}

return nil
}
67 changes: 67 additions & 0 deletions internal/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package internal

import (
"fmt"
"io/ioutil"
"os"
"path"

"gopkg.in/yaml.v3"
)

const resticVersion = "0.13.1"

type Context struct {
ResticVersion string
ResticBinary string
Config Config
IdentityFile string
}

func NewContext(file string) (*Context, error) {
tempDir := path.Join(os.TempDir(), "restic-plus")
if file == "" {
cwd, err := os.Getwd()
if err != nil {
return nil, err
}
file = path.Join(cwd, "restic-plus.yaml")
}

bytes, err := ioutil.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("unable to read config: %w", err)
}
config := Config{}
if err := yaml.Unmarshal(bytes, &config); err != nil {
return nil, fmt.Errorf("unable to unmarshall config: %w", err)
}

resticBinary := path.Join(tempDir, "restic")
if err := prepareResticBinary(resticVersion, resticBinary); err != nil {
return nil, fmt.Errorf("unable to prepare restic binary: %w", err)
}

identityFile := path.Join(tempDir, "sftp-identity.key")
if err := prepareIdentity(config.SFTP.IdentityPrivateKey, identityFile); err != nil {
return nil, fmt.Errorf("unable to prepare identity file: %w", err)
}

context := Context{
ResticVersion: resticVersion,
ResticBinary: resticBinary,
Config: config,
IdentityFile: identityFile,
}
return &context, nil
}

func prepareIdentity(identityPrivateKey string, target string) error {
err := os.MkdirAll(resticDir, 0o755)
if err != nil {
return err
}
ioutil.WriteFile(sftpIdentityFile, []byte(identityPrivateKey), 0o600)

return nil
}
100 changes: 38 additions & 62 deletions internal/restic.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"compress/bzip2"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"os/exec"
Expand All @@ -13,24 +12,53 @@ import (
)

var (
resticVersion = "0.13.1"
resticBinUrl = fmt.Sprintf("https://github.com/restic/restic/releases/download/v%s/restic_%s_%s_%s.bz2", resticVersion, resticVersion, runtime.GOOS, runtime.GOARCH)

resticDir = path.Join(os.TempDir(), "restic")
resticBin = path.Join(resticDir, "restic")
sftpIdentityFile = path.Join(resticDir, "sftp-identity.key")
)

func prepareBinary() error {
if stat, err := os.Stat(resticBin); err == nil && !stat.IsDir() {
func Restic(ctx *Context, args ...string) error {
config := ctx.Config

resticRepository := fmt.Sprintf("sftp://%s@%s:%d/", config.SFTP.User, config.SFTP.Host, config.SFTP.Port)
resticPassword := config.Restic.Password
cmdEnv := os.Environ()
cmdEnv = append(cmdEnv,
"RESTIC_REPOSITORY="+resticRepository,
"RESTIC_PASSWORD="+resticPassword,
)

sshCommand := fmt.Sprintf("ssh -i %s -p %d %s@%s -s sftp", sftpIdentityFile, config.SFTP.Port, config.SFTP.User, config.SFTP.Host)
cmdArgs := []string{}
cmdArgs = append(cmdArgs,
"-o",
fmt.Sprintf("sftp.command=%s", sshCommand),
)
cmdArgs = append(cmdArgs, args...)

cmd := exec.Command(ctx.ResticBinary, cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = cmdEnv

Debug.Printf("Executing restic command %v\n", cmdArgs)
if err := cmd.Run(); err != nil {
return fmt.Errorf("restic command failed: %w", err)
}
return nil
}

func prepareResticBinary(version string, target string) error {
resticBinUrl := fmt.Sprintf("https://github.com/restic/restic/releases/download/v%s/restic_%s_%s_%s.bz2", version, version, runtime.GOOS, runtime.GOARCH)

if stat, err := os.Stat(target); err == nil && !stat.IsDir() {
return nil
}

err := os.MkdirAll(resticDir, 0o755)
err := os.MkdirAll(path.Dir(target), 0o755)
if err != nil {
return err
}
Debug.Printf("Downloading %s to %s\n", resticBinUrl, resticBin)
Debug.Printf("Downloading %s to %s\n", resticBinUrl, target)
client := http.Client{
CheckRedirect: func(r *http.Request, via []*http.Request) error {
r.URL.Opaque = r.URL.Path
Expand All @@ -47,7 +75,7 @@ func prepareBinary() error {
if err != nil {
return err
}
file, err := os.OpenFile(resticBin, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o755)
file, err := os.OpenFile(target, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o755)
if err != nil {
return err
}
Expand All @@ -59,55 +87,3 @@ func prepareBinary() error {

return nil
}

func prepareIdentity(identityPrivateKey string) error {
err := os.MkdirAll(resticDir, 0o755)
if err != nil {
return err
}
ioutil.WriteFile(sftpIdentityFile, []byte(identityPrivateKey), 0o600)

return nil
}

func Restic(args ...string) error {
config := Config{}
if err := config.LoadFromFile(""); err != nil {
return err
}

if err := prepareBinary(); err != nil {
return fmt.Errorf("unable to prepare underlying restic binary: %w", err)
}

if err := prepareIdentity(config.SFTP.IdentityPrivateKey); err != nil {
return fmt.Errorf("unable to prepare identity file: %w", err)
}

resticRepository := fmt.Sprintf("sftp://%s@%s:%d/", config.SFTP.User, config.SFTP.Host, config.SFTP.Port)
resticPassword := config.Restic.Password
cmdEnv := os.Environ()
cmdEnv = append(cmdEnv,
"RESTIC_REPOSITORY="+resticRepository,
"RESTIC_PASSWORD="+resticPassword,
)

sshCommand := fmt.Sprintf("ssh -i %s -p %d %s@%s -s sftp", sftpIdentityFile, config.SFTP.Port, config.SFTP.User, config.SFTP.Host)
cmdArgs := []string{}
cmdArgs = append(cmdArgs,
"-o",
fmt.Sprintf("sftp.command=%s", sshCommand),
)
cmdArgs = append(cmdArgs, args...)

cmd := exec.Command(resticBin, cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Env = cmdEnv

Debug.Printf("Executing restic command %v\n", cmdArgs)
if err := cmd.Run(); err != nil {
return fmt.Errorf("restic command failed: %w", err)
}
return nil
}

0 comments on commit 4eaa278

Please sign in to comment.