mirror of
https://github.com/kopia/kopia.git
synced 2026-01-22 05:18:06 -05:00
* This is 99% mechanical: Extracted repo.Repository interface that only exposes high-level object and manifest management methods, but not blob nor content management. Renamed old *repo.Repository to *repo.DirectRepository Reviewed codebase to only depend on repo.Repository as much as possible, but added way for low-level CLI commands to use DirectRepository. * PR fixes
427 lines
13 KiB
Go
427 lines
13 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/repo/compression"
|
|
"github.com/kopia/kopia/snapshot/policy"
|
|
)
|
|
|
|
var booleanEnumValues = []string{"true", "false", "inherit"}
|
|
|
|
var (
|
|
policySetCommand = policyCommands.Command("set", "Set snapshot policy for a single directory, user@host or a global policy.")
|
|
policySetTargets = policySetCommand.Arg("target", "Target of a policy ('global','user@host','@host') or a path").Strings()
|
|
policySetGlobal = policySetCommand.Flag("global", "Set global policy").Bool()
|
|
|
|
// Frequency
|
|
policySetInterval = policySetCommand.Flag("snapshot-interval", "Interval between snapshots").DurationList()
|
|
policySetTimesOfDay = policySetCommand.Flag("snapshot-time", "Times of day when to take snapshot (HH:mm)").Strings()
|
|
|
|
// Expiration policies.
|
|
policySetKeepLatest = policySetCommand.Flag("keep-latest", "Number of most recent backups to keep per source (or 'inherit')").PlaceHolder("N").String()
|
|
policySetKeepHourly = policySetCommand.Flag("keep-hourly", "Number of most-recent hourly backups to keep per source (or 'inherit')").PlaceHolder("N").String()
|
|
policySetKeepDaily = policySetCommand.Flag("keep-daily", "Number of most-recent daily backups to keep per source (or 'inherit')").PlaceHolder("N").String()
|
|
policySetKeepWeekly = policySetCommand.Flag("keep-weekly", "Number of most-recent weekly backups to keep per source (or 'inherit')").PlaceHolder("N").String()
|
|
policySetKeepMonthly = policySetCommand.Flag("keep-monthly", "Number of most-recent monthly backups to keep per source (or 'inherit')").PlaceHolder("N").String()
|
|
policySetKeepAnnual = policySetCommand.Flag("keep-annual", "Number of most-recent annual backups to keep per source (or 'inherit')").PlaceHolder("N").String()
|
|
|
|
// Files to ignore.
|
|
policySetAddIgnore = policySetCommand.Flag("add-ignore", "List of paths to add to the ignore list").PlaceHolder("PATTERN").Strings()
|
|
policySetRemoveIgnore = policySetCommand.Flag("remove-ignore", "List of paths to remove from the ignore list").PlaceHolder("PATTERN").Strings()
|
|
policySetClearIgnore = policySetCommand.Flag("clear-ignore", "Clear list of paths in the ignore list").Bool()
|
|
|
|
// Name of compression algorithm.
|
|
policySetCompressionAlgorithm = policySetCommand.Flag("compression", "Compression algorithm").Enum(supportedCompressionAlgorithms()...)
|
|
policySetCompressionMinSize = policySetCommand.Flag("compression-min-size", "Min size of file to attempt compression for").String()
|
|
policySetCompressionMaxSize = policySetCommand.Flag("compression-max-size", "Max size of file to attempt compression for").String()
|
|
|
|
// Files to only compress.
|
|
policySetAddOnlyCompress = policySetCommand.Flag("add-only-compress", "List of extensions to add to the only-compress list").PlaceHolder("PATTERN").Strings()
|
|
policySetRemoveOnlyCompress = policySetCommand.Flag("remove-only-compress", "List of extensions to remove from the only-compress list").PlaceHolder("PATTERN").Strings()
|
|
policySetClearOnlyCompress = policySetCommand.Flag("clear-only-compress", "Clear list of extensions in the only-compress list").Bool()
|
|
|
|
// Files to never compress.
|
|
policySetAddNeverCompress = policySetCommand.Flag("add-never-compress", "List of extensions to add to the never compress list").PlaceHolder("PATTERN").Strings()
|
|
policySetRemoveNeverCompress = policySetCommand.Flag("remove-never-compress", "List of extensions to remove from the never compress list").PlaceHolder("PATTERN").Strings()
|
|
policySetClearNeverCompress = policySetCommand.Flag("clear-never-compress", "Clear list of extensions in the never compress list").Bool()
|
|
|
|
// Dot-ignore files to look at.
|
|
policySetAddDotIgnore = policySetCommand.Flag("add-dot-ignore", "List of paths to add to the dot-ignore list").PlaceHolder("FILENAME").Strings()
|
|
policySetRemoveDotIgnore = policySetCommand.Flag("remove-dot-ignore", "List of paths to remove from the dot-ignore list").PlaceHolder("FILENAME").Strings()
|
|
policySetClearDotIgnore = policySetCommand.Flag("clear-dot-ignore", "Clear list of paths in the dot-ignore list").Bool()
|
|
policySetMaxFileSize = policySetCommand.Flag("max-file-size", "Exclude files above given size").PlaceHolder("N").String()
|
|
|
|
// Error handling behavior.
|
|
policyIgnoreFileErrors = policySetCommand.Flag("ignore-file-errors", "Ignore errors reading files while traversing ('true', 'false', 'inherit')").Enum(booleanEnumValues...)
|
|
policyIgnoreDirectoryErrors = policySetCommand.Flag("ignore-dir-errors", "Ignore errors reading directories while traversing ('true', 'false', 'inherit").Enum(booleanEnumValues...)
|
|
|
|
// General policy.
|
|
policySetInherit = policySetCommand.Flag(inheritPolicyString, "Enable or disable inheriting policies from the parent").BoolList()
|
|
)
|
|
|
|
const (
|
|
inheritPolicyString = "inherit"
|
|
)
|
|
|
|
func init() {
|
|
policySetCommand.Action(repositoryAction(setPolicy))
|
|
}
|
|
|
|
func setPolicy(ctx context.Context, rep repo.Repository) error {
|
|
targets, err := policyTargets(ctx, rep, policySetGlobal, policySetTargets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, target := range targets {
|
|
p, err := policy.GetDefinedPolicy(ctx, rep, target)
|
|
|
|
switch {
|
|
case err == policy.ErrPolicyNotFound:
|
|
p = &policy.Policy{}
|
|
case err != nil:
|
|
return errors.Wrap(err, "could not get defined policy")
|
|
}
|
|
|
|
printStderr("Setting policy for %v\n", target)
|
|
|
|
changeCount := 0
|
|
if err := setPolicyFromFlags(p, &changeCount); err != nil {
|
|
return err
|
|
}
|
|
|
|
if changeCount == 0 {
|
|
return errors.New("no changes specified")
|
|
}
|
|
|
|
if err := policy.SetPolicy(ctx, rep, target, p); err != nil {
|
|
return errors.Wrapf(err, "can't save policy for %v", target)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setPolicyFromFlags(p *policy.Policy, changeCount *int) error {
|
|
if err := setRetentionPolicyFromFlags(&p.RetentionPolicy, changeCount); err != nil {
|
|
return errors.Wrap(err, "retention policy")
|
|
}
|
|
|
|
setFilesPolicyFromFlags(&p.FilesPolicy, changeCount)
|
|
|
|
if err := setErrorHandlingPolicyFromFlags(&p.ErrorHandlingPolicy, changeCount); err != nil {
|
|
return errors.Wrap(err, "error handling policy")
|
|
}
|
|
|
|
if err := setCompressionPolicyFromFlags(&p.CompressionPolicy, changeCount); err != nil {
|
|
return errors.Wrap(err, "compression policy")
|
|
}
|
|
|
|
if err := setSchedulingPolicyFromFlags(&p.SchedulingPolicy, changeCount); err != nil {
|
|
return errors.Wrap(err, "scheduling policy")
|
|
}
|
|
|
|
if err := applyPolicyNumber64("maximum file size", &p.FilesPolicy.MaxFileSize, *policySetMaxFileSize, changeCount); err != nil {
|
|
return errors.Wrap(err, "maximum file size")
|
|
}
|
|
|
|
// It's not really a list, just optional boolean, last one wins.
|
|
for _, inherit := range *policySetInherit {
|
|
*changeCount++
|
|
|
|
p.NoParent = !inherit
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setFilesPolicyFromFlags(fp *policy.FilesPolicy, changeCount *int) {
|
|
if *policySetClearDotIgnore {
|
|
*changeCount++
|
|
|
|
printStderr(" - removing all rules for dot-ignore files\n")
|
|
|
|
fp.DotIgnoreFiles = nil
|
|
} else {
|
|
fp.DotIgnoreFiles = addRemoveDedupeAndSort("dot-ignore files", fp.DotIgnoreFiles, *policySetAddDotIgnore, *policySetRemoveDotIgnore, changeCount)
|
|
}
|
|
|
|
if *policySetClearIgnore {
|
|
*changeCount++
|
|
|
|
fp.IgnoreRules = nil
|
|
|
|
printStderr(" - removing all ignore rules\n")
|
|
} else {
|
|
fp.IgnoreRules = addRemoveDedupeAndSort("ignored files", fp.IgnoreRules, *policySetAddIgnore, *policySetRemoveIgnore, changeCount)
|
|
}
|
|
}
|
|
|
|
func setErrorHandlingPolicyFromFlags(fp *policy.ErrorHandlingPolicy, changeCount *int) error {
|
|
switch {
|
|
case *policyIgnoreFileErrors == "":
|
|
case *policyIgnoreFileErrors == inheritPolicyString:
|
|
*changeCount++
|
|
|
|
fp.IgnoreFileErrors = nil
|
|
|
|
printStderr(" - inherit file read error behavior from parent\n")
|
|
default:
|
|
val, err := strconv.ParseBool(*policyIgnoreFileErrors)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*changeCount++
|
|
|
|
fp.IgnoreFileErrors = &val
|
|
|
|
printStderr(" - setting ignore file read errors to %v\n", val)
|
|
}
|
|
|
|
switch {
|
|
case *policyIgnoreDirectoryErrors == "":
|
|
case *policyIgnoreDirectoryErrors == inheritPolicyString:
|
|
*changeCount++
|
|
|
|
fp.IgnoreDirectoryErrors = nil
|
|
|
|
printStderr(" - inherit directory read error behavior from parent\n")
|
|
default:
|
|
val, err := strconv.ParseBool(*policyIgnoreDirectoryErrors)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
*changeCount++
|
|
|
|
fp.IgnoreDirectoryErrors = &val
|
|
|
|
printStderr(" - setting ignore directory read errors to %v\n", val)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setRetentionPolicyFromFlags(rp *policy.RetentionPolicy, changeCount *int) error {
|
|
cases := []struct {
|
|
desc string
|
|
max **int
|
|
flagValue *string
|
|
}{
|
|
{"number of annual backups to keep", &rp.KeepAnnual, policySetKeepAnnual},
|
|
{"number of monthly backups to keep", &rp.KeepMonthly, policySetKeepMonthly},
|
|
{"number of weekly backups to keep", &rp.KeepWeekly, policySetKeepWeekly},
|
|
{"number of daily backups to keep", &rp.KeepDaily, policySetKeepDaily},
|
|
{"number of hourly backups to keep", &rp.KeepHourly, policySetKeepHourly},
|
|
{"number of latest backups to keep", &rp.KeepLatest, policySetKeepLatest},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
if err := applyPolicyNumber(c.desc, c.max, *c.flagValue, changeCount); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setSchedulingPolicyFromFlags(sp *policy.SchedulingPolicy, changeCount *int) error {
|
|
// It's not really a list, just optional value.
|
|
for _, interval := range *policySetInterval {
|
|
*changeCount++
|
|
|
|
sp.SetInterval(interval)
|
|
printStderr(" - setting snapshot interval to %v\n", sp.Interval())
|
|
|
|
break
|
|
}
|
|
|
|
if len(*policySetTimesOfDay) > 0 {
|
|
var timesOfDay []policy.TimeOfDay
|
|
|
|
for _, tods := range *policySetTimesOfDay {
|
|
for _, tod := range strings.Split(tods, ",") {
|
|
if tod == inheritPolicyString {
|
|
timesOfDay = nil
|
|
break
|
|
}
|
|
|
|
var timeOfDay policy.TimeOfDay
|
|
if err := timeOfDay.Parse(tod); err != nil {
|
|
return errors.Wrap(err, "unable to parse time of day")
|
|
}
|
|
|
|
timesOfDay = append(timesOfDay, timeOfDay)
|
|
}
|
|
}
|
|
*changeCount++
|
|
|
|
sp.TimesOfDay = policy.SortAndDedupeTimesOfDay(timesOfDay)
|
|
|
|
if timesOfDay == nil {
|
|
printStderr(" - resetting snapshot times of day to default\n")
|
|
} else {
|
|
printStderr(" - setting snapshot times to %v\n", timesOfDay)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setCompressionPolicyFromFlags(p *policy.CompressionPolicy, changeCount *int) error {
|
|
if err := applyPolicyNumber64("minimum file size subject to compression", &p.MinSize, *policySetCompressionMinSize, changeCount); err != nil {
|
|
return errors.Wrap(err, "minimum file size subject to compression")
|
|
}
|
|
|
|
if err := applyPolicyNumber64("maximum file size subject to compression", &p.MaxSize, *policySetCompressionMaxSize, changeCount); err != nil {
|
|
return errors.Wrap(err, "maximum file size subject to compression")
|
|
}
|
|
|
|
if v := *policySetCompressionAlgorithm; v != "" {
|
|
*changeCount++
|
|
|
|
if v == inheritPolicyString {
|
|
printStderr(" - resetting compression algorithm to default value inherited from parent\n")
|
|
|
|
p.CompressorName = ""
|
|
} else {
|
|
printStderr(" - setting compression algorithm to %v\n", v)
|
|
|
|
p.CompressorName = compression.Name(v)
|
|
}
|
|
}
|
|
|
|
if *policySetClearOnlyCompress {
|
|
*changeCount++
|
|
|
|
p.OnlyCompress = nil
|
|
|
|
printStderr(" - removing all only-compress extensions\n")
|
|
} else {
|
|
p.OnlyCompress = addRemoveDedupeAndSort("only-compress extensions",
|
|
p.OnlyCompress, *policySetAddOnlyCompress, *policySetRemoveOnlyCompress, changeCount)
|
|
}
|
|
|
|
if *policySetClearNeverCompress {
|
|
*changeCount++
|
|
|
|
p.NeverCompress = nil
|
|
|
|
printStderr(" - removing all never-compress extensions\n")
|
|
} else {
|
|
p.NeverCompress = addRemoveDedupeAndSort("never-compress extensions",
|
|
p.NeverCompress, *policySetAddNeverCompress, *policySetRemoveNeverCompress, changeCount)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func addRemoveDedupeAndSort(desc string, base, add, remove []string, changeCount *int) []string {
|
|
entries := map[string]bool{}
|
|
for _, b := range base {
|
|
entries[b] = true
|
|
}
|
|
|
|
for _, b := range add {
|
|
*changeCount++
|
|
|
|
printStderr(" - adding %v to %v\n", b, desc)
|
|
|
|
entries[b] = true
|
|
}
|
|
|
|
for _, b := range remove {
|
|
*changeCount++
|
|
|
|
printStderr(" - removing %v from %v\n", b, desc)
|
|
delete(entries, b)
|
|
}
|
|
|
|
var s []string
|
|
for k := range entries {
|
|
s = append(s, k)
|
|
}
|
|
|
|
sort.Strings(s)
|
|
|
|
return s
|
|
}
|
|
|
|
func applyPolicyNumber(desc string, val **int, str string, changeCount *int) error {
|
|
if str == "" {
|
|
// not changed
|
|
return nil
|
|
}
|
|
|
|
if str == inheritPolicyString || str == "default" {
|
|
*changeCount++
|
|
|
|
printStderr(" - resetting %v to a default value inherited from parent.\n", desc)
|
|
|
|
*val = nil
|
|
|
|
return nil
|
|
}
|
|
|
|
v, err := strconv.ParseInt(str, 10, 32)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "can't parse the %v %q", desc, str)
|
|
}
|
|
|
|
i := int(v)
|
|
*changeCount++
|
|
|
|
printStderr(" - setting %v to %v.\n", desc, i)
|
|
*val = &i
|
|
|
|
return nil
|
|
}
|
|
|
|
func applyPolicyNumber64(desc string, val *int64, str string, changeCount *int) error {
|
|
if str == "" {
|
|
// not changed
|
|
return nil
|
|
}
|
|
|
|
if str == inheritPolicyString || str == "default" {
|
|
*changeCount++
|
|
|
|
printStderr(" - resetting %v to a default value inherited from parent.\n", desc)
|
|
|
|
*val = 0
|
|
|
|
return nil
|
|
}
|
|
|
|
v, err := strconv.ParseInt(str, 10, 64)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "can't parse the %v %q", desc, str)
|
|
}
|
|
|
|
*changeCount++
|
|
|
|
printStderr(" - setting %v to %v.\n", desc, v)
|
|
*val = v
|
|
|
|
return nil
|
|
}
|
|
|
|
func supportedCompressionAlgorithms() []string {
|
|
var res []string
|
|
for name := range compression.ByName {
|
|
res = append(res, string(name))
|
|
}
|
|
|
|
sort.Strings(res)
|
|
|
|
return append([]string{inheritPolicyString, "none"}, res...)
|
|
}
|