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,260 @@
1
+ #--
2
+ # Copyright 2011-2013 Splunk, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License"): you may
5
+ # not use this file except in compliance with the License. You may obtain
6
+ # a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations
14
+ # under the License.
15
+ #++
16
+
17
+ require_relative 'ambiguous_entity_reference'
18
+ require_relative 'atomfeed'
19
+ require_relative 'entity_not_ready'
20
+ require_relative 'synonyms'
21
+
22
+ module Splunk
23
+ ##
24
+ # Class representing individual entities in Splunk.
25
+ #
26
+ # +Entity+ objects represent individual items such as indexes, users, roles,
27
+ # etc. They are usually contained within +Collection+ objects.
28
+ #
29
+ # The basic, identifying information for an +Entity+ (name, namespace, path
30
+ # of the collection containing entity, and the service it's on) is all
31
+ # accessible via getters (+name+, +namespace+, +resource+, +service+). All
32
+ # the fields containing the +Entity+'s state, such as the capabilities of
33
+ # a role or whether an app should check for updates, are accessible with
34
+ # the [] operator (for instance, +role+["+capabilities+"] or
35
+ # +app+["+check_for_updates+"]).
36
+ #
37
+ # +Entity+ objects cache their state, so each lookup of a field does not
38
+ # make a roundtrip to the server. The state may be refreshed by calling
39
+ # the +refresh+ method on the +Entity+.
40
+ #
41
+ class Entity
42
+ extend Synonyms
43
+
44
+ def initialize(service, namespace, resource, name, state=nil) # :nodoc:
45
+ @service = service
46
+ @namespace = namespace
47
+ if !@namespace.is_exact?
48
+ raise StandardError.new("Must provide an exact namespace to " +
49
+ "Entity (found: #{@namespace}")
50
+ end
51
+ @resource = resource
52
+ @name = name
53
+ @state = state
54
+ if !state # If the state was not provided, we need to fetch it.
55
+ refresh()
56
+ end
57
+ end
58
+
59
+ ##
60
+ # The name of this Entity.
61
+ #
62
+ # Returns: a +String+.
63
+ #
64
+ attr_reader :name
65
+
66
+ ##
67
+ # The namespace of this Entity.
68
+ #
69
+ # Returns: a +Namespace+.
70
+ #
71
+ attr_reader :namespace
72
+
73
+ ##
74
+ # The path of the collection this entity lives in.
75
+ #
76
+ # For example, on an app this will be ["+apps+", "+local+"].
77
+ #
78
+ # Returns: an +Array+ of +Strings+.
79
+ #
80
+ attr_reader :resource
81
+
82
+ ##
83
+ # The service this entity refers to.
84
+ #
85
+ # Returns: a +Service+ object.
86
+ #
87
+ attr_reader :service
88
+
89
+ ##
90
+ # Deletes this entity from the server.
91
+ #
92
+ # Returns: +nil+.
93
+ #
94
+ def delete()
95
+ @service.request({:method => :DELETE,
96
+ :namespace => @namespace,
97
+ :resource => @resource + [name]})
98
+ end
99
+
100
+ ##
101
+ # Fetches the field _key_ on this entity.
102
+ #
103
+ # You may provide a default value. All values are returned
104
+ # as strings.
105
+ #
106
+ # Returns: a +String+.
107
+ #
108
+ def fetch(key, default=nil)
109
+ @state["content"].fetch(key, default)
110
+ end
111
+
112
+ ##
113
+ # Fetch a field on this entity.
114
+ #
115
+ # Returns: a +String+.
116
+ #
117
+ synonym "[]", "fetch"
118
+
119
+ ##
120
+ # Returns a Hash of the links associated with this entity.
121
+ #
122
+ # The links typically include keys such as "+list+", "+edit+", or
123
+ # "+disable+".
124
+ #
125
+ # Returns: a +Hash+ of +Strings+ to URL objects.
126
+ #
127
+ def links()
128
+ return @state["links"]
129
+ end
130
+
131
+ ##
132
+ # DEPRECATED. Use +fetch+ and [] instead (since entities now cache their
133
+ # state).
134
+ #
135
+ # Returns all or a specified subset of key/value pairs on this +Entity+
136
+ #
137
+ # In the absence of arguments, returns a Hash of all the fields on this
138
+ # +Entity+. If you specify one or more +Strings+ or +Arrays+ of +Strings+,
139
+ # all the keys specified in the arguments will be returned in the +Hash+.
140
+ #
141
+ # Returns: a +Hash+ with +Strings+ as keys, and +Strings+ or +Hashes+ or
142
+ # +Arrays+ as values.
143
+ #
144
+ def read(*field_list)
145
+ warn "[DEPRECATION] Entity#read is deprecated. Use [] and fetch instead."
146
+ if field_list.empty?
147
+ return @state["content"].clone()
148
+ else
149
+ field_list = field_list.flatten()
150
+ result = {}
151
+ field_list.each() do |key|
152
+ result[key] = fetch(key).clone()
153
+ end
154
+ return result
155
+ end
156
+ end
157
+
158
+ ##
159
+ # Returns the metadata for this +Entity+.
160
+ #
161
+ # This method is identical to
162
+ #
163
+ # entity.read('eai:acl', 'eai:attributes')
164
+ #
165
+ # Returns: a +Hash+ with the keys "+eai:acl+" and "+eai:attributes+".
166
+ #
167
+ def readmeta
168
+ read('eai:acl', 'eai:attributes')
169
+ end
170
+
171
+ ##
172
+ # Refreshes the cached state of this +Entity+.
173
+ #
174
+ # Returns: the +Entity+.
175
+ #
176
+ def refresh()
177
+ response = @service.request(:resource => @resource + [name],
178
+ :namespace => @namespace)
179
+ if response.code == 204 or response.body.nil?
180
+ # This code is here primarily to handle the case of a job not yet being
181
+ # ready, in which case you get back empty bodies.
182
+ raise EntityNotReady.new((@resource + [name]).join("/"))
183
+ end
184
+ # We are guaranteed a unique entity, since entities must have
185
+ # exact namespaces.
186
+ feed = AtomFeed.new(response.body)
187
+ @state = feed.entries[0]
188
+ self
189
+ end
190
+
191
+ ##
192
+ # Updates the values on the Entity specified in the arguments.
193
+ #
194
+ # The arguments can be either a Hash or a sequence of +key+ => +value+
195
+ # pairs. This method does not refresh the +Entity+, so if you want to see
196
+ # the new values, you must call +refresh+ yourself.
197
+ #
198
+ # Whatever values you pass will be coerced to +Strings+, so updating a
199
+ # numeric field with an Integer, for example, will work perfectly well.
200
+ #
201
+ # Returns: the +Entity+.
202
+ #
203
+ # *Example*:
204
+ # service = Splunk::connect(:username => 'admin', :password => 'foo')
205
+ # index = service.indexes['main']
206
+ # # You could use the string "61" as well here.
207
+ # index.update('rotatePeriodInSecs' => 61)
208
+ #
209
+ def update(args)
210
+ @service.request({:method => :POST,
211
+ :namespace => @namespace,
212
+ :resource => @resource + [name],
213
+ :body => args})
214
+ self
215
+ end
216
+
217
+ ##
218
+ # Updates the attribute _key_ with _value_.
219
+ #
220
+ # As for +update+, _value_ may be anything that may be coerced sensibly
221
+ # to a +String+.
222
+ #
223
+ # Returns: the new value.
224
+ #
225
+ def []=(key, value)
226
+ update(key => value)
227
+ value
228
+ end
229
+
230
+ ##
231
+ # Disables this entity.
232
+ #
233
+ # After a subsequent refresh, the "disabled" field will be set to "1".
234
+ # Note that on some entities, such as indexes in Splunk 5.x, most other
235
+ # operations do not work until it is enabled again.
236
+ #
237
+ # Returns: the +Entity+.
238
+ #
239
+ def disable
240
+ @service.request(:method => :POST,
241
+ :namespace => @namespace,
242
+ :resource => @resource + [name, "disable"])
243
+ self
244
+ end
245
+
246
+ ##
247
+ # Enables this entity.
248
+ #
249
+ # After a subsequent refresh, the "disabled" field will be set to "0".
250
+ #
251
+ # Returns: the +Entity+.
252
+ #
253
+ def enable
254
+ @service.request(:method => :POST,
255
+ :namespace => @namespace,
256
+ :resource => @resource + [name, "enable"])
257
+ self
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,191 @@
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 a class +Index+ to represent indexes on the Splunk server.
19
+ #
20
+
21
+ require_relative '../entity'
22
+
23
+ module Splunk
24
+ ##
25
+ # Class representing an index on the Splunk server.
26
+ #
27
+ # Beyond what its superclass +Entity+ provides, +Index+ also exposes methods
28
+ # to write data to an index and delete all data from an index.
29
+ #
30
+ class Index < Entity
31
+ # Opens a socket to write events to this index.
32
+ #
33
+ # Write events to the returned stream Socket, and Splunk will index the
34
+ # data. You can optionally pass a hash of _host_, _source_, and
35
+ # _sourcetype_ arguments to be sent with every event.
36
+ #
37
+ # Splunk may not index submitted events until the socket is closed or
38
+ # at least 1MB of data has been submitted.
39
+ #
40
+ # You are responsible for closing the socket.
41
+ #
42
+ # Note that +SSLSocket+ and +TCPSocket+ have incompatible APIs.
43
+ #
44
+ # Returns: an +SSLSocket+ or +TCPSocket+.
45
+ #
46
+ # *Example*:
47
+ #
48
+ # service = Splunk::connect(:username => 'admin', :password => 'foo')
49
+ # stream = service.indexes['main'].attach(:sourcetype => 'mysourcetype')
50
+ # (1..5).each { stream.write("This is a cheezy event\r\n") }
51
+ # stream.close()
52
+ #
53
+ def attach(args={})
54
+ args[:index] = @name
55
+
56
+ path = (@service.namespace.to_path_fragment() + ["receivers","stream"]).
57
+ map {|fragment| URI::encode(fragment)}.
58
+ join("/")
59
+ query = URI.encode_www_form(args)
60
+
61
+ cn = @service.connect
62
+ headers = "POST /#{path}?#{query} HTTP/1.1\r\n" +
63
+ "Host: #{@service.host}:#{@service.port}\r\n" +
64
+ "Accept-Encoding: identity\r\n" +
65
+ "Authorization: Splunk #{@service.token}\r\n" +
66
+ "X-Splunk-Input-Mode: Streaming\r\n" +
67
+ "\r\n"
68
+ cn.write(headers)
69
+ cn
70
+ end
71
+
72
+ ##
73
+ # DEPRECATED: Delete the index instead.
74
+ #
75
+ # Deletes all events in this index.
76
+ #
77
+ # The +clean+ method will wait until the operation completes, or _timeout_
78
+ # seconds have passed. By default, _timeout_ is 100 seconds.
79
+ #
80
+ # Cleaning an index is done by setting +maxTotalDataSizeMG+ and
81
+ # +frozenTimePeriodInSecs+ to +"1"+.
82
+ #
83
+ # Returns: the +Index+.
84
+ #
85
+ def clean(timeout=100)
86
+ warn "[DEPRECATION] Index#clean is deprecated. Delete the index instead."
87
+ refresh()
88
+ original_state = read(['maxTotalDataSizeMB', 'frozenTimePeriodInSecs'])
89
+ was_disabled_initially = fetch("disabled") == "1"
90
+ needed_restart_initially = @service.server_requires_restart?
91
+ if (!was_disabled_initially && @service.splunk_version[0] < 5)
92
+ disable()
93
+ end
94
+
95
+ update(:maxTotalDataSizeMB => 1, :frozenTimePeriodInSecs => 1)
96
+ roll_hot_buckets()
97
+
98
+ Timeout::timeout(timeout) do
99
+ while true
100
+ refresh()
101
+ if fetch("totalEventCount") == "0"
102
+ break
103
+ else
104
+ sleep(1)
105
+ end
106
+ end
107
+ end
108
+
109
+ # Restores the original state
110
+ if !was_disabled_initially
111
+ enable()
112
+ if !needed_restart_initially and @service.server_requires_restart?
113
+ service.request(:method => :DELETE,
114
+ :resource => ["messages", "restart_required"])
115
+ end
116
+ end
117
+ update(original_state)
118
+ end
119
+
120
+ ##
121
+ # DEPRECATED.
122
+ #
123
+ # Tells Splunk to roll the hot buckets in this index now.
124
+ #
125
+ # A Splunk index is a collection of buckets containing events. A bucket
126
+ # begins life "hot", where events may be written into it. At some point,
127
+ # when it grows to a certain size, or when +roll_hot_buckets+ is called,
128
+ # it is rolled to "warm" and a new hot bucket created. Warm buckets are
129
+ # fully accessible, but not longer receiving new events. Eventually warm
130
+ # buckets are archived to become cold buckets.
131
+ #
132
+ # Returns: the +Index+.
133
+ #
134
+ def roll_hot_buckets()
135
+ warn "[DEPRECATION] Index#roll_hot_buckets is deprecated."
136
+ @service.request(:method => :POST,
137
+ :resource => @resource + [@name, "roll-hot-buckets"])
138
+ return self
139
+ end
140
+
141
+ ##
142
+ # Writes a single event to this index.
143
+ #
144
+ # _event_ is the text of the event. You can optionally pass a hash
145
+ # with the optional keys +:host+, +:source+, and +:sourcetype+.
146
+ #
147
+ # Returns: the +Index+.
148
+ #
149
+ # *Example:*
150
+ # service = Splunk::connect(:username => 'admin', :password => 'foo')
151
+ # service.indexes['main'].submit("this is an event",
152
+ # :host => "baz",
153
+ # :sourcetype => "foo")
154
+ #
155
+ def submit(event, args={})
156
+ args[:index] = @name
157
+ @service.request(:method => :POST,
158
+ :resource => ["receivers", "simple"],
159
+ :query => args,
160
+ :body => event)
161
+ return self
162
+ end
163
+
164
+ ##
165
+ # Uploads a file accessible by the Splunk server.
166
+ #
167
+ # _filename_ should be the full path to the file on the server where
168
+ # Splunk is running. Besides _filename_, +upload+ also takes a hash of
169
+ # optional arguments, all of which take +String+s:
170
+ #
171
+ # * +:host+ - The host for the events.
172
+ # * +:host_regex+ - A regex to be used to extract a 'host' field from
173
+ # the path. If the path matches this regular expression, the captured
174
+ # value is used to populate the 'host' field or events from this data
175
+ # input. The regular expression must have one capture group.
176
+ # * +:host_segment+ - Use the specified slash-seperated segment of the
177
+ # path as the host field value.
178
+ # * +:rename-source+ - The value of the 'source' field to be applied to the
179
+ # data from this file.
180
+ # * +:sourcetype+ - The value of the 'sourcetype' field to be applied to
181
+ # data from this file.
182
+ #
183
+ def upload(filename, args={})
184
+ args['index'] = @name
185
+ args['name'] = filename
186
+ @service.request(:method => :POST,
187
+ :resource => ["data", "inputs", "oneshot"],
188
+ :body => args)
189
+ end
190
+ end
191
+ end