Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d9f1e5e78d |
@@ -360,16 +360,20 @@ snapshot from the local index and strips the snapshot's metadata from
|
|||||||
the backup destination store. Blobs are NOT touched — deleting blobs
|
the backup destination store. Blobs are NOT touched — deleting blobs
|
||||||
requires reading every remaining remote manifest (the destination store
|
requires reading every remaining remote manifest (the destination store
|
||||||
may hold snapshots this host doesn't know about), which is what
|
may hold snapshots this host doesn't know about), which is what
|
||||||
`vaultik prune` does. On success the command prints the exact `vaultik
|
`vaultik prune` does. The command prints the `vaultik prune` invocation
|
||||||
prune` invocation to run as a follow-up. An ID that names no snapshot in
|
to run as a follow-up only when it removed the snapshot's metadata from
|
||||||
the local index or on the destination store is an error; with
|
the destination store, and never under `--json`. An ID that names no
|
||||||
`--local-only`, the snapshot must be in the local index. Local row
|
snapshot in the local index or on the destination store is an error;
|
||||||
|
with `--local-only`, the snapshot must be in the local index. Local row
|
||||||
cleanup (files, chunks, blobs the snapshot was the last referrer for)
|
cleanup (files, chunks, blobs the snapshot was the last referrer for)
|
||||||
runs automatically. If the destination store is unreachable, the local-DB
|
runs automatically. If removing the snapshot's metadata from the
|
||||||
removal still completes and a warning is emitted; run `vaultik snapshot
|
destination store fails (for example because the store is unreachable),
|
||||||
remove <snapshot-id>` again once the store is reachable to remove the
|
the local-DB removal still completes and a warning is emitted; run
|
||||||
snapshot's metadata from it (`vaultik prune` does not). To wipe everything
|
`vaultik snapshot remove <snapshot-id>` again once the store is reachable
|
||||||
on the destination in one go, use `vaultik remote nuke --force`.
|
to remove the snapshot's metadata from it (`vaultik prune` does not). For
|
||||||
|
a snapshot that is only on the destination store, that failure is an
|
||||||
|
error. To wipe everything on the destination in one go, use `vaultik
|
||||||
|
remote nuke --force`.
|
||||||
* `--local-only`: Skip remote cleanup; only touch the local index
|
* `--local-only`: Skip remote cleanup; only touch the local index
|
||||||
* `--dry-run`: Show what would be deleted without deleting
|
* `--dry-run`: Show what would be deleted without deleting
|
||||||
* `--force`: Skip confirmation prompt
|
* `--force`: Skip confirmation prompt
|
||||||
|
|||||||
@@ -270,8 +270,9 @@ By default, this removes the snapshot from the local index database and
|
|||||||
strips the snapshot's metadata from the backup destination store. Blobs
|
strips the snapshot's metadata from the backup destination store. Blobs
|
||||||
are NOT touched: deleting them requires reading every remaining remote
|
are NOT touched: deleting them requires reading every remaining remote
|
||||||
manifest (the destination store may hold snapshots this host doesn't
|
manifest (the destination store may hold snapshots this host doesn't
|
||||||
know about), which is what 'vaultik prune' does. On success the command
|
know about), which is what 'vaultik prune' does. The command prints the
|
||||||
prints the exact 'vaultik prune' invocation to run as a follow-up.
|
'vaultik prune' invocation to run as a follow-up only when it removed the
|
||||||
|
snapshot's metadata from the destination store, and never under --json.
|
||||||
|
|
||||||
An ID that names no snapshot in the local index or on the destination
|
An ID that names no snapshot in the local index or on the destination
|
||||||
store is an error.
|
store is an error.
|
||||||
@@ -280,10 +281,12 @@ Use --local-only to skip the remote half (e.g. when you want to forget a
|
|||||||
snapshot locally without touching the destination store); the snapshot
|
snapshot locally without touching the destination store); the snapshot
|
||||||
must then be in the local index.
|
must then be in the local index.
|
||||||
|
|
||||||
If the remote is unreachable, the local-database removal still completes
|
If removing the snapshot's metadata from the destination store fails (for
|
||||||
and a warning is emitted; run 'vaultik snapshot remove <snapshot-id>' again
|
example because the remote is unreachable), the local-database removal
|
||||||
once the destination store is reachable to remove the snapshot's metadata
|
still completes and a warning is emitted; run 'vaultik snapshot remove
|
||||||
from it ('vaultik prune' does not).
|
<snapshot-id>' again once the destination store is reachable to remove
|
||||||
|
the snapshot's metadata from it ('vaultik prune' does not). For a
|
||||||
|
snapshot that is only on the destination store, that failure is an error.
|
||||||
|
|
||||||
To wipe the entire destination store and start over, use 'vaultik remote
|
To wipe the entire destination store and start over, use 'vaultik remote
|
||||||
nuke --force' — it is the single supported entry point for that.`,
|
nuke --force' — it is the single supported entry point for that.`,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"sneak.berlin/go/vaultik/internal/log"
|
"sneak.berlin/go/vaultik/internal/log"
|
||||||
"sneak.berlin/go/vaultik/internal/snapshot"
|
"sneak.berlin/go/vaultik/internal/snapshot"
|
||||||
"sneak.berlin/go/vaultik/internal/storage"
|
"sneak.berlin/go/vaultik/internal/storage"
|
||||||
|
"sneak.berlin/go/vaultik/internal/storage/faultstore"
|
||||||
"sneak.berlin/go/vaultik/internal/types"
|
"sneak.berlin/go/vaultik/internal/types"
|
||||||
"sneak.berlin/go/vaultik/internal/vaultik"
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
||||||
)
|
)
|
||||||
@@ -431,6 +432,40 @@ func TestRemoveSnapshot_NotInLocalIndex(t *testing.T) {
|
|||||||
assert.Contains(t, out, "Removed snapshot metadata from remote storage")
|
assert.Contains(t, out, "Removed snapshot metadata from remote storage")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestRemoveSnapshot_NotInLocalIndexDeleteFails checks that removing a
|
||||||
|
// snapshot only the destination store holds fails when its metadata
|
||||||
|
// cannot be deleted there, under --force and under --json: nothing was
|
||||||
|
// removed, so the command must not succeed.
|
||||||
|
func TestRemoveSnapshot_NotInLocalIndexDeleteFails(t *testing.T) {
|
||||||
|
log.Initialize(log.Config{})
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for name, opts := range map[string]vaultik.RemoveOptions{
|
||||||
|
"force": {Force: true},
|
||||||
|
"json": {JSON: true},
|
||||||
|
} {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
store := newTestStorer()
|
||||||
|
addManifest(t, store, "snapshot-001", nil)
|
||||||
|
|
||||||
|
faulty := faultstore.New(store)
|
||||||
|
faulty.OnDelete = func(string) bool { return true }
|
||||||
|
|
||||||
|
tv := vaultik.NewForTesting(faulty)
|
||||||
|
tv.Repositories = newLocalIndex(t)
|
||||||
|
|
||||||
|
result, err := tv.RemoveSnapshot("snapshot-001", &opts)
|
||||||
|
|
||||||
|
require.ErrorIs(t, err, faultstore.ErrInjectedDelete)
|
||||||
|
assert.False(t, result.RemoteRemoved)
|
||||||
|
assert.True(t, store.hasKey(remoteKeyPath("snapshot-001")))
|
||||||
|
assert.Empty(t, tv.Stdout.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRemoveAllSnapshots_RequiresForce(t *testing.T) {
|
func TestRemoveAllSnapshots_RequiresForce(t *testing.T) {
|
||||||
log.Initialize(log.Config{})
|
log.Initialize(log.Config{})
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|||||||
@@ -1219,9 +1219,11 @@ const snapshotRemoveCommandHint = "vaultik snapshot remove"
|
|||||||
// destination store. Blobs are NOT touched: removing a snapshot's
|
// destination store. Blobs are NOT touched: removing a snapshot's
|
||||||
// blobs requires reading every remaining remote manifest (the remote
|
// blobs requires reading every remaining remote manifest (the remote
|
||||||
// may hold snapshots this host doesn't know about), which is what
|
// may hold snapshots this host doesn't know about), which is what
|
||||||
// `vaultik prune` is for — the command prints the prune invocation to
|
// `vaultik prune` is for — when metadata was removed from the
|
||||||
// run as a follow-up. When the remote is unreachable the command still
|
// destination store, the text output names the prune invocation to run
|
||||||
// completes the local-DB removal and warns.
|
// as a follow-up. When removing the metadata fails, a snapshot in the
|
||||||
|
// local index is still removed from it, with a warning; a snapshot
|
||||||
|
// only on the destination store is an error.
|
||||||
func (v *Vaultik) RemoveSnapshot(
|
func (v *Vaultik) RemoveSnapshot(
|
||||||
snapshotID string, opts *RemoveOptions,
|
snapshotID string, opts *RemoveOptions,
|
||||||
) (*RemoveResult, error) {
|
) (*RemoveResult, error) {
|
||||||
@@ -1256,8 +1258,10 @@ func (v *Vaultik) RemoveSnapshot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.LocalOnly {
|
result.RemoteRemoved, err = v.removeSnapshotRemote(
|
||||||
result.RemoteRemoved = v.removeSnapshotRemote(snapshotID, opts)
|
snapshotID, inLocalIndex, opts)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.SnapshotManager != nil {
|
if v.SnapshotManager != nil {
|
||||||
@@ -1379,19 +1383,33 @@ func (v *Vaultik) confirmRemoveSnapshot(snapshotID string, opts *RemoveOptions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// removeSnapshotRemote strips the snapshot's metadata from the
|
// removeSnapshotRemote strips the snapshot's metadata from the
|
||||||
// destination store, warning and proceeding on failure: the local-DB
|
// destination store. It returns true only when the delete finished
|
||||||
// removal has already happened, so the user is told the remote half
|
// without an error and removed at least one object; under LocalOnly it
|
||||||
// didn't finish and to run `vaultik snapshot remove` for the snapshot
|
// does nothing. When the snapshot is only on the destination store, a
|
||||||
// again once the destination store is reachable (`vaultik prune` never
|
// failure is returned as an error, because that removal is all the
|
||||||
// removes snapshot metadata). Returns true when the remote removal
|
// command does. When the snapshot was in the local
|
||||||
// succeeded and deleted something.
|
// index, its removal there has already happened, so a failure is only a
|
||||||
func (v *Vaultik) removeSnapshotRemote(snapshotID string, opts *RemoveOptions) bool {
|
// warning telling the user to run `vaultik snapshot remove` for the
|
||||||
|
// snapshot again once the destination store is reachable (`vaultik
|
||||||
|
// prune` never removes snapshot metadata).
|
||||||
|
func (v *Vaultik) removeSnapshotRemote(
|
||||||
|
snapshotID string, inLocalIndex bool, opts *RemoveOptions,
|
||||||
|
) (bool, error) {
|
||||||
|
if opts.LocalOnly {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
log.Info("Removing snapshot metadata from remote storage",
|
log.Info("Removing snapshot metadata from remote storage",
|
||||||
"snapshot_id", snapshotID)
|
"snapshot_id", snapshotID)
|
||||||
|
|
||||||
remoteKey := snapshot.RemoteSnapshotKey(snapshotID)
|
remoteKey := snapshot.RemoteSnapshotKey(snapshotID)
|
||||||
|
|
||||||
deleted, err := v.deleteRemoteSnapshotByKey(remoteKey)
|
deleted, err := v.deleteRemoteSnapshotByKey(remoteKey)
|
||||||
|
if err != nil && !inLocalIndex {
|
||||||
|
return false, fmt.Errorf(
|
||||||
|
"removing snapshot metadata from destination store: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn("Could not remove snapshot metadata from remote storage; "+
|
log.Warn("Could not remove snapshot metadata from remote storage; "+
|
||||||
"run '"+snapshotRemoveCommandHint+"' with the snapshot's ID "+
|
"run '"+snapshotRemoveCommandHint+"' with the snapshot's ID "+
|
||||||
@@ -1406,10 +1424,10 @@ func (v *Vaultik) removeSnapshotRemote(snapshotID string, opts *RemoveOptions) b
|
|||||||
err, snapshotRemoveCommandHint, snapshotID)
|
err, snapshotRemoveCommandHint, snapshotID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return deleted > 0
|
return deleted > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveAllSnapshots removes every snapshot known to the local
|
// RemoveAllSnapshots removes every snapshot known to the local
|
||||||
|
|||||||
Reference in New Issue
Block a user