mirror of
https://github.com/kopia/kopia.git
synced 2026-01-25 06:48:48 -05:00
- `repo.Repository` is now read-only and only has methods that can be supported over kopia server
- `repo.RepositoryWriter` has read-write methods that can be supported over kopia server
- `repo.DirectRepository` is read-only and contains all methods of `repo.Repository` plus some low-level methods for data inspection
- `repo.DirectRepositoryWriter` contains write methods for `repo.DirectRepository`
- `repo.Reader` removed and merged with `repo.Repository`
- `repo.Writer` became `repo.RepositoryWriter`
- `*repo.DirectRepository` struct became `repo.DirectRepository`
interface
Getting `{Direct}RepositoryWriter` requires using `NewWriter()` or `NewDirectWriter()` on a read-only repository and multiple simultaneous writers are supported at the same time, each writing to their own indexes and pack blobs.
`repo.Open` returns `repo.Repository` (which is also `repo.RepositoryWriter`).
* content: removed implicit flush on content manager close
* repo: added tests for WriteSession() and implicit flush behavior
* invalidate manifest manager after write session
* cli: disable maintenance in 'kopia server start'
Server will close the repository before completing.
* repo: unconditionally close RepositoryWriter in {Direct,}WriteSession
* repo: added panic in case somebody tries to create RepositoryWriter after closing repository
- used atomic to manage SharedManager.closed
* removed stale example
* linter: fixed spurious failures
Co-authored-by: Julio López <julio+gh@kasten.io>
136 lines
3.5 KiB
Go
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.RepositoryWriter) 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:]
|
|
}
|