Some more base methods
This commit is contained in:
parent
8cbaae9c20
commit
c6201750a7
30
config.hcl
Normal file
30
config.hcl
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
job "My App" {
|
||||||
|
schedule = "* * * * *"
|
||||||
|
|
||||||
|
config {
|
||||||
|
repo = "s3://..."
|
||||||
|
passphrase = "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
task "Dump mysql" {
|
||||||
|
mysql {
|
||||||
|
hostname = "foo"
|
||||||
|
username = "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "Create biz file" {
|
||||||
|
on_backup {
|
||||||
|
body = <<EOF
|
||||||
|
echo foo > /biz.txt
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
task "Backup data files" {
|
||||||
|
files = [
|
||||||
|
"/foo/bar",
|
||||||
|
"/biz.txt",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
7
go.mod
7
go.mod
@ -3,4 +3,11 @@ module git.iamthefij.com/iamthefij/restic-scheduler
|
|||||||
go 1.17
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/agext/levenshtein v1.2.1 // indirect
|
||||||
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
|
||||||
|
github.com/google/go-cmp v0.3.1 // indirect
|
||||||
|
github.com/hashicorp/hcl/v2 v2.11.1 // indirect
|
||||||
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
|
||||||
|
github.com/zclconf/go-cty v1.8.0 // indirect
|
||||||
|
golang.org/x/text v0.3.5 // indirect
|
||||||
)
|
)
|
||||||
|
51
go.sum
Normal file
51
go.sum
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8=
|
||||||
|
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||||
|
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
|
||||||
|
github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
|
||||||
|
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
|
||||||
|
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
|
||||||
|
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||||
|
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||||
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.11.1 h1:yTyWcXcm9XB0TEkyU/JCRU6rYy4K+mgLtzn2wlrJbcc=
|
||||||
|
github.com/hashicorp/hcl/v2 v2.11.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||||
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
|
||||||
|
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||||
|
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
|
||||||
|
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
|
||||||
|
github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8=
|
||||||
|
github.com/zclconf/go-cty v1.8.0 h1:s4AvqaeQzJIu3ndv4gVIhplVD0krU+bgrcLSVUnaWuA=
|
||||||
|
github.com/zclconf/go-cty v1.8.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
|
||||||
|
github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
|
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
23
job.go
23
job.go
@ -5,11 +5,12 @@ type JobConfig struct {
|
|||||||
Repo string `hcl:"repo"`
|
Repo string `hcl:"repo"`
|
||||||
Passphrase string `hcl:"passphrase"`
|
Passphrase string `hcl:"passphrase"`
|
||||||
Env map[string]string `hcl:"env,optional"`
|
Env map[string]string `hcl:"env,optional"`
|
||||||
|
Args []string `hcl:"args"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// JobTaskScript is a sript to be executed as part of a job task
|
// JobTaskScript is a sript to be executed as part of a job task
|
||||||
type JobTaskScript struct {
|
type JobTaskScript struct {
|
||||||
ScriptPath string `hcl:"path,label,optional"`
|
ScriptPath string `hcl:"path,optional"`
|
||||||
Body string `hcl:"body,optional"`
|
Body string `hcl:"body,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ type JobTask struct {
|
|||||||
OnRestore []JobTaskScript `hcl:"on_restore,block"`
|
OnRestore []JobTaskScript `hcl:"on_restore,block"`
|
||||||
MySql []JobTaskMySQL `hcl:"mysql,block"`
|
MySql []JobTaskMySQL `hcl:"mysql,block"`
|
||||||
Sqlite []JobTaskSqlite `hcl:"sqlite,block"`
|
Sqlite []JobTaskSqlite `hcl:"sqlite,block"`
|
||||||
Files []string `hcl:"files"`
|
Files []string `hcl:"files,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Job contains all configuration required to construct and run a backup
|
// Job contains all configuration required to construct and run a backup
|
||||||
@ -46,8 +47,17 @@ type Job struct {
|
|||||||
Validate bool `hcl:"validate,optional"`
|
Validate bool `hcl:"validate,optional"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (job Job) NewRestic() ResticCmd {
|
||||||
|
return ResticCmd{
|
||||||
|
LogPrefix: job.Name,
|
||||||
|
Repo: job.Config.Repo,
|
||||||
|
Env: job.Config.Env,
|
||||||
|
Passphrase: job.Config.Passphrase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Jobs []Job `hcl:"job"`
|
Jobs []Job `hcl:"job,block"`
|
||||||
}
|
}
|
||||||
|
|
||||||
/***
|
/***
|
||||||
@ -56,6 +66,7 @@ job "My App" {
|
|||||||
schedule = "* * * * *"
|
schedule = "* * * * *"
|
||||||
config {
|
config {
|
||||||
repo = "s3://..."
|
repo = "s3://..."
|
||||||
|
passphrase = "foo"
|
||||||
}
|
}
|
||||||
|
|
||||||
task "Dump mysql" {
|
task "Dump mysql" {
|
||||||
@ -74,8 +85,10 @@ job "My App" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
task "Backup data files" {
|
task "Backup data files" {
|
||||||
files "/foo/bar"
|
files = [
|
||||||
files "/biz.txt"
|
"/foo/bar",
|
||||||
|
"/biz.txt",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
main.go
16
main.go
@ -1,5 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/hashicorp/hcl/v2/hclsimple"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// version of restic-scheduler being run
|
// version of restic-scheduler being run
|
||||||
version = "dev"
|
version = "dev"
|
||||||
@ -15,4 +23,12 @@ func main() {
|
|||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
if err := hclsimple.DecodeFile("config.hcl", nil, &config); err != nil {
|
||||||
|
log.Fatalf("Failed to load configuration: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Configuration is %#v", config)
|
||||||
}
|
}
|
||||||
|
169
run.go
Normal file
169
run.go
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultFlags = log.LstdFlags | log.Lmsgprefix
|
||||||
|
|
||||||
|
type ResticCmd struct {
|
||||||
|
LogPrefix string
|
||||||
|
Repo string
|
||||||
|
Env map[string]string
|
||||||
|
Passphrase string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rcmd ResticCmd) BuildEnv() []string {
|
||||||
|
rcmd.Env["RESTIC_PASSWORD"] = rcmd.Passphrase
|
||||||
|
|
||||||
|
envList := []string{}
|
||||||
|
|
||||||
|
for name, value := range rcmd.Env {
|
||||||
|
envList = append(envList, fmt.Sprintf("%s=%s", name, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
return envList
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rcmd ResticCmd) RunRestic(args []string) error {
|
||||||
|
cmd := exec.Command("restic", args...)
|
||||||
|
|
||||||
|
cmd.Stdout = rcmd.Logger().Writer()
|
||||||
|
cmd.Stderr = cmd.Stdout
|
||||||
|
cmd.Env = rcmd.BuildEnv()
|
||||||
|
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rcmd ResticCmd) Logger() *log.Logger {
|
||||||
|
logger := log.New(os.Stderr, rcmd.LogPrefix, defaultFlags)
|
||||||
|
|
||||||
|
return logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rcmd ResticCmd) Backup(path string, args []string) error {
|
||||||
|
args = append([]string{"--repo", rcmd.Repo, "backup"}, args...)
|
||||||
|
args = append(args, path)
|
||||||
|
|
||||||
|
err := rcmd.RunRestic(args)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForgetOpts struct {
|
||||||
|
KeepLast int
|
||||||
|
KeepHourly int
|
||||||
|
KeepDaily int
|
||||||
|
KeepWeekly int
|
||||||
|
KeepMonthly int
|
||||||
|
KeepYearly int
|
||||||
|
|
||||||
|
KeepWithin time.Duration
|
||||||
|
KeepWithinHourly time.Duration
|
||||||
|
KeepWithinDaily time.Duration
|
||||||
|
KeepWithinWeekly time.Duration
|
||||||
|
KeepWithinMonthly time.Duration
|
||||||
|
KeepWithinYearly time.Duration
|
||||||
|
|
||||||
|
Tags []string
|
||||||
|
KeepTags []string
|
||||||
|
|
||||||
|
Prune bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fo ForgetOpts) ToArgs() []string {
|
||||||
|
args := []string{}
|
||||||
|
|
||||||
|
// Add keep-*
|
||||||
|
|
||||||
|
if fo.KeepLast > 0 {
|
||||||
|
args = append(args, "--keep-last", fmt.Sprint(fo.KeepLast))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepHourly > 0 {
|
||||||
|
args = append(args, "--keep-hourly", fmt.Sprint(fo.KeepHourly))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepDaily > 0 {
|
||||||
|
args = append(args, "--keep-daily", fmt.Sprint(fo.KeepDaily))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepWeekly > 0 {
|
||||||
|
args = append(args, "--keep-weekly", fmt.Sprint(fo.KeepWeekly))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepMonthly > 0 {
|
||||||
|
args = append(args, "--keep-monthly", fmt.Sprint(fo.KeepMonthly))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepYearly > 0 {
|
||||||
|
args = append(args, "--keep-yearly", fmt.Sprint(fo.KeepYearly))
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepWithin > 0 {
|
||||||
|
args = append(args, "--keep-within", fmt.Sprint(fo.KeepWithin))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add keep-within-*
|
||||||
|
|
||||||
|
if fo.KeepWithinHourly > 0 {
|
||||||
|
args = append(args, "--keep-within-hourly", fo.KeepWithinHourly.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepWithinDaily > 0 {
|
||||||
|
args = append(args, "--keep-within-daily", fo.KeepWithinDaily.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepWithinWeekly > 0 {
|
||||||
|
args = append(args, "--keep-within-weekly", fo.KeepWithinWeekly.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepWithinMonthly > 0 {
|
||||||
|
args = append(args, "--keep-within-monthly", fo.KeepWithinMonthly.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if fo.KeepWithinYearly > 0 {
|
||||||
|
args = append(args, "--keep-within-yearly", fo.KeepWithinYearly.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tags
|
||||||
|
|
||||||
|
if len(fo.Tags) > 0 {
|
||||||
|
args = append(args, "--tag", strings.Join(fo.Tags, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(fo.KeepTags) > 0 {
|
||||||
|
args = append(args, "--keep-tag", strings.Join(fo.Tags, ","))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add prune options
|
||||||
|
|
||||||
|
if fo.Prune {
|
||||||
|
args = append(args, "--prune")
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rcmd ResticCmd) Cleanup(forgetOpts ForgetOpts) error {
|
||||||
|
args := append([]string{"--repo", rcmd.Repo, "forget"}, forgetOpts.ToArgs()...)
|
||||||
|
|
||||||
|
err := rcmd.RunRestic(args)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rcmd ResticCmd) Check() error {
|
||||||
|
args := []string{"--repo", rcmd.Repo, "check"}
|
||||||
|
|
||||||
|
err := rcmd.RunRestic(args)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user