Class: Splunk::Context

Inherits:
Object
  • Object
show all
Defined in:
lib/splunk-sdk-ruby/context.rb

Overview

Class encapsulating a connection to a Splunk server.

This class is used for lower-level REST-based control of Splunk. For most use, you will want to use Context‘s subclass Service, which provides convenient access to Splunk’s various collections and entities.

To use the Context class, create a new Context with a hash of arguments giving the details of the connection, and call the login method on it:

context = Splunk::Context.new(:username => "admin",
                              :password => "changeme").()

Context#new takes a hash of keyword arguments. The keys it understands are:

  • :username - log in to Splunk as this user (no default)

  • :password - password to use when logging in (no default)

  • :host - Splunk host (e.g. “10.1.2.3”) (default: ‘localhost’)

  • :port - the Splunk management port (default: 8089)

  • :protocol - either :https or :http (default: :https)

  • :namespace - a Namespace object representing the default namespace for this context (default: DefaultNamespace)

  • :proxy An instance of Net::HTTP::Proxy to use as a proxy service.

  • :path_prefix A path prefix that should be prepended to all URLs.

  • :ssl_client_cert A OpenSSL::X509::Certificate object to use as a client certificate.

  • :ssl_client_key A OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object to use as a client key.

  • :token - a preauthenticated Splunk token (default: nil)

  • :basic - indicates if HTTP Basic Auth is going to be used (default: false)

If you specify a token, you need not specify a username or password, nor do you need to call the login method.

Context provides three other important methods:

  • connect opens a socket to the Splunk server.

  • request issues a request to the REST API.

  • restart restarts the Splunk server and handles waiting for it to come back up.

Direct Known Subclasses

Service

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(args) ⇒ Context

Returns a new instance of Context.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/splunk-sdk-ruby/context.rb', line 79

def initialize(args)
  @token = args.fetch(:token, nil)
  @scheme = args.fetch(:scheme, DEFAULT_SCHEME).intern()
  @host = args.fetch(:host, DEFAULT_HOST)
  @port = Integer(args.fetch(:port, DEFAULT_PORT))
  @username = args.fetch(:username, nil)
  @password = args.fetch(:password, nil)
  # Have to use Splunk::namespace() or we will call the
  # local accessor.
  @namespace = args.fetch(:namespace,
                          Splunk::namespace(:sharing => "default"))
  @proxy = args.fetch(:proxy, nil)
  @basic = args.fetch(:basic, false)
  @path_prefix = args.fetch(:path_prefix, DEFAULT_PATH_PREFIX)
  @ssl_client_cert = args.fetch(:ssl_client_cert, nil)
  @ssl_client_key = args.fetch(:ssl_client_key, nil)
end

Instance Attribute Details

#hostObject (readonly)

The host to connect to.

Defaults to “localhost”.

Returns: a String.



113
114
115
# File 'lib/splunk-sdk-ruby/context.rb', line 113

def host
  @host
end

#namespaceObject (readonly)

The default namespace used for requests on this Context.

The namespace must be a Namespace object. If a call to request is made without a namespace, this namespace is used for the request.

Defaults to DefaultNamespace.

Returns: a Namespace object.



162
163
164
# File 'lib/splunk-sdk-ruby/context.rb', line 162

def namespace
  @namespace
end

#passwordObject (readonly)

The password used to connect.

If a token is provided, this field can be nil.

Returns: a String or nil.



150
151
152
# File 'lib/splunk-sdk-ruby/context.rb', line 150

def password
  @password
end

#path_prefixObject (readonly)

The path prefix that should be prepended to all URLs. This is useful if the Splunk server is behind a reverse proxy server.

Defaults to empty string.



175
176
177
# File 'lib/splunk-sdk-ruby/context.rb', line 175

def path_prefix
  @path_prefix
end

#portObject (readonly)

The port to connect to.

Defaults to 8089.

Returns: an Integer.



122
123
124
# File 'lib/splunk-sdk-ruby/context.rb', line 122

def port
  @port
end

#proxyObject (readonly)

An instance of Net::HTTP::Proxy to use as a proxy service.



167
168
169
# File 'lib/splunk-sdk-ruby/context.rb', line 167

def proxy
  @proxy
end

#schemeObject (readonly)

The protocol used to connect.

Defaults to :https.

Returns: :http or :https.



104
105
106
# File 'lib/splunk-sdk-ruby/context.rb', line 104

def scheme
  @scheme
end

#ssl_client_certObject (readonly)

A OpenSSL::X509::Certificate object to use as a client certificate.



180
181
182
# File 'lib/splunk-sdk-ruby/context.rb', line 180

def ssl_client_cert
  @ssl_client_cert
end

#ssl_client_keyObject (readonly)

A OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object to use as a client key.



185
186
187
# File 'lib/splunk-sdk-ruby/context.rb', line 185

def ssl_client_key
  @ssl_client_key
end

#tokenObject (readonly)

The authentication token on Splunk.

If this Context is not logged in, this is nil. Otherwise it is a String that is passed with each request.

Returns: a String or nil.



132
133
134
# File 'lib/splunk-sdk-ruby/context.rb', line 132

def token
  @token
end

#usernameObject (readonly)

The username used to connect.

If a token is provided, this field can be nil.

Returns: a String or nil.



141
142
143
# File 'lib/splunk-sdk-ruby/context.rb', line 141

def username
  @username
end

Instance Method Details

#connectObject

Opens a TCP socket to the Splunk HTTP server.

If the scheme field of this Context is :https, this method returns an SSLSocket. If scheme is :http, a TCPSocket is returned. Due to design errors in Ruby’s standard library, these two do not share the same method names, so code written for HTTPS will not work for HTTP.

Returns: an SSLSocket or TCPSocket.



197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/splunk-sdk-ruby/context.rb', line 197

def connect()
  socket = TCPSocket.new(@host, @port)
  if scheme == :https
    ssl_context = OpenSSL::SSL::SSLContext.new()
    ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
    ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context)
    ssl_socket.sync_close = true
    ssl_socket.connect()
    return ssl_socket
  else
    return socket
  end
end

#loginObject

Logs into Splunk and set the token field on this Context.

The login method assumes that the Context has a username and password set. You cannot pass them as arguments to this method. On a successful login, the token field of the Context is set to the token returned by Splunk, and all further requests to the server will send this token.

If this Context already has a token that is not nil, it is already logged in, and this method is a nop.

Raises SplunkHTTPError if there is a problem logging in.

Returns: the Context.



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/splunk-sdk-ruby/context.rb', line 226

def ()
  if @token # If we're already logged in, this method is a nop.
    return self
  end
  if @basic # We're using basic authentication, thus making this a nop
    return self
  end
  response = request(:namespace => Splunk::namespace(:sharing => "default"),
                     :method => :POST,
                     :resource => ["auth", "login"],
                     :query => {},
                     :headers => {},
                     :body => {:username=>@username, :password=>@password})
  # The response looks like:
  # <response>
  # <sessionKey>da950729652f8255c230afe37bdf8b97</sessionKey>
  # </response>
  @token = Splunk::text_at_xpath("//sessionKey", response.body)

  self
end

#logoutObject

Logs out of Splunk.

This sets the @token attribute to nil.

Returns: the Context.



255
256
257
258
# File 'lib/splunk-sdk-ruby/context.rb', line 255

def logout()
  @token = nil
  self
end

#request(args) ⇒ Object

Issues an HTTP(S) request to the Splunk instance.

The request method does not take a URL. Instead, it takes a hash of optional arguments specifying an action in the REST API. This avoids the problem knowing whether a given piece of data is URL encoded or not.

The arguments are:

  • method: The HTTP method to use (one of :GET, :POST, or :DELETE; default: :GET).

  • namespace: The namespace to request a resource from Splunk in. Must be a Namespace object. (default: the value of @namespace on this Context)

  • resource: An array of strings specifying the components of the path to the resource after the namespace. The strings should not be URL encoded, as that will be handled by request. (default: [])

  • query: A hash containing the values to be encoded as the query (the part following “?”) in the URL. Nothing should be URL encoded as request will do the encoding. If you need to pass multiple values for the same key, insert them as an Array as the value of their key into the Hash, and they will be properly encoded as a sequence of entries with the same key. (default: {})

  • headers: A hash containing the values to be encoded as headers. None should be URL encoded, and the request method will automatically add headers for User-Agent and Splunk authentication for you. Keys must be unique, so the values must be strings, not arrays, unlike for query. (default: {})

  • body: Either a hash to be encoded as the body of a POST request, or a string to be used as the raw, already encoded body of a POST request. If you pass a hash, you can pass multiple values for the same key by encoding them as an Array, which will be properly set as multiple instances of the same key in the POST body. Nothing in the hash should be URL encoded, as request will handle all such encoding. (default: {})

If Splunk responds with an HTTP code 2xx, the request method returns an HTTP response object (the import methods of which are code, message, and body, and each to enumerate over the response headers). If the HTTP code is not 2xx, request raises a SplunkHTTPError.

Examples:

c = Splunk::connect(username="admin", password="changeme")
# Get a list of the indexes in this Splunk instance.
c.request(:namespace => Splunk::namespace(),
          :resource => ["data", "indexes"])
# Create a new index called "my_new_index"
c.request(:method => :POST,
          :resource => ["data", "indexes"],
          :body => {"name", "my_new_index"})


313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/splunk-sdk-ruby/context.rb', line 313

def request(args)
  method = args.fetch(:method, :GET)
  scheme = @scheme
  host = @host
  port = @port
  namespace = args.fetch(:namespace, @namespace)
  resource = args.fetch(:resource, [])
  query = args.fetch(:query, {})
  headers = args.fetch(:headers, {})
  body = args.fetch(:body, {})

  if method != :GET && method != :POST && method != :DELETE
    raise ArgumentError.new("Method must be one of :GET, :POST, or " +
                                ":DELETE, found: #{method}")
  end

  if scheme && scheme != :http && scheme != :https
    raise ArgumentError.new("Scheme must be one of :http or :https, " +
                                "found: #{scheme}")
  end

  if port && !port.is_a?(Integer)
    raise ArgumentError.new("Port must be an Integer, found: #{port}")
  end

  if !namespace.is_a?(Namespace)
    raise ArgumentError.new("Namespace must be a Namespace, " +
                                "found: #{namespace}")
  end

  # Construct the URL for the request.
  url = ""
  url << "#{(scheme || @scheme).to_s}://"
  url << "#{host || @host}:#{(port || @port).to_s}/"
  url << @path_prefix
  # You would think that the second argument to URI::encode would be unnecessary
  # for it to actually escape URI reserved characters. You would be wrong. It does
  # have to be there, or URI::encode doesn't actually escape any characters.
  url << (namespace.to_path_fragment() + resource).
      map {|fragment| URI::encode(fragment, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))}.
      join("/")

  return request_by_url(:url => url,
                        :method => method,
                        :query => query,
                        :headers => headers,
                        :body => body)
end

#request_by_url(args) ⇒ Object

Makes a request to the Splunk server given a prebuilt URL.

Unless you are using a URL that was returned by the Splunk server as part of an Atom feed, you should prefer the request method, which has much clearer semantics.

The request_by_url method takes a hash of arguments. The recognized arguments are:

  • :url: (a URI object or a String) The URL, including authority, to make a request to.

  • :method: (:GET, :POST, or :DELETE) The HTTP method to use.

  • query: A hash containing the values to be encoded as the query (the part following “?”) in the URL. Nothing should be URL encoded as request will do the encoding. If you need to pass multiple values for the same key, insert them as an Array as the value of their key into the Hash, and they will be properly encoded as a sequence of entries with the same key. (default: {})

  • headers: A hash containing the values to be encoded as headers. None should be URL encoded, and the request method will automatically add headers for User-Agent and Splunk authentication for you. Keys must be unique, so the values must be strings, not arrays, unlike for query. (default: {})

  • body: Either a hash to be encoded as the body of a POST request, or a string to be used as the raw, already encoded body of a POST request. If you pass a hash, you can pass multiple values for the same key by encoding them as an Array, which will be properly set as multiple instances of the same key in the POST body. Nothing in the hash should be URL encoded, as request will handle all such encoding. (default: {})

If Splunk responds with an HTTP code 2xx, the request_by_url method returns an HTTP response object (the import methods of which are code, message, and body, and each to enumerate over the response headers). If the HTTP code is not 2xx, the request_by_url method raises a SplunkHTTPError.



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
# File 'lib/splunk-sdk-ruby/context.rb', line 400

def request_by_url(args)
  url = args.fetch(:url)
  if url.is_a?(String)
    url = URI(url)
  end
  method = args.fetch(:method, :GET)
  query = args.fetch(:query, {})
  headers = args.fetch(:headers, {})
  body = args.fetch(:body, {})

  if !query.empty?
    url.query = URI.encode_www_form(query)
  end

  if method == :GET
    request = Net::HTTP::Get.new(url.request_uri)
  elsif method == :POST
    request = Net::HTTP::Post.new(url.request_uri)
  elsif method == :DELETE
    request = Net::HTTP::Delete.new(url.request_uri)
  end

  # Headers
  request["User-Agent"] = "splunk-sdk-ruby/#{VERSION}"
  request["Authorization"] = "Splunk #{@token}" if @token

  # basic authentication supercedes Splunk authentication
  if @basic then
    request.basic_auth(@username, @password)
  end
  headers.each_entry do |key, value|
    request[key] = value
  end

  # Body
  if body.is_a?(String)
    # This case exists only for submitting an event to an index via HTTP.
    request.body = body
  else
    request.body = URI.encode_www_form(body)
  end

  # Issue the request.
  url_hostname = url.host
  if url.respond_to?(:hostname)
    url_hostname = url.hostname
  end
  response = (@proxy || Net::HTTP)::start(
      url_hostname, url.port,
      :use_ssl => url.scheme == 'https',
      # We don't support certificates.
      :verify_mode => OpenSSL::SSL::VERIFY_NONE,
      :cert => @ssl_client_cert,
      :key => @ssl_client_key
  ) do |http|
    http.request(request)
  end

  # Handle any errors.
  if !response.is_a?(Net::HTTPSuccess)
    raise SplunkHTTPError.new(response)
  else
    return response
  end
end

#restart(timeout = nil) ⇒ Object

Restarts this Splunk instance.

The restart method may be called with an optional timeout. If you pass a timeout, restart will wait up to that number of seconds for the server to come back up before returning. If restart did not time out, it leaves the Context logged in when it returns.

If the timeout is, omitted, the restart method returns immediately, and you will have to ascertain if Splunk has come back up yourself, for example with code like:

context = Context.new(...).login()
context.restart()
Timeout::timeout(timeout) do
    while !context.server_accepting_connections? ||
            context.server_requires_restart?
        sleep(0.3)
    end
end

Returns: this Context.



489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
# File 'lib/splunk-sdk-ruby/context.rb', line 489

def restart(timeout=nil)
  # Set a message saying that restart is required. Otherwise we have no
  # way of knowing if Splunk has actually gone down for a restart or not.
  request(:method => :POST,
          :namespace => Splunk::namespace(:sharing => "default"),
          :resource => ["messages"],
          :body => {"name" => "restart_required",
                    "value" => "Message set by restart method" +
                        " of the Splunk Ruby SDK"})

  # Make the actual restart request.
  request(:method => :POST,
          :resource => ["server", "control", "restart"])

  # Clear our old token, which will no longer work after the restart.
  logout()

  # If +timeout+ is +nil+, return immediately. If timeout is a positive
  # integer, wait for +timeout+ seconds for the server to come back up.
  if !timeout.nil?
    Timeout::timeout(timeout) do
      while !server_accepting_connections? || server_requires_restart?
        sleep(0.3)
      end
    end
  end

  # Return the +Context+.
  self
end

#server_accepting_connections?Boolean

Is the Splunk server accepting connections?

Returns true if the Splunk server is up and accepting REST API connections; false otherwise.

Returns:

  • (Boolean)


526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
# File 'lib/splunk-sdk-ruby/context.rb', line 526

def server_accepting_connections?()
  begin
    # Can't use login, since it has short circuits
    # when @token != nil on the Context. Instead, make
    # a request directly.
    request(:resource => ["data", "indexes"])
  rescue Errno::ECONNREFUSED, EOFError, Errno::ECONNRESET
    return false
  rescue OpenSSL::SSL::SSLError
    return false
  rescue SplunkHTTPError
    # Splunk is up, because it responded with a proper HTTP error
    # that our SplunkHTTPError parser understood.
    return true
  else
    # Or the request worked, so we know that Splunk is up.
    return true
  end
end

#server_requires_restart?Boolean

Is the Splunk server in a state requiring a restart?

Returns true if the Splunk server is down (equivalent to server_accepting_connections?), or if there is a restart_required message on the server; false otherwise.

Returns:

  • (Boolean)


553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# File 'lib/splunk-sdk-ruby/context.rb', line 553

def server_requires_restart?()
  begin # We must have two layers of rescue, because the login in the
        # SplunkHTTPError rescue can also throw Errno::ECONNREFUSED.
    begin
      request(:resource => ["messages", "restart_required"])
      return true
    rescue SplunkHTTPError => err
      if err.code == 401
        # The messages endpoint requires authentication.
        logout()
        ()
        return server_requires_restart?()
      elsif err.code == 404
        return false
      else
        raise err
      end
    end
  rescue Errno::ECONNREFUSED, EOFError, Errno::ECONNRESET
    return true
  end
end