go rewrite

This commit is contained in:
2021-04-09 01:55:10 +02:00
parent 805bed7db1
commit 03cbbfd91c
41 changed files with 1149 additions and 2732 deletions

55
internal/backend.go Normal file
View File

@@ -0,0 +1,55 @@
package internal
import (
"fmt"
)
type Backend struct {
Type string `mapstructure:"type"`
Path string `mapstructure:"path"`
Key string `mapstructure:"key"`
Env map[string]string `mapstructure:"env"`
}
func (b Backend) generateRepo() (string, error) {
switch b.Type {
case "local":
return GetPathRelativeToConfig(b.Path), nil
case "b2", "azure", "gs", "s3", "sftp", "rest":
return fmt.Sprintf("%s:%s", b.Type, b.Path), nil
default:
return "", fmt.Errorf("backend type \"%s\" is invalid", b.Type)
}
}
func (b Backend) getEnv() map[string]string {
env := make(map[string]string)
env["RESTIC_PASSWORD"] = b.Key
repo, err := b.generateRepo()
if err != nil {
panic(err)
}
env["RESTIC_REPOSITORY"] = repo
return env
}
func (b Backend) validate() error {
options := ExecuteOptions{Envs: b.getEnv()}
// Check if already initialized
_, err := ExecuteResticCommand(options, "snapshots")
if err == nil {
return nil
} else {
// If not initialize
out, err := ExecuteResticCommand(options, "init")
fmt.Println(out)
return err
}
}
func (b Backend) Exec(args []string) error {
options := ExecuteOptions{Envs: b.getEnv()}
out, err := ExecuteResticCommand(options, args...)
fmt.Println(out)
return err
}

100
internal/config.go Normal file
View File

@@ -0,0 +1,100 @@
package internal
import (
"fmt"
"log"
"path"
"strings"
"sync"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type Config struct {
Locations map[string]Location `mapstructure:"locations"`
Backends map[string]Backend `mapstructure:"backends"`
}
var once sync.Once
var config *Config
func GetConfig() *Config {
if config == nil {
once.Do(func() {
config = &Config{}
if err := viper.UnmarshalExact(config); err != nil {
log.Fatal("Nope ", err)
}
})
}
return config
}
func GetPathRelativeToConfig(p string) string {
if path.IsAbs(p) {
return p
} else if strings.HasPrefix(p, "~") {
home, err := homedir.Dir()
if err != nil {
panic(err)
}
return path.Join(home, strings.TrimPrefix(p, "~"))
} else {
return path.Join(path.Dir(viper.ConfigFileUsed()), p)
}
}
func (c Config) CheckConfig() error {
for name, backend := range c.Backends {
if err := backend.validate(); err != nil {
return fmt.Errorf("backend \"%s\": %s", name, err)
}
}
for name, location := range c.Locations {
if err := location.validate(c); err != nil {
return fmt.Errorf("location \"%s\": %s", name, err)
}
}
return nil
}
func GetAllOrLocation(cmd *cobra.Command, backends bool) []string {
var list []string
if backends {
for key := range config.Backends {
list = append(list, key)
}
} else {
for key := range config.Locations {
list = append(list, key)
}
}
all, _ := cmd.Flags().GetBool("all")
if all {
return list
} else {
var selected []string
if backends {
tmp, _ := cmd.Flags().GetStringSlice("backend")
selected = tmp
} else {
tmp, _ := cmd.Flags().GetStringSlice("location")
selected = tmp
}
for _, s := range selected {
found := false
for _, l := range list {
if l == s {
found = true
break
}
}
if !found {
panic("invalid key")
}
}
return selected
}
}

85
internal/location.go Normal file
View File

@@ -0,0 +1,85 @@
package internal
import (
"fmt"
)
type HookArray = []string
type Hooks struct {
Before HookArray `mapstructure:"before"`
After HookArray `mapstructure:"after"`
}
type Options map[string]map[string][]string
type Location struct {
From string `mapstructure:"from"`
To []string `mapstructure:"to"`
Hooks Hooks `mapstructure:"hooks"`
Cron string `mapstructure:"cron"`
Options Options `mapstructure:"options"`
}
func (l Location) validate(c Config) error {
// Check if backends are all valid
for _, to := range l.To {
_, ok := c.Backends[to]
if !ok {
return fmt.Errorf("invalid backend `%s`", to)
}
}
return nil
}
func (l Location) getOptions(key string) []string {
var options []string
saved := l.Options[key]
for k, values := range saved {
for _, value := range values {
options = append(options, fmt.Sprintf("--%s", k), value)
}
}
return options
}
func ExecuteHooks(commands []string, options ExecuteOptions) error {
for _, command := range commands {
out, err := ExecuteCommand(options, "-c", command)
fmt.Println(out)
return err
}
return nil
}
func (l Location) Backup() error {
c := GetConfig()
from := GetPathRelativeToConfig(l.From)
for _, to := range l.To {
backend := c.Backends[to]
options := ExecuteOptions{
Command: "bash",
Envs: backend.getEnv(),
Dir: from,
}
if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
return nil
}
{
flags := l.getOptions("backup")
cmd := []string{"backup"}
cmd = append(cmd, flags...)
cmd = append(cmd, ".")
out, err := ExecuteResticCommand(options, cmd...)
fmt.Println(out)
if err != nil {
return err
}
}
if err := ExecuteHooks(l.Hooks.After, options); err != nil {
return nil
}
}
return nil
}

52
internal/lock/lock.go Normal file
View File

@@ -0,0 +1,52 @@
package lock
import (
"errors"
"path"
"sync"
"github.com/spf13/viper"
)
var lock *viper.Viper
var file string
var once sync.Once
func getLock() *viper.Viper {
if lock == nil {
once.Do(func() {
lock = viper.New()
lock.SetDefault("running", false)
p := path.Dir(viper.ConfigFileUsed())
file = path.Join(p, ".autorestic.lock.yml")
lock.SetConfigFile(file)
lock.SetConfigType("yml")
lock.ReadInConfig()
})
}
return lock
}
func set(locked bool) error {
lock := getLock()
if locked {
running := lock.GetBool("running")
if running {
return errors.New("an instance is already running")
}
}
lock.Set("running", locked)
if err := lock.WriteConfigAs(file); err != nil {
return err
}
return nil
}
func Lock() error {
return set(true)
}
func Unlock() error {
return set(false)
}

20
internal/terminal/main.go Normal file
View File

@@ -0,0 +1,20 @@
package terminal
import (
tm "github.com/buger/goterm"
)
func Clear() {
tm.Clear()
}
func Append(line string) {
tm.Println(line)
tm.Flush()
}
func Replace(line string) {
tm.MoveCursorUp(1)
tm.Print("\033[K")
Append(line)
}

48
internal/utils.go Normal file
View File

@@ -0,0 +1,48 @@
package internal
import (
"bytes"
"fmt"
"os"
"os/exec"
)
func CheckIfCommandIsCallable(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
func CheckIfResticIsCallable() bool {
return CheckIfCommandIsCallable("restic")
}
type ExecuteOptions struct {
Command string
Envs map[string]string
Dir string
}
func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
cmd := exec.Command(options.Command, args...)
env := os.Environ()
for k, v := range options.Envs {
env = append(env, fmt.Sprintf("%s=%s", k, v))
}
cmd.Env = env
cmd.Dir = options.Dir
var out bytes.Buffer
var error bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &error
err := cmd.Run()
if err != nil {
return error.String(), err
}
return out.String(), nil
}
func ExecuteResticCommand(options ExecuteOptions, args ...string) (string, error) {
options.Command = "restic"
return ExecuteCommand(options, args...)
}