consul_application_settings 2.1.1 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/main.yml +2 -2
- data/.rubocop.yml +2 -1
- data/CHANGELOG.md +55 -1
- data/Gemfile.lock +2 -1
- data/README.md +110 -56
- data/bin/benchmark +62 -0
- data/consul_application_settings.gemspec +8 -1
- data/docker-compose.yml +9 -0
- data/lib/consul_application_settings/configuration.rb +11 -1
- data/lib/consul_application_settings/providers/abstract.rb +26 -0
- data/lib/consul_application_settings/providers/consul.rb +32 -0
- data/lib/consul_application_settings/providers/consul_preloaded.rb +45 -0
- data/lib/consul_application_settings/providers/local_storage.rb +42 -0
- data/lib/consul_application_settings/reader.rb +50 -0
- data/lib/consul_application_settings/resolvers/abstract.rb +14 -0
- data/lib/consul_application_settings/resolvers/env.rb +19 -0
- data/lib/consul_application_settings/resolvers/erb.rb +18 -0
- data/lib/consul_application_settings/utils.rb +13 -0
- data/lib/consul_application_settings/version.rb +1 -1
- data/lib/consul_application_settings.rb +9 -5
- metadata +45 -21
- data/lib/consul_application_settings/consul_provider.rb +0 -33
- data/lib/consul_application_settings/file_provider.rb +0 -54
- data/lib/consul_application_settings/settings_provider.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 361bfff1ca209489842135445a5265240919a881bd37b6463a8f64bd6c7583bd
|
4
|
+
data.tar.gz: a71131c9eeb965821b20c027be2c27ebe6000d5f1fffaaf1e806fea61c1be86e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1218d2841e41b34762d66e8eba0bc9d1db1b790114c59e7ba2fcecab34e4eb66d163fb3b21b41be96cb5f91d4a490d410b5372e5f7052157442db9b038f574b0
|
7
|
+
data.tar.gz: 864a3e31e29382c54a9cb046d62ddc85eca46a380a92214fb4b4b378de6aab9731a88981d7c46735d9cd9e8b692932ea127ba858a7864a0a3ae117d48ff7e100
|
data/.github/workflows/main.yml
CHANGED
data/.rubocop.yml
CHANGED
@@ -2,6 +2,7 @@ AllCops:
|
|
2
2
|
TargetRubyVersion: 2.5
|
3
3
|
Exclude:
|
4
4
|
- 'tmp/**/*'
|
5
|
+
- 'bin/**/*'
|
5
6
|
|
6
7
|
Metrics/LineLength:
|
7
8
|
Max: 120
|
@@ -10,4 +11,4 @@ Style/FrozenStringLiteralComment:
|
|
10
11
|
Enabled: false
|
11
12
|
|
12
13
|
Metrics/BlockLength:
|
13
|
-
ExcludedMethods: ['describe', 'context']
|
14
|
+
ExcludedMethods: ['describe', 'context', 'shared_examples']
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,51 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [4.0.0-alpha]
|
4
|
+
### Breaking Changes
|
5
|
+
- When using `settings.get` to retrieve object - exception is raised
|
6
|
+
### New features
|
7
|
+
- Introduce resolvers concept
|
8
|
+
- Add environment resolver
|
9
|
+
- Add erb resolver
|
10
|
+
- Add vault alpha resolver
|
11
|
+
|
12
|
+
## [3.0.1]
|
13
|
+
### Fixes
|
14
|
+
- Fix exception when preloading settings without consul
|
15
|
+
|
16
|
+
## [3.0.0]
|
17
|
+
### Breaking Changes
|
18
|
+
- Use Preloaded Consul Settings Provider by default
|
19
|
+
### New features
|
20
|
+
- Configurable setting providers
|
21
|
+
- Preloaded Consul Settings Provider to prioritize performance over consistency
|
22
|
+
- Performance tests in spec
|
23
|
+
- Benchmarking script
|
24
|
+
### Fixes
|
25
|
+
- Return nil instead of empty hash when reading missing setting from file
|
26
|
+
- Return nil instead of empty string when reading missing value from Consul
|
27
|
+
- Add missing load method on Settings Reader to create object with narrow scope
|
28
|
+
|
29
|
+
## [2.1.1]
|
30
|
+
### Changes
|
31
|
+
- Update Diplomat to latest version
|
32
|
+
|
33
|
+
## [2.1.0]
|
34
|
+
### Fixes
|
35
|
+
- Return nil for unknown keys
|
36
|
+
|
37
|
+
## [2.0.0]
|
38
|
+
### Breaking Changes
|
39
|
+
- Change default naming for setting files
|
40
|
+
|
41
|
+
## [1.0.0]
|
42
|
+
### Features
|
43
|
+
- Add support for second settings file (local settings for development)
|
44
|
+
|
45
|
+
## [0.1.4]
|
46
|
+
### Fixes
|
47
|
+
- Clone values before returning
|
48
|
+
|
3
49
|
## [0.1.3]
|
4
50
|
### Fixes
|
5
51
|
- Add `Diplomat::PathNotFound` to the list of caught exceptions
|
@@ -22,7 +68,15 @@
|
|
22
68
|
- Support deep settings search
|
23
69
|
- Support nested configs
|
24
70
|
|
25
|
-
[Unreleased]: https://github.com/matic-insurance/consul_application_settings/compare/0.
|
71
|
+
[Unreleased]: https://github.com/matic-insurance/consul_application_settings/compare/4.0.0-alpha...HEAD
|
72
|
+
[4.0.0-alpha]: https://github.com/matic-insurance/consul_application_settings/compare/3.0.1...4.0.0-alpha
|
73
|
+
[3.0.1]: https://github.com/matic-insurance/consul_application_settings/compare/3.0.0...3.0.1
|
74
|
+
[3.0.0]: https://github.com/matic-insurance/consul_application_settings/compare/2.0.0...3.0.0
|
75
|
+
[2.1.1]: https://github.com/matic-insurance/consul_application_settings/compare/2.1.0...2.1.1
|
76
|
+
[2.1.0]: https://github.com/matic-insurance/consul_application_settings/compare/2.0.0...2.1.0
|
77
|
+
[2.0.0]: https://github.com/matic-insurance/consul_application_settings/compare/1.0.0...2.0.0
|
78
|
+
[1.0.0]: https://github.com/matic-insurance/consul_application_settings/compare/0.1.4...1.0.0
|
79
|
+
[0.1.4]: https://github.com/matic-insurance/consul_application_settings/compare/0.1.3...0.1.4
|
26
80
|
[0.1.3]: https://github.com/matic-insurance/consul_application_settings/compare/0.1.2...0.1.3
|
27
81
|
[0.1.2]: https://github.com/matic-insurance/consul_application_settings/compare/0.1.1...0.1.2
|
28
82
|
[0.1.1]: https://github.com/matic-insurance/consul_application_settings/compare/0.1.0...0.1.1
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,28 +1,22 @@
|
|
1
1
|
# ConsulApplicationSettings
|
2
2
|
|
3
|
+
## Gem is Deprecated in favor of [SettingsReader](https://github.com/matic-insurance/settings_reader)
|
4
|
+
This gem is deprecated and all functionality has been moved to `SettingsReader`
|
5
|
+
See More: https://github.com/matic-insurance/settings_reader
|
6
|
+
|
3
7
|

|
4
8
|
[](https://codecov.io/gh/matic-insurance/consul_application_settings)
|
5
9
|
|
6
|
-
|
7
|
-
to host application settings.
|
8
|
-
to simplify storage and control of application with Consul KV storage.
|
9
|
-
|
10
|
-
Gem is trying to solve a problem of distributing application settings for local development environment and provide defaults
|
11
|
-
in production before custom value is set inside of consul.
|
12
|
-
|
13
|
-
Example use cases:
|
14
|
-
|
15
|
-
- One engineer has created a new feature that depend on consul key/value.
|
16
|
-
|
17
|
-
How enginner can notify other engineers that they need to set this value in their consul environments?
|
18
|
-
|
19
|
-
- DevOps team responsible to configure and maintain deployment.
|
10
|
+
This gem that simplifies usage of Consul (via [Diplomat gem](https://github.com/WeAreFarmGeek/diplomat))
|
11
|
+
to host application settings.
|
20
12
|
|
21
|
-
|
13
|
+
Except reading value from Consul the gem also:
|
14
|
+
- Fallbacks to YAML if value is missing in consul
|
15
|
+
- Resolve actual value from other sources to facilitate overriding via ENV, storing secret values in Vault,
|
16
|
+
or executing small ERB snippets
|
22
17
|
|
23
|
-
|
24
|
-
|
25
|
-
**NOTE** Consul is requested every time you query the settings. Defaults YAML file is loaded in memory and is not changing.
|
18
|
+
Default values in YAML also can be considered as a way to communicate structure of settings to other engineers.
|
19
|
+
Default values also support local settings to allow override on local environment or deployment in production.
|
26
20
|
|
27
21
|
## Installation
|
28
22
|
|
@@ -39,17 +33,27 @@ gem 'consul_application_settings'
|
|
39
33
|
At the load of application:
|
40
34
|
```ruby
|
41
35
|
ConsulApplicationSettings.configure do |config|
|
42
|
-
# Specify path to the base settings YML. Default: 'config/
|
43
|
-
config.base_file_path = Rails.root.join('config/
|
44
|
-
# Specify path to the local settings YML, which overrides the base file. Default: 'config/
|
45
|
-
config.local_file_path = Rails.root.join('config/
|
46
|
-
# Specify whether
|
36
|
+
# Specify path to the base settings YML. Default: 'config/app_settings.yml'
|
37
|
+
config.base_file_path = Rails.root.join('config/app_settings.yml')
|
38
|
+
# Specify path to the local settings YML, which overrides the base file. Default: 'config/app_settings.local.yml'
|
39
|
+
config.local_file_path = Rails.root.join('config/app_settings.local.yml')
|
40
|
+
# Specify whether exception should be thrown on Consul connection errors. Default: false
|
47
41
|
config.disable_consul_connection_errors = true
|
42
|
+
# Specify setting providers. Default: [ConsulApplicationSettings::Providers::ConsulPreloaded, ConsulApplicationSettings::Providers::LocalStorage]
|
43
|
+
config.settings_providers = [
|
44
|
+
ConsulApplicationSettings::Providers::Consul,
|
45
|
+
ConsulApplicationSettings::Providers::LocalStorage
|
46
|
+
]
|
47
|
+
# Specify how values will be additionally resolved. Default: [ConsulApplicationSettings::Resolvers::Env]
|
48
|
+
config.value_resolvers = [
|
49
|
+
ConsulApplicationSettings::Resolvers::Erb,
|
50
|
+
ConsulApplicationSettings::Resolvers::Env,
|
51
|
+
]
|
48
52
|
end
|
49
53
|
|
50
|
-
APP_SETTINGS = ConsulApplicationSettings.load
|
51
54
|
# Specify path to settings both in YML files and Consul
|
52
|
-
AUTH_SETTIGNS = ConsulApplicationSettings.load('
|
55
|
+
AUTH_SETTIGNS = ConsulApplicationSettings.load('my_cool_app')
|
56
|
+
# Load at root without any prefix: APP_SETTINGS = ConsulApplicationSettings.load
|
53
57
|
```
|
54
58
|
|
55
59
|
**NOTE** For rails you can add this code to custom initializer `console_application_settings.rb` in `app/config/initializers`
|
@@ -60,35 +64,32 @@ AUTH_SETTIGNS = ConsulApplicationSettings.load('authentication')
|
|
60
64
|
|
61
65
|
Assuming your defaults file in repository `config/application_settings.yml` looks like:
|
62
66
|
```yaml
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
webhook_url: 'https://hooks.slack.com/services/XXXXXX/XXXXX/XXXXXXX'
|
67
|
+
my_cool_app:
|
68
|
+
app_name: 'MyCoolApp'
|
69
|
+
hostname: 'http://localhost:3001'
|
70
|
+
|
71
|
+
integrations:
|
72
|
+
database:
|
73
|
+
domain: localhost
|
74
|
+
user: app
|
75
|
+
password: password1234
|
76
|
+
slack:
|
77
|
+
enabled: false
|
78
|
+
webhook_url: 'https://hooks.slack.com/services/XXXXXX/XXXXX/XXXXXXX'
|
76
79
|
```
|
77
80
|
|
78
81
|
And consul has following settings
|
79
82
|
```json
|
80
83
|
{
|
81
|
-
"
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
"
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
"
|
90
|
-
"enabled": "true"
|
91
|
-
}
|
84
|
+
"my_cool_app": {
|
85
|
+
"hostname": "https://mycoolapp.com",
|
86
|
+
"integrations": {
|
87
|
+
"database": {
|
88
|
+
"domain": "194.78.92.19",
|
89
|
+
"password": "*************"
|
90
|
+
},
|
91
|
+
"slack": {
|
92
|
+
"enabled": "true"
|
92
93
|
}
|
93
94
|
}
|
94
95
|
}
|
@@ -115,10 +116,60 @@ gem provides interface to avoid duplicating absolute path
|
|
115
116
|
|
116
117
|
```ruby
|
117
118
|
# You can load subsettings from root object
|
118
|
-
db_settings = APP_SETTINGS.load('integrations/database')
|
119
|
+
db_settings = APP_SETTINGS.load('integrations/database') # ConsulApplicationSettings::Reader
|
119
120
|
db_settings.get(:domain) # "194.78.92.19"
|
120
121
|
db_settings['user'] # "app"
|
121
|
-
|
122
|
+
|
123
|
+
#if you try to get subsettings via get - error is raised
|
124
|
+
APP_SETTINGS.get('integrations/database') # raise ConsulApplicationSettings::Error
|
125
|
+
```
|
126
|
+
|
127
|
+
## Advanced Configurations
|
128
|
+
|
129
|
+
### Setting Providers
|
130
|
+
Providers controls how and in which order settings are retrieved.
|
131
|
+
When application asks for specific setting - gem retrieves them from every provider in order of configuration
|
132
|
+
until one returns not nil value.
|
133
|
+
|
134
|
+
Default order for providers is:
|
135
|
+
1. `ConsulApplicationSettings::Providers::ConsulPreloaded`
|
136
|
+
2. `ConsulApplicationSettings::Providers::LocalStorage`
|
137
|
+
|
138
|
+
List of built in providers:
|
139
|
+
- `ConsulApplicationSettings::Providers::ConsulPreloaded` - Retrieves all settings from consul on every `.load`
|
140
|
+
- `ConsulApplicationSettings::Providers::Consul` - Retrieves setting every time `.get` method is called
|
141
|
+
- `ConsulApplicationSettings::Providers::LocalStorage` - Retrieves all settings from local files on every `.load`
|
142
|
+
|
143
|
+
Custom provider can be added as long as it support following interface:
|
144
|
+
```ruby
|
145
|
+
class CustomProvider
|
146
|
+
#constructor
|
147
|
+
def initialize(base_path, config)
|
148
|
+
end
|
149
|
+
|
150
|
+
# get value by `base_path + '/' + path`
|
151
|
+
def get(path)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
```
|
155
|
+
|
156
|
+
### Resolvers
|
157
|
+
Once value is retrieved - it will be additionally processed by resolvers.
|
158
|
+
This allows for additional flexibility like getting values from external sources.
|
159
|
+
While every resolver can be implemented in a form of a provider - one will be limited by the structure of settings,
|
160
|
+
while other system might not be compatible with this.
|
161
|
+
|
162
|
+
When value is retrieved - gem finds **first** provider that can resolve value and resolves it.
|
163
|
+
Resolved value is returned to application.
|
164
|
+
|
165
|
+
Default list of resolvers:
|
166
|
+
- `ConsulApplicationSettings::Resolvers::Env`
|
167
|
+
|
168
|
+
List of built in resolvers
|
169
|
+
- `ConsulApplicationSettings::Resolvers::Env` - resolves any value by looking up environment variable.
|
170
|
+
Matching any value that starts with `env://`. Value like `env://TEST_URL` will be resolved as `ENV['TEST_URL']`
|
171
|
+
- `ConsulApplicationSettings::Resolvers::Erb` - resolves value by rendering it via ERB.
|
172
|
+
Matching any value that contains `<%` and `%>` in it. Value like `<%= 2 + 2 %>` will be resolved as `4`
|
122
173
|
|
123
174
|
### Gem Configuration
|
124
175
|
You can configure gem with block:
|
@@ -139,15 +190,18 @@ All Gem configurations
|
|
139
190
|
| base_file_path | no | 'config/application_settings.yml' | String | Path to the file with base settings |
|
140
191
|
| local_file_path | no | 'config/application_settings.local.yml' | String | Path to the file with local settings overriding the base settings |
|
141
192
|
| disable_consul_connection_errors | no | true | Boolean | Do not raise exception when consul is not available (useful for development) |
|
193
|
+
| settings_providers | no | Array(Provider) | Array | Specify custom setting provider lists |
|
194
|
+
| value_resolvers | no | Array(Resolver) | Array | Specify custom value resolvers lists |
|
142
195
|
|
143
196
|
## Development
|
144
197
|
|
145
|
-
1. [Install Consul](https://www.consul.io/docs/install/index.html)
|
146
198
|
1. Run `bin/setup` to install dependencies
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
199
|
+
2. Run `docker-compose up` to spin up dependencies (Consul)
|
200
|
+
3. Run tests `rspec`
|
201
|
+
4. Add new test
|
202
|
+
5. Add new code
|
203
|
+
6. Go to step 3
|
204
|
+
7. Create PR
|
151
205
|
|
152
206
|
## Contributing
|
153
207
|
|
data/bin/benchmark
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'consul_application_settings'
|
5
|
+
require 'benchmark'
|
6
|
+
|
7
|
+
ITERATIONS = 1000
|
8
|
+
|
9
|
+
def kill_consul
|
10
|
+
`pgrep consul | xargs kill`
|
11
|
+
end
|
12
|
+
|
13
|
+
def start_consul
|
14
|
+
spawn('consul agent -dev -node machine > /dev/null 2>&1')
|
15
|
+
end
|
16
|
+
|
17
|
+
def application_settings(providers)
|
18
|
+
file = 'spec/fixtures/base_application_settings.yml'
|
19
|
+
ConsulApplicationSettings.configure do |config|
|
20
|
+
config.settings_providers = providers
|
21
|
+
end
|
22
|
+
ConsulApplicationSettings.load(file)
|
23
|
+
end
|
24
|
+
|
25
|
+
def benchmark_gem
|
26
|
+
Benchmark.bm(20) do |x|
|
27
|
+
x.report('Real Time Consul') do
|
28
|
+
ca = application_settings([
|
29
|
+
ConsulApplicationSettings::Providers::Consul,
|
30
|
+
ConsulApplicationSettings::Providers::LocalStorage
|
31
|
+
])
|
32
|
+
ITERATIONS.times { ca.get('application/name') }
|
33
|
+
end
|
34
|
+
|
35
|
+
x.report('Preloaded Consul') do
|
36
|
+
ca = application_settings([
|
37
|
+
ConsulApplicationSettings::Providers::ConsulPreloaded,
|
38
|
+
ConsulApplicationSettings::Providers::LocalStorage
|
39
|
+
])
|
40
|
+
ITERATIONS.times { ca.get('application/name') }
|
41
|
+
end
|
42
|
+
|
43
|
+
x.report('File Only') do
|
44
|
+
ca = application_settings([
|
45
|
+
ConsulApplicationSettings::Providers::ConsulPreloaded
|
46
|
+
])
|
47
|
+
ITERATIONS.times { ca.get('application/name') }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
puts '-' * 80
|
53
|
+
puts 'Benchmark without consul agent'
|
54
|
+
kill_consul
|
55
|
+
benchmark_gem
|
56
|
+
|
57
|
+
puts '-' * 80
|
58
|
+
puts 'Benchmark with consul agent running'
|
59
|
+
start_consul
|
60
|
+
benchmark_gem
|
61
|
+
|
62
|
+
kill_consul
|
@@ -29,13 +29,20 @@ Gem::Specification.new do |spec|
|
|
29
29
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
30
30
|
spec.require_paths = ['lib']
|
31
31
|
|
32
|
+
spec.add_dependency 'deep_merge', '~> 1.2'
|
32
33
|
spec.add_dependency 'diplomat', '~> 2.5.1'
|
33
34
|
|
34
35
|
spec.add_development_dependency 'bundler', '~> 2.0'
|
36
|
+
spec.add_development_dependency 'codecov', '~> 0.4'
|
35
37
|
spec.add_development_dependency 'rake', '~> 13.0'
|
36
38
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
39
|
spec.add_development_dependency 'rubocop', '~> 0.66'
|
38
40
|
spec.add_development_dependency 'rubocop-rspec', '~> 1.32.0'
|
39
41
|
spec.add_development_dependency 'simplecov', '~> 0.16'
|
40
|
-
|
42
|
+
|
43
|
+
spec.post_install_message = <<~MESSAGE
|
44
|
+
! The 'consul_application_settings' gem has been deprecated and has been replaced by 'settings_reader'.
|
45
|
+
! See: https://rubygems.org/gems/settings_reader
|
46
|
+
! And: https://github.com/matic-insurance/settings_reader
|
47
|
+
MESSAGE
|
41
48
|
end
|
data/docker-compose.yml
ADDED
@@ -3,12 +3,22 @@ module ConsulApplicationSettings
|
|
3
3
|
class Configuration
|
4
4
|
DEFAULT_BASE_FILE_PATH = 'config/app_settings.yml'.freeze
|
5
5
|
DEFAULT_LOCAL_FILE_PATH = 'config/app_settings.local.yml'.freeze
|
6
|
-
|
6
|
+
DEFAULT_PROVIDERS = [
|
7
|
+
ConsulApplicationSettings::Providers::ConsulPreloaded,
|
8
|
+
ConsulApplicationSettings::Providers::LocalStorage
|
9
|
+
].freeze
|
10
|
+
DEFAULT_RESOLVERS = [
|
11
|
+
ConsulApplicationSettings::Resolvers::Env
|
12
|
+
].freeze
|
13
|
+
attr_accessor :base_file_path, :local_file_path, :disable_consul_connection_errors,
|
14
|
+
:settings_providers, :value_resolvers
|
7
15
|
|
8
16
|
def initialize
|
9
17
|
@base_file_path = DEFAULT_BASE_FILE_PATH
|
10
18
|
@local_file_path = DEFAULT_LOCAL_FILE_PATH
|
11
19
|
@disable_consul_connection_errors = true
|
20
|
+
@settings_providers = DEFAULT_PROVIDERS
|
21
|
+
@value_resolvers = DEFAULT_RESOLVERS
|
12
22
|
end
|
13
23
|
end
|
14
24
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module ConsulApplicationSettings
|
2
|
+
module Providers
|
3
|
+
# Abstract class with basic functionality
|
4
|
+
class Abstract
|
5
|
+
def initialize(base_path, config)
|
6
|
+
@base_path = base_path
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(_path)
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def absolute_key_path(path)
|
17
|
+
ConsulApplicationSettings::Utils.generate_path(@base_path, path)
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_value_from_hash(path, data)
|
21
|
+
parts = ConsulApplicationSettings::Utils.decompose_path(path)
|
22
|
+
data.dig(*parts).clone
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'diplomat'
|
2
|
+
|
3
|
+
module ConsulApplicationSettings
|
4
|
+
module Providers
|
5
|
+
# Provides access to settings stored in Consul
|
6
|
+
class Consul < Abstract
|
7
|
+
def get(path)
|
8
|
+
full_path = absolute_key_path(path)
|
9
|
+
value = get_from_consul(full_path)
|
10
|
+
value = resolve_tree_response(value, full_path)
|
11
|
+
ConsulApplicationSettings::Utils.cast_consul_value(value)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def get_from_consul(path)
|
17
|
+
Diplomat::Kv.get(path, recurse: true)
|
18
|
+
rescue Diplomat::KeyNotFound
|
19
|
+
nil
|
20
|
+
rescue SystemCallError, Faraday::ConnectionFailed, Diplomat::PathNotFound => e
|
21
|
+
raise e unless @config.disable_consul_connection_errors
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve_tree_response(value, full_path)
|
25
|
+
return value unless value.is_a?(Array)
|
26
|
+
|
27
|
+
value.each { |item| item[:key] = item[:key].delete_prefix("#{full_path}/") }
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ConsulApplicationSettings
|
2
|
+
module Providers
|
3
|
+
# Provides access to settings stored in Consul. Loads them once
|
4
|
+
class ConsulPreloaded < Abstract
|
5
|
+
def initialize(base_path, config)
|
6
|
+
super
|
7
|
+
@data = read_all_from_consul
|
8
|
+
end
|
9
|
+
|
10
|
+
def get(path)
|
11
|
+
value = get_value_from_hash(absolute_key_path(path), @data)
|
12
|
+
value = resolve_tree_response(value)
|
13
|
+
ConsulApplicationSettings::Utils.cast_consul_value(value)
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def read_all_from_consul
|
19
|
+
Diplomat::Kv.get_all(@base_path, convert_to_hash: true)
|
20
|
+
rescue Diplomat::KeyNotFound
|
21
|
+
{}
|
22
|
+
rescue SystemCallError, Faraday::ConnectionFailed, Diplomat::PathNotFound => e
|
23
|
+
raise e unless @config.disable_consul_connection_errors
|
24
|
+
|
25
|
+
{}
|
26
|
+
end
|
27
|
+
|
28
|
+
def resolve_tree_response(value)
|
29
|
+
return value unless value.is_a?(Hash)
|
30
|
+
|
31
|
+
deep_cast(value)
|
32
|
+
end
|
33
|
+
|
34
|
+
def deep_cast(hash)
|
35
|
+
hash.each_pair do |k, v|
|
36
|
+
if v.is_a?(Hash)
|
37
|
+
deep_cast(v)
|
38
|
+
else
|
39
|
+
hash[k] = ConsulApplicationSettings::Utils.cast_consul_value(v)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module ConsulApplicationSettings
|
4
|
+
module Providers
|
5
|
+
# Provides access to settings stored in file system with support of base and local files
|
6
|
+
class LocalStorage < Abstract
|
7
|
+
def initialize(base_path, config)
|
8
|
+
super
|
9
|
+
@data = load
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(path)
|
13
|
+
get_value_from_hash(absolute_key_path(path), @data)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def load
|
19
|
+
base_yml = read_yml(base_file_path)
|
20
|
+
local_yml = read_yml(local_file_path)
|
21
|
+
DeepMerge.deep_merge!(local_yml, base_yml, preserve_unmergeables: false, overwrite_arrays: true,
|
22
|
+
merge_nil_values: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def base_file_path
|
26
|
+
@config.base_file_path
|
27
|
+
end
|
28
|
+
|
29
|
+
def local_file_path
|
30
|
+
@config.local_file_path
|
31
|
+
end
|
32
|
+
|
33
|
+
def read_yml(path)
|
34
|
+
return {} unless File.exist?(path)
|
35
|
+
|
36
|
+
YAML.safe_load(IO.read(path))
|
37
|
+
rescue Psych::SyntaxError, Errno::ENOENT => e
|
38
|
+
raise ConsulApplicationSettings::Error, "Cannot read settings file at #{path}: #{e.message}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ConsulApplicationSettings
|
2
|
+
# Orchestrates fetching values from provider and resolving them
|
3
|
+
class Reader
|
4
|
+
def initialize(base_path, config)
|
5
|
+
@base_path = base_path
|
6
|
+
@config = config
|
7
|
+
@providers = config.settings_providers.map { |provider| provider.new(base_path, config) }
|
8
|
+
@resolvers = config.value_resolvers.map(&:new)
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(path)
|
12
|
+
value = fetch_value(path)
|
13
|
+
resolve_value(value, path)
|
14
|
+
end
|
15
|
+
|
16
|
+
alias [] get
|
17
|
+
|
18
|
+
def load(sub_path)
|
19
|
+
new_path = ConsulApplicationSettings::Utils.generate_path(@base_path, sub_path)
|
20
|
+
self.class.new(new_path, @config)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
def check_deep_structure(value, path)
|
26
|
+
return unless value.is_a?(Hash)
|
27
|
+
|
28
|
+
message = "Getting value of complex object at path: '#{path}'. Use #load method to get new scoped instance"
|
29
|
+
raise ConsulApplicationSettings::Error, message if value.is_a?(Hash)
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch_value(path)
|
33
|
+
@providers.each do |provider|
|
34
|
+
value = provider.get(path)
|
35
|
+
check_deep_structure(value, path)
|
36
|
+
return value unless value.nil?
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
def resolve_value(value, path)
|
42
|
+
resolver = @resolvers.detect { |r| r.resolvable?(value, path) }
|
43
|
+
if resolver
|
44
|
+
resolver.resolve(value, path)
|
45
|
+
else
|
46
|
+
value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ConsulApplicationSettings
|
2
|
+
module Resolvers
|
3
|
+
# Resolve values in environment
|
4
|
+
class Env
|
5
|
+
IDENTIFIER = 'env://'.freeze
|
6
|
+
|
7
|
+
def resolvable?(value, _path)
|
8
|
+
return unless value.respond_to?(:start_with?)
|
9
|
+
|
10
|
+
value.start_with?(IDENTIFIER)
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve(value, _path)
|
14
|
+
env_path = value.to_s.delete_prefix(IDENTIFIER)
|
15
|
+
ENV[env_path]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ConsulApplicationSettings
|
2
|
+
module Resolvers
|
3
|
+
# Run values through ERB
|
4
|
+
class Erb
|
5
|
+
IDENTIFIER = /(<%).*(%>)/.freeze
|
6
|
+
|
7
|
+
def resolvable?(value, _path)
|
8
|
+
return unless value.is_a?(String)
|
9
|
+
|
10
|
+
IDENTIFIER.match?(value)
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolve(value, _path)
|
14
|
+
ERB.new(value.to_s).result
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -6,8 +6,11 @@ module ConsulApplicationSettings
|
|
6
6
|
|
7
7
|
class << self
|
8
8
|
def cast_consul_value(value)
|
9
|
+
return nil if value.nil?
|
9
10
|
return false if value == 'false'
|
10
11
|
return true if value == 'true'
|
12
|
+
return value if value.is_a?(Hash)
|
13
|
+
return convert_to_hash(value) if value.is_a?(Array)
|
11
14
|
|
12
15
|
cast_complex_value(value)
|
13
16
|
end
|
@@ -33,6 +36,16 @@ module ConsulApplicationSettings
|
|
33
36
|
end
|
34
37
|
value.to_s
|
35
38
|
end
|
39
|
+
|
40
|
+
def convert_to_hash(data)
|
41
|
+
data_h = data.map do |item|
|
42
|
+
value = cast_consul_value(item[:value])
|
43
|
+
item[:key].split('/').reverse.reduce(value) { |h, v| { v => h } }
|
44
|
+
end
|
45
|
+
data_h.reduce({}) do |dest, source|
|
46
|
+
DeepMerge.deep_merge!(source, dest, preserve_unmergeables: true)
|
47
|
+
end
|
48
|
+
end
|
36
49
|
end
|
37
50
|
end
|
38
51
|
end
|
@@ -1,8 +1,13 @@
|
|
1
1
|
require 'consul_application_settings/version'
|
2
|
+
require 'consul_application_settings/providers/abstract'
|
3
|
+
require 'consul_application_settings/providers/consul'
|
4
|
+
require 'consul_application_settings/providers/consul_preloaded'
|
5
|
+
require 'consul_application_settings/providers/local_storage'
|
6
|
+
require 'consul_application_settings/resolvers/abstract'
|
7
|
+
require 'consul_application_settings/resolvers/env'
|
8
|
+
require 'consul_application_settings/resolvers/erb'
|
2
9
|
require 'consul_application_settings/configuration'
|
3
|
-
require 'consul_application_settings/
|
4
|
-
require 'consul_application_settings/file_provider'
|
5
|
-
require 'consul_application_settings/settings_provider'
|
10
|
+
require 'consul_application_settings/reader'
|
6
11
|
require 'consul_application_settings/utils'
|
7
12
|
|
8
13
|
# The gem provides possibility to load settings from Consul and automatically fall back to data stored in file system
|
@@ -11,7 +16,6 @@ module ConsulApplicationSettings
|
|
11
16
|
|
12
17
|
class << self
|
13
18
|
attr_accessor :config
|
14
|
-
attr_accessor :defaults
|
15
19
|
end
|
16
20
|
|
17
21
|
self.config ||= ConsulApplicationSettings::Configuration.new
|
@@ -21,6 +25,6 @@ module ConsulApplicationSettings
|
|
21
25
|
end
|
22
26
|
|
23
27
|
def self.load(path = '')
|
24
|
-
|
28
|
+
Reader.new(path, config)
|
25
29
|
end
|
26
30
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: consul_application_settings
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Volodymyr Mykhailyk
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: deep_merge
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: diplomat
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,6 +52,20 @@ dependencies:
|
|
38
52
|
- - "~>"
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: codecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.4'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.4'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: rake
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,20 +136,6 @@ dependencies:
|
|
108
136
|
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
138
|
version: '0.16'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: codecov
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - "~>"
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '0.4'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - "~>"
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '0.4'
|
125
139
|
description: |-
|
126
140
|
Gem that simplifies usage of Consul (via Diplomat gem) to host application settings.
|
127
141
|
Gem provides defaults and utilities
|
@@ -143,15 +157,22 @@ files:
|
|
143
157
|
- LICENSE.txt
|
144
158
|
- README.md
|
145
159
|
- Rakefile
|
160
|
+
- bin/benchmark
|
146
161
|
- bin/console
|
147
162
|
- bin/rspec
|
148
163
|
- bin/setup
|
149
164
|
- consul_application_settings.gemspec
|
165
|
+
- docker-compose.yml
|
150
166
|
- lib/consul_application_settings.rb
|
151
167
|
- lib/consul_application_settings/configuration.rb
|
152
|
-
- lib/consul_application_settings/
|
153
|
-
- lib/consul_application_settings/
|
154
|
-
- lib/consul_application_settings/
|
168
|
+
- lib/consul_application_settings/providers/abstract.rb
|
169
|
+
- lib/consul_application_settings/providers/consul.rb
|
170
|
+
- lib/consul_application_settings/providers/consul_preloaded.rb
|
171
|
+
- lib/consul_application_settings/providers/local_storage.rb
|
172
|
+
- lib/consul_application_settings/reader.rb
|
173
|
+
- lib/consul_application_settings/resolvers/abstract.rb
|
174
|
+
- lib/consul_application_settings/resolvers/env.rb
|
175
|
+
- lib/consul_application_settings/resolvers/erb.rb
|
155
176
|
- lib/consul_application_settings/utils.rb
|
156
177
|
- lib/consul_application_settings/version.rb
|
157
178
|
homepage: https://github.com/matic-insurance/consul_application_settings
|
@@ -161,7 +182,10 @@ metadata:
|
|
161
182
|
homepage_uri: https://github.com/matic-insurance/consul_application_settings
|
162
183
|
source_code_uri: https://github.com/matic-insurance/consul_application_settings
|
163
184
|
changelog_uri: https://github.com/matic-insurance/consul_application_settings/blob/master/CHANGELOG.md
|
164
|
-
post_install_message:
|
185
|
+
post_install_message: |
|
186
|
+
! The 'consul_application_settings' gem has been deprecated and has been replaced by 'settings_reader'.
|
187
|
+
! See: https://rubygems.org/gems/settings_reader
|
188
|
+
! And: https://github.com/matic-insurance/settings_reader
|
165
189
|
rdoc_options: []
|
166
190
|
require_paths:
|
167
191
|
- lib
|
@@ -176,7 +200,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
176
200
|
- !ruby/object:Gem::Version
|
177
201
|
version: '0'
|
178
202
|
requirements: []
|
179
|
-
rubygems_version: 3.0.3
|
203
|
+
rubygems_version: 3.0.3.1
|
180
204
|
signing_key:
|
181
205
|
specification_version: 4
|
182
206
|
summary: Application settings via Consul with yaml defaults
|
@@ -1,33 +0,0 @@
|
|
1
|
-
require 'diplomat'
|
2
|
-
|
3
|
-
module ConsulApplicationSettings
|
4
|
-
# Provides access to settings stored in Consul
|
5
|
-
class ConsulProvider
|
6
|
-
def initialize(base_path, config)
|
7
|
-
@base_path = base_path
|
8
|
-
@config = config
|
9
|
-
end
|
10
|
-
|
11
|
-
def get(path)
|
12
|
-
value = fetch_value(path)
|
13
|
-
ConsulApplicationSettings::Utils.cast_consul_value(value)
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def fetch_value(path)
|
19
|
-
full_path = generate_full_path(path)
|
20
|
-
Diplomat::Kv.get(full_path, {}, :return)
|
21
|
-
rescue SystemCallError, Faraday::ConnectionFailed, Diplomat::PathNotFound => e
|
22
|
-
raise e unless disable_consul_connection_errors?
|
23
|
-
end
|
24
|
-
|
25
|
-
def generate_full_path(path)
|
26
|
-
ConsulApplicationSettings::Utils.generate_path(@base_path, path)
|
27
|
-
end
|
28
|
-
|
29
|
-
def disable_consul_connection_errors?
|
30
|
-
@config.disable_consul_connection_errors
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,54 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
module ConsulApplicationSettings
|
4
|
-
# Provides access to settings stored in file system with support of base and local files
|
5
|
-
class FileProvider
|
6
|
-
def initialize(base_path, config)
|
7
|
-
@base_path = base_path
|
8
|
-
@config = config
|
9
|
-
load
|
10
|
-
end
|
11
|
-
|
12
|
-
def get(path)
|
13
|
-
read_path(path).clone
|
14
|
-
end
|
15
|
-
|
16
|
-
private
|
17
|
-
|
18
|
-
def load
|
19
|
-
base_yml = read_yml(base_file_path)
|
20
|
-
local_yml = read_yml(local_file_path)
|
21
|
-
@data = DeepMerge.deep_merge!(local_yml, base_yml, preserve_unmergeables: false, overwrite_arrays: true,
|
22
|
-
merge_nil_values: true)
|
23
|
-
end
|
24
|
-
|
25
|
-
def base_file_path
|
26
|
-
@config.base_file_path
|
27
|
-
end
|
28
|
-
|
29
|
-
def local_file_path
|
30
|
-
@config.local_file_path
|
31
|
-
end
|
32
|
-
|
33
|
-
def read_yml(path)
|
34
|
-
return {} unless File.exist?(path)
|
35
|
-
|
36
|
-
YAML.safe_load(IO.read(path))
|
37
|
-
rescue Psych::SyntaxError, Errno::ENOENT => e
|
38
|
-
raise ConsulApplicationSettings::Error, "Cannot read settings file at #{path}: #{e.message}"
|
39
|
-
end
|
40
|
-
|
41
|
-
def read_path(path)
|
42
|
-
full_path = ConsulApplicationSettings::Utils.generate_path(@base_path, path)
|
43
|
-
parts = ConsulApplicationSettings::Utils.decompose_path(full_path)
|
44
|
-
parts.reduce(@data, &method(:read_value))
|
45
|
-
end
|
46
|
-
|
47
|
-
def read_value(hash, key)
|
48
|
-
raise ConsulApplicationSettings::Error, 'reading arrays not implemented' if hash.is_a? Array
|
49
|
-
return {} if hash.nil?
|
50
|
-
|
51
|
-
hash.fetch(key.to_s, nil)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
module ConsulApplicationSettings
|
2
|
-
# Provides access to settings stored in Consul or in file system
|
3
|
-
class SettingsProvider
|
4
|
-
def initialize(base_path, config)
|
5
|
-
@consul_provider = ConsulProvider.new(base_path, config)
|
6
|
-
@file_provider = FileProvider.new(base_path, config)
|
7
|
-
end
|
8
|
-
|
9
|
-
def get(path)
|
10
|
-
consul_value = @consul_provider.get(path)
|
11
|
-
!consul_value.nil? && consul_value != '' ? consul_value : @file_provider.get(path)
|
12
|
-
end
|
13
|
-
|
14
|
-
alias [] get
|
15
|
-
end
|
16
|
-
end
|