anyway_config 2.3.0 → 2.4.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 +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)
|