Consolidate command configuration and setup (#221)

This commit is contained in:
JT Olio 2018-08-13 09:07:05 -06:00 committed by GitHub
parent 5d20cf8829
commit ab029301bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 35 additions and 1141 deletions

View File

@ -1,82 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"golang.org/x/net/context"
"storj.io/storj/pkg/kademlia"
proto "storj.io/storj/protos/overlay"
)
// Config stores values from a farmer node config file
type Config struct {
NodeID string
PsHost string
PsPort string
KadListenPort string
KadPort string
KadHost string
PieceStoreDir string
}
// SetConfigPath sets and returns viper config directory and filepath
func SetConfigPath(fileName string) (configDir, configFile string, err error) {
home, err := homedir.Dir()
if err != nil {
return "", "", err
}
configDir = filepath.Join(home, ".storj")
configFile = filepath.Join(configDir, fileName+".yaml")
viper.SetConfigFile(configFile)
return configDir, configFile, nil
}
// GetConfigValues returns a struct with config file values
func GetConfigValues() Config {
config := Config{
NodeID: viper.GetString("piecestore.id"),
PsHost: viper.GetString("piecestore.host"),
PsPort: viper.GetString("piecestore.port"),
KadListenPort: viper.GetString("kademlia.listen.port"),
KadPort: viper.GetString("kademlia.port"),
KadHost: viper.GetString("kademlia.host"),
PieceStoreDir: viper.GetString("piecestore.dir"),
}
return config
}
// ConnectToKad joins the Kademlia network
func ConnectToKad(ctx context.Context, id, ip, kadListenPort, kadAddress string) (*kademlia.Kademlia, error) {
node := proto.Node{
Id: id,
Address: &proto.NodeAddress{
Transport: proto.NodeTransport_TCP,
Address: kadAddress,
},
}
kad, err := kademlia.NewKademlia(kademlia.StringToNodeID(id), []proto.Node{node}, ip, kadListenPort)
if err != nil {
return nil, errs.New("Failed to instantiate new Kademlia: %s", err.Error())
}
if err := kad.ListenAndServe(); err != nil {
return nil, errs.New("Failed to ListenAndServe on new Kademlia: %s", err.Error())
}
if err := kad.Bootstrap(ctx); err != nil {
return nil, errs.New("Failed to Bootstrap on new Kademlia: %s", err.Error())
}
return kad, nil
}

View File

@ -1,108 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"os"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"go.uber.org/zap"
"storj.io/storj/pkg/kademlia"
)
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "Create a new farmer node",
Long: "Create a config file and set values for a new farmer node",
RunE: createNode,
}
func init() {
RootCmd.AddCommand(createCmd)
// TODO@ASK: this does not create an identity
nodeID, err := kademlia.NewID()
if err != nil {
zap.S().Fatal(err)
}
home, err := homedir.Dir()
if err != nil {
zap.S().Fatal(err)
}
createCmd.Flags().String("kademliaHost", "bootstrap.storj.io", "Kademlia server `host`")
createCmd.Flags().String("kademliaPort", "8080", "Kademlia server `port`")
createCmd.Flags().String("kademliaListenPort", "7776", "Kademlia server `listen port`")
createCmd.Flags().String("pieceStoreHost", "127.0.0.1", "Farmer's public ip/host")
createCmd.Flags().String("pieceStorePort", "7777", "`port` where piece store data is accessed")
createCmd.Flags().String("dir", home, "`dir` of drive being shared")
if err := viper.BindPFlag("kademlia.host", createCmd.Flags().Lookup("kademliaHost")); err != nil {
zap.S().Fatalf("Failed to bind flag: %s", "kademlia.host")
}
if err := viper.BindPFlag("kademlia.port", createCmd.Flags().Lookup("kademliaPort")); err != nil {
zap.S().Fatalf("Failed to bind flag: %s", "kademlia.port")
}
if err := viper.BindPFlag("kademlia.listen.port", createCmd.Flags().Lookup("kademliaListenPort")); err != nil {
zap.S().Fatalf("Failed to bind flag: %s", "kademlia.listen.port")
}
if err := viper.BindPFlag("piecestore.host", createCmd.Flags().Lookup("pieceStoreHost")); err != nil {
zap.S().Fatalf("Failed to bind flag: %s", "piecestore.host")
}
if err := viper.BindPFlag("piecestore.port", createCmd.Flags().Lookup("pieceStorePort")); err != nil {
zap.S().Fatalf("Failed to bind flag: %s", "piecestore.port")
}
if err := viper.BindPFlag("piecestore.dir", createCmd.Flags().Lookup("dir")); err != nil {
zap.S().Fatalf("Failed to bind flag: %s", "piecestore.dir")
}
viper.SetDefault("piecestore.id", nodeID.String())
}
// createNode creates a config file for a new farmer node
func createNode(cmd *cobra.Command, args []string) error {
configDir, configFile, err := SetConfigPath(viper.GetString("piecestore.id"))
if err != nil {
return err
}
pieceStoreDir := viper.GetString("piecestore.dir")
err = os.MkdirAll(pieceStoreDir, 0700)
if err != nil {
return err
}
err = os.MkdirAll(configDir, 0700)
if err != nil {
return err
}
if _, err := os.Stat(configFile); err == nil {
return errs.New("Config already exists")
}
err = viper.WriteConfigAs(configFile)
if err != nil {
return err
}
path := viper.ConfigFileUsed()
fmt.Printf("Node %s created\n", viper.GetString("piecestore.id"))
fmt.Println("Config: ", path)
return nil
}

View File

@ -1,68 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"os"
"path/filepath"
_ "github.com/mattn/go-sqlite3" // sqlite driver
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zeebo/errs"
)
// deleteCmd represents the delete command
var deleteCmd = &cobra.Command{
Use: "delete",
Short: "Delete a farmer node by ID",
Long: "Delete config and all data stored on node by node ID",
RunE: deleteNode,
}
func init() {
RootCmd.AddCommand(deleteCmd)
}
// deleteNode deletes a farmer node by ID
func deleteNode(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errs.New("No ID specified")
}
nodeID := args[0]
_, configFile, err := SetConfigPath(nodeID)
if err != nil {
return err
}
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return errs.New("Invalid node ID. Config file does not exist")
}
if err := viper.ReadInConfig(); err != nil {
return err
}
// get folder for stored data
piecestoreDir := viper.GetString("piecestore.dir")
piecestoreDir = filepath.Join(piecestoreDir, fmt.Sprintf("store-%s", nodeID))
// remove all folders and files stored on node
if err := os.RemoveAll(piecestoreDir); err != nil {
return err
}
// delete node config
err = os.Remove(configFile)
if err != nil {
return err
}
fmt.Printf("Node %s deleted\n", nodeID)
return nil
}

View File

@ -1,383 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
)
// TestCreate tests the farmer CLI create command
func TestCreate(t *testing.T) {
home, err := homedir.Dir()
if err != nil {
t.Error(err.Error())
}
tempFile, err := ioutil.TempFile(os.TempDir(), "")
if err != nil {
t.Error(err.Error())
}
defer os.Remove(tempFile.Name())
tests := []struct {
it string
expectedConfig Config
args []string
err string
}{
{
it: "should successfully write config with default values",
expectedConfig: Config{
NodeID: "",
PsHost: "127.0.0.1",
PsPort: "7777",
KadListenPort: "7776",
KadPort: "8080",
KadHost: "bootstrap.storj.io",
PieceStoreDir: home,
},
args: []string{
"create",
},
err: "",
},
{
it: "should successfully write config with flag values",
expectedConfig: Config{
NodeID: "",
PsHost: "123.4.5.6",
PsPort: "1234",
KadListenPort: "4321",
KadPort: "4444",
KadHost: "hack@theplanet.com",
PieceStoreDir: os.TempDir(),
},
args: []string{
"create",
fmt.Sprintf("--pieceStoreHost=%s", "123.4.5.6"),
fmt.Sprintf("--pieceStorePort=%s", "1234"),
fmt.Sprintf("--kademliaListenPort=%s", "4321"),
fmt.Sprintf("--kademliaPort=%s", "4444"),
fmt.Sprintf("--kademliaHost=%s", "hack@theplanet.com"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
err: "",
},
{
it: "should err when a config with identical ID already exists",
expectedConfig: Config{
NodeID: "",
PsHost: "123.4.5.6",
PsPort: "1234",
KadListenPort: "4321",
KadPort: "4444",
KadHost: "hack@theplanet.com",
PieceStoreDir: home,
},
args: []string{
"create",
fmt.Sprintf("--dir=%s", home),
},
err: "Config already exists",
},
{
it: "should err when pieceStoreDir is not a directory",
expectedConfig: Config{
NodeID: "",
PsHost: "123.4.5.6",
PsPort: "1234",
KadListenPort: "4321",
KadPort: "4444",
KadHost: "hack@theplanet.com",
PieceStoreDir: tempFile.Name(),
},
args: []string{
"create",
fmt.Sprintf("--dir=%s", tempFile.Name()),
},
err: fmt.Sprintf("mkdir %s: not a directory", tempFile.Name()),
},
}
for _, tt := range tests {
t.Run(tt.it, func(t *testing.T) {
assert := assert.New(t)
RootCmd.SetArgs(tt.args)
path := filepath.Join(home, ".storj", viper.GetString("piecestore.id")+".yaml")
if tt.err == "Config already exists" {
_, err := os.Create(path)
if err != nil {
t.Error(err.Error())
return
}
}
defer os.Remove(path)
err := RootCmd.Execute()
if tt.err != "" {
if err != nil {
assert.Equal(tt.err, err.Error())
return
}
} else if err != nil {
t.Error(err.Error())
return
}
viper.SetConfigFile(path)
err = viper.ReadInConfig()
if err != nil {
t.Error(err.Error())
return
}
tt.expectedConfig.NodeID = viper.GetString("piecestore.id")
assert.Equal(tt.expectedConfig, GetConfigValues())
return
})
}
}
// TestStart tests the farmer CLI start command
func TestStart(t *testing.T) {
home, err := homedir.Dir()
if err != nil {
t.Error(err.Error())
}
tests := []struct {
it string
createArgs []string
startArgs []string
err string
}{
{
it: "should err with no ID specified",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
},
err: "No ID specified",
},
{
it: "should err with invalid Farmer IP",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "123"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
viper.GetString("piecestore.id"),
},
err: "Failed to instantiate new Kademlia: lookup 123: no such host",
},
{
it: "should err with missing Kademlia Listen Port",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", ""),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
viper.GetString("piecestore.id"),
},
err: "Failed to instantiate new Kademlia: node error: must specify port in request to NewKademlia",
},
{
it: "should err with missing IP",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", ""),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
startArgs: []string{
"start",
viper.GetString("piecestore.id"),
},
err: "Failed to instantiate new Kademlia: lookup : no such host",
},
}
for _, tt := range tests {
t.Run(tt.it, func(t *testing.T) {
assert := assert.New(t)
RootCmd.SetArgs(tt.createArgs)
err = RootCmd.Execute()
if err != nil {
t.Error(err.Error())
return
}
path := filepath.Join(home, ".storj", viper.GetString("piecestore.id")+".yaml")
defer os.Remove(path)
RootCmd.SetArgs(tt.startArgs)
err := RootCmd.Execute()
if tt.err != "" {
if err != nil {
assert.Equal(tt.err, err.Error())
}
} else if err != nil {
t.Error(err.Error())
return
}
})
}
}
// TestDelete tests the farmer CLI delete command
func TestDelete(t *testing.T) {
home, err := homedir.Dir()
if err != nil {
t.Error(err.Error())
}
tests := []struct {
it string
createArgs []string
deleteArgs []string
err string
}{
{
it: "should successfully delete node config",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
deleteArgs: []string{
"delete",
viper.GetString("piecestore.id"),
},
err: "",
},
{
it: "should err with no ID specified",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
deleteArgs: []string{
"delete",
},
err: "No ID specified",
},
{
it: "should err with no ID specified",
createArgs: []string{
"create",
fmt.Sprintf("--kademliaHost=%s", "bootstrap.storj.io"),
fmt.Sprintf("--kademliaPort=%s", "8080"),
fmt.Sprintf("--kademliaListenPort=%s", "7776"),
fmt.Sprintf("--pieceStoreHost=%s", "127.0.0.1"),
fmt.Sprintf("--pieceStorePort=%s", "7777"),
fmt.Sprintf("--dir=%s", os.TempDir()),
},
deleteArgs: []string{
"delete",
"123",
},
err: "Invalid node ID. Config file does not exist",
},
}
for _, tt := range tests {
t.Run(tt.it, func(t *testing.T) {
assert := assert.New(t)
RootCmd.SetArgs(tt.createArgs)
err := RootCmd.Execute()
if err != nil {
t.Error(err.Error())
return
}
configPath := filepath.Join(home, ".storj", viper.GetString("piecestore.id")+".yaml")
if _, err := os.Stat(configPath); err != nil {
t.Error(err.Error())
return
}
if tt.err != "" {
defer os.Remove(configPath)
}
RootCmd.SetArgs(tt.deleteArgs)
err = RootCmd.Execute()
if tt.err != "" {
if err != nil {
assert.Equal(tt.err, err.Error())
return
}
} else if err != nil {
t.Error(err.Error())
return
}
// if err is nil, file was not deleted
if _, err := os.Stat(configPath); err == nil {
t.Error(err.Error())
return
}
return
})
}
}
func TestMain(m *testing.M) {
m.Run()
}

View File

@ -1,15 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"github.com/spf13/cobra"
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "piecestore-farmer",
Short: "Piecestore-Farmer CLI",
Long: "Piecestore-Farmer command line utility for creating, starting, and deleting farmer nodes",
}

View File

@ -1,104 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package cmd
import (
"fmt"
"net"
"path/filepath"
_ "github.com/mattn/go-sqlite3" // sqlite driver
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"go.uber.org/zap"
"golang.org/x/net/context"
"google.golang.org/grpc"
"storj.io/storj/pkg/piecestore/rpc/server"
"storj.io/storj/pkg/piecestore/rpc/server/ttl"
pb "storj.io/storj/protos/piecestore"
)
// startCmd represents the start command
var startCmd = &cobra.Command{
Use: "start",
Short: "Start a farmer node by ID",
Long: "Start farmer node by ID using farmer node config values",
RunE: startNode,
}
func init() {
RootCmd.AddCommand(startCmd)
}
// startNode starts a farmer node by ID
func startNode(cmd *cobra.Command, args []string) error {
ctx := context.Background()
if len(args) == 0 {
return errs.New("No ID specified")
}
_, _, err := SetConfigPath(args[0])
if err != nil {
return err
}
err = viper.ReadInConfig()
if err != nil {
return err
}
config := GetConfigValues()
dbPath := filepath.Join(config.PieceStoreDir, fmt.Sprintf("store-%s", config.NodeID), "ttl-data.db")
dataDir := filepath.Join(config.PieceStoreDir, fmt.Sprintf("store-%s", config.NodeID), "piece-store-data")
_, err = ConnectToKad(ctx, config.NodeID, config.PsHost, config.KadListenPort, fmt.Sprintf("%s:%s", config.KadHost, config.KadPort))
if err != nil {
return err
}
ttlDB, err := ttl.NewTTL(dbPath)
if err != nil {
return err
}
// create a listener on TCP port
lis, err := net.Listen("tcp", fmt.Sprintf(":%s", config.PsPort))
if err != nil {
return err
}
defer func() {
if err := lis.Close(); err != nil {
zap.S().Fatalf("Error in listening: %v\n", err)
}
}()
// create a server instance
s := server.Server{PieceStoreDir: dataDir, DB: ttlDB}
// create a gRPC server object
grpcServer := grpc.NewServer()
// attach the api service to the server
pb.RegisterPieceStoreRoutesServer(grpcServer, &s)
// routinely check DB and delete expired entries
go func() {
err := s.DB.DBCleanup(dataDir)
zap.S().Fatalf("Error in DBCleanup: %v\n", err)
}()
fmt.Printf("Node %s started\n", config.NodeID)
// start the server
if err := grpcServer.Serve(lis); err != nil {
zap.S().Fatalf("failed to serve: %s\n", err)
}
return nil
}

View File

@ -1,13 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"storj.io/storj/cmd/piecestore-farmer/cmd"
"storj.io/storj/pkg/process"
)
func main() {
process.Execute(cmd.RootCmd)
}

View File

@ -4,15 +4,25 @@
package main
import (
"log"
"github.com/spf13/cobra"
"go.uber.org/zap"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/pkg/process"
"storj.io/storj/pkg/statdb"
)
func main() {
err := process.Main(process.ConfigEnvironment, &statdb.Service{})
if err != nil {
log.Fatal(err)
}
process.Exec(&cobra.Command{
Use: "statdb",
Short: "statdb",
RunE: run,
})
}
func run(cmd *cobra.Command, args []string) error {
s := &statdb.Service{}
s.SetLogger(zap.L())
s.SetMetricHandler(monkit.Default)
return s.Process(process.Ctx(cmd), cmd, args)
}

View File

@ -1,31 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package main
import (
"fmt"
"github.com/spf13/cobra"
"go.uber.org/zap"
"storj.io/storj/pkg/process"
)
func main() {
root := &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
zap.S().Debugf("hello world")
fmt.Println("hello world was logged to debug")
},
}
root.AddCommand(&cobra.Command{
Use: "subcommand",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("yay")
},
})
process.Execute(root)
}

View File

@ -4,12 +4,10 @@
package main
import (
"context"
"flag"
"fmt"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"storj.io/storj/pkg/process"
"storj.io/storj/pkg/telemetry"
@ -20,10 +18,15 @@ var (
)
func main() {
process.Must(process.Main(func() (*viper.Viper, error) { return nil, nil }, process.ServiceFunc(run)))
process.Exec(&cobra.Command{
Use: "metric-receiver",
Short: "receive metrics",
RunE: run,
})
}
func run(ctx context.Context, _ *cobra.Command, _ []string) error {
func run(cmd *cobra.Command, args []string) (err error) {
ctx := process.Ctx(cmd)
s, err := telemetry.Listen(*addr)
if err != nil {
return err

View File

@ -4,21 +4,23 @@
package main
import (
"context"
"flag"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"storj.io/storj/pkg/process"
)
func main() {
flag.Set("metrics.interval", "1s")
process.Must(process.Main(func() (*viper.Viper, error) { return nil, nil }, process.ServiceFunc(run)))
process.Exec(&cobra.Command{
Use: "metric-sender",
Short: "send metrics",
RunE: run,
})
}
func run(ctx context.Context, _ *cobra.Command, _ []string) error {
func run(cmd *cobra.Command, args []string) error {
// just go to sleep and let the background telemetry start sending
select {}
}

View File

@ -5,14 +5,12 @@ package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"sort"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/urfave/cli"
"github.com/zeebo/errs"
@ -22,7 +20,7 @@ import (
var argError = errs.Class("argError")
func run(ctx context.Context, _ *cobra.Command, _ []string) error {
func run(_ *cobra.Command, args []string) error {
app := cli.NewApp()
app.Name = "Piece Store CLI"
@ -142,9 +140,13 @@ func run(ctx context.Context, _ *cobra.Command, _ []string) error {
sort.Sort(cli.FlagsByName(app.Flags))
sort.Sort(cli.CommandsByName(app.Commands))
return app.Run(append([]string{os.Args[0]}, flag.Args()...))
return app.Run(append([]string{os.Args[0]}, args...))
}
func main() {
process.Must(process.Main(func() (*viper.Viper, error) { return nil, nil }, process.ServiceFunc(run)))
process.Exec(&cobra.Command{
Use: "piecestore-cli",
Short: "piecestore example cli",
RunE: run,
})
}

View File

@ -4,81 +4,10 @@
package process
import (
"context"
"flag"
"log"
"os"
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
// ReadFlags will read in and bind flags for viper and pflag
func readFlags() {
pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
pflag.Parse()
err := viper.BindPFlags(pflag.CommandLine)
if err != nil {
log.Print("error parsing command line flags into viper:", err)
}
}
// get default config folder
func configPath() string {
home, _ := homedir.Dir()
return filepath.Join(home, ".storj")
}
// get default config file
func defaultConfigFile(name string) string {
return filepath.Join(configPath(), name)
}
func generateConfig() error {
err := viper.WriteConfigAs(defaultConfigFile("main.json"))
return err
}
// ConfigEnvironment will read in command line flags, set the name of the config file,
// then look for configs in the current working directory and in $HOME/.storj
func ConfigEnvironment() (*viper.Viper, error) {
viper.SetEnvPrefix("storj")
viper.AutomaticEnv()
viper.SetConfigName("main")
viper.AddConfigPath(".")
viper.AddConfigPath(configPath())
// Override default config with a specific config
cfgFile := flag.String("config", "", "config file")
generate := flag.Bool("generate", false, "generate a default config in ~/.storj")
// if that file exists, set it as the config instead of reading in from default locations
if *cfgFile != "" && fileExists(*cfgFile) {
viper.SetConfigFile(*cfgFile)
}
err := viper.ReadInConfig()
if err != nil {
log.Print("could not read config file; defaulting to command line flags for configuration")
}
readFlags()
if *generate == true {
err := generateConfig()
if err != nil {
log.Print("unable to generate config file.", err)
}
}
v := viper.GetViper()
return v, nil
}
// check if file exists, handle error correctly if it doesn't
func fileExists(path string) bool {
_, err := os.Stat(path)
@ -90,48 +19,3 @@ func fileExists(path string) bool {
}
return true
}
// Execute runs a *cobra.Command and sets up Storj-wide process configuration
// like a configuration file and logging.
func Execute(cmd *cobra.Command) {
cobra.OnInitialize(func() {
_, err := ConfigEnvironment()
if err != nil {
log.Fatal("error configuring environment", err)
}
})
Must(cmd.Execute())
}
// Main runs a Service
func Main(configFn func() (*viper.Viper, error), s ...Service) error {
if _, err := configFn(); err != nil {
return err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
errors := make(chan error, len(s))
for _, service := range s {
go func(ctx context.Context, s Service, ch <-chan error) {
errors <- CtxService(s)(&cobra.Command{}, pflag.Args())
}(ctx, service, errors)
}
select {
case <-ctx.Done():
return nil
case err := <-errors:
return err
}
}
// Must checks for errors
func Must(err error) {
if err != nil {
log.Fatal(err)
}
}

View File

@ -1,91 +0,0 @@
// Copyright (C) 2018 Storj Labs, Inc.
// See LICENSE for copying information.
package process_test
import (
"context"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"go.uber.org/zap"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/pkg/process"
)
type MockedService struct {
mock.Mock
}
func (m *MockedService) InstanceID() string {
return ""
}
func (m *MockedService) Process(ctx context.Context, cmd *cobra.Command, args []string) error {
arguments := m.Called(ctx, cmd, args)
return arguments.Error(0)
}
func (m *MockedService) SetLogger(*zap.Logger) error {
args := m.Called()
return args.Error(0)
}
func (m *MockedService) SetMetricHandler(*monkit.Registry) error {
args := m.Called()
return args.Error(0)
}
func TestMainSingleProcess(t *testing.T) {
mockService := new(MockedService)
mockService.On("SetLogger", mock.Anything).Return(nil)
mockService.On("SetMetricHandler", mock.Anything).Return(nil)
mockService.On("Process", mock.Anything, mock.Anything, mock.Anything).Return(nil)
assert.Nil(t, process.Main(func() (*viper.Viper, error) { return nil, nil }, mockService))
mockService.AssertExpectations(t)
}
func TestMainMultipleProcess(t *testing.T) {
// TODO: Fix the async issues in this test
// mockService1 := MockedService{}
// mockService2 := MockedService{}
// mockService1.On("SetLogger", mock.Anything).Return(nil)
// mockService1.On("SetMetricHandler", mock.Anything).Return(nil)
// mockService1.On("Process", mock.Anything, mock.Anything, mock.Anything).Return(nil)
// mockService2.On("SetLogger", mock.Anything).Return(nil)
// mockService2.On("SetMetricHandler", mock.Anything).Return(nil)
// mockService2.On("Process", mock.Anything, mock.Anything, mock.Anything).Return(nil)
// assert.Nil(t, process.Main(func() error { return nil }, &mockService1, &mockService2))
// mockService1.AssertExpectations(t)
// mockService2.AssertExpectations(t)
t.Skip()
}
func TestMainProcessError(t *testing.T) {
mockService := MockedService{}
err := process.ErrLogger.New("Process Error")
mockService.On("SetLogger", mock.Anything).Return(nil)
mockService.On("SetMetricHandler", mock.Anything).Return(nil)
mockService.On("Process", mock.Anything, mock.Anything, mock.Anything).Return(err)
assert.Equal(t, err, process.Main(func() (*viper.Viper, error) { return nil, nil }, &mockService))
mockService.AssertExpectations(t)
}
func TestConfigEnvironment(t *testing.T) {
t.Skip()
}
func TestMust(t *testing.T) {
t.Skip()
}
func TestExecute(t *testing.T) {
t.Skip()
}

View File

@ -4,16 +4,9 @@
package process
import (
"context"
"flag"
"github.com/spf13/cobra"
"github.com/zeebo/errs"
"go.uber.org/zap"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"storj.io/storj/pkg/telemetry"
"storj.io/storj/pkg/utils"
)
var (
@ -22,109 +15,4 @@ var (
// Error is a process error class
Error = errs.Class("proc error")
// ErrUsage is a process error class
ErrUsage = errs.Class("usage error")
// ErrLogger Class
ErrLogger = errs.Class("Logger Error")
// ErrMetricHandler Class
ErrMetricHandler = errs.Class("Metric Handler Error")
//ErrProcess Class
ErrProcess = errs.Class("Process Error")
)
type idKey string
const (
id idKey = "SrvID"
)
// Service defines the interface contract for all Storj services
type Service interface {
// Process should run the program
Process(ctx context.Context, cmd *cobra.Command, args []string) error
SetLogger(*zap.Logger) error
SetMetricHandler(*monkit.Registry) error
// InstanceID should return a server or process instance identifier that is
// stable across restarts, or the empty string to use the first non-nil
// MAC address
InstanceID() string
}
// ServiceFunc allows one to implement a Service in terms of simply the Process
// method
type ServiceFunc func(ctx context.Context, cmd *cobra.Command,
args []string) error
// Process implements the Service interface and simply calls f
func (f ServiceFunc) Process(ctx context.Context, cmd *cobra.Command,
args []string) error {
return f(ctx, cmd, args)
}
// SetLogger implements the Service interface but is a no-op
func (f ServiceFunc) SetLogger(*zap.Logger) error { return nil }
// SetMetricHandler implements the Service interface but is a no-op
func (f ServiceFunc) SetMetricHandler(*monkit.Registry) error { return nil }
// InstanceID implements the Service interface and expects default behavior
func (f ServiceFunc) InstanceID() string { return "" }
// CtxRun is useful for generating cobra.Command.RunE methods that get
// a context
func CtxRun(fn func(ctx context.Context, cmd *cobra.Command,
args []string) error) func(cmd *cobra.Command, args []string) error {
return CtxService(ServiceFunc(fn))
}
// CtxService turns a Service into a cobra.Command.RunE method
func CtxService(s Service) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) (err error) {
instanceID := s.InstanceID()
if instanceID == "" {
instanceID = telemetry.DefaultInstanceID()
}
ctx := context.WithValue(context.Background(), id, instanceID)
registry := monkit.Default
scope := registry.ScopeNamed("process")
defer scope.TaskNamed("main")(&ctx)(&err)
logger, err := utils.NewLogger(*logDisposition,
zap.Fields(zap.String(string(id), instanceID)))
if err != nil {
return err
}
defer func() { _ = logger.Sync() }()
defer zap.ReplaceGlobals(logger)()
defer zap.RedirectStdLog(logger)()
if err := s.SetLogger(logger); err != nil {
logger.Error("failed to configure logger", zap.Error(err))
}
if err := s.SetMetricHandler(registry); err != nil {
logger.Error("failed to configure metric handler", zap.Error(err))
}
err = initMetrics(ctx, registry, instanceID)
if err != nil {
logger.Error("failed to configure telemetry", zap.Error(err))
}
err = initDebug(logger, registry)
if err != nil {
logger.Error("failed to start debug endpoints", zap.Error(err))
}
return s.Process(ctx, cmd, args)
}
}