Command line flags features and cleanup (#2068)

* change BindSetup to be an option to Bind
* add process.Bind to allow composite structures
* hack fix for noprefix flags
* used tagged version of structs

Before this PR, some flags were created by calling `cfgstruct.Bind` and having their fields create a flag. Once the flags were parsed, `viper` was used to acquire all the values from them and config files, and the fields in the struct were set through the flag interface.

This doesn't work for slices of things on config structs very well, since it can only set strings, and for a string slice, it turns out that the implementation in `pflag` appends an entry rather than setting it.

This changes three things:

1. Only have a `Bind` call instead of `Bind` and `BindSetup`, and make `BindSetup` an option instead.
2. Add a `process.Bind` call that takes in a `*cobra.Cmd`, binds the struct to the command's flags, and keeps track of that struct in a global map keyed by the command.
3. Use `viper` to get the values and load them into the bound configuration structs instead of using the flags to propagate the changes.

In this way, we can support whatever rich configuration we want in the config yaml files, while still getting command like flags when important.
This commit is contained in:
Jeff Wendling 2019-05-29 17:56:22 +00:00 committed by GitHub
parent 771271e7b8
commit e74cac52ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 132 additions and 114 deletions

View File

@ -56,8 +56,8 @@ func init() {
defaults := cfgstruct.DefaultsFlag(rootCmd)
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(setupCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(setupCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir), cfgstruct.SetupMode())
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {

View File

@ -82,15 +82,15 @@ func main() {
authCmd.AddCommand(authInfoCmd)
authCmd.AddCommand(authExportCmd)
cfgstruct.Bind(authCreateCmd.Flags(), &config, defaults, cfgstruct.ConfDir(confDir))
cfgstruct.Bind(authInfoCmd.Flags(), &config, defaults, cfgstruct.ConfDir(confDir))
cfgstruct.Bind(authExportCmd.Flags(), &config, defaults, cfgstruct.ConfDir(confDir))
cfgstruct.Bind(runCmd.Flags(), &config, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &config, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(signCmd.Flags(), &signCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(verifyCmd.Flags(), &verifyCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(claimsExportCmd.Flags(), &claimsExportCfg, defaults, cfgstruct.ConfDir(confDir))
cfgstruct.Bind(claimDeleteCmd.Flags(), &claimsDeleteCfg, defaults, cfgstruct.ConfDir(confDir))
process.Bind(authCreateCmd, &config, defaults, cfgstruct.ConfDir(confDir))
process.Bind(authInfoCmd, &config, defaults, cfgstruct.ConfDir(confDir))
process.Bind(authExportCmd, &config, defaults, cfgstruct.ConfDir(confDir))
process.Bind(runCmd, &config, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(setupCmd, &config, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir), cfgstruct.SetupMode())
process.Bind(signCmd, &signCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(verifyCmd, &verifyCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(claimsExportCmd, &claimsExportCfg, defaults, cfgstruct.ConfDir(confDir))
process.Bind(claimDeleteCmd, &claimsDeleteCfg, defaults, cfgstruct.ConfDir(confDir))
process.Exec(rootCmd)
}

View File

@ -74,8 +74,8 @@ func init() {
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(setupCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(setupCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir), cfgstruct.SetupMode())
}
func cmdSetup(cmd *cobra.Command, args []string) (err error) {

View File

@ -52,7 +52,7 @@ var (
func init() {
defaults = cfgstruct.DefaultsFlag(rootCmd)
rootCmd.AddCommand(keyGenerateCmd)
cfgstruct.Bind(keyGenerateCmd.Flags(), &keyCfg, defaults)
process.Bind(keyGenerateCmd, &keyCfg, defaults)
}
func cmdKeyGenerate(cmd *cobra.Command, args []string) (err error) {

View File

@ -106,11 +106,11 @@ func init() {
caCmd.AddCommand(revokeCACmd)
caCmd.AddCommand(revokePeerCACmd)
cfgstruct.Bind(newCACmd.Flags(), &newCACfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(getIDCmd.Flags(), &getIDCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(caExtCmd.Flags(), &caExtCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(revokeCACmd.Flags(), &revokeCACfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(revokePeerCACmd.Flags(), &revokePeerCACfg, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(newCACmd, &newCACfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(getIDCmd, &getIDCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(caExtCmd, &caExtCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(revokeCACmd, &revokeCACfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(revokePeerCACmd, &revokePeerCACfg, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
}
func cmdNewCA(cmd *cobra.Command, args []string) error {

View File

@ -11,6 +11,7 @@ import (
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/process"
)
var (
@ -67,9 +68,9 @@ func init() {
idCmd.AddCommand(leafExtCmd)
idCmd.AddCommand(revokeLeafCmd)
cfgstruct.Bind(newIDCmd.Flags(), &newIDCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(leafExtCmd.Flags(), &leafExtCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(revokeLeafCmd.Flags(), &revokeLeafCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(newIDCmd, &newIDCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(leafExtCmd, &leafExtCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(revokeLeafCmd, &revokeLeafCfg, defaults, cfgstruct.IdentityDir(defaultIdentityDir))
}
func cmdNewID(cmd *cobra.Command, args []string) (err error) {

View File

@ -69,8 +69,8 @@ func init() {
rootCmd.AddCommand(newServiceCmd)
rootCmd.AddCommand(authorizeCmd)
cfgstruct.Bind(newServiceCmd.Flags(), &config, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
cfgstruct.Bind(authorizeCmd.Flags(), &config, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(newServiceCmd, &config, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(authorizeCmd, &config, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
}
func main() {

View File

@ -14,6 +14,7 @@ import (
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/identity"
"storj.io/storj/pkg/process"
)
var (
@ -33,7 +34,7 @@ var (
func init() {
rootCmd.AddCommand(revocationsCmd)
cfgstruct.Bind(revocationsCmd.Flags(), &revCfg, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
process.Bind(revocationsCmd, &revCfg, defaults, cfgstruct.ConfDir(defaultConfigDir), cfgstruct.IdentityDir(defaultIdentityDir))
}
func cmdRevocations(cmd *cobra.Command, args []string) error {

View File

@ -45,8 +45,8 @@ func init() {
defaults := cfgstruct.DefaultsFlag(rootCmd)
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(listCmd)
cfgstruct.Bind(addCmd.Flags(), &cacheCfg, defaults)
cfgstruct.Bind(listCmd.Flags(), &cacheCfg, defaults)
process.Bind(addCmd, &cacheCfg, defaults)
process.Bind(listCmd, &cacheCfg, defaults)
}
func cmdList(cmd *cobra.Command, args []string) (err error) {

View File

@ -98,11 +98,11 @@ func init() {
rootCmd.AddCommand(qdiagCmd)
rootCmd.AddCommand(reportsCmd)
reportsCmd.AddCommand(nodeUsageCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(diagCmd.Flags(), &diagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(qdiagCmd.Flags(), &qdiagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(nodeUsageCmd.Flags(), &nodeUsageCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(setupCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir), cfgstruct.SetupMode())
process.Bind(diagCmd, &diagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(qdiagCmd, &qdiagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(nodeUsageCmd, &nodeUsageCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {

View File

@ -33,7 +33,7 @@ func main() {
RunE: Main,
}
defaults := cfgstruct.DefaultsFlag(cmd)
cfgstruct.Bind(cmd.Flags(), &Config, defaults, cfgstruct.ConfDir(defaultConfDir))
process.Bind(cmd, &Config, defaults, cfgstruct.ConfDir(defaultConfDir))
cmd.Flags().String("config", filepath.Join(defaultConfDir, "config.yaml"), "path to configuration")
process.Exec(cmd)
}

View File

@ -98,11 +98,11 @@ func init() {
rootCmd.AddCommand(configCmd)
rootCmd.AddCommand(diagCmd)
rootCmd.AddCommand(dashboardCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.BindSetup(configCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(diagCmd.Flags(), &diagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
cfgstruct.Bind(dashboardCmd.Flags(), &dashboardCfg, defaults, cfgstruct.ConfDir(defaultDiagDir))
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(setupCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir), cfgstruct.SetupMode())
process.Bind(configCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir), cfgstruct.SetupMode())
process.Bind(diagCmd, &diagCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.IdentityDir(identityDir))
process.Bind(dashboardCmd, &dashboardCfg, defaults, cfgstruct.ConfDir(defaultDiagDir))
}
func databaseConfig(config storagenode.Config) storagenodedb.Config {

View File

@ -16,6 +16,7 @@ import (
"storj.io/storj/internal/fpath"
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/process"
"storj.io/storj/pkg/storj"
"storj.io/storj/uplink"
)
@ -50,7 +51,7 @@ func addCmd(cmd *cobra.Command, root *cobra.Command) *cobra.Command {
defaultConfDir = confDirParam
}
cfgstruct.Bind(cmd.Flags(), &cfg, defaults, cfgstruct.ConfDir(defaultConfDir))
process.Bind(cmd, &cfg, defaults, cfgstruct.ConfDir(defaultConfDir))
return cmd
}

View File

@ -42,7 +42,7 @@ func init() {
cfgstruct.SetupFlag(zap.L(), RootCmd, &confDir, "config-dir", defaultConfDir, "main directory for uplink configuration")
defaults = cfgstruct.DefaultsFlag(RootCmd)
RootCmd.AddCommand(setupCmd)
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir))
process.Bind(setupCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.SetupMode())
}
func cmdSetup(cmd *cobra.Command, args []string) (err error) {

View File

@ -13,7 +13,6 @@ import (
"storj.io/storj/internal/fpath"
libuplink "storj.io/storj/lib/uplink"
"storj.io/storj/pkg/cfgstruct"
"storj.io/storj/pkg/encryption"
"storj.io/storj/pkg/macaroon"
"storj.io/storj/pkg/process"
@ -41,7 +40,7 @@ func init() {
RunE: shareMain,
}, RootCmd)
cfgstruct.Bind(shareCmd.Flags(), &shareCfg)
process.Bind(shareCmd, &shareCfg)
}
const shareISO8601 = "2006-01-02T15:04:05-0700"

View File

@ -50,8 +50,8 @@ func init() {
defaults := cfgstruct.DefaultsFlag(rootCmd)
rootCmd.AddCommand(runCmd)
rootCmd.AddCommand(setupCmd)
cfgstruct.Bind(runCmd.Flags(), &runCfg, defaults, cfgstruct.ConfDir(confDir))
cfgstruct.BindSetup(setupCmd.Flags(), &setupCfg, defaults, cfgstruct.ConfDir(confDir))
process.Bind(runCmd, &runCfg, defaults, cfgstruct.ConfDir(confDir))
process.Bind(setupCmd, &setupCfg, defaults, cfgstruct.ConfDir(confDir), cfgstruct.SetupMode())
}
func cmdRun(cmd *cobra.Command, args []string) (err error) {

3
go.mod
View File

@ -102,9 +102,10 @@ require (
github.com/vivint/infectious v0.0.0-20190108171102-2455b059135b
github.com/yuin/gopher-lua v0.0.0-20180918061612-799fa34954fb // indirect
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c
github.com/zeebo/errs v1.1.0
github.com/zeebo/errs v1.1.1
github.com/zeebo/float16 v0.1.0 // indirect
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 // indirect
github.com/zeebo/structs v1.0.1
go.etcd.io/bbolt v1.3.2 // indirect
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect

10
go.sum
View File

@ -340,6 +340,8 @@ github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
@ -369,12 +371,16 @@ github.com/yuin/gopher-lua v0.0.0-20180918061612-799fa34954fb h1:Jmfk7z2f/+gxVFA
github.com/yuin/gopher-lua v0.0.0-20180918061612-799fa34954fb/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c h1:WoYvMZp+keiJz+ZogLAhwsUZvWe81W+mCnpfdgEUOl4=
github.com/zeebo/admission v0.0.0-20180821192747-f24f2a94a40c/go.mod h1:Aq7yiXoKLFIDzh4eR6EG4owIO9alpttZ0XJ5c/z/QrE=
github.com/zeebo/errs v1.1.0 h1:4dNyQKsWPyBDqLzZUpx+QMP0Qil9STQPdBsKk6+O2qA=
github.com/zeebo/errs v1.1.0/go.mod h1:Yj8dHrUQwls1bF3dr/vcSIu+qf4mI7idnTcHfoACc6I=
github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6 h1:bs7mSHVrLRQHxqWcm0hyez0sA23YuXb8Pwnq5NhyZ8A=
github.com/zeebo/assert v0.0.0-20181109011804-10f827ce2ed6/go.mod h1:yssERNPivllc1yU3BvpjYI5BUW+zglcz6QWqeVRL5t0=
github.com/zeebo/errs v1.1.1 h1:Cs5Noqj/tj3Ql/hLkD9WdumKlssx/IN2zr7CRGNOKZA=
github.com/zeebo/errs v1.1.1/go.mod h1:Yj8dHrUQwls1bF3dr/vcSIu+qf4mI7idnTcHfoACc6I=
github.com/zeebo/float16 v0.1.0 h1:kRqxv5og6z1emEyz5FpW0/BVHe5VfxEAw6b1ljCZlUc=
github.com/zeebo/float16 v0.1.0/go.mod h1:fssGvvXu+XS8MH57cKmyrLB/cqioYeYX/2mXCN3a5wo=
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54 h1:+cwNE5KJ3pika4HuzmDHkDlK5myo0G9Sv+eO7WWxnUQ=
github.com/zeebo/incenc v0.0.0-20180505221441-0d92902eec54/go.mod h1:EI8LcOBDlSL3POyqwC1eJhOYlMBMidES+613EtmmT5w=
github.com/zeebo/structs v1.0.1 h1:MopCKn+ah1DF83tdMjcN+1V/gFpPO8eUnXEaiarwFLI=
github.com/zeebo/structs v1.0.1/go.mod h1:LphfpprlqJQcbCq+eA3iIK/NsejMwk9mlfH/tM1XuKQ=
go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=

View File

@ -22,8 +22,9 @@ import (
// BindOpt is an option for the Bind method
type BindOpt struct {
isDev *bool
varfn func(vars map[string]confVar)
isDev *bool
isSetup *bool
varfn func(vars map[string]confVar)
}
// ConfDir sets variables for default options called $CONFDIR and $CONFNAME.
@ -31,18 +32,6 @@ func ConfDir(path string) BindOpt {
val := filepath.Clean(os.ExpandEnv(path))
return BindOpt{varfn: func(vars map[string]confVar) {
vars["CONFDIR"] = confVar{val: val, nested: false}
vars["CONFNAME"] = confVar{val: val, nested: false}
}}
}
// ConfDirNested sets variables for default options called $CONFDIR and $CONFNAME.
// ConfDirNested also appends the parent struct field name to the paths before
// descending into substructs.
func ConfDirNested(confdir string) BindOpt {
val := filepath.Clean(os.ExpandEnv(confdir))
return BindOpt{varfn: func(vars map[string]confVar) {
vars["CONFDIR"] = confVar{val: val, nested: true}
vars["CONFNAME"] = confVar{val: val, nested: true}
}}
}
@ -54,6 +43,13 @@ func IdentityDir(path string) BindOpt {
}}
}
// SetupMode issues the bind in a mode where it does not ignore fields with the
// `setup:"true"` tag.
func SetupMode() BindOpt {
setup := true
return BindOpt{isSetup: &setup}
}
// UseDevDefaults forces the bind call to use development defaults unless
// UseReleaseDefaults is provided as a subsequent option.
// Without either, Bind will default to determining which defaults to use
@ -79,24 +75,18 @@ type confVar struct {
// Bind sets flags on a FlagSet that match the configuration struct
// 'config'. This works by traversing the config struct using the 'reflect'
// package. Will ignore fields with `setup:"true"` tag.
func Bind(flags FlagSet, config interface{}, opts ...BindOpt) {
bind(flags, config, false, opts...)
}
// BindSetup sets flags on a FlagSet that match the configuration struct
// 'config'. This works by traversing the config struct using the 'reflect'
// package.
func BindSetup(flags FlagSet, config interface{}, opts ...BindOpt) {
bind(flags, config, true, opts...)
func Bind(flags FlagSet, config interface{}, opts ...BindOpt) {
bind(flags, config, opts...)
}
func bind(flags FlagSet, config interface{}, setupCommand bool, opts ...BindOpt) {
func bind(flags FlagSet, config interface{}, opts ...BindOpt) {
ptrtype := reflect.TypeOf(config)
if ptrtype.Kind() != reflect.Ptr {
panic(fmt.Sprintf("invalid config type: %#v. Expecting pointer to struct.", config))
}
isDev := !version.Build.Release
setupCommand := false
vars := map[string]confVar{}
for _, opt := range opts {
if opt.varfn != nil {
@ -105,6 +95,9 @@ func bind(flags FlagSet, config interface{}, setupCommand bool, opts ...BindOpt)
if opt.isDev != nil {
isDev = *opt.isDev
}
if opt.isSetup != nil {
setupCommand = *opt.isSetup
}
}
bindConfig(flags, "", reflect.ValueOf(config).Elem(), vars, setupCommand, false, isDev)
@ -117,7 +110,7 @@ func bindConfig(flags FlagSet, prefix string, val reflect.Value, vars map[string
typ := val.Type()
resolvedVars := make(map[string]string, len(vars))
{
structpath := strings.Replace(prefix, ".", "/", -1)
structpath := strings.Replace(prefix, ".", string(filepath.Separator), -1)
for k, v := range vars {
if !v.nested {
resolvedVars[k] = v.val

View File

@ -5,7 +5,6 @@ package cfgstruct
import (
"fmt"
"path/filepath"
"reflect"
"testing"
"time"
@ -94,23 +93,6 @@ func TestConfDir(t *testing.T) {
assertEqual(f.Lookup("my-struct1.my-struct2.string").DefValue, "2confpath3")
}
func TestNesting(t *testing.T) {
f := pflag.NewFlagSet("test", pflag.PanicOnError)
var c struct {
String string `default:"-$CONFDIR+"`
MyStruct1 struct {
String string `default:"1${CONFDIR}2"`
MyStruct2 struct {
String string `default:"2${CONFDIR}3"`
}
}
}
Bind(f, &c, UseReleaseDefaults(), ConfDirNested("confpath"))
assertEqual(f.Lookup("string").DefValue, "-confpath+")
assertEqual(f.Lookup("my-struct1.string").DefValue, filepath.FromSlash("1confpath/my-struct12"))
assertEqual(f.Lookup("my-struct1.my-struct2.string").DefValue, filepath.FromSlash("2confpath/my-struct1/my-struct23"))
}
func TestBindDevDefaults(t *testing.T) {
f := pflag.NewFlagSet("test", pflag.PanicOnError)
var c struct {

View File

@ -22,12 +22,14 @@ import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/zeebo/errs"
"github.com/zeebo/structs"
"go.uber.org/zap"
monkit "gopkg.in/spacemonkeygo/monkit.v2"
"gopkg.in/spacemonkeygo/monkit.v2/collect"
"gopkg.in/spacemonkeygo/monkit.v2/present"
"storj.io/storj/internal/version"
"storj.io/storj/pkg/cfgstruct"
)
// DefaultCfgFilename is the default filename used for storing a configuration.
@ -38,8 +40,22 @@ var (
contextMtx sync.Mutex
contexts = map[*cobra.Command]context.Context{}
configMtx sync.Mutex
configs = map[*cobra.Command][]interface{}{}
)
// Bind sets flags on a command that match the configuration struct
// 'config'. It ensures that the config has all of the values loaded into it
// when the command runs.
func Bind(cmd *cobra.Command, config interface{}, opts ...cfgstruct.BindOpt) {
configMtx.Lock()
defer configMtx.Unlock()
cfgstruct.Bind(cmd.Flags(), config, opts...)
configs[cmd] = append(configs[cmd], config)
}
// Exec runs a Cobra command. If a "config" flag is defined it will be parsed
// and loaded using viper.
func Exec(cmd *cobra.Command) {
@ -195,30 +211,48 @@ func cleanup(cmd *cobra.Command) {
}
}
// 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
brokenKeys = append(brokenKeys, key)
} else {
flag := cmd.Flag(key)
// It's very hard to support string arrays from pflag
// because there's no way to unset some value. For now
// skip them. They can't be set from viper.
if flag.Value.Type() != "stringArray" {
oldChanged := flag.Changed
configMtx.Lock()
configValues := configs[cmd]
configMtx.Unlock()
err := cmd.Flags().Set(key, vip.GetString(key))
if err != nil {
// flag couldn't be set
brokenVals = append(brokenVals, key)
}
var (
brokenKeys = map[string]struct{}{}
missingKeys = map[string]struct{}{}
usedKeys = map[string]struct{}{}
allSettings = vip.AllSettings()
)
// revert Changed value
flag.Changed = oldChanged
}
// Hacky hack: these two keys are noprefix which breaks all scoping
if val, ok := allSettings["api-key"]; ok {
allSettings["client.api-key"] = val
delete(allSettings, "api-key")
}
if val, ok := allSettings["satellite-addr"]; ok {
allSettings["client.satellite-addr"] = val
delete(allSettings, "satellite-addr")
}
for _, config := range configValues {
// Decode and all of the resulting keys into our sets
res := structs.Decode(allSettings, config)
for key := range res.Used {
usedKeys[key] = struct{}{}
}
for key := range res.Missing {
missingKeys[key] = struct{}{}
}
for key := range res.Broken {
brokenKeys[key] = struct{}{}
}
}
// Filter the missing keys by removing ones that were used and ones that are flags.
for key := range usedKeys {
delete(missingKeys, key)
}
for key := range missingKeys {
if cmd.Flags().Lookup(key) != nil {
delete(missingKeys, key)
}
}
@ -243,11 +277,11 @@ func cleanup(cmd *cobra.Command) {
// okay now that logging is working, inform about the broken keys
if cmd.Annotations["type"] != "helper" {
for _, key := range brokenKeys {
for key := range missingKeys {
logger.Sugar().Infof("Invalid configuration file key: %s", key)
}
}
for _, key := range brokenVals {
for key := range brokenKeys {
logger.Sugar().Infof("Invalid configuration file value for key: %s", key)
}