1
0
Fork 0
mirror of https://github.com/eosswedenorg/thalos synced 2026-06-18 04:40:03 +02:00

refactor cli to use cobra.

This commit is contained in:
Henrik Hautakoski 2024-02-17 13:45:05 +01:00
parent fad70e19b9
commit 59480533d3
11 changed files with 236 additions and 289 deletions

View file

@ -1,80 +1,44 @@
package main
import (
"fmt"
"os"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var VersionString string = "dev"
func main() {
cli.AppHelpTemplate = `Usage: {{.HelpName}} [options]
var rootCmd *cobra.Command
{{range .VisibleFlags}}{{.}}
{{end}}`
cli.HelpFlag = &cli.BoolFlag{
Name: "help",
Aliases: []string{"h"},
Usage: "display this help text",
DisableDefaultText: true,
}
cli.VersionPrinter = func(cCtx *cli.Context) {
fmt.Printf("Version %s\n", VersionString)
}
cli.VersionFlag = &cli.BoolFlag{
Name: "version",
Aliases: []string{"v"},
Usage: "display the version",
DisableDefaultText: true,
}
app := &cli.App{
Version: VersionString,
Args: true,
UseShortOptionHandling: true,
HideHelpCommand: true,
Flags: []cli.Flag{
&cli.PathFlag{
Name: "config",
Aliases: []string{"c"},
Value: "./config.yml",
Usage: "Config `file` to read",
TakesFile: true,
},
&cli.StringFlag{
Name: "level",
Aliases: []string{"L"},
Usage: "Log level to use",
Value: "info",
},
&cli.PathFlag{
Name: "log",
Aliases: []string{"l"},
Usage: "Path to log `file`",
TakesFile: true,
},
&cli.BoolFlag{
Name: "n",
Usage: "Force the application to take start block from config/api",
DisableDefaultText: true,
},
&cli.StringFlag{
Name: "pid",
Aliases: []string{"p"},
Usage: "`file` to save process id to",
TakesFile: true,
},
func init() {
rootCmd = &cobra.Command{
Use: "thalos-server",
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true,
},
Action: serverCmd,
Version: VersionString,
Run: serverCmd,
}
if err := app.Run(os.Args); err != nil {
log.WithError(err).Fatal("Application error")
rootCmd.SetHelpTemplate(
`{{ .Use | trimTrailingWhitespaces}} v{{.Version}}
{{if or .Runnable .HasSubCommands}}{{.UsageString}}{{end}}
`)
rootCmd.SetVersionTemplate(`{{with .Name}}{{printf "%s " .}}{{end}}{{printf "v%s" .Version}}` + "\n")
flags := pflag.FlagSet{}
flags.StringP("config", "c", "./config.yml", "Config file to read")
flags.StringP("level", "L", "info", "Log level to use")
flags.StringP("log", "l", "", "Path to log file (default: print to stdout/stderr)")
flags.StringP("pid", "p", "", "Where to write process id")
flags.BoolP("no-state-cache", "n", false, "Force the application to take start block from config/api")
rootCmd.PersistentFlags().AddFlagSet(&flags)
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}

View file

@ -29,7 +29,8 @@ import (
"github.com/nikoksr/notify/service/telegram"
"github.com/redis/go-redis/v9"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// ---------------------------
@ -222,44 +223,52 @@ func stateSaver(state app.State) error {
return cache.Set(ctx, "state", state, 0)
}
func ReadConfig(cfg *config.Config, ctx *cli.Context) error {
func ReadConfig(cfg *config.Config, flags *pflag.FlagSet) error {
filename, err := flags.GetString("config")
if err != nil {
return err
}
// Read file first.
if err := cfg.ReadFile(ctx.Path("config")); err != nil {
if err := cfg.ReadFile(filename); err != nil {
return err
}
// Then override any cli flags
if err := cfg.ReadCliFlags(ctx); err != nil {
if err := cfg.ReadCliFlags(flags); err != nil {
return err
}
return nil
}
func serverCmd(ctx *cli.Context) error {
func serverCmd(cmd *cobra.Command, args []string) {
var err error
var chainInfo *eos.InfoResp
exit = make(chan bool)
skip_currentblock_cache := ctx.Bool("n")
skip_currentblock_cache, _ := cmd.Flags().GetBool("no-state-cache")
// Write PID file
pidFile := ctx.String("pid")
if len(pidFile) > 0 {
pidFile, err := cmd.Flags().GetString("pid")
if err != nil {
log.WithField("file", pidFile).Info("Writing pid to file")
if err = pid.Save(pidFile); err != nil {
return fmt.Errorf("pid: %s", err)
log.WithError(err).Fatal("Failed to write pid")
return
}
}
// Parse config
conf = config.New()
if err = ReadConfig(&conf, ctx); err != nil {
return fmt.Errorf("config: %s", err)
if err = ReadConfig(&conf, cmd.Flags()); err != nil {
log.WithError(err).Fatal("Failed to read config")
return
}
lvl, err := log.ParseLevel(ctx.String("level"))
flagLevel, _ := cmd.Flags().GetString("level")
lvl, err := log.ParseLevel(flagLevel)
if err == nil {
log.WithField("value", lvl).Info("Setting log level")
log.SetLevel(lvl)
@ -268,13 +277,17 @@ func serverCmd(ctx *cli.Context) error {
}
if len(conf.Log.Filename) > 0 {
fmt.Println(conf.Log.Filename)
stdWriter, err := NewRotatingFileFromConfig(conf.Log, "info")
if err != nil {
return fmt.Errorf("log: %s", err)
log.WithError(err).Fatal("Failed to set standard log file")
return
}
errWriter, err := NewRotatingFileFromConfig(conf.Log, "error")
if err != nil {
return fmt.Errorf("log: %s", err)
log.WithError(err).Fatal("Failed to set error log file")
return
}
log.WithFields(log.Fields{
@ -295,7 +308,8 @@ func serverCmd(ctx *cli.Context) error {
telegram, err := telegram.New(conf.Telegram.Id)
if err != nil {
return fmt.Errorf("telegram: %s", err)
log.WithError(err).Fatal("Failed to initialize telegram notifier")
return
}
telegram.AddReceivers(conf.Telegram.Channel)
@ -314,7 +328,8 @@ func serverCmd(ctx *cli.Context) error {
err = rdb.Ping(context.Background()).Err()
if err != nil {
return fmt.Errorf("redis: %s", err)
log.WithError(err).Fatal("Failed to connect to redis")
return
}
// Setup cache storage
@ -331,7 +346,8 @@ func serverCmd(ctx *cli.Context) error {
eosClient := eos.New(conf.Api)
chainInfo, err = eosClient.GetInfo(context.Background())
if err != nil {
return fmt.Errorf("eosapi: %s", err)
log.WithError(err).Fatal("Failed to call eos api")
return
}
shClient = shipclient.NewStream(func(s *shipclient.Stream) {
@ -343,7 +359,8 @@ func serverCmd(ctx *cli.Context) error {
// Get codec
codec, err := message.GetCodec(conf.MessageCodec)
if err != nil {
return fmt.Errorf("codec: %s", err)
log.WithError(err).Fatal("Failed to initialze codec")
return
}
chain_id := getChain(chainInfo.ChainID.String())
@ -365,6 +382,4 @@ func serverCmd(ctx *cli.Context) error {
// Close the processor properly
processor.Close()
return nil
}

View file

@ -7,7 +7,7 @@ import (
"os/signal"
"time"
"github.com/urfave/cli/v2"
"github.com/spf13/cobra"
"github.com/eosswedenorg/thalos/api"
"github.com/eosswedenorg/thalos/api/message"
@ -18,44 +18,38 @@ import (
log "github.com/sirupsen/logrus"
)
var benchCmd = &cli.Command{
Name: "bench",
Usage: "Run a benchmark against a thalos node",
Flags: []cli.Flag{
redisUrlFlag,
redisUserFlag,
redisPasswordFlag,
redisDbFlag,
prefixFlag,
chainIdFlag,
&cli.DurationFlag{
Name: "interval",
Aliases: []string{"i"},
Value: time.Minute,
Usage: "How often the benchmark results should be displayed.",
},
},
Action: func(ctx *cli.Context) error {
var benchCmd = &cobra.Command{
Use: "bench",
Short: "Run a benchmark against a thalos node",
Run: func(cmd *cobra.Command, args []string) {
var counter int = 0
interval := ctx.Duration("interval")
interval, _ := cmd.Flags().GetDuration("interval")
url, _ := cmd.Flags().GetString("redis-url")
user, _ := cmd.Flags().GetString("redis-user")
pw, _ := cmd.Flags().GetString("redis-pw")
prefix, _ := cmd.Flags().GetString("prefix")
chain_id, _ := cmd.Flags().GetString("chain_id")
db, _ := cmd.Flags().GetInt("redis-db")
log.WithFields(log.Fields{
"url": ctx.String("redis-url"),
"prefix": ctx.String("prefix"),
"chain_id": ctx.String("chain_id"),
"database": ctx.Int("redis-db"),
"url": url,
"prefix": prefix,
"chain_id": chain_id,
"database": db,
}).Info("Connecting to redis")
// Create redis client
rdb := redis.NewClient(&redis.Options{
Addr: ctx.String("redis-url"),
Username: ctx.String("redis-user"),
Password: ctx.String("redis-pw"),
DB: ctx.Int("redis-db"),
Addr: url,
Username: user,
Password: pw,
DB: db,
})
if err := rdb.Ping(context.Background()).Err(); err != nil {
return err
log.WithError(err).Fatal("Failed to connect to redis")
return
}
log.Println("Connected to redis")
@ -65,20 +59,22 @@ var benchCmd = &cli.Command{
}).Info("Starting benchmark")
sub := api_redis.NewSubscriber(context.Background(), rdb, api_redis.Namespace{
Prefix: ctx.String("prefix"),
ChainID: ctx.String("chain_id"),
Prefix: prefix,
ChainID: chain_id,
})
codec, err := message.GetCodec("json")
if err != nil {
return err
log.WithError(err).Fatal("Failed to get codec")
return
}
client := api.NewClient(sub, codec.Decoder)
// Subscribe to all actions
if err = client.Subscribe(api.ActionChannel{}.Channel()); err != nil {
return err
log.WithError(err).Fatal("Failed to subscribe to channels")
return
}
go func() {
@ -102,7 +98,7 @@ var benchCmd = &cli.Command{
case <-sig:
fmt.Println("Got interrupt")
client.Close()
return nil
return
case now := <-time.After(interval):
elapsed := now.Sub(t)
t = now

View file

@ -1,37 +0,0 @@
package main
import (
"github.com/urfave/cli/v2"
)
var redisUrlFlag = &cli.StringFlag{
Name: "redis-url",
Value: "127.0.0.1:6379",
Usage: "host:port to the redis server",
}
var redisUserFlag = &cli.StringFlag{
Name: "redis-user",
Usage: "User to use when authenticating to the server",
}
var redisPasswordFlag = &cli.StringFlag{
Name: "redis-pw",
Usage: "Password to use when authenticating to the server",
}
var redisDbFlag = &cli.IntFlag{
Name: "redis-db",
Value: 0,
Usage: "What redis database we should connect to.",
}
var prefixFlag = &cli.StringFlag{
Name: "prefix",
Value: "ship",
}
var chainIdFlag = &cli.StringFlag{
Name: "chain_id",
Value: "1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4",
}

View file

@ -1,29 +1,63 @@
package main
import (
"os"
"github.com/urfave/cli/v2"
"time"
_ "github.com/eosswedenorg/thalos/app/log"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var VersionString string = "dev"
func main() {
app := &cli.App{
Usage: "Collection of tools for dealing with the thalos application",
Version: VersionString,
Commands: []*cli.Command{
validateCmd,
benchCmd,
RedisACLCmd,
MockPublisherCmd,
var rootCmd *cobra.Command
func init() {
redisFlags := pflag.FlagSet{}
redisFlags.String("redis-url", "127.0.0.1:6379", "host:port to the redis server")
redisFlags.String("redis-user", "", "User to use when authenticating to the server")
redisFlags.String("redis-pw", "", "Password to use when authenticating to the server")
redisFlags.Int("redis-db", 0, "What redis database we should connect to.")
redisFlags.String("prefix", "ship", "redis prefix")
redisFlags.String("chain_id", "1064487b3cd1a897ce03ae5b6a865651747e2e152090f99c1d19d44e01aea5a4", "chain id")
rootCmd = &cobra.Command{
Use: "thalos-tools",
Short: "Collection of tools for dealing with the thalos application",
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true,
},
Version: VersionString,
}
if err := app.Run(os.Args); err != nil {
log.WithError(err).Fatal("Application error")
benchCmd.Flags().AddFlagSet(&redisFlags)
benchCmd.Flags().DurationP("interval", "i", time.Minute, "How often the benchmark results should be displayed.")
validateCmd.Flags().AddFlagSet(&redisFlags)
MockPublisherCmd.Flags().AddFlagSet(&redisFlags)
MockPublisherCmd.Flags().String("codec", "json", "codec to use")
RedisACLCmd.Flags().String("default-pw", "", "Password to use for the default account, if not provided a random one will be generated")
RedisACLCmd.Flags().String("client", "thalos-client", "Thalos client account name")
RedisACLCmd.Flags().String("client-pw", "", "Password to use for the thalos client account, if not provided a random one will be generated")
RedisACLCmd.Flags().String("server", "thalos", "Thalos account name")
RedisACLCmd.Flags().String("server-pw", "", "Password to use for the thalos server account, if not provided a random one will be generated")
RedisACLCmd.Flags().String("prefix", "ship", "Redis key prefix")
RedisACLCmd.Flags().Bool("cleartext", false, "If passwords should be hashed or left in cleartext.")
RedisACLCmd.Flags().String("file", "", "Where the config should be written to (default: standard out)")
rootCmd.AddCommand(
validateCmd,
benchCmd,
RedisACLCmd,
MockPublisherCmd,
)
}
func main() {
if err := rootCmd.Execute(); err != nil {
log.Fatal(err)
}
}

View file

@ -4,7 +4,7 @@ import (
"context"
"time"
"github.com/urfave/cli/v2"
"github.com/spf13/cobra"
"github.com/eosswedenorg/thalos/api"
"github.com/eosswedenorg/thalos/api/message"
@ -16,45 +16,43 @@ import (
log "github.com/sirupsen/logrus"
)
var MockPublisherCmd = &cli.Command{
Name: "mock_publisher",
Usage: "Run a publisher that mocks messages to a redis server. tries to send as many messages as possible",
Flags: []cli.Flag{
redisUrlFlag,
redisUserFlag,
redisPasswordFlag,
redisDbFlag,
prefixFlag,
chainIdFlag,
&cli.StringFlag{
Name: "codec",
Value: "json",
},
},
Action: func(ctx *cli.Context) error {
var MockPublisherCmd = &cobra.Command{
Use: "mock_publisher",
Short: "Run a publisher that mocks messages to a redis server. tries to send as many messages as possible",
Run: func(cmd *cobra.Command, args []string) {
url, _ := cmd.Flags().GetString("redis-url")
user, _ := cmd.Flags().GetString("redis-user")
pw, _ := cmd.Flags().GetString("redis-pw")
prefix, _ := cmd.Flags().GetString("prefix")
chain_id, _ := cmd.Flags().GetString("chain_id")
db, _ := cmd.Flags().GetInt("redis-db")
// Create redis client
rdb := redis.NewClient(&redis.Options{
Addr: ctx.String("redis-url"),
Username: ctx.String("redis-user"),
Password: ctx.String("redis-pw"),
DB: ctx.Int("redis-db"),
Addr: url,
Username: user,
Password: pw,
DB: db,
})
codec, err := message.GetCodec(ctx.String("codec"))
codecArg, _ := cmd.Flags().GetString("codec")
codec, err := message.GetCodec(codecArg)
if err != nil {
return err
log.WithError(err).Fatal("Failed to get codec")
return
}
log.WithFields(log.Fields{
"url": ctx.String("redis-url"),
"prefix": ctx.String("prefix"),
"chain_id": ctx.String("chain_id"),
"database": ctx.Int("redis-db"),
"url": url,
"prefix": prefix,
"chain_id": chain_id,
"database": db,
}).Info("Starting mock publisher")
ns := api_redis.Namespace{
Prefix: ctx.String("prefix"),
ChainID: ctx.String("chain_id"),
Prefix: prefix,
ChainID: chain_id,
}
publisher := redis_driver.NewPublisher(context.Background(), rdb, ns)
@ -104,7 +102,8 @@ var MockPublisherCmd = &cli.Command{
payload, err := codec.Encoder(msg)
if err != nil {
return err
log.WithError(err).Fatal("Failed to encode message")
return
}
channel := api.ActionChannel{}.Channel()

View file

@ -10,7 +10,8 @@ import (
"text/template"
"time"
"github.com/urfave/cli/v2"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var rnd *rand.Rand
@ -90,60 +91,30 @@ user {{.client}} on {{.clientpw}} resetchannels &{{.prefix}}::* -@all +subscribe
})
}
var RedisACLCmd = &cli.Command{
Name: "redis-acl",
Usage: "create a users.acl file",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "default-pw",
Usage: "Password to use for the default account, if not provided a random one will be generated",
},
&cli.StringFlag{
Name: "client",
Value: "thalos-client",
Usage: "Thalos client account name",
},
&cli.StringFlag{
Name: "client-pw",
Usage: "Password to use for the thalos client account, if not provided a random one will be generated",
},
&cli.StringFlag{
Name: "server",
Value: "thalos",
Usage: "Thalos account name",
},
&cli.StringFlag{
Name: "server-pw",
Usage: "Password to use for the thalos server account, if not provided a random one will be generated",
},
&cli.StringFlag{
Name: "prefix",
Value: "ship",
Usage: "Redis key prefix",
},
&cli.BoolFlag{
Name: "cleartext",
Usage: "If passwords should be hashed or left in cleartext.",
},
&cli.StringFlag{
Name: "file",
DefaultText: "Standard out",
Usage: "Where the config should be written to",
},
},
Action: func(ctx *cli.Context) error {
var RedisACLCmd = &cobra.Command{
Use: "redis-acl",
Short: "create a users.acl file",
Run: func(cmd *cobra.Command, args []string) {
var err error
var out *os.File = os.Stdout
rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
defaultUser := NewUser("default", ctx.String("default-pw"))
serverUser := NewUser(ctx.String("server"), ctx.String("server-pw"))
clientUser := NewUser(ctx.String("client"), ctx.String("client-pw"))
flagDefUserPw, _ := cmd.Flags().GetString("default-pw")
flagServer, _ := cmd.Flags().GetString("server")
flagServerPw, _ := cmd.Flags().GetString("server-pw")
flagClient, _ := cmd.Flags().GetString("client")
flagClientPw, _ := cmd.Flags().GetString("client-pw")
flagPrefix, _ := cmd.Flags().GetString("prefix")
defaultUser := NewUser("default", flagDefUserPw)
serverUser := NewUser(flagServer, flagServerPw)
clientUser := NewUser(flagClient, flagClientPw)
atleastOneGeneratedPw := defaultUser.Generated || serverUser.Generated || clientUser.Generated
if !ctx.Bool("cleartext") {
cleartext, _ := cmd.Flags().GetBool("cleartext")
if !cleartext {
if atleastOneGeneratedPw {
println("Passwords")
}
@ -157,17 +128,22 @@ var RedisACLCmd = &cli.Command{
clientUser.Hash()
}
filename := ctx.String("file")
filename, _ := cmd.Flags().GetString("file")
if len(filename) > 0 {
out, err = os.Create(filename)
if err != nil {
return err
log.WithError(err).Fatal("Failed to create output file")
return
}
defer out.Close()
} else if !ctx.Bool("cleartext") && atleastOneGeneratedPw {
} else if !cleartext && atleastOneGeneratedPw {
fmt.Println()
}
return writeTemplate(out, defaultUser, serverUser, clientUser, ctx.String("prefix"))
err = writeTemplate(out, defaultUser, serverUser, clientUser, flagPrefix)
if err != nil {
log.WithError(err).Fatal("Failed to writte config")
return
}
},
}

View file

@ -7,7 +7,7 @@ import (
"os/signal"
"time"
"github.com/urfave/cli/v2"
"github.com/spf13/cobra"
"github.com/eosswedenorg/thalos/api"
"github.com/eosswedenorg/thalos/api/message"
@ -18,33 +18,33 @@ import (
log "github.com/sirupsen/logrus"
)
var validateCmd = &cli.Command{
Name: "validate",
Usage: "Validate a thalos server by following action traces and makes sure that blocks arrive in order.",
Flags: []cli.Flag{
redisUrlFlag,
redisDbFlag,
prefixFlag,
chainIdFlag,
},
Action: func(ctx *cli.Context) error {
var validateCmd = &cobra.Command{
Use: "validate",
Short: "Validate a thalos server by following action traces and makes sure that blocks arrive in order.",
Run: func(cmd *cobra.Command, args []string) {
status_duration := time.Second * 10
url, _ := cmd.Flags().GetString("redis-url")
prefix, _ := cmd.Flags().GetString("prefix")
chain_id, _ := cmd.Flags().GetString("chain_id")
db, _ := cmd.Flags().GetInt("redis-db")
log.WithFields(log.Fields{
"url": ctx.String("redis-url"),
"prefix": ctx.String("prefix"),
"chain_id": ctx.String("chain_id"),
"database": ctx.Int("redis-db"),
"url": url,
"prefix": prefix,
"chain_id": chain_id,
"database": db,
}).Info("Connecting to redis")
// Create redis client
rdb := redis.NewClient(&redis.Options{
Addr: ctx.String("redis-url"),
DB: ctx.Int("redis-db"),
Addr: url,
DB: db,
})
if err := rdb.Ping(context.Background()).Err(); err != nil {
return err
log.WithError(err).Fatal("Failed to connect to redis")
return
}
log.Println("Connected to redis")
@ -52,20 +52,22 @@ var validateCmd = &cli.Command{
log.Info("Starting validation, following the stream")
sub := api_redis.NewSubscriber(context.Background(), rdb, api_redis.Namespace{
Prefix: ctx.String("prefix"),
ChainID: ctx.String("chain_id"),
Prefix: prefix,
ChainID: chain_id,
})
codec, err := message.GetCodec("json")
if err != nil {
return err
log.WithError(err).Fatal("Failed to get codec")
return
}
client := api.NewClient(sub, codec.Decoder)
// Subscribe to all actions
if err = client.Subscribe(api.ActionChannel{}.Channel()); err != nil {
return err
log.WithError(err).Fatal("Failed to subscribe to channels")
return
}
block_num := uint32(0)
@ -102,7 +104,7 @@ var validateCmd = &cli.Command{
case <-sig:
fmt.Println("Got interrupt")
client.Close()
return nil
return
case <-timer.C:
log.WithField("duration", timeout).
Warn("Did not get any messages during the defined duration")