splunk-sdk-ruby 0.1.0 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.md +28 -0
- data/README.md +5 -17
- data/examples/6_work_with_modular_inputs.rb +96 -0
- data/examples/example_modular_inputs.spl +0 -0
- data/lib/splunk-sdk-ruby/atomfeed.rb +1 -1
- data/lib/splunk-sdk-ruby/collection.rb +112 -109
- data/lib/splunk-sdk-ruby/collection/input_kinds.rb +136 -0
- data/lib/splunk-sdk-ruby/collection/jobs.rb +9 -1
- data/lib/splunk-sdk-ruby/context.rb +7 -4
- data/lib/splunk-sdk-ruby/entity.rb +37 -29
- data/lib/splunk-sdk-ruby/entity/job.rb +12 -4
- data/lib/splunk-sdk-ruby/entity/modular_input_kind.rb +47 -0
- data/lib/splunk-sdk-ruby/resultsreader.rb +71 -17
- data/lib/splunk-sdk-ruby/service.rb +37 -5
- data/lib/splunk-sdk-ruby/version.rb +1 -1
- data/lib/splunk-sdk-ruby/xml_shim.rb +11 -0
- data/splunk-sdk-ruby.gemspec +3 -2
- data/test/data/atom_test_data.json +457 -0
- data/test/{export_test_data.json → data/export_test_data.json} +302 -182
- data/test/data/resultsreader_test_data.json +1693 -0
- data/test/test_atomfeed.rb +20 -9
- data/test/test_configuration_file.rb +22 -0
- data/test/test_context.rb +1 -1
- data/test/test_helper.rb +27 -15
- data/test/test_index.rb +2 -3
- data/test/test_inputs.rb +211 -0
- data/test/test_jobs.rb +73 -2
- data/test/test_modular_input_kinds.rb +46 -0
- data/test/test_restarts.rb +10 -0
- data/test/test_resultsreader.rb +22 -7
- data/test/test_users.rb +8 -0
- data/test/test_xml_shim.rb +10 -0
- metadata +104 -101
- data/test/atom_test_data.rb +0 -472
- data/test/resultsreader_test_data.json +0 -1119
@@ -0,0 +1,136 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2011-2013 Splunk, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"): you may
|
5
|
+
# not use this file except in compliance with the License. You may obtain
|
6
|
+
# a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
12
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
13
|
+
# License for the specific language governing permissions and limitations
|
14
|
+
# under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require_relative '../collection'
|
18
|
+
|
19
|
+
##
|
20
|
+
# Provide a class representing a collection of input kinds.
|
21
|
+
#
|
22
|
+
module Splunk
|
23
|
+
##
|
24
|
+
# A collection of input types.
|
25
|
+
#
|
26
|
+
# Inputs in the Splunk REST API are arranged in what looks like a
|
27
|
+
# directory structure, as in
|
28
|
+
#
|
29
|
+
# monitor/
|
30
|
+
# tcp/
|
31
|
+
# cooked/
|
32
|
+
# raw/
|
33
|
+
# udp/
|
34
|
+
#
|
35
|
+
# You get the top level directory by calling +inputs+ on your +Service+.
|
36
|
+
# Then you can use it as if it were a Hash. If you fetch an entry that has
|
37
|
+
# subtypes, such as +tcp+, you get another +InputKinds+ containing the types
|
38
|
+
# in that entry. If you fetch an entry that doesn't have subtypes, such as
|
39
|
+
# "udp", then you get an +Inputs+ object (a subclass of +Collection+)
|
40
|
+
# containing specific inputs.
|
41
|
+
#
|
42
|
+
# *Example*:
|
43
|
+
#
|
44
|
+
# # Returns an InputKinds collection
|
45
|
+
# tcp_inputs = service.inputs["tcp"]
|
46
|
+
# tcp_inputs.has_key?("raw") # ==> true
|
47
|
+
# tcp_inputs.has_key?("cooked") # ==> true
|
48
|
+
#
|
49
|
+
# # A read only collection of all the inputs in Splunk.
|
50
|
+
# service.inputs["all"]
|
51
|
+
#
|
52
|
+
# # An Inputs object containing all the UDP inputs in Splunk.
|
53
|
+
# service.inputs["udp"]
|
54
|
+
#
|
55
|
+
class InputKinds < ReadOnlyCollection
|
56
|
+
def fetch(name, namespace=nil)
|
57
|
+
request_args = {:resource => @resource + [name]}
|
58
|
+
if not namespace.nil?
|
59
|
+
request_args[:namespace] = namespace
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
response = @service.request(request_args)
|
64
|
+
rescue SplunkHTTPError => err
|
65
|
+
if err.code == 404
|
66
|
+
return nil
|
67
|
+
else
|
68
|
+
raise err
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
feed = AtomFeed.new(response.body)
|
73
|
+
if feed.metadata["links"].has_key?("create")
|
74
|
+
Inputs.new(@service, resource + [name])
|
75
|
+
elsif name == "all"
|
76
|
+
ReadOnlyCollection.new(@service, resource + [name])
|
77
|
+
else
|
78
|
+
InputKinds.new(@service, resource + [name])
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
##
|
84
|
+
# A collection of specific inputs.
|
85
|
+
#
|
86
|
+
class Inputs < Collection
|
87
|
+
def initialize(service, resource)
|
88
|
+
super(service, resource)
|
89
|
+
@always_fetch = true
|
90
|
+
end
|
91
|
+
|
92
|
+
def create(name, args={})
|
93
|
+
body_args = args.clone()
|
94
|
+
body_args["name"] = name
|
95
|
+
|
96
|
+
request_args = {
|
97
|
+
:method => :POST,
|
98
|
+
:resource => @resource,
|
99
|
+
:body => body_args
|
100
|
+
}
|
101
|
+
if args.has_key?(:namespace)
|
102
|
+
request_args[:namespace] = body_args.delete(:namespace)
|
103
|
+
end
|
104
|
+
|
105
|
+
@service.request(request_args)
|
106
|
+
|
107
|
+
# If we have created a oneshot input, no actual entity
|
108
|
+
# is created. We return nil here in that case.
|
109
|
+
if @resource == ["data", "inputs", "oneshot"]
|
110
|
+
return nil
|
111
|
+
end
|
112
|
+
|
113
|
+
# TCP and UDP inputs have a key restrictToHost. If it is set
|
114
|
+
# then they are created with hostname:port as their resource
|
115
|
+
# instead of just port, and we must adjust our behavior
|
116
|
+
# accordingly.
|
117
|
+
if args.has_key?(:restrictToHost)
|
118
|
+
name = args[:restrictToHost] + ":" + name
|
119
|
+
end
|
120
|
+
|
121
|
+
fetch_args = {:method => :GET,
|
122
|
+
:resource => @resource + [name]}
|
123
|
+
if args.has_key?(:namespace)
|
124
|
+
fetch_args[:namespace] = args[:namespace]
|
125
|
+
end
|
126
|
+
response = @service.request(fetch_args)
|
127
|
+
|
128
|
+
feed = AtomFeed.new(response.body)
|
129
|
+
raise StandardError.new("No entities returned") if feed.entries.empty?
|
130
|
+
entity = atom_entry_to_entity(feed.entries[0])
|
131
|
+
raise StandardError.new("Found nil entity.") if entity.nil?
|
132
|
+
return entity
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
@@ -51,7 +51,7 @@ module Splunk
|
|
51
51
|
# Creates an asynchronous search job.
|
52
52
|
#
|
53
53
|
# The search job requires a _query_, and takes a hash of other, optional
|
54
|
-
# arguments, which are documented in the {Splunk REST documentation}[http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI/RESTsearch#search.2Fjobs
|
54
|
+
# arguments, which are documented in the {Splunk REST documentation}[http://docs.splunk.com/Documentation/Splunk/latest/RESTAPI/RESTsearch#search.2Fjobs].
|
55
55
|
#
|
56
56
|
def create(query, args={})
|
57
57
|
if args.has_key?(:exec_mode)
|
@@ -81,6 +81,10 @@ module Splunk
|
|
81
81
|
def create_oneshot(query, args={})
|
82
82
|
args[:search] = query
|
83
83
|
args[:exec_mode] = 'oneshot'
|
84
|
+
# Suppress segmentation (<sg> tags in the XML response) by default:
|
85
|
+
if !args.has_key?(:segmentation)
|
86
|
+
args[:segmentation] = "none"
|
87
|
+
end
|
84
88
|
response = @service.request(:method => :POST,
|
85
89
|
:resource => @resource,
|
86
90
|
:body => args)
|
@@ -100,6 +104,10 @@ module Splunk
|
|
100
104
|
#
|
101
105
|
def create_export(query, args={})
|
102
106
|
args["search"] = query
|
107
|
+
# Suppress segmentation (<sg> tags in the XML response) by default:
|
108
|
+
if !args.has_key?(:segmentation)
|
109
|
+
args[:segmentation] = "none"
|
110
|
+
end
|
103
111
|
response = @service.request(:method => :GET,
|
104
112
|
:resource => @resource + ["export"],
|
105
113
|
:query => args)
|
@@ -232,13 +232,13 @@ module Splunk
|
|
232
232
|
# * +method+: The HTTP method to use (one of +:GET+, +:POST+, or +:DELETE+;
|
233
233
|
# default: +:GET+).
|
234
234
|
# * +namespace+: The namespace to request a resource from Splunk in. Must
|
235
|
-
# be a +Namespace+ object. (default: the value of
|
235
|
+
# be a +Namespace+ object. (default: the value of @+namespace+ on
|
236
236
|
# this +Context+)
|
237
237
|
# * +resource+: An array of strings specifying the components of the path
|
238
238
|
# to the resource after the namespace. The strings should not be URL
|
239
239
|
# encoded, as that will be handled by +request+. (default: [])
|
240
240
|
# * +query+: A hash containing the values to be encoded as
|
241
|
-
# the query (the part following
|
241
|
+
# the query (the part following "?") in the URL. Nothing should be URL
|
242
242
|
# encoded as +request+ will do the encoding. If you need to pass multiple
|
243
243
|
# values for the same key, insert them as an Array as the value of their
|
244
244
|
# key into the Hash, and they will be properly encoded as a sequence of
|
@@ -332,7 +332,7 @@ module Splunk
|
|
332
332
|
# make a request to.
|
333
333
|
# * +:method+: (+:GET+, +:POST+, or +:DELETE+) The HTTP method to use.
|
334
334
|
# * +query+: A hash containing the values to be encoded as
|
335
|
-
# the query (the part following
|
335
|
+
# the query (the part following "?") in the URL. Nothing should be URL
|
336
336
|
# encoded as +request+ will do the encoding. If you need to pass multiple
|
337
337
|
# values for the same key, insert them as an +Array+ as the value of their
|
338
338
|
# key into the Hash, and they will be properly encoded as a sequence of
|
@@ -445,7 +445,8 @@ module Splunk
|
|
445
445
|
" of the Splunk Ruby SDK"})
|
446
446
|
|
447
447
|
# Make the actual restart request.
|
448
|
-
request(:
|
448
|
+
request(:method => :POST,
|
449
|
+
:resource => ["server", "control", "restart"])
|
449
450
|
|
450
451
|
# Clear our old token, which will no longer work after the restart.
|
451
452
|
logout()
|
@@ -478,6 +479,8 @@ module Splunk
|
|
478
479
|
request(:resource => ["data", "indexes"])
|
479
480
|
rescue Errno::ECONNREFUSED, EOFError, Errno::ECONNRESET
|
480
481
|
return false
|
482
|
+
rescue OpenSSL::SSL::SSLError
|
483
|
+
return false
|
481
484
|
rescue SplunkHTTPError
|
482
485
|
# Splunk is up, because it responded with a proper HTTP error
|
483
486
|
# that our SplunkHTTPError parser understood.
|
@@ -21,24 +21,12 @@ require_relative 'synonyms'
|
|
21
21
|
|
22
22
|
module Splunk
|
23
23
|
##
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# +Entity+ objects represent individual items such as indexes, users, roles,
|
27
|
-
# etc. They are usually contained within +Collection+ objects.
|
24
|
+
# ReadOnlyEntity represents entities that can be read, but not created or
|
25
|
+
# updated, via the REST API. The canonical example is a modular input kind.
|
28
26
|
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
# the fields containing the +Entity+'s state, such as the capabilities of
|
33
|
-
# a role or whether an app should check for updates, are accessible with
|
34
|
-
# the [] operator (for instance, +role+["+capabilities+"] or
|
35
|
-
# +app+["+check_for_updates+"]).
|
36
|
-
#
|
37
|
-
# +Entity+ objects cache their state, so each lookup of a field does not
|
38
|
-
# make a roundtrip to the server. The state may be refreshed by calling
|
39
|
-
# the +refresh+ method on the +Entity+.
|
40
|
-
#
|
41
|
-
class Entity
|
27
|
+
class ReadOnlyEntity
|
28
|
+
# ReadOnlyEntity was factored out of Entity to avoid having to add
|
29
|
+
# special behavior to modular input kinds.
|
42
30
|
extend Synonyms
|
43
31
|
|
44
32
|
def initialize(service, namespace, resource, name, state=nil) # :nodoc:
|
@@ -86,17 +74,6 @@ module Splunk
|
|
86
74
|
#
|
87
75
|
attr_reader :service
|
88
76
|
|
89
|
-
##
|
90
|
-
# Deletes this entity from the server.
|
91
|
-
#
|
92
|
-
# Returns: +nil+.
|
93
|
-
#
|
94
|
-
def delete()
|
95
|
-
@service.request({:method => :DELETE,
|
96
|
-
:namespace => @namespace,
|
97
|
-
:resource => @resource + [name]})
|
98
|
-
end
|
99
|
-
|
100
77
|
##
|
101
78
|
# Fetches the field _key_ on this entity.
|
102
79
|
#
|
@@ -138,7 +115,7 @@ module Splunk
|
|
138
115
|
# +Entity+. If you specify one or more +Strings+ or +Arrays+ of +Strings+,
|
139
116
|
# all the keys specified in the arguments will be returned in the +Hash+.
|
140
117
|
#
|
141
|
-
# Returns: a +Hash+ with +Strings+ as keys, and +Strings+ or +Hashes+ or
|
118
|
+
# Returns: a +Hash+ with +Strings+ as keys, and +Strings+ or +Hashes+ or
|
142
119
|
# +Arrays+ as values.
|
143
120
|
#
|
144
121
|
def read(*field_list)
|
@@ -187,6 +164,37 @@ module Splunk
|
|
187
164
|
@state = feed.entries[0]
|
188
165
|
self
|
189
166
|
end
|
167
|
+
end
|
168
|
+
|
169
|
+
##
|
170
|
+
# Class representing individual entities in Splunk.
|
171
|
+
#
|
172
|
+
# +Entity+ objects represent individual items such as indexes, users, roles,
|
173
|
+
# etc. They are usually contained within +Collection+ objects.
|
174
|
+
#
|
175
|
+
# The basic, identifying information for an +Entity+ (name, namespace, path
|
176
|
+
# of the collection containing entity, and the service it's on) is all
|
177
|
+
# accessible via getters (+name+, +namespace+, +resource+, +service+). All
|
178
|
+
# the fields containing the +Entity+'s state, such as the capabilities of
|
179
|
+
# a role or whether an app should check for updates, are accessible with
|
180
|
+
# the [] operator (for instance, +role+["+capabilities+"] or
|
181
|
+
# +app+["+check_for_updates+"]).
|
182
|
+
#
|
183
|
+
# +Entity+ objects cache their state, so each lookup of a field does not
|
184
|
+
# make a roundtrip to the server. The state may be refreshed by calling
|
185
|
+
# the +refresh+ method on the +Entity+.
|
186
|
+
#
|
187
|
+
class Entity < ReadOnlyEntity
|
188
|
+
##
|
189
|
+
# Deletes this entity from the server.
|
190
|
+
#
|
191
|
+
# Returns: +nil+.
|
192
|
+
#
|
193
|
+
def delete()
|
194
|
+
@service.request({:method => :DELETE,
|
195
|
+
:namespace => @namespace,
|
196
|
+
:resource => @resource + [name]})
|
197
|
+
end
|
190
198
|
|
191
199
|
##
|
192
200
|
# Updates the values on the Entity specified in the arguments.
|
@@ -105,10 +105,14 @@ module Splunk
|
|
105
105
|
# Returns: a stream that can be read with +ResultsReader+.
|
106
106
|
#
|
107
107
|
def events(args={})
|
108
|
+
# Suppress segmentation (<sg> tags in the XML response) by default:
|
109
|
+
if !args.has_key?(:segmentation)
|
110
|
+
args[:segmentation] = "none"
|
111
|
+
end
|
108
112
|
response = @service.request(
|
109
113
|
:method => :GET,
|
110
114
|
:resource => @resource + [sid, "events"],
|
111
|
-
:
|
115
|
+
:query => args)
|
112
116
|
return response.body
|
113
117
|
end
|
114
118
|
|
@@ -192,10 +196,14 @@ module Splunk
|
|
192
196
|
# Returns: a stream readable by +ResultsReader+.
|
193
197
|
#
|
194
198
|
def preview(args={})
|
199
|
+
# Suppress segmentation (<sg> tags in the XML response) by default:
|
200
|
+
if !args.has_key?(:segmentation)
|
201
|
+
args[:segmentation] = "none"
|
202
|
+
end
|
195
203
|
response = @service.request(:method => :GET,
|
196
204
|
:resource => @resource +
|
197
205
|
[sid, "results_preview"],
|
198
|
-
:
|
206
|
+
:query => args)
|
199
207
|
return response.body
|
200
208
|
end
|
201
209
|
|
@@ -247,7 +255,7 @@ module Splunk
|
|
247
255
|
end
|
248
256
|
|
249
257
|
##
|
250
|
-
# Return the +Job's
|
258
|
+
# Return the +Job+'s search id.
|
251
259
|
#
|
252
260
|
# Returns: a +String+.
|
253
261
|
#
|
@@ -268,7 +276,7 @@ module Splunk
|
|
268
276
|
# duration), and +:time+ (a string representing the bucket's time in human
|
269
277
|
# readable form).
|
270
278
|
#
|
271
|
-
# Returns: an +Array+ of
|
279
|
+
# Returns: an +Array+ of Hashes describing each time bucket.
|
272
280
|
#
|
273
281
|
def timeline(args={})
|
274
282
|
response = @service.request(:resource => @resource + [sid, "timeline"],
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2011-2013 Splunk, Inc.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"): you may
|
5
|
+
# not use this file except in compliance with the License. You may obtain
|
6
|
+
# a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
12
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
13
|
+
# License for the specific language governing permissions and limitations
|
14
|
+
# under the License.
|
15
|
+
#++
|
16
|
+
|
17
|
+
require_relative '../entity'
|
18
|
+
|
19
|
+
module Splunk
|
20
|
+
##
|
21
|
+
# Class representing a family of user defined inputs on the server.
|
22
|
+
#
|
23
|
+
# Modular input kinds define new, first-class input kinds on the server.
|
24
|
+
# This endpoint provides access to a list of the arguments and parameters
|
25
|
+
# of the various kinds defined. The actual inputs of these kinds can be
|
26
|
+
# accessed via the Serve#inputs method.
|
27
|
+
#
|
28
|
+
# Modular input kinds are read only, so there is no update or delete method
|
29
|
+
# on this class.
|
30
|
+
#
|
31
|
+
class ModularInputKind < ReadOnlyEntity
|
32
|
+
##
|
33
|
+
# Return a Hash of all the arguments support by this modular input kind.
|
34
|
+
#
|
35
|
+
# The keys in the Hash are the names of the arguments. The values are
|
36
|
+
# additional Hashes giving the metadata about each argument. The possible
|
37
|
+
# keys in those Hashes are +"title"+, +"description"+,
|
38
|
+
# +"required_on_create``+, +"required_on_edit"+, +"data_type"+. Each value is
|
39
|
+
# a string. It should be one of +"true"+ or +"false"+ for
|
40
|
+
# +"required_on_create"+ and +"required_on_edit"+, and one of +"boolean"+,
|
41
|
+
# +"string"+, or +"number"+ for +"data_type"+.
|
42
|
+
#
|
43
|
+
def arguments
|
44
|
+
@state["content"]["endpoint"]["args"]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -98,6 +98,18 @@ module Splunk
|
|
98
98
|
# in this set, in the order they should be displayed (if you're going
|
99
99
|
# to make a table or the like)
|
100
100
|
#
|
101
|
+
# The values yielded by calling +each+ and similar methods on +ResultsReader+
|
102
|
+
# are of class +Event+, which is a subclass of +Hash+ with one extra method,
|
103
|
+
# +segmented_raw+. The fields of the event are available as values in the hash,
|
104
|
+
# with no escaped characters and no XML tags. The +_raw+ field, however, is
|
105
|
+
# returned with extra XML specifying how terms should be highlighted for
|
106
|
+
# display, and this full XML form is available by called the +segmented_raw+
|
107
|
+
# method. The XML returned looks something like:
|
108
|
+
#
|
109
|
+
# "<v xml:space=\"preserve\" trunc=\"0\">127.0.0.1 - admin
|
110
|
+
# [11/Feb/2013:10:42:49.790 -0800] \"POST /services/search/jobs/export
|
111
|
+
# HTTP/1.1\" 200 440404 - - - 257ms</v>"
|
112
|
+
#
|
101
113
|
# *Example*:
|
102
114
|
#
|
103
115
|
# require 'splunk-sdk-ruby'
|
@@ -105,11 +117,12 @@ module Splunk
|
|
105
117
|
# service = Splunk::connect(:username => "admin", :password => "changeme")
|
106
118
|
#
|
107
119
|
# stream = service.jobs.create_oneshot("search index=_internal | head 10")
|
108
|
-
# reader = ResultsReader.new(stream)
|
120
|
+
# reader = Splunk::ResultsReader.new(stream)
|
109
121
|
# puts reader.is_preview?
|
110
122
|
# # Prints: false
|
111
123
|
# reader.each do |result|
|
112
|
-
# puts result
|
124
|
+
# puts result # Prints the fields in the result as a Hash
|
125
|
+
# puts result.segmented_raw() # Prints the XML version of the _raw field
|
113
126
|
# end
|
114
127
|
# # Prints a sequence of Hashes containing events.
|
115
128
|
#
|
@@ -137,7 +150,7 @@ module Splunk
|
|
137
150
|
def initialize(text_or_stream)
|
138
151
|
if text_or_stream.nil?
|
139
152
|
stream = StringIO.new("")
|
140
|
-
elsif
|
153
|
+
elsif text_or_stream.is_a?(ExportStream)
|
141
154
|
# The sensible behavior on streams from the export endpoints is to
|
142
155
|
# skip all preview results and concatenate all others. The export
|
143
156
|
# functions wrap their streams in ExportStream to mark that they need
|
@@ -154,11 +167,9 @@ module Splunk
|
|
154
167
|
stream = text_or_stream
|
155
168
|
end
|
156
169
|
|
157
|
-
if stream.eof?
|
170
|
+
if !stream.nil? and stream.eof?
|
158
171
|
@is_preview = nil
|
159
172
|
@fields = []
|
160
|
-
elsif stream.is_a?(ExportStream)
|
161
|
-
|
162
173
|
else
|
163
174
|
# We use a SAX parser. +listener+ is the event handler, but a SAX
|
164
175
|
# parser won't usually transfer control during parsing.
|
@@ -186,8 +197,7 @@ module Splunk
|
|
186
197
|
if @is_export
|
187
198
|
warn "[DEPRECATED] Do not use ResultsReader on the output of the " +
|
188
199
|
"export endpoint. Use MultiResultsReader instead."
|
189
|
-
|
190
|
-
enum = reader.each()
|
200
|
+
enum = @reader.each()
|
191
201
|
else
|
192
202
|
enum = Enumerator.new() do |yielder|
|
193
203
|
if !@iteration_fiber.nil? # Handle the case of empty files
|
@@ -219,6 +229,24 @@ module Splunk
|
|
219
229
|
end
|
220
230
|
end
|
221
231
|
|
232
|
+
##
|
233
|
+
# +Event+ represents a single event returned from a +ResultsReader+.
|
234
|
+
#
|
235
|
+
# +Event+ is a subclass of +Hash+, adding a single method +segmented_raw()+
|
236
|
+
# which returns a string containing the XML of the raw event, as opposed
|
237
|
+
# to the unescaped, raw strings returned by fetching a particular field
|
238
|
+
# via [].
|
239
|
+
#
|
240
|
+
class Event < Hash
|
241
|
+
@raw_xml = nil
|
242
|
+
|
243
|
+
attr_writer :raw_xml
|
244
|
+
|
245
|
+
def segmented_raw
|
246
|
+
@raw_xml
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
222
250
|
##
|
223
251
|
# +ResultsListener+ is the SAX event handler for +ResultsReader+.
|
224
252
|
#
|
@@ -269,7 +297,7 @@ module Splunk
|
|
269
297
|
elsif name == "result"
|
270
298
|
@state = :result
|
271
299
|
@current_offset = Integer(attributes["offset"])
|
272
|
-
@current_result =
|
300
|
+
@current_result = Event.new()
|
273
301
|
end
|
274
302
|
end,
|
275
303
|
:end_element => lambda do |name|
|
@@ -326,7 +354,23 @@ module Splunk
|
|
326
354
|
@current_value = nil
|
327
355
|
elsif name == "text" || name == "v"
|
328
356
|
@state = :field_values
|
329
|
-
@
|
357
|
+
@current_text = ""
|
358
|
+
s = ["v"] + attributes.map do |entry|
|
359
|
+
key, value = entry
|
360
|
+
# Nokogiri and REXML both drop the namespaces of attributes,
|
361
|
+
# and there is no way to recover them. To reconstruct the
|
362
|
+
# XML (since we can't get at its raw form) we put in the
|
363
|
+
# one instance of a namespace on an attribute that shows up
|
364
|
+
# in what Splunk returns. Yes, this is a terribly, ugly
|
365
|
+
# kludge.
|
366
|
+
if key == "space"
|
367
|
+
prefixed_key = "xml:space"
|
368
|
+
else
|
369
|
+
prefixed_key = key
|
370
|
+
end
|
371
|
+
"#{prefixed_key}=\"#{value}\""
|
372
|
+
end
|
373
|
+
@current_xml = "<" + s.join(" ") + ">"
|
330
374
|
end
|
331
375
|
end,
|
332
376
|
:end_element => lambda do |name|
|
@@ -346,6 +390,11 @@ module Splunk
|
|
346
390
|
else
|
347
391
|
@current_result[@current_field] = @current_value
|
348
392
|
end
|
393
|
+
|
394
|
+
if @current_field == "_raw"
|
395
|
+
@current_result.raw_xml = @current_xml
|
396
|
+
end
|
397
|
+
|
349
398
|
@current_field = nil
|
350
399
|
@current_value = nil
|
351
400
|
end
|
@@ -356,19 +405,23 @@ module Splunk
|
|
356
405
|
:end_element => lambda do |name|
|
357
406
|
if name == "text" || name == "v"
|
358
407
|
if @current_value == nil
|
359
|
-
@current_value = @
|
408
|
+
@current_value = @current_text
|
360
409
|
elsif @current_value.is_a?(Array)
|
361
|
-
@current_value << @
|
410
|
+
@current_value << @current_text
|
362
411
|
else
|
363
|
-
@current_value = [@current_value, @
|
412
|
+
@current_value = [@current_value, @current_text]
|
413
|
+
end
|
414
|
+
|
415
|
+
if name == "v"
|
416
|
+
@current_xml << "</v>"
|
364
417
|
end
|
365
418
|
|
366
|
-
@
|
419
|
+
@current_text = nil
|
367
420
|
@state = :result
|
368
421
|
elsif name == "sg"
|
369
422
|
# <sg> is emitted to delimit text that should be displayed
|
370
423
|
# highlighted. We preserve it in field values.
|
371
|
-
@
|
424
|
+
@current_xml << "</sg>"
|
372
425
|
end
|
373
426
|
end,
|
374
427
|
:start_element => lambda do |name, attributes|
|
@@ -378,11 +431,12 @@ module Splunk
|
|
378
431
|
"#{key}=\"#{value}\""
|
379
432
|
end
|
380
433
|
text = "<" + s.join(" ") + ">"
|
381
|
-
@
|
434
|
+
@current_xml << text
|
382
435
|
end
|
383
436
|
end,
|
384
437
|
:characters => lambda do |text|
|
385
|
-
@
|
438
|
+
@current_text << text
|
439
|
+
@current_xml << Splunk::escape_string(text)
|
386
440
|
end
|
387
441
|
}
|
388
442
|
}
|