qonfig 0.24.1 → 0.25.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 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
  - - ">="