check / check (push) Waiting to run
snapshot, config, remote, database and completion had no Run, and cobra checks a command's arguments only when it has one, so `vaultik snapshot craete` printed the group's help and exited 0. Each group now prints its help only when given no argument; any other word fails as an unknown command with exit 1, as at the top level. cobra adds the completion command only when Execute runs, so the root command now adds it itself to give it a Run. Its scripts then go to os.Stdout as it was at that moment; a later SetOut does not redirect them. Judgement call: the error has no "Did you mean" suggestion, which the top level gives. Model: opus-5-5
111 lines
3.0 KiB
Go
111 lines
3.0 KiB
Go
package cli
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/spf13/cobra"
|
|
"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",
|
|
// cobra checks Args only on a command that has a Run: without
|
|
// one, a mistyped subcommand printed help and exited 0.
|
|
Args: cobra.NoArgs,
|
|
Run: func(cmd *cobra.Command, _ []string) {
|
|
_ = cmd.Help()
|
|
},
|
|
}
|
|
|
|
// 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, mutating, 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 RunOperation(cmd.Context(), AppOptions{
|
|
ConfigPath: configPath,
|
|
LogOptions: log.Options{
|
|
Verbose: rootFlags.Verbose,
|
|
Debug: rootFlags.Debug,
|
|
Quiet: rootFlags.Quiet,
|
|
JSON: jsonOutput,
|
|
},
|
|
Mode: readOnly,
|
|
}, func(v *vaultik.Vaultik) error {
|
|
return v.RemoteInfo(jsonOutput)
|
|
}, func(err error) {
|
|
log.Error("Failed to get remote info", "error", err)
|
|
ReportErrorf("Failed to get remote info: %v", err)
|
|
})
|
|
},
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&jsonOutput, "json", false, "Output in JSON format")
|
|
|
|
return cmd
|
|
}
|