restore and verify report an unknown snapshot ID as not found (closes #426)
check / check (push) Successful in 9m36s

A snapshot ID that names no snapshot in the local index or on the
destination store made restore and verify fail on a missing object at
a hashed path, and verify printed a header for it first. Resolving a
snapshot ID now uses the look-up `snapshot remove` already does, so
all three commands give the same error, and verify prints its header
only after the snapshot is found.

Model: opus-5-5
This commit is contained in:
clawbot committed 2026-11-11 02:23:47 +00:00
1 parent 098ed80990
commit 5682918723
5 files changed
+113 -50

No files matched your search

+23 -23
View File
@@ -969,25 +969,22 @@ func (v *Vaultik) VerifySnapshotWithOptions(
Mode: "shallow",
}
v.printVerifyHeader(snapshotID, opts)
// Resolve the identifier to the snapshot's remote key. A human ID is
// hashed; a remote key (or its abbreviation, as printed for a
// remote-only snapshot) is used as-is, so a host with no local index
// can verify a snapshot it can only see on the store. The key is kept
// so we can also check for the snapshot's encrypted database below.
// The key is kept so we can also check for the snapshot's encrypted
// database below.
remoteKey, err := v.resolveSnapshotRemoteKey(snapshotID)
if err != nil {
if opts.JSON {
result.Status = verifyStatusFailed
result.ErrorMessage = fmt.Sprintf("resolving snapshot identifier: %v", err)
result.ErrorMessage = err.Error()
return v.outputVerifyJSON(result)
}
return fmt.Errorf("resolving snapshot identifier: %w", err)
return err
}
v.printVerifyHeader(snapshotID, opts)
manifest, err := v.downloadManifestByKey(remoteKey)
if err != nil {
if opts.JSON {
@@ -1480,7 +1477,7 @@ func (v *Vaultik) RemoveSnapshot(
return result, err
}
inLocalIndex, err := v.findSnapshotToRemove(snapshotID, opts)
inLocalIndex, err := v.checkSnapshotExists(snapshotID, opts.LocalOnly)
if err != nil {
return result, err
}
@@ -1543,17 +1540,20 @@ func (v *Vaultik) RemoveSnapshot(
return result, nil
}
// findSnapshotToRemove reports whether the local index has a row for
// snapshotID, and returns errSnapshotNotFound when there is nothing to
// remove: no row and, unless LocalOnly is set, no metadata on the
// destination store either. The destination store is consulted only
// when the local index has no row, so a snapshot in the local index can
// still be removed from it while the destination store is unreachable,
// and only after checkDestinationMarker passes, so an unplugged volume's
// empty mount point is not taken for a store without the snapshot.
func (v *Vaultik) findSnapshotToRemove(
snapshotID string, opts *RemoveOptions,
) (bool, error) {
// checkSnapshotExists returns errSnapshotNotFound when the local index
// has no row for snapshotID and, unless localOnly is set, the
// destination store has no metadata for it either. inLocalIndex reports
// whether the local index has that row. The destination store is
// consulted only when the local index has no row, so a snapshot in the
// local index can still be removed from it while the destination store
// is unreachable, and only after checkDestinationMarker passes, so an
// unplugged volume's empty mount point is not taken for a store without
// the snapshot.
//
//nolint:nonamedreturns // the result name says what the bool means
func (v *Vaultik) checkSnapshotExists(
snapshotID string, localOnly bool,
) (inLocalIndex bool, err error) {
if v.Repositories != nil {
snap, err := v.Repositories.Snapshots.GetByID(v.ctx, snapshotID)
if err != nil {
@@ -1565,12 +1565,12 @@ func (v *Vaultik) findSnapshotToRemove(
}
}
if opts.LocalOnly {
if localOnly {
return false, fmt.Errorf("%w in the local index: %s",
errSnapshotNotFound, snapshotID)
}
err := v.checkDestinationMarker()
err = v.checkDestinationMarker()
if err != nil {
return false, err
}