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,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