2017-06-18 13:06:52 +02:00
|
|
|
package restic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2022-10-03 14:27:11 +02:00
|
|
|
"fmt"
|
2018-03-03 18:02:47 +01:00
|
|
|
"path/filepath"
|
2023-05-18 23:15:38 +02:00
|
|
|
"strings"
|
2017-06-18 13:06:52 +02:00
|
|
|
"time"
|
2017-07-23 14:21:03 +02:00
|
|
|
|
|
|
|
"github.com/restic/restic/internal/errors"
|
2017-06-18 13:06:52 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
|
|
|
|
var ErrNoSnapshotFound = errors.New("no snapshot found")
|
|
|
|
|
2023-02-17 16:13:46 +01:00
|
|
|
// A SnapshotFilter denotes a set of snapshots based on hosts, tags and paths.
|
|
|
|
type SnapshotFilter struct {
|
|
|
|
_ struct{} // Force naming fields in literals.
|
|
|
|
|
|
|
|
Hosts []string
|
|
|
|
Tags TagLists
|
|
|
|
Paths []string
|
|
|
|
// Match snapshots from before this timestamp. Zero for no limit.
|
|
|
|
TimestampLimit time.Time
|
|
|
|
}
|
|
|
|
|
2024-04-14 22:44:32 +02:00
|
|
|
func (f *SnapshotFilter) Empty() bool {
|
2023-02-17 16:13:46 +01:00
|
|
|
return len(f.Hosts)+len(f.Tags)+len(f.Paths) == 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *SnapshotFilter) matches(sn *Snapshot) bool {
|
|
|
|
return sn.HasHostname(f.Hosts) && sn.HasTagList(f.Tags) && sn.HasPaths(f.Paths)
|
|
|
|
}
|
|
|
|
|
|
|
|
// findLatest finds the latest snapshot with optional target/directory,
|
|
|
|
// tags, hostname, and timestamp filters.
|
|
|
|
func (f *SnapshotFilter) findLatest(ctx context.Context, be Lister, loader LoaderUnpacked) (*Snapshot, error) {
|
2021-11-06 01:14:24 +01:00
|
|
|
|
2018-03-03 18:02:47 +01:00
|
|
|
var err error
|
2023-02-17 16:13:46 +01:00
|
|
|
absTargets := make([]string, 0, len(f.Paths))
|
|
|
|
for _, target := range f.Paths {
|
2018-03-03 18:02:47 +01:00
|
|
|
if !filepath.IsAbs(target) {
|
|
|
|
target, err = filepath.Abs(target)
|
|
|
|
if err != nil {
|
2022-10-03 14:48:14 +02:00
|
|
|
return nil, errors.Wrap(err, "Abs")
|
2018-03-03 18:02:47 +01:00
|
|
|
}
|
|
|
|
}
|
2018-04-30 22:03:11 +02:00
|
|
|
absTargets = append(absTargets, filepath.Clean(target))
|
2018-03-03 18:02:47 +01:00
|
|
|
}
|
2023-04-14 21:53:55 +02:00
|
|
|
f.Paths = absTargets
|
2018-03-03 18:02:47 +01:00
|
|
|
|
2022-10-03 14:48:14 +02:00
|
|
|
var latest *Snapshot
|
2017-06-18 13:06:52 +02:00
|
|
|
|
2021-11-06 01:14:24 +01:00
|
|
|
err = ForAllSnapshots(ctx, be, loader, nil, func(id ID, snapshot *Snapshot, err error) error {
|
2017-06-18 13:06:52 +02:00
|
|
|
if err != nil {
|
2020-11-28 08:59:12 +01:00
|
|
|
return errors.Errorf("Error loading snapshot %v: %v", id.Str(), err)
|
2017-06-18 13:06:52 +02:00
|
|
|
}
|
2020-02-26 22:17:59 +01:00
|
|
|
|
2023-02-17 16:13:46 +01:00
|
|
|
if !f.TimestampLimit.IsZero() && snapshot.Time.After(f.TimestampLimit) {
|
2022-01-04 07:03:53 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-10-03 14:48:14 +02:00
|
|
|
if latest != nil && snapshot.Time.Before(latest.Time) {
|
2020-02-26 22:17:59 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-02-17 16:13:46 +01:00
|
|
|
if !f.matches(snapshot) {
|
2018-01-21 17:25:36 +01:00
|
|
|
return nil
|
2017-07-09 09:47:41 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 14:48:14 +02:00
|
|
|
latest = snapshot
|
2018-01-21 17:25:36 +01:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
2022-10-03 14:48:14 +02:00
|
|
|
return nil, err
|
2017-06-18 13:06:52 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 14:48:14 +02:00
|
|
|
if latest == nil {
|
|
|
|
return nil, ErrNoSnapshotFound
|
2017-06-18 13:06:52 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 14:48:14 +02:00
|
|
|
return latest, nil
|
2017-06-18 13:06:52 +02:00
|
|
|
}
|
|
|
|
|
2023-07-16 16:50:54 +02:00
|
|
|
func splitSnapshotID(s string) (id, subfolder string) {
|
|
|
|
id, subfolder, _ = strings.Cut(s, ":")
|
2023-05-18 23:15:38 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-06-18 13:06:52 +02:00
|
|
|
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
|
|
|
|
// the string as closely as possible.
|
2023-05-18 23:15:38 +02:00
|
|
|
func FindSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, s string) (*Snapshot, string, error) {
|
2023-07-16 16:50:54 +02:00
|
|
|
s, subfolder := splitSnapshotID(s)
|
2023-05-18 23:15:38 +02:00
|
|
|
|
2022-10-03 14:51:00 +02:00
|
|
|
// no need to list snapshots if `s` is already a full id
|
|
|
|
id, err := ParseID(s)
|
2017-06-18 13:06:52 +02:00
|
|
|
if err != nil {
|
2022-10-03 14:51:00 +02:00
|
|
|
// find snapshot id with prefix
|
2022-10-15 16:00:05 +02:00
|
|
|
id, err = Find(ctx, be, SnapshotFile, s)
|
2022-10-03 14:51:00 +02:00
|
|
|
if err != nil {
|
2023-05-18 23:15:38 +02:00
|
|
|
return nil, "", err
|
2022-10-03 14:51:00 +02:00
|
|
|
}
|
2022-10-03 14:48:14 +02:00
|
|
|
}
|
2023-05-18 23:15:38 +02:00
|
|
|
sn, err := LoadSnapshot(ctx, loader, id)
|
2023-07-16 16:50:54 +02:00
|
|
|
return sn, subfolder, err
|
2017-06-18 13:06:52 +02:00
|
|
|
}
|
2017-06-18 13:18:12 +02:00
|
|
|
|
2023-02-17 16:13:46 +01:00
|
|
|
// FindLatest returns either the latest of a filtered list of all snapshots
|
|
|
|
// or a snapshot specified by `snapshotID`.
|
2023-05-18 23:15:38 +02:00
|
|
|
func (f *SnapshotFilter) FindLatest(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotID string) (*Snapshot, string, error) {
|
2023-07-16 16:50:54 +02:00
|
|
|
id, subfolder := splitSnapshotID(snapshotID)
|
2023-05-18 23:15:38 +02:00
|
|
|
if id == "latest" {
|
2023-02-17 16:13:46 +01:00
|
|
|
sn, err := f.findLatest(ctx, be, loader)
|
2022-10-03 14:16:33 +02:00
|
|
|
if err == ErrNoSnapshotFound {
|
2023-02-17 16:13:46 +01:00
|
|
|
err = fmt.Errorf("snapshot filter (Paths:%v Tags:%v Hosts:%v): %w",
|
|
|
|
f.Paths, f.Tags, f.Hosts, err)
|
2022-10-03 14:16:33 +02:00
|
|
|
}
|
2023-07-16 16:50:54 +02:00
|
|
|
return sn, subfolder, err
|
2022-10-03 14:16:33 +02:00
|
|
|
}
|
2022-10-03 14:48:14 +02:00
|
|
|
return FindSnapshot(ctx, be, loader, snapshotID)
|
2022-10-03 14:16:33 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 13:51:41 +02:00
|
|
|
type SnapshotFindCb func(string, *Snapshot, error) error
|
|
|
|
|
2023-07-16 16:50:54 +02:00
|
|
|
var ErrInvalidSnapshotSyntax = errors.New("<snapshot>:<subfolder> syntax not allowed")
|
2023-07-16 14:57:12 +02:00
|
|
|
|
2023-02-17 16:13:46 +01:00
|
|
|
// FindAll yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
|
|
|
func (f *SnapshotFilter) FindAll(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotIDs []string, fn SnapshotFindCb) error {
|
2022-10-03 13:51:41 +02:00
|
|
|
if len(snapshotIDs) != 0 {
|
|
|
|
var err error
|
|
|
|
usedFilter := false
|
|
|
|
|
|
|
|
ids := NewIDSet()
|
|
|
|
// Process all snapshot IDs given as arguments.
|
|
|
|
for _, s := range snapshotIDs {
|
2022-10-03 14:48:14 +02:00
|
|
|
var sn *Snapshot
|
2022-10-03 13:51:41 +02:00
|
|
|
if s == "latest" {
|
|
|
|
if usedFilter {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
usedFilter = true
|
|
|
|
|
2023-02-17 16:13:46 +01:00
|
|
|
sn, err = f.findLatest(ctx, be, loader)
|
2022-10-03 13:51:41 +02:00
|
|
|
if err == ErrNoSnapshotFound {
|
2023-02-17 16:13:46 +01:00
|
|
|
err = errors.Errorf("no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)",
|
|
|
|
f.Paths, f.Tags, f.Hosts)
|
2022-10-03 13:51:41 +02:00
|
|
|
}
|
2022-10-03 14:48:14 +02:00
|
|
|
if sn != nil {
|
|
|
|
ids.Insert(*sn.ID())
|
|
|
|
}
|
2023-07-16 14:57:12 +02:00
|
|
|
} else if strings.HasPrefix(s, "latest:") {
|
|
|
|
err = ErrInvalidSnapshotSyntax
|
2022-10-03 13:51:41 +02:00
|
|
|
} else {
|
2023-07-16 16:50:54 +02:00
|
|
|
var subfolder string
|
|
|
|
sn, subfolder, err = FindSnapshot(ctx, be, loader, s)
|
|
|
|
if err == nil && subfolder != "" {
|
2023-07-16 14:57:12 +02:00
|
|
|
err = ErrInvalidSnapshotSyntax
|
2023-05-18 23:15:38 +02:00
|
|
|
} else if err == nil {
|
2022-10-03 14:48:14 +02:00
|
|
|
if ids.Has(*sn.ID()) {
|
|
|
|
continue
|
|
|
|
}
|
2023-05-18 18:08:43 +02:00
|
|
|
|
|
|
|
ids.Insert(*sn.ID())
|
|
|
|
s = sn.ID().String()
|
2022-10-03 14:48:14 +02:00
|
|
|
}
|
2022-10-03 13:51:41 +02:00
|
|
|
}
|
|
|
|
err = fn(s, sn, err)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Give the user some indication their filters are not used.
|
2024-04-14 22:44:32 +02:00
|
|
|
if !usedFilter && !f.Empty() {
|
2022-10-03 13:51:41 +02:00
|
|
|
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 {
|
2023-02-17 16:13:46 +01:00
|
|
|
if err == nil && !f.matches(sn) {
|
2018-01-21 17:25:36 +01:00
|
|
|
return nil
|
2017-06-18 13:18:12 +02:00
|
|
|
}
|
|
|
|
|
2022-10-03 13:51:41 +02:00
|
|
|
return fn(id.String(), sn, err)
|
2018-01-21 17:25:36 +01:00
|
|
|
})
|
2017-06-18 13:18:12 +02:00
|
|
|
}
|