This is a new revision of the discovery server. Relevant changes and non-changes: - Protocol towards clients is unchanged. - Recommended large scale design is still to be deployed nehind nginx (I tested, and it's still a lot faster at terminating TLS). - Database backend is leveldb again, only. It scales enough, is easy to setup, and we don't need any backend to take care of. - Server supports replication. This is a simple TCP channel - protect it with a firewall when deploying over the internet. (We deploy this within the same datacenter, and with firewall.) Any incoming client announces are sent over the replication channel(s) to other peer discosrvs. Incoming replication changes are applied to the database as if they came from clients, but without the TLS/certificate overhead. - Metrics are exposed using the prometheus library, when enabled. - The database values and replication protocol is protobuf, because JSON was quite CPU intensive when I tried that and benchmarked it. - The "Retry-After" value for failed lookups gets slowly increased from a default of 120 seconds, by 5 seconds for each failed lookup, independently by each discosrv. This lowers the query load over time for clients that are never seen. The Retry-After maxes out at 3600 after a couple of weeks of this increase. The number of failed lookups is stored in the database, now and then (avoiding making each lookup a database put). All in all this means clients can be pointed towards a cluster using just multiple A / AAAA records to gain both load sharing and redundancy (if one is down, clients will talk to the remaining ones). GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4648
317 lines
7.7 KiB
Go
317 lines
7.7 KiB
Go
package cli
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Command is a subcommand for a cli.App.
|
|
type Command struct {
|
|
// The name of the command
|
|
Name string
|
|
// short name of the command. Typically one character (deprecated, use `Aliases`)
|
|
ShortName string
|
|
// A list of aliases for the command
|
|
Aliases []string
|
|
// A short description of the usage of this command
|
|
Usage string
|
|
// Custom text to show on USAGE section of help
|
|
UsageText string
|
|
// A longer explanation of how the command works
|
|
Description string
|
|
// A short description of the arguments of this command
|
|
ArgsUsage string
|
|
// The category the command is part of
|
|
Category string
|
|
// The function to call when checking for bash command completions
|
|
BashComplete BashCompleteFunc
|
|
// An action to execute before any sub-subcommands are run, but after the context is ready
|
|
// If a non-nil error is returned, no sub-subcommands are run
|
|
Before BeforeFunc
|
|
// An action to execute after any subcommands are run, but after the subcommand has finished
|
|
// It is run even if Action() panics
|
|
After AfterFunc
|
|
// The function to call when this command is invoked
|
|
Action interface{}
|
|
// TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind
|
|
// of deprecation period has passed, maybe?
|
|
|
|
// Execute this function if a usage error occurs.
|
|
OnUsageError OnUsageErrorFunc
|
|
// List of child commands
|
|
Subcommands Commands
|
|
// List of flags to parse
|
|
Flags []Flag
|
|
// Treat all flags as normal arguments if true
|
|
SkipFlagParsing bool
|
|
// Skip argument reordering which attempts to move flags before arguments,
|
|
// but only works if all flags appear after all arguments. This behavior was
|
|
// removed n version 2 since it only works under specific conditions so we
|
|
// backport here by exposing it as an option for compatibility.
|
|
SkipArgReorder bool
|
|
// Boolean to hide built-in help flag
|
|
HideHelp bool
|
|
// Boolean to hide built-in help command
|
|
HideHelpCommand bool
|
|
// Boolean to hide this command from help or completion
|
|
Hidden bool
|
|
|
|
// Full name of command for help, defaults to full command name, including parent commands.
|
|
HelpName string
|
|
commandNamePath []string
|
|
|
|
// CustomHelpTemplate the text template for the command help topic.
|
|
// cli.go uses text/template to render templates. You can
|
|
// render custom help text by setting this variable.
|
|
CustomHelpTemplate string
|
|
}
|
|
|
|
type CommandsByName []Command
|
|
|
|
func (c CommandsByName) Len() int {
|
|
return len(c)
|
|
}
|
|
|
|
func (c CommandsByName) Less(i, j int) bool {
|
|
return c[i].Name < c[j].Name
|
|
}
|
|
|
|
func (c CommandsByName) Swap(i, j int) {
|
|
c[i], c[j] = c[j], c[i]
|
|
}
|
|
|
|
// FullName returns the full name of the command.
|
|
// For subcommands this ensures that parent commands are part of the command path
|
|
func (c Command) FullName() string {
|
|
if c.commandNamePath == nil {
|
|
return c.Name
|
|
}
|
|
return strings.Join(c.commandNamePath, " ")
|
|
}
|
|
|
|
// Commands is a slice of Command
|
|
type Commands []Command
|
|
|
|
// Run invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
|
func (c Command) Run(ctx *Context) (err error) {
|
|
if len(c.Subcommands) > 0 {
|
|
return c.startApp(ctx)
|
|
}
|
|
|
|
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
|
// append help to flags
|
|
c.Flags = append(
|
|
c.Flags,
|
|
HelpFlag,
|
|
)
|
|
}
|
|
|
|
set, err := flagSet(c.Name, c.Flags)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
set.SetOutput(ioutil.Discard)
|
|
|
|
if c.SkipFlagParsing {
|
|
err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...))
|
|
} else if !c.SkipArgReorder {
|
|
firstFlagIndex := -1
|
|
terminatorIndex := -1
|
|
for index, arg := range ctx.Args() {
|
|
if arg == "--" {
|
|
terminatorIndex = index
|
|
break
|
|
} else if arg == "-" {
|
|
// Do nothing. A dash alone is not really a flag.
|
|
continue
|
|
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 {
|
|
firstFlagIndex = index
|
|
}
|
|
}
|
|
|
|
if firstFlagIndex > -1 {
|
|
args := ctx.Args()
|
|
regularArgs := make([]string, len(args[1:firstFlagIndex]))
|
|
copy(regularArgs, args[1:firstFlagIndex])
|
|
|
|
var flagArgs []string
|
|
if terminatorIndex > -1 {
|
|
flagArgs = args[firstFlagIndex:terminatorIndex]
|
|
regularArgs = append(regularArgs, args[terminatorIndex:]...)
|
|
} else {
|
|
flagArgs = args[firstFlagIndex:]
|
|
}
|
|
|
|
err = set.Parse(append(flagArgs, regularArgs...))
|
|
} else {
|
|
err = set.Parse(ctx.Args().Tail())
|
|
}
|
|
} else {
|
|
err = set.Parse(ctx.Args().Tail())
|
|
}
|
|
|
|
nerr := normalizeFlags(c.Flags, set)
|
|
if nerr != nil {
|
|
fmt.Fprintln(ctx.App.Writer, nerr)
|
|
fmt.Fprintln(ctx.App.Writer)
|
|
ShowCommandHelp(ctx, c.Name)
|
|
return nerr
|
|
}
|
|
|
|
context := NewContext(ctx.App, set, ctx)
|
|
context.Command = c
|
|
if checkCommandCompletions(context, c.Name) {
|
|
return nil
|
|
}
|
|
|
|
if err != nil {
|
|
if c.OnUsageError != nil {
|
|
err := c.OnUsageError(context, err, false)
|
|
HandleExitCoder(err)
|
|
return err
|
|
}
|
|
fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error())
|
|
fmt.Fprintln(context.App.Writer)
|
|
ShowCommandHelp(context, c.Name)
|
|
return err
|
|
}
|
|
|
|
if checkCommandHelp(context, c.Name) {
|
|
return nil
|
|
}
|
|
|
|
if c.After != nil {
|
|
defer func() {
|
|
afterErr := c.After(context)
|
|
if afterErr != nil {
|
|
HandleExitCoder(err)
|
|
if err != nil {
|
|
err = NewMultiError(err, afterErr)
|
|
} else {
|
|
err = afterErr
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
if c.Before != nil {
|
|
err = c.Before(context)
|
|
if err != nil {
|
|
fmt.Fprintln(context.App.Writer, err)
|
|
fmt.Fprintln(context.App.Writer)
|
|
ShowCommandHelp(context, c.Name)
|
|
HandleExitCoder(err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
if c.Action == nil {
|
|
c.Action = helpSubcommand.Action
|
|
}
|
|
|
|
err = HandleAction(c.Action, context)
|
|
|
|
if err != nil {
|
|
HandleExitCoder(err)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Names returns the names including short names and aliases.
|
|
func (c Command) Names() []string {
|
|
names := []string{c.Name}
|
|
|
|
if c.ShortName != "" {
|
|
names = append(names, c.ShortName)
|
|
}
|
|
|
|
return append(names, c.Aliases...)
|
|
}
|
|
|
|
// HasName returns true if Command.Name or Command.ShortName matches given name
|
|
func (c Command) HasName(name string) bool {
|
|
for _, n := range c.Names() {
|
|
if n == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (c Command) startApp(ctx *Context) error {
|
|
app := NewApp()
|
|
app.Metadata = ctx.App.Metadata
|
|
// set the name and usage
|
|
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name)
|
|
if c.HelpName == "" {
|
|
app.HelpName = c.HelpName
|
|
} else {
|
|
app.HelpName = app.Name
|
|
}
|
|
|
|
app.Usage = c.Usage
|
|
app.Description = c.Description
|
|
app.ArgsUsage = c.ArgsUsage
|
|
|
|
// set CommandNotFound
|
|
app.CommandNotFound = ctx.App.CommandNotFound
|
|
app.CustomAppHelpTemplate = c.CustomHelpTemplate
|
|
|
|
// set the flags and commands
|
|
app.Commands = c.Subcommands
|
|
app.Flags = c.Flags
|
|
app.HideHelp = c.HideHelp
|
|
app.HideHelpCommand = c.HideHelpCommand
|
|
|
|
app.Version = ctx.App.Version
|
|
app.HideVersion = ctx.App.HideVersion
|
|
app.Compiled = ctx.App.Compiled
|
|
app.Author = ctx.App.Author
|
|
app.Email = ctx.App.Email
|
|
app.Writer = ctx.App.Writer
|
|
app.ErrWriter = ctx.App.ErrWriter
|
|
|
|
app.categories = CommandCategories{}
|
|
for _, command := range c.Subcommands {
|
|
app.categories = app.categories.AddCommand(command.Category, command)
|
|
}
|
|
|
|
sort.Sort(app.categories)
|
|
|
|
// bash completion
|
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion
|
|
if c.BashComplete != nil {
|
|
app.BashComplete = c.BashComplete
|
|
}
|
|
|
|
// set the actions
|
|
app.Before = c.Before
|
|
app.After = c.After
|
|
if c.Action != nil {
|
|
app.Action = c.Action
|
|
} else {
|
|
app.Action = helpSubcommand.Action
|
|
}
|
|
|
|
for index, cc := range app.Commands {
|
|
app.Commands[index].commandNamePath = []string{c.Name, cc.Name}
|
|
}
|
|
|
|
return app.RunAsSubcommand(ctx)
|
|
}
|
|
|
|
// VisibleFlags returns a slice of the Flags with Hidden=false
|
|
func (c Command) VisibleFlags() []Flag {
|
|
flags := c.Flags
|
|
if !c.HideHelp && (HelpFlag != BoolFlag{}) {
|
|
// append help to flags
|
|
flags = append(
|
|
flags,
|
|
HelpFlag,
|
|
)
|
|
}
|
|
return visibleFlags(flags)
|
|
}
|