consul_application_settings 3.0.1 → 4.0.0.pre.alpha

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 686e4efaf1dd51db8fdb6ade9ec5622a080d2d1a84db1c78b1c66dd19444e3f5
4
- data.tar.gz: 8eba55e8bf3b905ba24e5f069b55b0dafd8c4374e98ee7fea5ed2feede597703
3
+ metadata.gz: 709d93b5794a39ed2f78eb61462cc07ff0ed77dccd66343ad22fc13abc45c267
4
+ data.tar.gz: 6daa5f0e9266fad7baee4e44efe8ed49ac5451744bd3abb60667afb1afb1a132
5
5
  SHA512:
6
- metadata.gz: 447e52e8cceef44793db9de2977fc2facb752fc1232f6975eb73af38bd189458ebfc3287a7cde383da3d41f6ed744bf791c1f7079f3d8f91616dca0779dc54ef
7
- data.tar.gz: e22c374ec16003f67bcf520943fcde6b00084178225c332d80df1fa6d34602c96bbce9c7b59a8290c68f197ba5ee8de444945ee9fda85d53b18667bd34428635
6
+ metadata.gz: 399c74e1bed212dea80f9b5d3f0f202c702b2e7704824214703f8003841b35792b66e3c6707ec7594d4b6860acbdd1b341c65e85312448d9fef3e2aa4a1c6c16
7
+ data.tar.gz: 4b04c272e96bc975d0cae6984dc6c44e4d442b0dc1a78a51b9d6ca772b4d5b09bcc10ce6155113a955210e9abe4d91ccb446224fd881f0a79f29da55aebb6fc7
@@ -14,12 +14,19 @@ jobs:
14
14
  runs-on: ubuntu-latest
15
15
  strategy:
16
16
  matrix:
17
- ruby: [ '2.5.x', '2.6.x' ]
17
+ ruby: [ '2.5.x', '2.6.x', '2.7.x', '3.0.x' ]
18
18
  services:
19
- redis:
19
+ consul:
20
20
  image: consul:1.6
21
21
  ports:
22
22
  - '8500:8500'
23
+ vault:
24
+ image: vault
25
+ ports:
26
+ - "8200:8200"
27
+ env:
28
+ VAULT_DEV_ROOT_TOKEN_ID: root_token
29
+ SKIP_SETCAP: true
23
30
  steps:
24
31
  - name: Checkout
25
32
  uses: actions/checkout@v1
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,14 @@
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
+
3
12
  ## [3.0.1]
4
13
  ### Fixes
5
14
  - Fix exception when preloading settings without consul
@@ -59,7 +68,8 @@
59
68
  - Support deep settings search
60
69
  - Support nested configs
61
70
 
62
- [Unreleased]: https://github.com/matic-insurance/consul_application_settings/compare/3.0.1...HEAD
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
63
73
  [3.0.1]: https://github.com/matic-insurance/consul_application_settings/compare/3.0.0...3.0.1
64
74
  [3.0.0]: https://github.com/matic-insurance/consul_application_settings/compare/2.0.0...3.0.0
65
75
  [2.1.1]: https://github.com/matic-insurance/consul_application_settings/compare/2.1.0...2.1.1
data/Gemfile.lock CHANGED
@@ -2,12 +2,16 @@ PATH
2
2
  remote: .
3
3
  specs:
4
4
  consul_application_settings (0.0.0)
5
+ deep_merge (~> 1.2)
5
6
  diplomat (~> 2.5.1)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
10
11
  ast (2.4.0)
12
+ aws-eventstream (1.2.0)
13
+ aws-sigv4 (1.4.0)
14
+ aws-eventstream (~> 1, >= 1.0.2)
11
15
  codecov (0.4.3)
12
16
  simplecov (>= 0.15, < 0.22)
13
17
  deep_merge (1.2.1)
@@ -61,6 +65,8 @@ GEM
61
65
  simplecov-html (~> 0.10.0)
62
66
  simplecov-html (0.10.2)
63
67
  unicode-display_width (1.5.0)
68
+ vault (0.16.0)
69
+ aws-sigv4
64
70
 
65
71
  PLATFORMS
66
72
  ruby
@@ -74,6 +80,7 @@ DEPENDENCIES
74
80
  rubocop (~> 0.66)
75
81
  rubocop-rspec (~> 1.32.0)
76
82
  simplecov (~> 0.16)
83
+ vault (~> 0.16)
77
84
 
78
85
  BUNDLED WITH
79
86
  2.1.2
data/README.md CHANGED
@@ -3,26 +3,16 @@
3
3
  ![Build Status](https://github.com/matic-insurance/consul_application_settings/workflows/ci/badge.svg?branch=master)
4
4
  [![Test Coverage](https://codecov.io/gh/matic-insurance/consul_application_settings/branch/master/graph/badge.svg?token=5E8NA8EE8L)](https://codecov.io/gh/matic-insurance/consul_application_settings)
5
5
 
6
- Gem that simplifies usage of Consul (via [Diplomat gem](https://github.com/WeAreFarmGeek/diplomat))
7
- to host application settings. Gem provides defaults via yaml files and other utilities
8
- to simplify storage and control of application with Consul KV storage.
6
+ This gem that simplifies usage of Consul (via [Diplomat gem](https://github.com/WeAreFarmGeek/diplomat))
7
+ to host application settings.
9
8
 
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.
9
+ Except reading value from Consul the gem also:
10
+ - Fallbacks to YAML if value is missing in consul
11
+ - Resolve actual value from other sources to facilitate overriding via ENV, storing secret values in Vault,
12
+ or executing small ERB snippets
12
13
 
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.
20
-
21
- How do they learn (have reference) of what settings and structure application expect?
22
-
23
- Gem reads any particular setting from consul and if it is missing tries to find value in YAML defaults file
24
-
25
- **NOTE** Consul is requested every time you query the settings. Defaults YAML file is loaded in memory and is not changing.
14
+ Default values in YAML also can be considered as a way to communicate structure of settings to other engineers.
15
+ Default values also support local settings to allow override on local environment or deployment in production.
26
16
 
27
17
  ## Installation
28
18
 
@@ -39,22 +29,27 @@ gem 'consul_application_settings'
39
29
  At the load of application:
40
30
  ```ruby
41
31
  ConsulApplicationSettings.configure do |config|
42
- # Specify path to the base settings YML. Default: 'config/application_settings.yml'
43
- config.base_file_path = Rails.root.join('config/my_settings.yml')
44
- # Specify path to the local settings YML, which overrides the base file. Default: 'config/application_settings.local.yml'
45
- config.local_file_path = Rails.root.join('config/my_settings.local.yml')
46
- # Specify whether exceprion should be thrown on Consul connection errors. Default: false
32
+ # Specify path to the base settings YML. Default: 'config/app_settings.yml'
33
+ config.base_file_path = Rails.root.join('config/app_settings.yml')
34
+ # Specify path to the local settings YML, which overrides the base file. Default: 'config/app_settings.local.yml'
35
+ config.local_file_path = Rails.root.join('config/app_settings.local.yml')
36
+ # Specify whether exception should be thrown on Consul connection errors. Default: false
47
37
  config.disable_consul_connection_errors = true
48
38
  # Specify setting providers. Default: [ConsulApplicationSettings::Providers::ConsulPreloaded, ConsulApplicationSettings::Providers::LocalStorage]
49
39
  config.settings_providers = [
50
40
  ConsulApplicationSettings::Providers::Consul,
51
41
  ConsulApplicationSettings::Providers::LocalStorage
52
42
  ]
43
+ # Specify how values will be additionally resolved. Default: [ConsulApplicationSettings::Resolvers::Env]
44
+ config.value_resolvers = [
45
+ ConsulApplicationSettings::Resolvers::Erb,
46
+ ConsulApplicationSettings::Resolvers::Env,
47
+ ]
53
48
  end
54
49
 
55
- APP_SETTINGS = ConsulApplicationSettings.load
56
50
  # Specify path to settings both in YML files and Consul
57
- AUTH_SETTIGNS = ConsulApplicationSettings.load('authentication')
51
+ AUTH_SETTIGNS = ConsulApplicationSettings.load('my_cool_app')
52
+ # Load at root without any prefix: APP_SETTINGS = ConsulApplicationSettings.load
58
53
  ```
59
54
 
60
55
  **NOTE** For rails you can add this code to custom initializer `console_application_settings.rb` in `app/config/initializers`
@@ -65,35 +60,32 @@ AUTH_SETTIGNS = ConsulApplicationSettings.load('authentication')
65
60
 
66
61
  Assuming your defaults file in repository `config/application_settings.yml` looks like:
67
62
  ```yaml
68
- staging:
69
- my_cool_app:
70
- app_name: 'MyCoolApp'
71
- hostname: 'http://localhost:3001'
72
-
73
- integrations:
74
- database:
75
- domain: localhost
76
- user: app
77
- password: password1234
78
- slack:
79
- enabled: false
80
- webhook_url: 'https://hooks.slack.com/services/XXXXXX/XXXXX/XXXXXXX'
63
+ my_cool_app:
64
+ app_name: 'MyCoolApp'
65
+ hostname: 'http://localhost:3001'
66
+
67
+ integrations:
68
+ database:
69
+ domain: localhost
70
+ user: app
71
+ password: password1234
72
+ slack:
73
+ enabled: false
74
+ webhook_url: 'https://hooks.slack.com/services/XXXXXX/XXXXX/XXXXXXX'
81
75
  ```
82
76
 
83
77
  And consul has following settings
84
78
  ```json
85
79
  {
86
- "staging": {
87
- "my_cool_app": {
88
- "hostname": "https://mycoolapp.com",
89
- "integrations": {
90
- "database": {
91
- "domain": "194.78.92.19",
92
- "password": "*************"
93
- },
94
- "slack": {
95
- "enabled": "true"
96
- }
80
+ "my_cool_app": {
81
+ "hostname": "https://mycoolapp.com",
82
+ "integrations": {
83
+ "database": {
84
+ "domain": "194.78.92.19",
85
+ "password": "*************"
86
+ },
87
+ "slack": {
88
+ "enabled": "true"
97
89
  }
98
90
  }
99
91
  }
@@ -120,10 +112,60 @@ gem provides interface to avoid duplicating absolute path
120
112
 
121
113
  ```ruby
122
114
  # You can load subsettings from root object
123
- db_settings = APP_SETTINGS.load('integrations/database')
115
+ db_settings = APP_SETTINGS.load('integrations/database') # ConsulApplicationSettings::Reader
124
116
  db_settings.get(:domain) # "194.78.92.19"
125
117
  db_settings['user'] # "app"
126
- ```
118
+
119
+ #if you try to get subsettings via get - error is raised
120
+ APP_SETTINGS.get('integrations/database') # raise ConsulApplicationSettings::Error
121
+ ```
122
+
123
+ ## Advanced Configurations
124
+
125
+ ### Setting Providers
126
+ Providers controls how and in which order settings are retrieved.
127
+ When application asks for specific setting - gem retrieves them from every provider in order of configuration
128
+ until one returns not nil value.
129
+
130
+ Default order for providers is:
131
+ 1. `ConsulApplicationSettings::Providers::ConsulPreloaded`
132
+ 2. `ConsulApplicationSettings::Providers::LocalStorage`
133
+
134
+ List of built in providers:
135
+ - `ConsulApplicationSettings::Providers::ConsulPreloaded` - Retrieves all settings from consul on every `.load`
136
+ - `ConsulApplicationSettings::Providers::Consul` - Retrieves setting every time `.get` method is called
137
+ - `ConsulApplicationSettings::Providers::LocalStorage` - Retrieves all settings from local files on every `.load`
138
+
139
+ Custom provider can be added as long as it support following interface:
140
+ ```ruby
141
+ class CustomProvider
142
+ #constructor
143
+ def initialize(base_path, config)
144
+ end
145
+
146
+ # get value by `base_path + '/' + path`
147
+ def get(path)
148
+ end
149
+ end
150
+ ```
151
+
152
+ ### Resolvers
153
+ Once value is retrieved - it will be additionally processed by resolvers.
154
+ This allows for additional flexibility like getting values from external sources.
155
+ While every resolver can be implemented in a form of a provider - one will be limited by the structure of settings,
156
+ while other system might not be compatible with this.
157
+
158
+ When value is retrieved - gem finds **first** provider that can resolve value and resolves it.
159
+ Resolved value is returned to application.
160
+
161
+ Default list of resolvers:
162
+ - `ConsulApplicationSettings::Resolvers::Env`
163
+
164
+ List of built in resolvers
165
+ - `ConsulApplicationSettings::Resolvers::Env` - resolves any value by looking up environment variable.
166
+ Matching any value that starts with `env://`. Value like `env://TEST_URL` will be resolved as `ENV['TEST_URL']`
167
+ - `ConsulApplicationSettings::Resolvers::Erb` - resolves value by rendering it via ERB.
168
+ Matching any value that contains `<%` and `%>` in it. Value like `<%= 2 + 2 %>` will be resolved as `4`
127
169
 
128
170
  ### Gem Configuration
129
171
  You can configure gem with block:
@@ -145,18 +187,17 @@ All Gem configurations
145
187
  | local_file_path | no | 'config/application_settings.local.yml' | String | Path to the file with local settings overriding the base settings |
146
188
  | disable_consul_connection_errors | no | true | Boolean | Do not raise exception when consul is not available (useful for development) |
147
189
  | settings_providers | no | Array(Provider) | Array | Specify custom setting provider lists |
148
-
149
- ### Performance vs Consistency
150
- To be defined in future iterations on Consul Providers
190
+ | value_resolvers | no | Array(Resolver) | Array | Specify custom value resolvers lists |
151
191
 
152
192
  ## Development
153
193
 
154
- 1. [Install Consul](https://www.consul.io/docs/install/index.html)
155
194
  1. Run `bin/setup` to install dependencies
156
- 1. Run tests `rspec`
157
- 1. Add new test
158
- 1. Add new code
159
- 1. Go to step 3
195
+ 2. Run `docker-compose up` to spin up dependencies (Consul)
196
+ 3. Run tests `rspec`
197
+ 4. Add new test
198
+ 5. Add new code
199
+ 6. Go to step 3
200
+ 7. Create PR
160
201
 
161
202
  ## Contributing
162
203
 
data/bin/benchmark CHANGED
@@ -54,10 +54,9 @@ puts 'Benchmark without consul agent'
54
54
  kill_consul
55
55
  benchmark_gem
56
56
 
57
-
58
57
  puts '-' * 80
59
58
  puts 'Benchmark with consul agent running'
60
59
  start_consul
61
60
  benchmark_gem
62
61
 
63
- kill_consul
62
+ kill_consul
@@ -29,13 +29,15 @@ 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
- spec.add_development_dependency 'codecov', '~> 0.4'
42
+ spec.add_development_dependency 'vault', '~> 0.16'
41
43
  end
@@ -0,0 +1,17 @@
1
+ # dependencies needed for development environment
2
+ version: '3'
3
+ services:
4
+ vault:
5
+ image: vault
6
+ ports:
7
+ - "8200:8200"
8
+ cap_add:
9
+ - IPC_LOCK
10
+ environment:
11
+ VAULT_DEV_ROOT_TOKEN_ID: root_token
12
+ consul:
13
+ image: consul
14
+ ports:
15
+ - "8500:8500"
16
+ environment:
17
+ CONSUL_BIND_INTERFACE: eth0
@@ -6,14 +6,19 @@ module ConsulApplicationSettings
6
6
  DEFAULT_PROVIDERS = [
7
7
  ConsulApplicationSettings::Providers::ConsulPreloaded,
8
8
  ConsulApplicationSettings::Providers::LocalStorage
9
- ]
10
- attr_accessor :base_file_path, :local_file_path, :disable_consul_connection_errors, :settings_providers
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
11
15
 
12
16
  def initialize
13
17
  @base_file_path = DEFAULT_BASE_FILE_PATH
14
18
  @local_file_path = DEFAULT_LOCAL_FILE_PATH
15
19
  @disable_consul_connection_errors = true
16
20
  @settings_providers = DEFAULT_PROVIDERS
21
+ @value_resolvers = DEFAULT_RESOLVERS
17
22
  end
18
23
  end
19
24
  end
@@ -3,10 +3,11 @@ module ConsulApplicationSettings
3
3
  # Abstract class with basic functionality
4
4
  class Abstract
5
5
  def initialize(base_path, config)
6
- @base_path, @config = base_path, config
6
+ @base_path = base_path
7
+ @config = config
7
8
  end
8
9
 
9
- def get(path)
10
+ def get(_path)
10
11
  raise NotImplementedError
11
12
  end
12
13
 
@@ -22,4 +23,4 @@ module ConsulApplicationSettings
22
23
  end
23
24
  end
24
25
  end
25
- end
26
+ end
@@ -7,18 +7,26 @@ module ConsulApplicationSettings
7
7
  def get(path)
8
8
  full_path = absolute_key_path(path)
9
9
  value = get_from_consul(full_path)
10
+ value = resolve_tree_response(value, full_path)
10
11
  ConsulApplicationSettings::Utils.cast_consul_value(value)
11
12
  end
12
13
 
13
14
  private
14
15
 
15
16
  def get_from_consul(path)
16
- Diplomat::Kv.get(path, {})
17
+ Diplomat::Kv.get(path, recurse: true)
17
18
  rescue Diplomat::KeyNotFound
18
- return nil
19
+ nil
19
20
  rescue SystemCallError, Faraday::ConnectionFailed, Diplomat::PathNotFound => e
20
21
  raise e unless @config.disable_consul_connection_errors
21
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
22
30
  end
23
31
  end
24
32
  end
@@ -4,24 +4,42 @@ module ConsulApplicationSettings
4
4
  class ConsulPreloaded < Abstract
5
5
  def initialize(base_path, config)
6
6
  super
7
- @data = get_all_from_consul
7
+ @data = read_all_from_consul
8
8
  end
9
9
 
10
10
  def get(path)
11
11
  value = get_value_from_hash(absolute_key_path(path), @data)
12
+ value = resolve_tree_response(value)
12
13
  ConsulApplicationSettings::Utils.cast_consul_value(value)
13
14
  end
14
15
 
15
16
  protected
16
17
 
17
- def get_all_from_consul
18
- Diplomat::Kv.get_all(@base_path, { convert_to_hash: true })
18
+ def read_all_from_consul
19
+ Diplomat::Kv.get_all(@base_path, convert_to_hash: true)
19
20
  rescue Diplomat::KeyNotFound
20
21
  {}
21
22
  rescue SystemCallError, Faraday::ConnectionFailed, Diplomat::PathNotFound => e
22
23
  raise e unless @config.disable_consul_connection_errors
24
+
23
25
  {}
24
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
25
43
  end
26
44
  end
27
- end
45
+ end
@@ -19,7 +19,7 @@ module ConsulApplicationSettings
19
19
  base_yml = read_yml(base_file_path)
20
20
  local_yml = read_yml(local_file_path)
21
21
  DeepMerge.deep_merge!(local_yml, base_yml, preserve_unmergeables: false, overwrite_arrays: true,
22
- merge_nil_values: true)
22
+ merge_nil_values: true)
23
23
  end
24
24
 
25
25
  def base_file_path
@@ -1,18 +1,16 @@
1
1
  module ConsulApplicationSettings
2
- # Provides access to settings stored in Consul or in file system
2
+ # Orchestrates fetching values from provider and resolving them
3
3
  class Reader
4
4
  def initialize(base_path, config)
5
5
  @base_path = base_path
6
6
  @config = config
7
7
  @providers = config.settings_providers.map { |provider| provider.new(base_path, config) }
8
+ @resolvers = config.value_resolvers.map(&:new)
8
9
  end
9
10
 
10
11
  def get(path)
11
- @providers.each do |provider|
12
- value = provider.get(path)
13
- return value unless value.nil?
14
- end
15
- nil
12
+ value = fetch_value(path)
13
+ resolve_value(value, path)
16
14
  end
17
15
 
18
16
  alias [] get
@@ -21,5 +19,32 @@ module ConsulApplicationSettings
21
19
  new_path = ConsulApplicationSettings::Utils.generate_path(@base_path, sub_path)
22
20
  self.class.new(new_path, @config)
23
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
24
49
  end
25
50
  end
@@ -0,0 +1,14 @@
1
+ module ConsulApplicationSettings
2
+ module Resolvers
3
+ # Abstract resolver with basic functionality
4
+ class Abstract
5
+ def resolvable?(_value, _path)
6
+ false
7
+ end
8
+
9
+ def resolve(value, _path)
10
+ value
11
+ end
12
+ end
13
+ end
14
+ 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
@@ -0,0 +1,24 @@
1
+ module ConsulApplicationSettings
2
+ module Resolvers
3
+ # Resolve values using vault
4
+ class Vault
5
+ IDENTIFIER = 'vault://'.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
+ # Expect value in format `vault://mount/path/to/secret?attribute_name`
14
+ def resolve(value, _path)
15
+ value = value.delete_prefix(IDENTIFIER)
16
+ mount, secret = value.split('/', 2)
17
+ secret, attribute = secret.split('?')
18
+ attribute ||= 'value'
19
+ secret = ::Vault.kv(mount).read(secret)
20
+ secret && secret.data[attribute.to_sym]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -9,6 +9,8 @@ module ConsulApplicationSettings
9
9
  return nil if value.nil?
10
10
  return false if value == 'false'
11
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)
12
14
 
13
15
  cast_complex_value(value)
14
16
  end
@@ -34,6 +36,16 @@ module ConsulApplicationSettings
34
36
  end
35
37
  value.to_s
36
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
37
49
  end
38
50
  end
39
51
  end
@@ -1,3 +1,3 @@
1
1
  module ConsulApplicationSettings
2
- VERSION = '3.0.1'.freeze
2
+ VERSION = '4.0.0-alpha'.freeze
3
3
  end
@@ -3,6 +3,9 @@ require 'consul_application_settings/providers/abstract'
3
3
  require 'consul_application_settings/providers/consul'
4
4
  require 'consul_application_settings/providers/consul_preloaded'
5
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'
6
9
  require 'consul_application_settings/configuration'
7
10
  require 'consul_application_settings/reader'
8
11
  require 'consul_application_settings/utils'
@@ -13,7 +16,6 @@ module ConsulApplicationSettings
13
16
 
14
17
  class << self
15
18
  attr_accessor :config
16
- attr_accessor :defaults
17
19
  end
18
20
 
19
21
  self.config ||= ConsulApplicationSettings::Configuration.new
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: 3.0.1
4
+ version: 4.0.0.pre.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Volodymyr Mykhailyk
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-09 00:00:00.000000000 Z
11
+ date: 2021-12-06 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
@@ -109,19 +137,19 @@ dependencies:
109
137
  - !ruby/object:Gem::Version
110
138
  version: '0.16'
111
139
  - !ruby/object:Gem::Dependency
112
- name: codecov
140
+ name: vault
113
141
  requirement: !ruby/object:Gem::Requirement
114
142
  requirements:
115
143
  - - "~>"
116
144
  - !ruby/object:Gem::Version
117
- version: '0.4'
145
+ version: '0.16'
118
146
  type: :development
119
147
  prerelease: false
120
148
  version_requirements: !ruby/object:Gem::Requirement
121
149
  requirements:
122
150
  - - "~>"
123
151
  - !ruby/object:Gem::Version
124
- version: '0.4'
152
+ version: '0.16'
125
153
  description: |-
126
154
  Gem that simplifies usage of Consul (via Diplomat gem) to host application settings.
127
155
  Gem provides defaults and utilities
@@ -148,6 +176,7 @@ files:
148
176
  - bin/rspec
149
177
  - bin/setup
150
178
  - consul_application_settings.gemspec
179
+ - docker-compose.yml
151
180
  - lib/consul_application_settings.rb
152
181
  - lib/consul_application_settings/configuration.rb
153
182
  - lib/consul_application_settings/providers/abstract.rb
@@ -155,6 +184,10 @@ files:
155
184
  - lib/consul_application_settings/providers/consul_preloaded.rb
156
185
  - lib/consul_application_settings/providers/local_storage.rb
157
186
  - lib/consul_application_settings/reader.rb
187
+ - lib/consul_application_settings/resolvers/abstract.rb
188
+ - lib/consul_application_settings/resolvers/env.rb
189
+ - lib/consul_application_settings/resolvers/erb.rb
190
+ - lib/consul_application_settings/resolvers/vault.rb
158
191
  - lib/consul_application_settings/utils.rb
159
192
  - lib/consul_application_settings/version.rb
160
193
  homepage: https://github.com/matic-insurance/consul_application_settings
@@ -175,9 +208,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
175
208
  version: '0'
176
209
  required_rubygems_version: !ruby/object:Gem::Requirement
177
210
  requirements:
178
- - - ">="
211
+ - - ">"
179
212
  - !ruby/object:Gem::Version
180
- version: '0'
213
+ version: 1.3.1
181
214
  requirements: []
182
215
  rubygems_version: 3.0.3.1
183
216
  signing_key: