consul_application_settings 3.0.1 → 4.0.0.pre.alpha

Sign up to get free protection for your applications and to get access to all the features.
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: