check / check (push) Successful in 5s
Updates golangci-lint to v2.12.2 everywhere it is pinned and installs the canonical `.golangci.yml`, then remediates every finding the new linter/config surfaces so `make check` is green. ## Version bump - `Dockerfile` lint stage: `golangci/golangci-lint:v2.11.3-alpine` -> `v2.12.2-alpine` (digest-pinned, date comment updated) - `Makefile` `deps` target: `go install` moved from the old v1 module path at `@latest` to the pinned `github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2` - `.golangci.yml` replaced with the canonical config (v2 schema; settings under `linters.settings` so the thresholds actually apply; `default: all` with the standard six disables) - `script/bootstrap` installs golangci-lint via the system package manager and carries no version pin, so it is unchanged - CI (`.gitea/workflows/check.yml`) only runs `script/cibuild`, so it needed no change ## Lint remediation The canonical config surfaced ~3,300 findings across 56k lines. All are fixed, behavior-preserving; incorporates and supersedes the per-package mechanical passes already merged to `main` (refs #61). Highlights: - `err113`: dynamic errors replaced with package sentinels + `%w` wrapping; comparisons via `errors.Is` - `goprintffuncname`: printf-style helpers renamed with an `f` suffix (`ui.Writer` message methods, `cli.ReportErrorf`, `database.Fatalf`) and all call sites updated - `revive` stutter renames: `blob.Handler`, `blob.WithReader`, `blob.ChunkPosition`, `storage.URL`, `storage.Info`; missing doc comments added - `contextcheck`/`noctx`: `context.Context` threaded through `blob.Packer` and the scanner call sites; context-aware `exec`/`sql` variants - `funlen`/`cyclop`/`gocognit`/`dupl`: oversized and duplicated functions split into focused helpers (production and test code) - tests: `t.Parallel()` added where safe (global logger init kept in the serial phase for `-race`), `t.TempDir()`/`t.Helper()` adopted, several suites converted to external test packages - `gosec`: bounded integer conversions, `ReadHeaderTimeout` on the test HTTP server; remaining warnings suppressed per-site with justifications - remaining `nolint` directives are rare, targeted, and each carries a reason (e.g. `nilnil` not-found contract in the repository layer, fx module globals, on-disk snake_case struct tags) - removed the deprecated `log.LogOptions` alias (callers migrated to `log.Options`) `make check` (tests with `-race`, lint, fmt-check) passes. Co-authored-by: sneak <sneak@sneak.berlin> Reviewed-on: #62 Co-authored-by: clawbot <clawbot@noreply.example.org> Co-committed-by: clawbot <clawbot@noreply.example.org>
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"
|
|
}
|