class NewRelic::Agent::Configuration::Manager

Constants

DEPENDENCY_DETECTION_VALUES
MALFORMED_LABELS_WARNING
MAX_LABEL_COUNT
MAX_LABEL_LENGTH
PARSING_LABELS_FAILURE

Public Class Methods

new() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 34
def initialize
  reset_to_defaults
  @callbacks = Hash.new { |hash, key| hash[key] = [] }
  @lock = Mutex.new
end

Public Instance Methods

[](key) click to toggle source

Defining these explicitly saves object allocations that we incur if we use Forwardable and def_delegators.

# File lib/new_relic/agent/configuration/manager.rb, line 22
def [](key)
  @cache[key]
end
add_config_for_testing(source, level = 0) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 40
def add_config_for_testing(source, level = 0)
  raise 'Invalid config type for testing' unless [Hash, DottedHash].include?(source.class)

  invoke_callbacks(:add, source)
  @configs_for_testing << [source.freeze, level]
  reset_cache
  log_config(:add, source)
end
apply_mask(hash) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 243
def apply_mask(hash)
  MASK_DEFAULTS \
    .select { |_, proc| proc.call } \
    .each { |key, _| hash.delete(key) }
  hash
end
apply_transformations(key, value) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 148
def apply_transformations(key, value)
  if transform = transform_from_default(key)
    begin
      transform.call(value)
    rescue => e
      NewRelic::Agent.logger.error("Error applying transformation for #{key}, pre-transform value was: #{value}.", e)
      raise e
    end
  else
    value
  end
end
break_label_string_into_pairs(labels) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 288
def break_label_string_into_pairs(labels)
  stripped_labels = labels.strip.sub(/^;*/, '').sub(/;*$/, '')
  stripped_labels.split(';').map do |pair|
    pair.split(':').map(&:strip)
  end
end
config_classes_for_testing() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 424
def config_classes_for_testing
  config_stack.map(&:class)
end
default_source() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 174
def default_source
  NewRelic::Agent::Configuration::DefaultSource
end
delete_all_configs_for_testing() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 409
def delete_all_configs_for_testing
  @security_policy_source = nil
  @high_security_source = nil
  @environment_source = nil
  @server_source = nil
  @manual_source = nil
  @yaml_source = nil
  @default_source = nil
  @configs_for_testing = []
end
enforce_allowlist(key, value) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 161
def enforce_allowlist(key, value)
  return unless allowlist = default_source.allowlist_for(key)
  return if allowlist.include?(value)

  default = default_source.default_for(key)
  NewRelic::Agent.logger.warn "Invalid value '#{value}' for #{key}, applying default value of '#{default}'"
  default
end
evaluate_and_apply_transformations(key, value) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 140
def evaluate_and_apply_transformations(key, value)
  evaluated = evaluate_procs(value)
  default = enforce_allowlist(key, evaluated)
  return default if default

  apply_transformations(key, evaluated)
end
evaluate_procs(value) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 132
def evaluate_procs(value)
  if value.respond_to?(:call)
    instance_eval(&value)
  else
    value
  end
end
fetch(key) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 114
def fetch(key)
  config_stack.each do |config|
    next unless config

    accessor = key.to_sym

    if config.has_key?(accessor)
      begin
        return evaluate_and_apply_transformations(accessor, config[accessor])
      rescue
        next
      end
    end
  end

  nil
end
finished_configuring?() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 223
def finished_configuring?
  !@server_source.nil?
end
flattened() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 227
def flattened
  config_stack.reverse.inject({}) do |flat, layer|
    thawed_layer = layer.to_hash.dup
    thawed_layer.each do |k, v|
      begin
        thawed_layer[k] = instance_eval(&v) if v.respond_to?(:call)
      rescue => e
        NewRelic::Agent.logger.debug("#{e.class.name} : #{e.message} - when accessing config key #{k}")
        thawed_layer[k] = nil
      end
      thawed_layer.delete(:config)
    end
    flat.merge(thawed_layer.to_hash)
  end
end
has_key?(key) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 26
def has_key?(key)
  @cache.has_key?(key)
end
invoke_callbacks(direction, source) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 183
def invoke_callbacks(direction, source)
  return unless source

  source.keys.each do |key|
    begin
      # we need to evaluate and apply transformations for the value to deal with procs as values
      # this is usually done by the fetch method when accessing config, however the callbacks bypass that
      evaluated_cache = evaluate_and_apply_transformations(key, @cache[key])
      evaluated_source = evaluate_and_apply_transformations(key, source[key])
    rescue
      next
    end

    if evaluated_cache != evaluated_source
      @callbacks[key].each do |proc|
        if direction == :add
          proc.call(evaluated_source)
        else
          proc.call(evaluated_cache)
        end
      end
    end
  end
end
keys() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 30
def keys
  @cache.keys
end
limit_number_of_labels(pairs) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 346
def limit_number_of_labels(pairs)
  if pairs.length > MAX_LABEL_COUNT
    NewRelic::Agent.logger.warn("Too many labels defined. Only taking first #{MAX_LABEL_COUNT}")
    pairs[0...64]
  else
    pairs
  end
end
log_config(direction, source) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 398
def log_config(direction, source)
  # Just generating this log message (specifically calling `flattened`)
  # is expensive enough that we don't want to do it unless we're
  # actually going to be logging the message based on our current log
  # level, so use a `do` block.
  NewRelic::Agent.logger.debug do
    hash = flattened.delete_if { |k, _h| DEFAULTS.fetch(k, {}).fetch(:exclude_from_reported_settings, false) }
    "Updating config (#{direction}) from #{source.class}. Results: #{hash.inspect}"
  end
end
make_label_hash(pairs, labels = nil) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 313
def make_label_hash(pairs, labels = nil)
  # This can accept a hash, so force it down to an array of pairs first
  pairs = Array(pairs)

  unless valid_label_pairs?(pairs)
    NewRelic::Agent.logger.warn("#{MALFORMED_LABELS_WARNING}: #{labels || pairs}")
    return NewRelic::EMPTY_ARRAY
  end

  pairs = limit_number_of_labels(pairs)
  pairs = remove_duplicates(pairs)
  pairs.map do |key, value|
    {
      'label_type' => truncate(key),
      'label_value' => truncate(value.to_s, key)
    }
  end
end
new_cache() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 394
def new_cache
  @cache = Hash.new { |hash, key| hash[key] = self.fetch(key) }
end
notify_finished_configuring() click to toggle source

This event is intended to be fired once during the entire lifespan of an agent run, after the server source has been applied for the first time. This should indicate that all configuration has been applied, and the main functions of the agent are safe to start.

# File lib/new_relic/agent/configuration/manager.rb, line 219
def notify_finished_configuring
  NewRelic::Agent.instance.events.notify(:initial_configuration_complete)
end
notify_server_source_added() click to toggle source

This event is intended to be fired every time the server source is applied. This happens after the agent’s initial connect, and again on every forced reconnect.

# File lib/new_relic/agent/configuration/manager.rb, line 211
def notify_server_source_added
  NewRelic::Agent.instance.events.notify(:server_source_configuration_added)
end
num_configs_for_testing() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 420
def num_configs_for_testing
  config_stack.size
end
parse_labels_from_dictionary() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 361
def parse_labels_from_dictionary
  make_label_hash(NewRelic::Agent.config[:labels])
end
parse_labels_from_string() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 282
def parse_labels_from_string
  labels = NewRelic::Agent.config[:labels]
  label_pairs = break_label_string_into_pairs(labels)
  make_label_hash(label_pairs, labels)
end
parsed_labels() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 270
def parsed_labels
  case NewRelic::Agent.config[:labels]
  when String
    parse_labels_from_string
  else
    parse_labels_from_dictionary
  end
rescue => e
  NewRelic::Agent.logger.error(PARSING_LABELS_FAILURE, e)
  NewRelic::EMPTY_ARRAY
end
register_callback(key) { |cache| ... } click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 178
def register_callback(key, &proc)
  @callbacks[key] << proc
  yield(@cache[key])
end
remove_config(source) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 63
def remove_config(source)
  case source
  when SecurityPolicySource then @security_policy_source = nil
  when HighSecuritySource then @high_security_source = nil
  when EnvironmentSource then @environment_source = nil
  when ServerSource then @server_source = nil
  when ManualSource then @manual_source = nil
  when YamlSource then @yaml_source = nil
  when DefaultSource then @default_source = nil
  else
    @configs_for_testing.delete_if { |src, lvl| src == source }
  end

  reset_cache
  invoke_callbacks(:remove, source)
  log_config(:remove, source)
end
remove_config_type(sym) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 49
def remove_config_type(sym)
  source = case sym
  when :security_policy then @security_policy_source
  when :high_security then @high_security_source
  when :environment then @environment_source
  when :server then @server_source
  when :manual then @manual_source
  when :yaml then @yaml_source
  when :default then @default_source
  end

  remove_config(source)
end
remove_duplicates(pairs) click to toggle source

We only take the last value provided for a given label type key

# File lib/new_relic/agent/configuration/manager.rb, line 356
def remove_duplicates(pairs)
  grouped_by_type = pairs.group_by(&:first)
  grouped_by_type.values.map(&:last)
end
replace_or_add_config(source) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 81
def replace_or_add_config(source)
  source.freeze
  was_finished = finished_configuring?

  invoke_callbacks(:add, source)

  case source
  when SecurityPolicySource then @security_policy_source = source
  when HighSecuritySource then @high_security_source = source
  when EnvironmentSource then @environment_source = source
  when ServerSource then @server_source = source
  when ManualSource then @manual_source = source
  when YamlSource then @yaml_source = source
  when DefaultSource then @default_source = source
  else
    NewRelic::Agent.logger.warn("Invalid config format; config will be ignored: #{source}")
  end

  reset_cache
  log_config(:add, source)

  notify_server_source_added if ServerSource === source
  notify_finished_configuring if !was_finished && finished_configuring?
end
reset_cache() click to toggle source

reset the configuration hash, but do not replace previously auto determined dependency detection values with nil or ‘auto’

# File lib/new_relic/agent/configuration/manager.rb, line 382
def reset_cache
  return new_cache unless defined?(@cache) && @cache

  @lock.synchronize do
    preserved = @cache.dup.select { |_k, v| DEPENDENCY_DETECTION_VALUES.include?(v) }
    new_cache
    preserved.each { |k, v| @cache[k] = v }
  end

  @cache
end
reset_to_defaults() click to toggle source

Generally only useful during initial construction and tests

# File lib/new_relic/agent/configuration/manager.rb, line 366
def reset_to_defaults
  @security_policy_source = nil
  @high_security_source = nil
  @environment_source = EnvironmentSource.new
  @server_source = nil
  @manual_source = nil
  @yaml_source = nil
  @default_source = DefaultSource.new

  @configs_for_testing = []

  reset_cache
end
source(key) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 106
def source(key)
  config_stack.each do |config|
    if config.respond_to?(key.to_sym) || config.has_key?(key.to_sym)
      return config
    end
  end
end
to_collector_hash() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 250
def to_collector_hash
  DottedHash.new(apply_mask(flattened)).to_hash.delete_if do |k, _v|
    default = DEFAULTS[k]
    if default
      default[:exclude_from_reported_settings]
    else
      # In our tests, we add totally bogus configs, because testing.
      # In those cases, there will be no default. So we'll just let
      # them through.
      false
    end
  end
end
transform_from_default(key) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 170
def transform_from_default(key)
  default_source.transform_for(key)
end
truncate(text, key = nil) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 332
def truncate(text, key = nil)
  if text.length > MAX_LABEL_LENGTH
    if key
      msg = "The value for the label '#{key}' is longer than the allowed #{MAX_LABEL_LENGTH} and will be truncated. Value = '#{text}'"
    else
      msg = "Label name longer than the allowed #{MAX_LABEL_LENGTH} will be truncated. Name = '#{text}'"
    end
    NewRelic::Agent.logger.warn(msg)
    text[0..MAX_LABEL_LENGTH - 1]
  else
    text
  end
end
valid_label_item?(item) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 303
def valid_label_item?(item)
  case item
  when String then !item.empty?
  when Numeric then true
  when true then true
  when false then true
  else false
  end
end
valid_label_pairs?(label_pairs) click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 295
def valid_label_pairs?(label_pairs)
  label_pairs.all? do |pair|
    pair.length == 2 &&
      valid_label_item?(pair.first) &&
      valid_label_item?(pair.last)
  end
end

Private Instance Methods

config_stack() click to toggle source
# File lib/new_relic/agent/configuration/manager.rb, line 430
def config_stack
  stack = [@security_policy_source,
    @high_security_source,
    @environment_source,
    @server_source,
    @manual_source,
    @yaml_source,
    @default_source]

  stack.compact!

  @configs_for_testing.each do |config, at_start|
    if at_start
      stack.insert(0, config)
    else
      stack.push(config)
    end
  end

  stack
end