Skip to content
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

Merged
merged 38 commits into from
Jan 31, 2019
Merged

Improve config (closes #810) #834

merged 38 commits into from
Jan 31, 2019

Conversation

ire-and-curses
Copy link
Member

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 to configOpts, and a new field to the horizon.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:

  1. Unused option ruby-horizon-url (I removed)
  2. Incomplete configuration for skip-cursor-update (I added missing config)
  3. Redundant viper defaults for port and history-retention-count (I removed)
  4. 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?
  5. 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).
  6. Inconsistent names could be cleaned up, e.g. there is no reason to map history-stale-threshold to stale-threshold, or db-url to the rather generic env variable DATABASE_URL.
  7. Some numbers are uint but others are not. Not sure what the logic is for the uints.

@ire-and-curses ire-and-curses changed the title Es 810 improve config Improve config (closes #810) Jan 25, 2019
Copy link
Contributor

@bartekn bartekn left a 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 Show resolved Hide resolved
services/horizon/main.go Outdated Show resolved Hide resolved
services/horizon/main.go Outdated Show resolved Hide resolved
services/horizon/main.go Outdated Show resolved Hide resolved
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
Copy link
Contributor

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.

Copy link
Member Author

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.

// Check all required args were provided - needed for migrations check
for i := range configOpts {
co := &configOpts[i]
co.require()
Copy link
Contributor

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.

Copy link
Member Author

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.

services/horizon/main.go Outdated Show resolved Hide resolved
services/horizon/main.go Outdated Show resolved Hide resolved
services/horizon/main.go Outdated Show resolved Hide resolved
services/horizon/main.go Outdated Show resolved Hide resolved
@bartekn
Copy link
Contributor

bartekn commented Jan 28, 2019

PS. you can consider closing #817 in this PR.

@ire-and-curses
Copy link
Member Author

Can't change MaxDBConnections to uint, code expects int.
Most other changes made. Please take another look.

Copy link
Contributor

@bartekn bartekn left a 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!

Name: "db-url",
EnvVar: "DATABASE_URL",
ConfigKey: &config.DatabaseURL,
FlagDefault: "",
Copy link
Contributor

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.

Copy link
Contributor

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

Copy link
Member Author

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.

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()
Copy link
Contributor

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().

Copy link
Member Author

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.

Copy link
Contributor

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.

Copy link
Member Author

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?

services/horizon/main.go Show resolved Hide resolved
support/config/config_option.go Outdated Show resolved Hide resolved
Copy link
Contributor

@bartekn bartekn left a 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.

services/horizon/main.go Outdated Show resolved Hide resolved
support/config/config_option.go Outdated Show resolved Hide resolved
support/config/config_option.go Show resolved Hide resolved
support/config/config_option.go Outdated Show resolved Hide resolved
support/config/config_option.go Outdated Show resolved Hide resolved
@ire-and-curses
Copy link
Member Author

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.

Copy link
Contributor

@bartekn bartekn left a 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.

@bartekn bartekn merged commit 09d6ab4 into master Jan 31, 2019
@bartekn bartekn deleted the es-810-improve-config branch January 31, 2019 13:38
oryband added a commit to kinecosystem/go that referenced this pull request Feb 17, 2019
* 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)
leighmcculloch added a commit that referenced this pull request Dec 12, 2019
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve configuration code
3 participants