class LogStash::Outputs::Icinga

This plugin runs actions on an Icinga server by calling its API. The Icinga API is available since version 2.4. It replaces the formerly used command pipe by providing a similiar interface with filter capabilities. Actions are used in order to process check results, manage downtimes, tell Icinga to send notifications and so on.

This plugin handles a defined set of actions. A list of all Icinga actions is avaiable in the docs.icinga.com/icinga2/latest/doc/module/icinga2/chapter/icinga2-api#icinga2-api-actions[Icinga Docs].

Examples:

. Process a check result based on syslog severity

source,ruby

filter {

if [syslog_severity] == "error" {
  mutate {
    replace => { "exit_status" => "2" }
  }
}

} output {

icinga {
  host           => 'demo.icinga.com'
  user           => 'icinga'
  password       => 'supersecret'
  action         => 'process-check-result'
  action_config  => {
    exit_status   => "%{exit_status}"
    plugin_output => "%{message}"
  }
  icinga_host    => "%{hostname}"
  icinga_service => "dummy"
}

}

. Set a downtime of 2 hours, starting from now

source,ruby

filter {

ruby { code => "event.set('start_time', Time.now.to_i)" }
ruby { code => "event.set('end_time', Time.now.to_i + 7200)" }

} output {

icinga {
  host           => 'demo'
  user           => 'root'
  password       => 'icinga'
  ssl_verify     => false
  action         => 'schedule-downtime'
  action_config  => {
    author     => "logstash"
    comment    => "Downtime set by Logstash Output"
    start_time => "%{start_time}"
    end_time   => "%{end_time}"
  }
  icinga_host    => '%{hostname}'
  icinga_service => 'dummy'
}

Constants

ACTION_CONFIG_FIELDS

Public Instance Methods

receive(event) click to toggle source
# File lib/logstash/outputs/icinga.rb, line 290
def receive(event)

  @available_hosts = @host.count

  begin
    @httpclient ||= connect
    request_body = Hash.new
    icinga_host = event.sprintf(@icinga_host)
    icinga_service = event.sprintf(@icinga_service)

    @uri.path = "/v1/actions/#{@action}"

    # Depending on the action we take, set either a filter in the request body or set a host and/or service in the
    # url parameters.
    case @action
      when 'remove-downtime', 'remove-comment'
        action_type = @action.split('-').last
        request_body['type'] = action_type.capitalize
        if @icinga_service
          request_body['filter'] = "host.name == \"#{icinga_host}\" && service.name == \"#{icinga_service}\" && #{action_type}.author == \"#{@action_config['author']}\""
        else
          request_body['filter'] = "host.name == \"#{icinga_host}\" && #{action_type}.author == \"#{@action_config['author']}\""
        end
      else
        if @icinga_service
          @uri.query = URI.encode_www_form({:service => "#{icinga_host}!#{icinga_service}"}).gsub('+','%20')
        else
          @uri.query = URI.encode_www_form({:host => icinga_host}).gsub('+','%20')
        end

        @action_config.each do |key, value|
          request_body[key] = event.sprintf(value)
        end
    end

    request = Net::HTTP::Post.new(@uri.request_uri)
    request.initialize_http_header({'Accept' => 'application/json'})
    request.basic_auth(@user, @password.value)
    request.body = LogStash::Json.dump(request_body)

    response = @httpclient.request(request)
    raise StandardError if response.code != '200'

    response_body = LogStash::Json.load(response.body)
    response_body['results'].each do |result|
      logging_data = {
          :host => "#{@uri.host}:#{@uri.port}",
          :request_path => request.path,
          :request_body => request.body,
          :result_code => result['code'].to_i,
          :result_status => result['status']
      }

      if result['code'] == 200
        @logger.debug("Action '#{@action}' succeeded", logging_data)
      else
        @logger.warn("Action '#{@action}' failed", logging_data)
      end
    end.empty? and begin
      @logger.debug('Returned result was epty', :response_body => response.body)
    end

  rescue Timeout::Error => e
    @logger.warn( "Request failed",
                  :host => @uri.host, :port => @uri.port,
                  :path => request.path, :body => request.body,
                  :error => e )
    # If a host is not reachable, try the same request with the next host in the list. Try each host host only once per
    # request.
    if not (@available_hosts -= 1).zero?
      @httpclient = connect
      @logger.info("Retrying request with '#{@uri.host}:#{@uri.port}'")
      retry
    end

  rescue Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError,
      Net::ProtocolError, OpenSSL::SSL::SSLError, StandardError => e
    @logger.warn("Request failed",
                   :host => @uri.host, :port => @uri.port,
                   :path => request.path, :body => request.body,
                   :error => e )

    if response
      @logger.warn("Response: ", :response_code => response.code, :response_body => response.body)

      if response.code == '404' && @create_object == true
        object = create_object(event)

        if object.code == '200'
          @logger.info("Retrying action on freshly created object", :action => @action)
          retry
        else
          @logger.warn("Failed to create object", :response_code => object.code, :response_body => object.body)
        end
      end
    end


  end
end
register() click to toggle source
# File lib/logstash/outputs/icinga.rb, line 283
def register
  validate_action_config
  @ssl_verify ? @ssl_verify_mode = OpenSSL::SSL::VERIFY_PEER : @ssl_verify_mode = OpenSSL::SSL::VERIFY_NONE
  @host_id = 0
end

Private Instance Methods

connect() click to toggle source
# File lib/logstash/outputs/icinga.rb, line 406
def connect
  @current_host, @current_port = @host[@host_id].split(':')
  @host_id = @host_id + 1 >= @host.length ? 0 : @host_id + 1

  if not @current_port
    @current_port = @port
  end

  @uri = URI.parse("https://#{@current_host}:#{@current_port}")

  http = Net::HTTP.new(@uri.host, @uri.port)
  http.use_ssl = true
  http.verify_mode = @ssl_verify_mode
  http.ca_file = @ca_file if @ca_file
  http.open_timeout = 2
  http.read_timeout = 5
  http
end
create_object(event) click to toggle source
# File lib/logstash/outputs/icinga.rb, line 425
def create_object(event)
  object_config = Hash.new
  object_config['templates'] = @object_templates
  object_config['attrs'] = Hash.new
  icinga_host = event.sprintf(@icinga_host)
  icinga_service = event.sprintf(@icinga_service)

  @object_attrs.each do |key, value|
    object_config['attrs'][key] = event.sprintf(value)
  end

  if @icinga_service
    @uri.path = '/v1/objects/services/' + URI.encode("#{icinga_host}!#{icinga_service}")
  else
    @uri.path = '/v1/objects/hosts/' + URI.encode(icinga_host)
  end

  @uri.query = URI.encode_www_form({:ignore_on_error => 1})

  request = Net::HTTP::Put.new(@uri.request_uri)
  request.initialize_http_header({'Accept' => 'application/json'})
  request.basic_auth(@user, @password.value)
  request.body = LogStash::Json.dump(object_config)

  @logger.info( "Creating Object",
                 :request_uri => @uri.request_uri,
                 :request_body => request.body,
                 :icinga_host => icinga_host, :icinga_service => icinga_service )

  response = @httpclient.request(request)
  response
end
validate_action_config() click to toggle source
# File lib/logstash/outputs/icinga.rb, line 392
def validate_action_config
  ACTION_CONFIG_FIELDS[@action].each do |field, settings|
    if settings['required'] && !@action_config.key?(field)
      @logger.error("Setting '#{field}' is required for action '#{@action}'")
    end
  end

  @action_config.each_key do |field|
    if not ACTION_CONFIG_FIELDS[@action].key?(field)
      @logger.warn("Unknown setting '#{field}' for action '#{action}'")
    end
  end
end