2
2
mirror of https://github.com/octoleo/restic.git synced 2024-11-10 23:31:09 +00:00
restic/internal/restic/snapshot_find.go
2022-10-15 13:33:48 +02:00

169 lines
4.1 KiB
Go

package restic
import (
"context"
"fmt"
"path/filepath"
"time"
"github.com/restic/restic/internal/errors"
)
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
var ErrNoSnapshotFound = errors.New("no snapshot found")
// findLatestSnapshot finds latest snapshot with optional target/directory, tags, hostname, and timestamp filters.
func findLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked,
hosts []string, tags []TagList, paths []string, timeStampLimit *time.Time) (ID, error) {
var err error
absTargets := make([]string, 0, len(paths))
for _, target := range paths {
if !filepath.IsAbs(target) {
target, err = filepath.Abs(target)
if err != nil {
return ID{}, errors.Wrap(err, "Abs")
}
}
absTargets = append(absTargets, filepath.Clean(target))
}
var (
latest time.Time
latestID ID
found bool
)
err = ForAllSnapshots(ctx, be, loader, nil, func(id ID, snapshot *Snapshot, err error) error {
if err != nil {
return errors.Errorf("Error loading snapshot %v: %v", id.Str(), err)
}
if timeStampLimit != nil && snapshot.Time.After(*timeStampLimit) {
return nil
}
if snapshot.Time.Before(latest) {
return nil
}
if !snapshot.HasHostname(hosts) {
return nil
}
if !snapshot.HasTagList(tags) {
return nil
}
if !snapshot.HasPaths(absTargets) {
return nil
}
latest = snapshot.Time
latestID = id
found = true
return nil
})
if err != nil {
return ID{}, err
}
if !found {
return ID{}, ErrNoSnapshotFound
}
return latestID, nil
}
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
// the string as closely as possible.
func FindSnapshot(ctx context.Context, be Lister, s string) (ID, error) {
// find snapshot id with prefix
name, err := Find(ctx, be, SnapshotFile, s)
if err != nil {
return ID{}, err
}
return ParseID(name)
}
func FindFilteredSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string, timeStampLimit *time.Time, snapshotID string) (ID, error) {
if snapshotID == "latest" {
id, err := findLatestSnapshot(ctx, be, loader, hosts, tags, paths, timeStampLimit)
if err == ErrNoSnapshotFound {
err = fmt.Errorf("snapshot filter (Paths:%v Tags:%v Hosts:%v): %w", paths, tags, hosts, err)
}
return id, err
} else {
id, err := FindSnapshot(ctx, be, snapshotID)
if err != nil {
return ID{}, err
}
return id, err
}
}
type SnapshotFindCb func(string, *Snapshot, error) error
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
func FindFilteredSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string, snapshotIDs []string, fn SnapshotFindCb) error {
if len(snapshotIDs) != 0 {
var err error
usedFilter := false
ids := NewIDSet()
// Process all snapshot IDs given as arguments.
for _, s := range snapshotIDs {
var id ID
if s == "latest" {
if usedFilter {
continue
}
usedFilter = true
id, err = findLatestSnapshot(ctx, be, loader, hosts, tags, paths, nil)
if err == ErrNoSnapshotFound {
err = errors.Errorf("no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)", paths, tags, hosts)
}
} else {
id, err = FindSnapshot(ctx, be, s)
}
var sn *Snapshot
if ids.Has(id) {
continue
} else if !id.IsNull() {
ids.Insert(id)
sn, err = LoadSnapshot(ctx, loader, id)
s = id.String()
}
err = fn(s, sn, err)
if err != nil {
return err
}
}
// Give the user some indication their filters are not used.
if !usedFilter && (len(hosts) != 0 || len(tags) != 0 || len(paths) != 0) {
return fn("filters", nil, errors.Errorf("explicit snapshot ids are given"))
}
return nil
}
return ForAllSnapshots(ctx, be, loader, nil, func(id ID, sn *Snapshot, err error) error {
if err != nil {
return fn(id.String(), sn, err)
}
if !sn.HasHostname(hosts) || !sn.HasTagList(tags) || !sn.HasPaths(paths) {
return nil
}
return fn(id.String(), sn, err)
})
}