class ComplexConfig::Provider
@see ComplexConfig::Encryption
@see ComplexConfig::KeySource
@see ComplexConfig::Proxy
@see ComplexConfig::Settings
@see ComplexConfig::Config
access to configuration data through caching mechanisms.
processing plugins for dynamic attribute resolution, and providing memoized
configuration files from disk, applying environment-specific settings,
configuration data within the ComplexConfig system. It handles loading
The Provider class serves as the central hub for accessing and managing
A provider class that manages configuration loading, caching, and access
def [](name)
-
(ComplexConfig::Settings)- the configuration settings object for the
Parameters:
-
name(String) -- the name of the configuration to retrieve
def [](name) config pathname(name), name end
def add_plugin(plugin)
-
(self)- Returns self to allow for method chaining
Parameters:
-
plugin(Proc) -- The plugin proc to add to the provider's plugins set
def add_plugin(plugin) plugins.add plugin self end
def apply_plugins(setting, id)
-
(Object, nil)- The result from the first applicable plugin, or nil
Parameters:
-
id(Object) -- The identifier used when executing plugins -
setting(ComplexConfig::Settings) -- The settings object to apply
def apply_plugins(setting, id) plugins.find do |plugin| catch :skip do value = setting.instance_exec(id, &plugin) and return value end nil end end
def config(pathname, name = nil)
- See: ComplexConfig::Settings.build - for Settings object construction
See: evaluate - for ERB processing
See: decrypt_config_case - for decryption logic
Raises:
-
(TypeError)- If the configuration data cannot be converted to a hash -
(ComplexConfig::ConfigurationSyntaxError)- If the YAML syntax in the -
(ComplexConfig::EncryptionKeyMissing)- If an encrypted configuration -
(ComplexConfig::ConfigurationFileMissing)- If the configuration file
Returns:
-
(ComplexConfig::Settings)- A Settings object containing the parsed
Parameters:
-
name(String, nil) -- The name to use when building the Settings object, -
pathname(String, Pathname) -- The path to the configuration file to read
def config(pathname, name = nil) datas = [] path_exist = File.exist?(pathname) if path_exist datas << IO.binread(pathname) end decrypted, reason, enc_pathname = decrypt_config_case(pathname) case reason when :ok datas << decrypted when :key_missing datas.empty? and raise ComplexConfig::EncryptionKeyMissing, "encryption key for #{enc_pathname.to_s.inspect} is missing" when :file_missing datas.empty? and raise ComplexConfig::ConfigurationFileMissing, "configuration file #{pathname.to_s.inspect} is missing" end results = datas.map { |d| evaluate(pathname, d) } hashes = if ::Psych::VERSION < "4" results.map { |r| ::YAML.load(r, pathname) } else results.map { |r| ::YAML.unsafe_load(r, filename: pathname) } end settings = ComplexConfig::Settings.build(name, hashes.shift) hashes.each { |h| settings.attributes_update(h) } if shared = settings.shared? shared = shared.to_h settings.each do |key, value| if value.is_a? ComplexConfig::Settings value.attributes_update_if_undefined(shared) elsif value.nil? settings[key] = ComplexConfig::Settings.build(nil, shared.dup) end end end deep_freeze? and settings.deep_freeze settings rescue ::Psych::SyntaxError => e raise ComplexConfig::ComplexConfigError.wrap(:ConfigurationSyntaxError, e) end
def config_dir
-
(Pathname)- the configuration directory path
def config_dir @config_dir || (defined?(Rails) && Rails.respond_to?(:root) && Rails.root || Pathname.pwd) + 'config' end
def config_dir=(dir)
- Attr_writer: dir - The path to the configuration directory, or nil to
def config_dir=(dir) if dir.nil? @config_dir = nil else @config_dir = Pathname.new(dir) end end
def configure_with(config)
-
(self)- returns self for chaining operations
Parameters:
-
config(ComplexConfig::Config) -- the configuration object to apply to
def configure_with(config) config.configure(self) flush_cache end
def decrypt_config(pathname)
- See: decrypt_config_case - for the internal implementation that handles the
Returns:
-
(String, nil)- the decrypted configuration content if successful, or
Parameters:
-
pathname(String, Pathname) -- the path to the encrypted configuration
Other tags:
- Example: Decrypting a configuration file -
def decrypt_config(pathname) decrypt_config_case(pathname).first end
def decrypt_config_case(pathname)
-
(Array)- An array containing the decrypted data (or nil), a symbol
Parameters:
-
pathname(String, Pathname) -- The path to the configuration file to
def decrypt_config_case(pathname) enc_pathname = pathname.to_s + '.enc' my_ks = key_source(pathname) if File.exist?(enc_pathname) if my_ks.ask_and_send(:key) text = IO.binread(enc_pathname) decrypted = ComplexConfig::Encryption.new(my_ks.key_bytes).decrypt(text) return decrypted, :ok, enc_pathname else return nil, :key_missing, enc_pathname end end return nil, :file_missing, enc_pathname end
def decrypt_data(ciphertext, pathname = nil)
-
(String, nil)- the decrypted plain text, or nil if no valid key was found
Parameters:
-
pathname(String, nil) -- an optional path used to locate a key file (e.g., .key) -
ciphertext(String) -- the encrypted data to decrypt
def decrypt_data(ciphertext, pathname = nil) ks = key_source(pathname) return nil unless ks && ks.key ComplexConfig::Encryption.new(ks.key_bytes).decrypt(ciphertext) end
def deep_freeze=(flag)
- Attr_writer: flag - true to enable deep freezing, false to disable it
def deep_freeze=(flag) if @deep_freeze && !flag mize_cache_clear end @deep_freeze = flag end
def deep_freeze?
-
(TrueClass, FalseClass)- true if deep freezing is enabled, false
def deep_freeze? !!@deep_freeze end
def encrypt_config(pathname, config)
-
(String)- the base64-encoded encrypted string including the
Parameters:
-
config(Object) -- the configuration object to encrypt -
pathname(String, Pathname) -- the path to the configuration file to be
def encrypt_config(pathname, config) ks = key_source(pathname) ComplexConfig::Encryption.new(ks.key_bytes).encrypt(config) end
def env
-
(String)- the name of the current environment
def env @env || defined?(Rails) && Rails.respond_to?(:env) && Rails.env || ENV['RAILS_ENV'] || 'development' end
def evaluate(pathname, data)
-
(String)- The processed configuration data with all ERB templates
Parameters:
-
data(String) -- The raw configuration data string that may contain -
pathname(String, Pathname) -- The path to the file being evaluated,
def evaluate(pathname, data) erb = ::ERB.new(data, trim_mode: '-') erb.filename = pathname.to_s erb.result end
def evaluate(pathname, data)
def evaluate(pathname, data) erb = ::ERB.new(data, nil, '-') erb.filename = pathname.to_s erb.result end
def exist?(name)
- See: ComplexConfig::EncryptionKeyMissing - when an encryption key is required but not available
See: ComplexConfig::ConfigurationFileMissing - when a configuration file is missing
See: config - for the underlying configuration loading logic
Returns:
-
(TrueClass, FalseClass)- true if the configuration file exists and
Parameters:
-
name(String) -- the name of the configuration to check for existence
def exist?(name) !!config(pathname(name), name) rescue ComplexConfig::ConfigurationFileMissing, ComplexConfig::EncryptionKeyMissing false end
def flush_cache
-
(ComplexConfig::Provider)- the provider instance itself for chaining operations
def flush_cache mize_cache_clear self end
def initialize
Sets up the provider with an empty plugins collection and enables deep
Initializes a new provider instance with default settings
def initialize @plugins = Set.new @deep_freeze = true end
def interpret_name_value(name, value)
-
(ArgumentError)- if the name parameter is not a string/symbol or
Returns:
-
(Array- An array containing the normalized name and)
Parameters:
-
value(Object, nil) -- The value parameter to be processed, typically a -
name(String, Symbol, ComplexConfig::Settings) -- The name parameter
def interpret_name_value(name, value) if ComplexConfig::Settings === name if value name = name.name_prefix else value = name.to_h name = name.name_prefix end elsif name.respond_to?(:to_sym) value = value.to_h else raise ArgumentError, "name has to be either string/symbol or ComplexConfig::Settings" end return name, value end
def key(pathname = nil)
-
(String, nil)- The encryption key as a string if found, or nil if no
Parameters:
-
pathname(String, nil) -- The path to a configuration file that may
def key(pathname = nil) key_source(pathname).ask_and_send(:key) end
def key_source(pathname = nil)
-
(ComplexConfig::KeySource, nil)- A KeySource object containing the
Parameters:
-
pathname(String, nil) -- The path to a configuration file that may
def key_source(pathname = nil) [ ComplexConfig::KeySource.new(var: @key), ComplexConfig::KeySource.new(pathname: pathname), ComplexConfig::KeySource.new(env_var: 'COMPLEX_CONFIG_KEY'), ComplexConfig::KeySource.new(env_var: 'RAILS_MASTER_KEY'), ComplexConfig::KeySource.new(master_key_pathname: master_key_pathname), ].find(&:key) end
def master_key_pathname
-
(String, Pathname)- the path to the master key file or a Pathname object
def master_key_pathname if @master_key_pathname @master_key_pathname else config_dir + 'master.key' end end
def new_key
-
(String)- a randomly generated hexadecimal encryption key
def new_key SecureRandom.hex(16) end
def pathname(name)
-
(Pathname)- the full path to the configuration file with .yml extension
Parameters:
-
name(String) -- the name of the configuration file (without extension)
def pathname(name) config_dir + "#{name}.yml" end
def prepare_output(value)
-
(String)- the YAML representation of the value
Parameters:
-
value(Object) -- the value to convert to YAML format
def prepare_output(value) value.each_with_object({}) do |(k, v), h| h[k.to_s] = v end.to_yaml end
def provide_key_source(pathname, encrypt)
-
(ComplexConfig::EncryptionKeyInvalid)- if the encryption key is
Returns:
-
(ComplexConfig::KeySource)- A key source object configured with the
Parameters:
-
encrypt(Boolean, Symbol, String) -- Encryption directive that -
pathname(String, nil) -- The path to a configuration file that may
def provide_key_source(pathname, encrypt) ks = case encrypt when :random ComplexConfig::KeySource.new(var: new_key) when true key_source(pathname) when String if encrypt =~ /\A\h{32}\z/ ComplexConfig::KeySource.new(var: encrypt) else raise ComplexConfig::EncryptionKeyInvalid, "encryption key has wrong format, has to be hex number of length "\ "32, was #{encrypt.inspect}" end end ks or raise ComplexConfig::EncryptionKeyInvalid, "encryption key is missing" end
def proxy(env = nil)
-
(ComplexConfig::Proxy)- A new proxy object for dynamic configuration
Parameters:
-
env(String, nil) -- The environment name to use for configuration lookups,
def proxy(env = nil) ComplexConfig::Proxy.new(env) end
def valid_key?(key)
-
(ComplexConfig::KeySource, FalseClass)- returns the KeySource object
Parameters:
-
key(String) -- the encryption key to validate
def valid_key?(key) ks = ComplexConfig::KeySource.new(var: key) ComplexConfig::Encryption.new(ks.key_bytes) ks rescue false end
def write_config(name, value: nil, encrypt: false, store_key: false)
- See: flush_cache - to clear the configuration cache after writing
See: provide_key_source - for key management
See: prepare_output - for data formatting
Returns:
-
(String, Boolean)- The encryption key if stored, otherwise true
Parameters:
-
store_key(Boolean) -- Whether to store the encryption key in a -
encrypt(Boolean, Symbol, String) -- Whether to encrypt the -
value(Object, nil) -- The configuration value to write, required when -
name(String, ComplexConfig::Settings) -- The name of the configuration
def write_config(name, value: nil, encrypt: false, store_key: false) name, value = interpret_name_value(name, value) config_pathname = pathname(name).to_s if encrypt ks = provide_key_source(config_pathname, encrypt) File.secure_write(config_pathname + '.enc') do |out| out.write ComplexConfig::Encryption.new(ks.key_bytes).encrypt(prepare_output(value)) end if store_key File.secure_write(config_pathname + '.key') do |out| out.write ks.key end end ks.key else File.secure_write(config_pathname) do |out| out.puts prepare_output(value) end true end ensure flush_cache end