lib/contrast/configuration.rb in contrast-agent-3.14.0 vs lib/contrast/configuration.rb in contrast-agent-3.15.0

- old
+ new

@@ -22,12 +22,10 @@ def_delegator :root, :assign_value_to_path_array attr_reader :default_name, :root DEFAULT_YAML_PATH = 'contrast_security.yaml' - DEFAULT_HOST = '127.0.0.1' - DEFAULT_PORT = '30555' MILLISECOND_MARKER = '_ms' CONVERSION = { 'agent.service.enable' => 'agent.start_bundled_service' }.cs__freeze CONFIG_BASE_PATHS = [ @@ -35,13 +33,10 @@ 'config/', '/etc/contrast/ruby/', '/etc/contrast/', '/etc/' ].cs__freeze - REMOVE_FIELDS = [ - 'contrast' - ].cs__freeze def initialize cli_options = nil, default_name = DEFAULT_YAML_PATH @default_name = default_name # Load config_kv from file @@ -51,11 +46,10 @@ cli_options = deep_stringify_all_keys(cli_options) config_kv = deep_merge(cli_options, config_kv) if cli_options # Some in-flight rewrites to maintain backwards compatibility config_kv = update_prop_keys(config_kv) - config_kv = deprecate_fields(config_kv) @root = Contrast::Config::RootConfiguration.new(config_kv) end # Because we call this method to determine the need for scoping, it itself @@ -71,82 +65,71 @@ def respond_to_missing? method_name, *args root&.cs__respond_to?(method_name) || super end + # Get a loggable YAML format of this configuration + # @return [String] the current active configuration of the Agent, + # represented as a YAML string + def loggable + convert_to_hash.to_yaml + end + protected # TODO: RUBY-546 move utility methods to auxiliary classes def load_config config = {} configuration_paths.find do |path| - found = File.exist?(path) - next unless found + next unless File.exist?(path) - readable = File.readable?(path) - unless readable - puts "!!! Contrast - Configuration file at #{ path } is not readable by current user" + unless File.readable?(path) + log_file_read_error(path) next end config = yaml_to_hash(path) || {} break end - if config.empty? - puts "!!! Contrast - working directory: #{ Dir.pwd }" - puts '!!! Contrast - valid configuration file could not be found at any of the search paths' - puts 'Valid configuration paths are: ' - configuration_paths.each do |path| - puts(path) - end - end config end def yaml_to_hash path if path && File.readable?(path) begin yaml = IO.read(path) yaml = ERB.new(yaml).result if defined?(ERB) return YAML.safe_load(yaml) + rescue Psych::Exception => e + log_yaml_parse_error(path, e) rescue RuntimeError => e - puts "ERROR: unable to load configuration from path due to #{ e }" - puts "ERROR: path=#{ path } pwd=#{ Dir.pwd }" + puts "WARN: Unable to load configuration. #{ e }; path: #{ path }, pwd: #{ Dir.pwd }" end end {} end # We're updating properties loaded from the configuration # files to match the new agreed upon standard configuration # names, so that one file works for all agents def update_prop_keys config - converted = false CONVERSION.each_pair do |old_method, new_method| # See if the old value was set and needs to be translated deprecated_keys = old_method.split('.') - old_value = config deprecated_keys.each do |key| old_value = old_value[key] break if old_value.nil? end + next if old_value.nil? # have to account for literal false - next if old_value.nil? - - converted = true - - puts "The deprecated property #{ old_method } is being set." - puts "Please update your config to use the property #{ new_method } instead." - + log_deprecated_property(old_method, new_method) new_keys = new_method.split('.') - # We changed the seconds values into ms values. Multiply them accordingly old_value = old_value.to_i * 1000 if new_method.end_with?(MILLISECOND_MARKER) - new_value = config end_idx = new_keys.length - 1 new_keys.each_with_index do |new_key, index| if index == end_idx new_value[new_key] = old_value if new_value[new_key].nil? @@ -159,25 +142,10 @@ end config end - def deprecate_fields hash - REMOVE_FIELDS.each do |field| - path = field.split('.') - active_path = hash - path.each_with_index do |delete_path, index| - if index == path.length - 1 && active_path - active_path.delete(delete_path) - elsif active_path - active_path = active_path[delete_path] - end - end - end - hash - end - # Base paths to check for the contrast configuration file, sorted by # reverse order of precedence (first is most important). def configuration_paths @_configuration_paths ||= begin basename = default_name.split('.').first @@ -205,8 +173,81 @@ new_hash = {} hash.each do |key, value| new_hash[key.to_s] = value.is_a?(Hash) ? deep_stringify_all_keys(value) : value end new_hash + end + + private + + # We cannot use all access components at this point, unfortunately, as they + # may not have been initialized. Instead, we need to access the logger + # directly. + def logger + @_logger ||= (Contrast::Logger::Log.instance.logger if defined?(Contrast::Logger::Log)) + end + + # When we fail to parse a configuration because it is misformatted, log an + # appropriate message based on the Agent Onboarding specification + def log_yaml_parse_error path, exception + hash = { + path: path, + pwd: Dir.pwd + } + if exception.is_a?(Psych::SyntaxError) + hash[:context] = exception.context + hash[:column] = exception.column + hash[:line] = exception.line + hash[:offset] = exception.offset + hash[:problem] = exception.problem + end + + if logger + logger.warn('YAML validator found an error', hash) + else + puts "CONTRAST - WARN: YAML validator found an error. #{ hash.inspect }" + end + end + + def log_file_read_error path + if logger + logger.warn('Configuration file is not readable by current user', path: path) + else + puts "CONTRAST - WARN: Configuration file is not readable by current user; path: #{ path }" + end + end + + def log_deprecated_property old_method, new_method + if logger + logger.warn('Deprecated property in use', old_method: old_method, new_method: new_method) + else + puts "CONTRAST - WARN: Deprecated property in use; old_method: #{ old_method }, new_method: #{ new_method }" + end + end + + # Convert this entire configuration into a hash, walking down the entries + # in the thing to convert and setting them in the given hash. For now, this + # logs every possible key, whether set or not. If we want to change that + # behavior, we can skip adding keys to the hash if the value is nil, blank, + # or Contrast::Config::DefaultValue depending on desired behavior + # + # @param hash [Hash] the hash to populate + # @param convert [Contrast::Config::BaseConfiguration, Object] the level of + # configuration from which to convert. Note that at least one top level + # Contrast::Config::BaseConfiguration is required for anything to be set + # in the hash + # @return [Hash, Object] the leaf of each + # Contrast::Config::BaseConfiguration will be returned in the N > 0 steps + # the Hash will be returned at the end of the 0 level + def convert_to_hash convert=root, hash={} + case convert + when Contrast::Config::BaseConfiguration + convert.cs__class::KEYS.each_key do |key| + hash[key] = convert_to_hash(convert.send(key), {}) + end + hash + else + convert + end end end end