From 65bc50de5db12b8196af0c8d73ff7cd7a78d053f Mon Sep 17 00:00:00 2001 From: Jarek Kowalski Date: Wed, 23 Nov 2016 23:49:29 -0800 Subject: [PATCH] changed how paths are interpreted for 'kopia backups' and 'kopia expire' --- cmd/kopia/command_backup.go | 25 +++++++++++++------------ cmd/kopia/command_backups.go | 29 ++++++++++++----------------- cmd/kopia/command_expire.go | 31 ++++++++++++++----------------- fs/repofs/snapshot.go | 31 ++++++++++++++++++++++++++++++- 4 files changed, 69 insertions(+), 47 deletions(-) diff --git a/cmd/kopia/command_backup.go b/cmd/kopia/command_backup.go index 395bf94a1..b2fd5d59e 100644 --- a/cmd/kopia/command_backup.go +++ b/cmd/kopia/command_backup.go @@ -79,8 +79,8 @@ func runBackupCommand(c *kingpin.ParseContext) error { sourceInfo := repofs.SnapshotSourceInfo{ Path: filepath.Clean(dir), - Host: getBackupHostName(), - UserName: getBackupUser(), + Host: getHostNameOrDefault(*backupHostName), + UserName: getUserOrDefault(*backupUser), } if len(*backupDescription) > backupMaxDescriptionLength { @@ -142,8 +142,8 @@ func runBackupCommand(c *kingpin.ParseContext) error { } func getLocalBackupPaths(vlt *vault.Vault) ([]string, error) { - u := getBackupUser() - h := getBackupHostName() + u := getHostNameOrDefault(*backupHostName) + h := getUserOrDefault(*backupUser) log.Printf("Looking for previous backups of '%v@%v'...", u, h) backupItems, err := vlt.List("B") if err != nil { @@ -179,19 +179,15 @@ func hashObjectID(oid string) string { return hex.EncodeToString(sum[0:foldLen]) } -func getBackupUser() string { - return getUserOrDefault(*backupUser) -} - -func getBackupHostName() string { - return getHostNameOrDefault(*backupHostName) -} - func getUserOrDefault(userName string) string { if userName != "" { return userName } + return getUserName() +} + +func getUserName() string { currentUser, err := user.Current() if err != nil { log.Fatalf("Cannot determine current user: %s", err) @@ -213,6 +209,10 @@ func getHostNameOrDefault(hostName string) string { return hostName } + return getHostName() +} + +func getHostName() string { hostname, err := os.Hostname() if err != nil { log.Fatalf("Unable to determine hostname: %s", err) @@ -223,6 +223,7 @@ func getHostNameOrDefault(hostName string) string { return hostname } + func init() { backupCommand.Action(runBackupCommand) } diff --git a/cmd/kopia/command_backups.go b/cmd/kopia/command_backups.go index 24dd6a72e..04402f501 100644 --- a/cmd/kopia/command_backups.go +++ b/cmd/kopia/command_backups.go @@ -19,16 +19,10 @@ maxResultsPerPath = backupsCommand.Flag("maxresults", "Maximum number of results.").Default("100").Int() ) -func findBackups(vlt *vault.Vault, path string) ([]string, string, error) { +func findBackups(vlt *vault.Vault, sourceInfo repofs.SnapshotSourceInfo) ([]string, string, error) { var relPath string - for len(path) > 0 { - sourceInfo := repofs.SnapshotSourceInfo{ - Path: path, - Host: getBackupHostName(), - UserName: getBackupUser(), - } - + for len(sourceInfo.Path) > 0 { prefix := sourceInfo.HashString() + "." list, err := vlt.List("B" + prefix) @@ -41,18 +35,18 @@ func findBackups(vlt *vault.Vault, path string) ([]string, string, error) { } if len(relPath) > 0 { - relPath = filepath.Base(path) + "/" + relPath + relPath = filepath.Base(sourceInfo.Path) + "/" + relPath } else { - relPath = filepath.Base(path) + relPath = filepath.Base(sourceInfo.Path) } log.Printf("No backups of %v@%v:%v", sourceInfo.UserName, sourceInfo.Host, sourceInfo.Path) - parent := filepath.Dir(path) - if parent == path { + parentPath := filepath.Dir(sourceInfo.Path) + if parentPath == sourceInfo.Path { break } - path = parent + sourceInfo.Path = parentPath } return nil, "", nil @@ -67,12 +61,15 @@ func runBackupsCommand(context *kingpin.ParseContext) error { var err error if *backupsPath != "" { - path, err := filepath.Abs(*backupsPath) + si, err := repofs.ParseSourceSnashotInfo( + *backupsPath, + getHostName(), + getUserName()) if err != nil { return fmt.Errorf("invalid directory: '%s': %s", *backupsPath, err) } - previous, relPath, err = findBackups(conn.Vault, filepath.Clean(path)) + previous, relPath, err = findBackups(conn.Vault, si) if relPath != "" { relPath = "/" + relPath } @@ -165,6 +162,4 @@ func loadBackupManifests(vlt *vault.Vault, names []string) []*repofs.Snapshot { func init() { backupsCommand.Action(runBackupsCommand) - backupsCommand.Flag("host", "Override backup hostname.").StringVar(backupHostName) - backupsCommand.Flag("user", "Override backup user.").StringVar(backupUser) } diff --git a/cmd/kopia/command_expire.go b/cmd/kopia/command_expire.go index ed71f0da6..b6f1212f9 100644 --- a/cmd/kopia/command_expire.go +++ b/cmd/kopia/command_expire.go @@ -4,7 +4,6 @@ "fmt" "log" "os" - "path/filepath" "strings" "time" @@ -18,9 +17,9 @@ expireCommand = app.Command("expire", "Remove old backups.") expirationPolicies = map[string]func(){ - "KEEP_ALL": expirationPolicyKeepAll, - "MANUAL": expirationPolicyManual, - "DEFAULT": expirationPolicyDefault, + "keep-all": expirationPolicyKeepAll, + "manual": expirationPolicyManual, + "default": expirationPolicyDefault, } expireKeepLatest = expireCommand.Flag("keep-latest", "Number of most recent backups to keep per source").Int() @@ -29,9 +28,10 @@ expireKeepWeekly = expireCommand.Flag("keep-weekly", "Number of most-recent weekly backups to keep per source").Int() expireKeepMonthly = expireCommand.Flag("keep-monthly", "Number of most-recent monthly backups to keep per source").Int() expireKeepAnnual = expireCommand.Flag("keep-annual", "Number of most-recent annual backups to keep per source").Int() - expirePolicy = expireCommand.Flag("policy", "Expiration policy to use: "+strings.Join(expirationPolicyNames(), ",")).Default("DEFAULT").Enum(expirationPolicyNames()...) + expirePolicy = expireCommand.Flag("policy", "Expiration policy to use: "+strings.Join(expirationPolicyNames(), ",")).Required().Enum(expirationPolicyNames()...) expireHost = expireCommand.Flag("host", "Expire backups from a given host").Default("").String() expireUser = expireCommand.Flag("user", "Expire backups from a given user").Default("").String() + expireAll = expireCommand.Flag("all", "Expire all backups").Bool() expirePaths = expireCommand.Arg("path", "Expire backups for a given paths only").Strings() expireDelete = expireCommand.Flag("delete", "Whether to actually delete backups").Default("no").String() @@ -149,28 +149,23 @@ func expire(snapshots []*repofs.Snapshot, snapshotNames []string) []string { } func getSnapshotNamesToExpire(v *vault.Vault) ([]string, error) { - if len(*expirePaths) == 0 { + if !*expireAll && len(*expirePaths) == 0 { + return nil, fmt.Errorf("Must specify paths to expire or --all") + } + + if *expireAll { fmt.Fprintf(os.Stderr, "Scanning all active snapshots...\n") return v.List("B") } var result []string - hostName := getHostNameOrDefault(*expireHost) - user := getUserOrDefault(*expireUser) - for _, p := range *expirePaths { - path, err := filepath.Abs(p) + si, err := repofs.ParseSourceSnashotInfo(p, *expireHost, *expireUser) if err != nil { - return nil, fmt.Errorf("invalid directory: '%s': %s", p, err) + return nil, fmt.Errorf("unable to parse %v: %v", p, err) } - var si repofs.SnapshotSourceInfo - - si.Host = hostName - si.UserName = user - si.Path = filepath.Clean(path) - log.Printf("Looking for backups of %v", si) matches, err := v.List("B" + si.HashString()) @@ -178,6 +173,8 @@ func getSnapshotNamesToExpire(v *vault.Vault) ([]string, error) { return nil, fmt.Errorf("error listing backups for %v: %v", si, err) } + log.Printf("Found %v backups of %v", len(matches), si) + result = append(result, matches...) } diff --git a/fs/repofs/snapshot.go b/fs/repofs/snapshot.go index 81a3271af..ec7dfaf11 100644 --- a/fs/repofs/snapshot.go +++ b/fs/repofs/snapshot.go @@ -5,6 +5,8 @@ "encoding/hex" "fmt" "io" + "path/filepath" + "strings" "time" "github.com/kopia/kopia/repo" @@ -20,7 +22,34 @@ type SnapshotSourceInfo struct { } func (ssi SnapshotSourceInfo) String() string { - return fmt.Sprintf("%v@%v : %v", ssi.UserName, ssi.Host, ssi.Path) + return fmt.Sprintf("%v@%v:%v", ssi.UserName, ssi.Host, ssi.Path) +} + +// ParseSourceSnashotInfo parses a given path in the context of given hostname and username and returns +// SnapshotSourceInfo. 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 ParseSourceSnashotInfo(path string, hostname string, username string) (SnapshotSourceInfo, error) { + p1 := strings.Index(path, "@") + p2 := strings.Index(path, ":") + + if p1 > 0 && p2 > 0 && p1 < p2 && p2 < len(path) { + return SnapshotSourceInfo{ + UserName: path[0:p1], + Host: path[p1+1 : p2], + Path: path[p2+1:], + }, nil + } + + absPath, err := filepath.Abs(path) + if err != nil { + return SnapshotSourceInfo{}, fmt.Errorf("invalid directory: '%s': %s", path, err) + } + + return SnapshotSourceInfo{ + Host: hostname, + UserName: username, + Path: filepath.Clean(absPath), + }, nil } // HashString generates hash of SnapshotSourceInfo.