diff --git a/cmd/captplanet/main.go b/cmd/captplanet/main.go index abbd23dec..945732117 100644 --- a/cmd/captplanet/main.go +++ b/cmd/captplanet/main.go @@ -4,11 +4,9 @@ package main import ( - "flag" "path/filepath" "github.com/spf13/cobra" - "github.com/spf13/pflag" monkit "gopkg.in/spacemonkeygo/monkit.v2" "storj.io/storj/pkg/process" ) @@ -25,7 +23,10 @@ var ( ) func main() { - pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - process.ExecuteWithConfig(rootCmd, - filepath.Join(defaultConfDir, "config.yaml")) + // process.Exec will load this for this command. + runCmd.Flags().String("config", + filepath.Join(defaultConfDir, "config.yaml"), "path to configuration") + setupCmd.Flags().String("config", + filepath.Join(defaultConfDir, "setup.yaml"), "path to configuration") + process.Exec(rootCmd) } diff --git a/cmd/captplanet/run.go b/cmd/captplanet/run.go index d304d2f9f..63270586a 100644 --- a/cmd/captplanet/run.go +++ b/cmd/captplanet/run.go @@ -5,8 +5,6 @@ package main import ( "fmt" - "net" - "path/filepath" "github.com/spf13/cobra" @@ -20,122 +18,76 @@ import ( "storj.io/storj/pkg/provider" ) +const ( + farmerCount = 40 +) + +type HeavyClient struct { + Identity provider.IdentityConfig + Kademlia kademlia.Config + PointerDB pointerdb.Config + Overlay overlay.Config +} + +type Farmer struct { + Identity provider.IdentityConfig + Kademlia kademlia.Config + Storage psservice.Config +} + var ( runCmd = &cobra.Command{ Use: "run", Short: "Run all providers", RunE: cmdRun, } - runCfg Config + + runCfg struct { + HeavyClient HeavyClient + Farmers [farmerCount]Farmer + Gateway miniogw.Config + } ) func init() { rootCmd.AddCommand(runCmd) - cfgstruct.Bind(runCmd.Flags(), &runCfg, - cfgstruct.ConfDir(defaultConfDir), - ) + cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir)) } func cmdRun(cmd *cobra.Command, args []string) (err error) { ctx := process.Ctx(cmd) defer mon.Task()(&ctx)(&err) - startingPort := runCfg.StartingPort - - errch := make(chan error, runCfg.FarmerCount+2) - - // define heavy client config programmatically - type HeavyClient struct { - Identity provider.IdentityConfig - Kademlia kademlia.Config - PointerDB pointerdb.Config - Overlay overlay.Config - } - - hc := HeavyClient{ - Identity: provider.IdentityConfig{ - CertPath: filepath.Join(runCfg.BasePath, "hc", "ident.leaf.cert"), - KeyPath: filepath.Join(runCfg.BasePath, "hc", "ident.leaf.key"), - Address: joinHostPort(runCfg.ListenHost, startingPort+1), - }, - Kademlia: kademlia.Config{ - TODOListenAddr: joinHostPort(runCfg.ListenHost, startingPort+2), - BootstrapAddr: joinHostPort(runCfg.ListenHost, startingPort+4), - }, - PointerDB: pointerdb.Config{ - DatabaseURL: "bolt://" + filepath.Join( - runCfg.BasePath, "hc", "pointerdb.db"), - }, - Overlay: overlay.Config{ - DatabaseURL: "bolt://" + filepath.Join( - runCfg.BasePath, "hc", "overlay.db"), - }, - } + errch := make(chan error, len(runCfg.Farmers)+2) // start heavy client go func() { - _, _ = fmt.Printf("starting heavy client on %s\n", hc.Identity.Address) - errch <- hc.Identity.Run(ctx, hc.Kademlia, hc.PointerDB, hc.Overlay) + _, _ = fmt.Printf("starting heavy client on %s\n", + runCfg.HeavyClient.Identity.Address) + errch <- runCfg.HeavyClient.Identity.Run(ctx, + runCfg.HeavyClient.Kademlia, + runCfg.HeavyClient.PointerDB, + runCfg.HeavyClient.Overlay) }() - // define and start a bunch of farmers programmatically - type Farmer struct { - Identity provider.IdentityConfig - Kademlia kademlia.Config - Storage psservice.Config - } - - for i := 0; i < runCfg.FarmerCount; i++ { - basepath := filepath.Join(runCfg.BasePath, fmt.Sprintf("f%d", i)) - farmer := Farmer{ - Identity: provider.IdentityConfig{ - CertPath: filepath.Join(basepath, "ident.leaf.cert"), - KeyPath: filepath.Join(basepath, "ident.leaf.key"), - Address: joinHostPort(runCfg.ListenHost, startingPort+i*2+3), - }, - Kademlia: kademlia.Config{ - TODOListenAddr: joinHostPort(runCfg.ListenHost, startingPort+i*2+4), - BootstrapAddr: joinHostPort(runCfg.ListenHost, startingPort+1), - }, - Storage: psservice.Config{ - Path: filepath.Join(basepath, "data"), - }, - } + // start the farmers + for i := 0; i < len(runCfg.Farmers); i++ { go func(i int) { - _, _ = fmt.Printf("starting farmer %d grpc on %s, kad on %s\n", - i, farmer.Identity.Address, farmer.Kademlia.TODOListenAddr) - errch <- farmer.Identity.Run(ctx, farmer.Kademlia, farmer.Storage) + _, _ = fmt.Printf("starting farmer %d grpc on %s, kad on %s\n", i, + runCfg.Farmers[i].Identity.Address, + runCfg.Farmers[i].Kademlia.TODOListenAddr) + errch <- runCfg.Farmers[i].Identity.Run(ctx, + runCfg.Farmers[i].Kademlia, + runCfg.Farmers[i].Storage) }(i) } - // start s3 gateway - gw := miniogw.Config{ - IdentityConfig: provider.IdentityConfig{ - CertPath: filepath.Join(runCfg.BasePath, "gw", "ident.leaf.cert"), - KeyPath: filepath.Join(runCfg.BasePath, "gw", "ident.leaf.key"), - Address: joinHostPort(runCfg.ListenHost, startingPort), - }, - MinioConfig: runCfg.MinioConfig, - ClientConfig: miniogw.ClientConfig{ - OverlayAddr: joinHostPort( - runCfg.ListenHost, startingPort+1), - PointerDBAddr: joinHostPort( - runCfg.ListenHost, startingPort+1), - }, - RSConfig: runCfg.RSConfig, - } - gw.MinioConfig.MinioDir = filepath.Join(runCfg.BasePath, "gw", "minio") - // start s3 gateway go func() { _, _ = fmt.Printf("starting minio gateway on %s\n", - gw.IdentityConfig.Address) - errch <- gw.Run(ctx) + runCfg.Gateway.IdentityConfig.Address) + errch <- runCfg.Gateway.Run(ctx) }() return <-errch } - -func joinHostPort(host string, port int) string { - return net.JoinHostPort(host, fmt.Sprint(port)) -} diff --git a/cmd/captplanet/setup.go b/cmd/captplanet/setup.go index 1a462b03d..67f7bb8a6 100644 --- a/cmd/captplanet/setup.go +++ b/cmd/captplanet/setup.go @@ -5,25 +5,22 @@ package main import ( "fmt" + "net" "os" "path/filepath" "github.com/spf13/cobra" "storj.io/storj/pkg/cfgstruct" - "storj.io/storj/pkg/miniogw" "storj.io/storj/pkg/peertls" "storj.io/storj/pkg/process" ) // Config defines broad Captain Planet configuration type Config struct { - FarmerCount int `help:"number of farmers to run" default:"20"` BasePath string `help:"base path for captain planet storage" default:"$CONFDIR"` ListenHost string `help:"the host for providers to listen on" default:"127.0.0.1"` StartingPort int `help:"all providers will listen on ports consecutively starting with this one" default:"7777"` - miniogw.RSConfig - miniogw.MinioConfig } var ( @@ -54,7 +51,7 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) { return err } - for i := 0; i < setupCfg.FarmerCount; i++ { + for i := 0; i < len(runCfg.Farmers); i++ { farmerPath := filepath.Join(setupCfg.BasePath, fmt.Sprintf("f%d", i)) err = os.MkdirAll(farmerPath, 0700) if err != nil { @@ -78,5 +75,58 @@ func cmdSetup(cmd *cobra.Command, args []string) (err error) { return err } - return process.SaveConfig(cmd) + startingPort := setupCfg.StartingPort + + overrides := map[string]interface{}{ + "heavy-client.identity.cert-path": filepath.Join( + setupCfg.BasePath, "hc", "ident.leaf.cert"), + "heavy-client.identity.key-path": filepath.Join( + setupCfg.BasePath, "hc", "ident.leaf.key"), + "heavy-client.identity.address": joinHostPort( + setupCfg.ListenHost, startingPort+1), + "heavy-client.kademlia.todo-listen-addr": joinHostPort( + setupCfg.ListenHost, startingPort+2), + "heavy-client.kademlia.bootstrap-addr": joinHostPort( + setupCfg.ListenHost, startingPort+4), + "heavy-client.pointer-db.database-url": "bolt://" + filepath.Join( + setupCfg.BasePath, "hc", "pointerdb.db"), + "heavy-client.overlay.database-url": "bolt://" + filepath.Join( + setupCfg.BasePath, "hc", "overlay.db"), + "gateway.cert-path": filepath.Join( + setupCfg.BasePath, "gw", "ident.leaf.cert"), + "gateway.key-path": filepath.Join( + setupCfg.BasePath, "gw", "ident.leaf.key"), + "gateway.address": joinHostPort( + setupCfg.ListenHost, startingPort), + "gateway.overlay-addr": joinHostPort( + setupCfg.ListenHost, startingPort+1), + "gateway.pointer-db-addr": joinHostPort( + setupCfg.ListenHost, startingPort+1), + "gateway.minio-dir": filepath.Join( + setupCfg.BasePath, "gw", "minio"), + "pointer-db.auth.api-key": "abc123", + } + + for i := 0; i < len(runCfg.Farmers); i++ { + basepath := filepath.Join(setupCfg.BasePath, fmt.Sprintf("f%d", i)) + farmer := fmt.Sprintf("farmers.%02d.", i) + overrides[farmer+"identity.cert-path"] = filepath.Join( + basepath, "ident.leaf.cert") + overrides[farmer+"identity.key-path"] = filepath.Join( + basepath, "ident.leaf.key") + overrides[farmer+"identity.address"] = joinHostPort( + setupCfg.ListenHost, startingPort+i*2+3) + overrides[farmer+"kademlia.todo-listen-addr"] = joinHostPort( + setupCfg.ListenHost, startingPort+i*2+4) + overrides[farmer+"kademlia.bootstrap-addr"] = joinHostPort( + setupCfg.ListenHost, startingPort+1) + overrides[farmer+"storage.path"] = filepath.Join(basepath, "data") + } + + return process.SaveConfig(runCmd.Flags(), + filepath.Join(setupCfg.BasePath, "config.yaml"), overrides) +} + +func joinHostPort(host string, port int) string { + return net.JoinHostPort(host, fmt.Sprint(port)) } diff --git a/cmd/gw/main.go b/cmd/gw/main.go index 3d25a86ae..1a57c1224 100644 --- a/cmd/gw/main.go +++ b/cmd/gw/main.go @@ -4,10 +4,8 @@ package main import ( - "fmt" "os" "path/filepath" - "strings" "github.com/spf13/cobra" @@ -22,67 +20,54 @@ var ( Use: "gw", Short: "Gateway", } + runCmd = &cobra.Command{ + Use: "run", + Short: "Run the gateway", + RunE: cmdRun, + } + setupCmd = &cobra.Command{ + Use: "setup", + Short: "Create config files", + RunE: cmdSetup, + } - cfg miniogw.Config + runCfg miniogw.Config + setupCfg struct { + BasePath string `default:"$CONFDIR" help:"base path for setup"` + } defaultConfDir = "$HOME/.storj/gw" ) func init() { - rootCmd.AddCommand(&cobra.Command{ - Use: "run", - Short: "Run the gateway", - RunE: cmdRun, - }) - rootCmd.AddCommand(&cobra.Command{ - Use: "setup", - Short: "Create config files", - RunE: cmdSetup, - }) - cfgstruct.Bind(rootCmd.PersistentFlags(), &cfg, - cfgstruct.ConfDir(defaultConfDir)) + rootCmd.AddCommand(runCmd) + rootCmd.AddCommand(setupCmd) + cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir)) + cfgstruct.Bind(setupCmd.Flags(), &setupCfg, cfgstruct.ConfDir(defaultConfDir)) } func cmdRun(cmd *cobra.Command, args []string) (err error) { - return cfg.Run(process.Ctx(cmd)) + return runCfg.Run(process.Ctx(cmd)) } func cmdSetup(cmd *cobra.Command, args []string) (err error) { - ctx := process.Ctx(cmd) - - // TODO: clean this up somehow? - if !strings.HasSuffix(cfg.IdentityConfig.CertPath, ".leaf.cert") { - return fmt.Errorf("certificate path should end with .leaf.cert") - } - certpath := strings.TrimSuffix(cfg.IdentityConfig.CertPath, ".leaf.cert") - if !strings.HasSuffix(cfg.IdentityConfig.KeyPath, ".leaf.key") { - return fmt.Errorf("key path should end with .leaf.key") - } - keypath := strings.TrimSuffix(cfg.IdentityConfig.KeyPath, ".leaf.key") - - err = os.MkdirAll(filepath.Dir(certpath), 0700) - if err != nil { - return err - } - err = os.MkdirAll(filepath.Dir(keypath), 0700) + err = os.MkdirAll(setupCfg.BasePath, 0700) if err != nil { return err } - _, err = peertls.NewTLSFileOptions(certpath, keypath, true, false) + identityPath := filepath.Join(setupCfg.BasePath, "identity") + _, err = peertls.NewTLSFileOptions(identityPath, identityPath, true, false) if err != nil { return err } - err = os.MkdirAll(filepath.Dir(process.CfgPath(ctx)), 0700) - if err != nil { - return err - } - - return process.SaveConfigAs(cmd, process.CfgPath(ctx)) + return process.SaveConfig(runCmd.Flags(), + filepath.Join(setupCfg.BasePath, "config.yaml"), nil) } func main() { - process.ExecuteWithConfig(rootCmd, - filepath.Join(defaultConfDir, "config.yaml")) + runCmd.Flags().String("config", + filepath.Join(defaultConfDir, "config.yaml"), "path to configuration") + process.Exec(rootCmd) } diff --git a/cmd/hc/main.go b/cmd/hc/main.go index 385f4f0ea..3bf3c504b 100644 --- a/cmd/hc/main.go +++ b/cmd/hc/main.go @@ -4,10 +4,8 @@ package main import ( - "fmt" "os" "path/filepath" - "strings" "github.com/spf13/cobra" @@ -20,80 +18,65 @@ import ( "storj.io/storj/pkg/provider" ) -const ( - defaultConfFolder = "$HOME/.storj/hc" -) - var ( rootCmd = &cobra.Command{ Use: "hc", Short: "Heavy client", } + runCmd = &cobra.Command{ + Use: "run", + Short: "Run the heavy client", + RunE: cmdRun, + } + setupCmd = &cobra.Command{ + Use: "setup", + Short: "Create config files", + RunE: cmdSetup, + } - cfg struct { + runCfg struct { Identity provider.IdentityConfig Kademlia kademlia.Config PointerDB pointerdb.Config Overlay overlay.Config } + setupCfg struct { + BasePath string `default:"$CONFDIR" help:"base path for setup"` + } + + defaultConfDir = "$HOME/.storj/hc" ) func init() { - rootCmd.AddCommand(&cobra.Command{ - Use: "run", - Short: "Run the heavy client", - RunE: cmdRun, - }) - rootCmd.AddCommand(&cobra.Command{ - Use: "setup", - Short: "Create config files", - RunE: cmdSetup, - }) - cfgstruct.Bind(rootCmd.PersistentFlags(), &cfg, - cfgstruct.ConfDir(defaultConfFolder)) + rootCmd.AddCommand(runCmd) + rootCmd.AddCommand(setupCmd) + cfgstruct.Bind(runCmd.Flags(), &runCfg, cfgstruct.ConfDir(defaultConfDir)) + cfgstruct.Bind(setupCmd.Flags(), &setupCfg, cfgstruct.ConfDir(defaultConfDir)) } func cmdRun(cmd *cobra.Command, args []string) (err error) { - return cfg.Identity.Run(process.Ctx(cmd), - cfg.Kademlia, cfg.PointerDB, cfg.Overlay) + return runCfg.Identity.Run(process.Ctx(cmd), + runCfg.Kademlia, runCfg.PointerDB, runCfg.Overlay) } func cmdSetup(cmd *cobra.Command, args []string) (err error) { - ctx := process.Ctx(cmd) - - // TODO: clean this up somehow? - if !strings.HasSuffix(cfg.Identity.CertPath, ".leaf.cert") { - return fmt.Errorf("certificate path should end with .leaf.cert") - } - certpath := strings.TrimSuffix(cfg.Identity.CertPath, ".leaf.cert") - if !strings.HasSuffix(cfg.Identity.KeyPath, ".leaf.key") { - return fmt.Errorf("key path should end with .leaf.key") - } - keypath := strings.TrimSuffix(cfg.Identity.KeyPath, ".leaf.key") - - err = os.MkdirAll(filepath.Dir(certpath), 0700) - if err != nil { - return err - } - err = os.MkdirAll(filepath.Dir(keypath), 0700) + err = os.MkdirAll(setupCfg.BasePath, 0700) if err != nil { return err } - _, err = peertls.NewTLSFileOptions(certpath, keypath, true, false) + identityPath := filepath.Join(setupCfg.BasePath, "identity") + _, err = peertls.NewTLSFileOptions(identityPath, identityPath, true, false) if err != nil { return err } - err = os.MkdirAll(filepath.Dir(process.CfgPath(ctx)), 0700) - if err != nil { - return err - } - - return process.SaveConfigAs(cmd, process.CfgPath(ctx)) + return process.SaveConfig(runCmd.Flags(), + filepath.Join(setupCfg.BasePath, "config.yaml"), nil) } func main() { - process.ExecuteWithConfig(rootCmd, - filepath.Join(defaultConfFolder, "config.yaml")) + runCmd.Flags().String("config", + filepath.Join(defaultConfDir, "config.yaml"), "path to configuration") + process.Exec(rootCmd) } diff --git a/pkg/process/exec_conf.go b/pkg/process/exec_conf.go index 062ac2cfd..90a0edc2a 100644 --- a/pkg/process/exec_conf.go +++ b/pkg/process/exec_conf.go @@ -8,7 +8,6 @@ import ( "flag" "log" "os" - "strings" "sync" "github.com/spf13/cobra" @@ -23,9 +22,15 @@ import ( // ExecuteWithConfig runs a Cobra command with the provided default config func ExecuteWithConfig(cmd *cobra.Command, defaultConfig string) { - cfgFile := flag.String("config", os.ExpandEnv(defaultConfig), "config file") + flag.String("config", os.ExpandEnv(defaultConfig), "config file") + Exec(cmd) +} + +// Exec runs a Cobra command. If a "config" flag is defined it will be parsed +// and loaded using viper. +func Exec(cmd *cobra.Command) { pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - cleanup(cmd, cfgFile) + cleanup(cmd) _ = cmd.Execute() } @@ -36,6 +41,39 @@ var ( contexts = map[*cobra.Command]context.Context{} ) +// SaveConfig will save all flags with default values to outfilewith specific +// values specified in 'overrides' overridden. +func SaveConfig(flagset *pflag.FlagSet, outfile string, + overrides map[string]interface{}) error { + + vip := viper.New() + err := vip.BindPFlags(pflag.CommandLine) + if err != nil { + return err + } + flagset.VisitAll(func(f *pflag.Flag) { + // stop processing if we hit an error on a BindPFlag call + if err != nil { + return + } + if f.Name == "config" { + return + } + err = vip.BindPFlag(f.Name, f) + }) + if err != nil { + return err + } + + if overrides != nil { + for key, val := range overrides { + vip.Set(key, val) + } + } + + return vip.WriteConfigAs(os.ExpandEnv(outfile)) +} + // Ctx returns the appropriate context.Context for ExecuteWithConfig commands func Ctx(cmd *cobra.Command) context.Context { contextMtx.Lock() @@ -47,44 +85,9 @@ func Ctx(cmd *cobra.Command) context.Context { return ctx } -type ctxKey int - -const ( - ctxKeyVip ctxKey = iota - ctxKeyCfg -) - -// SaveConfig outputs the configuration to the configured (or default) config -// file given to ExecuteWithConfig -func SaveConfig(cmd *cobra.Command) error { - ctx := Ctx(cmd) - return getViper(ctx).WriteConfigAs(CfgPath(ctx)) -} - -// SaveConfigAs outputs the configuration to the provided path assuming the -// command was executed with ExecuteWithConfig -func SaveConfigAs(cmd *cobra.Command, path string) error { - return getViper(Ctx(cmd)).WriteConfigAs(path) -} - -func getViper(ctx context.Context) *viper.Viper { - if v, ok := ctx.Value(ctxKeyVip).(*viper.Viper); ok { - return v - } - return nil -} - -// CfgPath returns the configuration path used with ExecuteWithConfig -func CfgPath(ctx context.Context) string { - if v, ok := ctx.Value(ctxKeyCfg).(string); ok { - return v - } - return "" -} - -func cleanup(cmd *cobra.Command, cfgFile *string) { +func cleanup(cmd *cobra.Command) { for _, ccmd := range cmd.Commands() { - cleanup(ccmd, cfgFile) + cleanup(ccmd) } if cmd.Run != nil { panic("Please use cobra's RunE instead of Run") @@ -104,16 +107,22 @@ func cleanup(cmd *cobra.Command, cfgFile *string) { } vip.SetEnvPrefix("storj") vip.AutomaticEnv() - if *cfgFile != "" && fileExists(*cfgFile) { - vip.SetConfigFile(*cfgFile) - err = vip.ReadInConfig() - if err != nil { - return err + + cfgFlag := cmd.Flags().Lookup("config") + if cfgFlag != nil && cfgFlag.Value.String() != "" { + path := os.ExpandEnv(cfgFlag.Value.String()) + if cfgFlag.Changed || fileExists(path) { + vip.SetConfigFile(path) + err = vip.ReadInConfig() + if err != nil { + return err + } } } // go back and propagate changed config values to appropriate flags var brokenKeys []string + var brokenVals []string for _, key := range vip.AllKeys() { if cmd.Flags().Lookup(key) == nil { // flag couldn't be found @@ -122,14 +131,11 @@ func cleanup(cmd *cobra.Command, cfgFile *string) { err := cmd.Flags().Set(key, vip.GetString(key)) if err != nil { // flag couldn't be set - brokenKeys = append(brokenKeys, key) + brokenVals = append(brokenVals, key) } } } - ctx = context.WithValue(ctx, ctxKeyVip, vip) - ctx = context.WithValue(ctx, ctxKeyCfg, *cfgFile) - logger, err := utils.NewLogger(*logDisposition) if err != nil { return err @@ -139,12 +145,11 @@ func cleanup(cmd *cobra.Command, cfgFile *string) { defer zap.RedirectStdLog(logger)() // okay now that logging is working, inform about the broken keys - // these keys are almost certainly broken because they have capital - // letters - if len(brokenKeys) > 0 { - logger.Sugar().Infof("TODO: these flags are not configurable via "+ - "config file, probably due to having uppercase letters: %s", - strings.Join(brokenKeys, ", ")) + for _, key := range brokenKeys { + logger.Sugar().Infof("Invalid configuration file key: %s", key) + } + for _, key := range brokenVals { + logger.Sugar().Infof("Invalid configuration file value for key: %s", key) } err = initMetrics(ctx, monkit.Default,