mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 23:50:43 +02:00
* shell: non-interactive mode exits non-zero when a command fails A failed command in a piped weed shell run printed 'error: ...' but the process still exited 0, so a CronJob wrapping e.g. echo 's3.lifecycle.run-shard -shards 0-15' | weed shell -master=... reported green while the run aborted partway (shards N+1..15 unwalked). An unknown command likewise exited 0. RunShell now returns the last command failure from the non-interactive stdin path (unknown commands included), and the shell command exits 2 on it. Interactive sessions are unchanged: errors are shown to the operator and the session continues, exiting 0 as before. * shell: route the piped-failure exit through main's shutdown path Review follow-up: os.Exit(2) inside the shell command skipped main's shutdown work. The command now records the status (SetCommandExitStatus) and returns normally; main applies it via setExitStatus before exit(). exit() itself now flushes sentry before os.Exit -- main's deferred sentry.Flush never ran on this path (os.Exit skips defers), so the existing 'flush buffered events before the program terminates' intent only worked for the autocomplete early-return. Exit status 2 on a failed piped run is preserved (verified: piped success exits 0, piped failing command exits 2). * shell: test the registered-command failure path Review follow-up: the error-propagation test only covered unknown commands. A fake registered command now drives processEachCmd's real dispatch path: a failing Do surfaces its exact error (errors.Is) and a succeeding one returns nil. The non-interactive exit status itself is main-level plumbing, verified end to end against the reproduction (piped failure exits 2). * shell: trim the comments added with the exit status Keep the non-obvious why -- why a piped run has to fail its wrapper, why the status is recorded instead of os.Exit'ed -- and drop the narration. Claude-Session: https://claude.ai/code/session_018DWwctzD4T2DmnczPRM47t * shell: fail a piped run with the status weed already uses for that weed.go spends 1 on a command that failed and 2 on a usage or syntax error, and runShell returns true precisely so the usage dump is skipped. Exiting 2 there told a wrapper the command line was wrong. Claude-Session: https://claude.ai/code/session_018DWwctzD4T2DmnczPRM47t --------- Co-authored-by: Carlos Leyva <carlos.leyva@idener.es>
114 lines
2.2 KiB
Go
114 lines
2.2 KiB
Go
package command
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
flag "github.com/seaweedfs/seaweedfs/weed/util/fla9"
|
|
)
|
|
|
|
var Commands = []*Command{
|
|
cmdAdmin,
|
|
cmdAutocomplete,
|
|
cmdUnautocomplete,
|
|
cmdBackup,
|
|
cmdBenchmark,
|
|
cmdCompact,
|
|
cmdDownload,
|
|
cmdExport,
|
|
cmdFiler,
|
|
cmdFilerBackup,
|
|
cmdFilerCat,
|
|
cmdFilerCopy,
|
|
cmdFilerMetaBackup,
|
|
cmdFilerMetaScan,
|
|
cmdFilerMetaTail,
|
|
cmdFilerRemoteGateway,
|
|
cmdFilerRemoteSynchronize,
|
|
cmdFilerReplicate,
|
|
cmdFilerSynchronize,
|
|
cmdFilerSyncVerify,
|
|
cmdFix,
|
|
cmdFuse,
|
|
cmdIam,
|
|
cmdMaster,
|
|
cmdMasterFollower,
|
|
cmdMini,
|
|
cmdMount,
|
|
cmdMqAgent,
|
|
cmdMqBroker,
|
|
cmdMqKafkaGateway,
|
|
cmdS3,
|
|
cmdScaffold,
|
|
cmdServer,
|
|
cmdShell,
|
|
cmdUpdate,
|
|
cmdUpload,
|
|
cmdVersion,
|
|
cmdVolume,
|
|
cmdWebDav,
|
|
cmdSftp,
|
|
cmdWorker,
|
|
}
|
|
|
|
type Command struct {
|
|
// Run runs the command.
|
|
// The args are the arguments after the command name.
|
|
Run func(cmd *Command, args []string) bool
|
|
|
|
// UsageLine is the one-line usage message.
|
|
// The first word in the line is taken to be the command name.
|
|
UsageLine string
|
|
|
|
// Short is the short description shown in the 'go help' output.
|
|
Short string
|
|
|
|
// Long is the long message shown in the 'go help <this-command>' output.
|
|
Long string
|
|
|
|
// Flag is a set of flags specific to this command.
|
|
Flag flag.FlagSet
|
|
|
|
IsDebug *bool
|
|
}
|
|
|
|
// Name returns the command's name: the first word in the usage line.
|
|
func (c *Command) Name() string {
|
|
name := c.UsageLine
|
|
i := strings.Index(name, " ")
|
|
if i >= 0 {
|
|
name = name[:i]
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (c *Command) Usage() {
|
|
fmt.Fprintf(os.Stderr, "Example: weed %s\n", c.UsageLine)
|
|
fmt.Fprintf(os.Stderr, "Default Usage:\n")
|
|
c.Flag.PrintDefaults()
|
|
fmt.Fprintf(os.Stderr, "Description:\n")
|
|
fmt.Fprintf(os.Stderr, " %s\n", strings.TrimSpace(c.Long))
|
|
os.Exit(2)
|
|
}
|
|
|
|
// Runnable reports whether the command can be run; otherwise
|
|
// it is a documentation pseudo-command such as importpath.
|
|
func (c *Command) Runnable() bool {
|
|
return c.Run != nil
|
|
}
|
|
|
|
// Recorded, not os.Exit'ed, so the failure still travels through main's
|
|
// shutdown path. The highest requested status wins.
|
|
var commandExitStatus int
|
|
|
|
func SetCommandExitStatus(n int) {
|
|
if n > commandExitStatus {
|
|
commandExitStatus = n
|
|
}
|
|
}
|
|
|
|
func CommandExitStatus() int {
|
|
return commandExitStatus
|
|
}
|