splunk-sdk-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +160 -0
- data/Gemfile +8 -0
- data/LICENSE +177 -0
- data/README.md +310 -0
- data/Rakefile +40 -0
- data/examples/1_connect.rb +51 -0
- data/examples/2_manage.rb +103 -0
- data/examples/3_blocking_searches.rb +82 -0
- data/examples/4_asynchronous_searches.rb +79 -0
- data/examples/5_stream_data_to_splunk.rb +79 -0
- data/lib/splunk-sdk-ruby.rb +47 -0
- data/lib/splunk-sdk-ruby/ambiguous_entity_reference.rb +28 -0
- data/lib/splunk-sdk-ruby/atomfeed.rb +323 -0
- data/lib/splunk-sdk-ruby/collection.rb +417 -0
- data/lib/splunk-sdk-ruby/collection/apps.rb +35 -0
- data/lib/splunk-sdk-ruby/collection/case_insensitive_collection.rb +58 -0
- data/lib/splunk-sdk-ruby/collection/configuration_file.rb +50 -0
- data/lib/splunk-sdk-ruby/collection/configurations.rb +80 -0
- data/lib/splunk-sdk-ruby/collection/jobs.rb +136 -0
- data/lib/splunk-sdk-ruby/collection/messages.rb +51 -0
- data/lib/splunk-sdk-ruby/context.rb +522 -0
- data/lib/splunk-sdk-ruby/entity.rb +260 -0
- data/lib/splunk-sdk-ruby/entity/index.rb +191 -0
- data/lib/splunk-sdk-ruby/entity/job.rb +339 -0
- data/lib/splunk-sdk-ruby/entity/message.rb +36 -0
- data/lib/splunk-sdk-ruby/entity/saved_search.rb +71 -0
- data/lib/splunk-sdk-ruby/entity/stanza.rb +45 -0
- data/lib/splunk-sdk-ruby/entity_not_ready.rb +26 -0
- data/lib/splunk-sdk-ruby/illegal_operation.rb +27 -0
- data/lib/splunk-sdk-ruby/namespace.rb +239 -0
- data/lib/splunk-sdk-ruby/resultsreader.rb +716 -0
- data/lib/splunk-sdk-ruby/service.rb +339 -0
- data/lib/splunk-sdk-ruby/splunk_http_error.rb +49 -0
- data/lib/splunk-sdk-ruby/synonyms.rb +50 -0
- data/lib/splunk-sdk-ruby/version.rb +27 -0
- data/lib/splunk-sdk-ruby/xml_shim.rb +117 -0
- data/splunk-sdk-ruby.gemspec +27 -0
- data/test/atom_test_data.rb +472 -0
- data/test/data/atom/atom_feed_with_message.xml +19 -0
- data/test/data/atom/atom_with_feed.xml +99 -0
- data/test/data/atom/atom_with_several_entries.xml +101 -0
- data/test/data/atom/atom_with_simple_entries.xml +30 -0
- data/test/data/atom/atom_without_feed.xml +248 -0
- data/test/data/export/4.2.5/export_results.xml +88 -0
- data/test/data/export/4.3.5/export_results.xml +87 -0
- data/test/data/export/5.0.1/export_results.xml +78 -0
- data/test/data/export/5.0.1/nonreporting.xml +232 -0
- data/test/data/results/4.2.5/results-empty.xml +0 -0
- data/test/data/results/4.2.5/results-preview.xml +255 -0
- data/test/data/results/4.2.5/results.xml +336 -0
- data/test/data/results/4.3.5/results-empty.xml +0 -0
- data/test/data/results/4.3.5/results-preview.xml +1057 -0
- data/test/data/results/4.3.5/results.xml +626 -0
- data/test/data/results/5.0.2/results-empty.xml +1 -0
- data/test/data/results/5.0.2/results-empty_preview.xml +1 -0
- data/test/data/results/5.0.2/results-preview.xml +448 -0
- data/test/data/results/5.0.2/results.xml +501 -0
- data/test/export_test_data.json +360 -0
- data/test/resultsreader_test_data.json +1119 -0
- data/test/services.server.info.xml +43 -0
- data/test/services.xml +111 -0
- data/test/test_atomfeed.rb +71 -0
- data/test/test_collection.rb +278 -0
- data/test/test_configuration_file.rb +124 -0
- data/test/test_context.rb +119 -0
- data/test/test_entity.rb +95 -0
- data/test/test_helper.rb +250 -0
- data/test/test_http_error.rb +52 -0
- data/test/test_index.rb +91 -0
- data/test/test_jobs.rb +319 -0
- data/test/test_messages.rb +17 -0
- data/test/test_namespace.rb +188 -0
- data/test/test_restarts.rb +49 -0
- data/test/test_resultsreader.rb +106 -0
- data/test/test_roles.rb +41 -0
- data/test/test_saved_searches.rb +119 -0
- data/test/test_service.rb +65 -0
- data/test/test_users.rb +33 -0
- data/test/test_xml_shim.rb +28 -0
- data/test/testfile.txt +1 -0
- metadata +200 -0
@@ -0,0 +1,28 @@
|
|
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
|
+
module Splunk
|
18
|
+
##
|
19
|
+
# Exception thrown when a request found multiple matching entities.
|
20
|
+
#
|
21
|
+
# An entity is uniquely defined by its name plus its namespace, so when you
|
22
|
+
# try to fetch an entity by name alone, it is possible to get multiple
|
23
|
+
# results. In that case, this error is thrown by methods that are supposed
|
24
|
+
# to return only a single entity.
|
25
|
+
#
|
26
|
+
class AmbiguousEntityReference < StandardError
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,323 @@
|
|
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
|
+
##
|
18
|
+
# +atomfeed.rb+ provides an +AtomFeed+ class to parse the Atom XML feeds
|
19
|
+
# returned by most of Splunk's endpoints.
|
20
|
+
#
|
21
|
+
|
22
|
+
require_relative 'xml_shim'
|
23
|
+
|
24
|
+
#--
|
25
|
+
# Nokogiri returns attribute values as objects on which we have to call
|
26
|
+
# `#text`. REXML returns Strings. To make them both work, add a `text` method to
|
27
|
+
# String that returns itself.
|
28
|
+
#++
|
29
|
+
class String # :nodoc:
|
30
|
+
def text
|
31
|
+
self
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#--
|
36
|
+
# For compatibility with Nokogiri, we add a method `text`, which, like
|
37
|
+
# Nokogiri's `text` method, returns the contents without escaping entities.
|
38
|
+
# This is identical to REXML's `value` method.
|
39
|
+
#++
|
40
|
+
#if $splunk_xml_library == :rexml
|
41
|
+
# class REXML::Text # :nodoc:
|
42
|
+
# def text
|
43
|
+
# value
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#end
|
47
|
+
|
48
|
+
module Splunk
|
49
|
+
##
|
50
|
+
# Reads an Atom XML feed into a Ruby object.
|
51
|
+
#
|
52
|
+
# +AtomFeed.new+ accepts either a string or any object with a +read+ method.
|
53
|
+
# It parses that as an Atom feed and exposes two read-only fields, +metadata+
|
54
|
+
# and +entries+. The +metadata+ field is a hash of all the header fields of
|
55
|
+
# the feed. The +entries+ field is a list of hashes giving the details of
|
56
|
+
# each entry in the feed.
|
57
|
+
#
|
58
|
+
# *Example:*
|
59
|
+
#
|
60
|
+
# file = File.open("some_feed.xml")
|
61
|
+
# feed = AtomFeed.new(file)
|
62
|
+
# # or AtomFeed.new(file.read())
|
63
|
+
# # or AtomFeed.new(file, xml_library=:rexml)
|
64
|
+
# feed.metadata.is_a?(Hash) == true
|
65
|
+
# feed.entries.is_a?(Array) == true
|
66
|
+
#
|
67
|
+
class AtomFeed
|
68
|
+
public
|
69
|
+
def initialize(text_or_stream)
|
70
|
+
if text_or_stream.respond_to?(:read)
|
71
|
+
text = text_or_stream.read()
|
72
|
+
else
|
73
|
+
text = text_or_stream
|
74
|
+
end
|
75
|
+
# Sanity checks
|
76
|
+
raise ArgumentError, 'text is nil' if text.nil?
|
77
|
+
text = text.strip
|
78
|
+
raise ArgumentError, 'text size is 0' if text.size == 0
|
79
|
+
|
80
|
+
if $splunk_xml_library == :nokogiri
|
81
|
+
doc = Nokogiri::XML(text)
|
82
|
+
else
|
83
|
+
doc = REXML::Document.new(text)
|
84
|
+
end
|
85
|
+
# Skip down to the content of the Atom feed. Most of Splunk's
|
86
|
+
# endpoints return a feed of the form
|
87
|
+
#
|
88
|
+
# <feed>
|
89
|
+
# ...metadata...
|
90
|
+
# <entry>...details of entry...</entry>
|
91
|
+
# <entry>...details of entry...</entry>
|
92
|
+
# <entry>...details of entry...</entry>
|
93
|
+
# ...
|
94
|
+
# </feed>
|
95
|
+
#
|
96
|
+
# with the exception of fetching a single job entity from Splunk 4.3,
|
97
|
+
# where it returns
|
98
|
+
#
|
99
|
+
# <entry>...details of entry...</entry>
|
100
|
+
#
|
101
|
+
# To handle both, we have to check whether <feed> is there
|
102
|
+
# before skipping.
|
103
|
+
if doc.root.name == "feed"
|
104
|
+
@metadata, @entries = read_feed(doc.root)
|
105
|
+
elsif doc.root.name == "entry"
|
106
|
+
@metadata = {}
|
107
|
+
@entries = [read_entry(doc.root)]
|
108
|
+
else
|
109
|
+
raise ArgumentError, 'root element of Atom must be feed or entry'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# The header fields of the feed.
|
115
|
+
#
|
116
|
+
# Typically this has keys such as "+author+", "+title+", and
|
117
|
+
# "+totalResults+".
|
118
|
+
#
|
119
|
+
# Returns: a +Hash+ with +Strings+ as keys.
|
120
|
+
#
|
121
|
+
attr_reader :metadata
|
122
|
+
|
123
|
+
##
|
124
|
+
# The entries in the feed.
|
125
|
+
#
|
126
|
+
# Returns: an +Array+ containing +Hashes+ that represent each entry in the feed.
|
127
|
+
#
|
128
|
+
attr_reader :entries
|
129
|
+
|
130
|
+
private # All methods below here are internal to AtomFeed.
|
131
|
+
|
132
|
+
##
|
133
|
+
# Produces a +String+ from the children of _element_.
|
134
|
+
#
|
135
|
+
# _element_ should be either a REXML or Nokogiri element.
|
136
|
+
#
|
137
|
+
# Returns: a +String+.
|
138
|
+
#
|
139
|
+
def children_to_s(element) # :nodoc:
|
140
|
+
result = ""
|
141
|
+
element.children.each do |child|
|
142
|
+
if $splunk_xml_library == :nokogiri
|
143
|
+
result << child.text
|
144
|
+
else
|
145
|
+
result << child.value
|
146
|
+
end
|
147
|
+
end
|
148
|
+
result
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Reads a feed from the the XML in _feed_.
|
153
|
+
#
|
154
|
+
# Returns: [+metadata, entries+], where +metadata+ is a hash of feed
|
155
|
+
# headers, and +entries+ is an +Array+ of +Hashes+ representing the feed.
|
156
|
+
#
|
157
|
+
def read_feed(feed)
|
158
|
+
metadata = {"links" => {}, "messages" => []}
|
159
|
+
entries = []
|
160
|
+
|
161
|
+
feed.elements.each do |element|
|
162
|
+
if element.name == "entry"
|
163
|
+
entries << read_entry(element)
|
164
|
+
elsif element.name == "author"
|
165
|
+
# The author tag has the form <author><name>...</name></author>
|
166
|
+
# so we have to fetch the value out of the inside of it.
|
167
|
+
metadata["author"] = read_author(element)
|
168
|
+
elsif element.name == "generator"
|
169
|
+
# To handle elements of the form:
|
170
|
+
# <generator build="144175" version="5.0.2"/>
|
171
|
+
metadata["generator"] = {}
|
172
|
+
element.attributes.each do |name, attribute|
|
173
|
+
metadata["generator"][name] = attribute.text
|
174
|
+
end
|
175
|
+
elsif element.name == "link"
|
176
|
+
rel, uri = read_link(element)
|
177
|
+
metadata["links"][rel] = uri
|
178
|
+
elsif element.name == "id"
|
179
|
+
metadata[element.name] = URI(children_to_s(element))
|
180
|
+
elsif element.name == "messages"
|
181
|
+
element.elements.each do |element|
|
182
|
+
if element.name == "msg"
|
183
|
+
metadata["messages"] << {
|
184
|
+
"type" => element.attributes["type"].text.intern,
|
185
|
+
"message" => children_to_s(element)
|
186
|
+
}
|
187
|
+
end
|
188
|
+
end
|
189
|
+
else
|
190
|
+
metadata[element.name] = children_to_s(element)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
return metadata, entries
|
195
|
+
end
|
196
|
+
|
197
|
+
##
|
198
|
+
# Reads a single entry from the XML in _entry_.
|
199
|
+
#
|
200
|
+
# Returns: a +Hash+ representing the entry.
|
201
|
+
#
|
202
|
+
def read_entry(entry)
|
203
|
+
result = {"links" => {}}
|
204
|
+
entry.elements.each do |element|
|
205
|
+
name = element.name
|
206
|
+
if name == "link"
|
207
|
+
rel, uri = read_link(element)
|
208
|
+
result["links"][rel] = uri
|
209
|
+
else
|
210
|
+
value = read_entry_field(element)
|
211
|
+
result[name] = value
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
return result
|
216
|
+
end
|
217
|
+
|
218
|
+
##
|
219
|
+
# Reads a name and link from the XML in _link_.
|
220
|
+
#
|
221
|
+
# Returns: [+name, link+], where _name_ is a +String+ giving the name of
|
222
|
+
# the link, and _link_ is a +URI+.
|
223
|
+
#
|
224
|
+
def read_link(link)
|
225
|
+
# To handle elements of the form:
|
226
|
+
# <link href="/%252FUsers%252Ffross%252Ffile%20with%20spaces"
|
227
|
+
# Note that the link is already URL encoded.
|
228
|
+
uri = URI(link.attributes['href'].text)
|
229
|
+
rel = link.attributes['rel'].text
|
230
|
+
return rel, uri
|
231
|
+
end
|
232
|
+
|
233
|
+
##
|
234
|
+
# Reads a single field of an entry from the XML in _field_.
|
235
|
+
#
|
236
|
+
# Returns: a single value (either a +String+ or a +URI+).
|
237
|
+
#
|
238
|
+
def read_entry_field(field)
|
239
|
+
# We have to coerce this to an Array to call length,
|
240
|
+
# since Nokogiri defines `#length` on element sets,
|
241
|
+
# but REXML does not.
|
242
|
+
elements = Array(field.elements)
|
243
|
+
if elements.length == 0 # This is a simple text field
|
244
|
+
return read_simple_entry_field(field)
|
245
|
+
elsif elements.length > 1
|
246
|
+
raise ArgumentError, "Entry fields should contain one element " +
|
247
|
+
"(found #{elements.length} in #{field.to_s}."
|
248
|
+
elsif field.name == "author"
|
249
|
+
return read_author(field)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Coerce to Array because Nokogiri indexes from 0, and REXML from 1.
|
253
|
+
# Arrays always index from 0.
|
254
|
+
value_element = Array(field.elements)[0]
|
255
|
+
if value_element.name == "dict"
|
256
|
+
return read_dict(value_element)
|
257
|
+
elsif value_element.name == "list"
|
258
|
+
return read_list(value_element)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
##
|
263
|
+
# Reads a simple field.
|
264
|
+
#
|
265
|
+
# Returns: a +String+ or a +URI+.
|
266
|
+
#
|
267
|
+
def read_simple_entry_field(field)
|
268
|
+
value = children_to_s(field)
|
269
|
+
if field.name == "id"
|
270
|
+
return URI(value)
|
271
|
+
else
|
272
|
+
return value
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
##
|
277
|
+
# Reads a dictionary from the XML in _dict_.
|
278
|
+
#
|
279
|
+
# Returns: a +Hash+.
|
280
|
+
#
|
281
|
+
def read_dict(dict)
|
282
|
+
result = {}
|
283
|
+
dict.elements.each do |element|
|
284
|
+
key = element.attributes["name"].text
|
285
|
+
value = read_entry_field(element)
|
286
|
+
result[key] = value
|
287
|
+
end
|
288
|
+
|
289
|
+
return result
|
290
|
+
end
|
291
|
+
|
292
|
+
##
|
293
|
+
# Reads an +Array+ from the XML in _list_.
|
294
|
+
#
|
295
|
+
# Returns: an +Array+.
|
296
|
+
#
|
297
|
+
def read_list(list)
|
298
|
+
result = []
|
299
|
+
list.elements.each do |element|
|
300
|
+
value = read_entry_field(element)
|
301
|
+
result << value
|
302
|
+
end
|
303
|
+
|
304
|
+
return result
|
305
|
+
end
|
306
|
+
|
307
|
+
##
|
308
|
+
# Reads the author from its special tag.
|
309
|
+
#
|
310
|
+
# Returns: a +String+.
|
311
|
+
#
|
312
|
+
def read_author(author)
|
313
|
+
# The author tag has the form <author><name>...</name></author>
|
314
|
+
# so we have to fetch the value out of the inside of it.
|
315
|
+
#
|
316
|
+
# In REXML, sets of elements are indexed starting at 1 to match
|
317
|
+
# XPath. In Nokogiri they are indexed starting at 0. To work around
|
318
|
+
# this, we coerce it to an array, which is always indexed starting at 0.
|
319
|
+
#
|
320
|
+
return Array(author.elements)[0].text
|
321
|
+
end
|
322
|
+
end
|
323
|
+
end
|
@@ -0,0 +1,417 @@
|
|
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
|
+
##
|
18
|
+
# Provides the +Collection+ class, which represents a collection in Splunk.
|
19
|
+
#
|
20
|
+
|
21
|
+
require_relative 'ambiguous_entity_reference'
|
22
|
+
require_relative 'atomfeed'
|
23
|
+
require_relative 'entity'
|
24
|
+
require_relative 'splunk_http_error'
|
25
|
+
require_relative 'synonyms'
|
26
|
+
|
27
|
+
module Splunk
|
28
|
+
# Class representing a collection in Splunk.
|
29
|
+
#
|
30
|
+
# A +Collection+ is a group of items, usually of class +Entity+ or one of its
|
31
|
+
# subclasses, but occasionally another +Collection+. Usually you obtain a
|
32
|
+
# +Collection+ by calling one of the convenience methods on +Service+.
|
33
|
+
#
|
34
|
+
# A +Collection+ is enumerable, and implements many of the methods found on
|
35
|
+
# +Hash+, so methods like +each+, +select+, and +delete_if+ all work, as does
|
36
|
+
# fetching a member of the +Collection+ with [].
|
37
|
+
#
|
38
|
+
class Collection
|
39
|
+
include Enumerable
|
40
|
+
extend Synonyms
|
41
|
+
|
42
|
+
def initialize(service, resource, entity_class=Entity)
|
43
|
+
@service = service
|
44
|
+
@resource = resource
|
45
|
+
@entity_class = entity_class
|
46
|
+
|
47
|
+
# @infinite_count declares the value used for listing all the entities
|
48
|
+
# in a collection. It is usually -1, but some collections use 0.
|
49
|
+
@infinite_count = -1
|
50
|
+
|
51
|
+
# @always_fetch tells whether, when creating an entity in this collection,
|
52
|
+
# to bother trying to parse the response and always fetch the new state
|
53
|
+
# after the fact. This is necessary for some collections, such as users,
|
54
|
+
# which don't return the newly created object.
|
55
|
+
@always_fetch = false
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# The service through which this +Collection+ refers to Splunk.
|
60
|
+
#
|
61
|
+
# Returns: a +Service+.
|
62
|
+
#
|
63
|
+
attr_reader :service
|
64
|
+
|
65
|
+
##
|
66
|
+
# The path after the namespace to reach this collection.
|
67
|
+
#
|
68
|
+
# For example, for apps +resource+ will be ["+apps+", "+local+"].
|
69
|
+
#
|
70
|
+
# Returns: an +Array+ of +Strings+.
|
71
|
+
#
|
72
|
+
attr_reader :resource
|
73
|
+
|
74
|
+
##
|
75
|
+
# The class used to represent members of this +Collection+.
|
76
|
+
#
|
77
|
+
# By default this will be +Entity+, but many collections such as jobs
|
78
|
+
# will use a subclass of it (in the case of jobs, the +Job+ class), or
|
79
|
+
# even another collection (+ConfigurationFile+ in the case of
|
80
|
+
# configurations).
|
81
|
+
#
|
82
|
+
# Returns: a class.
|
83
|
+
#
|
84
|
+
attr_reader :entity_class
|
85
|
+
|
86
|
+
##
|
87
|
+
# Find the first entity in the collection with the given name.
|
88
|
+
#
|
89
|
+
# Optionally, you may provide a _namespace_. If there are multiple entities
|
90
|
+
# visible in this collection named _name_, you _must_ provide a namespace
|
91
|
+
# or +assoc+ will raise an +AmbiguousEntityReference+ error.
|
92
|
+
#
|
93
|
+
# Returns: an +Array+ of [_name_, _entity_] or +nil+ if there is
|
94
|
+
# no matching element.
|
95
|
+
#
|
96
|
+
def assoc(name, namespace=nil)
|
97
|
+
entity = fetch(name, namespace)
|
98
|
+
if entity.nil?
|
99
|
+
return nil
|
100
|
+
else
|
101
|
+
return [entity.name, entity]
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Convert an Atom entry into an entity in this collection.
|
107
|
+
#
|
108
|
+
# The Atom entry should be in the form of an entry from +AtomFeed+.
|
109
|
+
#
|
110
|
+
# Returns: An object of class @+entity_class+.
|
111
|
+
#
|
112
|
+
def atom_entry_to_entity(entry)
|
113
|
+
name = entry["title"]
|
114
|
+
namespace = Splunk::eai_acl_to_namespace(entry["content"]["eai:acl"])
|
115
|
+
|
116
|
+
@entity_class.new(service=@service,
|
117
|
+
namespace=namespace,
|
118
|
+
resource=@resource,
|
119
|
+
name=name,
|
120
|
+
state=entry)
|
121
|
+
end
|
122
|
+
|
123
|
+
##
|
124
|
+
# Creates an item in this collection.
|
125
|
+
#
|
126
|
+
# The _name_ argument is required. All other arguments are passed as a hash,
|
127
|
+
# though they vary from collection to collection.
|
128
|
+
#
|
129
|
+
# Returns: the created entity.
|
130
|
+
#
|
131
|
+
# *Example:*
|
132
|
+
# service = Splunk::connect(:username => 'admin', :password => 'foo')
|
133
|
+
# service.users.create('jack',
|
134
|
+
# :password => 'mypassword',
|
135
|
+
# :realname => 'Jack_be_nimble',
|
136
|
+
# :roles => ['user'])
|
137
|
+
#
|
138
|
+
def create(name, args={})
|
139
|
+
body_args = args.clone()
|
140
|
+
body_args["name"] = name
|
141
|
+
|
142
|
+
request_args = {
|
143
|
+
:method => :POST,
|
144
|
+
:resource => @resource,
|
145
|
+
:body => body_args
|
146
|
+
}
|
147
|
+
if args.has_key?(:namespace)
|
148
|
+
request_args[:namespace] = body_args.delete(:namespace)
|
149
|
+
end
|
150
|
+
|
151
|
+
response = @service.request(request_args)
|
152
|
+
|
153
|
+
if @always_fetch
|
154
|
+
fetch_args = {:method => :GET,
|
155
|
+
:resource => @resource + [name]}
|
156
|
+
if args.has_key?(:namespace)
|
157
|
+
fetch_args[:namespace] = args[:namespace]
|
158
|
+
end
|
159
|
+
response = @service.request(fetch_args)
|
160
|
+
end
|
161
|
+
feed = AtomFeed.new(response.body)
|
162
|
+
raise StandardError.new("No entities returned") if feed.entries.empty?
|
163
|
+
entity = atom_entry_to_entity(feed.entries[0])
|
164
|
+
raise StandardError.new("Found nil entity.") if entity.nil?
|
165
|
+
return entity
|
166
|
+
end
|
167
|
+
|
168
|
+
##
|
169
|
+
# Deletes an item from the collection.
|
170
|
+
#
|
171
|
+
# Entities from different namespaces may have the same name, so if you are
|
172
|
+
# connected to Splunk using a namespace with wildcards in it, there may
|
173
|
+
# be multiple entities in the collection with the same name. In this case
|
174
|
+
# you must specify a namespace as well, or the +delete+ method will raise an
|
175
|
+
# AmbiguousEntityReference error.
|
176
|
+
#
|
177
|
+
# *Example:*
|
178
|
+
# service = Splunk::connect(:username => 'admin', :password => 'foo')
|
179
|
+
# props = service.confs['props']
|
180
|
+
# props.delete('sdk-tests')
|
181
|
+
#
|
182
|
+
def delete(name, namespace=nil)
|
183
|
+
if namespace.nil?
|
184
|
+
namespace = @service.namespace
|
185
|
+
end
|
186
|
+
|
187
|
+
# We don't want to handle any cases about deleting ambiguously named
|
188
|
+
# entities.
|
189
|
+
if !namespace.is_exact?
|
190
|
+
raise StandardError.new("Must provide an exact namespace to delete an entity.")
|
191
|
+
end
|
192
|
+
|
193
|
+
@service.request(:method => :DELETE,
|
194
|
+
:namespace => namespace,
|
195
|
+
:resource => @resource + [name])
|
196
|
+
return self
|
197
|
+
end
|
198
|
+
|
199
|
+
##
|
200
|
+
# Deletes all entities on this collection for which the block returns true.
|
201
|
+
#
|
202
|
+
# If block is omitted, returns an enumerator over all members of the
|
203
|
+
# collection.
|
204
|
+
#
|
205
|
+
def delete_if(&block)
|
206
|
+
# Without a block, just return an enumerator
|
207
|
+
return each() if !block_given?
|
208
|
+
|
209
|
+
values.each() do |entity|
|
210
|
+
if block.call(entity)
|
211
|
+
delete(entity.name, entity.namespace)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
##
|
218
|
+
# Calls block once for each item in the collection.
|
219
|
+
#
|
220
|
+
# The +each+ method takes three optional arguments as well:
|
221
|
+
#
|
222
|
+
# * +count+ sets the maximum number of entities to fetch (integer >= 0)
|
223
|
+
# * +offset+ sets how many items to skip before returning items in the
|
224
|
+
# collection (integer >= 0)
|
225
|
+
# * +page_size+ sets how many items at a time should be fetched from the
|
226
|
+
# server and processed before fetching another set
|
227
|
+
#
|
228
|
+
# The block is called with the entity as its argument.
|
229
|
+
#
|
230
|
+
# If the block is omitted, returns an enumerator over all members of the
|
231
|
+
# entity.
|
232
|
+
#
|
233
|
+
# *Example:*
|
234
|
+
# service = Splunk::connect(:username => 'admin', :password => 'foo')
|
235
|
+
# service.loggers.each do |key, logger|
|
236
|
+
# puts logger.name + ":" + logger['level']
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
def each(args={})
|
240
|
+
enum = Enumerator.new() do |yielder|
|
241
|
+
count = args.fetch(:count, @infinite_count)
|
242
|
+
offset = args.fetch(:offset, 0)
|
243
|
+
page_size = args.fetch(:page_size, nil)
|
244
|
+
|
245
|
+
if !page_size.nil?
|
246
|
+
# Do pagination. Fetch page_size at a time
|
247
|
+
current_offset = offset
|
248
|
+
remaining_count = count
|
249
|
+
while remaining_count > 0
|
250
|
+
n_entities = 0
|
251
|
+
each(:offset => current_offset,
|
252
|
+
:count => [remaining_count, page_size].min) do |entity|
|
253
|
+
n_entities += 1
|
254
|
+
yielder << entity
|
255
|
+
end
|
256
|
+
|
257
|
+
if n_entities < page_size
|
258
|
+
break # We've reached the end of the collection.
|
259
|
+
else
|
260
|
+
remaining_count -= n_entities
|
261
|
+
current_offset += n_entities
|
262
|
+
end
|
263
|
+
end
|
264
|
+
else
|
265
|
+
# Fetch the specified range in one pass.
|
266
|
+
response = @service.request(:resource => @resource,
|
267
|
+
:query => {"count" => count.to_s,
|
268
|
+
"offset" => offset.to_s})
|
269
|
+
feed = AtomFeed.new(response.body)
|
270
|
+
feed.entries.each() do |entry|
|
271
|
+
entity = atom_entry_to_entity(entry)
|
272
|
+
yielder << entity
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
if block_given?
|
278
|
+
enum.each() { |e| yield e }
|
279
|
+
else
|
280
|
+
return enum
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
##
|
285
|
+
# Identical to the +each+ method.
|
286
|
+
#
|
287
|
+
synonym "each_value", "each"
|
288
|
+
|
289
|
+
##
|
290
|
+
# Identical to the +each+ method, but the block is passed the entity's name.
|
291
|
+
#
|
292
|
+
def each_key(args={}, &block)
|
293
|
+
each(args).map() { |e| e.name }.each(&block)
|
294
|
+
end
|
295
|
+
|
296
|
+
##
|
297
|
+
# Identical to the +each+ method, but the block is passed both the entity's
|
298
|
+
# name, and the entity.
|
299
|
+
#
|
300
|
+
def each_pair(args={}, &block)
|
301
|
+
each(args).map() { |e| [e.name, e] }.each(&block)
|
302
|
+
end
|
303
|
+
|
304
|
+
##
|
305
|
+
# Returns whether there are any entities in this collection.
|
306
|
+
#
|
307
|
+
# Returns: +true+ or +false+.
|
308
|
+
#
|
309
|
+
def empty?()
|
310
|
+
return length() == 0
|
311
|
+
end
|
312
|
+
|
313
|
+
##
|
314
|
+
# Fetches _name_ from this collection.
|
315
|
+
#
|
316
|
+
# If _name_ does not exist, returns +nil+. Otherwise returns the element.
|
317
|
+
# If, due to wildcards in your namespace, there are two entities visible
|
318
|
+
# in the collection with the same name, fetch will raise an
|
319
|
+
# AmbiguousEntityReference error. You must specify a namespace in this
|
320
|
+
# case to disambiguate the fetch.
|
321
|
+
#
|
322
|
+
def fetch(name, namespace=nil)
|
323
|
+
request_args = {:resource => @resource + [name]}
|
324
|
+
if !namespace.nil?
|
325
|
+
request_args[:namespace] = namespace
|
326
|
+
end
|
327
|
+
|
328
|
+
begin
|
329
|
+
response = @service.request(request_args)
|
330
|
+
rescue SplunkHTTPError => err
|
331
|
+
if err.code == 404
|
332
|
+
return nil
|
333
|
+
else
|
334
|
+
raise err
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
feed = AtomFeed.new(response.body)
|
339
|
+
|
340
|
+
if feed.entries.length > 1
|
341
|
+
raise AmbiguousEntityReference.new("Found multiple entities with " +
|
342
|
+
"name #{name}. Please specify a disambiguating namespace.")
|
343
|
+
else
|
344
|
+
atom_entry_to_entity(feed.entries[0])
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
synonym "[]", "fetch"
|
349
|
+
|
350
|
+
##
|
351
|
+
# Returns whether there is an entity named _name_ in this collection.
|
352
|
+
#
|
353
|
+
# Returns: a boolean.
|
354
|
+
# Synonyms: contains?, include?, key?, member?
|
355
|
+
#
|
356
|
+
def has_key?(name)
|
357
|
+
begin
|
358
|
+
response = @service.request(:resource => @resource + [name])
|
359
|
+
return true
|
360
|
+
rescue SplunkHTTPError => err
|
361
|
+
if err.code == 404
|
362
|
+
return false
|
363
|
+
else
|
364
|
+
raise err
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
synonym "contains?", "has_key?"
|
370
|
+
synonym "include?", "has_key?"
|
371
|
+
synonym "key?", "has_key?"
|
372
|
+
synonym "member?", "has_key?"
|
373
|
+
|
374
|
+
##
|
375
|
+
# Returns an +Array+ of all entity names in the +Collection+.
|
376
|
+
#
|
377
|
+
# Returns: an +Array+ of +Strings+.
|
378
|
+
#
|
379
|
+
def keys()
|
380
|
+
return values().map() { |e| e.name }
|
381
|
+
end
|
382
|
+
|
383
|
+
##
|
384
|
+
# Returns the number of entities in this collection.
|
385
|
+
#
|
386
|
+
# Returns: a nonnegative +Integer+.
|
387
|
+
# Synonyms: +size+.
|
388
|
+
#
|
389
|
+
def length()
|
390
|
+
return values().length()
|
391
|
+
end
|
392
|
+
|
393
|
+
synonym "size", "length"
|
394
|
+
|
395
|
+
##
|
396
|
+
# Returns an +Array+ of the entities in this collection.
|
397
|
+
#
|
398
|
+
# The +values+ method takes three optional arguments:
|
399
|
+
#
|
400
|
+
# * +count+ sets the maximum number of entities to fetch (integer >= 0)
|
401
|
+
# * +offset+ sets how many items to skip before returning items in the
|
402
|
+
# collection (integer >= 0)
|
403
|
+
# * +page_size+ sets how many items at a time should be fetched from the
|
404
|
+
# server and processed before fetching another set
|
405
|
+
#
|
406
|
+
# Returns: an +Array+ of @entity_class.
|
407
|
+
# Synonyms: +list+, +to_a+.
|
408
|
+
#
|
409
|
+
def values(args={})
|
410
|
+
each(args).to_a()
|
411
|
+
end
|
412
|
+
|
413
|
+
synonym "list", "values"
|
414
|
+
synonym "to_a", "values"
|
415
|
+
end
|
416
|
+
|
417
|
+
end
|