Switch to cobra for the CLI

This commit is contained in:
Julian Kornberger 2017-09-17 03:26:19 +02:00 committed by Julian K
parent f4650213b8
commit cbb5f2e59b
12 changed files with 310 additions and 143 deletions

View File

@ -8,5 +8,4 @@ install:
- go get golang.org/x/tools/cmd/cover
script:
- ./.test-coverage
- go install github.com/FreifunkBremen/yanic/cmd/yanic
- go install github.com/FreifunkBremen/yanic/cmd/yanic-query
- go install github.com/FreifunkBremen/yanic

View File

@ -28,16 +28,77 @@ Recently seen nodes that does not reply are requested via a unicast message.
* [Mobi](https://www.gitbook.com/download/mobi/book/freifunkbremen/yanic)
* [ePUB](https://www.gitbook.com/download/epub/book/freifunkbremen/yanic)
## Configuration
Read comments in [config_example.toml](config_example.toml) for more information.
## Quick startup
## Running
Yanic provides several commands:
### Usage
Run Yanic without any arguments to get the usage information:
```
Usage of ./yanic:
-config path/to/config.toml
Usage:
yanic [command]
Available Commands:
help Help about any command
import Imports global statistics from the given RRD files, requires InfluxDB
query Sends a query on the interface to the destination and waits for a response
serve Runs the yanic server
Flags:
-h, --help help for yanic
--timestamps Enables timestamps for log output
Use "yanic [command] --help" for more information about a command.
```
### Configuration
Read comments in [config_example.toml](config_example.toml) for more information.
#### Serve
```
Usage:
yanic serve [flags]
Examples:
yanic serve -config /etc/yanic.toml
Flags:
-c, --config string Path to configuration file (default "config.toml")
-h, --help help for serve
```
#### Import
```
Usage:
yanic import <file.rrd> [flags]
Examples:
yanic import -config /etc/yanic.toml olddata.rrd
Flags:
-c, --config string Path to configuration file (default "config.toml")
-h, --help help for import
```
#### Query
```
Usage:
yanic query <interface> <destination> [flags]
Examples:
yanic query wlan0 "[fe80::eade:27ff:dead:beef%wlp4s0]:1001"
Flags:
-h, --help help for query
--wait int Seconds to wait for a response (default 1)
```
### Live
* [meshviewer](https://map.bremen.freifunk.net) **Freifunk Bremen** with a patch to show state-version of `nodes.json`

26
cmd/config.go Normal file
View File

@ -0,0 +1,26 @@
package cmd
import (
"fmt"
"os"
"github.com/FreifunkBremen/yanic/database"
"github.com/FreifunkBremen/yanic/respond"
"github.com/FreifunkBremen/yanic/runtime"
)
var (
configPath string
collector *respond.Collector
connections database.Connection
nodes *runtime.Nodes
)
func loadConfig() *runtime.Config {
config, err := runtime.ReadConfigFile(configPath)
if err != nil {
fmt.Fprintln(os.Stderr, "unable to load config file:", err)
os.Exit(2)
}
return config
}

47
cmd/import.go Normal file
View File

@ -0,0 +1,47 @@
package cmd
import (
"log"
"github.com/FreifunkBremen/yanic/database"
"github.com/FreifunkBremen/yanic/database/all"
"github.com/FreifunkBremen/yanic/rrd"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/spf13/cobra"
)
// importCmd represents the import command
var importCmd = &cobra.Command{
Use: "import <file.rrd>",
Short: "Imports global statistics from the given RRD files, requires InfluxDB",
Example: "yanic import -config /etc/yanic.toml olddata.rrd",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
path := args[0]
config := loadConfig()
connections, err := all.Connect(config.Database.Connection)
if err != nil {
panic(err)
}
database.Start(connections, config)
defer database.Close(connections)
log.Println("importing RRD from", path)
for ds := range rrd.Read(path) {
connections.InsertGlobals(
&runtime.GlobalStats{
Nodes: uint32(ds.Nodes),
Clients: uint32(ds.Clients),
},
ds.Time,
)
}
},
}
func init() {
RootCmd.AddCommand(importCmd)
importCmd.Flags().StringVarP(&configPath, "config", "c", "config.toml", "Path to configuration file")
}

44
cmd/query.go Normal file
View File

@ -0,0 +1,44 @@
package cmd
import (
"log"
"net"
"time"
"github.com/FreifunkBremen/yanic/respond"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/spf13/cobra"
)
var wait int
// queryCmd represents the query command
var queryCmd = &cobra.Command{
Use: "query <interface> <destination>",
Short: "Sends a query on the interface to the destination and waits for a response",
Example: `yanic query wlan0 "[fe80::eade:27ff:dead:beef%wlp4s0]:1001"`,
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
iface := args[0]
dstAddress := args[1]
log.Printf("Sending request address=%s iface=%s", dstAddress, iface)
nodes := runtime.NewNodes(&runtime.Config{})
collector := respond.NewCollector(nil, nodes, iface, 0)
defer collector.Close()
collector.SendPacket(net.ParseIP(dstAddress))
time.Sleep(time.Second * time.Duration(wait))
for id, data := range nodes.List {
log.Printf("%s: %+v", id, data)
}
},
}
func init() {
RootCmd.AddCommand(queryCmd)
queryCmd.Flags().IntVar(&wait, "wait", 1, "Seconds to wait for a response")
}

46
cmd/root.go Normal file
View File

@ -0,0 +1,46 @@
package cmd
import (
"fmt"
"log"
"os"
"github.com/spf13/cobra"
)
var (
timestamps bool
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "yanic",
Short: "Yet another node info collector",
Long: `A respondd client that fetches, stores and publishes information about a Freifunk network.`,
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
RootCmd.PersistentFlags().BoolVar(&timestamps, "timestamps", false, "Enables timestamps for log output")
}
func initConfig() {
if timestamps {
log.SetFlags(log.Lshortfile)
} else {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
}

70
cmd/serve.go Normal file
View File

@ -0,0 +1,70 @@
package cmd
import (
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/FreifunkBremen/yanic/database"
"github.com/FreifunkBremen/yanic/database/all"
"github.com/FreifunkBremen/yanic/meshviewer"
"github.com/FreifunkBremen/yanic/respond"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/FreifunkBremen/yanic/webserver"
"github.com/spf13/cobra"
)
// serveCmd represents the serve command
var serveCmd = &cobra.Command{
Use: "serve",
Short: "Runs the yanic server",
Example: "yanic serve -config /etc/yanic.toml",
Run: func(cmd *cobra.Command, args []string) {
config := loadConfig()
connections, err := all.Connect(config.Database.Connection)
if err != nil {
panic(err)
}
database.Start(connections, config)
defer database.Close(connections)
nodes = runtime.NewNodes(config)
nodes.Start()
meshviewer.Start(config, nodes)
if config.Webserver.Enable {
log.Println("starting webserver on", config.Webserver.Bind)
srv := webserver.New(config.Webserver.Bind, config.Webserver.Webroot)
go srv.Close()
}
if config.Respondd.Enable {
// Delaying startup to start at a multiple of `duration` since the zero time.
if duration := config.Respondd.Synchronize.Duration; duration > 0 {
now := time.Now()
delay := duration - now.Sub(now.Truncate(duration))
log.Printf("delaying %0.1f seconds", delay.Seconds())
time.Sleep(delay)
}
collector = respond.NewCollector(connections, nodes, config.Respondd.Interface, config.Respondd.Port)
collector.Start(config.Respondd.CollectInterval.Duration)
defer collector.Close()
}
// Wait for INT/TERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("received", sig)
},
}
func init() {
RootCmd.AddCommand(serveCmd)
serveCmd.Flags().StringVarP(&configPath, "config", "c", "config.toml", "Path to configuration file")
}

View File

@ -1,32 +0,0 @@
package main
import (
"log"
"net"
"os"
"time"
"github.com/FreifunkBremen/yanic/respond"
"github.com/FreifunkBremen/yanic/runtime"
)
// Usage: yanic-query wlp4s0 "[fe80::eade:27ff:dead:beef%wlp4s0]:1001"
func main() {
iface := os.Args[1]
dstAddress := os.Args[2]
log.Printf("Sending request address=%s iface=%s", dstAddress, iface)
nodes := runtime.NewNodes(&runtime.Config{})
collector := respond.NewCollector(nil, nodes, iface, 0)
collector.SendPacket(net.ParseIP(dstAddress))
time.Sleep(time.Second)
for id, data := range nodes.List {
log.Printf("%s: %+v", id, data)
}
collector.Close()
}

View File

@ -1,101 +0,0 @@
package main
import (
"flag"
"log"
"os"
"os/signal"
"syscall"
"time"
"github.com/FreifunkBremen/yanic/database"
"github.com/FreifunkBremen/yanic/database/all"
"github.com/FreifunkBremen/yanic/meshviewer"
"github.com/FreifunkBremen/yanic/respond"
"github.com/FreifunkBremen/yanic/rrd"
"github.com/FreifunkBremen/yanic/runtime"
"github.com/FreifunkBremen/yanic/webserver"
)
var (
configFile string
config *runtime.Config
collector *respond.Collector
connections database.Connection
nodes *runtime.Nodes
)
func main() {
var importPath string
var timestamps bool
flag.StringVar(&importPath, "import", "", "import global statistics from the given RRD file, requires influxdb")
flag.StringVar(&configFile, "config", "config.toml", "path of configuration file (default:config.yaml)")
flag.BoolVar(&timestamps, "timestamps", true, "print timestamps in output")
flag.Parse()
if !timestamps {
log.SetFlags(0)
}
log.Println("Yanic say hello")
config, err := runtime.ReadConfigFile(configFile)
if err != nil {
panic(err)
}
nodes = runtime.NewNodes(config)
connections, err = all.Connect(config.Database.Connection)
if err != nil {
panic(err)
}
database.Start(connections, config)
defer database.Close(connections)
if connections != nil && importPath != "" {
importRRD(importPath)
return
}
nodes.Start()
meshviewer.Start(config, nodes)
if config.Webserver.Enable {
log.Println("starting webserver on", config.Webserver.Bind)
srv := webserver.New(config.Webserver.Bind, config.Webserver.Webroot)
go srv.Close()
}
if config.Respondd.Enable {
// Delaying startup to start at a multiple of `duration` since the zero time.
if duration := config.Respondd.Synchronize.Duration; duration > 0 {
now := time.Now()
delay := duration - now.Sub(now.Truncate(duration))
log.Printf("delaying %0.1f seconds", delay.Seconds())
time.Sleep(delay)
}
collector = respond.NewCollector(connections, nodes, config.Respondd.Interface, config.Respondd.Port)
collector.Start(config.Respondd.CollectInterval.Duration)
defer collector.Close()
}
// Wait for INT/TERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("received", sig)
}
func importRRD(path string) {
log.Println("importing RRD from", path)
for ds := range rrd.Read(path) {
connections.InsertGlobals(
&runtime.GlobalStats{
Nodes: uint32(ds.Nodes),
Clients: uint32(ds.Clients),
},
ds.Time,
)
}
}

View File

@ -4,7 +4,7 @@ Description=yanic
[Service]
Type=simple
User=yanic
ExecStart=/opt/go/bin/yanic -config /etc/yanic.conf
ExecStart=/opt/go/bin/yanic serve -config /etc/yanic.conf
Restart=always
RestartSec=5s
Environment=PATH=/usr/bin:/usr/local/bin

7
main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "github.com/FreifunkBremen/yanic/cmd"
func main() {
cmd.Execute()
}

View File

@ -45,12 +45,12 @@ func ReadConfigFile(path string) (config *Config, err error) {
file, err := ioutil.ReadFile(path)
if err != nil {
panic(err)
return nil, err
}
err = toml.Unmarshal(file, config)
if err != nil {
panic(err)
return nil, err
}
return