splunk-sdk-ruby 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|