mirror of
https://github.com/kopia/kopia.git
synced 2026-01-24 06:18:02 -05:00
238 lines
7.9 KiB
Go
238 lines
7.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/kopia/kopia/repo"
|
|
"github.com/kopia/kopia/snapshot"
|
|
)
|
|
|
|
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 include (by default everything).
|
|
policySetAddInclude = policySetCommand.Flag("add-include", "List of paths to add to the include list").PlaceHolder("PATTERN").Strings()
|
|
policySetRemoveInclude = policySetCommand.Flag("remove-include", "List of paths to remove from the include list").PlaceHolder("PATTERN").Strings()
|
|
policySetClearInclude = policySetCommand.Flag("clear-include", "Clear list of paths in the include list").Bool()
|
|
|
|
// Files to exclude.
|
|
policySetAddExclude = policySetCommand.Flag("add-exclude", "List of paths to add to the exclude list").PlaceHolder("PATTERN").Strings()
|
|
policySetRemoveExclude = policySetCommand.Flag("remove-exclude", "List of paths to remove from the exclude list").PlaceHolder("PATTERN").Strings()
|
|
policySetClearExclude = policySetCommand.Flag("clear-exclude", "Clear list of paths in the exclude list").Bool()
|
|
policySetMaxFileSize = policySetCommand.Flag("max-file-size", "Exclude files above given size").PlaceHolder("N").String()
|
|
|
|
// General policy.
|
|
policySetInherit = policySetCommand.Flag("inherit", "Enable or disable inheriting policies from the parent").BoolList()
|
|
)
|
|
|
|
func init() {
|
|
policySetCommand.Action(repositoryAction(setPolicy))
|
|
}
|
|
|
|
func setPolicy(ctx context.Context, rep *repo.Repository) error {
|
|
mgr := snapshot.NewPolicyManager(rep)
|
|
|
|
targets, err := policyTargets(mgr, policySetGlobal, policySetTargets)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, target := range targets {
|
|
p, err := mgr.GetDefinedPolicy(target)
|
|
if err == snapshot.ErrPolicyNotFound {
|
|
p = &snapshot.Policy{}
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "Setting policy for %v\n", target)
|
|
changeCount := 0
|
|
|
|
if err := setPolicyFromFlags(target, p, &changeCount); err != nil {
|
|
return err
|
|
}
|
|
|
|
if changeCount == 0 {
|
|
return fmt.Errorf("no changes specified")
|
|
}
|
|
|
|
if err := mgr.SetPolicy(target, p); err != nil {
|
|
return fmt.Errorf("can't save policy for %v: %v", target, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setPolicyFromFlags(target snapshot.SourceInfo, p *snapshot.Policy, changeCount *int) error {
|
|
if err := setRetentionPolicyFromFlags(&p.RetentionPolicy, changeCount); err != nil {
|
|
return fmt.Errorf("retention policy: %v", err)
|
|
}
|
|
|
|
if err := setFilesPolicyFromFlags(&p.FilesPolicy, changeCount); err != nil {
|
|
return fmt.Errorf("files policy: %v", err)
|
|
}
|
|
|
|
if err := setSchedulingPolicyFromFlags(&p.SchedulingPolicy, changeCount); err != nil {
|
|
return fmt.Errorf("scheduling policy: %v", err)
|
|
}
|
|
|
|
if err := applyPolicyNumber("maximum file size", &p.FilesPolicy.MaxSize, *policySetMaxFileSize, changeCount); err != nil {
|
|
return fmt.Errorf("maximum file size: %v", err)
|
|
}
|
|
|
|
// 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 *snapshot.FilesPolicy, changeCount *int) error {
|
|
if *policySetClearExclude {
|
|
*changeCount++
|
|
fmt.Fprintf(os.Stderr, " - removing all rules for exclude files\n")
|
|
fp.Exclude = nil
|
|
} else {
|
|
fp.Exclude = addRemoveDedupeAndSort("excluded files", fp.Exclude, *policySetAddExclude, *policySetRemoveExclude, changeCount)
|
|
}
|
|
if *policySetClearInclude {
|
|
*changeCount++
|
|
fp.Include = nil
|
|
fmt.Fprintf(os.Stderr, " - removing all rules for include files\n")
|
|
} else {
|
|
fp.Include = addRemoveDedupeAndSort("included files", fp.Include, *policySetAddInclude, *policySetRemoveInclude, changeCount)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func setRetentionPolicyFromFlags(rp *snapshot.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 *snapshot.SchedulingPolicy, changeCount *int) error {
|
|
// It's not really a list, just optional value.
|
|
for _, interval := range *policySetInterval {
|
|
*changeCount++
|
|
sp.Interval = &interval
|
|
fmt.Fprintf(os.Stderr, " - setting snapshot interval to %v\n", sp.Interval)
|
|
break
|
|
}
|
|
|
|
if len(*policySetTimesOfDay) > 0 {
|
|
var timesOfDay []snapshot.TimeOfDay
|
|
|
|
for _, tods := range *policySetTimesOfDay {
|
|
for _, tod := range strings.Split(tods, ",") {
|
|
if tod == "inherit" {
|
|
timesOfDay = nil
|
|
break
|
|
}
|
|
|
|
var timeOfDay snapshot.TimeOfDay
|
|
if err := timeOfDay.Parse(tod); err != nil {
|
|
return fmt.Errorf("unable to parse time of day: %v", err)
|
|
}
|
|
timesOfDay = append(timesOfDay, timeOfDay)
|
|
}
|
|
}
|
|
*changeCount++
|
|
|
|
sp.TimesOfDay = snapshot.SortAndDedupeTimesOfDay(timesOfDay)
|
|
|
|
if timesOfDay == nil {
|
|
fmt.Fprintf(os.Stderr, " - resetting snapshot times of day to default\n")
|
|
} else {
|
|
fmt.Fprintf(os.Stderr, " - setting snapshot times to %v\n", timesOfDay)
|
|
}
|
|
}
|
|
|
|
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++
|
|
fmt.Fprintf(os.Stderr, " - adding %v to %v\n", b, desc)
|
|
entries[b] = true
|
|
}
|
|
for _, b := range remove {
|
|
*changeCount++
|
|
fmt.Fprintf(os.Stderr, " - 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 == "inherit" || str == "default" {
|
|
*changeCount++
|
|
fmt.Fprintf(os.Stderr, " - 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 fmt.Errorf("can't parse the %v %q: %v", desc, str, err)
|
|
}
|
|
|
|
i := int(v)
|
|
*changeCount++
|
|
fmt.Fprintf(os.Stderr, " - setting %v to %v.\n", desc, i)
|
|
*val = &i
|
|
return nil
|
|
}
|