cmd: add 'test' subcommand

configbreak
This commit is contained in:
Christian Schwarz 2017-09-02 12:24:17 +02:00
parent 287e0620ba
commit 70258fbada
3 changed files with 139 additions and 15 deletions

View File

@ -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))

View File

@ -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
View 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())
}
}