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

feat: config reloading #1771

Merged
merged 5 commits into from
Sep 24, 2024
Merged

feat: config reloading #1771

merged 5 commits into from
Sep 24, 2024

Conversation

cstockton
Copy link
Contributor

What kind of change does this PR introduce?

File based configuration reloading using fsnotify.

What is the current behavior?

Currently the Auth config is loaded once from the environment or file (-c flag) and persists until the service is restarted.

What is the new behavior?

A new optional flag (long: --watch-dir, short: -w) has been added. When present any files with a ".env" suffix will be loaded into the environment before the *GlobalConfiguration is created, otherwise existing behavior is preserved.

In addition when the watch-dir flag is present a goroutine will be started in serve_cmd.go and begin blocking on a call to (*Reloader).Watch with a callback function that accepts a *conf.GlobalConfiguration object. Each time this function is called we create a new *api.API object and store it within our AtomicHandler, previously given as the root handler to the *http.Server.

The Reloader uses some simple heuristics to deal with a few edge cases, an overview:

  • At most 1 configuration reload may happen per 10 seconds with a +-1s margin of error.
  • After a file within --watch-dir has changed the 10 second grace period begins. After that it will reload the config.
  • Config reloads first sort each file by name then processes them in sequence.
  • Directories within --watch-dir are ignored during config reloading.
    • Implementation quirk: directory changes can trigger a config reload, as I don't stat fsnotify events. This and similar superfulous reloads could be easily fixed by storing a snapshot of os.Environ() after successful reloads to compare with the latest via slices.Equal() before reloading.
  • Files that do not end with a .env suffix are ignored.
  • It handles the removal or renaming of the -watch-dir during runtime, but an error message will be printed every 10 seconds as long as it's missing.
  • The config file passed with -c is only loaded once. Live reloads only read the config dir. Meaning it would be possible to create a config dir change that results in a new final configuration on the next reload due to the persistence of os.Environ().

Chris Stockton added 3 commits September 10, 2024 14:42
This commit provides a basic primitive for the thread safe hot
swapping of an active http.Handler.

It works by wrapping an atomic.Value within a struct implementing
the http.Handler interface. Before serving each request it first
makes a load call on the atomic.Value, returning the most recently
stored http.Handler.

The package is accompanied by two tests, one to test the basic
store & load support. The second test is meant to be ran only when
the race detector is present.
I've added LoadDirectory and LoadFile with comments explaining the
behavior of each. LoadGlobalFromEnv simply calls loadGlobal which
configures a *GlobalConfiguration from the processes current env.

These will be used to compose a *GlobalConfiguration from the
multiple configuration sources during process initialization and
during live reloads.
A new optional flag (long: --watch-dir, short: -w) has been
added. When present any files with a ".env" suffix will be
loaded into the environment before the *GlobalConfiguration
is created, otherwise existing behavior is preserved.

In addition when the watch-dir flag is present a goroutine
will be started in serve_cmd.go and begin blocking on a call
to (*Reloader).Watch with a callback function that accepts a
*conf.GlobalConfiguration object. Each time this function is
called we create a new API object and store it within our
AtomicHandler, previously given as the root handler to the
*http.Server.

The Reloader uses some simple heuristics to deal with a few
edge cases, an overview:

  - At most 1 configuration reload may happen per 10 seconds
  with a +-1s margin of error.
  - After a file within -watch-dir has changed the 10 second
  grace period begins. After that it will reload the config.
  - Config reloads first sort each file by name then processes
  them in sequence.
  - Directories within watch-dir are ignored during config
  reloading.
    - Implementation quirk: directory changes can trigger
      a config reload, as I don't stat fsnotify events. This
	  and similar superfulous reloads could be easily fixed
	  by storing a snapshot of os.Environ() after successful
	  reloads to compare with the latest via slices.Equal()
	  before reloading.
  - Files that do not end with a .env suffix are ignored.
  - It handles the removal or renaming of the -watch-dir
    during runtime, but an error message will be printed
	every 10 seconds as long as it's missing.
  - The config file passed with -c is only loaded once. Live
    reloads only read the config dir. Meaning it would be
	possible to create a config dir change that results in a
	new final configuration on the next reload due to the
	persistence of os.Environ().
@cstockton cstockton requested a review from a team as a code owner September 10, 2024 22:35
@kangmingtay
Copy link
Member

@cstockton seems like some of the gosec tests are broken

@coveralls
Copy link

coveralls commented Sep 11, 2024

Pull Request Test Coverage Report for Build 10927565463

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 56 of 208 (26.92%) changed or added relevant lines in 6 files are covered.
  • 150 unchanged lines in 6 files lost coverage.
  • Overall coverage decreased (-0.2%) to 57.718%

Changes Missing Coverage Covered Lines Changed/Added Lines %
cmd/root_cmd.go 0 2 0.0%
internal/api/api.go 0 3 0.0%
internal/reloader/handler.go 11 15 73.33%
internal/conf/configuration.go 25 56 44.64%
cmd/serve_cmd.go 0 56 0.0%
internal/reloader/reloader.go 20 76 26.32%
Files with Coverage Reduction New Missed Lines %
internal/api/admin.go 7 65.75%
internal/api/mail.go 7 57.62%
internal/models/factor.go 7 72.0%
internal/models/user.go 19 76.18%
internal/api/user.go 28 66.48%
internal/conf/configuration.go 82 71.18%
Totals Coverage Status
Change from base Build 10779572699: -0.2%
Covered Lines: 9202
Relevant Lines: 15943

💛 - Coveralls

cmd/root_cmd.go Outdated Show resolved Hide resolved
Copy link
Member

@kangmingtay kangmingtay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice one @cstockton !

@hf hf merged commit 6ee0091 into master Sep 24, 2024
2 checks passed
@hf hf deleted the cs/feat-config-reloader branch September 24, 2024 07:09
hf pushed a commit that referenced this pull request Sep 24, 2024
🤖 I have created a release *beep* *boop*
---


##
[2.161.0](v2.160.0...v2.161.0)
(2024-09-24)


### Features

* add `x-sb-error-code` header, show error code in logs
([#1765](#1765))
([ed91c59](ed91c59))
* add webauthn configuration variables
([#1773](#1773))
([77d5897](77d5897))
* config reloading
([#1771](#1771))
([6ee0091](6ee0091))


### Bug Fixes

* add additional information around errors for missing content type
header ([#1576](#1576))
([c2b2f96](c2b2f96))
* add token to hook payload for non-secure email change
([#1763](#1763))
([7e472ad](7e472ad))
* update aal requirements to update user
([#1766](#1766))
([25d9874](25d9874))
* update mfa admin methods
([#1774](#1774))
([567ea7e](567ea7e))
* user sanitization should clean up email change info too
([#1759](#1759))
([9d419b4](9d419b4))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants