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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/jinzhu/copier"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/zrepl/zrepl/jobrun"
|
||||
"github.com/zrepl/zrepl/rpc"
|
||||
"github.com/zrepl/zrepl/sshbytestream"
|
||||
@ -24,6 +24,8 @@ var (
|
||||
JobSectionPull string = "pull"
|
||||
JobSectionPrune string = "prune"
|
||||
JobSectionAutosnap string = "autosnap"
|
||||
JobSectionPullACL string = "pull_acl"
|
||||
JobSectionSinks string = "sink"
|
||||
)
|
||||
|
||||
type Remote struct {
|
||||
@ -51,7 +53,7 @@ type SSHTransport struct {
|
||||
type Push struct {
|
||||
jobName string // for use with jobrun package
|
||||
To *Remote
|
||||
Filter zfs.DatasetFilter
|
||||
Filter DatasetMapFilter
|
||||
InitialReplPolicy InitialReplPolicy
|
||||
RepeatStrategy jobrun.RepeatStrategy
|
||||
}
|
||||
@ -65,7 +67,7 @@ type Pull struct {
|
||||
|
||||
type Prune struct {
|
||||
jobName string // for use with jobrun package
|
||||
DatasetFilter zfs.DatasetFilter
|
||||
DatasetFilter DatasetMapFilter
|
||||
SnapshotFilter zfs.FilesystemVersionFilter
|
||||
RetentionPolicy *RetentionGrid // TODO abstract interface to support future policies?
|
||||
Repeat jobrun.RepeatStrategy
|
||||
@ -75,7 +77,7 @@ type Autosnap struct {
|
||||
jobName string // for use with jobrun package
|
||||
Prefix string
|
||||
Interval jobrun.RepeatStrategy
|
||||
DatasetFilter zfs.DatasetFilter
|
||||
DatasetFilter DatasetMapFilter
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@ -95,10 +97,12 @@ func ParseConfig(path string) (config Config, err error) {
|
||||
var bytes []byte
|
||||
|
||||
if bytes, err = ioutil.ReadFile(path); err != nil {
|
||||
err = errors.WithStack(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = yaml.Unmarshal(bytes, &c); err != nil {
|
||||
err = errors.WithStack(err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -118,28 +122,57 @@ func parseMain(root map[string]interface{}) (c Config, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if c.Pushs, err = parsePushs(root["pushs"], remoteLookup); err != nil {
|
||||
if c.Pushs, err = parsePushs(root[JobSectionPush], remoteLookup); err != nil {
|
||||
return
|
||||
}
|
||||
if c.Pulls, err = parsePulls(root["pulls"], remoteLookup); err != nil {
|
||||
if c.Pulls, err = parsePulls(root[JobSectionPull], remoteLookup); err != nil {
|
||||
return
|
||||
}
|
||||
if c.Sinks, err = parseSinks(root["sinks"]); err != nil {
|
||||
if c.Sinks, err = parseSinks(root[JobSectionSinks]); err != nil {
|
||||
return
|
||||
}
|
||||
if c.PullACLs, err = parsePullACLs(root["pull_acls"]); err != nil {
|
||||
if c.PullACLs, err = parsePullACLs(root[JobSectionPullACL]); err != nil {
|
||||
return
|
||||
}
|
||||
if c.Prunes, err = parsePrunes(root["prune"]); err != nil {
|
||||
if c.Prunes, err = parsePrunes(root[JobSectionPrune]); err != nil {
|
||||
return
|
||||
}
|
||||
if c.Autosnaps, err = parseAutosnaps(root["autosnap"]); err != nil {
|
||||
if c.Autosnaps, err = parseAutosnaps(root[JobSectionAutosnap]); err != nil {
|
||||
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) {
|
||||
if len(name) < 1 {
|
||||
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)
|
||||
if err = mapstructure.Decode(m, &asList); err != nil {
|
||||
return
|
||||
return nil, errors.Wrap(err, "mapstructure error")
|
||||
}
|
||||
|
||||
rets = make(map[string]*Prune, len(asList))
|
||||
|
@ -7,7 +7,7 @@ remotes:
|
||||
port: 22
|
||||
identity_file: /etc/zrepl/identities/offsite_backups
|
||||
|
||||
pushs:
|
||||
push:
|
||||
|
||||
offsite:
|
||||
to: offsite_backups
|
||||
@ -17,7 +17,7 @@ pushs:
|
||||
"tank/usr/home<": ok,
|
||||
}
|
||||
|
||||
pulls:
|
||||
pull:
|
||||
|
||||
offsite:
|
||||
from: offsite_backups
|
||||
@ -35,7 +35,7 @@ pulls:
|
||||
"tank/usr/home":"mirrorpool/foo/bar"
|
||||
}
|
||||
|
||||
sinks:
|
||||
sink:
|
||||
|
||||
db1:
|
||||
mapping: {
|
||||
@ -61,7 +61,7 @@ sinks:
|
||||
}
|
||||
|
||||
|
||||
pull_acls:
|
||||
pull_acl:
|
||||
|
||||
office_backup:
|
||||
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