splunk-sdk-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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