anyway_config 2.3.0 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -0
- data/README.md +60 -1
- data/lib/.rbnext/2.6/anyway/ejson_parser.rb +40 -0
- data/lib/.rbnext/2.7/anyway/config.rb +21 -30
- data/lib/.rbnext/2.7/anyway/loaders/yaml.rb +2 -2
- data/lib/.rbnext/2.7/anyway/rbs.rb +1 -1
- data/lib/.rbnext/2.7/anyway/settings.rb +14 -0
- data/lib/.rbnext/2.7/anyway/tracing.rb +10 -4
- data/lib/.rbnext/2.7/anyway/type_casting.rb +14 -1
- data/lib/.rbnext/3.0/anyway/config.rb +21 -30
- data/lib/.rbnext/3.0/anyway/loaders.rb +2 -0
- data/lib/.rbnext/3.0/anyway/tracing.rb +10 -4
- data/lib/.rbnext/3.1/anyway/config.rb +21 -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 +21 -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 +85 -0
- data/lib/anyway/loaders/yaml.rb +2 -2
- data/lib/anyway/loaders.rb +2 -0
- data/lib/anyway/rails/settings.rb +2 -0
- 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 +8 -3
- data/sig/manifest.yml +7 -0
- metadata +38 -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
|
135
|
+
return unless Settings.matching_env?(env)
|
138
136
|
|
139
|
-
|
140
|
-
|
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
|
154
|
-
|
155
|
-
def names_with_exclude_env_option(names, env)
|
156
|
-
envs = env[ENV_OPTION_EXCLUDE_KEY]
|
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
|
@@ -223,6 +203,12 @@ module Anyway # :nodoc:
|
|
223
203
|
|
224
204
|
def coerce_types(mapping)
|
225
205
|
Utils.deep_merge!(coercion_mapping, mapping)
|
206
|
+
|
207
|
+
mapping.each do |key, val|
|
208
|
+
next unless val == :boolean || (val.is_a?(::Hash) && val[:type] == :boolean)
|
209
|
+
|
210
|
+
alias_method :"#{key}?", :"#{key}"
|
211
|
+
end
|
226
212
|
end
|
227
213
|
|
228
214
|
def coercion_mapping
|
@@ -268,7 +254,7 @@ module Anyway # :nodoc:
|
|
268
254
|
names.each do |name|
|
269
255
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
270
256
|
def #{name}=(val)
|
271
|
-
__trace__&.record_value(val,
|
257
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
272
258
|
values[:#{name}] = val
|
273
259
|
end
|
274
260
|
|
@@ -297,7 +283,7 @@ module Anyway # :nodoc:
|
|
297
283
|
# - SomeModule::Config => "some_module"
|
298
284
|
# - SomeConfig => "some"
|
299
285
|
unless name =~ /^(\w+)(::)?Config$/
|
300
|
-
raise "Couldn't infer config name, please, specify it explicitly" \
|
286
|
+
raise "Couldn't infer config name, please, specify it explicitly " \
|
301
287
|
"via `config_name :my_config`"
|
302
288
|
end
|
303
289
|
|
@@ -430,13 +416,18 @@ module Anyway # :nodoc:
|
|
430
416
|
end
|
431
417
|
end
|
432
418
|
|
419
|
+
def as_env
|
420
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
421
|
+
end
|
422
|
+
|
433
423
|
private
|
434
424
|
|
435
425
|
attr_reader :values, :__trace__
|
436
426
|
|
437
427
|
def validate_required_attributes!
|
438
428
|
self.class.required_attributes.select do |name|
|
439
|
-
values
|
429
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
430
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
440
431
|
end.then do |missing|
|
441
432
|
next if missing.empty?
|
442
433
|
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
|
135
|
+
return unless Settings.matching_env?(env)
|
138
136
|
|
139
|
-
|
140
|
-
|
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
|
154
|
-
|
155
|
-
def names_with_exclude_env_option(names, env)
|
156
|
-
envs = env[ENV_OPTION_EXCLUDE_KEY]
|
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
|
@@ -223,6 +203,12 @@ module Anyway # :nodoc:
|
|
223
203
|
|
224
204
|
def coerce_types(mapping)
|
225
205
|
Utils.deep_merge!(coercion_mapping, mapping)
|
206
|
+
|
207
|
+
mapping.each do |key, val|
|
208
|
+
next unless val == :boolean || (val.is_a?(::Hash) && val[:type] == :boolean)
|
209
|
+
|
210
|
+
alias_method :"#{key}?", :"#{key}"
|
211
|
+
end
|
226
212
|
end
|
227
213
|
|
228
214
|
def coercion_mapping
|
@@ -268,7 +254,7 @@ module Anyway # :nodoc:
|
|
268
254
|
names.each do |name|
|
269
255
|
accessors_module.module_eval <<~RUBY, __FILE__, __LINE__ + 1
|
270
256
|
def #{name}=(val)
|
271
|
-
__trace__&.record_value(val,
|
257
|
+
__trace__&.record_value(val, "#{name}", **Tracing.current_trace_source)
|
272
258
|
values[:#{name}] = val
|
273
259
|
end
|
274
260
|
|
@@ -297,7 +283,7 @@ module Anyway # :nodoc:
|
|
297
283
|
# - SomeModule::Config => "some_module"
|
298
284
|
# - SomeConfig => "some"
|
299
285
|
unless name =~ /^(\w+)(::)?Config$/
|
300
|
-
raise "Couldn't infer config name, please, specify it explicitly" \
|
286
|
+
raise "Couldn't infer config name, please, specify it explicitly " \
|
301
287
|
"via `config_name :my_config`"
|
302
288
|
end
|
303
289
|
|
@@ -430,13 +416,18 @@ module Anyway # :nodoc:
|
|
430
416
|
end
|
431
417
|
end
|
432
418
|
|
419
|
+
def as_env
|
420
|
+
Env.from_hash(to_h, prefix: env_prefix)
|
421
|
+
end
|
422
|
+
|
433
423
|
private
|
434
424
|
|
435
425
|
attr_reader :values, :__trace__
|
436
426
|
|
437
427
|
def validate_required_attributes!
|
438
428
|
self.class.required_attributes.select do |name|
|
439
|
-
values
|
429
|
+
val = values.dig(*name.to_s.split(".").map(&:to_sym))
|
430
|
+
val.nil? || (val.is_a?(String) && val.empty?)
|
440
431
|
end.then do |missing|
|
441
432
|
next if missing.empty?
|
442
433
|
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,85 @@
|
|
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_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 = secrets_hash[name]
|
27
|
+
|
28
|
+
next unless config_hash.is_a?(Hash)
|
29
|
+
|
30
|
+
configs <<
|
31
|
+
trace!(:ejson, path: rel_path) do
|
32
|
+
config_hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
return {} if configs.empty?
|
37
|
+
|
38
|
+
configs.inject do |result_config, next_config|
|
39
|
+
Utils.deep_merge!(result_config, next_config)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def rel_config_paths
|
46
|
+
chain = [environmental_rel_config_path]
|
47
|
+
|
48
|
+
chain << "secrets.local.ejson" if use_local?
|
49
|
+
|
50
|
+
chain
|
51
|
+
end
|
52
|
+
|
53
|
+
def environmental_rel_config_path
|
54
|
+
if Settings.current_environment
|
55
|
+
# if environment file is absent, then take data from the default one
|
56
|
+
[
|
57
|
+
"#{Settings.current_environment}/secrets.ejson",
|
58
|
+
default_rel_config_path
|
59
|
+
]
|
60
|
+
else
|
61
|
+
default_rel_config_path
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def default_rel_config_path
|
66
|
+
"secrets.ejson"
|
67
|
+
end
|
68
|
+
|
69
|
+
def extract_hash_from_rel_config_path(ejson_parser:, rel_config_path:)
|
70
|
+
rel_config_path = [rel_config_path] unless rel_config_path.is_a?(Array)
|
71
|
+
|
72
|
+
rel_config_path.each do |rel_conf_path|
|
73
|
+
rel_path = "config/#{rel_conf_path}"
|
74
|
+
abs_path = "#{Settings.app_root}/#{rel_path}"
|
75
|
+
|
76
|
+
result = ejson_parser.call(abs_path)
|
77
|
+
|
78
|
+
return [result, rel_path] if result
|
79
|
+
end
|
80
|
+
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
data/lib/anyway/loaders/yaml.rb
CHANGED
@@ -53,13 +53,13 @@ module Anyway
|
|
53
53
|
# the interface when no config file is present.
|
54
54
|
begin
|
55
55
|
if defined?(ERB)
|
56
|
-
::YAML.load(ERB.new(File.read(path)).result, aliases: true) || {}
|
56
|
+
::YAML.load(ERB.new(File.read(path)).result, aliases: true) || {}
|
57
57
|
else
|
58
58
|
::YAML.load_file(path, aliases: true) || {}
|
59
59
|
end
|
60
60
|
rescue ArgumentError
|
61
61
|
if defined?(ERB)
|
62
|
-
::YAML.load(ERB.new(File.read(path)).result) || {}
|
62
|
+
::YAML.load(ERB.new(File.read(path)).result) || {}
|
63
63
|
else
|
64
64
|
::YAML.load_file(path) || {}
|
65
65
|
end
|
data/lib/anyway/loaders.rb
CHANGED
data/lib/anyway/settings.rb
CHANGED
@@ -80,6 +80,20 @@ module Anyway
|
|
80
80
|
def default_environmental_key?
|
81
81
|
!default_environmental_key.nil?
|
82
82
|
end
|
83
|
+
|
84
|
+
def matching_env?(env)
|
85
|
+
return true if env.nil? || env.to_s == current_environment
|
86
|
+
|
87
|
+
if env.is_a?(::Hash)
|
88
|
+
envs = env[:except]
|
89
|
+
excluded_envs = [envs].flat_map(&:to_s)
|
90
|
+
excluded_envs.none?(current_environment)
|
91
|
+
elsif env.is_a?(::Array)
|
92
|
+
env.flat_map(&:to_s).include?(current_environment)
|
93
|
+
else
|
94
|
+
false
|
95
|
+
end
|
96
|
+
end
|
83
97
|
end
|
84
98
|
|
85
99
|
# By default, use local files only in development (that's the purpose if the local files)
|