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.
Files changed (81) hide show
  1. data/CHANGELOG.md +160 -0
  2. data/Gemfile +8 -0
  3. data/LICENSE +177 -0
  4. data/README.md +310 -0
  5. data/Rakefile +40 -0
  6. data/examples/1_connect.rb +51 -0
  7. data/examples/2_manage.rb +103 -0
  8. data/examples/3_blocking_searches.rb +82 -0
  9. data/examples/4_asynchronous_searches.rb +79 -0
  10. data/examples/5_stream_data_to_splunk.rb +79 -0
  11. data/lib/splunk-sdk-ruby.rb +47 -0
  12. data/lib/splunk-sdk-ruby/ambiguous_entity_reference.rb +28 -0
  13. data/lib/splunk-sdk-ruby/atomfeed.rb +323 -0
  14. data/lib/splunk-sdk-ruby/collection.rb +417 -0
  15. data/lib/splunk-sdk-ruby/collection/apps.rb +35 -0
  16. data/lib/splunk-sdk-ruby/collection/case_insensitive_collection.rb +58 -0
  17. data/lib/splunk-sdk-ruby/collection/configuration_file.rb +50 -0
  18. data/lib/splunk-sdk-ruby/collection/configurations.rb +80 -0
  19. data/lib/splunk-sdk-ruby/collection/jobs.rb +136 -0
  20. data/lib/splunk-sdk-ruby/collection/messages.rb +51 -0
  21. data/lib/splunk-sdk-ruby/context.rb +522 -0
  22. data/lib/splunk-sdk-ruby/entity.rb +260 -0
  23. data/lib/splunk-sdk-ruby/entity/index.rb +191 -0
  24. data/lib/splunk-sdk-ruby/entity/job.rb +339 -0
  25. data/lib/splunk-sdk-ruby/entity/message.rb +36 -0
  26. data/lib/splunk-sdk-ruby/entity/saved_search.rb +71 -0
  27. data/lib/splunk-sdk-ruby/entity/stanza.rb +45 -0
  28. data/lib/splunk-sdk-ruby/entity_not_ready.rb +26 -0
  29. data/lib/splunk-sdk-ruby/illegal_operation.rb +27 -0
  30. data/lib/splunk-sdk-ruby/namespace.rb +239 -0
  31. data/lib/splunk-sdk-ruby/resultsreader.rb +716 -0
  32. data/lib/splunk-sdk-ruby/service.rb +339 -0
  33. data/lib/splunk-sdk-ruby/splunk_http_error.rb +49 -0
  34. data/lib/splunk-sdk-ruby/synonyms.rb +50 -0
  35. data/lib/splunk-sdk-ruby/version.rb +27 -0
  36. data/lib/splunk-sdk-ruby/xml_shim.rb +117 -0
  37. data/splunk-sdk-ruby.gemspec +27 -0
  38. data/test/atom_test_data.rb +472 -0
  39. data/test/data/atom/atom_feed_with_message.xml +19 -0
  40. data/test/data/atom/atom_with_feed.xml +99 -0
  41. data/test/data/atom/atom_with_several_entries.xml +101 -0
  42. data/test/data/atom/atom_with_simple_entries.xml +30 -0
  43. data/test/data/atom/atom_without_feed.xml +248 -0
  44. data/test/data/export/4.2.5/export_results.xml +88 -0
  45. data/test/data/export/4.3.5/export_results.xml +87 -0
  46. data/test/data/export/5.0.1/export_results.xml +78 -0
  47. data/test/data/export/5.0.1/nonreporting.xml +232 -0
  48. data/test/data/results/4.2.5/results-empty.xml +0 -0
  49. data/test/data/results/4.2.5/results-preview.xml +255 -0
  50. data/test/data/results/4.2.5/results.xml +336 -0
  51. data/test/data/results/4.3.5/results-empty.xml +0 -0
  52. data/test/data/results/4.3.5/results-preview.xml +1057 -0
  53. data/test/data/results/4.3.5/results.xml +626 -0
  54. data/test/data/results/5.0.2/results-empty.xml +1 -0
  55. data/test/data/results/5.0.2/results-empty_preview.xml +1 -0
  56. data/test/data/results/5.0.2/results-preview.xml +448 -0
  57. data/test/data/results/5.0.2/results.xml +501 -0
  58. data/test/export_test_data.json +360 -0
  59. data/test/resultsreader_test_data.json +1119 -0
  60. data/test/services.server.info.xml +43 -0
  61. data/test/services.xml +111 -0
  62. data/test/test_atomfeed.rb +71 -0
  63. data/test/test_collection.rb +278 -0
  64. data/test/test_configuration_file.rb +124 -0
  65. data/test/test_context.rb +119 -0
  66. data/test/test_entity.rb +95 -0
  67. data/test/test_helper.rb +250 -0
  68. data/test/test_http_error.rb +52 -0
  69. data/test/test_index.rb +91 -0
  70. data/test/test_jobs.rb +319 -0
  71. data/test/test_messages.rb +17 -0
  72. data/test/test_namespace.rb +188 -0
  73. data/test/test_restarts.rb +49 -0
  74. data/test/test_resultsreader.rb +106 -0
  75. data/test/test_roles.rb +41 -0
  76. data/test/test_saved_searches.rb +119 -0
  77. data/test/test_service.rb +65 -0
  78. data/test/test_users.rb +33 -0
  79. data/test/test_xml_shim.rb +28 -0
  80. data/test/testfile.txt +1 -0
  81. 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