anyway_config 2.3.1 → 2.4.1
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 +55 -0
- data/README.md +76 -2
- data/lib/.rbnext/2.6/anyway/ejson_parser.rb +40 -0
- data/lib/.rbnext/2.7/anyway/config.rb +39 -30
- data/lib/.rbnext/2.7/anyway/settings.rb +14 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +8 -2
- data/lib/.rbnext/2.7/anyway/type_casting.rb +14 -1
- data/lib/.rbnext/3.0/anyway/config.rb +39 -30
- data/lib/.rbnext/3.0/anyway/loaders.rb +2 -0
- data/lib/.rbnext/3.0/anyway/tracing.rb +8 -2
- data/lib/.rbnext/3.1/anyway/config.rb +39 -30
- data/lib/.rbnext/3.1/anyway/env.rb +22 -5
- data/lib/.rbnext/3.1/anyway/tracing.rb +8 -2
- data/lib/anyway/config.rb +39 -30
- data/lib/anyway/ejson_parser.rb +40 -0
- data/lib/anyway/env.rb +22 -5
- data/lib/anyway/ext/flatten_names.rb +37 -0
- data/lib/anyway/ext/hash.rb +8 -1
- data/lib/anyway/ext/string_constantize.rb +24 -0
- data/lib/anyway/loaders/doppler.rb +63 -0
- data/lib/anyway/loaders/ejson.rb +89 -0
- data/lib/anyway/loaders.rb +2 -0
- data/lib/anyway/rails/settings.rb +22 -14
- data/lib/anyway/settings.rb +14 -0
- data/lib/anyway/tracing.rb +8 -2
- data/lib/anyway/type_casting.rb +11 -1
- data/lib/anyway/utils/which.rb +18 -0
- data/lib/anyway/version.rb +1 -1
- data/lib/anyway_config.rb +7 -0
- data/sig/anyway_config.rbs +7 -2
- metadata +37 -15
@@ -8,6 +8,7 @@ module Anyway # :nodoc:
|
|
8
8
|
using Anyway::Ext::DeepDup
|
9
9
|
using Anyway::Ext::DeepFreeze
|
10
10
|
using Anyway::Ext::Hash
|
11
|
+
using Anyway::Ext::FlattenNames
|
11
12
|
|
12
13
|
using(Module.new do
|
13
14
|
refine Object do
|
@@ -26,6 +27,7 @@ module Anyway # :nodoc:
|
|
26
27
|
RESERVED_NAMES = %i[
|
27
28
|
config_name
|
28
29
|
env_prefix
|
30
|
+
as_env
|
29
31
|
values
|
30
32
|
class
|
31
33
|
clear
|
@@ -47,8 +49,6 @@ module Anyway # :nodoc:
|
|
47
49
|
__type_caster__
|
48
50
|
].freeze
|
49
51
|
|
50
|
-
ENV_OPTION_EXCLUDE_KEY = :except
|
51
|
-
|
52
52
|
class Error < StandardError; end
|
53
53
|
|
54
54
|
class ValidationError < Error; end
|
@@ -128,34 +128,14 @@ module Anyway # :nodoc:
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
|
-
def required(*names, env: nil)
|
132
|
-
unknown_names = names - config_attributes
|
131
|
+
def required(*names, env: nil, **nested)
|
132
|
+
unknown_names = names + nested.keys - config_attributes
|
133
133
|
raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
|
134
134
|
|
135
|
-
|
136
|
-
required_attributes.push(*names)
|
137
|
-
end
|
138
|
-
|
139
|
-
def filter_by_env(names, env)
|
140
|
-
return names if env.nil? || env.to_s == current_env
|
141
|
-
|
142
|
-
filtered_names = if env.is_a?(Hash)
|
143
|
-
names_with_exclude_env_option(names, env)
|
144
|
-
elsif env.is_a?(Array)
|
145
|
-
names if env.flat_map(&:to_s).include?(current_env)
|
146
|
-
end
|
147
|
-
|
148
|
-
filtered_names || []
|
149
|
-
end
|
150
|
-
|
151
|
-
def current_env
|
152
|
-
Settings.current_environment.to_s
|
153
|
-
end
|
135
|
+
return unless Settings.matching_env?(env)
|
154
136
|
|
155
|
-
|
156
|
-
|
157
|
-
excluded_envs = [envs].flat_map(&:to_s)
|
158
|
-
names if excluded_envs.none?(current_env)
|
137
|
+
required_attributes.push(*names)
|
138
|
+
required_attributes.push(*nested.flatten_names)
|
159
139
|
end
|
160
140
|
|
161
141
|
def required_attributes
|
@@ -219,10 +199,28 @@ module Anyway # :nodoc:
|
|
219
199
|
end
|
220
200
|
end
|
221
201
|
|
202
|
+
def loader_options(val = nil)
|
203
|
+
return (@loader_options = val) unless val.nil?
|
204
|
+
|
205
|
+
return @loader_options if instance_variable_defined?(:@loader_options)
|
206
|
+
|
207
|
+
@loader_options = if superclass < Anyway::Config
|
208
|
+
superclass.loader_options
|
209
|
+
else
|
210
|
+
{}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
222
214
|
def new_empty_config() = {}
|
223
215
|
|
224
216
|
def coerce_types(mapping)
|
225
217
|
Utils.deep_merge!(coercion_mapping, mapping)
|
218
|
+
|
219
|
+
mapping.each do |key, val|
|
220
|
+
next unless val == :boolean || (val.is_a?(::Hash) && val[:type] == :boolean)
|
221
|
+
|
222
|
+
alias_method :"#{key}?", :"#{key}"
|
223
|
+
end
|
226
224
|
end
|
227
225
|
|
228
226
|
def coercion_mapping
|
@@ -268,7 +266,7 @@ module Anyway # :nodoc:
|
|
268
266
|
names.each do |name|
|
269
267
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
270
268
|
def #{name}=(val)
|
271
|
-
__trace__&.record_value(val,
|
269
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
272
270
|
values[:#{name}] = val
|
273
271
|
end
|
274
272
|
|
@@ -357,7 +355,13 @@ module Anyway # :nodoc:
|
|
357
355
|
|
358
356
|
config_path = resolve_config_path(config_name, env_prefix)
|
359
357
|
|
360
|
-
load_from_sources(
|
358
|
+
load_from_sources(
|
359
|
+
base_config,
|
360
|
+
name: config_name,
|
361
|
+
env_prefix: env_prefix,
|
362
|
+
config_path: config_path,
|
363
|
+
**self.class.loader_options
|
364
|
+
)
|
361
365
|
|
362
366
|
if overrides
|
363
367
|
Tracing.trace!(:load) { overrides }
|
@@ -430,13 +434,18 @@ module Anyway # :nodoc:
|
|
430
434
|
end
|
431
435
|
end
|
432
436
|
|
437
|
+
def as_env
|
438
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
439
|
+
end
|
440
|
+
|
433
441
|
private
|
434
442
|
|
435
443
|
attr_reader :values, :__trace__
|
436
444
|
|
437
445
|
def validate_required_attributes!
|
438
446
|
self.class.required_attributes.select do |name|
|
439
|
-
values
|
447
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
448
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
440
449
|
end.then do |missing|
|
441
450
|
next if missing.empty?
|
442
451
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
@@ -8,14 +8,31 @@ module Anyway
|
|
8
8
|
using Anyway::Ext::DeepDup
|
9
9
|
using Anyway::Ext::Hash
|
10
10
|
|
11
|
+
class << self
|
12
|
+
def from_hash(hash, prefix: nil, memo: {})
|
13
|
+
hash.each do |key, value|
|
14
|
+
prefix_with_key = (prefix && !prefix.empty?) ? "#{prefix}_#{key.to_s.upcase}" : key.to_s.upcase
|
15
|
+
|
16
|
+
if value.is_a?(Hash)
|
17
|
+
from_hash(value, prefix: "#{prefix_with_key}_", memo: memo)
|
18
|
+
else
|
19
|
+
memo[prefix_with_key] = value.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
memo
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
11
27
|
include Tracing
|
12
28
|
|
13
|
-
attr_reader :data, :traces, :type_cast
|
29
|
+
attr_reader :data, :traces, :type_cast, :env_container
|
14
30
|
|
15
|
-
def initialize(type_cast: AutoCast)
|
31
|
+
def initialize(type_cast: AutoCast, env_container: ENV)
|
16
32
|
@type_cast = type_cast
|
17
33
|
@data = {}
|
18
34
|
@traces = {}
|
35
|
+
@env_container = env_container
|
19
36
|
end
|
20
37
|
|
21
38
|
def clear
|
@@ -42,11 +59,11 @@ module Anyway
|
|
42
59
|
private
|
43
60
|
|
44
61
|
def parse_env(prefix)
|
45
|
-
match_prefix = "#{prefix}_"
|
46
|
-
|
62
|
+
match_prefix = prefix.empty? ? prefix : "#{prefix}_"
|
63
|
+
env_container.each_pair.with_object({}) do |(key, val), data|
|
47
64
|
next unless key.start_with?(match_prefix)
|
48
65
|
|
49
|
-
path = key.sub(/^#{
|
66
|
+
path = key.sub(/^#{match_prefix}/, "").downcase
|
50
67
|
|
51
68
|
paths = path.split("__")
|
52
69
|
trace!(:env, *paths, key: key) { data.bury(type_cast.call(val), *paths) }
|
@@ -19,7 +19,7 @@ module Anyway
|
|
19
19
|
def initialize(type = :trace, value = UNDEF, **source)
|
20
20
|
@type = type
|
21
21
|
@source = source
|
22
|
-
@value = value == UNDEF ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
22
|
+
@value = (value == UNDEF) ? Hash.new { |h, k| h[k] = Trace.new(:trace) } : value
|
23
23
|
end
|
24
24
|
|
25
25
|
def dig(...)
|
@@ -35,7 +35,7 @@ module Anyway
|
|
35
35
|
end
|
36
36
|
|
37
37
|
target_trace = path.empty? ? self : value.dig(*path)
|
38
|
-
target_trace.
|
38
|
+
target_trace.record_key(key.to_s, trace)
|
39
39
|
|
40
40
|
val
|
41
41
|
end
|
@@ -54,6 +54,12 @@ module Anyway
|
|
54
54
|
hash
|
55
55
|
end
|
56
56
|
|
57
|
+
def record_key(key, key_trace)
|
58
|
+
@value = Hash.new { |h, k| h[k] = Trace.new(:trace) } unless value.is_a?(::Hash)
|
59
|
+
|
60
|
+
value[key] = key_trace
|
61
|
+
end
|
62
|
+
|
57
63
|
def merge!(another_trace)
|
58
64
|
raise ArgumentError, "You can only merge into a :trace type, and this is :#{type}" unless trace?
|
59
65
|
raise ArgumentError, "You can only merge a :trace type, but trying :#{type}" unless another_trace.trace?
|
data/lib/anyway/config.rb
CHANGED
@@ -8,6 +8,7 @@ module Anyway # :nodoc:
|
|
8
8
|
using Anyway::Ext::DeepDup
|
9
9
|
using Anyway::Ext::DeepFreeze
|
10
10
|
using Anyway::Ext::Hash
|
11
|
+
using Anyway::Ext::FlattenNames
|
11
12
|
|
12
13
|
using(Module.new do
|
13
14
|
refine Object do
|
@@ -26,6 +27,7 @@ module Anyway # :nodoc:
|
|
26
27
|
RESERVED_NAMES = %i[
|
27
28
|
config_name
|
28
29
|
env_prefix
|
30
|
+
as_env
|
29
31
|
values
|
30
32
|
class
|
31
33
|
clear
|
@@ -47,8 +49,6 @@ module Anyway # :nodoc:
|
|
47
49
|
__type_caster__
|
48
50
|
].freeze
|
49
51
|
|
50
|
-
ENV_OPTION_EXCLUDE_KEY = :except
|
51
|
-
|
52
52
|
class Error < StandardError; end
|
53
53
|
|
54
54
|
class ValidationError < Error; end
|
@@ -128,34 +128,14 @@ module Anyway # :nodoc:
|
|
128
128
|
end
|
129
129
|
end
|
130
130
|
|
131
|
-
def required(*names, env: nil)
|
132
|
-
unknown_names = names - config_attributes
|
131
|
+
def required(*names, env: nil, **nested)
|
132
|
+
unknown_names = names + nested.keys - config_attributes
|
133
133
|
raise ArgumentError, "Unknown config param: #{unknown_names.join(",")}" if unknown_names.any?
|
134
134
|
|
135
|
-
|
136
|
-
required_attributes.push(*names)
|
137
|
-
end
|
138
|
-
|
139
|
-
def filter_by_env(names, env)
|
140
|
-
return names if env.nil? || env.to_s == current_env
|
141
|
-
|
142
|
-
filtered_names = if env.is_a?(Hash)
|
143
|
-
names_with_exclude_env_option(names, env)
|
144
|
-
elsif env.is_a?(Array)
|
145
|
-
names if env.flat_map(&:to_s).include?(current_env)
|
146
|
-
end
|
147
|
-
|
148
|
-
filtered_names || []
|
149
|
-
end
|
150
|
-
|
151
|
-
def current_env
|
152
|
-
Settings.current_environment.to_s
|
153
|
-
end
|
135
|
+
return unless Settings.matching_env?(env)
|
154
136
|
|
155
|
-
|
156
|
-
|
157
|
-
excluded_envs = [envs].flat_map(&:to_s)
|
158
|
-
names if excluded_envs.none?(current_env)
|
137
|
+
required_attributes.push(*names)
|
138
|
+
required_attributes.push(*nested.flatten_names)
|
159
139
|
end
|
160
140
|
|
161
141
|
def required_attributes
|
@@ -219,10 +199,28 @@ module Anyway # :nodoc:
|
|
219
199
|
end
|
220
200
|
end
|
221
201
|
|
202
|
+
def loader_options(val = nil)
|
203
|
+
return (@loader_options = val) unless val.nil?
|
204
|
+
|
205
|
+
return @loader_options if instance_variable_defined?(:@loader_options)
|
206
|
+
|
207
|
+
@loader_options = if superclass < Anyway::Config
|
208
|
+
superclass.loader_options
|
209
|
+
else
|
210
|
+
{}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
222
214
|
def new_empty_config() = {}
|
223
215
|
|
224
216
|
def coerce_types(mapping)
|
225
217
|
Utils.deep_merge!(coercion_mapping, mapping)
|
218
|
+
|
219
|
+
mapping.each do |key, val|
|
220
|
+
next unless val == :boolean || (val.is_a?(::Hash) && val[:type] == :boolean)
|
221
|
+
|
222
|
+
alias_method :"#{key}?", :"#{key}"
|
223
|
+
end
|
226
224
|
end
|
227
225
|
|
228
226
|
def coercion_mapping
|
@@ -268,7 +266,7 @@ module Anyway # :nodoc:
|
|
268
266
|
names.each do |name|
|
269
267
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
270
268
|
def #{name}=(val)
|
271
|
-
__trace__&.record_value(val,
|
269
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
272
270
|
values[:#{name}] = val
|
273
271
|
end
|
274
272
|
|
@@ -357,7 +355,13 @@ module Anyway # :nodoc:
|
|
357
355
|
|
358
356
|
config_path = resolve_config_path(config_name, env_prefix)
|
359
357
|
|
360
|
-
load_from_sources(
|
358
|
+
load_from_sources(
|
359
|
+
base_config,
|
360
|
+
name: config_name,
|
361
|
+
env_prefix:,
|
362
|
+
config_path:,
|
363
|
+
**self.class.loader_options
|
364
|
+
)
|
361
365
|
|
362
366
|
if overrides
|
363
367
|
Tracing.trace!(:load) { overrides }
|
@@ -430,13 +434,18 @@ module Anyway # :nodoc:
|
|
430
434
|
end
|
431
435
|
end
|
432
436
|
|
437
|
+
def as_env
|
438
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
439
|
+
end
|
440
|
+
|
433
441
|
private
|
434
442
|
|
435
443
|
attr_reader :values, :__trace__
|
436
444
|
|
437
445
|
def validate_required_attributes!
|
438
446
|
self.class.required_attributes.select do |name|
|
439
|
-
values
|
447
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
448
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
440
449
|
end.then do |missing|
|
441
450
|
next if missing.empty?
|
442
451
|
raise_validation_error "The following config parameters for `#{self.class.name}(config_name: #{self.class.config_name})` are missing or empty: #{missing.join(", ")}"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "anyway/ext/hash"
|
5
|
+
|
6
|
+
using Anyway::Ext::Hash
|
7
|
+
|
8
|
+
module Anyway
|
9
|
+
class EJSONParser
|
10
|
+
attr_reader :bin_path
|
11
|
+
|
12
|
+
def initialize(bin_path = "ejson")
|
13
|
+
@bin_path = bin_path
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(file_path)
|
17
|
+
return unless File.exist?(file_path)
|
18
|
+
|
19
|
+
raw_content = nil
|
20
|
+
|
21
|
+
stdout, stderr, status = Open3.capture3("#{bin_path} decrypt #{file_path}")
|
22
|
+
|
23
|
+
if status.success?
|
24
|
+
raw_content = JSON.parse(stdout.chomp)
|
25
|
+
else
|
26
|
+
Kernel.warn "Failed to decrypt #{file_path}: #{stderr}"
|
27
|
+
end
|
28
|
+
|
29
|
+
return unless raw_content
|
30
|
+
|
31
|
+
raw_content.deep_transform_keys do |key|
|
32
|
+
if key[0] == "_"
|
33
|
+
key[1..]
|
34
|
+
else
|
35
|
+
key
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/anyway/env.rb
CHANGED
@@ -8,14 +8,31 @@ module Anyway
|
|
8
8
|
using Anyway::Ext::DeepDup
|
9
9
|
using Anyway::Ext::Hash
|
10
10
|
|
11
|
+
class << self
|
12
|
+
def from_hash(hash, prefix: nil, memo: {})
|
13
|
+
hash.each do |key, value|
|
14
|
+
prefix_with_key = (prefix && !prefix.empty?) ? "#{prefix}_#{key.to_s.upcase}" : key.to_s.upcase
|
15
|
+
|
16
|
+
if value.is_a?(Hash)
|
17
|
+
from_hash(value, prefix: "#{prefix_with_key}_", memo:)
|
18
|
+
else
|
19
|
+
memo[prefix_with_key] = value.to_s
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
memo
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
11
27
|
include Tracing
|
12
28
|
|
13
|
-
attr_reader :data, :traces, :type_cast
|
29
|
+
attr_reader :data, :traces, :type_cast, :env_container
|
14
30
|
|
15
|
-
def initialize(type_cast: AutoCast)
|
31
|
+
def initialize(type_cast: AutoCast, env_container: ENV)
|
16
32
|
@type_cast = type_cast
|
17
33
|
@data = {}
|
18
34
|
@traces = {}
|
35
|
+
@env_container = env_container
|
19
36
|
end
|
20
37
|
|
21
38
|
def clear
|
@@ -42,11 +59,11 @@ module Anyway
|
|
42
59
|
private
|
43
60
|
|
44
61
|
def parse_env(prefix)
|
45
|
-
match_prefix = "#{prefix}_"
|
46
|
-
|
62
|
+
match_prefix = prefix.empty? ? prefix : "#{prefix}_"
|
63
|
+
env_container.each_pair.with_object({}) do |(key, val), data|
|
47
64
|
next unless key.start_with?(match_prefix)
|
48
65
|
|
49
|
-
path = key.sub(/^#{
|
66
|
+
path = key.sub(/^#{match_prefix}/, "").downcase
|
50
67
|
|
51
68
|
paths = path.split("__")
|
52
69
|
trace!(:env, *paths, key:) { data.bury(type_cast.call(val), *paths) }
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
module Ext
|
5
|
+
# Convert Hash with mixed array and hash values to an
|
6
|
+
# array of paths.
|
7
|
+
module FlattenNames
|
8
|
+
refine ::Array do
|
9
|
+
def flatten_names(prefix, buf)
|
10
|
+
if empty?
|
11
|
+
buf << :"#{prefix}"
|
12
|
+
return buf
|
13
|
+
end
|
14
|
+
|
15
|
+
each_with_object(buf) do |name, acc|
|
16
|
+
if name.is_a?(::Symbol)
|
17
|
+
acc << :"#{prefix}.#{name}"
|
18
|
+
else
|
19
|
+
name.flatten_names(prefix, acc)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
refine ::Hash do
|
26
|
+
def flatten_names(prefix = nil, buf = [])
|
27
|
+
each_with_object(buf) do |(k, v), acc|
|
28
|
+
parent = prefix ? "#{prefix}.#{k}" : k
|
29
|
+
v.flatten_names(parent, acc)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
using self
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/anyway/ext/hash.rb
CHANGED
@@ -21,11 +21,18 @@ module Anyway
|
|
21
21
|
|
22
22
|
last_key = path.pop
|
23
23
|
hash = path.reduce(self) do |hash, k|
|
24
|
-
hash[k] = {} unless hash.key?(k)
|
24
|
+
hash[k] = {} unless hash.key?(k) && hash[k].is_a?(::Hash)
|
25
25
|
hash[k]
|
26
26
|
end
|
27
|
+
|
27
28
|
hash[last_key] = val
|
28
29
|
end
|
30
|
+
|
31
|
+
def deep_transform_keys(&block)
|
32
|
+
each_with_object({}) do |(key, value), result|
|
33
|
+
result[yield(key)] = value.is_a?(::Hash) ? value.deep_transform_keys(&block) : value
|
34
|
+
end
|
35
|
+
end
|
29
36
|
end
|
30
37
|
|
31
38
|
using self
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Anyway
|
4
|
+
module Ext
|
5
|
+
# Add simple safe_constantize method to String
|
6
|
+
module StringConstantize
|
7
|
+
refine String do
|
8
|
+
def safe_constantize
|
9
|
+
names = split("::")
|
10
|
+
|
11
|
+
return nil if names.empty?
|
12
|
+
|
13
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
14
|
+
names.shift if names.size > 1 && names.first.empty?
|
15
|
+
|
16
|
+
names.inject(Object) do |constant, name|
|
17
|
+
break if constant.nil?
|
18
|
+
constant.const_get(name, false) if constant.const_defined?(name, false)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "uri"
|
4
|
+
require "net/http"
|
5
|
+
require "json"
|
6
|
+
|
7
|
+
module Anyway
|
8
|
+
using RubyNext
|
9
|
+
|
10
|
+
module Loaders
|
11
|
+
class Doppler < Base
|
12
|
+
class RequestError < StandardError; end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
attr_accessor :download_url
|
16
|
+
attr_writer :token
|
17
|
+
|
18
|
+
def token
|
19
|
+
@token || ENV["DOPPLER_TOKEN"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
self.download_url = "https://api.doppler.com/v3/configs/config/secrets/download"
|
24
|
+
|
25
|
+
def call(env_prefix:, **_options)
|
26
|
+
env_payload = parse_doppler_response(url: Doppler.download_url, token: Doppler.token)
|
27
|
+
|
28
|
+
env = ::Anyway::Env.new(type_cast: ::Anyway::NoCast, env_container: env_payload)
|
29
|
+
|
30
|
+
env.fetch_with_trace(env_prefix).then do |(conf, trace)|
|
31
|
+
Tracing.current_trace&.merge!(trace)
|
32
|
+
conf
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def parse_doppler_response(url:, token:)
|
39
|
+
response = fetch_doppler_config(url, token)
|
40
|
+
|
41
|
+
unless response.is_a?(Net::HTTPSuccess)
|
42
|
+
raise RequestError, "#{response.code} #{response.message}"
|
43
|
+
end
|
44
|
+
|
45
|
+
JSON.parse(response.read_body)
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch_doppler_config(url, token)
|
49
|
+
uri = URI.parse(url)
|
50
|
+
raise "Doppler token is required to load configuration from Doppler" if token.nil?
|
51
|
+
|
52
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
53
|
+
http.use_ssl = true if uri.scheme == "https"
|
54
|
+
|
55
|
+
request = Net::HTTP::Get.new(uri)
|
56
|
+
request["Accept"] = "application/json"
|
57
|
+
request["Authorization"] = "Bearer #{token}"
|
58
|
+
|
59
|
+
http.request(request)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,89 @@
|
|
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.except("_public_key")
|
30
|
+
end
|
31
|
+
|
32
|
+
next unless config_hash.is_a?(Hash)
|
33
|
+
|
34
|
+
configs <<
|
35
|
+
trace!(:ejson, path: rel_path) do
|
36
|
+
config_hash
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
return {} if configs.empty?
|
41
|
+
|
42
|
+
configs.inject do |result_config, next_config|
|
43
|
+
Utils.deep_merge!(result_config, next_config)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def rel_config_paths
|
50
|
+
chain = [environmental_rel_config_path]
|
51
|
+
|
52
|
+
chain << "secrets.local.ejson" if use_local?
|
53
|
+
|
54
|
+
chain
|
55
|
+
end
|
56
|
+
|
57
|
+
def environmental_rel_config_path
|
58
|
+
if Settings.current_environment
|
59
|
+
# if environment file is absent, then take data from the default one
|
60
|
+
[
|
61
|
+
"#{Settings.current_environment}/secrets.ejson",
|
62
|
+
default_rel_config_path
|
63
|
+
]
|
64
|
+
else
|
65
|
+
default_rel_config_path
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def default_rel_config_path
|
70
|
+
"secrets.ejson"
|
71
|
+
end
|
72
|
+
|
73
|
+
def extract_hash_from_rel_config_path(ejson_parser:, rel_config_path:)
|
74
|
+
rel_config_path = [rel_config_path] unless rel_config_path.is_a?(Array)
|
75
|
+
|
76
|
+
rel_config_path.each do |rel_conf_path|
|
77
|
+
rel_path = "config/#{rel_conf_path}"
|
78
|
+
abs_path = "#{Settings.app_root}/#{rel_path}"
|
79
|
+
|
80
|
+
result = ejson_parser.call(abs_path)
|
81
|
+
|
82
|
+
return [result, rel_path] if result
|
83
|
+
end
|
84
|
+
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|