Commit 4ef1d3f5 authored by Janos Guljas's avatar Janos Guljas

use cobra for cli and viper for config

parent 7147c4b2
...@@ -6,10 +6,14 @@ Work in progress. This is by no means the final abstraction. ...@@ -6,10 +6,14 @@ Work in progress. This is by no means the final abstraction.
## Usage ## Usage
Execute the same command in two terminals to start `node 1` and `node 2`: Execute the commands in two terminals to start `node 1` and `node 2`:
```sh ```sh
go run ./cmd/bee go run ./cmd/bee start --listen :8501
```
```sh
go run ./cmd/bee start --listen :8502
``` ```
Copy one of the multiaddresses from one running instance. Copy one of the multiaddresses from one running instance.
...@@ -17,7 +21,7 @@ Copy one of the multiaddresses from one running instance. ...@@ -17,7 +21,7 @@ Copy one of the multiaddresses from one running instance.
Make an HTTP request to `localhost:{PORT1}/pingpong/{MULTIADDRESS2}` like: Make an HTTP request to `localhost:{PORT1}/pingpong/{MULTIADDRESS2}` like:
```sh ```sh
curl localhost:8080/pingpong/ip4/127.0.0.1/tcp/60304/p2p/Qmdao2FbfSK8ZcFxuUVmVDPUJifgRmbofNWH21WQESZm7x curl localhost:8501/pingpong/ip4/127.0.0.1/tcp/60304/p2p/Qmdao2FbfSK8ZcFxuUVmVDPUJifgRmbofNWH21WQESZm7x
``` ```
Ping pong messages should be exchanged from `node 1` (listening on `PORT1`) to `node 2` (with multiaddress `MULTIADDRESS2`). Ping pong messages should be exchanged from `node 1` (listening on `PORT1`) to `node 2` (with multiaddress `MULTIADDRESS2`).
...@@ -25,6 +29,7 @@ Ping pong messages should be exchanged from `node 1` (listening on `PORT1`) to ` ...@@ -25,6 +29,7 @@ Ping pong messages should be exchanged from `node 1` (listening on `PORT1`) to `
## Structure ## Structure
- cmd/bee - a simple application integrating p2p and pingpong service - cmd/bee - a simple application integrating p2p and pingpong service
- pkg/api - a simple http api exposing pingpong endpoint
- pkg/p2p - p2p abstraction - pkg/p2p - p2p abstraction
- pkg/p2p/libp2p - p2p implementation using libp2p - pkg/p2p/libp2p - p2p implementation using libp2p
- pkg/p2p/mock - p2p protocol testing tools - pkg/p2p/mock - p2p protocol testing tools
......
package cmd
import (
"errors"
"path/filepath"
"strings"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func init() {
cobra.EnableCommandSorting = false
}
type command struct {
root *cobra.Command
config *viper.Viper
cfgFile string
homeDir string
}
type option func(*command)
func newCommand(opts ...option) (c *command, err error) {
c = &command{
root: &cobra.Command{
Use: "bee",
Short: "Ethereum Swarm Bee",
SilenceErrors: true,
SilenceUsage: true,
},
}
for _, o := range opts {
o(c)
}
c.initGlobalFlags()
if err := c.initConfig(); err != nil {
return nil, err
}
if err := c.initStartCmd(); err != nil {
return nil, err
}
c.initVersionCmd()
return c, nil
}
func (c *command) Execute() (err error) {
return c.root.Execute()
}
// Execute parses command line arguments and runs appropriate functions.
func Execute() (err error) {
c, err := newCommand()
if err != nil {
return err
}
return c.Execute()
}
func (c *command) initGlobalFlags() {
globalFlags := c.root.PersistentFlags()
globalFlags.StringVar(&c.cfgFile, "config", "", "config file (default is $HOME/.bee.yaml)")
}
func (c *command) initConfig() (err error) {
config := viper.New()
configName := ".bee"
if c.cfgFile != "" {
// Use config file from the flag.
config.SetConfigFile(c.cfgFile)
} else {
// Find home directory.
if err := c.setHomeDir(); err != nil {
return err
}
// Search config in home directory with name ".bee" (without extension).
config.AddConfigPath(c.homeDir)
config.SetConfigName(configName)
}
// Environment
config.SetEnvPrefix("bee")
config.AutomaticEnv() // read in environment variables that match
config.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
if c.homeDir != "" && c.cfgFile == "" {
c.cfgFile = filepath.Join(c.homeDir, configName+".yaml")
}
// If a config file is found, read it in.
if err := config.ReadInConfig(); err != nil {
var e viper.ConfigFileNotFoundError
if !errors.As(err, &e) {
return err
}
}
c.config = config
return nil
}
func (c *command) setHomeDir() (err error) {
if c.homeDir != "" {
return
}
dir, err := homedir.Dir()
if err != nil {
return err
}
c.homeDir = dir
return nil
}
package cmd_test
import (
"errors"
"fmt"
"io/ioutil"
"os"
"testing"
"time"
"github.com/janos/bee/cmd/bee/cmd"
)
var homeDir string
var errTest = errors.New("test error")
func TestMain(m *testing.M) {
dir, err := ioutil.TempDir("", "bee-cmd-")
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer os.RemoveAll(dir)
homeDir = dir
os.Exit(m.Run())
}
func newCommand(t *testing.T, opts ...cmd.Option) (c *cmd.Command) {
t.Helper()
c, err := cmd.NewCommand(append([]cmd.Option{cmd.WithHomeDir(homeDir)}, opts...)...)
if err != nil {
t.Fatal(err)
}
return c
}
func newTime(t *testing.T, s string) (tm time.Time) {
t.Helper()
tm, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
t.Fatal(err)
}
return tm
}
package cmd
import "io"
type (
Command = command
Option = option
)
var (
NewCommand = newCommand
)
func WithCfgFile(f string) func(c *Command) {
return func(c *Command) {
c.cfgFile = f
}
}
func WithHomeDir(dir string) func(c *Command) {
return func(c *Command) {
c.homeDir = dir
}
}
func WithArgs(a ...string) func(c *Command) {
return func(c *Command) {
c.root.SetArgs(a)
}
}
func WithInput(r io.Reader) func(c *Command) {
return func(c *Command) {
c.root.SetIn(r)
}
}
func WithOutput(w io.Writer) func(c *Command) {
return func(c *Command) {
c.root.SetOut(w)
}
}
func WithErrorOutput(w io.Writer) func(c *Command) {
return func(c *Command) {
c.root.SetErr(w)
}
}
package cmd
import (
"context"
"fmt"
"net"
"net/http"
"github.com/spf13/cobra"
"github.com/janos/bee/pkg/api"
"github.com/janos/bee/pkg/p2p/libp2p"
"github.com/janos/bee/pkg/pingpong"
)
func (c *command) initStartCmd() (err error) {
const (
optionNameListen = "listen"
)
cmd := &cobra.Command{
Use: "start",
Short: "Start a Swarm node",
RunE: func(cmd *cobra.Command, args []string) (err error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
//var idht *dht.IpfsDHT
// Construct P2P service.
p2ps, err := libp2p.New(ctx, libp2p.Options{
// Routing: func(h host.Host) (r routing.PeerRouting, err error) {
// idht, err = dht.New(ctx, h)
// return idht, err
// },
})
if err != nil {
return fmt.Errorf("p2p service: %w", err)
}
// Construct protocols.
pingPong := pingpong.New(p2ps)
// Add protocols to the P2P service.
if err = p2ps.AddProtocol(pingPong.Protocol()); err != nil {
return fmt.Errorf("pingpong service: %w", err)
}
addrs, err := p2ps.Addresses()
if err != nil {
return fmt.Errorf("get server addresses: %w", err)
}
for _, addr := range addrs {
cmd.Println(addr)
}
h := api.New(api.Options{
P2P: p2ps,
Pingpong: pingPong,
})
l, err := net.Listen("tcp", c.config.GetString(optionNameListen))
if err != nil {
return fmt.Errorf("listen TCP: %w", err)
}
cmd.Println("http address:", l.Addr())
return http.Serve(l, h)
},
}
cmd.Flags().String(optionNameListen, ":8500", "HTTP API listen address")
c.config.BindPFlags(cmd.Flags())
c.root.AddCommand(cmd)
return nil
}
package cmd
import (
"github.com/janos/bee"
"github.com/spf13/cobra"
)
func (c *command) initVersionCmd() {
c.root.AddCommand(&cobra.Command{
Use: "version",
Short: "Print version number",
Run: func(cmd *cobra.Command, args []string) {
cmd.Println(bee.Version)
},
})
}
package cmd_test
import (
"bytes"
"testing"
"github.com/janos/bee"
"github.com/janos/bee/cmd/bee/cmd"
)
func TestVersionCmd(t *testing.T) {
var outputBuf bytes.Buffer
if err := newCommand(t,
cmd.WithArgs("version"),
cmd.WithOutput(&outputBuf),
).Execute(); err != nil {
t.Fatal(err)
}
want := bee.Version + "\n"
got := outputBuf.String()
if got != want {
t.Errorf("got output %q, want %q", got, want)
}
}
package main package main
import ( // import (
"context" // "context"
"flag" // "flag"
"fmt" // "fmt"
"log" // "log"
"net" // "net"
"net/http" // "net/http"
"github.com/janos/bee/pkg/api" // "github.com/janos/bee/pkg/api"
"github.com/janos/bee/pkg/p2p/libp2p" // "github.com/janos/bee/pkg/p2p/libp2p"
"github.com/janos/bee/pkg/pingpong" // "github.com/janos/bee/pkg/pingpong"
) // )
var addr = flag.String("addr", ":0", "http api listen address") // var addr = flag.String("addr", ":0", "http api listen address")
func main() { // func main() {
flag.Parse() // flag.Parse()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
//var idht *dht.IpfsDHT
// Construct P2P service.
p2ps, err := libp2p.New(ctx, libp2p.Options{
// Routing: func(h host.Host) (r routing.PeerRouting, err error) {
// idht, err = dht.New(ctx, h)
// return idht, err
// },
})
if err != nil {
log.Fatal("p2p service: ", err)
}
// Construct protocols. // ctx, cancel := context.WithCancel(context.Background())
pingPong := pingpong.New(p2ps) // defer cancel()
// Add protocols to the P2P service. // //var idht *dht.IpfsDHT
if err = p2ps.AddProtocol(pingPong.Protocol()); err != nil {
log.Fatal("pingpong service: ", err)
}
addrs, err := p2ps.Addresses() // // Construct P2P service.
if err != nil { // p2ps, err := libp2p.New(ctx, libp2p.Options{
log.Fatal("get server addresses: ", err) // // Routing: func(h host.Host) (r routing.PeerRouting, err error) {
} // // idht, err = dht.New(ctx, h)
// // return idht, err
// // },
// })
// if err != nil {
// log.Fatal("p2p service: ", err)
// }
for _, addr := range addrs { // // Construct protocols.
fmt.Println(addr) // pingPong := pingpong.New(p2ps)
}
h := api.New(api.Options{ // // Add protocols to the P2P service.
P2P: p2ps, // if err = p2ps.AddProtocol(pingPong.Protocol()); err != nil {
Pingpong: pingPong, // log.Fatal("pingpong service: ", err)
}) // }
l, err := net.Listen("tcp", *addr) // addrs, err := p2ps.Addresses()
if err != nil { // if err != nil {
log.Fatal("tcp: ", err) // log.Fatal("get server addresses: ", err)
} // }
// for _, addr := range addrs {
// fmt.Println(addr)
// }
// h := api.New(api.Options{
// P2P: p2ps,
// Pingpong: pingPong,
// })
log.Println("listening: ", l.Addr()) // l, err := net.Listen("tcp", *addr)
// if err != nil {
// log.Fatal("tcp: ", err)
// }
log.Fatal(http.Serve(l, h)) // log.Println("listening: ", l.Addr())
// log.Fatal(http.Serve(l, h))
// }
import (
"fmt"
"os"
"github.com/janos/bee/cmd/bee/cmd"
)
func main() {
if err := cmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, "Error:", err)
os.Exit(1)
}
} }
// Package bee is the root package of Ethereum Swarm node implementation.
package bee
...@@ -25,12 +25,18 @@ require ( ...@@ -25,12 +25,18 @@ require (
github.com/libp2p/go-libp2p-tls v0.1.2 github.com/libp2p/go-libp2p-tls v0.1.2
github.com/libp2p/go-libp2p-transport v0.1.0 // indirect github.com/libp2p/go-libp2p-transport v0.1.0 // indirect
github.com/libp2p/go-stream-muxer v0.1.0 // indirect github.com/libp2p/go-stream-muxer v0.1.0 // indirect
github.com/mitchellh/go-homedir v1.1.0
github.com/multiformats/go-multiaddr v0.2.0 github.com/multiformats/go-multiaddr v0.2.0
github.com/multiformats/go-multistream v0.1.0 github.com/multiformats/go-multistream v0.1.0
github.com/olekukonko/tablewriter v0.0.4
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.6.1
github.com/whyrusleeping/go-smux-multiplex v3.0.16+incompatible // indirect github.com/whyrusleeping/go-smux-multiplex v3.0.16+incompatible // indirect
github.com/whyrusleeping/go-smux-multistream v2.0.2+incompatible // indirect github.com/whyrusleeping/go-smux-multistream v2.0.2+incompatible // indirect
github.com/whyrusleeping/go-smux-yamux v2.0.9+incompatible // indirect github.com/whyrusleeping/go-smux-yamux v2.0.9+incompatible // indirect
github.com/whyrusleeping/yamux v1.2.0 // indirect github.com/whyrusleeping/yamux v1.2.0 // indirect
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc
newreleases.io/cmd v0.1.0
newreleases.io/newreleases v1.3.1
resenje.org/web v0.4.0 resenje.org/web v0.4.0
) )
This diff is collapsed.
package bee
// Version is a manually set semantic version number.
var Version = "v0.1.0-alpha"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment