use cobra, closes #101

This commit is contained in:
Zach Ramsay
2017-10-18 12:48:19 -04:00
parent bb9bb4aa46
commit 9883013adf
3 changed files with 184 additions and 204 deletions

View File

@ -7,13 +7,15 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"os/exec"
"strings" "strings"
abcicli "github.com/tendermint/abci/client" abcicli "github.com/tendermint/abci/client"
"github.com/tendermint/abci/types" "github.com/tendermint/abci/types"
"github.com/tendermint/abci/version" //"github.com/tendermint/abci/version"
"github.com/tendermint/tmlibs/log" "github.com/tendermint/tmlibs/log"
"github.com/urfave/cli"
"github.com/spf13/cobra"
) )
// Structure for data passed to print response. // Structure for data passed to print response.
@ -38,149 +40,135 @@ var client abcicli.Client
var logger log.Logger var logger log.Logger
func main() { // flags
var (
address string
abci string
verbose bool
//workaround for the cli library (https://github.com/urfave/cli/issues/565) path string
cli.OsExiter = func(_ int) {} height int
prove bool
)
app := cli.NewApp() var RootCmd = &cobra.Command{
app.Name = "abci-cli" Use: "abci-cli",
app.Usage = "abci-cli [command] [args...]" Short: "",
app.Version = version.Version Long: "",
app.Flags = []cli.Flag{ PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
cli.StringFlag{ if logger == nil {
Name: "address", logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), log.AllowError())
Value: "tcp://127.0.0.1:46658",
Usage: "address of application socket",
},
cli.StringFlag{
Name: "abci",
Value: "socket",
Usage: "socket or grpc",
},
cli.BoolFlag{
Name: "verbose",
Usage: "print the command and results as if it were a console session",
},
}
app.Commands = []cli.Command{
{
Name: "batch",
Usage: "Run a batch of abci commands against an application",
Action: func(c *cli.Context) error {
return cmdBatch(app, c)
},
},
{
Name: "console",
Usage: "Start an interactive abci console for multiple commands",
Action: func(c *cli.Context) error {
return cmdConsole(app, c)
},
},
{
Name: "echo",
Usage: "Have the application echo a message",
Action: func(c *cli.Context) error {
return cmdEcho(c)
},
},
{
Name: "info",
Usage: "Get some info about the application",
Action: func(c *cli.Context) error {
return cmdInfo(c)
},
},
{
Name: "set_option",
Usage: "Set an option on the application",
Action: func(c *cli.Context) error {
return cmdSetOption(c)
},
},
{
Name: "deliver_tx",
Usage: "Deliver a new tx to application",
Action: func(c *cli.Context) error {
return cmdDeliverTx(c)
},
},
{
Name: "check_tx",
Usage: "Validate a tx",
Action: func(c *cli.Context) error {
return cmdCheckTx(c)
},
},
{
Name: "commit",
Usage: "Commit the application state and return the Merkle root hash",
Action: func(c *cli.Context) error {
return cmdCommit(c)
},
},
{
Name: "query",
Usage: "Query application state",
Action: func(c *cli.Context) error {
return cmdQuery(c)
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "path",
Value: "/store",
Usage: "Path to prefix the query with",
},
cli.IntFlag{
Name: "height",
Value: 0,
Usage: "Height to query the blockchain at",
},
cli.BoolFlag{
Name: "prove",
Usage: "Whether or not to return a merkle proof of the query result",
},
},
},
}
app.Before = before
err := app.Run(os.Args)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
}
func before(c *cli.Context) error {
if logger == nil {
logger = log.NewFilter(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), log.AllowError())
}
if client == nil {
var err error
client, err = abcicli.NewClient(c.GlobalString("address"), c.GlobalString("abci"), false)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
} }
client.SetLogger(logger.With("module", "abci-client")) if client == nil {
if _, err := client.Start(); err != nil { var err error
return err client, err = abcicli.NewClient(address, abci, false)
if err != nil {
logger.Error(err.Error())
os.Exit(1)
}
client.SetLogger(logger.With("module", "abci-client"))
if _, err := client.Start(); err != nil {
return err
}
} }
} return nil
return nil },
} }
// badCmd is called when we invoke with an invalid first argument (just for console for now) func Execute() {
func badCmd(c *cli.Context, cmd string) { addGlobalFlags()
fmt.Println("Unknown command:", cmd) addCommands()
fmt.Println("Please try one of the following:") RootCmd.Execute() //err?
fmt.Println("")
cli.DefaultAppComplete(c)
} }
//Generates new Args array based off of previous call args to maintain flag persistence func addGlobalFlags() {
RootCmd.PersistentFlags().StringVarP(&address, "address", "", "tcp://127.0.0.1:46658", "address of application socket")
RootCmd.PersistentFlags().StringVarP(&abci, "abci", "", "socket", "socket or grpc")
RootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "print the command and results as if it were a console session")
}
func addQueryFlags() {
queryCmd.PersistentFlags().StringVarP(&path, "path", "", "/store", "Path to prefix query with")
queryCmd.PersistentFlags().IntVarP(&height, "height", "", 0, "Height to query the blockchain at")
queryCmd.PersistentFlags().BoolVarP(&prove, "prove", "", false, "Whether or not to return a merkle proof of the query result")
}
func addCommands() {
RootCmd.AddCommand(batchCmd)
RootCmd.AddCommand(consoleCmd)
RootCmd.AddCommand(echoCmd)
RootCmd.AddCommand(infoCmd)
RootCmd.AddCommand(setOptionCmd)
RootCmd.AddCommand(deliverTxCmd)
RootCmd.AddCommand(checkTxCmd)
RootCmd.AddCommand(commitCmd)
addQueryFlags()
RootCmd.AddCommand(queryCmd)
}
var batchCmd = &cobra.Command{
Use: "batch",
Short: "Run a batch of abci commands against an application",
Long: "",
Run: cmdBatch,
}
var consoleCmd = &cobra.Command{
Use: "console",
Short: "Start an interactive abci console for multiple commands",
Long: "",
ValidArgs: []string{"batch", "echo", "info", "set_option", "deliver_tx", "check_tx", "commit", "query"},
Run: cmdConsole,
}
var echoCmd = &cobra.Command{
Use: "echo",
Short: "",
Long: "Have the application echo a message",
Run: cmdEcho,
}
var infoCmd = &cobra.Command{
Use: "info",
Short: "Get some info about the application",
Long: "",
Run: cmdInfo,
}
var setOptionCmd = &cobra.Command{
Use: "set_option",
Short: "Set an options on the application",
Long: "",
Run: cmdSetOption,
}
var deliverTxCmd = &cobra.Command{
Use: "deliver_tx",
Short: "Deliver a new tx to the application",
Long: "",
Run: cmdDeliverTx,
}
var checkTxCmd = &cobra.Command{
Use: "check_tx",
Short: "Validate a tx",
Long: "",
Run: cmdCheckTx,
}
var commitCmd = &cobra.Command{
Use: "commit",
Short: "Commit the application state and return the Merkle root hash",
Long: "",
Run: cmdCommit,
}
var queryCmd = &cobra.Command{
Use: "query",
Short: "Query the application state",
Long: "",
Run: cmdQuery,
}
// Generates new Args array based off of previous call args to maintain flag persistence
func persistentArgs(line []byte) []string { func persistentArgs(line []byte) []string {
// generate the arguments to run from original os.Args // generate the arguments to run from original os.Args
@ -196,154 +184,142 @@ func persistentArgs(line []byte) []string {
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
func cmdBatch(app *cli.App, c *cli.Context) error { func cmdBatch(cmd *cobra.Command, args []string) {
bufReader := bufio.NewReader(os.Stdin) bufReader := bufio.NewReader(os.Stdin)
for { for {
line, more, err := bufReader.ReadLine() line, more, err := bufReader.ReadLine()
if more { if more {
return errors.New("Input line is too long") ifExit(errors.New("Input line is too long"))
} else if err == io.EOF { } else if err == io.EOF {
break break
} else if len(line) == 0 { } else if len(line) == 0 {
continue continue
} else if err != nil { } else if err != nil {
return err ifExit(err)
} }
args := persistentArgs(line) pArgs := persistentArgs(line)
app.Run(args) //cli prints error within its func call out, err := exec.Command(pArgs[0], pArgs[1:]...).Output()
if err != nil {
panic(err)
}
fmt.Println(string(out))
} }
return nil
} }
func cmdConsole(app *cli.App, c *cli.Context) error { func cmdConsole(cmd *cobra.Command, args []string) {
// don't hard exit on mistyped commands (eg. check vs check_tx)
app.CommandNotFound = badCmd
for { for {
fmt.Printf("\n> ") fmt.Printf("\n> ")
bufReader := bufio.NewReader(os.Stdin) bufReader := bufio.NewReader(os.Stdin)
line, more, err := bufReader.ReadLine() line, more, err := bufReader.ReadLine()
if more { if more {
return errors.New("Input is too long") ifExit(errors.New("Input is too long"))
} else if err != nil { } else if err != nil {
return err ifExit(err)
} }
args := persistentArgs(line) pArgs := persistentArgs(line)
app.Run(args) //cli prints error within its func call out, err := exec.Command(pArgs[0], pArgs[1:]...).Output()
if err != nil {
panic(err)
}
fmt.Println(string(out))
} }
} }
// Have the application echo a message // Have the application echo a message
func cmdEcho(c *cli.Context) error { func cmdEcho(cmd *cobra.Command, args []string) {
args := c.Args()
if len(args) != 1 { if len(args) != 1 {
return errors.New("Command echo takes 1 argument") ifExit(errors.New("Command echo takes 1 argument"))
} }
resEcho := client.EchoSync(args[0]) resEcho := client.EchoSync(args[0])
printResponse(c, response{ printResponse(cmd, args, response{
Data: resEcho.Data, Data: resEcho.Data,
}) })
return nil
} }
// Get some info from the application // Get some info from the application
func cmdInfo(c *cli.Context) error { func cmdInfo(cmd *cobra.Command, args []string) {
args := c.Args()
var version string var version string
if len(args) == 1 { if len(args) == 1 {
version = args[0] version = args[0]
} }
resInfo, err := client.InfoSync(types.RequestInfo{version}) resInfo, err := client.InfoSync(types.RequestInfo{version})
if err != nil { if err != nil {
return err ifExit(err)
} }
printResponse(c, response{ printResponse(cmd, args, response{
Data: []byte(resInfo.Data), Data: []byte(resInfo.Data),
}) })
return nil
} }
// Set an option on the application // Set an option on the application
func cmdSetOption(c *cli.Context) error { func cmdSetOption(cmd *cobra.Command, args []string) {
args := c.Args()
if len(args) != 2 { if len(args) != 2 {
return errors.New("Command set_option takes 2 arguments (key, value)") ifExit(errors.New("Command set_option takes 2 arguments (key, value)"))
} }
resSetOption := client.SetOptionSync(args[0], args[1]) resSetOption := client.SetOptionSync(args[0], args[1])
printResponse(c, response{ printResponse(cmd, args, response{
Log: resSetOption.Log, Log: resSetOption.Log,
}) })
return nil
} }
// Append a new tx to application // Append a new tx to application
func cmdDeliverTx(c *cli.Context) error { func cmdDeliverTx(cmd *cobra.Command, args []string) {
args := c.Args()
if len(args) != 1 { if len(args) != 1 {
return errors.New("Command deliver_tx takes 1 argument") ifExit(errors.New("Command deliver_tx takes 1 argument"))
} }
txBytes, err := stringOrHexToBytes(c.Args()[0]) txBytes, err := stringOrHexToBytes(args[0])
if err != nil { if err != nil {
return err ifExit(err)
} }
res := client.DeliverTxSync(txBytes) res := client.DeliverTxSync(txBytes)
printResponse(c, response{ printResponse(cmd, args, response{
Code: res.Code, Code: res.Code,
Data: res.Data, Data: res.Data,
Log: res.Log, Log: res.Log,
}) })
return nil
} }
// Validate a tx // Validate a tx
func cmdCheckTx(c *cli.Context) error { func cmdCheckTx(cmd *cobra.Command, args []string) {
args := c.Args()
if len(args) != 1 { if len(args) != 1 {
return errors.New("Command check_tx takes 1 argument") ifExit(errors.New("Command check_tx takes 1 argument"))
} }
txBytes, err := stringOrHexToBytes(c.Args()[0]) txBytes, err := stringOrHexToBytes(args[0])
if err != nil { if err != nil {
return err ifExit(err)
} }
res := client.CheckTxSync(txBytes) res := client.CheckTxSync(txBytes)
printResponse(c, response{ printResponse(cmd, args, response{
Code: res.Code, Code: res.Code,
Data: res.Data, Data: res.Data,
Log: res.Log, Log: res.Log,
}) })
return nil
} }
// Get application Merkle root hash // Get application Merkle root hash
func cmdCommit(c *cli.Context) error { func cmdCommit(cmd *cobra.Command, args []string) {
res := client.CommitSync() res := client.CommitSync()
printResponse(c, response{ printResponse(cmd, args, response{
Code: res.Code, Code: res.Code,
Data: res.Data, Data: res.Data,
Log: res.Log, Log: res.Log,
}) })
return nil
} }
// Query application state // Query application state
func cmdQuery(c *cli.Context) error { func cmdQuery(cmd *cobra.Command, args []string) {
args := c.Args()
if len(args) != 1 { if len(args) != 1 {
return errors.New("Command query takes 1 argument, the query bytes") ifExit(errors.New("Command query takes 1 argument, the query bytes"))
} }
queryBytes, err := stringOrHexToBytes(args[0]) queryBytes, err := stringOrHexToBytes(args[0])
if err != nil { if err != nil {
return err ifExit(err)
} }
path := c.String("path")
height := c.Int("height")
prove := c.Bool("prove")
resQuery, err := client.QuerySync(types.RequestQuery{ resQuery, err := client.QuerySync(types.RequestQuery{
Data: queryBytes, Data: queryBytes,
Path: path, Path: path,
@ -351,9 +327,9 @@ func cmdQuery(c *cli.Context) error {
Prove: prove, Prove: prove,
}) })
if err != nil { if err != nil {
return err ifExit(err)
} }
printResponse(c, response{ printResponse(cmd, args, response{
Code: resQuery.Code, Code: resQuery.Code,
Log: resQuery.Log, Log: resQuery.Log,
Query: &queryResponse{ Query: &queryResponse{
@ -363,17 +339,14 @@ func cmdQuery(c *cli.Context) error {
Proof: resQuery.Proof, Proof: resQuery.Proof,
}, },
}) })
return nil
} }
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
func printResponse(c *cli.Context, rsp response) { func printResponse(cmd *cobra.Command, args []string, rsp response) {
verbose := c.GlobalBool("verbose")
if verbose { if verbose {
fmt.Println(">", c.Command.Name, strings.Join(c.Args(), " ")) fmt.Println(">", cmd.Use, strings.Join(args, " "))
} }
if !rsp.Code.IsOK() { if !rsp.Code.IsOK() {
@ -401,11 +374,6 @@ func printResponse(c *cli.Context, rsp response) {
fmt.Printf("-> proof: %X\n", rsp.Query.Proof) fmt.Printf("-> proof: %X\n", rsp.Query.Proof)
} }
} }
if verbose {
fmt.Println("")
}
} }
// NOTE: s is interpreted as a string unless prefixed with 0x // NOTE: s is interpreted as a string unless prefixed with 0x
@ -426,3 +394,10 @@ func stringOrHexToBytes(s string) ([]byte, error) {
return []byte(s[1 : len(s)-1]), nil return []byte(s[1 : len(s)-1]), nil
} }
func ifExit(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

5
cmd/abci-cli/main.go Normal file
View File

@ -0,0 +1,5 @@
package main
func main() {
Execute()
}

View File

@ -18,7 +18,7 @@ import:
- log - log
- merkle - merkle
- process - process
- package: github.com/urfave/cli - package: github.com/spf13/cobra
- package: golang.org/x/net - package: golang.org/x/net
subpackages: subpackages:
- context - context