Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14e4c85365 |
No files matched your search
@@ -83,12 +83,12 @@ func (v *Vaultik) checkDestinationMarker() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// markDestination refuses a local index bound to a different
|
||||
// destination, checks the backup destination store for its marker,
|
||||
// writes the marker, and then records the binding. A first backup marks
|
||||
// its destination and a later one writes the same text again. The
|
||||
// binding is recorded last, so a first backup that cannot write to the
|
||||
// destination leaves the local index unbound.
|
||||
// markDestination checks the backup destination store with
|
||||
// checkStorageBinding and checkDestinationMarker, writes the marker, and
|
||||
// then binds the local index to the destination with
|
||||
// EnsureStorageBinding. A first backup marks its destination and a later
|
||||
// one writes the same text again. Binding after the write means a backup
|
||||
// that cannot write to the destination leaves the local index unbound.
|
||||
func (v *Vaultik) markDestination() error {
|
||||
err := v.checkStorageBinding()
|
||||
if err != nil {
|
||||
|
||||
@@ -24,9 +24,9 @@ var errStorageBindingMismatch = errors.New(
|
||||
// snapshots whose manifests reference blobs that aren't on the new
|
||||
// destination).
|
||||
//
|
||||
// It records nothing. Only a backup binds the local index, through
|
||||
// EnsureStorageBinding, and it does so before adding anything to it. An
|
||||
// unbound local index therefore holds nothing from any destination.
|
||||
// It records nothing: only a backup binds the local index, through
|
||||
// EnsureStorageBinding, and only once it has written to the destination.
|
||||
// An unbound local index therefore holds nothing from any destination.
|
||||
//
|
||||
// Read-only inspection commands (remote info, snapshot list, etc.)
|
||||
// deliberately don't call this: they can be run against a bare
|
||||
@@ -53,33 +53,32 @@ func (v *Vaultik) checkStorageBinding() error {
|
||||
errStorageBindingMismatch, buildBindingMismatchMessage(stored, configured))
|
||||
}
|
||||
|
||||
// EnsureStorageBinding records the configured destination (storage_url,
|
||||
// or the s3.* endpoint, bucket and prefix) as the binding of an unbound
|
||||
// local index. It refuses as checkStorageBinding does when the local
|
||||
// index is bound to a different destination.
|
||||
// EnsureStorageBinding refuses as checkStorageBinding does and, when the
|
||||
// local index is not bound yet, records the configured destination
|
||||
// (storage_url, or the s3.* endpoint, bucket and prefix) as its binding.
|
||||
// A backup calls it right after writing the destination marker, its
|
||||
// first write to the destination, so a backup that fails before then
|
||||
// (a mistyped bucket, a refused credential) leaves the local index
|
||||
// unbound and a corrected config is enough to go on.
|
||||
func (v *Vaultik) EnsureStorageBinding() error {
|
||||
stored, configured, err := v.storageBinding()
|
||||
if err != nil {
|
||||
err := v.checkStorageBinding()
|
||||
if err != nil || v.Repositories == nil || v.Config == nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if stored == "" {
|
||||
err = v.Repositories.LocalMeta.Set(
|
||||
v.ctx, database.LocalMetaKeyStorageURL, configured)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recording local storage binding: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Bound local index to storage destination", "storage_url", configured)
|
||||
|
||||
return nil
|
||||
stored, configured, err := v.storageBinding()
|
||||
if err != nil || stored != "" {
|
||||
return err
|
||||
}
|
||||
|
||||
if stored != configured {
|
||||
return fmt.Errorf("%w\n%s",
|
||||
errStorageBindingMismatch, buildBindingMismatchMessage(stored, configured))
|
||||
err = v.Repositories.LocalMeta.Set(
|
||||
v.ctx, database.LocalMetaKeyStorageURL, configured)
|
||||
if err != nil {
|
||||
return fmt.Errorf("recording local storage binding: %w", err)
|
||||
}
|
||||
|
||||
log.Info("Bound local index to storage destination", "storage_url", configured)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"sneak.berlin/go/vaultik/internal/config"
|
||||
"sneak.berlin/go/vaultik/internal/database"
|
||||
"sneak.berlin/go/vaultik/internal/log"
|
||||
"sneak.berlin/go/vaultik/internal/storage"
|
||||
"sneak.berlin/go/vaultik/internal/storage/faultstore"
|
||||
"sneak.berlin/go/vaultik/internal/vaultik"
|
||||
)
|
||||
@@ -56,78 +55,17 @@ func TestFailedFirstBackupLeavesIndexUnbound(t *testing.T) {
|
||||
assert.Equal(t, "file://"+storeDir, storageBindingOf(ctx, t, repos))
|
||||
}
|
||||
|
||||
// TestBackupToOtherDestinationWritesNoMarker checks that a backup
|
||||
// refuses a local index bound to a different destination before it
|
||||
// writes the marker to the configured one.
|
||||
func TestBackupToOtherDestinationWritesNoMarker(t *testing.T) {
|
||||
// TestPruneLeavesIndexUnbound checks that a command other than a backup
|
||||
// does not bind an unbound local index.
|
||||
func TestPruneLeavesIndexUnbound(t *testing.T) {
|
||||
log.Initialize(log.Config{})
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
firstDir := t.TempDir()
|
||||
v, _ := newFileDestinationVaultik(ctx, t, firstDir)
|
||||
v.Config.StorageURL = "file://" + firstDir
|
||||
v, repos := newFileDestinationVaultik(ctx, t, t.TempDir())
|
||||
|
||||
require.NoError(t, backUp(v, "first"))
|
||||
|
||||
secondDir := t.TempDir()
|
||||
secondStore, err := storage.NewFileStorer(secondDir)
|
||||
require.NoError(t, err)
|
||||
|
||||
v.Storage = secondStore
|
||||
v.Config.StorageURL = "file://" + secondDir
|
||||
|
||||
require.ErrorContains(t, backUp(v, "first"),
|
||||
"local index is bound to a different backup destination")
|
||||
assert.NoFileExists(t, filepath.Join(secondDir, markerFile))
|
||||
}
|
||||
|
||||
// TestCommandsOtherThanBackupLeaveIndexUnbound checks that commands
|
||||
// other than a backup do not bind an unbound local index, whether or not
|
||||
// they fail. Each runs on a fresh local index.
|
||||
func TestCommandsOtherThanBackupLeaveIndexUnbound(t *testing.T) {
|
||||
log.Initialize(log.Config{})
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
run func(v *vaultik.Vaultik) error
|
||||
}{
|
||||
{"prune", func(v *vaultik.Vaultik) error {
|
||||
return v.Prune(&vaultik.PruneOptions{Force: true})
|
||||
}},
|
||||
{"snapshot purge", func(v *vaultik.Vaultik) error {
|
||||
return v.PurgeSnapshotsWithOptions(
|
||||
&vaultik.SnapshotPurgeOptions{KeepLatest: true, Force: true})
|
||||
}},
|
||||
{"snapshot remove", func(v *vaultik.Vaultik) error {
|
||||
_, err := v.RemoveSnapshot("host_first_2026-01-02T03:04:05Z",
|
||||
&vaultik.RemoveOptions{Force: true})
|
||||
|
||||
return err
|
||||
}},
|
||||
{"snapshot remove --all", func(v *vaultik.Vaultik) error {
|
||||
_, err := v.RemoveAllSnapshots(&vaultik.RemoveOptions{Force: true})
|
||||
|
||||
return err
|
||||
}},
|
||||
{"remote nuke", func(v *vaultik.Vaultik) error {
|
||||
return v.NukeRemote(true)
|
||||
}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
v, repos := newFileDestinationVaultik(ctx, t, t.TempDir())
|
||||
|
||||
_ = tt.run(v)
|
||||
|
||||
assert.Empty(t, storageBindingOf(ctx, t, repos))
|
||||
})
|
||||
}
|
||||
require.NoError(t, v.Prune(&vaultik.PruneOptions{Force: true}))
|
||||
assert.Empty(t, storageBindingOf(ctx, t, repos))
|
||||
}
|
||||
|
||||
// buildBindTestVaultik returns a minimal Vaultik wired with a real
|
||||
|
||||
Reference in new issue
Block a user