finalized CLI to manipulate policies

This commit is contained in:
Jarek Kowalski
2018-06-16 12:17:58 -07:00
parent 70ecebb769
commit 4faf3cd9d0
15 changed files with 338 additions and 110 deletions

View File

@@ -6,7 +6,7 @@
"github.com/kopia/kopia/snapshot"
)
func policyTargets(globalFlag *bool, targetsFlag *[]string) ([]snapshot.SourceInfo, error) {
func policyTargets(pmgr *snapshot.PolicyManager, globalFlag *bool, targetsFlag *[]string) ([]snapshot.SourceInfo, error) {
if *globalFlag == (len(*targetsFlag) > 0) {
return nil, fmt.Errorf("must pass either '--global' or a list of path targets")
}
@@ -19,6 +19,10 @@ func policyTargets(globalFlag *bool, targetsFlag *[]string) ([]snapshot.SourceIn
var res []snapshot.SourceInfo
for _, ts := range *targetsFlag {
if t, err := pmgr.GetPolicyByID(ts); err == nil {
res = append(res, t.Target())
continue
}
target, err := snapshot.ParseSourceInfo(ts, getHostName(), getUserName())
if err != nil {
return nil, err

View File

@@ -3,6 +3,7 @@
import (
"context"
"fmt"
"sort"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/snapshot"
@@ -24,8 +25,12 @@ func listPolicies(ctx context.Context, rep *repo.Repository) error {
return err
}
sort.Slice(policies, func(i, j int) bool {
return policies[i].Target().String() < policies[j].Target().String()
})
for _, pol := range policies {
fmt.Println(pol.Labels)
fmt.Println(pol.ID(), pol.Target())
}
return nil

View File

@@ -22,7 +22,7 @@ func init() {
func removePolicy(ctx context.Context, rep *repo.Repository) error {
mgr := snapshot.NewPolicyManager(rep)
targets, err := policyTargets(policyRemoveGlobal, policyRemoveTargets)
targets, err := policyTargets(mgr, policyRemoveGlobal, policyRemoveTargets)
if err != nil {
return err
}

View File

@@ -3,10 +3,10 @@
import (
"context"
"fmt"
"os"
"sort"
"strconv"
"github.com/rs/zerolog/log"
"strings"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/snapshot"
@@ -18,7 +18,8 @@
policySetGlobal = policySetCommand.Flag("global", "Set global policy").Bool()
// Frequency
policySetFrequency = policySetCommand.Flag("min-duration-between-backups", "Minimum duration between snapshots").DurationList()
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()
@@ -37,6 +38,7 @@
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()
@@ -49,7 +51,7 @@ func init() {
func setPolicy(ctx context.Context, rep *repo.Repository) error {
mgr := snapshot.NewPolicyManager(rep)
targets, err := policyTargets(policySetGlobal, policySetTargets)
targets, err := policyTargets(mgr, policySetGlobal, policySetTargets)
if err != nil {
return err
}
@@ -60,10 +62,17 @@ func setPolicy(ctx context.Context, rep *repo.Repository) error {
p = &snapshot.Policy{}
}
if err := setPolicyFromFlags(target, p); err != nil {
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)
}
@@ -72,61 +81,125 @@ func setPolicy(ctx context.Context, rep *repo.Repository) error {
return nil
}
func setPolicyFromFlags(target snapshot.SourceInfo, p *snapshot.Policy) error {
cases := []struct {
desc string
max **int
flagValue *string
}{
{"number of annual backups to keep", &p.RetentionPolicy.KeepAnnual, policySetKeepAnnual},
{"number of monthly backups to keep", &p.RetentionPolicy.KeepMonthly, policySetKeepMonthly},
{"number of weekly backups to keep", &p.RetentionPolicy.KeepWeekly, policySetKeepWeekly},
{"number of daily backups to keep", &p.RetentionPolicy.KeepDaily, policySetKeepDaily},
{"number of hourly backups to keep", &p.RetentionPolicy.KeepHourly, policySetKeepHourly},
{"number of latest backups to keep", &p.RetentionPolicy.KeepLatest, policySetKeepLatest},
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)
}
for _, c := range cases {
if err := applyPolicyNumber(target, c.desc, c.max, *c.flagValue); err != nil {
return err
}
if err := setFilesPolicyFromFlags(&p.FilesPolicy, changeCount); err != nil {
return fmt.Errorf("files policy: %v", err)
}
// It's not really a list, just optional boolean.
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
break
}
if *policySetClearExclude {
p.FilesPolicy.Exclude = nil
} else {
p.FilesPolicy.Exclude = addRemoveDedupeAndSort(p.FilesPolicy.Exclude, *policySetAddExclude, *policySetRemoveExclude)
}
if *policySetClearInclude {
p.FilesPolicy.Include = nil
} else {
p.FilesPolicy.Include = addRemoveDedupeAndSort(p.FilesPolicy.Include, *policySetAddInclude, *policySetRemoveInclude)
}
// It's not really a list, just optional value.
for _, freq := range *policySetFrequency {
p.SchedulingPolicy.MaxFrequency = &freq
break
}
return nil
}
func addRemoveDedupeAndSort(base, add, remove []string) []string {
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)
}
@@ -138,14 +211,15 @@ func addRemoveDedupeAndSort(base, add, remove []string) []string {
return s
}
func applyPolicyNumber(src snapshot.SourceInfo, desc string, val **int, str string) error {
func applyPolicyNumber(desc string, val **int, str string, changeCount *int) error {
if str == "" {
// not changed
return nil
}
if str == "inherit" || str == "default" {
log.Printf("Resetting %v for %q to a default value inherited from parent.", desc, src)
*changeCount++
fmt.Fprintf(os.Stderr, " - resetting %v to a default value inherited from parent.\n", desc)
*val = nil
return nil
}
@@ -156,7 +230,8 @@ func applyPolicyNumber(src snapshot.SourceInfo, desc string, val **int, str stri
}
i := int(v)
log.Printf("Setting %v on %q to %v.", desc, src, i)
*changeCount++
fmt.Fprintf(os.Stderr, " - setting %v to %v.\n", desc, i)
*val = &i
return nil
}

View File

@@ -1,9 +1,9 @@
package cli
import (
"bytes"
"context"
"fmt"
"io"
"os"
"github.com/kopia/kopia/internal/units"
@@ -12,10 +12,10 @@
)
var (
policyShowCommand = policyCommands.Command("show", "Show snapshot policy.").Alias("get")
policyShowEffective = policyShowCommand.Flag("effective", "Show effective policy").Bool()
policyShowGlobal = policyShowCommand.Flag("global", "Get global policy").Bool()
policyShowTargets = policyShowCommand.Arg("target", "Target to show the policy for").Strings()
policyShowCommand = policyCommands.Command("show", "Show snapshot policy.").Alias("get")
policyShowGlobal = policyShowCommand.Flag("global", "Get global policy").Bool()
policyShowTargets = policyShowCommand.Arg("target", "Target to show the policy for").Strings()
policyShowJSON = policyShowCommand.Flag("json", "Show JSON").Short('j').Bool()
)
func init() {
@@ -25,79 +25,155 @@ func init() {
func showPolicy(ctx context.Context, rep *repo.Repository) error {
pmgr := snapshot.NewPolicyManager(rep)
targets, err := policyTargets(policyShowGlobal, policyShowTargets)
targets, err := policyTargets(pmgr, policyShowGlobal, policyShowTargets)
if err != nil {
return err
}
for _, target := range targets {
var p *snapshot.Policy
var policyKind string
var err error
effective, policies, err := pmgr.GetEffectivePolicy(target)
if err != nil {
return fmt.Errorf("can't get effective policy for %q: %v", target, err)
}
if *policyShowEffective {
p, err = pmgr.GetEffectivePolicy(target)
policyKind = "effective"
if *policyShowJSON {
fmt.Println(effective)
} else {
p, err = pmgr.GetDefinedPolicy(target)
policyKind = "defined"
printPolicy(os.Stdout, effective, policies)
}
if err == nil {
fmt.Printf("The %v policy for %q:\n", policyKind, target)
fmt.Println(policyToString(p))
continue
}
if err == snapshot.ErrPolicyNotFound {
fmt.Fprintf(os.Stderr, "No %v policy for %q, pass --effective to compute effective policy used for backups.\n", policyKind, target)
continue
}
return fmt.Errorf("can't get %v policy for %q: %v", policyKind, target, err)
}
return nil
}
func policyToString(p *snapshot.Policy) string {
var buf bytes.Buffer
func getDefinitionPoint(parents []*snapshot.Policy, match func(p *snapshot.Policy) bool) string {
for i, p := range parents {
if match(p) {
if i == 0 {
return "(defined for this target)"
}
fmt.Fprintf(&buf, "Retention policy:\n")
fmt.Fprintf(&buf, " keep annual:%v monthly:%v weekly:%v daily:%v hourly:%v latest:%v\n",
return "inherited from " + p.Target().String()
}
if p.NoParent {
break
}
}
return "(default)"
}
func containsString(s []string, v string) bool {
for _, item := range s {
if item == v {
return true
}
}
return false
}
func printPolicy(w io.Writer, p *snapshot.Policy, parents []*snapshot.Policy) {
fmt.Fprintf(w, "Policy for %v:\n", p.Target())
printRetentionPolicy(w, p, parents)
fmt.Fprintf(w, "\n")
printFilesPolicy(w, p, parents)
fmt.Fprintf(w, "\n")
printSchedulingPolicy(w, p, parents)
}
func printRetentionPolicy(w io.Writer, p *snapshot.Policy, parents []*snapshot.Policy) {
fmt.Fprintf(w, "Keep:\n")
fmt.Fprintf(w, " Annual snapshots: %3v %v\n",
valueOrNotSet(p.RetentionPolicy.KeepAnnual),
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.RetentionPolicy.KeepAnnual != nil
}))
fmt.Fprintf(w, " Monthly snapshots: %3v %v\n",
valueOrNotSet(p.RetentionPolicy.KeepMonthly),
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.RetentionPolicy.KeepMonthly != nil
}))
fmt.Fprintf(w, " Weekly snapshots: %3v %v\n",
valueOrNotSet(p.RetentionPolicy.KeepWeekly),
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.RetentionPolicy.KeepWeekly != nil
}))
fmt.Fprintf(w, " Daily snapshots: %3v %v\n",
valueOrNotSet(p.RetentionPolicy.KeepDaily),
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.RetentionPolicy.KeepDaily != nil
}))
fmt.Fprintf(w, " Hourly snapshots: %3v %v\n",
valueOrNotSet(p.RetentionPolicy.KeepHourly),
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.RetentionPolicy.KeepHourly != nil
}))
fmt.Fprintf(w, " Latest snapshots: %3v %v\n",
valueOrNotSet(p.RetentionPolicy.KeepLatest),
)
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.RetentionPolicy.KeepLatest != nil
}))
}
fmt.Fprintf(&buf, "Files policy:\n")
func printFilesPolicy(w io.Writer, p *snapshot.Policy, parents []*snapshot.Policy) {
fmt.Fprintf(w, "Files policy:\n")
if len(p.FilesPolicy.Include) == 0 {
fmt.Fprintf(&buf, " Include all files\n")
fmt.Fprintf(w, " Include all files.\n")
} else {
fmt.Fprintf(&buf, " Include only:\n")
fmt.Fprintf(w, " Include only:\n")
}
for _, inc := range p.FilesPolicy.Include {
fmt.Fprintf(&buf, " %v\n", inc)
fmt.Fprintf(w, " %-30v %v\n", inc, getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return containsString(pol.FilesPolicy.Include, inc)
}))
}
if len(p.FilesPolicy.Exclude) > 0 {
fmt.Fprintf(&buf, " Exclude:\n")
}
for _, exc := range p.FilesPolicy.Exclude {
fmt.Fprintf(&buf, " %v\n", exc)
fmt.Fprintf(w, " Exclude:\n")
for _, exc := range p.FilesPolicy.Exclude {
fmt.Fprintf(w, " %-30v %v\n", exc, getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return containsString(pol.FilesPolicy.Exclude, exc)
}))
}
} else {
fmt.Fprintf(w, " No excluded files.\n")
}
if s := p.FilesPolicy.MaxSize; s != nil {
fmt.Fprintf(&buf, " Exclude files above size: %v\n", units.BytesStringBase2(int64(*s)))
fmt.Fprintf(w, " Exclude files above: %10v %v\n",
units.BytesStringBase2(int64(*s)),
getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.FilesPolicy.MaxSize != nil
}))
}
}
func printSchedulingPolicy(w io.Writer, p *snapshot.Policy, parents []*snapshot.Policy) {
if p.SchedulingPolicy.Interval != nil {
fmt.Fprintf(w, "Snapshot interval: %10v %v\n", p.SchedulingPolicy.Interval, getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
return pol.SchedulingPolicy.Interval != nil
}))
}
if len(p.SchedulingPolicy.TimesOfDay) > 0 {
fmt.Fprintf(w, "Snapshot times:\n")
for _, tod := range p.SchedulingPolicy.TimesOfDay {
fmt.Fprintf(w, " %9v %v\n", tod, getDefinitionPoint(parents, func(pol *snapshot.Policy) bool {
for _, t := range pol.SchedulingPolicy.TimesOfDay {
if t == tod {
return true
}
}
return false
}))
}
}
return buf.String()
}
func valueOrNotSet(p *int) string {
if p == nil {
return "(none)"
return "-"
}
return fmt.Sprintf("%v", *p)

View File

@@ -90,7 +90,7 @@ func runBackupCommand(ctx context.Context, rep *repo.Repository) error {
func snapshotSingleSource(ctx context.Context, rep *repo.Repository, mgr *snapshot.Manager, pmgr *snapshot.PolicyManager, u *snapshot.Uploader, sourceInfo snapshot.SourceInfo) error {
t0 := time.Now()
rep.Blocks.ResetStats()
policy, err := pmgr.GetEffectivePolicy(sourceInfo)
policy, _, err := pmgr.GetEffectivePolicy(sourceInfo)
if err != nil {
return fmt.Errorf("unable to get backup policy for source %v: %v", sourceInfo, err)
}

View File

@@ -68,7 +68,7 @@ func runSnapshotEstimateCommand(ctx context.Context, rep *repo.Repository) error
}
sourceInfo := snapshot.SourceInfo{Path: filepath.Clean(path), Host: getHostName(), UserName: getUserName()}
policy, err := pmgr.GetEffectivePolicy(sourceInfo)
policy, _, err := pmgr.GetEffectivePolicy(sourceInfo)
if err != nil {
return fmt.Errorf("unable to get backup policy for source %v: %v", sourceInfo, err)
}

View File

@@ -69,7 +69,7 @@ func expireSnapshots(pmgr *snapshot.PolicyManager, snapshots []*snapshot.Manifes
func expireSnapshotsForSingleSource(pmgr *snapshot.PolicyManager, snapshots []*snapshot.Manifest) ([]string, error) {
src := snapshots[0].Source
pol, err := pmgr.GetEffectivePolicy(src)
pol, _, err := pmgr.GetEffectivePolicy(src)
if err != nil {
return nil, err
}

View File

@@ -98,7 +98,7 @@ func outputManifestGroups(ctx context.Context, manifests []*snapshot.Manifest, r
fmt.Printf("%v%v\n", separator, src)
separator = "\n"
pol, err := polMgr.GetEffectivePolicy(src)
pol, _, err := polMgr.GetEffectivePolicy(src)
if err != nil {
log.Warn().Msgf("unable to determine effective policy for %v", src)
} else {

View File

@@ -8,7 +8,7 @@
type policyListEntry struct {
ID string `json:"id"`
Source snapshot.SourceInfo `json:"source"`
Target snapshot.SourceInfo `json:"target"`
Policy *snapshot.Policy `json:"policy"`
}
@@ -27,13 +27,13 @@ func (s *Server) handlePolicyList(r *http.Request) (interface{}, *apiError) {
}
for _, pol := range policies {
src := pol.Source()
if !sourceMatchesURLFilter(src, r.URL.Query()) {
target := pol.Target()
if !sourceMatchesURLFilter(target, r.URL.Query()) {
continue
}
resp.Policies = append(resp.Policies, &policyListEntry{
ID: pol.ID(),
Source: src,
Target: target,
Policy: pol,
})
}

View File

@@ -43,7 +43,7 @@ func (s *Server) handleSourceSnapshotList(r *http.Request) (interface{}, *apiErr
continue
}
pol, err := s.policyManager.GetEffectivePolicy(first.Source)
pol, _, err := s.policyManager.GetEffectivePolicy(first.Source)
if err == nil {
pol.RetentionPolicy.ComputeRetentionReasons(grp)
}

View File

@@ -69,7 +69,7 @@ func (s *sourceManager) run() {
func (s *sourceManager) refreshStatus() {
log.Info().Msgf("refreshing status for %v", s.src)
pol, err := s.server.policyManager.GetEffectivePolicy(s.src)
pol, _, err := s.server.policyManager.GetEffectivePolicy(s.src)
if err != nil {
s.setStatus("FAILED")
return

View File

@@ -31,11 +31,13 @@ func (p *Policy) String() string {
return buf.String()
}
// ID returns globally unique identifier of the policy.
func (p *Policy) ID() string {
return p.Labels["id"]
}
func (p *Policy) Source() SourceInfo {
// Target returns the SourceInfo describing username, host and path targeted by the policy.
func (p *Policy) Target() SourceInfo {
return SourceInfo{
Host: p.Labels["hostname"],
UserName: p.Labels["username"],

View File

@@ -6,6 +6,7 @@
"github.com/kopia/kopia/manifest"
"github.com/kopia/kopia/repo"
"github.com/rs/zerolog/log"
)
// PolicyManager manages snapshotting policies.
@@ -15,12 +16,14 @@ type PolicyManager struct {
// GetEffectivePolicy calculates effective snapshot policy for a given source by combining the source-specifc policy (if any)
// with parent policies. The source must contain a path.
func (m *PolicyManager) GetEffectivePolicy(si SourceInfo) (*Policy, error) {
// Returns the effective policies and all source policies that contributed to that (most specific first).
func (m *PolicyManager) GetEffectivePolicy(si SourceInfo) (*Policy, []*Policy, error) {
var md []*manifest.EntryMetadata
// Find policies applying to paths all the way up to the root.
for tmp := si; len(si.Path) > 0; {
md = append(md, m.repository.Manifests.Find(labelsForSource(si))...)
manifests := m.repository.Manifests.Find(labelsForSource(tmp))
md = append(md, manifests...)
parentPath := filepath.Dir(tmp.Path)
if parentPath == tmp.Path {
@@ -37,18 +40,24 @@ func (m *PolicyManager) GetEffectivePolicy(si SourceInfo) (*Policy, error) {
md = append(md, m.repository.Manifests.Find(labelsForSource(SourceInfo{Host: si.Host}))...)
// Global policy.
md = append(md, m.repository.Manifests.Find(labelsForSource(GlobalPolicySourceInfo))...)
globalManifests := m.repository.Manifests.Find(labelsForSource(GlobalPolicySourceInfo))
md = append(md, globalManifests...)
var policies []*Policy
for _, em := range md {
p := &Policy{}
if err := m.repository.Manifests.Get(em.ID, &p); err != nil {
return nil, fmt.Errorf("got unexpected error when loading policy item %v: %v", em.ID, err)
return nil, nil, fmt.Errorf("got unexpected error when loading policy item %v: %v", em.ID, err)
}
p.Labels = em.Labels
policies = append(policies, p)
log.Printf("loaded parent policy for %v: %v", si, p.Target())
}
return MergePolicies(policies), nil
merged := MergePolicies(policies)
merged.Labels = labelsForSource(si)
return merged, policies, nil
}
// GetDefinedPolicy returns the policy defined on the provided SourceInfo or ErrPolicyNotFound if not present.
@@ -108,6 +117,18 @@ func (m *PolicyManager) RemovePolicy(si SourceInfo) error {
return nil
}
// GetPolicyByID gets the policy for a given unique ID or ErrPolicyNotFound if not found.
func (m *PolicyManager) GetPolicyByID(id string) (*Policy, error) {
p := &Policy{}
if err := m.repository.Manifests.Get(id, &p); err != nil {
if err == manifest.ErrNotFound {
return nil, ErrPolicyNotFound
}
}
return p, nil
}
// ListPolicies returns a list of all policies.
func (m *PolicyManager) ListPolicies() ([]*Policy, error) {
ids := m.repository.Manifests.Find(map[string]string{

View File

@@ -1,16 +1,61 @@
package snapshot
import "time"
import (
"fmt"
"sort"
"time"
)
// TimeOfDay represents the time of day (hh:mm) using 24-hour time format.
type TimeOfDay struct {
Hour int `json:"hour"`
Minute int `json:"min"`
}
// Parse parses the time of day.
func (t *TimeOfDay) Parse(s string) error {
if _, err := fmt.Sscanf(s, "%v:%02v", &t.Hour, &t.Minute); err != nil {
return fmt.Errorf("invalid time of day, must be HH:MM")
}
if t.Hour < 0 || t.Hour > 23 {
return fmt.Errorf("invalid hour %q, must be between 0 and 23", s)
}
if t.Minute < 0 || t.Minute > 59 {
return fmt.Errorf("invalid minute %q, must be between 0 and 59", s)
}
return nil
}
// TimeOfDay returns string representation of time of day.
func (t TimeOfDay) String() string {
return fmt.Sprintf("%v:%02v", t.Hour, t.Minute)
}
// SortAndDedupeTimesOfDay sorts the slice of times of day and removes duplicates.
func SortAndDedupeTimesOfDay(tod []TimeOfDay) []TimeOfDay {
sort.Slice(tod, func(i, j int) bool {
if a, b := tod[i].Hour, tod[j].Hour; a != b {
return a < b
}
return tod[i].Minute < tod[j].Minute
})
return tod
}
// SchedulingPolicy describes policy for scheduling snapshots.
type SchedulingPolicy struct {
MaxFrequency *time.Duration `json:"frequency"`
Interval *time.Duration `json:"interval"`
TimesOfDay []TimeOfDay `json:"timeOfDay"`
}
func mergeSchedulingPolicy(dst, src *SchedulingPolicy) {
if dst.MaxFrequency == nil {
dst.MaxFrequency = src.MaxFrequency
if dst.Interval == nil {
dst.Interval = src.Interval
}
dst.TimesOfDay = SortAndDedupeTimesOfDay(
append(append([]TimeOfDay(nil), src.TimesOfDay...), dst.TimesOfDay...))
}
var defaultSchedulingPolicy = &SchedulingPolicy{}