class Datadog::Core::Configuration::AgentSettingsResolver

DEV-2.0: The deprecated_for_removal_transport_configuration_proc should be removed.
about it and pick a value based on the following priority: code > environment variable > defaults.
Whenever there is a conflict (different configurations are provided in different orders), it MUST warn the users
this class will reflect that simplification as well.
single place. As we deprecate more and more of the different ways that these things can be configured,
configuration today. E.g., this is just all of the complexity regarding agent settings gathered together in a
It has quite a lot of complexity, but this complexity just reflects the actual complexity we have around our
This class unifies all the different ways that users can configure how we talk to the agent.

def self.call(settings, logger: Datadog.logger)

def self.call(settings, logger: Datadog.logger)
  new(settings, logger: logger).send(:call)
end

def adapter

def adapter
  if should_use_uds?
    Datadog::Core::Transport::Ext::UnixSocket::ADAPTER
  else
    Datadog::Core::Transport::Ext::HTTP::ADAPTER
  end
end

def call

def call
  # A transport_options proc configured for unix domain socket overrides most of the logic on this file
  if transport_options.adapter == Datadog::Core::Transport::Ext::UnixSocket::ADAPTER
    return AgentSettings.new(
      adapter: Datadog::Core::Transport::Ext::UnixSocket::ADAPTER,
      ssl: false,
      hostname: nil,
      port: nil,
      uds_path: transport_options.uds_path,
      timeout_seconds: timeout_seconds,
      deprecated_for_removal_transport_configuration_proc: nil,
    )
  end
  AgentSettings.new(
    adapter: adapter,
    ssl: ssl?,
    hostname: hostname,
    port: port,
    uds_path: uds_path,
    timeout_seconds: timeout_seconds,
    # NOTE: When provided, the deprecated_for_removal_transport_configuration_proc can override all
    # values above (ssl, hostname, port, timeout), or even make them irrelevant (by using an unix socket or
    # enabling test mode instead).
    # That is the main reason why it is deprecated -- it's an opaque function that may set a bunch of settings
    # that we know nothing of until we actually call it.
    deprecated_for_removal_transport_configuration_proc: deprecated_for_removal_transport_configuration_proc,
  )
end

def can_use_uds?

def can_use_uds?
  parsed_url && unix_scheme?(parsed_url) ||
    # If no agent settings have been provided, we try to connect using a local unix socket.
    # We only do so if the socket is present when `ddtrace` runs.
    !uds_fallback.nil?
end

def configured_hostname

def configured_hostname
  return @configured_hostname if defined?(@configured_hostname)
  @configured_hostname = pick_from(
    DetectedConfiguration.new(
      friendly_name: "'c.tracing.transport_options'",
      value: transport_options.hostname,
    ),
    DetectedConfiguration.new(
      friendly_name: "'c.agent.host'",
      value: settings.agent.host
    ),
    DetectedConfiguration.new(
      friendly_name: "#{Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_URL} environment variable",
      value: parsed_http_url && parsed_http_url.hostname
    ),
    DetectedConfiguration.new(
      friendly_name: "#{Datadog::Core::Configuration::Ext::Transport::ENV_DEFAULT_HOST} environment variable",
      value: ENV[Datadog::Core::Configuration::Ext::Transport::ENV_DEFAULT_HOST]
    )
  )
end

def configured_port

def configured_port
  return @configured_port if defined?(@configured_port)
  @configured_port = pick_from(
    try_parsing_as_integer(
      friendly_name: "'c.tracing.transport_options'",
      value: transport_options.port,
    ),
    try_parsing_as_integer(
      friendly_name: '"c.agent.port"',
      value: settings.agent.port,
    ),
    DetectedConfiguration.new(
      friendly_name: "#{Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_URL} environment variable",
      value: parsed_http_url && parsed_http_url.port,
    ),
    try_parsing_as_integer(
      friendly_name: "#{Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_PORT} environment variable",
      value: ENV[Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_PORT],
    )
  )
end

def deprecated_for_removal_transport_configuration_proc

doesn't work, we include the proc directly in the agent settings result.
In transport_options, we try to invoke the transport_options proc and get its configuration. In case that
def deprecated_for_removal_transport_configuration_proc
  transport_options_settings if transport_options_settings.is_a?(Proc) && transport_options.adapter.nil?
end

def hostname

def hostname
  configured_hostname || (should_use_uds? ? nil : Datadog::Core::Configuration::Ext::Agent::HTTP::DEFAULT_HOST)
end

def http_scheme?(uri)

def http_scheme?(uri)
  ['http', 'https'].include?(uri.scheme)
end

def initialize(settings, logger: Datadog.logger)

def initialize(settings, logger: Datadog.logger)
  @settings = settings
  @logger = logger
end

def log_warning(message)

def log_warning(message)
  logger.warn(message) if logger
end

def mixed_http_and_uds?

When we have mixed settings for http/https and uds, we print a warning and ignore the uds settings
def mixed_http_and_uds?
  return @mixed_http_and_uds if defined?(@mixed_http_and_uds)
  @mixed_http_and_uds = (configured_hostname || configured_port) && can_use_uds?
  if @mixed_http_and_uds
    warn_if_configuration_mismatch(
      [
        DetectedConfiguration.new(
          friendly_name: 'configuration of hostname/port for http/https use',
          value: "hostname: '#{hostname}', port: '#{port}'",
        ),
        DetectedConfiguration.new(
          friendly_name: 'configuration for unix domain socket',
          value: parsed_url.to_s,
        ),
      ]
    )
  end
  @mixed_http_and_uds
end

def parsed_http_url

Expected to return nil (not false!) when it's not http
def parsed_http_url
  parsed_url if parsed_url && http_scheme?(parsed_url)
end

def parsed_url

def parsed_url
  return @parsed_url if defined?(@parsed_url)
  unparsed_url_from_env = ENV[Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_URL]
  @parsed_url =
    if unparsed_url_from_env
      parsed = URI.parse(unparsed_url_from_env)
      if http_scheme?(parsed) || unix_scheme?(parsed)
        parsed
      else
        # rubocop:disable Layout/LineLength
        log_warning(
          "Invalid URI scheme '#{parsed.scheme}' for #{Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_URL} " \
          "environment variable ('#{unparsed_url_from_env}'). " \
          "Ignoring the contents of #{Datadog::Core::Configuration::Ext::Agent::ENV_DEFAULT_URL}."
        )
        # rubocop:enable Layout/LineLength
        nil
      end
    end
end

def pick_from(*configurations_in_priority_order)

def pick_from(*configurations_in_priority_order)
  detected_configurations_in_priority_order = configurations_in_priority_order.select(&:value?)
  if detected_configurations_in_priority_order.any?
    warn_if_configuration_mismatch(detected_configurations_in_priority_order)
    # The configurations are listed in priority, so we only need to look at the first; if there's more than
    # one, we emit a warning above
    detected_configurations_in_priority_order.first.value
  end
end

def port

def port
  configured_port || (should_use_uds? ? nil : Datadog::Core::Configuration::Ext::Agent::HTTP::DEFAULT_PORT)
end

def should_use_uds?

def should_use_uds?
  can_use_uds? && !mixed_http_and_uds?
end

def ssl?

def ssl?
  transport_options.ssl ||
    (!parsed_url.nil? && parsed_url.scheme == 'https')
end

def timeout_seconds

works best in their case.
Defaults to +nil+, letting the adapter choose what default
def timeout_seconds
  transport_options.timeout_seconds
end

def transport_options

`TransportOptionsResolver` to call the proc and extract its information.
in the specific case of the http and unix socket adapters we can, and we use this method together with the
communicate with the agent. In the general case, we can't extract the configuration from this proc, but
The settings.tracing.transport_options allows users to have full control over the settings used to
def transport_options
  return @transport_options if defined?(@transport_options)
  transport_options_proc = transport_options_settings
  @transport_options = TransportOptions.new
  if transport_options_proc.is_a?(Proc)
    begin
      transport_options_proc.call(TransportOptionsResolver.new(@transport_options))
    rescue NoMethodError => e
      if logger
        logger.debug do
          'Could not extract configuration from transport_options proc. ' \
          "Cause: #{e.class.name} #{e.message} Source: #{Array(e.backtrace).first}"
        end
      end
      # Reset the object; we shouldn't return the same one we passed into the proc as it may have
      # some partial configuration and we want all-or-nothing.
      @transport_options = TransportOptions.new
    end
  end
  @transport_options.freeze
end

def transport_options_settings

def transport_options_settings
  @transport_options_settings ||= begin
    settings.tracing.transport_options if settings.respond_to?(:tracing) && settings.tracing
  end
end

def try_parsing_as_integer(value:, friendly_name:)

def try_parsing_as_integer(value:, friendly_name:)
  value =
    begin
      Integer(value) if value
    rescue ArgumentError, TypeError
      log_warning("Invalid value for #{friendly_name} (#{value.inspect}). Ignoring this configuration.")
      nil
    end
  DetectedConfiguration.new(friendly_name: friendly_name, value: value)
end

def uds_fallback

This is by design, as we still want to use the default host:port if no unix socket is present.
We only use the default unix socket if it is already present.
def uds_fallback
  return @uds_fallback if defined?(@uds_fallback)
  @uds_fallback =
    if configured_hostname.nil? &&
        configured_port.nil? &&
        deprecated_for_removal_transport_configuration_proc.nil? &&
        File.exist?(Datadog::Core::Configuration::Ext::Agent::UnixSocket::DEFAULT_PATH)
      Datadog::Core::Configuration::Ext::Agent::UnixSocket::DEFAULT_PATH
    end
end

def uds_path

Unix socket path in the file system
def uds_path
  if mixed_http_and_uds?
    nil
  elsif parsed_url && unix_scheme?(parsed_url)
    path = parsed_url.to_s
    # Some versions of the built-in uri gem leave the original url untouched, and others remove the //, so this
    # supports both
    if path.start_with?('unix://')
      path.sub('unix://', '')
    else
      path.sub('unix:', '')
    end
  else
    uds_fallback
  end
end

def unix_scheme?(uri)

def unix_scheme?(uri)
  uri.scheme == 'unix'
end

def warn_if_configuration_mismatch(detected_configurations_in_priority_order)

def warn_if_configuration_mismatch(detected_configurations_in_priority_order)
  return unless detected_configurations_in_priority_order.map(&:value).uniq.size > 1
  log_warning(
    'Configuration mismatch: values differ between ' \
    "#{detected_configurations_in_priority_order
      .map { |config| "#{config.friendly_name} (#{config.value.inspect})" }.join(' and ')}" \
    ". Using #{detected_configurations_in_priority_order.first.value.inspect} and ignoring other configuration."
  )
end