runger_config 3.0.1 → 5.0.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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -35
  3. data/README.md +83 -83
  4. data/lib/generators/runger/app_config/app_config_generator.rb +13 -0
  5. data/lib/generators/runger/config/config_generator.rb +49 -0
  6. data/lib/generators/runger/install/install_generator.rb +45 -0
  7. data/lib/generators/{anyway → runger}/install/templates/application_config.rb.tt +1 -1
  8. data/lib/{anyway → runger}/auto_cast.rb +4 -4
  9. data/lib/{anyway → runger}/config.rb +124 -104
  10. data/lib/runger/dynamic_config.rb +29 -0
  11. data/lib/runger/ejson_parser.rb +40 -0
  12. data/lib/runger/env.rb +70 -0
  13. data/lib/runger/ext/deep_dup.rb +45 -0
  14. data/lib/runger/ext/deep_freeze.rb +40 -0
  15. data/lib/runger/ext/flatten_names.rb +33 -0
  16. data/lib/runger/ext/hash.rb +37 -0
  17. data/lib/runger/ext/string_constantize.rb +21 -0
  18. data/lib/runger/loaders/base.rb +17 -0
  19. data/lib/runger/loaders/doppler.rb +57 -0
  20. data/lib/runger/loaders/ejson.rb +91 -0
  21. data/lib/runger/loaders/env.rb +12 -0
  22. data/lib/runger/loaders/yaml.rb +86 -0
  23. data/lib/runger/loaders.rb +75 -0
  24. data/lib/runger/option_parser_builder.rb +27 -0
  25. data/lib/{anyway → runger}/optparse_config.rb +15 -14
  26. data/lib/runger/rails/autoload.rb +38 -0
  27. data/lib/runger/rails/config.rb +19 -0
  28. data/lib/runger/rails/loaders/credentials.rb +58 -0
  29. data/lib/runger/rails/loaders/secrets.rb +31 -0
  30. data/lib/runger/rails/loaders/yaml.rb +4 -0
  31. data/lib/runger/rails/loaders.rb +5 -0
  32. data/lib/runger/rails/settings.rb +83 -0
  33. data/lib/runger/rails.rb +22 -0
  34. data/lib/{anyway → runger}/railtie.rb +9 -8
  35. data/lib/{anyway → runger}/rbs.rb +30 -30
  36. data/lib/runger/settings.rb +109 -0
  37. data/lib/runger/testing/helpers.rb +34 -0
  38. data/lib/{anyway → runger}/testing.rb +3 -3
  39. data/lib/runger/tracing.rb +195 -0
  40. data/lib/{anyway → runger}/type_casting.rb +19 -14
  41. data/lib/{anyway → runger}/utils/deep_merge.rb +2 -2
  42. data/lib/runger/utils/which.rb +16 -0
  43. data/lib/runger/version.rb +5 -0
  44. data/lib/{anyway.rb → runger.rb} +1 -1
  45. data/lib/runger_config.rb +56 -0
  46. data/sig/{anyway_config.rbs → runger_config.rbs} +1 -1
  47. metadata +68 -68
  48. data/lib/anyway/dynamic_config.rb +0 -31
  49. data/lib/anyway/ejson_parser.rb +0 -40
  50. data/lib/anyway/env.rb +0 -72
  51. data/lib/anyway/ext/deep_dup.rb +0 -48
  52. data/lib/anyway/ext/deep_freeze.rb +0 -44
  53. data/lib/anyway/ext/flatten_names.rb +0 -37
  54. data/lib/anyway/ext/hash.rb +0 -40
  55. data/lib/anyway/ext/string_constantize.rb +0 -24
  56. data/lib/anyway/loaders/base.rb +0 -21
  57. data/lib/anyway/loaders/doppler.rb +0 -61
  58. data/lib/anyway/loaders/ejson.rb +0 -89
  59. data/lib/anyway/loaders/env.rb +0 -16
  60. data/lib/anyway/loaders/yaml.rb +0 -83
  61. data/lib/anyway/loaders.rb +0 -77
  62. data/lib/anyway/option_parser_builder.rb +0 -29
  63. data/lib/anyway/rails/autoload.rb +0 -40
  64. data/lib/anyway/rails/config.rb +0 -23
  65. data/lib/anyway/rails/loaders/credentials.rb +0 -62
  66. data/lib/anyway/rails/loaders/secrets.rb +0 -35
  67. data/lib/anyway/rails/loaders/yaml.rb +0 -9
  68. data/lib/anyway/rails/loaders.rb +0 -5
  69. data/lib/anyway/rails/settings.rb +0 -83
  70. data/lib/anyway/rails.rb +0 -24
  71. data/lib/anyway/settings.rb +0 -111
  72. data/lib/anyway/testing/helpers.rb +0 -36
  73. data/lib/anyway/tracing.rb +0 -188
  74. data/lib/anyway/utils/which.rb +0 -18
  75. data/lib/anyway/version.rb +0 -5
  76. data/lib/anyway_config.rb +0 -49
  77. data/lib/generators/anyway/app_config/app_config_generator.rb +0 -17
  78. data/lib/generators/anyway/config/config_generator.rb +0 -46
  79. data/lib/generators/anyway/install/install_generator.rb +0 -47
  80. /data/lib/generators/{anyway → runger}/app_config/USAGE +0 -0
  81. /data/lib/generators/{anyway → runger}/config/USAGE +0 -0
  82. /data/lib/generators/{anyway → runger}/config/templates/config.rb.tt +0 -0
  83. /data/lib/generators/{anyway → runger}/config/templates/config.yml.tt +0 -0
  84. /data/lib/generators/{anyway → runger}/install/USAGE +0 -0
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add #deep_freeze to hashes and arrays
4
+ module Runger::Ext::DeepFreeze
5
+ refine ::Hash do
6
+ def deep_freeze
7
+ freeze
8
+ each_value do |value|
9
+ value.deep_freeze if value.is_a?(::Hash) || value.is_a?(::Array)
10
+ end
11
+ end
12
+ end
13
+
14
+ refine ::Array do
15
+ def deep_freeze
16
+ freeze
17
+ each do |value|
18
+ value.deep_freeze if value.is_a?(::Hash) || value.is_a?(::Array)
19
+ end
20
+ end
21
+ end
22
+
23
+ begin
24
+ require 'active_support/core_ext/hash/indifferent_access'
25
+ rescue LoadError
26
+ end
27
+
28
+ if defined?(::ActiveSupport::HashWithIndifferentAccess)
29
+ refine ::ActiveSupport::HashWithIndifferentAccess do
30
+ def deep_freeze
31
+ freeze
32
+ each_value do |value|
33
+ value.deep_freeze if value.is_a?(::Hash) || value.is_a?(::Array)
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ using self
40
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Convert Hash with mixed array and hash values to an
4
+ # array of paths.
5
+ module Runger::Ext::FlattenNames
6
+ refine ::Array do
7
+ def flatten_names(prefix, buf)
8
+ if empty?
9
+ buf << :"#{prefix}"
10
+ return buf
11
+ end
12
+
13
+ each_with_object(buf) do |name, acc|
14
+ if name.is_a?(::Symbol)
15
+ acc << :"#{prefix}.#{name}"
16
+ else
17
+ name.flatten_names(prefix, acc)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ refine ::Hash do
24
+ def flatten_names(prefix = nil, buf = [])
25
+ each_with_object(buf) do |(k, v), acc|
26
+ parent = prefix ? "#{prefix}.#{k}" : k
27
+ v.flatten_names(parent, acc)
28
+ end
29
+ end
30
+ end
31
+
32
+ using self
33
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Extend Hash through refinements
4
+ module Runger::Ext::Hash
5
+ refine ::Hash do
6
+ def stringify_keys!
7
+ keys.each do |key|
8
+ value = delete(key)
9
+ self[key.to_s] = value
10
+ end
11
+
12
+ self
13
+ end
14
+
15
+ def bury(val, *path)
16
+ raise(ArgumentError, 'No path specified') if path.empty?
17
+ raise(ArgumentError, 'Path cannot contain nil') if path.compact.size != path.size
18
+
19
+ last_key = path.pop
20
+ hash =
21
+ path.reduce(self) do |hash, k|
22
+ hash[k] = {} unless hash.key?(k) && hash[k].is_a?(::Hash)
23
+ hash[k]
24
+ end
25
+
26
+ hash[last_key] = val
27
+ end
28
+
29
+ def deep_transform_keys(&block)
30
+ each_with_object({}) do |(key, value), result|
31
+ result[yield(key)] = value.is_a?(::Hash) ? value.deep_transform_keys(&block) : value
32
+ end
33
+ end
34
+ end
35
+
36
+ using self
37
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Add simple safe_constantize method to String
4
+ module Runger::Ext::StringConstantize
5
+ refine String do
6
+ def safe_constantize
7
+ names = split('::')
8
+
9
+ return nil if names.empty?
10
+
11
+ # Remove the first blank element in case of '::ClassName' notation.
12
+ names.shift if names.size > 1 && names.first.empty?
13
+
14
+ names.inject(Object) do |constant, name|
15
+ break if constant.nil?
16
+
17
+ constant.const_get(name, false) if constant.const_defined?(name, false)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Runger::Loaders::Base
4
+ include ::Runger::Tracing
5
+
6
+ class << self
7
+ def call(local: Runger::Settings.use_local_files, **options)
8
+ new(local:).call(**options)
9
+ end
10
+ end
11
+
12
+ def initialize(local:)
13
+ @local = local
14
+ end
15
+
16
+ def use_local? = @local == true
17
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'net/http'
5
+ require 'uri'
6
+
7
+ class Runger::Loaders::Doppler < Runger::Loaders::Base
8
+ class RequestError < StandardError; end
9
+
10
+ class << self
11
+ attr_accessor :download_url
12
+ attr_writer :token
13
+
14
+ def token
15
+ @token || ENV.fetch('DOPPLER_TOKEN', nil)
16
+ end
17
+ end
18
+
19
+ self.download_url = 'https://api.doppler.com/v3/configs/config/secrets/download'
20
+
21
+ def call(env_prefix:, **_options)
22
+ env_payload = parse_doppler_response(url: Runger::Loaders::Doppler.download_url, token: Runger::Loaders::Doppler.token)
23
+
24
+ env = ::Runger::Env.new(type_cast: ::Runger::NoCast, env_container: env_payload)
25
+
26
+ env.fetch_with_trace(env_prefix).then do |(conf, trace)|
27
+ ::Runger::Tracing.current_trace&.merge!(trace)
28
+ conf
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def parse_doppler_response(url:, token:)
35
+ response = fetch_doppler_config(url, token)
36
+
37
+ unless response.is_a?(Net::HTTPSuccess)
38
+ raise(RequestError, "#{response.code} #{response.message}")
39
+ end
40
+
41
+ JSON.parse(response.read_body)
42
+ end
43
+
44
+ def fetch_doppler_config(url, token)
45
+ uri = URI.parse(url)
46
+ raise('Doppler token is required to load configuration from Doppler') if token.nil?
47
+
48
+ http = Net::HTTP.new(uri.host, uri.port)
49
+ http.use_ssl = true if uri.scheme == 'https'
50
+
51
+ request = Net::HTTP::Get.new(uri)
52
+ request['Accept'] = 'application/json'
53
+ request['Authorization'] = "Bearer #{token}"
54
+
55
+ http.request(request)
56
+ end
57
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'runger/ejson_parser'
4
+
5
+ class Runger::Loaders::EJSON < Runger::Loaders::Base
6
+ class << self
7
+ attr_accessor :bin_path
8
+ end
9
+
10
+ self.bin_path = 'ejson'
11
+
12
+ def call(
13
+ name:,
14
+ ejson_namespace: name,
15
+ ejson_parser: Runger::EJSONParser.new(Runger::Loaders::EJSON.bin_path),
16
+ **_options
17
+ )
18
+ configs = []
19
+
20
+ rel_config_paths.each do |rel_config_path|
21
+ secrets_hash, rel_path =
22
+ extract_hash_from_rel_config_path(
23
+ ejson_parser:,
24
+ rel_config_path:,
25
+ )
26
+
27
+ next unless secrets_hash
28
+
29
+ config_hash =
30
+ if ejson_namespace
31
+ secrets_hash[ejson_namespace]
32
+ else
33
+ secrets_hash.except('_public_key')
34
+ end
35
+
36
+ next unless config_hash.is_a?(Hash)
37
+
38
+ configs <<
39
+ trace!(:ejson, path: rel_path) do
40
+ config_hash
41
+ end
42
+ end
43
+
44
+ return {} if configs.empty?
45
+
46
+ configs.inject do |result_config, next_config|
47
+ ::Runger::Utils.deep_merge!(result_config, next_config)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def rel_config_paths
54
+ chain = [environmental_rel_config_path]
55
+
56
+ chain << 'secrets.local.ejson' if use_local?
57
+
58
+ chain
59
+ end
60
+
61
+ def environmental_rel_config_path
62
+ if ::Runger::Settings.current_environment
63
+ # if environment file is absent, then take data from the default one
64
+ [
65
+ "#{::Runger::Settings.current_environment}/secrets.ejson",
66
+ default_rel_config_path,
67
+ ]
68
+ else
69
+ default_rel_config_path
70
+ end
71
+ end
72
+
73
+ def default_rel_config_path
74
+ 'secrets.ejson'
75
+ end
76
+
77
+ def extract_hash_from_rel_config_path(ejson_parser:, rel_config_path:)
78
+ rel_config_path = Array(rel_config_path)
79
+
80
+ rel_config_path.each do |rel_conf_path|
81
+ rel_path = "config/#{rel_conf_path}"
82
+ abs_path = "#{::Runger::Settings.app_root}/#{rel_path}"
83
+
84
+ result = ejson_parser.call(abs_path)
85
+
86
+ return [result, rel_path] if result
87
+ end
88
+
89
+ nil
90
+ end
91
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Runger::Loaders::Env < Runger::Loaders::Base
4
+ def call(env_prefix:, **_options)
5
+ env = ::Runger::Env.new(type_cast: ::Runger::NoCast)
6
+
7
+ env.fetch_with_trace(env_prefix).then do |(conf, trace)|
8
+ ::Runger::Tracing.current_trace&.merge!(trace)
9
+ conf
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'runger/ext/hash'
5
+
6
+ using Runger::Ext::Hash
7
+
8
+ class Runger::Loaders::YAML < Runger::Loaders::Base
9
+ def call(config_path:, **_options)
10
+ rel_config_path = relative_config_path(config_path).to_s
11
+ base_config =
12
+ trace!(:yml, path: rel_config_path) do
13
+ config = load_base_yml(config_path)
14
+ environmental?(config) ? config_with_env(config) : config
15
+ end
16
+
17
+ return base_config unless use_local?
18
+
19
+ local_path = local_config_path(config_path)
20
+ local_config =
21
+ trace!(:yml, path: relative_config_path(local_path).to_s) {
22
+ load_local_yml(local_path)
23
+ }
24
+ ::Runger::Utils.deep_merge!(base_config, local_config)
25
+ end
26
+
27
+ private
28
+
29
+ def environmental?(parsed_yml)
30
+ # strange, but still possible
31
+ return true if ::Runger::Settings.default_environmental_key? && parsed_yml.key?(::Runger::Settings.default_environmental_key)
32
+ # possible
33
+ return true if !::Runger::Settings.future.unwrap_known_environments && ::Runger::Settings.current_environment
34
+ # for other environments
35
+ return true if ::Runger::Settings.known_environments&.any? { parsed_yml.key?(_1) }
36
+
37
+ # preferred
38
+ parsed_yml.key?(::Runger::Settings.current_environment)
39
+ end
40
+
41
+ def config_with_env(config)
42
+ env_config = config[::Runger::Settings.current_environment] || {}
43
+ return env_config unless ::Runger::Settings.default_environmental_key?
44
+
45
+ default_config = config[::Runger::Settings.default_environmental_key] || {}
46
+ ::Runger::Utils.deep_merge!(default_config, env_config)
47
+ end
48
+
49
+ def parse_yml(path)
50
+ return {} unless File.file?(path)
51
+
52
+ require 'yaml' unless defined?(::YAML)
53
+
54
+ # By default, YAML load will return `false` when the yaml document is
55
+ # empty. When this occurs, we return an empty hash instead, to match
56
+ # the interface when no config file is present.
57
+ begin
58
+ if defined?(ERB)
59
+ ::YAML.load(ERB.new(File.read(path)).result, aliases: true) || {}
60
+ else
61
+ ::YAML.load_file(path, aliases: true) || {}
62
+ end
63
+ rescue ArgumentError
64
+ if defined?(ERB)
65
+ ::YAML.load(ERB.new(File.read(path)).result) || {}
66
+ else
67
+ ::YAML.load_file(path) || {}
68
+ end
69
+ end
70
+ end
71
+
72
+ alias load_base_yml parse_yml
73
+ alias load_local_yml parse_yml
74
+
75
+ def local_config_path(path)
76
+ path.sub('.yml', '.local.yml')
77
+ end
78
+
79
+ def relative_config_path(path)
80
+ Pathname.new(path).then do |path|
81
+ return path if path.relative?
82
+
83
+ path.relative_path_from(::Runger::Settings.app_root)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Runger::Loaders::Registry
4
+ attr_reader :registry
5
+
6
+ def initialize
7
+ @registry = []
8
+ end
9
+
10
+ def prepend(id, handler = nil, &block)
11
+ handler ||= block
12
+ insert_at(0, id, handler)
13
+ end
14
+
15
+ def append(id, handler = nil, &block)
16
+ handler ||= block
17
+ insert_at(registry.size, id, handler)
18
+ end
19
+
20
+ def insert_before(another_id, id, handler = nil, &block)
21
+ ind = registry.find_index { |(hid, _)| hid == another_id }
22
+ raise(ArgumentError, "Loader with ID #{another_id} hasn't been registered") if ind.nil?
23
+
24
+ handler ||= block
25
+ insert_at(ind, id, handler)
26
+ end
27
+
28
+ def insert_after(another_id, id, handler = nil, &block)
29
+ ind = registry.find_index { |(hid, _)| hid == another_id }
30
+ raise(ArgumentError, "Loader with ID #{another_id} hasn't been registered") if ind.nil?
31
+
32
+ handler ||= block
33
+ insert_at(ind + 1, id, handler)
34
+ end
35
+
36
+ def override(id, handler)
37
+ find(id).then do |id_to_handler|
38
+ raise(ArgumentError, "Loader with ID #{id} hasn't been registered") if id_to_handler.nil?
39
+
40
+ id_to_handler[1] = handler
41
+ end
42
+ end
43
+
44
+ def delete(id)
45
+ find(id).then do |id_to_handler|
46
+ raise(ArgumentError, "Loader with ID #{id} hasn't been registered") if id_to_handler.nil?
47
+
48
+ registry.delete(id_to_handler)
49
+ end
50
+ end
51
+
52
+ def each(&block)
53
+ registry.each(&block)
54
+ end
55
+
56
+ def freeze = registry.freeze
57
+
58
+ private
59
+
60
+ def insert_at(index, id, handler)
61
+ raise(ArgumentError, "Loader with ID #{id} has been already registered") unless find(id).nil?
62
+
63
+ registry.insert(index, [id, handler])
64
+ end
65
+
66
+ def find(id)
67
+ registry.find { |(hid, _)| hid == id }
68
+ end
69
+ end
70
+
71
+ require 'runger/loaders/base'
72
+ require 'runger/loaders/doppler'
73
+ require 'runger/loaders/ejson'
74
+ require 'runger/loaders/env'
75
+ require 'runger/loaders/yaml'
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ # Initializes the OptionParser instance using the given configuration
6
+ class Runger::OptionParserBuilder
7
+ class << self
8
+ def call(options)
9
+ OptionParser.new do |opts|
10
+ options.each do |key, descriptor|
11
+ opts.on(*option_parser_on_args(key, **descriptor)) do |val|
12
+ yield([key, val])
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def option_parser_on_args(key, flag: false, desc: nil, type: ::String)
21
+ on_args = ["--#{key.to_s.tr('_', '-')}#{flag ? '' : ' VALUE'}"]
22
+ on_args << type unless flag
23
+ on_args << desc unless desc.nil?
24
+ on_args
25
+ end
26
+ end
27
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "anyway/option_parser_builder"
3
+ require 'runger/option_parser_builder'
4
4
 
5
- require "anyway/ext/deep_dup"
5
+ require 'runger/ext/deep_dup'
6
6
 
7
- module Anyway
8
- using Anyway::Ext::DeepDup
7
+ module Runger
8
+ using Runger::Ext::DeepDup
9
9
 
10
10
  # Adds ability to use script options as the source
11
11
  # of configuration (via optparse)
@@ -19,7 +19,7 @@ module Anyway
19
19
 
20
20
  def describe_options(**hargs)
21
21
  hargs.each do |name, desc|
22
- if String === desc
22
+ if desc.is_a?(String)
23
23
  option_parser_descriptors[name.to_s][:desc] = desc
24
24
  else
25
25
  option_parser_descriptors[name.to_s].merge!(desc)
@@ -50,7 +50,7 @@ module Anyway
50
50
  return @option_parser_extensions if instance_variable_defined?(:@option_parser_extensions)
51
51
 
52
52
  @option_parser_extensions =
53
- if superclass < Anyway::Config
53
+ if superclass < Runger::Config
54
54
  superclass.option_parser_extensions.dup
55
55
  else
56
56
  []
@@ -61,7 +61,7 @@ module Anyway
61
61
  return @option_parser_descriptors if instance_variable_defined?(:@option_parser_descriptors)
62
62
 
63
63
  @option_parser_descriptors =
64
- if superclass < Anyway::Config
64
+ if superclass < Runger::Config
65
65
  superclass.option_parser_descriptors.deep_dup
66
66
  else
67
67
  Hash.new { |h, k| h[k] = {} }
@@ -70,13 +70,14 @@ module Anyway
70
70
  end
71
71
 
72
72
  def option_parser
73
- @option_parser ||= OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
74
- write_config_attr(key, val)
75
- end.tap do |parser|
76
- self.class.option_parser_extensions.map do |extension|
77
- extension.call(parser, self)
73
+ @option_parser ||=
74
+ OptionParserBuilder.call(self.class.option_parser_options) do |key, val|
75
+ write_config_attr(key, val)
76
+ end.tap do |parser|
77
+ self.class.option_parser_extensions.map do |extension|
78
+ extension.call(parser, self)
79
+ end
78
80
  end
79
- end
80
81
  end
81
82
 
82
83
  def parse_options!(options)
@@ -86,7 +87,7 @@ module Anyway
86
87
  end
87
88
 
88
89
  def self.included(base)
89
- base.extend ClassMethods
90
+ base.extend(ClassMethods)
90
91
  end
91
92
  end
92
93
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This module is used to detect a Rails application and activate the corresponding plugins
4
+ # when Runger Config is loaded before Rails (e.g., in config/puma.rb).
5
+ module Runger::Rails
6
+ class << self
7
+ attr_reader :tracer, :name_method
8
+ attr_accessor :disable_postponed_load_warning
9
+
10
+ private
11
+
12
+ def tracepoint_class_callback(event)
13
+ # Ignore singletons
14
+ return if event.self.singleton_class?
15
+
16
+ # We wait till `rails/application/configuration.rb` has been loaded, since we rely on it
17
+ # See https://github.com/palkan/runger_config/issues/134
18
+ return unless name_method.bind_call(event.self) == 'Rails::Application::Configuration'
19
+
20
+ tracer.disable
21
+
22
+ unless disable_postponed_load_warning
23
+ warn("Runger Config was loaded before Rails. Activating Runger Config Rails plugins now.\n" \
24
+ 'NOTE: Already loaded configs were provisioned without Rails-specific sources.')
25
+ end
26
+
27
+ require 'runger/rails'
28
+ end
29
+ end
30
+
31
+ # TruffleRuby doesn't support TracePoint's end event
32
+ unless defined?(::TruffleRuby)
33
+ @tracer = TracePoint.new(:end, &method(:tracepoint_class_callback))
34
+ @tracer.enable
35
+ # Use `Module#name` instead of `self.name` to handle overwritten `name` method
36
+ @name_method = Module.instance_method(:name)
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/hash/indifferent_access'
4
+
5
+ module Runger::Rails::Config ; end
6
+
7
+ # Enhance config to be more Railsy-like:
8
+ # – accept hashes with indeferent access
9
+ # - load data from secrets
10
+ # - recognize Rails env when loading from YML
11
+ module Runger::Rails::Config::ClassMethods
12
+ # Make defaults to be a Hash with indifferent access
13
+ def new_empty_config
14
+ {}.with_indifferent_access
15
+ end
16
+ end
17
+
18
+ Runger::Config.prepend(Runger::Rails::Config)
19
+ Runger::Config.singleton_class.prepend(Runger::Rails::Config::ClassMethods)
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Runger::Rails::Loaders ; end
4
+
5
+ class Runger::Rails::Loaders::Credentials < Runger::Loaders::Base
6
+ LOCAL_CONTENT_PATH = 'config/credentials/local.yml.enc'
7
+
8
+ def call(name:, **_options)
9
+ return {} unless ::Rails.application.respond_to?(:credentials)
10
+
11
+ # do not load from credentials if we're in the context
12
+ # of the `credentials:edit` command
13
+ return {} if defined?(::Rails::Command::CredentialsCommand)
14
+
15
+ # Create a new hash cause credentials are mutable!
16
+ config = {}
17
+
18
+ trace!(
19
+ :credentials,
20
+ store: credentials_path,
21
+ ) do
22
+ ::Rails.application.credentials.config[name.to_sym]
23
+ end.then do |creds|
24
+ Runger::Utils.deep_merge!(config, creds) if creds
25
+ end
26
+
27
+ if use_local?
28
+ trace!(:credentials, store: LOCAL_CONTENT_PATH) do
29
+ local_credentials(name)
30
+ end.then { |creds| Runger::Utils.deep_merge!(config, creds) if creds }
31
+ end
32
+
33
+ config
34
+ end
35
+
36
+ private
37
+
38
+ def local_credentials(name)
39
+ local_creds_path = ::Rails.root.join(LOCAL_CONTENT_PATH).to_s
40
+
41
+ return unless File.file?(local_creds_path)
42
+
43
+ creds = ::Rails.application.encrypted(
44
+ local_creds_path,
45
+ key_path: ::Rails.root.join('config/credentials/local.key'),
46
+ )
47
+
48
+ creds.config[name.to_sym]
49
+ end
50
+
51
+ def credentials_path
52
+ if ::Rails.application.config.respond_to?(:credentials)
53
+ ::Rails.root.join(::Rails.application.config.credentials.content_path).relative_path_from(::Rails.root).to_s
54
+ else
55
+ 'config/credentials.yml.enc'
56
+ end
57
+ end
58
+ end