-
Notifications
You must be signed in to change notification settings - Fork 499
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve config (closes #810) #834
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small changes are required.
Answering your questions:
friendbot-url is stored as a url.URL, but other URLs (e.g. db-url, stellar-core-db-url, stellar-core-url, redis-url, sentry-dsn etc.) are just strings. Should we change this in the future?
friendbot-url
is going to be removed eventually so I think we shouldn't spend more time on this.
tls-cert and tls-key have custom checking to ensure both are provided, but there are other pairs that could be similarly checked, e.g. redis-limit-redis-key and redis-url, loggly-token and loggly-tag (easy to do).
Can be checked in initConfig()
after all variables have been set to Config
.
Some numbers are uint but others are not. Not sure what the logic is for the uints.
Good point, let's change Port
and MaxDBConnections
to uint
.
services/horizon/main.go
Outdated
name string // e.g. "db-url" | ||
envVar string // e.g. "DATABASE_URL". Defaults to uppercase/underscore representation of name | ||
flagType flagType // e.g. boolFlag | ||
flagDefault interface{} // A default if no option is provided. Set to "" if no default |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's use nil
instead of ""
if no default value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use "" because flagDefault
is used to infer the type. We never set empty defaults for other types, so we can use the empty string as a type indicator. This allows us to determine the option type without the need to explicitly set it, saving a lot of extra configuration.
services/horizon/main.go
Outdated
// Check all required args were provided - needed for migrations check | ||
for i := range configOpts { | ||
co := &configOpts[i] | ||
co.require() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be moved to func (* configOption) init()
method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this idea. But I think we can't move the contents of both loops to the same method. They are executed at different times. This require
step only happens when the app is executed.
PS. you can consider closing #817 in this PR. |
Can't change |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other that new comments all LGTM!
services/horizon/main.go
Outdated
Name: "db-url", | ||
EnvVar: "DATABASE_URL", | ||
ConfigKey: &config.DatabaseURL, | ||
FlagDefault: "", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing this discussion here.
I use "" because flagDefault is used to infer the type.
I think it shouldn't be used to get type. It's like embedding two values in a single field. Instead we should use types.*
(types.Bool
, types.Uint
, types.String
, etc). The advantage is that it's clear what type we are dealing with and we can omit FlagDefault
if it's a zero value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This suggestion makes sense to me too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Unfortunately pflags
library requires a default value for all flags (see e.g. pflag.Bool), so I wrote a helper setDefault
to add the empty string if a FlagDefault
is not explicitly declared.
services/horizon/main.go
Outdated
stdLog.Fatal("Invalid config: stellar-core-url is blank. Please specify --stellar-core-url on the command line or set the STELLAR_CORE_URL environment variable.") | ||
// Initialise and validate the global configuration | ||
for _, co := range configOpts { | ||
co.SetValue() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Continuing this discussion here.
But I think we can't move the contents of both loops to the same method. They are executed at different times. This require step only happens when the app is executed.
Any reason why Require
and SetValue
can't be called along with other init steps in one place? You can actually move:
for _, co := range configOpts {
co.Init(rootCmd)
}
to initConfig()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can't be combined - they are needed at different stages of the option parsing lifecycle. I think this is why the code was originally written with both init()
and initConfig()
methods. You can test for yourself - if you move the code into a single loop, it will fail and display help text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I pulled the branch locally and indeed it wasn't possible with viper.BindPFlags
as it required all flags to be set upfront. However, after switching to viper.BindPFlag
(note: no s
) it was possible. I actually pushed this commit: 75b8658 hope you don't mine. I also removed flagType
as it was used in only a single method I actually changed and added error handling for viper
methods that return errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great, thanks for those improvements! 👍 Are we good to merge now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Last (promise!) round of minor comments.
Ok, fixed issue with location of pflags binding, addressed other comments. Please take another look, and I'd appreciate a quick test to make sure this runs for you as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Checked it locally and it works. I fixed a small comment issue in 1b08f7b.
* tag 'horizon-v0.16.0': horizon: 0.16.0 CHANGELOG (stellar#851) Allow smooth migration for failed transactions column (stellar#847) Clean up the redundancy in the Horizon command line option handling (stellar#834) horizon/actions: fix a bug in `GetPageQuery` and `GetAddress` (stellar#846) remove JSON horizon/actions: check hash before sending an event to the stream of orderbook (stellar#828) Deprecated check (stellar#838) Report right protocol version in horizon root path (stellar#783) fix migration ordering (stellar#821) Add failed transactions to ledger resource (stellar#756) Ignore "0" trades effects (stellar#791) Add package index to README (stellar#831) horizon/db2/history: optimize account queries for payments and operations (stellar#829) Update "gap detected" message (stellar#830) Update setTickInProgress comment (stellar#824) Bump postgres version for Travis (stellar#822) fix wrong effect of a circular path payment (stellar#807) Update README.md (stellar#820) Fix build badge (stellar#816) Require migration to run Horizon version successfully (fixes stellar#778) (stellar#808)
Move the call to `viper.BindPFlags` from our services that use the `support/config` package, into the config package `Init` function. Why: The `support/config` package does a good job of wrapping up cobra and viper functionality so that an application only needs to define one set of config and by calling a few functions can have that config provided by flags and environment variables in a consistent manner. While the package does most of the work to configure all the moving parts, it doesn't call the BindPFlags function and relies on the app to call this. It's really easy to miss that the app has to do this one thing, and I lost a good amount of time trying to understand why none of my configs were being populated. The package owns setting viper up, and so it should own this part of the process too. We're only using this on Horizon right now but this package looks like a good starting point for us to have a common pattern to configure our services with environment variables and flags. On some of our services this doesn't matter because the configs are all non-sensitive values, but as we write services that need keys making use of flags and environment variables consistently will be ideal. @bartekn originally proposed this change here #834 (comment) and made the change in 75b8658. Before the PR was merged @ire-and-curses reverted the change in 2a8354b because the first commit had broken the setting of values, but as it turned out only some of the code needed to be reverted.
This closes #810 by cleaning up the redundancy in the Horizon command line option handling.
A new array,
configOpts
, is used to fully define the command line parameters. To add a new command line parameter, it should be sufficient to add a line toconfigOpts
, and a new field to thehorizon.Config
struct.Optionally, the developer can hook a custom function to the new
configOpts
line for validation and/or transformation of the incoming parameter.I wanted to avoid invasive changes, but during this refactoring I found a few inconsistencies which are worth pointing out:
ruby-horizon-url
(I removed)skip-cursor-update
(I added missing config)port
andhistory-retention-count
(I removed)friendbot-url
is stored as aurl.URL
, but other URLs (e.g.db-url
,stellar-core-db-url
,stellar-core-url
,redis-url
,sentry-dsn
etc.) are just strings. Should we change this in the future?tls-cert
andtls-key
have custom checking to ensure both are provided, but there are other pairs that could be similarly checked, e.g.redis-limit-redis-key
andredis-url
,loggly-token
andloggly-tag
(easy to do).history-stale-threshold
tostale-threshold
, ordb-url
to the rather generic env variableDATABASE_URL
.uint
but others are not. Not sure what the logic is for theuints
.