mirror of
https://github.com/kopia/kopia.git
synced 2026-05-19 12:14:45 -04:00
policies work in progress
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
)
|
||||
|
||||
var (
|
||||
lsCommand = app.Command("ls", "List a directory stored in repository object.").Alias("list")
|
||||
lsCommand = app.Command("list", "List a directory stored in repository object.").Alias("ls")
|
||||
|
||||
lsCommandLong = lsCommand.Flag("long", "Long output").Short('l').Bool()
|
||||
lsCommandPath = lsCommand.Arg("path", "Path").Required().String()
|
||||
|
||||
5
cmd/kopia/command_policy.go
Normal file
5
cmd/kopia/command_policy.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
var (
|
||||
policyCommands = app.Command("policy", "Commands to manipulate snapshotting policies.")
|
||||
)
|
||||
32
cmd/kopia/command_policy_ls.go
Normal file
32
cmd/kopia/command_policy_ls.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kopia/kopia/snapshot"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
policyListCommand = policyCommands.Command("list", "List policies.").Alias("ls")
|
||||
)
|
||||
|
||||
func init() {
|
||||
policyListCommand.Action(listPolicies)
|
||||
}
|
||||
|
||||
func listPolicies(context *kingpin.ParseContext) error {
|
||||
conn := mustOpenConnection()
|
||||
mgr := snapshot.NewManager(conn)
|
||||
|
||||
entries, err := mgr.ListPolicies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, e := range entries {
|
||||
fmt.Println(e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
48
cmd/kopia/command_policy_set.go
Normal file
48
cmd/kopia/command_policy_set.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/kopia/kopia/snapshot"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
policySetCommand = policyCommands.Command("set", "Set snapshot policy for a single directory, user@host or a global policy.")
|
||||
policySetTarget = policySetCommand.Flag("target", "Target of a policy ('global','user@host','@host') or a path").Required().String()
|
||||
|
||||
// Frequency
|
||||
policySetFrequency = policySetCommand.Flag("min-duration-between-backups", "Minimum duration between snapshots").Duration()
|
||||
|
||||
// Expiration policies.
|
||||
policySetKeepLatest = policySetCommand.Flag("keep-latest", "Number of most recent backups to keep per source").Int()
|
||||
policySetKeepHourly = policySetCommand.Flag("keep-hourly", "Number of most-recent hourly backups to keep per source").Int()
|
||||
policySetKeepDaily = policySetCommand.Flag("keep-daily", "Number of most-recent daily backups to keep per source").Int()
|
||||
policySetKeepWeekly = policySetCommand.Flag("keep-weekly", "Number of most-recent weekly backups to keep per source").Int()
|
||||
policySetKeepMonthly = policySetCommand.Flag("keep-monthly", "Number of most-recent monthly backups to keep per source").Int()
|
||||
policySetKeepAnnual = policySetCommand.Flag("keep-annual", "Number of most-recent annual backups to keep per source").Int()
|
||||
|
||||
// Files to ignore.
|
||||
policySetAddIgnore = policySetCommand.Flag("add-ignore", "List of paths to add to ignore list").Strings()
|
||||
policySetRemoveIgnore = policySetCommand.Flag("remove-ignore", "List of paths to remove from ignore list").Strings()
|
||||
policySetReplaceIgnore = policySetCommand.Flag("set-ignore", "List of paths to replace ignore list with").Strings()
|
||||
)
|
||||
|
||||
func init() {
|
||||
policySetCommand.Action(setPolicy)
|
||||
}
|
||||
|
||||
func setPolicy(context *kingpin.ParseContext) error {
|
||||
conn := mustOpenConnection()
|
||||
mgr := snapshot.NewManager(conn)
|
||||
_ = mgr
|
||||
|
||||
target, err := snapshot.ParseSourceInfo(*policySetTarget, getHostName(), getUserName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("target: %v", target)
|
||||
|
||||
return nil
|
||||
}
|
||||
32
cmd/kopia/command_policy_show.go
Normal file
32
cmd/kopia/command_policy_show.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kopia/kopia/snapshot"
|
||||
kingpin "gopkg.in/alecthomas/kingpin.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
policyShowCommand = policyCommands.Command("show", "Show snapshot policy.")
|
||||
)
|
||||
|
||||
func init() {
|
||||
policyShowCommand.Action(showPolicy)
|
||||
}
|
||||
|
||||
func showPolicy(context *kingpin.ParseContext) error {
|
||||
conn := mustOpenConnection()
|
||||
mgr := snapshot.NewManager(conn)
|
||||
|
||||
entries, err := mgr.ListPolicies()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, e := range entries {
|
||||
fmt.Println(e)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
)
|
||||
|
||||
var (
|
||||
vaultListCommand = vaultCommands.Command("ls", "List contents of a vault").Alias("list")
|
||||
vaultListCommand = vaultCommands.Command("list", "List contents of a vault").Alias("ls")
|
||||
vaultListPrefix = vaultListCommand.Flag("prefix", "Prefix").String()
|
||||
)
|
||||
|
||||
|
||||
@@ -8,12 +8,18 @@
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/kopia/kopia"
|
||||
"github.com/kopia/kopia/vault"
|
||||
)
|
||||
|
||||
const sourcePrefix = "S"
|
||||
const backupPrefix = "B"
|
||||
const policyPrefix = "P"
|
||||
|
||||
// ErrPolicyNotFound is returned when the policy is not found.
|
||||
var ErrPolicyNotFound = errors.New("policy not found")
|
||||
|
||||
// Manager manages filesystem snapshots.
|
||||
type Manager struct {
|
||||
@@ -134,6 +140,96 @@ func (m *Manager) ListSnapshotManifests(src *SourceInfo, limit int) ([]string, e
|
||||
return m.vault.List(backupPrefix+prefix, limit)
|
||||
}
|
||||
|
||||
// GetPolicy loads snapshot policy for a given source, optionally fall back to default.
|
||||
func (m *Manager) GetPolicy(src *SourceInfo, fallback bool) (*Policy, error) {
|
||||
if p, err := m.getRawPolicy(src); err != ErrPolicyNotFound {
|
||||
return p, err
|
||||
}
|
||||
|
||||
if !fallback {
|
||||
return nil, ErrPolicyNotFound
|
||||
}
|
||||
|
||||
if src.Path != "" {
|
||||
userHostDefault := *src
|
||||
userHostDefault.Path = ""
|
||||
|
||||
if p, err := m.getRawPolicy(&userHostDefault); err != ErrPolicyNotFound {
|
||||
return p, nil
|
||||
}
|
||||
}
|
||||
|
||||
return m.getRawPolicy(&SourceInfo{"", "", ""})
|
||||
}
|
||||
|
||||
// SavePolicy persists the given snapshot policy.
|
||||
func (m *Manager) SavePolicy(p *Policy) error {
|
||||
itemID := fmt.Sprintf("%v%v", policyPrefix, p.Source.HashString())
|
||||
|
||||
b, err := json.Marshal(p)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal policy to JSON: %v", err)
|
||||
}
|
||||
|
||||
return m.vault.Put(itemID, b)
|
||||
}
|
||||
|
||||
func (m *Manager) getRawPolicy(src *SourceInfo) (*Policy, error) {
|
||||
itemID := fmt.Sprintf("%v%v", policyPrefix, src.HashString())
|
||||
|
||||
return m.getPolicyItem(itemID)
|
||||
}
|
||||
|
||||
func (m *Manager) getPolicyItem(itemID string) (*Policy, error) {
|
||||
b, err := m.vault.Get(itemID)
|
||||
if err == vault.ErrItemNotFound {
|
||||
return nil, ErrPolicyNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var s Policy
|
||||
if err := json.Unmarshal(b, &s); err != nil {
|
||||
return nil, fmt.Errorf("invalid policy: %v", err)
|
||||
}
|
||||
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
// ListPolicies returns a list of all policies stored in a vault.
|
||||
func (m *Manager) ListPolicies() ([]*Policy, error) {
|
||||
names, err := m.vault.List(policyPrefix, -1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*Policy, len(names))
|
||||
sem := make(chan bool, 50)
|
||||
|
||||
for i, n := range names {
|
||||
sem <- true
|
||||
go func(i int, n string) {
|
||||
defer func() { <-sem }()
|
||||
|
||||
p, err := m.getPolicyItem(n)
|
||||
if err != nil {
|
||||
log.Printf("WARNING: Unable to parse policy %v: %v", n, err)
|
||||
return
|
||||
}
|
||||
result[i] = p
|
||||
}(i, n)
|
||||
}
|
||||
|
||||
for i := 0; i < cap(sem); i++ {
|
||||
sem <- true
|
||||
}
|
||||
close(sem)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// NewManager creates new snapshot manager for a given connection.
|
||||
func NewManager(conn *kopia.Connection) *Manager {
|
||||
return &Manager{conn.Vault}
|
||||
|
||||
10
snapshot/policy.go
Normal file
10
snapshot/policy.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package snapshot
|
||||
|
||||
// Expiration describes snapshot expiration policy.
|
||||
type Expiration struct {
|
||||
}
|
||||
|
||||
// Policy describes snapshot policy for a single source.
|
||||
type Policy struct {
|
||||
Source *SourceInfo
|
||||
}
|
||||
@@ -26,6 +26,10 @@ func (ssi SourceInfo) String() string {
|
||||
// SourceInfo. The path may be bare (in which case it's interpreted as local path and canonicalized)
|
||||
// or may be 'username@host:path' where path, username and host are not processed.
|
||||
func ParseSourceInfo(path string, hostname string, username string) (SourceInfo, error) {
|
||||
if path == "(global)" {
|
||||
return SourceInfo{}, nil
|
||||
}
|
||||
|
||||
p1 := strings.Index(path, "@")
|
||||
p2 := strings.Index(path, ":")
|
||||
|
||||
@@ -37,6 +41,18 @@ func ParseSourceInfo(path string, hostname string, username string) (SourceInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if p1 >= 0 && p2 < 0 {
|
||||
if p1+1 < len(path) {
|
||||
// support @host and user@host without path
|
||||
return SourceInfo{
|
||||
UserName: path[0:p1],
|
||||
Host: path[p1+1:],
|
||||
}, nil
|
||||
}
|
||||
|
||||
return SourceInfo{}, fmt.Errorf("invalid hostname in %q", path)
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
return SourceInfo{}, fmt.Errorf("invalid directory: '%s': %s", path, err)
|
||||
|
||||
Reference in New Issue
Block a user