qonfig 0.24.1 → 0.25.0

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: 6d1999e0aae940158d50623dcf308d0cc37456f2f88fb3a2fea1b6f3e595c102
4
- data.tar.gz: 9e80d958e30a13850712353ee6b624cccb7666e39b91c13d0c5308f9e6988e7a
3
+ metadata.gz: 9b875f81a892a041c391457b18989daa2626ce3b04afb3e0290314a0fafeb8ad
4
+ data.tar.gz: f7eb3ff0d6801364070e22a8560fdfcc10e08bec59d8e876834b800682832394
5
5
  SHA512:
6
- metadata.gz: 94637e644c352e6c972c9387b773687212553bbc1dc88da8498dc07bd212edda281173d5d793df4d47ebf5f1e03f4ba65e3fb48bb89a85a24048ff6f25d379b1
7
- data.tar.gz: ef6f5f2d6ef534f07daade1be1ba6f39ca4fee282ea1c082688e6e6c23aff257613349a4fefbad7582a1e907246c47f291c6e235ed5b0a95399e7e7ed5f1bcc4
6
+ metadata.gz: d0ea42e1bcaed282dee699f798eb8b21e188afb2f37720b9645c97dc9a791596604f65de2d81b34e83102a8f94a5a5fbb7892bd20184c0141884e3e4ac86a024
7
+ data.tar.gz: 6657ab2f25a10c86bda9c87cb32ccaa494fd831f1d237bd30f561f7c8e14da50e173de18ec2cca3a400d16769f8001397160b4ef33a9154da599d69317385ef9
@@ -5,7 +5,7 @@ inherit_gem:
5
5
  - lib/rubocop.rspec.yml
6
6
 
7
7
  AllCops:
8
- TargetRubyVersion: 2.6.5
8
+ TargetRubyVersion: 2.7.1
9
9
  Include:
10
10
  - lib/**/*.rb
11
11
  - spec/**/*.rb
@@ -1,17 +1,22 @@
1
1
  language: ruby
2
- matrix:
2
+ os: linux
3
+ dist: xenial
4
+ cache: bundler
5
+ before_install: gem install bundler
6
+ script: bundle exec rspec
7
+ jobs:
3
8
  fast_finish: true
4
9
  include:
5
- - rvm: 2.4.9
10
+ - rvm: 2.4.10
6
11
  gemfile: gemfiles/with_external_deps.gemfile
7
12
  env: TEST_PLUGINS=true FULL_TEST_COVERAGE_CHECK=true
8
- - rvm: 2.5.7
13
+ - rvm: 2.5.8
9
14
  gemfile: gemfiles/with_external_deps.gemfile
10
15
  env: TEST_PLUGINS=true FULL_TEST_COVERAGE_CHECK=true
11
- - rvm: 2.6.5
16
+ - rvm: 2.6.6
12
17
  gemfile: gemfiles/with_external_deps.gemfile
13
18
  env: TEST_PLUGINS=true FULL_TEST_COVERAGE_CHECK=true
14
- - rvm: 2.7.0
19
+ - rvm: 2.7.1
15
20
  gemfile: gemfiles/with_external_deps.gemfile
16
21
  env: TEST_PLUGINS=true FULL_TEST_COVERAGE_CHECK=true
17
22
  - rvm: ruby-head
@@ -23,13 +28,13 @@ matrix:
23
28
  - rvm: truffleruby
24
29
  gemfile: gemfiles/with_external_deps.gemfile
25
30
  env: TEST_PLUGINS=true FULL_TEST_COVERAGE_CHECK=true
26
- - rvm: 2.4.9
31
+ - rvm: 2.4.10
27
32
  gemfile: gemfiles/without_external_deps.gemfile
28
- - rvm: 2.5.7
33
+ - rvm: 2.5.8
29
34
  gemfile: gemfiles/without_external_deps.gemfile
30
- - rvm: 2.6.5
35
+ - rvm: 2.6.6
31
36
  gemfile: gemfiles/without_external_deps.gemfile
32
- - rvm: 2.7.0
37
+ - rvm: 2.7.1
33
38
  gemfile: gemfiles/without_external_deps.gemfile
34
39
  - rvm: ruby-head
35
40
  gemfile: gemfiles/without_external_deps.gemfile
@@ -41,7 +46,3 @@ matrix:
41
46
  - rvm: ruby-head
42
47
  - rvm: jruby-head
43
48
  - rvm: truffleruby
44
- sudo: false
45
- cache: bundler
46
- before_install: gem install bundler
47
- script: bundle exec rspec
@@ -1,6 +1,40 @@
1
1
  # Changelog
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [0.25.0] - 2020-09-15
5
+ ### Added
6
+ - Support for **Vault** config provider:
7
+ - realized as a plugin (`Qonfig.plugin(:vault)`);
8
+ - provides `#load_from_vault`, `#expose_vault` methods and works in `#*_yaml`-like manner);
9
+ - depends on `gem vault (>= 0.1)`
10
+ - `Qonfig::Settings#[]` behave like `Qonfig::Settings#__dig__`;
11
+ - An ability to represent the config hash in dot-notated style (all config keys are represented in dot-notated format):
12
+ - works via `#to_h(dot_style: true)`;
13
+ - `key_transformer:` and `value_transfomer:` options are supported too;
14
+
15
+ ```ruby
16
+ class Config << Qonfig::DataSet
17
+ setting :database do
18
+ setting :host, 'localhost'
19
+ setting :port, 6432
20
+ end
21
+
22
+ setting :api do
23
+ setting :rest_enabled, true
24
+ setting :rpc_enabled, false
25
+ end
26
+ end
27
+
28
+ Config.new.to_h(dot_style: true)
29
+ # =>
30
+ {
31
+ 'database.host' => 'localhost',
32
+ 'database.port' => 6432,
33
+ 'api.rest_enabled' => true,
34
+ 'api.rpc_enabled' => false,
35
+ }
36
+ ```
37
+
4
38
  ## [0.24.1] - 2020-03-10
5
39
  ### Changed
6
40
  - Enhanced dot-notated key resolving algorithm: now it goes through the all dot-notated key parts
@@ -9,7 +43,7 @@ All notable changes to this project will be documented in this file.
9
43
  ### Fixed
10
44
  - (**Pretty-Print Plugin**):
11
45
  - dot-noted setting keys can not be pretty-printed (they raise `Qonfig::UnknownSettingKeyError`);
12
- - added `set` and `pp` dependnecy as pre-reloaded dependencies;
46
+ - added `set` and `pp` as preloaded dependencies;
13
47
 
14
48
  ## [0.24.0] - 2019-12-29
15
49
  ### Added
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2018-2019 Rustam Ibragimov
3
+ Copyright (c) 2018-2020 Rustam Ibragimov
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -40,6 +40,9 @@ require 'qonfig'
40
40
  - [Inheritance](#inheritance)
41
41
  - [Composition](#composition)
42
42
  - [Hash representation](#hash-representation)
43
+ - [Default behaviour (without options)](#default-behavior-without-options)
44
+ - [With transformations](#with-transformations)
45
+ - [Dot-style format](#dot-style-format)
43
46
  - [Smart Mixin](#smart-mixin) (`Qonfig::Configurable`)
44
47
  - [Instantiation without class definition](#instantiation-without-class-definition) (`Qonfig::DataSet.build(&definitions)`)
45
48
  - [Compacted config](#compacted-config)
@@ -102,6 +105,7 @@ require 'qonfig'
102
105
  - [Plugins](#plugins)
103
106
  - [toml](#plugins-toml) (support for `TOML` format)
104
107
  - [pretty_print](#plugins-pretty_print) (beautified/prettified console output)
108
+ - [vault](#plugins-vault) (support for `Vault` store)
105
109
  - [Roadmap](#roadmap)
106
110
  - [Build](#build)
107
111
  ---
@@ -188,6 +192,10 @@ config.settings['vendor_api']['domain'] # => 'app.service.com'
188
192
  config.settings['vendor_api']['login'] # => 'test_user'
189
193
  config.settings['enable_graphql'] # => false
190
194
 
195
+ # dig to value
196
+ config.settings[:vendor_api, :domain] # => 'app.service.com'
197
+ config.settings[:vendor_api, 'login'] # => 'test_user'
198
+
191
199
  # get option value directly via index (with indifferent access)
192
200
  config['project_id'] # => nil
193
201
  config['enable_graphql'] # => false
@@ -271,7 +279,7 @@ config.slice_value('vendor_api.port') # => Qonfig::UnknownSettingError # (key do
271
279
  # - get a subset (a set of sets) of config settings represented as a hash;
272
280
  # - each key (or key set) represents a requirement of a certain setting key;
273
281
 
274
- config.subet(:vendor_api, :enable_graphql)
282
+ config.subset(:vendor_api, :enable_graphql)
275
283
  # => { 'vendor_api' => { 'login' => ..., 'domain' => ... }, 'enable_graphql' => false }
276
284
 
277
285
  config.subset(:project_id, [:vendor_api, :domain], [:credentials, :user, :login])
@@ -436,6 +444,12 @@ project_config.settings.db.password # => 'testpaswd'
436
444
 
437
445
  ### Hash representation
438
446
 
447
+ - works via `#to_h` and `#to_hash`;
448
+ - supported options:
449
+ - `key_transformer:` - an optional proc that accepts setting key and makes your custom transformations;
450
+ - `value_transformer:` - an optional proc that accepts setting value and makes your custom transformations;
451
+ - `dot_style:` - (`false` by default) represent setting keys in dot-notation (transformations are supported too);
452
+
439
453
  ```ruby
440
454
  class Config < Qonfig::DataSet
441
455
  setting :serializers do
@@ -454,9 +468,13 @@ class Config < Qonfig::DataSet
454
468
 
455
469
  setting :logger, Logger.new(STDOUT)
456
470
  end
471
+ ```
457
472
 
458
- Config.new.to_h
473
+ #### Default behavior (without-options)
459
474
 
475
+ ```ruby
476
+ Config.new.to_h
477
+ # =>
460
478
  {
461
479
  "serializers": {
462
480
  "json" => { "engine" => :ok },
@@ -467,6 +485,55 @@ Config.new.to_h
467
485
  }
468
486
  ```
469
487
 
488
+ #### With transformations
489
+
490
+ - with `key_transformer` and/or `value_transformer`;
491
+
492
+ ```ruby
493
+ key_transformer = -> (key) { "#{key}!!" }
494
+ value_transformer = -> (value) { "#{value}??" }
495
+
496
+ Config.new.to_h(key_transformer: key_transformer, value_transformer: value_transformer)
497
+ # =>
498
+ {
499
+ "serializers!!": {
500
+ "json!!" => { "engine!!" => "ok??" },
501
+ "hash!!" => { "engine!!" => "native??" },
502
+ },
503
+ "adapter!!" => { "default!!" => "memory_sync??" },
504
+ "logger!!" => "#<Logger:0x00007fcde799f158>??"
505
+ }
506
+ ```
507
+
508
+ #### Dot-style format
509
+
510
+ - transformations are supported too (`key_transformer` and `value_transformer`);
511
+
512
+ ```ruby
513
+ Config.new.to_h(dot_style: true)
514
+ # =>
515
+ {
516
+ "serializers.json.engine" => :ok,
517
+ "serializers.hash.engine" => :native,
518
+ "adapter.default" => :memory_sync,
519
+ "logger" => #<Logger:0x4b0d79fc>,
520
+ }
521
+ ```
522
+
523
+ ```ruby
524
+ transformer = -> (value) { "$$#{value}$$" }
525
+
526
+ Config.new.to_h(dot_style: true, key_transformer: transformer, value_transformer: transformer)
527
+
528
+ # => "#<Logger:0x00007fcde799f158>??"
529
+ {
530
+ "$$serializers.json.engine$$" => "$$ok$$",
531
+ "$$serializers.hash.engine$$" => "$$native$$",
532
+ "$$adapter.default$$" => "$$memory_sync$$",
533
+ "$$logger$$" => "$$#<Logger:0x00007fcde799f158>$$",
534
+ }
535
+ ```
536
+
470
537
  ---
471
538
 
472
539
  ### Smart Mixin
@@ -3091,6 +3158,7 @@ dynamic: 10
3091
3158
 
3092
3159
  - [toml](#plugins-toml) (provides `load_from_toml`, `save_to_toml`, `expose_toml`);
3093
3160
  - [pretty_print](#plugins-pretty_print) (beautified/prettified console output);
3161
+ - [vault](#plugins-vault) (provides `load_from_vault`, `expose_vault`)
3094
3162
 
3095
3163
  ---
3096
3164
 
@@ -3214,6 +3282,31 @@ config = Config.new
3214
3282
 
3215
3283
  ---
3216
3284
 
3285
+ ### Plugins: vault
3286
+
3287
+ - `Qonfig.plugin(:vault)`
3288
+ - adds support for `vault kv store`, [more info](https://www.vaultproject.io/docs/secrets/kv/kv-v2)
3289
+ - depends on `vault` gem ([link](https://github.com/hashicorp/vault-ruby)) (tested on `>= 0.1`);
3290
+ - provides `.load_from_vault` (works in `.load_from_yaml` manner ([doc](#load-from-yaml-file)));
3291
+ - provides `.expose_vault` (works in `.expose_yaml` manner ([doc](#expose-yaml)));
3292
+
3293
+ ```ruby
3294
+ # 1) require external dependency
3295
+ require 'vault'
3296
+
3297
+ # 2) Setup vault client
3298
+
3299
+ Vault.address = 'http://localhost:8200'
3300
+ Vault.token = 'super-duper-token-here'
3301
+
3302
+ # 3) enable plugin
3303
+ Qonfig.plugin(:vault)
3304
+
3305
+ # 3) use vault :)
3306
+ ```
3307
+
3308
+ ---
3309
+
3217
3310
  ## Roadmap
3218
3311
 
3219
3312
  - **Major**:
@@ -3223,7 +3316,10 @@ config = Config.new
3223
3316
  - support for pattern matching;
3224
3317
  - **Minor**:
3225
3318
  - An ability to flag `Qonfig::Configurable`'s config object as `compacted` (`Qonfig::Compacted`);
3319
+ - Instance-based behavior for `Vault` plugin, also use instance of `Vault` client instead of `Singleton`;
3226
3320
  - External validation class with an importing api for better custom validations;
3321
+ - Setting value changement trace (in `anyway_config` manner);
3322
+ - Instantiation and reloading callbacks;
3227
3323
 
3228
3324
  ## Build
3229
3325
 
data/Rakefile CHANGED
@@ -4,7 +4,6 @@ require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
5
  require 'rubocop'
6
6
  require 'rubocop/rake_task'
7
- require 'rubocop-rails'
8
7
  require 'rubocop-performance'
9
8
  require 'rubocop-rspec'
10
9
  require 'rubocop-rake'
@@ -4,5 +4,7 @@ source 'https://rubygems.org'
4
4
 
5
5
  # @since 0.17.0 (qonfig: :toml plugin)
6
6
  gem 'toml-rb', '>= 2'
7
+ # @since 0.25.0 (qonfig: :vault plugin)
8
+ gem 'vault', '>= 0.1'
7
9
 
8
10
  gemspec path: '..'
@@ -64,4 +64,6 @@ module Qonfig
64
64
  register_plugin('toml', Qonfig::Plugins::TOML)
65
65
  # @since 0.19.0
66
66
  register_plugin('pretty_print', Qonfig::Plugins::PrettyPrint)
67
+ # @since 0.25.0
68
+ register_plugin('vault', Qonfig::Plugins::Vault)
67
69
  end
@@ -184,6 +184,7 @@ class Qonfig::DataSet # rubocop:disable Metrics/ClassLength
184
184
  end
185
185
  end
186
186
 
187
+ # @option dot_style [Boolean]
187
188
  # @option key_transformer [Proc]
188
189
  # @option value_transformer [Proc]
189
190
  # @return [Hash]
@@ -191,11 +192,13 @@ class Qonfig::DataSet # rubocop:disable Metrics/ClassLength
191
192
  # @api public
192
193
  # @since 0.1.0
193
194
  def to_h(
195
+ dot_style: Qonfig::Settings::REPRESENT_HASH_IN_DOT_STYLE,
194
196
  key_transformer: Qonfig::Settings::BASIC_SETTING_KEY_TRANSFORMER,
195
197
  value_transformer: Qonfig::Settings::BASIC_SETTING_VALUE_TRANSFORMER
196
198
  )
197
199
  thread_safe_access do
198
200
  settings.__to_hash__(
201
+ dot_notation: dot_style,
199
202
  transform_key: key_transformer,
200
203
  transform_value: value_transformer
201
204
  )
@@ -8,6 +8,7 @@ module Qonfig::Plugins
8
8
  require_relative 'plugins/abstract'
9
9
  require_relative 'plugins/toml'
10
10
  require_relative 'plugins/pretty_print'
11
+ require_relative 'plugins/vault'
11
12
 
12
13
  # @since 0.4.0
13
14
  @plugin_registry = Registry.new
@@ -101,7 +101,7 @@ class Qonfig::Commands::Definition::ExposeTOML < Qonfig::Commands::Base
101
101
  realfile = dirname.join(envfile).to_s
102
102
 
103
103
  toml_data = load_toml_data(realfile)
104
- toml_based_settings = builde_data_set_klass(toml_data).new.settings
104
+ toml_based_settings = build_data_set_klass(toml_data).new.settings
105
105
 
106
106
  settings.__append_settings__(toml_based_settings)
107
107
  end
@@ -125,7 +125,7 @@ class Qonfig::Commands::Definition::ExposeTOML < Qonfig::Commands::Base
125
125
  "#{file_path} file does not contain settings with <#{env}> environment key!"
126
126
  ) unless toml_data_slice
127
127
 
128
- toml_based_settings = builde_data_set_klass(toml_data_slice).new.settings
128
+ toml_based_settings = build_data_set_klass(toml_data_slice).new.settings
129
129
 
130
130
  settings.__append_settings__(toml_based_settings)
131
131
  end
@@ -145,7 +145,7 @@ class Qonfig::Commands::Definition::ExposeTOML < Qonfig::Commands::Base
145
145
  #
146
146
  # @api private
147
147
  # @since 0.12.0
148
- def builde_data_set_klass(toml_data)
148
+ def build_data_set_klass(toml_data)
149
149
  Qonfig::DataSet::ClassBuilder.build_from_hash(toml_data)
150
150
  end
151
151
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.25.0
5
+ class Qonfig::Plugins::Vault < Qonfig::Plugins::Abstract
6
+ class << self
7
+ # @return [void]
8
+ #
9
+ # @api private
10
+ # @since 0.25.0
11
+ def install!
12
+ raise(
13
+ Qonfig::UnresolvedPluginDependencyError,
14
+ '::Vault does not exist or "vault" gem is not loaded'
15
+ ) unless const_defined?('::Vault')
16
+
17
+ require_relative 'vault/errors'
18
+ require_relative 'vault/loaders/vault'
19
+ require_relative 'vault/commands/definition/load_from_vault'
20
+ require_relative 'vault/commands/definition/expose_vault'
21
+ require_relative 'vault/dsl'
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.25.0
5
+ class Qonfig::Commands::Definition::ExposeVault < Qonfig::Commands::Base
6
+ # @since 0.25.0
7
+ self.inheritable = true
8
+
9
+ # @return [Hash<Symbol,Symbol>]
10
+ #
11
+ # @api private
12
+ # @since 0.25.0
13
+ EXPOSERS = { path: :path, env_key: :env_key }.freeze
14
+
15
+ # @return [Hash]
16
+ #
17
+ # @api private
18
+ # @since 0.25.0
19
+ EMPTY_VAULT_DATA = {}.freeze
20
+
21
+ # @return [String, Pathname]
22
+ #
23
+ # @api private
24
+ # @since 0.25.0
25
+ attr_reader :path
26
+
27
+ # @return [Boolean]
28
+ #
29
+ # @api private
30
+ # @since 0.25.0
31
+ attr_reader :strict
32
+
33
+ # @return [Symbol]
34
+ #
35
+ # @api private
36
+ # @since 0.25.0
37
+ attr_reader :via
38
+
39
+ # @return [Symbol, String]
40
+ #
41
+ # @api private
42
+ # @since 0.25.0
43
+ attr_reader :env
44
+
45
+ # @param path [String Pathname]
46
+ # @option strict [Boolean]
47
+ # @option via [Symbol]
48
+ # @option env [String, Symbol]
49
+ # @return [void]
50
+ #
51
+ # @api private
52
+ # @since 0.25.0
53
+ def initialize(path, strict: true, via:, env:)
54
+ unless env.is_a?(Symbol) || env.is_a?(String) || env.is_a?(Numeric)
55
+ raise Qonfig::ArgumentError, ':env should be a string or a symbol'
56
+ end
57
+
58
+ raise Qonfig::ArgumentError, ':env should be provided' if env.to_s.empty?
59
+ raise Qonfig::ArgumentError, 'used :via is unsupported' unless EXPOSERS.key?(via)
60
+
61
+ @path = path
62
+ @strict = strict
63
+ @via = via
64
+ @env = env
65
+ end
66
+
67
+ # @param data_set [Qonfig::DataSet]
68
+ # @param settings [Qonfig::Settings]
69
+ # @return [void]
70
+ #
71
+ # @api private
72
+ # @since 0.25.0
73
+ def call(_data_set, settings)
74
+ case via
75
+ when EXPOSERS[:path]
76
+ expose_path!(settings)
77
+ when EXPOSERS[:env_key]
78
+ expose_env_key!(settings)
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ # @param settings [Qonfig::Settings]
85
+ # @return [void]
86
+ #
87
+ # @api private
88
+ # @since 0.25.0
89
+ def expose_path!(settings)
90
+ # NOTE: transform path (insert environment name into a secret name)
91
+ # from: kv/data/secret_name
92
+ # to: kv/data/env_name/secret_name
93
+
94
+ splitted_path = path.split('/')
95
+ real_path = splitted_path.insert(-2, env.to_s).join('/')
96
+
97
+ vault_data = load_vault_data(real_path)
98
+ vault_based_settings = build_data_set_class(vault_data).new.settings
99
+
100
+ settings.__append_settings__(vault_based_settings)
101
+ end
102
+
103
+ # @param settings [Qonfig::Settings]
104
+ # @return [void]
105
+ #
106
+ # @raise [Qonfig::ExposeError]
107
+ #
108
+ # @api private
109
+ # @since 0.25.0
110
+ def expose_env_key!(settings)
111
+ vault_data = load_vault_data(path)
112
+ vault_data_slice = vault_data[env.to_sym]
113
+ vault_data_slice = EMPTY_VAULT_DATA.dup if vault_data_slice.nil? && !strict
114
+
115
+ raise(
116
+ Qonfig::ExposeError,
117
+ "#{path} does not contain settings with <#{env}> environment key!"
118
+ ) unless vault_data_slice
119
+
120
+ vault_based_settings = build_data_set_class(vault_data_slice).new.settings
121
+
122
+ settings.__append_settings__(vault_based_settings)
123
+ end
124
+
125
+ # @param path [String]
126
+ # @return [Hash]
127
+ #
128
+ # @api private
129
+ # @since 0.25.0
130
+ def load_vault_data(path)
131
+ Qonfig::Loaders::Vault.load_file(path, fail_on_unexist: strict)
132
+ end
133
+
134
+ # @param vault_data [Hash]
135
+ # @return [Class<Qonfig::DataSet>]
136
+ #
137
+ # @api private
138
+ # @since 0.25.0
139
+ def build_data_set_class(vault_data)
140
+ Qonfig::DataSet::ClassBuilder.build_from_hash(vault_data)
141
+ end
142
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.25.0
5
+ class Qonfig::Commands::Definition::LoadFromVault < Qonfig::Commands::Base
6
+ # @since 0.25.0
7
+ self.inheritable = true
8
+
9
+ # @return [String, Pathname]
10
+ #
11
+ # @api private
12
+ # @since 0.25.0
13
+ attr_reader :path
14
+
15
+ # @return [Boolean]
16
+ #
17
+ # @api private
18
+ # @since 0.25.0
19
+ attr_reader :strict
20
+
21
+ # @param path [String]
22
+ # @option strict [Boolean]
23
+ #
24
+ # @api private
25
+ # @since 0.25.0
26
+ def initialize(path, strict: true)
27
+ @path = path
28
+ @strict = strict
29
+ end
30
+
31
+ # @param data_set [Qonfig::DataSet]
32
+ # @param settings [Qonfig::Settings]
33
+ # @return [void]
34
+ #
35
+ # @api private
36
+ # @since 0.25.0
37
+ def call(_data_set, settings)
38
+ vault_data = Qonfig::Loaders::Vault.load_file(path, fail_on_unexist: strict)
39
+ vault_based_settings = build_data_set_klass(vault_data).new.settings
40
+ settings.__append_settings__(vault_based_settings)
41
+ end
42
+
43
+ private
44
+
45
+ # @param toml_data [Hash]
46
+ # @return [Class<Qonfig::DataSet>]
47
+ #
48
+ # @api private
49
+ # @since 0.25.0
50
+ def build_data_set_klass(toml_data)
51
+ Qonfig::DataSet::ClassBuilder.build_from_hash(toml_data)
52
+ end
53
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.25.0
5
+ module Qonfig::DSL
6
+ # @param path [String, Pathname]
7
+ # @option strict [Boolean]
8
+ # @return [void]
9
+ #
10
+ # @see Qonfig::Commands::Definition::LoadFromVault
11
+ #
12
+ # @api public
13
+ # @since 0.25.0
14
+ def load_from_vault(path, strict: true)
15
+ definition_commands << Qonfig::Commands::Definition::LoadFromVault.new(
16
+ path, strict: strict
17
+ )
18
+ end
19
+
20
+ # @param path [String, Pathname]
21
+ # @option strict [Boolean]
22
+ # @option via [Symbol]
23
+ # @option env [Symbol, String]
24
+ # @return [void]
25
+ #
26
+ # @see Qonfig::Commands::Definition::ExposeVault
27
+ #
28
+ # @api public
29
+ # @since 0.25.0
30
+ def expose_vault(path, strict: true, via:, env:)
31
+ definition_commands << Qonfig::Commands::Definition::ExposeVault.new(
32
+ path, strict: strict, via: via, env: env
33
+ )
34
+ end
35
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api public
4
+ # @since 0.25.0
5
+ module Qonfig
6
+ # @api public
7
+ # @since 0.25.0
8
+ VaultLoaderError = Class.new(Vault::VaultError)
9
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @api private
4
+ # @since 0.25.0
5
+ class Qonfig::Loaders::Vault < Qonfig::Loaders::Basic
6
+ # @return [Binding]
7
+ #
8
+ # @api private
9
+ # @since 0.25.0
10
+ VAULT_EXPR_EVAL_SCOPE = BasicObject.new.__binding__.tap do |binding|
11
+ Object.new.method(:freeze).unbind.bind(binding.receiver).call
12
+ end
13
+
14
+ class << self
15
+ # @param path [String, Pathname]
16
+ # @option fail_on_unexist [Boolean]
17
+ # @return [Object]
18
+ #
19
+ # @raise [Qonfig::FileNotFoundError]
20
+ #
21
+ # @api private
22
+ # @since 0.25.0
23
+ def load_file(path, fail_on_unexist: true)
24
+ data = ::Vault.with_retries(Vault::HTTPError) do
25
+ ::Vault.logical.read(path.to_s)&.data&.dig(:data)
26
+ end
27
+ raise Qonfig::FileNotFoundError, "Path #{path} not exist" if data.nil? && fail_on_unexist
28
+ result = data || empty_data
29
+ deep_transform_values(result)
30
+ rescue Vault::VaultError => error
31
+ raise(Qonfig::VaultLoaderError.new(error.message).tap do |exception|
32
+ exception.set_backtrace(error.backtrace)
33
+ end)
34
+ end
35
+
36
+ # @return [Hash]
37
+ #
38
+ # @api private
39
+ # @since 0.25.0
40
+ def empty_data
41
+ {}
42
+ end
43
+
44
+ private
45
+
46
+ # @param vault_data [Hash<Object,Object>]
47
+ # @return [Object]
48
+ #
49
+ # @api private
50
+ # @since 0.25.0
51
+ def deep_transform_values(vault_data)
52
+ return vault_data unless vault_data.is_a?(Hash)
53
+
54
+ vault_data.transform_values do |value|
55
+ next safely_evaluate(value) if value.is_a?(String)
56
+
57
+ deep_transform_values(value)
58
+ end
59
+ end
60
+
61
+ # @param vault_expr [String]
62
+ # @return [Object]
63
+ #
64
+ # @api private
65
+ # @since 0.25.0
66
+ def safely_evaluate(vault_expr)
67
+ parsed_expr = ::ERB.new(vault_expr).result
68
+ VAULT_EXPR_EVAL_SCOPE.eval(parsed_expr)
69
+ rescue StandardError, ScriptError
70
+ parsed_expr
71
+ end
72
+ end
73
+ end
@@ -22,6 +22,12 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
22
22
  # @since 0.11.0
23
23
  BASIC_SETTING_VALUE_TRANSFORMER = (proc { |value| value }).freeze
24
24
 
25
+ # @return [Boolean]
26
+ #
27
+ # @api private
28
+ # @since 0.25.0
29
+ REPRESENT_HASH_IN_DOT_STYLE = false
30
+
25
31
  # @return [String]
26
32
  #
27
33
  # @api private
@@ -133,16 +139,13 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
133
139
  # @param key [Symbol, String]
134
140
  # @return [Object]
135
141
  #
142
+ # @raise [Qonfig::ArgumentError]
143
+ #
136
144
  # @api public
137
145
  # @since 0.1.0
138
- def [](key)
139
- __lock__.thread_safe_access do
140
- begin
141
- __get_value__(key)
142
- rescue Qonfig::UnknownSettingError
143
- __deep_access__(*__parse_dot_notated_key__(key))
144
- end
145
- end
146
+ # @version 0.25.0
147
+ def [](*keys)
148
+ __dig__(*keys)
146
149
  end
147
150
 
148
151
  # @param key [String, Symbol]
@@ -211,27 +214,47 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
211
214
  __lock__.thread_safe_access { __deep_subset__(*keys) }
212
215
  end
213
216
 
217
+ # @option dot_notation [Boolean]
214
218
  # @option transform_key [Proc]
215
219
  # @option transform_value [Proc]
216
220
  # @return [Hash]
217
221
  #
218
222
  # @api private
219
223
  # @since 0.1.0
220
- # rubocop:disable Layout/LineLength
221
- def __to_hash__(transform_key: BASIC_SETTING_KEY_TRANSFORMER, transform_value: BASIC_SETTING_VALUE_TRANSFORMER)
224
+ # @version 0.25.0
225
+ def __to_hash__(
226
+ dot_notation: REPRESENT_HASH_IN_DOT_STYLE,
227
+ transform_key: BASIC_SETTING_KEY_TRANSFORMER,
228
+ transform_value: BASIC_SETTING_VALUE_TRANSFORMER
229
+ )
222
230
  unless transform_key.is_a?(Proc)
223
- ::Kernel.raise(Qonfig::IncorrectKeyTransformerError, 'Key transformer should be a type of proc')
231
+ ::Kernel.raise(
232
+ Qonfig::IncorrectKeyTransformerError,
233
+ 'Key transformer should be a type of proc'
234
+ )
224
235
  end
225
236
 
226
237
  unless transform_value.is_a?(Proc)
227
- ::Kernel.raise(Qonfig::IncorrectValueTransformerError, 'Value transformer should be a type of proc')
238
+ ::Kernel.raise(
239
+ Qonfig::IncorrectValueTransformerError,
240
+ 'Value transformer should be a type of proc'
241
+ )
228
242
  end
229
243
 
230
244
  __lock__.thread_safe_access do
231
- __build_hash_representation__(transform_key: transform_key, transform_value: transform_value)
245
+ if dot_notation
246
+ __build_dot_notated_hash_representation__(
247
+ transform_key: transform_key,
248
+ transform_value: transform_value
249
+ )
250
+ else
251
+ __build_basic_hash_representation__(
252
+ transform_key: transform_key,
253
+ transform_value: transform_value
254
+ )
255
+ end
232
256
  end
233
257
  end
234
- # rubocop:enable Layout/LineLength
235
258
  alias_method :__to_h__, :__to_hash__
236
259
 
237
260
  # @option all_variants [Boolean]
@@ -496,7 +519,7 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
496
519
  #
497
520
  # @api private
498
521
  # @since 0.21.0
499
- # rubocop:disable Naming/RescuedExceptionsVariableName
522
+ # rubocop:disable Naming/RescuedExceptionsVariableName, Style/SlicingWithRange
500
523
  def __assign_value__(key, value)
501
524
  key = __indifferently_accessable_option_key__(key)
502
525
  __set_value__(key, value)
@@ -515,7 +538,7 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
515
538
  raise(initial_error)
516
539
  end
517
540
  end
518
- # rubocop:enable Naming/RescuedExceptionsVariableName
541
+ # rubocop:enable Naming/RescuedExceptionsVariableName, Style/SlicingWithRange
519
542
 
520
543
  # @param key [String, Symbol]
521
544
  # @param value [Object]
@@ -558,7 +581,8 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
558
581
  #
559
582
  # @api private
560
583
  # @since 0.2.0
561
- def __deep_access__(*keys) # rubocop:disable Metrics/AbcSize
584
+ # rubocop:disable Metrics/AbcSize, Style/SlicingWithRange
585
+ def __deep_access__(*keys)
562
586
  ::Kernel.raise(Qonfig::ArgumentError, 'Key list can not be empty') if keys.empty?
563
587
 
564
588
  result = nil
@@ -588,6 +612,7 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
588
612
  result.__dig__(*rest_keys)
589
613
  end
590
614
  end
615
+ # rubocop:enable Metrics/AbcSize, Style/SlicingWithRange
591
616
 
592
617
  # @param keys [Array<Symbol, String>]
593
618
  # @return [Hash]
@@ -680,14 +705,15 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
680
705
  # @return [Hash]
681
706
  #
682
707
  # @api private
683
- # @since 0.2.0
684
- def __build_hash_representation__(options_part = __options__, transform_key:, transform_value:)
708
+ # @since 0.25.0
709
+ # rubocop:disable Layout/LineLength
710
+ def __build_basic_hash_representation__(options_part = __options__, transform_key:, transform_value:)
685
711
  options_part.each_with_object({}) do |(key, value), hash|
686
712
  final_key = transform_key.call(key)
687
713
 
688
714
  case
689
715
  when value.is_a?(Hash)
690
- hash[final_key] = __build_hash_representation__(
716
+ hash[final_key] = __build_basic_hash_representation__(
691
717
  value,
692
718
  transform_key: transform_key,
693
719
  transform_value: transform_value
@@ -703,6 +729,24 @@ class Qonfig::Settings # NOTE: Layout/ClassStructure is disabled only for CORE_M
703
729
  end
704
730
  end
705
731
  end
732
+ # rubocop:enable Layout/LineLength
733
+
734
+ # @option transform_key [Proc]
735
+ # @option transform_value [Proc]
736
+ # @return [Hash]
737
+ #
738
+ # @api private
739
+ # @since 0.25.0
740
+ def __build_dot_notated_hash_representation__(transform_key:, transform_value:)
741
+ {}.tap do |hash|
742
+ __deep_each_key_value_pair__ do |setting_key, setting_value|
743
+ final_key = transform_key.call(setting_key)
744
+ final_value = transform_value.call(setting_value)
745
+
746
+ hash[final_key] = final_value
747
+ end
748
+ end
749
+ end
706
750
 
707
751
  # @param key [Symbol, String]
708
752
  # @return [void]
@@ -147,12 +147,14 @@ class Qonfig::Settings::KeyMatcher
147
147
  #
148
148
  # @api private
149
149
  # @since 0.13.0
150
+ # rubocop:disable Style/SlicingWithRange
150
151
  def strip_regexp_string(regexp_string, left: false, right: false)
151
152
  pattern = regexp_string
152
153
  pattern = pattern[2..-1] if left && pattern[0..1] == MATCHER_SCOPE_SPLITTER
153
154
  pattern = pattern[0..-3] if right && pattern[-2..-1] == MATCHER_SCOPE_SPLITTER
154
155
  pattern
155
156
  end
157
+ # rubocop:enable Style/SlicingWithRange
156
158
 
157
159
  # @param scope_pattern [String]
158
160
  # @return [Regexp]
@@ -5,5 +5,5 @@ module Qonfig
5
5
  #
6
6
  # @api public
7
7
  # @since 0.1.0
8
- VERSION = '0.24.1'
8
+ VERSION = '0.25.0'
9
9
  end
@@ -6,7 +6,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
  require 'qonfig/version'
7
7
 
8
8
  Gem::Specification.new do |spec|
9
- spec.required_ruby_version = '>= 2.4.9'
9
+ spec.required_ruby_version = '>= 2.4.10'
10
10
 
11
11
  spec.name = 'qonfig'
12
12
  spec.version = Qonfig::VERSION
@@ -29,9 +29,9 @@ Gem::Specification.new do |spec|
29
29
  f.match(%r{^(test|spec|features)/})
30
30
  end
31
31
 
32
- spec.add_development_dependency 'simplecov', '~> 0.18'
32
+ spec.add_development_dependency 'simplecov', '~> 0.19'
33
33
  spec.add_development_dependency 'rspec', '~> 3.9'
34
- spec.add_development_dependency 'armitage-rubocop', '~> 0.79'
34
+ spec.add_development_dependency 'armitage-rubocop', '~> 0.89'
35
35
 
36
36
  spec.add_development_dependency 'bundler'
37
37
  spec.add_development_dependency 'rake', '>= 13'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: qonfig
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.1
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rustam Ibragimov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-03-09 00:00:00.000000000 Z
11
+ date: 2020-09-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: simplecov
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.18'
19
+ version: '0.19'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.18'
26
+ version: '0.19'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rspec
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0.79'
47
+ version: '0.89'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0.79'
54
+ version: '0.89'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: bundler
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -184,6 +184,12 @@ files:
184
184
  - lib/qonfig/plugins/toml/loaders/toml.rb
185
185
  - lib/qonfig/plugins/toml/tomlrb_fixes.rb
186
186
  - lib/qonfig/plugins/toml/uploaders/toml.rb
187
+ - lib/qonfig/plugins/vault.rb
188
+ - lib/qonfig/plugins/vault/commands/definition/expose_vault.rb
189
+ - lib/qonfig/plugins/vault/commands/definition/load_from_vault.rb
190
+ - lib/qonfig/plugins/vault/dsl.rb
191
+ - lib/qonfig/plugins/vault/errors.rb
192
+ - lib/qonfig/plugins/vault/loaders/vault.rb
187
193
  - lib/qonfig/settings.rb
188
194
  - lib/qonfig/settings/builder.rb
189
195
  - lib/qonfig/settings/callbacks.rb
@@ -225,7 +231,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
225
231
  requirements:
226
232
  - - ">="
227
233
  - !ruby/object:Gem::Version
228
- version: 2.4.9
234
+ version: 2.4.10
229
235
  required_rubygems_version: !ruby/object:Gem::Requirement
230
236
  requirements:
231
237
  - - ">="