Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
103 lines
4.1 KiB
Go
103 lines
4.1 KiB
Go
package vaultik
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
"sneak.berlin/go/vaultik/internal/database"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
)
|
|
|
|
// errStorageBindingMismatch is returned when the local index database is
|
|
// bound to a different destination than the configured storage_url.
|
|
var errStorageBindingMismatch = errors.New(
|
|
"local index is bound to a different backup destination")
|
|
|
|
// EnsureStorageBinding guarantees that the local index database is
|
|
// bound to the currently-configured storage destination. Every mutating
|
|
// command must call this before touching either the local index or the
|
|
// destination store, because the two live in lockstep: the local index
|
|
// records which chunks/blobs already exist at the destination, and
|
|
// mismatched destination + local index produces silent corruption (the
|
|
// scanner sees "known" chunks and skips uploads, then writes snapshots
|
|
// whose manifests reference blobs that aren't on the new destination).
|
|
//
|
|
// Behaviour:
|
|
// - On first use (empty stored value), record the configured
|
|
// storage_url and log the binding.
|
|
// - When the stored value matches the configured storage_url, do
|
|
// nothing and return nil.
|
|
// - When the two differ, refuse with an error that tells the user
|
|
// how to recover (revert the config, or run `vaultik database
|
|
// purge` to discard the local index and rebuild against the new
|
|
// destination on the next backup).
|
|
//
|
|
// Read-only inspection commands (remote info, snapshot list, etc.)
|
|
// deliberately don't call this: they can be run against a bare
|
|
// destination store without any binding state.
|
|
func (v *Vaultik) EnsureStorageBinding() error {
|
|
if v.Repositories == nil || v.Config == nil {
|
|
// NewForTesting builds a Vaultik with no DB or config;
|
|
// there's nothing to bind and no binding to check. Callers
|
|
// exercising the bind path use a real DB and populate Config
|
|
// explicitly (see storage_bind_test.go).
|
|
return nil
|
|
}
|
|
|
|
configured := v.Config.StorageURL
|
|
if configured == "" {
|
|
// Some legacy configs still use the split s3.* keys instead of
|
|
// storage_url. Falling back to a synthetic URL for those would
|
|
// hide the fact that the binding is loose. Instead, treat
|
|
// unset as "nothing to bind against" — the check is
|
|
// necessarily best-effort for those configs.
|
|
return nil
|
|
}
|
|
|
|
stored, err := v.Repositories.LocalMeta.Get(v.ctx, database.LocalMetaKeyStorageURL)
|
|
if err != nil {
|
|
return fmt.Errorf("reading local storage binding: %w", 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
|
|
}
|
|
|
|
if stored == configured {
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("%w\n%s",
|
|
errStorageBindingMismatch, buildBindingMismatchMessage(stored, configured))
|
|
}
|
|
|
|
// buildBindingMismatchMessage assembles the multi-line explanation
|
|
// shown when the local index is bound to a different destination than
|
|
// the currently-configured one (the first line lives in the
|
|
// errStorageBindingMismatch sentinel). Kept as a separate function so
|
|
// the multi-line text is expressed as a plain string literal rather
|
|
// than a fmt.Errorf argument (staticcheck ST1005 disallows trailing
|
|
// punctuation on error format strings).
|
|
func buildBindingMismatchMessage(stored, configured string) string {
|
|
return " local index bound to: " + stored + "\n" +
|
|
" currently configured: " + configured + "\n" +
|
|
"\n" +
|
|
"The local index database tracks which chunks and blobs already exist at the\n" +
|
|
"destination store. Using it against a different destination would silently\n" +
|
|
"skip uploads (the scanner would treat every chunk as already present), leaving\n" +
|
|
"future snapshots referencing blobs that don't exist at the new destination.\n" +
|
|
"\n" +
|
|
"To proceed, either:\n" +
|
|
" - revert storage_url in your config to the bound destination, or\n" +
|
|
" - run 'vaultik database delete' to discard the local index and rebuild it\n" +
|
|
" from a fresh full backup against the new destination"
|
|
}
|