anyway_config 2.8.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +48 -2
- data/lib/.rbnext/2.7/anyway/loaders/yaml.rb +2 -0
- data/lib/.rbnext/2.7/anyway/settings.rb +12 -0
- data/lib/.rbnext/3.0/anyway/config.rb +50 -2
- data/lib/.rbnext/3.0/anyway/loaders/base.rb +5 -1
- data/lib/.rbnext/3.0/anyway/loaders/ejson.rb +94 -0
- data/lib/.rbnext/3.0/anyway/loaders/yaml.rb +2 -0
- data/lib/.rbnext/3.0/anyway/rails/loaders/credentials.rb +64 -0
- data/lib/.rbnext/3.0/anyway/rails/loaders/secrets.rb +42 -0
- data/lib/.rbnext/3.1/anyway/config.rb +50 -2
- data/lib/.rbnext/3.1/anyway/loaders/base.rb +5 -1
- data/lib/.rbnext/3.2/anyway/config.rb +50 -2
- data/lib/.rbnext/3.2/anyway/loaders/base.rb +5 -1
- data/lib/.rbnext/3.4/anyway/config.rb +50 -2
- data/lib/anyway/config.rb +50 -2
- data/lib/anyway/loaders/base.rb +5 -1
- data/lib/anyway/loaders/ejson.rb +6 -1
- data/lib/anyway/loaders/yaml.rb +2 -0
- data/lib/anyway/rails/loaders/credentials.rb +2 -0
- data/lib/anyway/rails/loaders/secrets.rb +2 -0
- data/lib/anyway/rails.rb +1 -1
- data/lib/anyway/settings.rb +12 -0
- data/lib/anyway/version.rb +1 -1
- data/sig/anyway_config.rbs +12 -1
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 910bf1014d5b1b2a7952b2e6da131a341722998f3b891d096f4d5764006fac23
|
|
4
|
+
data.tar.gz: 6f9233771e022ccb6d3ea3d03ee2b15bee8e2e3d322aa3434cd5d08b8794cf14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 11f0d4f61ebabda431abba36837438c767405a0432e2f8f8ae1d2460b35e8e14438b9ed2acb6cb9d2d1b6889111e61cfecdf7abed2e2d4bc40eccba416c1d6f5
|
|
7
|
+
data.tar.gz: 3bf99f8766ed1a38ea3d9b6f114bf59c694c492ecd0f635067b97750750c2afea3e4d3c476e4d7bfa70c6e02fb478e36f273a4b32613712c79e10dc7c9fed400
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## master
|
|
4
4
|
|
|
5
|
+
## 2.9.0 (2026-10-20)
|
|
6
|
+
|
|
7
|
+
- Add a new `enforce_config_schema` to warn or raise on unknown attributes. ([@palkan][])
|
|
8
|
+
|
|
9
|
+
When the schema is enforced, the config attributes defined in the key-based sources (YAML, credentials, etc.) must correspond to `attr_config` names in the corresponding class. Providing an unknown attribute (e.g., a typo) would raise or print a warning depending on the parameter value:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
Anyway::Settings.enforce_config_schema = true # or :warn
|
|
13
|
+
|
|
14
|
+
# in Rails
|
|
15
|
+
config.anyway_config.enforce_config_schema = true
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- EJSON loader now ignores underscore-prefixed (unencrypted) keys. ([@palkan][])
|
|
19
|
+
|
|
5
20
|
## 2.8.1 (2026-07-22)
|
|
6
21
|
|
|
7
22
|
- Fix default loaders order to keep ENV the last one (=higher precedence). ([@palkan][])
|
data/README.md
CHANGED
|
@@ -22,8 +22,9 @@ For application developers, Anyway Config could be useful to:
|
|
|
22
22
|
**NOTE:** this readme shows documentation for 2.x version.
|
|
23
23
|
For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_config/tree/1-4-stable).
|
|
24
24
|
|
|
25
|
-
<
|
|
26
|
-
|
|
25
|
+
<br/>
|
|
26
|
+
|
|
27
|
+
<img src="https://cdn.evilmartians.com/badges/logo-no-label.svg" alt="Evil Martians logo" width="22" height="16" /> <b>Anyway Config</b> is built by <b><a href="https://evilmartians.com/">Evil Martians</a></b>, an American design and engineering consultancy for <b>developer tools, AI, and cybersecurity startups</b>.
|
|
27
28
|
|
|
28
29
|
## Links
|
|
29
30
|
|
|
@@ -37,6 +38,7 @@ For version 1.x see the [1-4-stable branch](https://github.com/palkan/anyway_con
|
|
|
37
38
|
- [Configuration classes](#configuration-classes)
|
|
38
39
|
- [Dynamic configuration](#dynamic-configuration)
|
|
39
40
|
- [Validation & Callbacks](#validation-and-callbacks)
|
|
41
|
+
- [Enforcing config schema](#enforcing-config-schema)
|
|
40
42
|
- [Using with Rails applications](#using-with-rails)
|
|
41
43
|
- [Data population](#data-population)
|
|
42
44
|
- [Organizing configs](#organizing-configs)
|
|
@@ -345,6 +347,45 @@ class MyConfig < Anyway::Config
|
|
|
345
347
|
end
|
|
346
348
|
```
|
|
347
349
|
|
|
350
|
+
### Enforcing config schema
|
|
351
|
+
|
|
352
|
+
By default, Anyway Config ignores keys that don't match any of the config attributes. Thus, a typo in a YAML file (e.g., `hots` instead of `host`) goes unnoticed. You can make Anyway Config check for unknown attributes via the `enforce_config_schema` setting:
|
|
353
|
+
|
|
354
|
+
```ruby
|
|
355
|
+
# Raise Anyway::Config::UnknownAttributesError
|
|
356
|
+
Anyway::Settings.enforce_config_schema = true
|
|
357
|
+
|
|
358
|
+
# Print a warning instead
|
|
359
|
+
Anyway::Settings.enforce_config_schema = :warn
|
|
360
|
+
|
|
361
|
+
# Or in Rails
|
|
362
|
+
config.anyway_config.enforce_config_schema = true
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The schema is only enforced for key-based (_schemaful_) sources: YAML files, Rails credentials and secrets, and EJSON. Environment-based sources (ENV, Doppler) are never checked, since env prefixes can easily collide with unrelated variables (e.g., `AWS_PROFILE` for the `aws` config).
|
|
366
|
+
|
|
367
|
+
Only top-level keys are checked; nested hashes are not validated.
|
|
368
|
+
|
|
369
|
+
#### Allowing unknown attributes for some configs
|
|
370
|
+
|
|
371
|
+
You can use the `rescue_unknown_attributes_with` configuration parameter to ignore specific attributes. It's called for every unknown attribute with the config object and the attribute name; return a truthy value to skip (allow) it:
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
Anyway::Settings.rescue_unknown_attributes_with = ->(config, key) do
|
|
375
|
+
config.config_name == "app"
|
|
376
|
+
end
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
This is particularly useful when you have multiple config classes associated with the same configurable entity (say, a service provider).
|
|
380
|
+
|
|
381
|
+
#### Unknown explicit values
|
|
382
|
+
|
|
383
|
+
Unknown attributes passed explicitly (`MyConfig.new(hots: "localhost")`) will raise disregards the `enforce_config_schema` setting in the future. Currently, they follow the `enforce_config_schema` configuration but we recommend to opt-in for the stricter future behaviour:
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
Anyway::Settings.future.use :reject_unknown_overrides
|
|
387
|
+
```
|
|
388
|
+
|
|
348
389
|
## Using with Rails
|
|
349
390
|
|
|
350
391
|
**NOTE:** version 2.x supports Rails >= 6.0; for older versions use version 1.x of the gem.
|
|
@@ -825,6 +866,8 @@ class MyConfig < Anyway::Config
|
|
|
825
866
|
end
|
|
826
867
|
```
|
|
827
868
|
|
|
869
|
+
Underscore-prefixed keys (which are not encrypted by EJSON, e.g., `_public_key`) are ignored.
|
|
870
|
+
|
|
828
871
|
**NOTE:** You can opt-out from EJSON loader by specifying the `ANYWAY_CONFIG_DISABLE_EJSON=true` env var (in case you have the `ejson` executable in your PATH, but don't want to use it with Anyway Config).
|
|
829
872
|
|
|
830
873
|
### Custom loaders
|
|
@@ -865,6 +908,9 @@ class ChamberConfigLoader < Base
|
|
|
865
908
|
warn "Couldn't decrypt Chamber settings: #{e.message}"
|
|
866
909
|
{}
|
|
867
910
|
end
|
|
911
|
+
|
|
912
|
+
# Report unknown keys if enforce_config_schema is enabled
|
|
913
|
+
def schemaful? = true
|
|
868
914
|
end
|
|
869
915
|
|
|
870
916
|
# Don't forget to register it
|
|
@@ -38,6 +38,7 @@ module Anyway
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
setting :unwrap_known_environments, true
|
|
41
|
+
setting :reject_unknown_overrides, true
|
|
41
42
|
|
|
42
43
|
private
|
|
43
44
|
|
|
@@ -55,6 +56,14 @@ module Anyway
|
|
|
55
56
|
# Suppress required validations for CI/CD pipelines
|
|
56
57
|
attr_accessor :suppress_required_validations
|
|
57
58
|
|
|
59
|
+
# Whether to check for unknown attributes in schemaful (key-based) sources:
|
|
60
|
+
# false — ignore unknown attributes, true — raise, :warn — print a warning
|
|
61
|
+
attr_accessor :enforce_config_schema
|
|
62
|
+
|
|
63
|
+
# A callable (config, key) -> bool to ignore specific unknown attributes
|
|
64
|
+
# (e.g., when the same source is shared by multiple configs)
|
|
65
|
+
attr_accessor :rescue_unknown_attributes_with
|
|
66
|
+
|
|
58
67
|
# A proc returning a path to YML config file given the config name
|
|
59
68
|
attr_reader :default_config_path
|
|
60
69
|
|
|
@@ -113,5 +122,8 @@ module Anyway
|
|
|
113
122
|
|
|
114
123
|
# By default, use ANYWAY_SUPPRESS_VALIDATIONS
|
|
115
124
|
self.suppress_required_validations = %w[1 t true y yes].include?(ENV["ANYWAY_SUPPRESS_VALIDATIONS"])
|
|
125
|
+
|
|
126
|
+
# Unknown attributes are ignored by default
|
|
127
|
+
self.enforce_config_schema = false
|
|
116
128
|
end
|
|
117
129
|
end
|
|
@@ -33,6 +33,7 @@ module Anyway # :nodoc:
|
|
|
33
33
|
deconstruct_keys
|
|
34
34
|
dig
|
|
35
35
|
dup
|
|
36
|
+
format_unknown_attribute
|
|
36
37
|
initialize
|
|
37
38
|
load
|
|
38
39
|
load_from_sources
|
|
@@ -52,6 +53,8 @@ module Anyway # :nodoc:
|
|
|
52
53
|
|
|
53
54
|
class ValidationError < Error; end
|
|
54
55
|
|
|
56
|
+
class UnknownAttributesError < ValidationError; end
|
|
57
|
+
|
|
55
58
|
include OptparseConfig
|
|
56
59
|
include DynamicConfig
|
|
57
60
|
|
|
@@ -374,6 +377,8 @@ module Anyway # :nodoc:
|
|
|
374
377
|
)
|
|
375
378
|
|
|
376
379
|
if overrides
|
|
380
|
+
reject_unknown_config_attributes!(overrides, source: :overrides, strict: Settings.future.reject_unknown_overrides == true)
|
|
381
|
+
|
|
377
382
|
Tracing.trace!(:load) { overrides }
|
|
378
383
|
|
|
379
384
|
Utils.deep_merge!(base_config, overrides)
|
|
@@ -400,10 +405,16 @@ module Anyway # :nodoc:
|
|
|
400
405
|
def load_from_sources(base_config, **opts)
|
|
401
406
|
source_filter = self.class.configuration_sources
|
|
402
407
|
|
|
403
|
-
Anyway.loaders.each do |(id,
|
|
408
|
+
Anyway.loaders.each do |(id, loader_or_proc)|
|
|
404
409
|
next if source_filter && !source_filter.include?(id)
|
|
405
|
-
|
|
410
|
+
|
|
411
|
+
loader = (loader_or_proc.is_a?(Class) && loader_or_proc <= Loaders::Base) ? loader_or_proc.new(**opts) : loader_or_proc
|
|
412
|
+
loader_data = loader.call(**opts)
|
|
413
|
+
reject_unknown_config_attributes!(loader_data, source: id) if loader.is_a?(Loaders::Base) && loader.schemaful?
|
|
414
|
+
|
|
415
|
+
Utils.deep_merge!(base_config, loader_data)
|
|
406
416
|
end
|
|
417
|
+
|
|
407
418
|
base_config
|
|
408
419
|
end
|
|
409
420
|
|
|
@@ -467,6 +478,43 @@ module Anyway # :nodoc:
|
|
|
467
478
|
end
|
|
468
479
|
end
|
|
469
480
|
|
|
481
|
+
def reject_unknown_config_attributes!(data, source:, strict: false)
|
|
482
|
+
return if data.nil? || data.empty?
|
|
483
|
+
|
|
484
|
+
mode = strict || Settings.enforce_config_schema
|
|
485
|
+
return unless mode
|
|
486
|
+
|
|
487
|
+
known = self.class.config_attributes.map(&:to_s)
|
|
488
|
+
unknown = data.keys.map(&:to_s) - known
|
|
489
|
+
|
|
490
|
+
unless strict
|
|
491
|
+
rescuer = Settings.rescue_unknown_attributes_with
|
|
492
|
+
unknown.reject! { |it| rescuer.call(self, it) } if rescuer
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
return if unknown.empty?
|
|
496
|
+
|
|
497
|
+
msg = "Unknown config attributes for `#{self.class.name || "<anonymous>"}(config_name: #{config_name})` " \
|
|
498
|
+
"provided by #{source}: #{unknown.map { |it| format_unknown_attribute(it, known) }.join(", ")}"
|
|
499
|
+
|
|
500
|
+
if strict
|
|
501
|
+
raise ArgumentError, msg
|
|
502
|
+
elsif mode == :warn
|
|
503
|
+
Kernel.warn msg
|
|
504
|
+
else
|
|
505
|
+
raise UnknownAttributesError, msg
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def format_unknown_attribute(key, known)
|
|
510
|
+
return key unless defined?(::DidYouMean::SpellChecker)
|
|
511
|
+
|
|
512
|
+
suggestions = ::DidYouMean::SpellChecker.new(dictionary: known).correct(key)
|
|
513
|
+
return key if suggestions.empty?
|
|
514
|
+
|
|
515
|
+
"#{key} (did you mean: #{suggestions.join(", ")}?)"
|
|
516
|
+
end
|
|
517
|
+
|
|
470
518
|
def write_config_attr(key, val)
|
|
471
519
|
key = key.to_sym
|
|
472
520
|
return unless self.class.config_attributes.include?(key)
|
|
@@ -11,11 +11,15 @@ module Anyway
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def initialize(local:)
|
|
14
|
+
def initialize(local: Anyway::Settings.use_local_files, **__kwrest__)
|
|
15
15
|
@local = local
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def use_local?() ; @local == true; end
|
|
19
|
+
|
|
20
|
+
# Whether this loader supports strict schema (so we can warn or raise on
|
|
21
|
+
# unknown attributes)
|
|
22
|
+
def schemaful?() ; false; end
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "anyway/ejson_parser"
|
|
4
|
+
|
|
5
|
+
module Anyway
|
|
6
|
+
module Loaders
|
|
7
|
+
class EJSON < Base
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :bin_path
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
self.bin_path = "ejson"
|
|
13
|
+
|
|
14
|
+
def call(name:, ejson_namespace: name, ejson_parser: Anyway::EJSONParser.new(EJSON.bin_path), **_options)
|
|
15
|
+
configs = []
|
|
16
|
+
|
|
17
|
+
rel_config_paths.each do |rel_config_path|
|
|
18
|
+
secrets_hash, rel_path =
|
|
19
|
+
extract_hash_from_rel_config_path(
|
|
20
|
+
ejson_parser: ejson_parser,
|
|
21
|
+
rel_config_path: rel_config_path
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
next unless secrets_hash
|
|
25
|
+
|
|
26
|
+
config_hash = if ejson_namespace
|
|
27
|
+
secrets_hash[ejson_namespace]
|
|
28
|
+
else
|
|
29
|
+
secrets_hash
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
next unless config_hash.is_a?(Hash)
|
|
33
|
+
|
|
34
|
+
# Underscore-prefixed keys are not encrypted (e.g., _public_key)
|
|
35
|
+
config_hash = config_hash.reject { |key, _| key.start_with?("_") }
|
|
36
|
+
|
|
37
|
+
configs <<
|
|
38
|
+
trace!(:ejson, path: rel_path) do
|
|
39
|
+
config_hash
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
return {} if configs.empty?
|
|
44
|
+
|
|
45
|
+
configs.inject do |result_config, next_config|
|
|
46
|
+
Utils.deep_merge!(result_config, next_config)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def schemaful?() ; true; end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def rel_config_paths
|
|
55
|
+
chain = [environmental_rel_config_path]
|
|
56
|
+
|
|
57
|
+
chain << "secrets.local.ejson" if use_local?
|
|
58
|
+
|
|
59
|
+
chain
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def environmental_rel_config_path
|
|
63
|
+
if Settings.current_environment
|
|
64
|
+
# if environment file is absent, then take data from the default one
|
|
65
|
+
[
|
|
66
|
+
"#{Settings.current_environment}/secrets.ejson",
|
|
67
|
+
default_rel_config_path
|
|
68
|
+
]
|
|
69
|
+
else
|
|
70
|
+
default_rel_config_path
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def default_rel_config_path
|
|
75
|
+
"secrets.ejson"
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def extract_hash_from_rel_config_path(ejson_parser:, rel_config_path:)
|
|
79
|
+
rel_config_path = [rel_config_path] unless rel_config_path.is_a?(Array)
|
|
80
|
+
|
|
81
|
+
rel_config_path.each do |rel_conf_path|
|
|
82
|
+
rel_path = "config/#{rel_conf_path}"
|
|
83
|
+
abs_path = "#{Settings.app_root}/#{rel_path}"
|
|
84
|
+
|
|
85
|
+
result = ejson_parser.call(abs_path)
|
|
86
|
+
|
|
87
|
+
return [result, rel_path] if result
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
nil
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anyway
|
|
4
|
+
module Rails
|
|
5
|
+
module Loaders
|
|
6
|
+
class Credentials < Anyway::Loaders::Base
|
|
7
|
+
LOCAL_CONTENT_PATH = "config/credentials/local.yml.enc"
|
|
8
|
+
|
|
9
|
+
def call(name:, **_options)
|
|
10
|
+
return {} unless ::Rails.application.respond_to?(:credentials)
|
|
11
|
+
|
|
12
|
+
# do not load from credentials if we're in the context
|
|
13
|
+
# of the `credentials:edit` command
|
|
14
|
+
return {} if defined?(::Rails::Command::CredentialsCommand)
|
|
15
|
+
|
|
16
|
+
# Create a new hash cause credentials are mutable!
|
|
17
|
+
config = {}
|
|
18
|
+
|
|
19
|
+
trace!(
|
|
20
|
+
:credentials,
|
|
21
|
+
store: credentials_path
|
|
22
|
+
) do
|
|
23
|
+
::Rails.application.credentials.config[name.to_sym]
|
|
24
|
+
end.then do |creds|
|
|
25
|
+
Utils.deep_merge!(config, creds) if creds
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
if use_local?
|
|
29
|
+
trace!(:credentials, store: LOCAL_CONTENT_PATH) do
|
|
30
|
+
local_credentials(name)
|
|
31
|
+
end.then { |creds| Utils.deep_merge!(config, creds) if creds }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
config
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def schemaful?() ; true; end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def local_credentials(name)
|
|
42
|
+
local_creds_path = ::Rails.root.join(LOCAL_CONTENT_PATH).to_s
|
|
43
|
+
|
|
44
|
+
return unless File.file?(local_creds_path)
|
|
45
|
+
|
|
46
|
+
creds = ::Rails.application.encrypted(
|
|
47
|
+
local_creds_path,
|
|
48
|
+
key_path: ::Rails.root.join("config/credentials/local.key")
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
creds.config[name.to_sym]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def credentials_path
|
|
55
|
+
if ::Rails.application.config.respond_to?(:credentials)
|
|
56
|
+
::Rails.root.join(::Rails.application.config.credentials.content_path).relative_path_from(::Rails.root).to_s
|
|
57
|
+
else
|
|
58
|
+
"config/credentials.yml.enc"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Anyway
|
|
4
|
+
module Rails
|
|
5
|
+
module Loaders
|
|
6
|
+
class Secrets < Anyway::Loaders::Base
|
|
7
|
+
def call(name:, **_options)
|
|
8
|
+
return {} unless ::Rails.application.respond_to?(:secrets)
|
|
9
|
+
|
|
10
|
+
# Create a new hash cause secrets are mutable!
|
|
11
|
+
config = {}
|
|
12
|
+
|
|
13
|
+
trace!(:secrets) do
|
|
14
|
+
secrets.public_send(name)
|
|
15
|
+
end.then do |secrets|
|
|
16
|
+
Utils.deep_merge!(config, secrets) if secrets
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
config
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def schemaful?() ; true; end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def secrets
|
|
27
|
+
@secrets ||= ::Rails.application.secrets.tap do |_|
|
|
28
|
+
# Reset secrets state if the app hasn't been initialized
|
|
29
|
+
# See https://github.com/palkan/anyway_config/issues/14
|
|
30
|
+
next if ::Rails.application.initialized?
|
|
31
|
+
|
|
32
|
+
# Address unable to load application: NameError: instance variable @secrets
|
|
33
|
+
# not defined
|
|
34
|
+
next unless ::Rails.application.instance_variable_defined?(:@secrets)
|
|
35
|
+
|
|
36
|
+
::Rails.application.remove_instance_variable(:@secrets)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -33,6 +33,7 @@ module Anyway # :nodoc:
|
|
|
33
33
|
deconstruct_keys
|
|
34
34
|
dig
|
|
35
35
|
dup
|
|
36
|
+
format_unknown_attribute
|
|
36
37
|
initialize
|
|
37
38
|
load
|
|
38
39
|
load_from_sources
|
|
@@ -52,6 +53,8 @@ module Anyway # :nodoc:
|
|
|
52
53
|
|
|
53
54
|
class ValidationError < Error; end
|
|
54
55
|
|
|
56
|
+
class UnknownAttributesError < ValidationError; end
|
|
57
|
+
|
|
55
58
|
include OptparseConfig
|
|
56
59
|
include DynamicConfig
|
|
57
60
|
|
|
@@ -374,6 +377,8 @@ module Anyway # :nodoc:
|
|
|
374
377
|
)
|
|
375
378
|
|
|
376
379
|
if overrides
|
|
380
|
+
reject_unknown_config_attributes!(overrides, source: :overrides, strict: Settings.future.reject_unknown_overrides == true)
|
|
381
|
+
|
|
377
382
|
Tracing.trace!(:load) { overrides }
|
|
378
383
|
|
|
379
384
|
Utils.deep_merge!(base_config, overrides)
|
|
@@ -400,10 +405,16 @@ module Anyway # :nodoc:
|
|
|
400
405
|
def load_from_sources(base_config, **opts)
|
|
401
406
|
source_filter = self.class.configuration_sources
|
|
402
407
|
|
|
403
|
-
Anyway.loaders.each do |(id,
|
|
408
|
+
Anyway.loaders.each do |(id, loader_or_proc)|
|
|
404
409
|
next if source_filter && !source_filter.include?(id)
|
|
405
|
-
|
|
410
|
+
|
|
411
|
+
loader = (loader_or_proc.is_a?(Class) && loader_or_proc <= Loaders::Base) ? loader_or_proc.new(**opts) : loader_or_proc
|
|
412
|
+
loader_data = loader.call(**opts)
|
|
413
|
+
reject_unknown_config_attributes!(loader_data, source: id) if loader.is_a?(Loaders::Base) && loader.schemaful?
|
|
414
|
+
|
|
415
|
+
Utils.deep_merge!(base_config, loader_data)
|
|
406
416
|
end
|
|
417
|
+
|
|
407
418
|
base_config
|
|
408
419
|
end
|
|
409
420
|
|
|
@@ -467,6 +478,43 @@ module Anyway # :nodoc:
|
|
|
467
478
|
end
|
|
468
479
|
end
|
|
469
480
|
|
|
481
|
+
def reject_unknown_config_attributes!(data, source:, strict: false)
|
|
482
|
+
return if data.nil? || data.empty?
|
|
483
|
+
|
|
484
|
+
mode = strict || Settings.enforce_config_schema
|
|
485
|
+
return unless mode
|
|
486
|
+
|
|
487
|
+
known = self.class.config_attributes.map(&:to_s)
|
|
488
|
+
unknown = data.keys.map(&:to_s) - known
|
|
489
|
+
|
|
490
|
+
unless strict
|
|
491
|
+
rescuer = Settings.rescue_unknown_attributes_with
|
|
492
|
+
unknown.reject! { |it| rescuer.call(self, it) } if rescuer
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
return if unknown.empty?
|
|
496
|
+
|
|
497
|
+
msg = "Unknown config attributes for `#{self.class.name || "<anonymous>"}(config_name: #{config_name})` " \
|
|
498
|
+
"provided by #{source}: #{unknown.map { |it| format_unknown_attribute(it, known) }.join(", ")}"
|
|
499
|
+
|
|
500
|
+
if strict
|
|
501
|
+
raise ArgumentError, msg
|
|
502
|
+
elsif mode == :warn
|
|
503
|
+
Kernel.warn msg
|
|
504
|
+
else
|
|
505
|
+
raise UnknownAttributesError, msg
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def format_unknown_attribute(key, known)
|
|
510
|
+
return key unless defined?(::DidYouMean::SpellChecker)
|
|
511
|
+
|
|
512
|
+
suggestions = ::DidYouMean::SpellChecker.new(dictionary: known).correct(key)
|
|
513
|
+
return key if suggestions.empty?
|
|
514
|
+
|
|
515
|
+
"#{key} (did you mean: #{suggestions.join(", ")}?)"
|
|
516
|
+
end
|
|
517
|
+
|
|
470
518
|
def write_config_attr(key, val)
|
|
471
519
|
key = key.to_sym
|
|
472
520
|
return unless self.class.config_attributes.include?(key)
|
|
@@ -11,11 +11,15 @@ module Anyway
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def initialize(local:)
|
|
14
|
+
def initialize(local: Anyway::Settings.use_local_files, **__kwrest__)
|
|
15
15
|
@local = local
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def use_local?() = @local == true
|
|
19
|
+
|
|
20
|
+
# Whether this loader supports strict schema (so we can warn or raise on
|
|
21
|
+
# unknown attributes)
|
|
22
|
+
def schemaful?() = false
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
|
@@ -33,6 +33,7 @@ module Anyway # :nodoc:
|
|
|
33
33
|
deconstruct_keys
|
|
34
34
|
dig
|
|
35
35
|
dup
|
|
36
|
+
format_unknown_attribute
|
|
36
37
|
initialize
|
|
37
38
|
load
|
|
38
39
|
load_from_sources
|
|
@@ -52,6 +53,8 @@ module Anyway # :nodoc:
|
|
|
52
53
|
|
|
53
54
|
class ValidationError < Error; end
|
|
54
55
|
|
|
56
|
+
class UnknownAttributesError < ValidationError; end
|
|
57
|
+
|
|
55
58
|
include OptparseConfig
|
|
56
59
|
include DynamicConfig
|
|
57
60
|
|
|
@@ -374,6 +377,8 @@ module Anyway # :nodoc:
|
|
|
374
377
|
)
|
|
375
378
|
|
|
376
379
|
if overrides
|
|
380
|
+
reject_unknown_config_attributes!(overrides, source: :overrides, strict: Settings.future.reject_unknown_overrides == true)
|
|
381
|
+
|
|
377
382
|
Tracing.trace!(:load) { overrides }
|
|
378
383
|
|
|
379
384
|
Utils.deep_merge!(base_config, overrides)
|
|
@@ -400,10 +405,16 @@ module Anyway # :nodoc:
|
|
|
400
405
|
def load_from_sources(base_config, **opts)
|
|
401
406
|
source_filter = self.class.configuration_sources
|
|
402
407
|
|
|
403
|
-
Anyway.loaders.each do |(id,
|
|
408
|
+
Anyway.loaders.each do |(id, loader_or_proc)|
|
|
404
409
|
next if source_filter && !source_filter.include?(id)
|
|
405
|
-
|
|
410
|
+
|
|
411
|
+
loader = (loader_or_proc.is_a?(Class) && loader_or_proc <= Loaders::Base) ? loader_or_proc.new(**opts) : loader_or_proc
|
|
412
|
+
loader_data = loader.call(**opts)
|
|
413
|
+
reject_unknown_config_attributes!(loader_data, source: id) if loader.is_a?(Loaders::Base) && loader.schemaful?
|
|
414
|
+
|
|
415
|
+
Utils.deep_merge!(base_config, loader_data)
|
|
406
416
|
end
|
|
417
|
+
|
|
407
418
|
base_config
|
|
408
419
|
end
|
|
409
420
|
|
|
@@ -467,6 +478,43 @@ module Anyway # :nodoc:
|
|
|
467
478
|
end
|
|
468
479
|
end
|
|
469
480
|
|
|
481
|
+
def reject_unknown_config_attributes!(data, source:, strict: false)
|
|
482
|
+
return if data.nil? || data.empty?
|
|
483
|
+
|
|
484
|
+
mode = strict || Settings.enforce_config_schema
|
|
485
|
+
return unless mode
|
|
486
|
+
|
|
487
|
+
known = self.class.config_attributes.map(&:to_s)
|
|
488
|
+
unknown = data.keys.map(&:to_s) - known
|
|
489
|
+
|
|
490
|
+
unless strict
|
|
491
|
+
rescuer = Settings.rescue_unknown_attributes_with
|
|
492
|
+
unknown.reject! { |it| rescuer.call(self, it) } if rescuer
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
return if unknown.empty?
|
|
496
|
+
|
|
497
|
+
msg = "Unknown config attributes for `#{self.class.name || "<anonymous>"}(config_name: #{config_name})` " \
|
|
498
|
+
"provided by #{source}: #{unknown.map { |it| format_unknown_attribute(it, known) }.join(", ")}"
|
|
499
|
+
|
|
500
|
+
if strict
|
|
501
|
+
raise ArgumentError, msg
|
|
502
|
+
elsif mode == :warn
|
|
503
|
+
Kernel.warn msg
|
|
504
|
+
else
|
|
505
|
+
raise UnknownAttributesError, msg
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def format_unknown_attribute(key, known)
|
|
510
|
+
return key unless defined?(::DidYouMean::SpellChecker)
|
|
511
|
+
|
|
512
|
+
suggestions = ::DidYouMean::SpellChecker.new(dictionary: known).correct(key)
|
|
513
|
+
return key if suggestions.empty?
|
|
514
|
+
|
|
515
|
+
"#{key} (did you mean: #{suggestions.join(", ")}?)"
|
|
516
|
+
end
|
|
517
|
+
|
|
470
518
|
def write_config_attr(key, val)
|
|
471
519
|
key = key.to_sym
|
|
472
520
|
return unless self.class.config_attributes.include?(key)
|
|
@@ -11,11 +11,15 @@ module Anyway
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def initialize(local:)
|
|
14
|
+
def initialize(local: Anyway::Settings.use_local_files, **__kwrest__)
|
|
15
15
|
@local = local
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def use_local?() = @local == true
|
|
19
|
+
|
|
20
|
+
# Whether this loader supports strict schema (so we can warn or raise on
|
|
21
|
+
# unknown attributes)
|
|
22
|
+
def schemaful?() = false
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
|
@@ -33,6 +33,7 @@ module Anyway # :nodoc:
|
|
|
33
33
|
deconstruct_keys
|
|
34
34
|
dig
|
|
35
35
|
dup
|
|
36
|
+
format_unknown_attribute
|
|
36
37
|
initialize
|
|
37
38
|
load
|
|
38
39
|
load_from_sources
|
|
@@ -52,6 +53,8 @@ module Anyway # :nodoc:
|
|
|
52
53
|
|
|
53
54
|
class ValidationError < Error; end
|
|
54
55
|
|
|
56
|
+
class UnknownAttributesError < ValidationError; end
|
|
57
|
+
|
|
55
58
|
include OptparseConfig
|
|
56
59
|
include DynamicConfig
|
|
57
60
|
|
|
@@ -374,6 +377,8 @@ module Anyway # :nodoc:
|
|
|
374
377
|
)
|
|
375
378
|
|
|
376
379
|
if overrides
|
|
380
|
+
reject_unknown_config_attributes!(overrides, source: :overrides, strict: Settings.future.reject_unknown_overrides == true)
|
|
381
|
+
|
|
377
382
|
Tracing.trace!(:load) { overrides }
|
|
378
383
|
|
|
379
384
|
Utils.deep_merge!(base_config, overrides)
|
|
@@ -400,10 +405,16 @@ module Anyway # :nodoc:
|
|
|
400
405
|
def load_from_sources(base_config, **opts)
|
|
401
406
|
source_filter = self.class.configuration_sources
|
|
402
407
|
|
|
403
|
-
Anyway.loaders.each do |(id,
|
|
408
|
+
Anyway.loaders.each do |(id, loader_or_proc)|
|
|
404
409
|
next if source_filter && !source_filter.include?(id)
|
|
405
|
-
|
|
410
|
+
|
|
411
|
+
loader = (loader_or_proc.is_a?(Class) && loader_or_proc <= Loaders::Base) ? loader_or_proc.new(**opts) : loader_or_proc
|
|
412
|
+
loader_data = loader.call(**opts)
|
|
413
|
+
reject_unknown_config_attributes!(loader_data, source: id) if loader.is_a?(Loaders::Base) && loader.schemaful?
|
|
414
|
+
|
|
415
|
+
Utils.deep_merge!(base_config, loader_data)
|
|
406
416
|
end
|
|
417
|
+
|
|
407
418
|
base_config
|
|
408
419
|
end
|
|
409
420
|
|
|
@@ -467,6 +478,43 @@ module Anyway # :nodoc:
|
|
|
467
478
|
end
|
|
468
479
|
end
|
|
469
480
|
|
|
481
|
+
def reject_unknown_config_attributes!(data, source:, strict: false)
|
|
482
|
+
return if data.nil? || data.empty?
|
|
483
|
+
|
|
484
|
+
mode = strict || Settings.enforce_config_schema
|
|
485
|
+
return unless mode
|
|
486
|
+
|
|
487
|
+
known = self.class.config_attributes.map(&:to_s)
|
|
488
|
+
unknown = data.keys.map(&:to_s) - known
|
|
489
|
+
|
|
490
|
+
unless strict
|
|
491
|
+
rescuer = Settings.rescue_unknown_attributes_with
|
|
492
|
+
unknown.reject! { |it| rescuer.call(self, it) } if rescuer
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
return if unknown.empty?
|
|
496
|
+
|
|
497
|
+
msg = "Unknown config attributes for `#{self.class.name || "<anonymous>"}(config_name: #{config_name})` " \
|
|
498
|
+
"provided by #{source}: #{unknown.map { |it| format_unknown_attribute(it, known) }.join(", ")}"
|
|
499
|
+
|
|
500
|
+
if strict
|
|
501
|
+
raise ArgumentError, msg
|
|
502
|
+
elsif mode == :warn
|
|
503
|
+
Kernel.warn msg
|
|
504
|
+
else
|
|
505
|
+
raise UnknownAttributesError, msg
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def format_unknown_attribute(key, known)
|
|
510
|
+
return key unless defined?(::DidYouMean::SpellChecker)
|
|
511
|
+
|
|
512
|
+
suggestions = ::DidYouMean::SpellChecker.new(dictionary: known).correct(key)
|
|
513
|
+
return key if suggestions.empty?
|
|
514
|
+
|
|
515
|
+
"#{key} (did you mean: #{suggestions.join(", ")}?)"
|
|
516
|
+
end
|
|
517
|
+
|
|
470
518
|
def write_config_attr(key, val)
|
|
471
519
|
key = key.to_sym
|
|
472
520
|
return unless self.class.config_attributes.include?(key)
|
data/lib/anyway/config.rb
CHANGED
|
@@ -33,6 +33,7 @@ module Anyway # :nodoc:
|
|
|
33
33
|
deconstruct_keys
|
|
34
34
|
dig
|
|
35
35
|
dup
|
|
36
|
+
format_unknown_attribute
|
|
36
37
|
initialize
|
|
37
38
|
load
|
|
38
39
|
load_from_sources
|
|
@@ -52,6 +53,8 @@ module Anyway # :nodoc:
|
|
|
52
53
|
|
|
53
54
|
class ValidationError < Error; end
|
|
54
55
|
|
|
56
|
+
class UnknownAttributesError < ValidationError; end
|
|
57
|
+
|
|
55
58
|
include OptparseConfig
|
|
56
59
|
include DynamicConfig
|
|
57
60
|
|
|
@@ -374,6 +377,8 @@ module Anyway # :nodoc:
|
|
|
374
377
|
)
|
|
375
378
|
|
|
376
379
|
if overrides
|
|
380
|
+
reject_unknown_config_attributes!(overrides, source: :overrides, strict: Settings.future.reject_unknown_overrides == true)
|
|
381
|
+
|
|
377
382
|
Tracing.trace!(:load) { overrides }
|
|
378
383
|
|
|
379
384
|
Utils.deep_merge!(base_config, overrides)
|
|
@@ -400,10 +405,16 @@ module Anyway # :nodoc:
|
|
|
400
405
|
def load_from_sources(base_config, **opts)
|
|
401
406
|
source_filter = self.class.configuration_sources
|
|
402
407
|
|
|
403
|
-
Anyway.loaders.each do |(id,
|
|
408
|
+
Anyway.loaders.each do |(id, loader_or_proc)|
|
|
404
409
|
next if source_filter && !source_filter.include?(id)
|
|
405
|
-
|
|
410
|
+
|
|
411
|
+
loader = (loader_or_proc.is_a?(Class) && loader_or_proc <= Loaders::Base) ? loader_or_proc.new(**opts) : loader_or_proc
|
|
412
|
+
loader_data = loader.call(**opts)
|
|
413
|
+
reject_unknown_config_attributes!(loader_data, source: id) if loader.is_a?(Loaders::Base) && loader.schemaful?
|
|
414
|
+
|
|
415
|
+
Utils.deep_merge!(base_config, loader_data)
|
|
406
416
|
end
|
|
417
|
+
|
|
407
418
|
base_config
|
|
408
419
|
end
|
|
409
420
|
|
|
@@ -467,6 +478,43 @@ module Anyway # :nodoc:
|
|
|
467
478
|
end
|
|
468
479
|
end
|
|
469
480
|
|
|
481
|
+
def reject_unknown_config_attributes!(data, source:, strict: false)
|
|
482
|
+
return if data.nil? || data.empty?
|
|
483
|
+
|
|
484
|
+
mode = strict || Settings.enforce_config_schema
|
|
485
|
+
return unless mode
|
|
486
|
+
|
|
487
|
+
known = self.class.config_attributes.map(&:to_s)
|
|
488
|
+
unknown = data.keys.map(&:to_s) - known
|
|
489
|
+
|
|
490
|
+
unless strict
|
|
491
|
+
rescuer = Settings.rescue_unknown_attributes_with
|
|
492
|
+
unknown.reject! { rescuer.call(self, it) } if rescuer
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
return if unknown.empty?
|
|
496
|
+
|
|
497
|
+
msg = "Unknown config attributes for `#{self.class.name || "<anonymous>"}(config_name: #{config_name})` " \
|
|
498
|
+
"provided by #{source}: #{unknown.map { format_unknown_attribute(it, known) }.join(", ")}"
|
|
499
|
+
|
|
500
|
+
if strict
|
|
501
|
+
raise ArgumentError, msg
|
|
502
|
+
elsif mode == :warn
|
|
503
|
+
Kernel.warn msg
|
|
504
|
+
else
|
|
505
|
+
raise UnknownAttributesError, msg
|
|
506
|
+
end
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def format_unknown_attribute(key, known)
|
|
510
|
+
return key unless defined?(::DidYouMean::SpellChecker)
|
|
511
|
+
|
|
512
|
+
suggestions = ::DidYouMean::SpellChecker.new(dictionary: known).correct(key)
|
|
513
|
+
return key if suggestions.empty?
|
|
514
|
+
|
|
515
|
+
"#{key} (did you mean: #{suggestions.join(", ")}?)"
|
|
516
|
+
end
|
|
517
|
+
|
|
470
518
|
def write_config_attr(key, val)
|
|
471
519
|
key = key.to_sym
|
|
472
520
|
return unless self.class.config_attributes.include?(key)
|
data/lib/anyway/loaders/base.rb
CHANGED
|
@@ -11,11 +11,15 @@ module Anyway
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def initialize(local:)
|
|
14
|
+
def initialize(local: Anyway::Settings.use_local_files, **)
|
|
15
15
|
@local = local
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def use_local?() = @local == true
|
|
19
|
+
|
|
20
|
+
# Whether this loader supports strict schema (so we can warn or raise on
|
|
21
|
+
# unknown attributes)
|
|
22
|
+
def schemaful?() = false
|
|
19
23
|
end
|
|
20
24
|
end
|
|
21
25
|
end
|
data/lib/anyway/loaders/ejson.rb
CHANGED
|
@@ -26,11 +26,14 @@ module Anyway
|
|
|
26
26
|
config_hash = if ejson_namespace
|
|
27
27
|
secrets_hash[ejson_namespace]
|
|
28
28
|
else
|
|
29
|
-
secrets_hash
|
|
29
|
+
secrets_hash
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
next unless config_hash.is_a?(Hash)
|
|
33
33
|
|
|
34
|
+
# Underscore-prefixed keys are not encrypted (e.g., _public_key)
|
|
35
|
+
config_hash = config_hash.reject { |key, _| key.start_with?("_") }
|
|
36
|
+
|
|
34
37
|
configs <<
|
|
35
38
|
trace!(:ejson, path: rel_path) do
|
|
36
39
|
config_hash
|
|
@@ -44,6 +47,8 @@ module Anyway
|
|
|
44
47
|
end
|
|
45
48
|
end
|
|
46
49
|
|
|
50
|
+
def schemaful?() = true
|
|
51
|
+
|
|
47
52
|
private
|
|
48
53
|
|
|
49
54
|
def rel_config_paths
|
data/lib/anyway/loaders/yaml.rb
CHANGED
data/lib/anyway/rails.rb
CHANGED
|
@@ -12,7 +12,7 @@ require "anyway/rails/loaders"
|
|
|
12
12
|
# Configure Rails loaders
|
|
13
13
|
Anyway.loaders.override :yml, Anyway::Rails::Loaders::YAML
|
|
14
14
|
|
|
15
|
-
if Rails
|
|
15
|
+
if Gem::Version.new(Rails.version) >= Gem::Version.new("7.1")
|
|
16
16
|
Anyway.loaders.insert_after :yml, :credentials, Anyway::Rails::Loaders::Credentials
|
|
17
17
|
else
|
|
18
18
|
Anyway.loaders.insert_after :yml, :secrets, Anyway::Rails::Loaders::Secrets
|
data/lib/anyway/settings.rb
CHANGED
|
@@ -38,6 +38,7 @@ module Anyway
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
setting :unwrap_known_environments, true
|
|
41
|
+
setting :reject_unknown_overrides, true
|
|
41
42
|
|
|
42
43
|
private
|
|
43
44
|
|
|
@@ -55,6 +56,14 @@ module Anyway
|
|
|
55
56
|
# Suppress required validations for CI/CD pipelines
|
|
56
57
|
attr_accessor :suppress_required_validations
|
|
57
58
|
|
|
59
|
+
# Whether to check for unknown attributes in schemaful (key-based) sources:
|
|
60
|
+
# false — ignore unknown attributes, true — raise, :warn — print a warning
|
|
61
|
+
attr_accessor :enforce_config_schema
|
|
62
|
+
|
|
63
|
+
# A callable (config, key) -> bool to ignore specific unknown attributes
|
|
64
|
+
# (e.g., when the same source is shared by multiple configs)
|
|
65
|
+
attr_accessor :rescue_unknown_attributes_with
|
|
66
|
+
|
|
58
67
|
# A proc returning a path to YML config file given the config name
|
|
59
68
|
attr_reader :default_config_path
|
|
60
69
|
|
|
@@ -113,5 +122,8 @@ module Anyway
|
|
|
113
122
|
|
|
114
123
|
# By default, use ANYWAY_SUPPRESS_VALIDATIONS
|
|
115
124
|
self.suppress_required_validations = %w[1 t true y yes].include?(ENV["ANYWAY_SUPPRESS_VALIDATIONS"])
|
|
125
|
+
|
|
126
|
+
# Unknown attributes are ignored by default
|
|
127
|
+
self.enforce_config_schema = false
|
|
116
128
|
end
|
|
117
129
|
end
|
data/lib/anyway/version.rb
CHANGED
data/sig/anyway_config.rbs
CHANGED
|
@@ -9,6 +9,10 @@ module Anyway
|
|
|
9
9
|
def self.default_environmental_key: -> String?
|
|
10
10
|
def self.suppress_required_validations: -> bool
|
|
11
11
|
def self.known_environments: -> Array[String]?
|
|
12
|
+
def self.enforce_config_schema: -> (bool | :warn)
|
|
13
|
+
def self.enforce_config_schema=: (bool | :warn) -> void
|
|
14
|
+
def self.rescue_unknown_attributes_with: -> (^(Config, String) -> boolish)?
|
|
15
|
+
def self.rescue_unknown_attributes_with=: ((^(Config, String) -> boolish)?) -> void
|
|
12
16
|
|
|
13
17
|
class Future
|
|
14
18
|
def self.setting: (untyped name, untyped default_value) -> untyped
|
|
@@ -107,6 +111,8 @@ module Anyway
|
|
|
107
111
|
private
|
|
108
112
|
attr_reader values: Hash[untyped, untyped]
|
|
109
113
|
def raise_validation_error: (String msg) -> void
|
|
114
|
+
def reject_unknown_config_attributes!: (Hash[untyped, untyped]? data, source: Symbol, ?strict: bool) -> void
|
|
115
|
+
def format_unknown_attribute: (String key, Array[String] known) -> String
|
|
110
116
|
def flatten_hash: (Hash[untyped, untyped], String, Hash[String, String]) -> Hash[String, String]
|
|
111
117
|
|
|
112
118
|
class Error < StandardError
|
|
@@ -114,6 +120,9 @@ module Anyway
|
|
|
114
120
|
|
|
115
121
|
class ValidationError < Error
|
|
116
122
|
end
|
|
123
|
+
|
|
124
|
+
class UnknownAttributesError < ValidationError
|
|
125
|
+
end
|
|
117
126
|
end
|
|
118
127
|
|
|
119
128
|
class Env
|
|
@@ -127,8 +136,10 @@ module Anyway
|
|
|
127
136
|
include Tracing
|
|
128
137
|
|
|
129
138
|
def self.call: (?local: bool, **untyped) -> untyped
|
|
130
|
-
def initialize: (local: bool) -> void
|
|
139
|
+
def initialize: (?local: bool, **untyped) -> void
|
|
140
|
+
def call: (name: String, env_prefix: String, config_path: String | Pathname, **untyped) -> Hash[untyped, untyped]
|
|
131
141
|
def use_local?: -> bool
|
|
142
|
+
def schemaful?: -> bool
|
|
132
143
|
end
|
|
133
144
|
|
|
134
145
|
interface _Loader
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: anyway_config
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.9.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vladimir Dementyev
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: ruby-next-core
|
|
@@ -130,7 +130,10 @@ files:
|
|
|
130
130
|
- lib/.rbnext/3.0/anyway/config.rb
|
|
131
131
|
- lib/.rbnext/3.0/anyway/loaders.rb
|
|
132
132
|
- lib/.rbnext/3.0/anyway/loaders/base.rb
|
|
133
|
+
- lib/.rbnext/3.0/anyway/loaders/ejson.rb
|
|
133
134
|
- lib/.rbnext/3.0/anyway/loaders/yaml.rb
|
|
135
|
+
- lib/.rbnext/3.0/anyway/rails/loaders/credentials.rb
|
|
136
|
+
- lib/.rbnext/3.0/anyway/rails/loaders/secrets.rb
|
|
134
137
|
- lib/.rbnext/3.0/anyway/tracing.rb
|
|
135
138
|
- lib/.rbnext/3.1/anyway/config.rb
|
|
136
139
|
- lib/.rbnext/3.1/anyway/dynamic_config.rb
|