mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 08:23:50 +01:00
cmd: add 'test' subcommand
configbreak
This commit is contained in:
parent
287e0620ba
commit
70258fbada
@ -1,10 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/jinzhu/copier"
|
"github.com/jinzhu/copier"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
"github.com/pkg/errors"
|
||||||
"github.com/zrepl/zrepl/jobrun"
|
"github.com/zrepl/zrepl/jobrun"
|
||||||
"github.com/zrepl/zrepl/rpc"
|
"github.com/zrepl/zrepl/rpc"
|
||||||
"github.com/zrepl/zrepl/sshbytestream"
|
"github.com/zrepl/zrepl/sshbytestream"
|
||||||
@ -24,6 +24,8 @@ var (
|
|||||||
JobSectionPull string = "pull"
|
JobSectionPull string = "pull"
|
||||||
JobSectionPrune string = "prune"
|
JobSectionPrune string = "prune"
|
||||||
JobSectionAutosnap string = "autosnap"
|
JobSectionAutosnap string = "autosnap"
|
||||||
|
JobSectionPullACL string = "pull_acl"
|
||||||
|
JobSectionSinks string = "sink"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Remote struct {
|
type Remote struct {
|
||||||
@ -51,7 +53,7 @@ type SSHTransport struct {
|
|||||||
type Push struct {
|
type Push struct {
|
||||||
jobName string // for use with jobrun package
|
jobName string // for use with jobrun package
|
||||||
To *Remote
|
To *Remote
|
||||||
Filter zfs.DatasetFilter
|
Filter DatasetMapFilter
|
||||||
InitialReplPolicy InitialReplPolicy
|
InitialReplPolicy InitialReplPolicy
|
||||||
RepeatStrategy jobrun.RepeatStrategy
|
RepeatStrategy jobrun.RepeatStrategy
|
||||||
}
|
}
|
||||||
@ -65,7 +67,7 @@ type Pull struct {
|
|||||||
|
|
||||||
type Prune struct {
|
type Prune struct {
|
||||||
jobName string // for use with jobrun package
|
jobName string // for use with jobrun package
|
||||||
DatasetFilter zfs.DatasetFilter
|
DatasetFilter DatasetMapFilter
|
||||||
SnapshotFilter zfs.FilesystemVersionFilter
|
SnapshotFilter zfs.FilesystemVersionFilter
|
||||||
RetentionPolicy *RetentionGrid // TODO abstract interface to support future policies?
|
RetentionPolicy *RetentionGrid // TODO abstract interface to support future policies?
|
||||||
Repeat jobrun.RepeatStrategy
|
Repeat jobrun.RepeatStrategy
|
||||||
@ -75,7 +77,7 @@ type Autosnap struct {
|
|||||||
jobName string // for use with jobrun package
|
jobName string // for use with jobrun package
|
||||||
Prefix string
|
Prefix string
|
||||||
Interval jobrun.RepeatStrategy
|
Interval jobrun.RepeatStrategy
|
||||||
DatasetFilter zfs.DatasetFilter
|
DatasetFilter DatasetMapFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -95,10 +97,12 @@ func ParseConfig(path string) (config Config, err error) {
|
|||||||
var bytes []byte
|
var bytes []byte
|
||||||
|
|
||||||
if bytes, err = ioutil.ReadFile(path); err != nil {
|
if bytes, err = ioutil.ReadFile(path); err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = yaml.Unmarshal(bytes, &c); err != nil {
|
if err = yaml.Unmarshal(bytes, &c); err != nil {
|
||||||
|
err = errors.WithStack(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,28 +122,57 @@ func parseMain(root map[string]interface{}) (c Config, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Pushs, err = parsePushs(root["pushs"], remoteLookup); err != nil {
|
if c.Pushs, err = parsePushs(root[JobSectionPush], remoteLookup); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Pulls, err = parsePulls(root["pulls"], remoteLookup); err != nil {
|
if c.Pulls, err = parsePulls(root[JobSectionPull], remoteLookup); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Sinks, err = parseSinks(root["sinks"]); err != nil {
|
if c.Sinks, err = parseSinks(root[JobSectionSinks]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.PullACLs, err = parsePullACLs(root["pull_acls"]); err != nil {
|
if c.PullACLs, err = parsePullACLs(root[JobSectionPullACL]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Prunes, err = parsePrunes(root["prune"]); err != nil {
|
if c.Prunes, err = parsePrunes(root[JobSectionPrune]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c.Autosnaps, err = parseAutosnaps(root["autosnap"]); err != nil {
|
if c.Autosnaps, err = parseAutosnaps(root[JobSectionAutosnap]); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) resolveJobName(jobname string) (i interface{}, err error) {
|
||||||
|
s := strings.SplitN(jobname, ".", 2)
|
||||||
|
if len(s) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid job name syntax (section.name)")
|
||||||
|
}
|
||||||
|
section, name := s[0], s[1]
|
||||||
|
var ok bool
|
||||||
|
switch section {
|
||||||
|
case JobSectionAutosnap:
|
||||||
|
i, ok = c.Autosnaps[name]
|
||||||
|
case JobSectionPush:
|
||||||
|
i, ok = c.Pushs[name]
|
||||||
|
case JobSectionPull:
|
||||||
|
i, ok = c.Pulls[name]
|
||||||
|
case JobSectionPrune:
|
||||||
|
i, ok = c.Prunes[name]
|
||||||
|
case JobSectionPullACL:
|
||||||
|
i, ok = c.PullACLs[name]
|
||||||
|
case JobSectionSinks:
|
||||||
|
i, ok = c.Sinks[name]
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid section name: %s", section)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("cannot find job '%s' in section '%s'", name, section)
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
||||||
func fullJobName(section, name string) (full string, err error) {
|
func fullJobName(section, name string) (full string, err error) {
|
||||||
if len(name) < 1 {
|
if len(name) < 1 {
|
||||||
err = fmt.Errorf("job name not set")
|
err = fmt.Errorf("job name not set")
|
||||||
@ -470,7 +503,7 @@ func parsePrunes(m interface{}) (rets map[string]*Prune, err error) {
|
|||||||
|
|
||||||
asList := make(map[string]map[string]interface{}, 0)
|
asList := make(map[string]map[string]interface{}, 0)
|
||||||
if err = mapstructure.Decode(m, &asList); err != nil {
|
if err = mapstructure.Decode(m, &asList); err != nil {
|
||||||
return
|
return nil, errors.Wrap(err, "mapstructure error")
|
||||||
}
|
}
|
||||||
|
|
||||||
rets = make(map[string]*Prune, len(asList))
|
rets = make(map[string]*Prune, len(asList))
|
||||||
|
@ -7,7 +7,7 @@ remotes:
|
|||||||
port: 22
|
port: 22
|
||||||
identity_file: /etc/zrepl/identities/offsite_backups
|
identity_file: /etc/zrepl/identities/offsite_backups
|
||||||
|
|
||||||
pushs:
|
push:
|
||||||
|
|
||||||
offsite:
|
offsite:
|
||||||
to: offsite_backups
|
to: offsite_backups
|
||||||
@ -17,7 +17,7 @@ pushs:
|
|||||||
"tank/usr/home<": ok,
|
"tank/usr/home<": ok,
|
||||||
}
|
}
|
||||||
|
|
||||||
pulls:
|
pull:
|
||||||
|
|
||||||
offsite:
|
offsite:
|
||||||
from: offsite_backups
|
from: offsite_backups
|
||||||
@ -35,7 +35,7 @@ pulls:
|
|||||||
"tank/usr/home":"mirrorpool/foo/bar"
|
"tank/usr/home":"mirrorpool/foo/bar"
|
||||||
}
|
}
|
||||||
|
|
||||||
sinks:
|
sink:
|
||||||
|
|
||||||
db1:
|
db1:
|
||||||
mapping: {
|
mapping: {
|
||||||
@ -61,7 +61,7 @@ sinks:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pull_acls:
|
pull_acl:
|
||||||
|
|
||||||
office_backup:
|
office_backup:
|
||||||
filter: {
|
filter: {
|
||||||
|
91
cmd/test.go
Normal file
91
cmd/test.go
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/zrepl/zrepl/zfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var testCmd = &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "test configuration",
|
||||||
|
}
|
||||||
|
|
||||||
|
var testConfigSyntaxCmd = &cobra.Command{
|
||||||
|
Use: "config",
|
||||||
|
Short: "test if config file can be parsed",
|
||||||
|
Run: doTestConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
var testDatasetMapFilter = &cobra.Command{
|
||||||
|
Use: "pattern jobtype.name test/zfs/dataset/path",
|
||||||
|
Short: "test dataset mapping / filter specified in config",
|
||||||
|
Example: ` zrepl test pattern prune.clean_backups tank/backups/legacyscript/foo`,
|
||||||
|
Run: doTestDatasetMapFilter,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RootCmd.AddCommand(testCmd)
|
||||||
|
testCmd.AddCommand(testConfigSyntaxCmd)
|
||||||
|
testCmd.AddCommand(testDatasetMapFilter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestConfig(cmd *cobra.Command, args []string) {
|
||||||
|
log.Printf("config ok")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTestDatasetMapFilter(cmd *cobra.Command, args []string) {
|
||||||
|
if len(args) != 2 {
|
||||||
|
log.Printf("specify job name as first postitional argument, test input as second")
|
||||||
|
log.Printf(cmd.UsageString())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
n, i := args[0], args[1]
|
||||||
|
jobi, err := conf.resolveJobName(n)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var mf DatasetMapFilter
|
||||||
|
switch j := jobi.(type) {
|
||||||
|
case *Autosnap:
|
||||||
|
mf = j.DatasetFilter
|
||||||
|
case *Prune:
|
||||||
|
mf = j.DatasetFilter
|
||||||
|
case *Pull:
|
||||||
|
mf = j.Mapping
|
||||||
|
case *Push:
|
||||||
|
mf = j.Filter
|
||||||
|
case DatasetMapFilter:
|
||||||
|
mf = j
|
||||||
|
default:
|
||||||
|
panic("incomplete implementation")
|
||||||
|
}
|
||||||
|
|
||||||
|
ip, err := zfs.NewDatasetPath(i)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("cannot parse test input as ZFS dataset path: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mf.filterOnly {
|
||||||
|
pass, err := mf.Filter(ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error evaluating filter: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
log.Printf("filter result: %v", pass)
|
||||||
|
} else {
|
||||||
|
res, err := mf.Map(ip)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error evaluating mapping: %s", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
log.Printf("%s => %s", ip.ToString(), res.ToString())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user