go-ethereum/internal/cli/server/command.go
Manav Darji 8613ff1c06
internal/cli: add support to overwrite config.toml values via cli flags (#1008)
* internal/cli: add support to overwrite config.toml via cli flags

* fix lint and refactor

* add extensive tests for flagset

* fix type conversion for big.Int

* add more tests for coverage

* add t.parallel

* internal/cli/flagset: handle flag conversion using interface

* internal/cli/flagset: fix test
2023-09-21 12:52:59 +05:30

221 lines
5.2 KiB
Go

package server
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"github.com/maticnetwork/heimdall/cmd/heimdalld/service"
"github.com/mitchellh/cli"
"github.com/pelletier/go-toml"
"github.com/ethereum/go-ethereum/log"
)
// Command is the command to start the sever
type Command struct {
UI cli.Ui
// cli configuration
cliConfig *Config
// final configuration
config *Config
configFile string
srv *Server
}
// MarkDown implements cli.MarkDown interface
func (c *Command) MarkDown() string {
items := []string{
"# Server",
"The ```bor server``` command runs the Bor client.",
c.Flags(nil).MarkDown(),
}
return strings.Join(items, "\n\n")
}
// Help implements the cli.Command interface
func (c *Command) Help() string {
return `Usage: bor [options]
Run the Bor server.
` + c.Flags(nil).Help()
}
// Synopsis implements the cli.Command interface
func (c *Command) Synopsis() string {
return "Run the Bor server"
}
// checkConfigFlag checks if the config flag is set or not. If set,
// it returns the value else an empty string.
func checkConfigFlag(args []string) string {
for i := 0; i < len(args); i++ {
arg := args[i]
// Check for single or double dashes
if strings.HasPrefix(arg, "-config") || strings.HasPrefix(arg, "--config") {
parts := strings.SplitN(arg, "=", 2)
if len(parts) == 2 {
return parts[1]
}
// If there's no equal sign, check the next argument
if i+1 < len(args) {
return args[i+1]
}
}
}
return ""
}
func (c *Command) extractFlags(args []string) error {
// Check if config file is provided or not
configFilePath := checkConfigFlag(args)
if configFilePath != "" {
log.Info("Reading config file", "path", configFilePath)
// Parse the config file
cfg, err := readConfigFile(configFilePath)
if err != nil {
c.UI.Error(err.Error())
return err
}
log.Warn("Config set via config file will be overridden by cli flags")
// Initialse a flagset based on the config created above
flags := c.Flags(cfg)
// Check for explicit cli args
cmd := Command{} // use a new variable to keep the original config intact
cliFlags := cmd.Flags(nil)
if err := cliFlags.Parse(args); err != nil {
c.UI.Error(err.Error())
return err
}
// Get the list of flags set explicitly
names, values := cliFlags.Visit()
// Set these flags using the flagset created earlier
flags.UpdateValue(names, values)
} else {
flags := c.Flags(nil)
if err := flags.Parse(args); err != nil {
c.UI.Error(err.Error())
return err
}
}
// nolint: nestif
// check for log-level and verbosity here
if configFilePath != "" {
data, _ := toml.LoadFile(configFilePath)
if data.Has("verbosity") && data.Has("log-level") {
log.Warn("Config contains both, verbosity and log-level, log-level will be deprecated soon. Use verbosity only.", "using", data.Get("verbosity"))
} else if !data.Has("verbosity") && data.Has("log-level") {
log.Warn("Config contains log-level only, note that log-level will be deprecated soon. Use verbosity instead.", "using", data.Get("log-level"))
c.cliConfig.Verbosity = VerbosityStringToInt(strings.ToLower(data.Get("log-level").(string)))
}
} else {
tempFlag := 0
for _, val := range args {
if (strings.HasPrefix(val, "-verbosity") || strings.HasPrefix(val, "--verbosity")) && c.cliConfig.LogLevel != "" {
tempFlag = 1
break
}
}
if tempFlag == 1 {
log.Warn("Both, verbosity and log-level flags are provided, log-level will be deprecated soon. Use verbosity only.", "using", c.cliConfig.Verbosity)
} else if tempFlag == 0 && c.cliConfig.LogLevel != "" {
log.Warn("Only log-level flag is provided, note that log-level will be deprecated soon. Use verbosity instead.", "using", c.cliConfig.LogLevel)
c.cliConfig.Verbosity = VerbosityStringToInt(strings.ToLower(c.cliConfig.LogLevel))
}
}
c.config = c.cliConfig
return nil
}
// Run implements the cli.Command interface
func (c *Command) Run(args []string) int {
err := c.extractFlags(args)
if err != nil {
c.UI.Error(err.Error())
return 1
}
if c.config.Heimdall.RunHeimdall {
shutdownCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
defer stop()
go func() {
service.NewHeimdallService(shutdownCtx, c.getHeimdallArgs())
}()
}
srv, err := NewServer(c.config, WithGRPCAddress())
if err != nil {
c.UI.Error(err.Error())
return 1
}
c.srv = srv
return c.handleSignals()
}
func (c *Command) handleSignals() int {
signalCh := make(chan os.Signal, 4)
signal.Notify(signalCh, os.Interrupt, syscall.SIGTERM, syscall.SIGHUP)
sig := <-signalCh
c.UI.Output(fmt.Sprintf("Caught signal: %v", sig))
c.UI.Output("Gracefully shutting down agent...")
gracefulCh := make(chan struct{})
go func() {
c.srv.Stop()
close(gracefulCh)
}()
for i := 10; i > 0; i-- {
select {
case <-signalCh:
log.Warn("Already shutting down, interrupt more force stop.", "times", i-1)
case <-gracefulCh:
return 0
}
}
return 1
}
// GetConfig returns the user specified config
func (c *Command) GetConfig() *Config {
return c.cliConfig
}
func (c *Command) getHeimdallArgs() []string {
heimdallArgs := strings.Split(c.config.Heimdall.RunHeimdallArgs, ",")
return append([]string{"start"}, heimdallArgs...)
}