Files
kopia/cli/command_policy_set_scheduling.go
Jarek Kowalski cb98abbc2c feat(snapshots): added ability to use cron expressions to schedule snapshots (#3149)
* feat(snapshots): added ability to use cron expressions to schedule snapshots

We use `github.com/hashicorp/cronexpr` to parse and evaluate expressions,
as documented in https://github.com/hashicorp/cronexpr#implementation

* upgrade ui

* pr feedback
2023-07-19 19:26:17 -07:00

166 lines
4.2 KiB
Go

package cli
import (
"context"
"strings"
"time"
"github.com/alecthomas/kingpin/v2"
"github.com/pkg/errors"
"github.com/kopia/kopia/snapshot/policy"
)
type policySchedulingFlags struct {
policySetInterval []time.Duration // not a list, just optional duration
policySetTimesOfDay []string
policySetCron string
policySetManual bool
}
func (c *policySchedulingFlags) setup(cmd *kingpin.CmdClause) {
cmd.Flag("snapshot-interval", "Interval between snapshots").DurationListVar(&c.policySetInterval)
cmd.Flag("snapshot-time", "Comma-separated times of day when to take snapshot (HH:mm,HH:mm,...) or 'inherit' to remove override").StringsVar(&c.policySetTimesOfDay)
cmd.Flag("snapshot-time-crontab", "Semicolon-separated crontab-compatible expressions (or 'inherit')").StringVar(&c.policySetCron)
cmd.Flag("manual", "Only create snapshots manually").BoolVar(&c.policySetManual)
}
func (c *policySchedulingFlags) setSchedulingPolicyFromFlags(ctx context.Context, sp *policy.SchedulingPolicy, changeCount *int) error {
if c.policySetManual {
return c.setManualFromFlags(ctx, sp, changeCount)
}
return c.setScheduleFromFlags(ctx, sp, changeCount)
}
func (c *policySchedulingFlags) setScheduleFromFlags(ctx context.Context, sp *policy.SchedulingPolicy, changeCount *int) error {
// It's not really a list, just optional value.
for _, interval := range c.policySetInterval {
*changeCount++
sp.SetInterval(interval)
log(ctx).Infof(" - setting snapshot interval to %v", sp.Interval())
break
}
if len(c.policySetTimesOfDay) > 0 {
var timesOfDay []policy.TimeOfDay
for _, tods := range c.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 {
log(ctx).Infof(" - resetting snapshot times of day to default")
} else {
log(ctx).Infof(" - setting snapshot times to %v", timesOfDay)
}
}
if c.policySetCron != "" {
ce := splitCronExpressions(c.policySetCron)
if ce == nil {
log(ctx).Infof(" - resetting cron snapshot times to default")
} else {
log(ctx).Infof(" - setting cron snapshot times to %v", ce)
}
*changeCount++
sp.Cron = ce
if err := policy.ValidateSchedulingPolicy(*sp); err != nil {
return errors.Wrap(err, "invalid scheduling policy")
}
}
if sp.Manual {
*changeCount++
sp.Manual = false
log(ctx).Infof(" - resetting manual snapshot field to false\n")
}
return nil
}
// splitCronExpressions splits the provided string into a list of cron expressions.
// Individual items are separated by semi-colons. As a special case, the string "inherit"
// returns a nil slice.
func splitCronExpressions(expr string) []string {
if expr == inheritPolicyString || expr == defaultPolicyString {
return nil
}
var result []string
parts := strings.Split(expr, ";")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
result = append(result, part)
}
return result
}
func (c *policySchedulingFlags) setManualFromFlags(ctx context.Context, sp *policy.SchedulingPolicy, changeCount *int) error {
// Cannot set both schedule and manual setting
if len(c.policySetInterval) > 0 || len(c.policySetTimesOfDay) > 0 || len(c.policySetCron) > 0 {
return errors.New("cannot set manual field when scheduling snapshots")
}
// Reset the existing policy schedule, if present
if sp.IntervalSeconds != 0 {
*changeCount++
sp.IntervalSeconds = 0
log(ctx).Infof(" - resetting snapshot interval to default\n")
}
if len(sp.TimesOfDay) > 0 {
*changeCount++
sp.TimesOfDay = nil
log(ctx).Infof(" - resetting snapshot times of day to default\n")
}
if len(sp.Cron) > 0 {
*changeCount++
sp.Cron = nil
log(ctx).Infof(" - resetting cron snapshot times to default\n")
}
*changeCount++
sp.Manual = c.policySetManual
log(ctx).Infof(" - setting manual snapshot field to %v\n", c.policySetManual)
return nil
}