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.
135 lines
3.4 KiB
Go
135 lines
3.4 KiB
Go
package cli
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
|
|
"github.com/spf13/cobra"
|
|
"go.uber.org/fx"
|
|
"sneak.berlin/go/vaultik/internal/log"
|
|
"sneak.berlin/go/vaultik/internal/vaultik"
|
|
)
|
|
|
|
// errNukeNeedsForce guards the destructive 'remote nuke' subcommand.
|
|
var errNukeNeedsForce = errors.New(
|
|
"remote nuke requires --force (this deletes ALL remote snapshots and blobs)")
|
|
|
|
// NewRemoteCommand creates the remote command and subcommands
|
|
func NewRemoteCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "remote",
|
|
Short: "Remote storage management commands",
|
|
Long: "Commands for inspecting and managing remote storage",
|
|
}
|
|
|
|
// Add subcommands
|
|
cmd.AddCommand(newRemoteInfoCommand())
|
|
cmd.AddCommand(newRemoteNukeCommand())
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newRemoteNukeCommand creates the 'remote nuke' subcommand.
|
|
func newRemoteNukeCommand() *cobra.Command {
|
|
var force bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "nuke",
|
|
Short: "Delete ALL snapshot metadata and blobs from the backup destination store",
|
|
Long: `Removes every snapshot's metadata and every blob from remote
|
|
storage. After this command completes successfully the bucket prefix is
|
|
empty and the next backup starts from scratch.
|
|
|
|
This is destructive and irreversible. Requires --force.`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
if !force {
|
|
return errNukeNeedsForce
|
|
}
|
|
|
|
return runVaultikApp(cmd, false, false, "Remote nuke failed",
|
|
func(v *vaultik.Vaultik) error {
|
|
return v.NukeRemote(true)
|
|
})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&force, "force", false,
|
|
"Required: confirm destruction of ALL remote data")
|
|
|
|
return cmd
|
|
}
|
|
|
|
// newRemoteInfoCommand creates the 'remote info' subcommand
|
|
func newRemoteInfoCommand() *cobra.Command {
|
|
var jsonOutput bool
|
|
|
|
cmd := &cobra.Command{
|
|
Use: "info",
|
|
Short: "Display remote storage information",
|
|
Long: `Shows detailed information about remote storage, including:
|
|
- Size of all snapshot metadata (per snapshot and total)
|
|
- Count and total size of all blobs
|
|
- Count and size of referenced blobs (from all manifests)
|
|
- Count and size of orphaned blobs (not referenced by any manifest)`,
|
|
Args: cobra.NoArgs,
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
// Use unified config resolution
|
|
configPath, err := ResolveConfigPath()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rootFlags := GetRootFlags()
|
|
|
|
return RunWithApp(cmd.Context(), AppOptions{
|
|
ConfigPath: configPath,
|
|
LogOptions: log.Options{
|
|
Verbose: rootFlags.Verbose,
|
|
Debug: rootFlags.Debug,
|
|
Quiet: rootFlags.Quiet || jsonOutput,
|
|
},
|
|
Modules: []fx.Option{},
|
|
Invokes: []fx.Option{
|
|
fx.Invoke(func(v *vaultik.Vaultik, lc fx.Lifecycle) {
|
|
lc.Append(fx.Hook{
|
|
OnStart: func(_ context.Context) error {
|
|
go func() {
|
|
err := v.RemoteInfo(jsonOutput)
|
|
if err != nil {
|
|
if !errors.Is(err, context.Canceled) {
|
|
if !jsonOutput {
|
|
log.Error("Failed to get remote info", "error", err)
|
|
ReportErrorf("Failed to get remote info: %v", err)
|
|
}
|
|
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
err = v.Shutdowner.Shutdown()
|
|
if err != nil {
|
|
log.Error("Failed to shutdown", "error", err)
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
},
|
|
OnStop: func(_ context.Context) error {
|
|
v.Cancel()
|
|
|
|
return nil
|
|
},
|
|
})
|
|
}),
|
|
},
|
|
})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
|
|
|
|
return cmd
|
|
}
|