mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-08 15:41:15 +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>
209 lines
4.4 KiB
Go
209 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"embed"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"text/template"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
weed_server "github.com/seaweedfs/seaweedfs/weed/server"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
flag "github.com/seaweedfs/seaweedfs/weed/util/fla9"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/seaweedfs/seaweedfs/weed/command"
|
|
"github.com/seaweedfs/seaweedfs/weed/glog"
|
|
util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
|
|
)
|
|
|
|
var IsDebug *bool
|
|
|
|
var commands = command.Commands
|
|
|
|
var exitStatus = 0
|
|
var exitMu sync.Mutex
|
|
|
|
func setExitStatus(n int) {
|
|
exitMu.Lock()
|
|
if exitStatus < n {
|
|
exitStatus = n
|
|
}
|
|
exitMu.Unlock()
|
|
}
|
|
|
|
//go:embed static
|
|
var static embed.FS
|
|
|
|
func init() {
|
|
weed_server.StaticFS, _ = fs.Sub(static, "static")
|
|
|
|
flag.Var(&util.ConfigurationFileDirectory, "config_dir", "directory with toml configuration files")
|
|
}
|
|
|
|
func main() {
|
|
glog.MaxSize = 1024 * 1024 * 10
|
|
glog.MaxFileCount = 5
|
|
flag.Usage = usage
|
|
|
|
err := sentry.Init(sentry.ClientOptions{
|
|
SampleRate: 0.1,
|
|
EnableTracing: true,
|
|
TracesSampleRate: 0.1,
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "sentry.Init: %v", err)
|
|
}
|
|
// Flush buffered events before the program terminates.
|
|
// Set the timeout to the maximum duration the program can afford to wait.
|
|
defer sentry.Flush(2 * time.Second)
|
|
|
|
if command.AutocompleteMain(commands) {
|
|
return
|
|
}
|
|
|
|
flag.Parse()
|
|
|
|
args := flag.Args()
|
|
if len(args) < 1 {
|
|
usage()
|
|
}
|
|
|
|
if args[0] == "help" {
|
|
help(args[1:])
|
|
for _, cmd := range commands {
|
|
if len(args) >= 2 && cmd.Name() == args[1] && cmd.Run != nil {
|
|
fmt.Fprintf(os.Stderr, "Default Parameters:\n")
|
|
cmd.Flag.PrintDefaults()
|
|
}
|
|
}
|
|
return
|
|
}
|
|
if args[0] != command.GetFuseCommandName() {
|
|
util_http.InitGlobalHttpClient()
|
|
}
|
|
for _, cmd := range commands {
|
|
if cmd.Name() == args[0] && cmd.Run != nil {
|
|
cmd.Flag.Usage = func() { cmd.Usage() }
|
|
cmd.Flag.Parse(args[1:])
|
|
args = cmd.Flag.Args()
|
|
IsDebug = cmd.IsDebug
|
|
if !cmd.Run(cmd, args) {
|
|
fmt.Fprintf(os.Stderr, "\n")
|
|
cmd.Flag.Usage()
|
|
fmt.Fprintf(os.Stderr, "Default Parameters:\n")
|
|
cmd.Flag.PrintDefaults()
|
|
// Command execution failed - general error
|
|
setExitStatus(1)
|
|
}
|
|
// A command can also record a failure without returning false.
|
|
setExitStatus(command.CommandExitStatus())
|
|
exit()
|
|
return
|
|
}
|
|
}
|
|
|
|
// Unknown command - syntax error
|
|
fmt.Fprintf(os.Stderr, "weed: unknown subcommand %q\nRun 'weed help' for usage.\n", args[0])
|
|
setExitStatus(2)
|
|
exit()
|
|
}
|
|
|
|
var usageTemplate = `
|
|
SeaweedFS: store billions of files and serve them fast!
|
|
|
|
Usage:
|
|
|
|
weed command [arguments]
|
|
|
|
The commands are:
|
|
{{range .}}{{if .Runnable}}
|
|
{{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}}
|
|
|
|
Use "weed help [command]" for more information about a command.
|
|
|
|
`
|
|
|
|
var helpTemplate = `{{if .Runnable}}Usage: weed {{.UsageLine}}
|
|
{{end}}
|
|
{{.Long}}
|
|
`
|
|
|
|
// tmpl executes the given template text on data, writing the result to w.
|
|
func tmpl(w io.Writer, text string, data interface{}) {
|
|
t := template.New("top")
|
|
t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize})
|
|
template.Must(t.Parse(text))
|
|
if err := t.Execute(w, data); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func capitalize(s string) string {
|
|
if s == "" {
|
|
return s
|
|
}
|
|
r, n := utf8.DecodeRuneInString(s)
|
|
return string(unicode.ToTitle(r)) + s[n:]
|
|
}
|
|
|
|
func printUsage(w io.Writer) {
|
|
tmpl(w, usageTemplate, commands)
|
|
}
|
|
|
|
func usage() {
|
|
printUsage(os.Stderr)
|
|
fmt.Fprintf(os.Stderr, "For Logging, use \"weed [logging_options] [command]\". The logging options are:\n")
|
|
flag.PrintDefaults()
|
|
// Invalid command line usage - syntax error
|
|
setExitStatus(2)
|
|
exit()
|
|
}
|
|
|
|
// help implements the 'help' command.
|
|
func help(args []string) {
|
|
if len(args) == 0 {
|
|
printUsage(os.Stdout)
|
|
// Success - help displayed correctly
|
|
return
|
|
}
|
|
if len(args) != 1 {
|
|
fmt.Fprintf(os.Stderr, "usage: weed help command\n\nToo many arguments given.\n")
|
|
// Invalid help usage - syntax error
|
|
setExitStatus(2)
|
|
exit()
|
|
}
|
|
|
|
arg := args[0]
|
|
|
|
for _, cmd := range commands {
|
|
if cmd.Name() == arg {
|
|
tmpl(os.Stdout, helpTemplate, cmd)
|
|
// Success - help for specific command displayed correctly
|
|
return
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'weed help'.\n", arg)
|
|
// Unknown help topic - syntax error
|
|
setExitStatus(2)
|
|
exit()
|
|
}
|
|
|
|
var atexitFuncs []func()
|
|
|
|
func exit() {
|
|
for _, f := range atexitFuncs {
|
|
f()
|
|
}
|
|
// os.Exit below skips main's deferred flush.
|
|
sentry.Flush(2 * time.Second)
|
|
os.Exit(exitStatus)
|
|
}
|