Files
kopia/cli/command_policy_edit.go
Jarek Kowalski 5e8e175cfa repo: refactored read/write methods of repo.Repository (#749)
Reader methods go to repo.Reader and write methods go to repo.Writer
Switched usage to new interfaces based on linter errors.
2021-01-04 21:33:12 -08:00

136 lines
3.5 KiB
Go

package cli
import (
"bytes"
"context"
"encoding/json"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/editor"
"github.com/kopia/kopia/repo"
"github.com/kopia/kopia/snapshot/policy"
)
const policyEditHelpText = `
# Editing policy for '%v'
# Make changes to the policy, save your file and exit the editor.
# The output must be valid JSON.
# Lines starting with # are comments and automatically removed.
`
const policyEditRetentionHelpText = ` # Retention for snapshots of this directory. Options include:
# "keepLatest": number
# "keepDaily": number
# "keepHourly": number
# "keepWeekly": number
# "keepMonthly": number
# "keepAnnual": number
`
const policyEditFilesHelpText = `
# Which files to include in snapshots. Options include:
# "ignore": ["*.ext", "*.ext2"]
# "dotIgnoreFiles": [".gitignore", ".kopiaignore"]
# "maxFileSize": number
# "noParentDotFiles": true
# "noParentIgnore": true
# "oneFileSystem": false
`
const policyEditSchedulingHelpText = `
# Snapshot scheduling options. Options include:
# "intervalSeconds": number /* 86400-day, 3600-hour, 60-minute */
# "timesOfDay": [{"hour":H,"min":M},{"hour":H,"min":M}]
`
var (
policyEditCommand = policyCommands.Command("edit", "Set snapshot policy for a single directory, user@host or a global policy.")
policyEditTargets = policyEditCommand.Arg("target", "Target of a policy ('global','user@host','@host') or a path").Strings()
policyEditGlobal = policyEditCommand.Flag("global", "Set global policy").Bool()
)
func init() {
policyEditCommand.Action(repositoryWriterAction(editPolicy))
}
func editPolicy(ctx context.Context, rep repo.Writer) error {
targets, err := policyTargets(ctx, rep, policyEditGlobal, policyEditTargets)
if err != nil {
return err
}
for _, target := range targets {
original, err := policy.GetDefinedPolicy(ctx, rep, target)
if errors.Is(err, policy.ErrPolicyNotFound) {
original = &policy.Policy{}
}
log(ctx).Infof("Editing policy for %v using external editor...", target)
s := policyEditHelpText + prettyJSON(original)
s = insertHelpText(s, ` "retention": {`, policyEditRetentionHelpText)
s = insertHelpText(s, ` "files": {`, policyEditFilesHelpText)
s = insertHelpText(s, ` "scheduling": {`, policyEditSchedulingHelpText)
var updated *policy.Policy
if err := editor.EditLoop(ctx, "policy.conf", s, func(edited string) error {
updated = &policy.Policy{}
d := json.NewDecoder(bytes.NewBufferString(edited))
d.DisallowUnknownFields()
return d.Decode(updated)
}); err != nil {
return errors.Wrap(err, "unable to launch editor")
}
if jsonEqual(updated, original) {
log(ctx).Infof("Policy for %v unchanged", target)
continue
}
log(ctx).Infof("Updated policy for %v\n%v", target, prettyJSON(updated))
fmt.Print("Save updated policy? (y/N) ")
var shouldSave string
fmt.Scanf("%v", &shouldSave)
if strings.HasPrefix(strings.ToLower(shouldSave), "y") {
if err := policy.SetPolicy(ctx, rep, target, updated); err != nil {
return errors.Wrapf(err, "can't save policy for %v", target)
}
}
}
return nil
}
func prettyJSON(v interface{}) string {
var b bytes.Buffer
e := json.NewEncoder(&b)
e.SetIndent("", " ")
e.Encode(v) //nolint:errcheck
return b.String()
}
func jsonEqual(v1, v2 interface{}) bool {
return prettyJSON(v1) == prettyJSON(v2)
}
func insertHelpText(s, lookFor, help string) string {
p := strings.Index(s, lookFor)
if p < 0 {
return s
}
return s[0:p] + help + s[p:]
}