mirror of
https://github.com/kopia/kopia.git
synced 2026-01-24 14:28:06 -05:00
cli: major refactoring of how CLI commands are registered The goal is to eliminate flags as global variables to allow for better testing. Each command and subcommand and most sets of flags are now their own struct with 'setup()' methods that attached the flags or subcommand to the provided parent. This change is 94.3% mechanical, but is fully organic and hand-made. * introduced cli.appServices interface which provides the environment in which commands run * remove auto-maintenance global flag * removed globals in memory_tracking.go * removed globals from cli_progress.go * removed globals from the update_check.go * moved configPath into TheApp * removed remaining globals from config.go * refactored logfile to get rid of global variables * removed 'app' global variable * linter fixes * fixed password_*.go build * fixed BSD build
129 lines
4.8 KiB
Go
129 lines
4.8 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"encoding/csv"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/alecthomas/kingpin"
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/snapshot/policy"
|
|
)
|
|
|
|
const maxScriptLength = 32000
|
|
|
|
type policyActionFlags struct {
|
|
policySetBeforeFolderActionCommand string
|
|
policySetAfterFolderActionCommand string
|
|
policySetBeforeSnapshotRootActionCommand string
|
|
policySetAfterSnapshotRootActionCommand string
|
|
policySetActionCommandTimeout time.Duration
|
|
policySetActionCommandMode string
|
|
policySetPersistActionScript bool
|
|
}
|
|
|
|
func (c *policyActionFlags) setup(cmd *kingpin.CmdClause) {
|
|
cmd.Flag("before-folder-action", "Path to before-folder action command ('none' to remove)").Default("-").PlaceHolder("COMMAND").StringVar(&c.policySetBeforeFolderActionCommand)
|
|
cmd.Flag("after-folder-action", "Path to after-folder action command ('none' to remove)").Default("-").PlaceHolder("COMMAND").StringVar(&c.policySetAfterFolderActionCommand)
|
|
cmd.Flag("before-snapshot-root-action", "Path to before-snapshot-root action command ('none' to remove or 'inherit')").Default("-").PlaceHolder("COMMAND").StringVar(&c.policySetBeforeSnapshotRootActionCommand)
|
|
cmd.Flag("after-snapshot-root-action", "Path to after-snapshot-root action command ('none' to remove or 'inherit')").Default("-").PlaceHolder("COMMAND").StringVar(&c.policySetAfterSnapshotRootActionCommand)
|
|
cmd.Flag("action-command-timeout", "Max time allowed for a action to run in seconds").Default("5m").DurationVar(&c.policySetActionCommandTimeout)
|
|
cmd.Flag("action-command-mode", "Action command mode").Default("essential").EnumVar(&c.policySetActionCommandMode, "essential", "optional", "async")
|
|
cmd.Flag("persist-action-script", "Persist action script").BoolVar(&c.policySetPersistActionScript)
|
|
}
|
|
|
|
func (c *policyActionFlags) setActionsFromFlags(ctx context.Context, p *policy.ActionsPolicy, changeCount *int) error {
|
|
if err := c.setActionCommandFromFlags(ctx, "before-folder", &p.BeforeFolder, c.policySetBeforeFolderActionCommand, changeCount); err != nil {
|
|
return errors.Wrap(err, "invalid before-folder-action")
|
|
}
|
|
|
|
if err := c.setActionCommandFromFlags(ctx, "after-folder", &p.AfterFolder, c.policySetAfterFolderActionCommand, changeCount); err != nil {
|
|
return errors.Wrap(err, "invalid after-folder-action")
|
|
}
|
|
|
|
if err := c.setActionCommandFromFlags(ctx, "before-snapshot-root", &p.BeforeSnapshotRoot, c.policySetBeforeSnapshotRootActionCommand, changeCount); err != nil {
|
|
return errors.Wrap(err, "invalid before-snapshot-root-action")
|
|
}
|
|
|
|
if err := c.setActionCommandFromFlags(ctx, "after-snapshot-root", &p.AfterSnapshotRoot, c.policySetAfterSnapshotRootActionCommand, changeCount); err != nil {
|
|
return errors.Wrap(err, "invalid after-snapshot-root-action")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *policyActionFlags) setActionCommandFromFlags(ctx context.Context, actionName string, cmd **policy.ActionCommand, value string, changeCount *int) error {
|
|
if value == "-" {
|
|
// not set
|
|
return nil
|
|
}
|
|
|
|
if value == "" {
|
|
log(ctx).Infof(" - removing %v action", actionName)
|
|
|
|
*changeCount++
|
|
|
|
*cmd = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
*cmd = &policy.ActionCommand{
|
|
TimeoutSeconds: int(c.policySetActionCommandTimeout.Seconds()),
|
|
Mode: c.policySetActionCommandMode,
|
|
}
|
|
|
|
*changeCount++
|
|
|
|
if c.policySetPersistActionScript {
|
|
script, err := ioutil.ReadFile(value) //nolint:gosec
|
|
if err != nil {
|
|
return errors.Wrap(err, "unable to read script file")
|
|
}
|
|
|
|
if len(script) > maxScriptLength {
|
|
return errors.Errorf("action script file (%v) too long: %v, max allowed %d", value, len(script), maxScriptLength)
|
|
}
|
|
|
|
log(ctx).Infof(" - setting %v (%v) action script from file %v (%v bytes) with timeout %v", actionName, c.policySetActionCommandMode, value, len(script), c.policySetActionCommandTimeout)
|
|
|
|
(*cmd).Script = string(script)
|
|
|
|
return nil
|
|
}
|
|
|
|
// parse path as CSV as if space was the separator, this automatically takes care of quotations
|
|
r := csv.NewReader(strings.NewReader(value))
|
|
r.Comma = ' ' // space
|
|
|
|
fields, err := r.Read()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "error parsing %v command", actionName)
|
|
}
|
|
|
|
(*cmd).Command = fields[0]
|
|
(*cmd).Arguments = fields[1:]
|
|
|
|
if len((*cmd).Arguments) == 0 {
|
|
log(ctx).Infof(" - setting %v (%v) action command to %v and timeout %v", actionName, c.policySetActionCommandMode, quoteArguments((*cmd).Command), c.policySetActionCommandTimeout)
|
|
} else {
|
|
log(ctx).Infof(" - setting %v (%v) action command to %v with arguments %v and timeout %v", actionName, c.policySetActionCommandMode, quoteArguments((*cmd).Command), quoteArguments((*cmd).Arguments...), c.policySetActionCommandTimeout)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func quoteArguments(s ...string) string {
|
|
var result []string
|
|
|
|
for _, v := range s {
|
|
result = append(result, fmt.Sprintf("\"%v\"", v))
|
|
}
|
|
|
|
return strings.Join(result, " ")
|
|
}
|