class Sensu::Settings::Loader

Attributes

errors[R]

@!attribute [r] errors

@return [Array] loader errors.
loaded_files[R]

@!attribute [r] loaded_files

@return [Array] loaded config files.
warnings[R]

@!attribute [r] warnings

@return [Array] loader warnings.

Public Class Methods

create_category_methods() click to toggle source

Create setting category accessors and methods to test the existence of definitions. Called in initialize().

# File lib/sensu/settings/loader.rb, line 83
def self.create_category_methods
  CATEGORIES.each do |category|
    define_method(category) do
      setting_category(category)
    end
    method_name = category.to_s.chop + "_exists?"
    define_method(method_name.to_sym) do |name|
      definition_exists?(category, name)
    end
  end
end
new() click to toggle source
# File lib/sensu/settings/loader.rb, line 24
def initialize
  @warnings = []
  @errors = []
  @settings = default_settings
  @indifferent_access = false
  @loaded_files = []
  self.class.create_category_methods
end

Public Instance Methods

[](key) click to toggle source

Retrieve the setting object corresponding to a key, acting like a Hash object.

@param key [String, Symbol] @return [Object] value for key.

# File lib/sensu/settings/loader.rb, line 111
def [](key)
  to_hash[key]
end
client_defaults() click to toggle source

Auto-detected defaults for client definition

Client name defaults to system hostname. Client address defaults to first detected non-loopback ipv4 address.

Client subscriptions are intentionally omitted here as sensu-client will provide defaults using client name after final settings are loaded.

@return [Hash] default client settings

# File lib/sensu/settings/loader.rb, line 43
def client_defaults
  {
    :name => system_hostname,
    :address => system_address
  }
end
default_settings() click to toggle source

Default settings.

@return [Hash] settings.

# File lib/sensu/settings/loader.rb, line 53
def default_settings
  default = {
    :client => {},
    :sensu => {
      :spawn => {
        :limit => 12
      },
      :keepalives => {
        :thresholds => {
          :warning => 120,
          :critical => 180
        }
      }
    },
    :transport => {
      :name => "rabbitmq",
      :reconnect_on_error => true
    }
  }
  CATEGORIES.each do |category|
    default[category] = {}
  end
  if ["client", "rspec"].include?(sensu_service_name)
    default[:client] = client_defaults
  end
  default
end
hexdigest() click to toggle source

Create a SHA256 hex digest for the settings Hash object. The client definition scope is ignored when the current process is not a Sensu client, as it is essentially ignored and it will likely cause a sum mismatch between two Sensu service systems. This method will not recalculate the hex digest, unless the settings have been altered, determine by the values of `@hexdigest` and `@indifferent_access`.

@return [String] SHA256 hex digest.

# File lib/sensu/settings/loader.rb, line 124
def hexdigest
  if @hexdigest && @indifferent_access
    @hexdigest
  else
    hash = case sensu_service_name
    when "client", "rspec"
      to_hash
    else
      to_hash.reject do |key, value|
        key.to_s == "client"
      end
    end
    @hexdigest = Digest::SHA256.hexdigest(hash.to_s)
  end
end
load_client_overrides() click to toggle source

Load Sensu client settings overrides. This method adds any overrides to the client definition. Overrides include:

  • Ensuring client subscriptions include a single subscription based on the

client name, e.g “client:i-424242”.

# File lib/sensu/settings/loader.rb, line 210
def load_client_overrides
  @settings[:client][:subscriptions] ||= []
  if @settings[:client][:subscriptions].is_a?(Array)
    @settings[:client][:subscriptions] << "client:#{@settings[:client][:name]}"
    @settings[:client][:subscriptions].uniq!
    warning("applied sensu client overrides", :client => @settings[:client])
    @indifferent_access = false
  else
    warning("unable to apply sensu client overrides", {
      :reason => "client subscriptions is not an array",
      :client => @settings[:client]
    })
  end
end
load_directory(directory) click to toggle source

Load settings from files in a directory. Files may be in nested directories.

@param [String] directory path.

# File lib/sensu/settings/loader.rb, line 193
def load_directory(directory)
  warning("loading config files from directory", :directory => directory)
  path = directory.gsub(/\\(?=\S)/, "/")
  if File.readable?(path) && File.executable?(path)
    Dir.glob(File.join(path, "**{,/*/**}/*.json")).uniq.each do |file|
      load_file(file)
    end
  else
    load_error("insufficient permissions for loading", :directory => directory)
  end
end
load_env() click to toggle source

Load settings from the environment.

Loads: SENSU_TRANSPORT_NAME, RABBITMQ_URL, REDIS_URL,

SENSU_CLIENT_NAME, SENSU_CLIENT_ADDRESS
SENSU_CLIENT_SUBSCRIPTIONS, SENSU_API_PORT
# File lib/sensu/settings/loader.rb, line 145
def load_env
  load_transport_env
  load_rabbitmq_env
  load_redis_env
  load_client_env
  load_api_env
end
load_file(file, must_exist=true) click to toggle source

Load settings from a JSON file.

@param [String] file path. @param must_exist [TrueClass, FalseClass] if the file must

exist and is readable.
# File lib/sensu/settings/loader.rb, line 158
def load_file(file, must_exist=true)
  if File.file?(file) && File.readable?(file)
    begin
      warning("loading config file", :file => file)
      contents = read_config_file(file)
      config = contents.empty? ? {} : Sensu::JSON.load(contents)
      merged = deep_merge(@settings, config)
      unless @loaded_files.empty?
        changes = deep_diff(@settings, merged)
        warning("config file applied changes", {
          :file => file,
          :changes => changes
        })
      end
      @settings = merged
      @indifferent_access = false
      @loaded_files << file
    rescue Sensu::JSON::ParseError => error
      load_error("config file must be valid json", {
        :file => file,
        :error => error.to_s
      })
    end
  elsif must_exist
    load_error("config file does not exist or is not readable", :file => file)
  else
    warning("config file does not exist or is not readable", :file => file)
    warning("ignoring config file", :file => file)
  end
end
load_overrides!() click to toggle source

Load overrides, i.e. settings which should always be present. Examples include client settings overrides which ensure a per-client subscription.

# File lib/sensu/settings/loader.rb, line 227
def load_overrides!
  load_client_overrides if ["client", "rspec"].include?(sensu_service_name)
end
set_env!() click to toggle source

Set Sensu settings related environment variables. This method sets `SENSU_LOADED_TEMPFILE` to a new temporary file path, a file containing the colon delimited list of loaded configuration files (using `create_loaded_tempfile!()`. The environment variable `SENSU_CONFIG_FILES` has been removed, due to the exec ARG_MAX (E2BIG) error when spawning processes after loading many configuration files (e.g. > 2000).

# File lib/sensu/settings/loader.rb, line 238
def set_env!
  ENV["SENSU_LOADED_TEMPFILE"] = create_loaded_tempfile!
end
to_hash() click to toggle source

Access settings as an indifferent hash.

@return [Hash] settings.

# File lib/sensu/settings/loader.rb, line 98
def to_hash
  unless @indifferent_access
    indifferent_access!
    @hexdigest = nil
  end
  @settings
end
validate() click to toggle source

Validate the loaded settings.

@return [Array] validation failures.

# File lib/sensu/settings/loader.rb, line 245
def validate
  validator = Validator.new
  @errors += validator.run(@settings, sensu_service_name)
end

Private Instance Methods

create_loaded_tempfile!() click to toggle source

Create a temporary file containing the colon delimited list of loaded configuration files. Ruby TempFile is not used to create the temporary file as it would be removed if the Sensu service daemonizes (fork/detach). The file is created in the system temporary file directory for the platform (Linux, Windows, etc.) and the file name contains the Sensu service name to reduce the likelihood of one Sensu service affecting another.

@return [String] tempfile path.

# File lib/sensu/settings/loader.rb, line 442
def create_loaded_tempfile!
  dir = ENV["SENSU_LOADED_TEMPFILE_DIR"] || Dir.tmpdir
  file_name = "sensu_#{sensu_service_name}_loaded_files"
  path = File.join(dir, file_name)
  File.open(path, "w") do |file|
    file.write(@loaded_files.join(":"))
  end
  path
end
deep_diff(hash_one, hash_two) click to toggle source

Compare two hashes.

@param [Hash] hash_one to compare. @param [Hash] hash_two to compare. @return [Hash] comparison diff hash.

# File lib/sensu/settings/loader.rb, line 418
def deep_diff(hash_one, hash_two)
  keys = hash_one.keys.concat(hash_two.keys).uniq
  keys.inject(Hash.new) do |diff, key|
    unless hash_one[key] == hash_two[key]
      if hash_one[key].is_a?(Hash) && hash_two[key].is_a?(Hash)
        diff[key] = deep_diff(hash_one[key], hash_two[key])
      else
        diff[key] = [hash_one[key], hash_two[key]]
      end
    end
    diff
  end
end
deep_merge(hash_one, hash_two) click to toggle source

Deep merge two hashes.

@param [Hash] hash_one to serve as base. @param [Hash] hash_two to merge in. @return [Hash] deep merged hash.

# File lib/sensu/settings/loader.rb, line 398
def deep_merge(hash_one, hash_two)
  merged = hash_one.dup
  hash_two.each do |key, value|
    merged[key] = case
    when hash_one[key].is_a?(Hash) && value.is_a?(Hash)
      deep_merge(hash_one[key], value)
    when hash_one[key].is_a?(Array) && value.is_a?(Array)
      hash_one[key].concat(value).uniq
    else
      value
    end
  end
  merged
end
definition_exists?(category, name) click to toggle source

Check to see if a definition exists in a category.

@param [Symbol] category to inspect for the definition. @param [String] name of definition. @return [TrueClass, FalseClass]

# File lib/sensu/settings/loader.rb, line 267
def definition_exists?(category, name)
  @settings[category].has_key?(name.to_sym)
end
indifferent_access!() click to toggle source

Update settings to have indifferent access.

# File lib/sensu/settings/loader.rb, line 296
def indifferent_access!
  @settings = with_indifferent_access(@settings)
  @indifferent_access = true
end
indifferent_hash() click to toggle source

Creates an indifferent hash.

@return [Hash] indifferent hash.

# File lib/sensu/settings/loader.rb, line 274
def indifferent_hash
  Hash.new do |hash, key|
    if key.is_a?(String)
      hash[key.to_sym]
    end
  end
end
load_api_env() click to toggle source

Load Sensu API settings from the environment. This method sets the API port to `SENSU_API_PORT` if set.

# File lib/sensu/settings/loader.rb, line 363
def load_api_env
  if ENV["SENSU_API_PORT"]
    @settings[:api] ||= {}
    @settings[:api][:port] = ENV["SENSU_API_PORT"].to_i
    warning("using api port environment variable", :api => @settings[:api])
    @indifferent_access = false
  end
end
load_client_env() click to toggle source

Load Sensu client settings from the environment. This method loads client settings from several variables: `SENSU_CLIENT_NAME`, `SENSU_CLIENT_ADDRESS`, and `SENSU_CLIENT_SUBSCRIPTIONS`.

# File lib/sensu/settings/loader.rb, line 351
def load_client_env
  @settings[:client][:name] = ENV["SENSU_CLIENT_NAME"] if ENV["SENSU_CLIENT_NAME"]
  @settings[:client][:address] = ENV["SENSU_CLIENT_ADDRESS"] if ENV["SENSU_CLIENT_ADDRESS"]
  @settings[:client][:subscriptions] = ENV["SENSU_CLIENT_SUBSCRIPTIONS"].split(",") if ENV["SENSU_CLIENT_SUBSCRIPTIONS"]
  if ENV.keys.any? {|k| k =~ /^SENSU_CLIENT/}
    warning("using sensu client environment variables", :client => @settings[:client])
  end
  @indifferent_access = false
end
load_error(message, data={}) click to toggle source

Record a load error and raise a load error exception.

@param message [String] load error message. @param data [Hash] load error context.

# File lib/sensu/settings/loader.rb, line 494
def load_error(message, data={})
  @errors << {
    :message => message
  }.merge(data)
  raise(Error, message)
end
load_rabbitmq_env() click to toggle source

Load Sensu RabbitMQ settings from the environment. This method sets the RabbitMQ settings to `RABBITMQ_URL` if set. The Sensu RabbitMQ transport accepts a URL string for options.

# File lib/sensu/settings/loader.rb, line 315
def load_rabbitmq_env
  if ENV["RABBITMQ_URL"]
    @settings[:rabbitmq] = ENV["RABBITMQ_URL"]
    warning("using rabbitmq url environment variable", :rabbitmq => @settings[:rabbitmq])
    @indifferent_access = false
  end
end
load_redis_env() click to toggle source

Load Sensu Redis settings from the environment.

This method evaluates the REDIS_SENTINEL_URLS and REDIS_URL environment variables and configures the Redis settings accordingly.

When REDIS_SENTINEL_URLS is provided as a list of one or more comma-separated URLs, e.g. “redis://10.0.0.1:26379,redis://10.0.0.2:26379” these URLs will take precedence over the value provided by REDIS_URL, if any.

As the redis library accepts a URL string for options. This configuration applies to data storage and the redis transport, if used.

# File lib/sensu/settings/loader.rb, line 335
def load_redis_env
  if ENV["REDIS_SENTINEL_URLS"]
    @settings[:redis] = {:sentinels => ENV["REDIS_SENTINEL_URLS"]}
    warning("using redis sentinel url environment variable", :sentinels => @settings[:redis][:sentinels])
    @indifferent_access = false
  elsif ENV["REDIS_URL"]
    @settings[:redis] = ENV["REDIS_URL"]
    warning("using redis url environment variable", :redis => @settings[:redis])
    @indifferent_access = false
  end
end
load_transport_env() click to toggle source

Load Sensu transport settings from the environment. This method sets the Sensu transport name to `SENSU_TRANSPORT_NAME` if set.

# File lib/sensu/settings/loader.rb, line 304
def load_transport_env
  if ENV["SENSU_TRANSPORT_NAME"]
    @settings[:transport][:name] = ENV["SENSU_TRANSPORT_NAME"]
    warning("using sensu transport name environment variable", :transport => @settings[:transport])
    @indifferent_access = false
  end
end
read_config_file(file) click to toggle source

Read a configuration file and force its encoding to 8-bit ASCII, ignoring invalid characters. If there is a UTF-8 BOM, it will be removed. Some JSON parsers force ASCII but do not remove the UTF-8 BOM if present, causing encoding conversion errors. This method is for consistency across Sensu::JSON adapters and system platforms.

@param [String] file path to read. @return [String] file contents.

# File lib/sensu/settings/loader.rb, line 381
def read_config_file(file)
  contents = IO.read(file)
  if contents.respond_to?(:force_encoding)
    encoding = ::Encoding::ASCII_8BIT
    contents = contents.force_encoding(encoding)
    contents.sub!("\xEF\xBB\xBF".force_encoding(encoding), "")
  else
    contents.sub!(/^\357\273\277/, "")
  end
  contents.strip
end
sensu_service_name() click to toggle source

Retrieve Sensu service name.

@return [String] service name.

# File lib/sensu/settings/loader.rb, line 455
def sensu_service_name
  File.basename($0).split("-").last
end
setting_category(category) click to toggle source

Retrieve setting category definitions.

@param [Symbol] category to retrive. @return [Array<Hash>] category definitions.

# File lib/sensu/settings/loader.rb, line 256
def setting_category(category)
  @settings[category].map do |name, details|
    details.merge(:name => name.to_s)
  end
end
system_address() click to toggle source

Retrieve the system IP address. If a valid non-loopback IPv4 address cannot be found and an error is thrown, “unknown” will be returned.

@return [String] system ip address

# File lib/sensu/settings/loader.rb, line 473
def system_address
  Socket.ip_address_list.find { |address|
    address.ipv4? && !address.ipv4_loopback?
  }.ip_address rescue "unknown"
end
system_hostname() click to toggle source

Retrieve the system hostname. If the hostname cannot be determined and an error is thrown, return “unknown”, the same value Sensu uses for JIT clients.

@return [String] system hostname.

# File lib/sensu/settings/loader.rb, line 464
def system_hostname
  Socket.gethostname rescue "unknown"
end
warning(message, data={}) click to toggle source

Record a warning.

@param message [String] warning message. @param data [Hash] warning context. @return [Array] current warnings.

# File lib/sensu/settings/loader.rb, line 484
def warning(message, data={})
  @warnings << {
    :message => message
  }.merge(data)
end
with_indifferent_access(hash) click to toggle source

Create a copy of a hash with indifferent access.

@param hash [Hash] hash to make indifferent. @return [Hash] indifferent version of hash.

# File lib/sensu/settings/loader.rb, line 286
def with_indifferent_access(hash)
  hash = indifferent_hash.merge(hash)
  hash.each do |key, value|
    if value.is_a?(Hash)
      hash[key] = with_indifferent_access(value)
    end
  end
end