Files
kopia/cli/command_policy_edit.go
Julio Lopez bb20d9e11a chore(ci): enable wsl_v5:{assign,expr} linter settings (#4982)
Enable wsl_v5 settings:
- assign
- expr
2025-11-12 23:12:06 -08:00

142 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"]
# "ignoreDotFiles": [".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 */
# "timeOfDay": [{"hour":H,"min":M},{"hour":H,"min":M}]
# "manual": false /* Only create snapshots manually if set to true. NOTE: cannot be used with the above two fields */
`
type commandPolicyEdit struct {
policyTargetFlags
out textOutput
}
func (c *commandPolicyEdit) setup(svc appServices, parent commandParent) {
cmd := parent.Command("edit", "Edit policy.")
c.policyTargetFlags.setup(cmd)
cmd.Action(svc.repositoryWriterAction(c.run))
c.out.setup(svc)
}
func (c *commandPolicyEdit) run(ctx context.Context, rep repo.RepositoryWriter) error {
targets, err := c.policyTargets(ctx, rep)
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 := fmt.Sprintf(policyEditHelpText, target) + 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, true, 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))
c.out.printStdout("Save updated policy? (y/N) ")
var shouldSave string
fmt.Scanf("%v", &shouldSave) //nolint:errcheck
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 *policy.Policy) string {
var b bytes.Buffer
e := json.NewEncoder(&b)
e.SetIndent("", " ")
e.Encode(v) //nolint:errcheck,errchkjson
return b.String()
}
func jsonEqual(v1, v2 *policy.Policy) 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:]
}