From 63169c51b77705dabb8ad7a65011dc1521f49d3d Mon Sep 17 00:00:00 2001 From: Christian Schwarz Date: Sat, 13 Oct 2018 15:53:52 +0200 Subject: [PATCH] add 'test filesystems' subcommand for testing filesystem filters --- cli/cli.go | 15 +++- client/testcmd.go | 108 +++++++++++++++++++++++++++ config/config.go | 22 ++++++ docs/configuration/filter_syntax.rst | 2 +- main.go | 1 + 5 files changed, 145 insertions(+), 3 deletions(-) create mode 100644 client/testcmd.go diff --git a/cli/cli.go b/cli/cli.go index 5e646d1..8904bdc 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -45,6 +45,7 @@ type Subcommand struct { NoRequireConfig bool Run func(subcommand *Subcommand, args []string) error SetupFlags func(f *pflag.FlagSet) + SetupSubcommands func() []*Subcommand config *config.Config configErr error @@ -86,15 +87,25 @@ func (s *Subcommand) tryParseConfig() { } func AddSubcommand(s *Subcommand) { + addSubcommandToCobraCmd(rootCmd, s) +} + +func addSubcommandToCobraCmd(c *cobra.Command, s *Subcommand) { cmd := cobra.Command{ Use: s.Use, Short: s.Short, - Run: s.run, + } + if s.SetupSubcommands == nil { + cmd.Run = s.run + } else { + for _, sub := range s.SetupSubcommands() { + addSubcommandToCobraCmd(&cmd, sub) + } } if s.SetupFlags != nil { s.SetupFlags(cmd.Flags()) } - rootCmd.AddCommand(&cmd) + c.AddCommand(&cmd) } diff --git a/client/testcmd.go b/client/testcmd.go new file mode 100644 index 0000000..6635db5 --- /dev/null +++ b/client/testcmd.go @@ -0,0 +1,108 @@ +package client + +import ( + "fmt" + "github.com/spf13/pflag" + "github.com/zrepl/zrepl/cli" + "github.com/zrepl/zrepl/config" + "github.com/zrepl/zrepl/daemon/filters" + "github.com/zrepl/zrepl/zfs" +) + +var TestCmd = &cli.Subcommand { + Use: "test", + SetupSubcommands: func() []*cli.Subcommand { + return []*cli.Subcommand{testFilter} + }, +} + +var testFilterArgs struct { + job string + all bool + input string +} + +var testFilter = &cli.Subcommand{ + Use: "filesystems --job JOB [--all | --input INPUT]", + Short: "test filesystems filter specified in push or source job", + SetupFlags: func(f *pflag.FlagSet) { + f.StringVar(&testFilterArgs.job, "job", "", "the name of the push or source job") + f.StringVar(&testFilterArgs.input, "input", "", "a filesystem name to test against the job's filters") + f.BoolVar(&testFilterArgs.all, "all", false, "test all local filesystems") + }, + Run: runTestFilterCmd, +} + +func runTestFilterCmd(subcommand *cli.Subcommand, args []string) error { + + if testFilterArgs.job == "" { + return fmt.Errorf("must specify --job flag") + } + if !(testFilterArgs.all != (testFilterArgs.input != "")) { // xor + return fmt.Errorf("must set one: --all or --input") + } + + conf := subcommand.Config() + + var confFilter config.FilesystemsFilter + job, err := conf.Job(testFilterArgs.job) + if err != nil { + return err + } + switch j := job.Ret.(type) { + case *config.SourceJob: confFilter = j.Filesystems + case *config.PushJob: confFilter = j.Filesystems + default: + return fmt.Errorf("job type %T does not have filesystems filter", j) + } + + f, err := filters.DatasetMapFilterFromConfig(confFilter) + if err != nil { + return fmt.Errorf("filter invalid: %s", err) + } + + var fsnames []string + if testFilterArgs.input != "" { + fsnames = []string{testFilterArgs.input} + } else { + out, err := zfs.ZFSList([]string{"name"}) + if err != nil { + return fmt.Errorf("could not list ZFS filesystems: %s", err) + } + for _, row := range out { + + fsnames = append(fsnames, row[0]) + } + } + + fspaths := make([]*zfs.DatasetPath, len(fsnames)) + for i, fsname := range fsnames { + path, err := zfs.NewDatasetPath(fsname) + if err != nil { + return err + } + fspaths[i] = path + } + + hadFilterErr := false + for _, in := range fspaths { + var res string + var errStr string + pass, err := f.Filter(in) + if err != nil { + res = "ERROR" + errStr = err.Error() + hadFilterErr = true + } else if pass { + res = "ACCEPT" + } else { + res = "REJECT" + } + fmt.Printf("%s\t%s\t%s\n", res, in.ToString(), errStr) + } + + if hadFilterErr { + return fmt.Errorf("filter errors occurred") + } + return nil +} \ No newline at end of file diff --git a/config/config.go b/config/config.go index 62d2cb3..1b14dba 100644 --- a/config/config.go +++ b/config/config.go @@ -17,10 +17,32 @@ type Config struct { Global *Global `yaml:"global,optional,fromdefaults"` } +func (c *Config) Job(name string) (*JobEnum, error) { + for _, j := range c.Jobs { + if j.Name() == name { + return &j, nil + } + } + return nil, fmt.Errorf("job %q not defined in config", name) +} + type JobEnum struct { Ret interface{} } +func (j JobEnum) Name() string { + var name string + switch v := j.Ret.(type) { + case *PushJob: name = v.Name + case *SinkJob: name = v.Name + case *PullJob: name = v.Name + case *SourceJob: name = v.Name + default: + panic(fmt.Sprintf("unknownn job type %T", v)) + } + return name +} + type ActiveJob struct { Type string `yaml:"type"` Name string `yaml:"name"` diff --git a/docs/configuration/filter_syntax.rst b/docs/configuration/filter_syntax.rst index ea6bf29..6657998 100644 --- a/docs/configuration/filter_syntax.rst +++ b/docs/configuration/filter_syntax.rst @@ -18,7 +18,7 @@ The following rules determine which result is chosen for a given filesystem path The **subtree wildcard** ``<`` means "the dataset left of ``<`` and all its children". .. TIP:: - You can try out patterns for a configured job using the ``zrepl test`` subcommand. + You can try out patterns for a configured job using the ``zrepl test filesystems`` subcommand for push and source jobs. Examples -------- diff --git a/main.go b/main.go index 3b5ae45..7d209b9 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ func init() { cli.AddSubcommand(client.ConfigcheckCmd) cli.AddSubcommand(client.VersionCmd) cli.AddSubcommand(client.PprofCmd) + cli.AddSubcommand(client.TestCmd) } func main() {