require 'forwardable' require 'openssl' require 'papertrail/http_client' require 'papertrail/search_query' module Papertrail class Connection extend Forwardable attr_reader :connection def_delegators :@connection, :get, :put, :post, :delete def initialize(options) ssl_options = { :verify => options.fetch(:verify_ssl) { OpenSSL::SSL::VERIFY_PEER } } unless (options[:username] && options[:password]) || options[:token] raise ArgumentError, "Must provide a username and password or a token" end # Make Ubuntu OpenSSL work # # From: https://bugs.launchpad.net/ubuntu/+source/openssl/+bug/396818 # "[OpenSSL] does not presume to select a set of CAs by default." if File.file?('/etc/ssl/certs/ca-certificates.crt') ssl_options[:ca_file] = '/etc/ssl/certs/ca-certificates.crt' end @connection = Papertrail::HttpClient.new(ssl_options).tap do |conn| if options[:username] && options[:password] conn.basic_auth(options[:username], options[:password]) else conn.token_auth(options[:token]) end end end def find_id_for_source(name) response = @connection.get('systems.json') find_id_for_item(response.body, name) end def find_id_for_group(name) response = @connection.get('groups.json') find_id_for_item(response.body, name) end def find_search(name, group_id = nil) response = @connection.get('searches.json') candidates = find_items_by_name(response.body, name) return nil if candidates.empty? candidates.each do |item| if !group_id || group_id == item['group_id'] return item end end return candidates.first end def find_items_by_name(items, name_wanted) results = [] items.each do |item| results << item if item['name'] == name_wanted end items.each do |item| results << item if item['name'] =~ /#{Regexp.escape(name_wanted)}/i end results end def find_item_by_name(items, name_wanted) find_items_by_name(items, name_wanted).first end def find_id_for_item(items, name_wanted) item = find_item_by_name(items, name_wanted) if item return item['id'] end return nil end def create_group(name, system_wildcard = nil) group = { :group => { :name => name } } if system_wildcard group[:group][:system_wildcard] = system_wildcard end @connection.post("groups.json", group) end def show_group(name) if id = find_id_for_group(name) @connection.get("groups/#{id}.json").body end end def show_source(name) if id = find_id_for_source(name) @connection.get("systems/#{id}.json").body end end def join_group(source_name, group_name) source_id = find_id_for_source(source_name) group_id = find_id_for_group(group_name) if source_id && group_id @connection.post("systems/#{source_id}/join.json", :group_id => group_id) end end def leave_group(source_name, group_name) source_id = find_id_for_source(source_name) group_id = find_id_for_group(group_name) if source_id && group_id @connection.post("systems/#{source_id}/leave.json", :group_id => group_id) end end def register_source(name, *args) options = args.last.is_a?(Hash) ? args.pop.dup : {} if ip_address = args.shift options[:ip_address] = ip_address end if hostname = args.shift options[:hostname] = hostname end request = { :system => { :name => name } } if options[:ip_address] request[:system][:ip_address] = options[:ip_address] end if options[:hostname] request[:system][:hostname] = options[:hostname] end if options[:destination_port] request[:destination_port] = options[:destination_port] end @connection.post("systems.json", request) end def unregister_source(name) if source_id = find_id_for_source(name) @connection.delete("systems/#{source_id}.json") end end def query(query = nil, options = {}) Papertrail::SearchQuery.new(self, query, options) end def each_event(query_term = nil, options = {}, &block) # If there was no query but there were options, shuffle around # the parameters if query_term.is_a?(Hash) options, query_term = query_term, nil end # Remove all of the options that shouldn't be in each query options = options.dup min_id = options.delete(:min_id) max_id = options.delete(:max_id) min_time = options.delete(:min_time) max_time = options.delete(:max_time) # Figure out where to start querying if min_id search_results = query(query_term, options.merge(:min_id => min_id, :tail => false)).search elsif min_time search_results = query(query_term, options.merge(:min_time => min_time.to_i, :tail => false)).search else raise ArgumentError, "Either :min_id or :min_time must be specified" end # Start processing events loop do search_results.events.each do |event| # If we've found an event beyond what we were looking for, we're done if max_time && event.received_at > max_time break end if max_id && event.id > max_id break end block.call(event) end # If we've found the end of what we're looking for, we're done if max_time && search_results.max_time_at > max_time break end if max_id && search_results.max_id > max_id break end # If we've reached the most current log message, we're done if search_results.reached_end? break end # Perform the next search search_results = query(query_term, options.merge(:min_id => search_results.max_id, :tail => false)).search end nil end end end