logstash-filter-rest-sbt 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 12519697df5ac6e90b017304f97636754f248778
4
+ data.tar.gz: ccb2cd11e41cb68f73d8a73d5ea9575cc15ea2fb
5
+ SHA512:
6
+ metadata.gz: c83888daa52d3e97211f2070a3b2167ddec2b5325b43717672e281ee541fa6b7aad5113cad0e54504c91d144330c5c0b2cf0e74a374d8201ec05e0dc327dbcd8
7
+ data.tar.gz: 6017d5b08f68995d979731be19666ac37bae5a5385eff74f34387df27bf8d18fe478da713024a5799ca7f1fc855c512c0a9230999d7a874fa080c9b5ccf8fc39
data/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ ## 0.5.4
2
+
3
+ - update `gemspec` to work with logstash 5.5
4
+
5
+ ## 0.5.3
6
+ - freeze all instance variables
7
+ - fix parallel processing by creating a `deep_clone` for each event
8
+ - use `LogStash::Util.deep_clone` for object cloning
9
+ - only dump body as json, if json is enabled in config (default)
10
+ - delete empty target testcase, as catched by upper logstash `LogStash::ConfigurationError`
11
+ - fix `sprintf` find and merge for more complex structures
12
+
13
+ ## 0.5.2
14
+ - Fix behavior, where a referenced field (`%{...}`) has `ruby` chars
15
+ (i.e., consisting of `:`)
16
+ - Field interpolation is done by assigning explicit values instead
17
+ of converting the `sprintf` string back into a `hash`
18
+
19
+ ## 0.5.0
20
+ - Relax constraint on logstash-core-plugin-api to >= 1.60 <= 2.99
21
+ - Require devutils >= 0 to make `bundler` update the package
22
+ - Use Event API for LS-5
23
+ - Implicit `sprintf`, deprecating the setting
24
+ - `target` is now required, dropping support to write into top-level in favor of only using new Event API
25
+ - this follows other logstash-plugins like `logstash-filter-json`
26
+ - if the response is empty, add the restfailure tags
27
+ - Some logging moved before code
28
+ - Testcases adapted to new behavior with error check
data/CONTRIBUTORS ADDED
@@ -0,0 +1,11 @@
1
+ The following is a list of people who have contributed ideas, code, bug
2
+ reports, or in general have helped logstash along its way.
3
+
4
+ Contributors:
5
+ * Aaron Mildenstein (untergeek)
6
+ * Pier-Hugues Pellerin (ph)
7
+
8
+ Note: If you've sent us patches, bug reports, or otherwise contributed to
9
+ Logstash, and you aren't on the list above and want to be, please let us know
10
+ and we'll make sure you're here. Contributions from folks like you are what make
11
+ open source awesome.
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012–2015 Elasticsearch <http://www.elastic.co>
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # Logstash REST Filter [![Build Status](https://travis-ci.org/gandalfb/logstash-filter-rest.svg?branch=master)](https://travis-ci.org/gandalfb/logstash-filter-rest)
2
+
3
+ This is a filter plugin for [Logstash](https://github.com/elasticsearch/logstash).
4
+
5
+ It is fully free and fully open source. The license is Apache 2.0, meaning you are pretty much free to use it however you want in whatever way.
6
+
7
+ ## Documentation
8
+
9
+ This logstash filter provides an easy way to access RESTful Resources within logstash. It can be used to post data to a REST API or to gather data and save it in your log file.
10
+
11
+ ## Usage
12
+ ### 1. Installation
13
+ You can use the built-in plugin tool of Logstash to install the filter:
14
+ ```
15
+ $LS_HOME/bin/logstash-plugin install logstash-filter-rest
16
+ ```
17
+
18
+ Or you can build it yourself:
19
+ ```
20
+ git clone https://github.com/lucashenning/logstash-filter-rest.git
21
+ bundle install
22
+ gem build logstash-filter-rest.gemspec
23
+ $LS_HOME/bin/logstash-plugin install logstash-filter-rest-0.1.0.gem
24
+ ```
25
+
26
+ ### 2. Filter Configuration
27
+ Add the following inside the filter section of your logstash configuration:
28
+
29
+ ```sh
30
+ filter {
31
+ rest {
32
+ request => {
33
+ url => "http://example.com" # string (required, with field reference: "http://example.com?id=%{id}" or params, if defined)
34
+ method => "post" # string (optional, default = "get")
35
+ headers => { # hash (optional)
36
+ "key1" => "value1"
37
+ "key2" => "value2"
38
+ }
39
+ auth => {
40
+ user => "AzureDiamond"
41
+ password => "hunter2"
42
+ }
43
+ params => { # hash (optional, available for method => "get" and "post"; if post it will be transformed into body hash and posted as json)
44
+ "key1" => "value1"
45
+ "key2" => "value2"
46
+ "key3" => "%{somefield}" # sprintf is used implicitly
47
+ }
48
+ }
49
+ json => true # boolean (optional, default = true)
50
+ target => "my_key" # string (mandatory, no default)
51
+ fallback => { # hash describing a default in case of error
52
+ "key1" => "value1"
53
+ "key2" => "value2"
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Print plugin version:
60
+
61
+ ``` bash
62
+ bin/logstash-plugin list --verbose | grep rest
63
+ ```
64
+
65
+ Examples for running logstash from `cli`:
66
+
67
+ ``` bash
68
+ bin/logstash --debug -e 'input { stdin{} } filter { rest { request => { url => "https://jsonplaceholder.typicode.com/posts" method => "post" params => { "userId" => "%{message}" } headers => { "Content-Type" => "application/json" } } target => 'rest' } } output {stdout { codec => rubydebug }}'
69
+ ```
70
+
71
+ ``` bash
72
+ bin/logstash --debug -e 'input { stdin{} } filter { rest { request => { url => "https://jsonplaceholder.typicode.com/posts" method => "post" body => { "userId" => "%{message}" } headers => { "Content-Type" => "application/json" } } target => 'rest' } } output {stdout { codec => rubydebug }}'
73
+ ```
74
+
75
+ ``` bash
76
+ bin/logstash --debug -e 'input { stdin{} } filter { rest { request => { url => "http://jsonplaceholder.typicode.com/users/%{message}" } target => 'rest' } } output {stdout { codec => rubydebug }}'
77
+ ```
78
+
79
+ ``` bash
80
+ bin/logstash --debug -e 'input { stdin{} } filter { rest { request => { url => "https://jsonplaceholder.typicode.com/posts" method => "get" params => { "userId" => "%{message}" } headers => { "Content-Type" => "application/json" } } target => 'rest' } } output {stdout { codec => rubydebug }}'
81
+ ```
82
+
83
+
84
+ ## Contributing
85
+
86
+ All contributions are welcome: ideas, patches, documentation, bug reports, complaints, and even something you drew up on a napkin.
87
+
88
+ Programming is not a required skill. Whatever you've seen about open source and maintainers or community members saying "send patches or die" - you will not see that here.
89
+
90
+ It is more important to the community that you are able to contribute.
91
+
92
+ For more information about contributing, see the [CONTRIBUTING](https://github.com/elasticsearch/logstash/blob/master/CONTRIBUTING.md) file.
@@ -0,0 +1,330 @@
1
+ # encoding: utf-8
2
+ require 'logstash/filters/base'
3
+ require 'logstash/namespace'
4
+ require 'logstash/plugin_mixins/http_client'
5
+ require 'logstash/json'
6
+
7
+ # Monkey Patch hsh with a recursive compact and deep freeze
8
+ class Hash
9
+ def compact
10
+ delete_if { |_k, v| v.respond_to?(:each) ? v.compact.empty? : v.nil? }
11
+ end
12
+
13
+ def deep_freeze
14
+ each { |_k, v| v.deep_freeze if v.respond_to? :deep_freeze }
15
+ freeze
16
+ end
17
+ end
18
+
19
+ # Monkey Patch Array with deep freeze
20
+ class Array
21
+ def compact
22
+ delete_if { |v| v.respond_to?(:each) ? v.compact.empty? : v.nil? }
23
+ end
24
+
25
+ def deep_freeze
26
+ each { |j| j.deep_freeze if j.respond_to? :deep_freeze }
27
+ freeze
28
+ end
29
+ end
30
+
31
+ # Logstash REST Filter
32
+ # This filter calls a defined URL and saves the answer into a specified field.
33
+ #
34
+ class LogStash::Filters::Rest < LogStash::Filters::Base
35
+ include LogStash::PluginMixins::HttpClient
36
+
37
+ config_name 'rest'
38
+
39
+ # Configure the rest request send via HttpClient Plugin
40
+ # with hash objects used by the mixin plugin
41
+ #
42
+ # For example, if you want the data to be put in the `doc` field:
43
+ # [source,ruby]
44
+ # filter {
45
+ # rest {
46
+ # request => {
47
+ # url => "http://example.com" # string (required, with field reference: "http://example.com?id=%{id}" or params, if defined)
48
+ # method => "post" # string (optional, default = "get")
49
+ # headers => { # hash (optional)
50
+ # "key1" => "value1"
51
+ # "key2" => "value2"
52
+ # }
53
+ # auth => {
54
+ # user => "AzureDiamond"
55
+ # password => "hunter2"
56
+ # }
57
+ # params => { # hash (optional, available for method => "get" and "post"; if post it will be transformed into body hash and posted as json)
58
+ # "key1" => "value1"
59
+ # "key2" => "value2"
60
+ # "key3" => "%{somefield}" # Field references are found implicitly on startup
61
+ # }
62
+ # }
63
+ # target => "doc"
64
+ # }
65
+ # }
66
+ #
67
+ # NOTE: for further details, please reference https://github.com/logstash-plugins/logstash-mixin-http_client[logstash-mixin-http_client]
68
+ config :request, :validate => :hash, :required => true
69
+
70
+ # The plugin is written json centric, which defaults to true
71
+ # the response body will be parsed to json if true
72
+ #
73
+ # [source,ruby]
74
+ # filter {
75
+ # rest {
76
+ # json => true
77
+ # }
78
+ # }
79
+ config :json, :validate => :boolean, :default => true
80
+
81
+ # If true, references to event fields can be made in
82
+ # url, params or body by using '%{somefield}'
83
+ #
84
+ # [source,ruby]
85
+ # filter {
86
+ # rest {
87
+ # request => { .. }
88
+ # sprintf => true
89
+ # }
90
+ # }
91
+ config :sprintf, :validate => :boolean, :default => false, :deprecated => true
92
+
93
+ # Define the target field for placing the response data. This setting is
94
+ # required and may not be omitted. It is not possible to place the response
95
+ # into the event top-level.
96
+ #
97
+ # For example, if you want the data to be put in the `doc` field:
98
+ # [source,ruby]
99
+ # filter {
100
+ # rest {
101
+ # target => "doc"
102
+ # }
103
+ # }
104
+ #
105
+ # Rest response will be expanded into a data structure in the `target` field.
106
+ #
107
+ # NOTE: if the `target` field already exists, it will be overwritten!
108
+ config :target, :validate => :string, :required => true
109
+
110
+ # If set, any error like json parsing or invalid http response
111
+ # will result in this hash to be added to target instead of error tags
112
+ #
113
+ # For example, if you want the fallback data to be put in the `target` field:
114
+ # [source,ruby]
115
+ # filter {
116
+ # rest {
117
+ # fallback => {
118
+ # 'key1' => 'value1'
119
+ # 'key2' => 'value2'
120
+ # ...
121
+ # }
122
+ # }
123
+ # }
124
+ config :fallback, :validate => :hash, :default => {}
125
+
126
+ # Append values to the `tags` field when there has been no
127
+ # successful match or json parsing error
128
+ config :tag_on_rest_failure, :validate => :array, :default => ['_restfailure']
129
+ config :tag_on_json_failure, :validate => :array, :default => ['_jsonparsefailure']
130
+
131
+ # If you need to use a custom truststore (`.jks`) specify that here. This does not work with .pem certs!
132
+ config :truststore, :validate => :path
133
+
134
+ # Specify the truststore password here.
135
+ # Note, most .jks files created with keytool require a password!
136
+ config :truststore_password, :validate => :password
137
+
138
+ # Specify the truststore type here. One of `JKS` or `PKCS12`. Default is `JKS`
139
+ config :truststore_type, :validate => :string, :default => "JKS"
140
+
141
+ public
142
+
143
+ def register
144
+ @request = normalize_request(@request).deep_freeze
145
+ @sprintf_fields = find_sprintf(
146
+ LogStash::Util.deep_clone(@request)
147
+ ).deep_freeze
148
+ @sprintf_needed = !@sprintf_fields.empty?
149
+ @target = normalize_target(@target).freeze
150
+ end # def register
151
+
152
+ private
153
+
154
+ def normalize_target(target)
155
+ # make sure @target is in the format [field name] if defined,
156
+ # i.e. not empty and surrounded by brakets
157
+ raise LogStash::ConfigurationError, 'target config string is empty, please set a valid field name' if target.empty?
158
+ target = "[#{target}]" if target && target !~ /^\[[^\[\]]+\]$/
159
+ target
160
+ end
161
+
162
+ private
163
+
164
+ def normalize_request(url_or_spec)
165
+ if url_or_spec.is_a?(String)
166
+ res = [:get, url_or_spec]
167
+ elsif url_or_spec.is_a?(Hash)
168
+ # The client will expect keys / values
169
+ spec = Hash[url_or_spec.clone.map { |k, v| [k.to_sym, v] }]
170
+
171
+ # method and url aren't really part of the options, so we pull them out
172
+ method = (spec.delete(:method) || :get).to_sym.downcase
173
+ url = spec.delete(:url)
174
+
175
+ # if it is a post and json, it is used as body string, not params
176
+ spec[:body] = spec.delete(:params) if method == :post && spec[:params]
177
+
178
+ # We need these strings to be keywords!
179
+ spec[:auth] = { user: spec[:auth]['user'], pass: spec[:auth]['password'] } if spec[:auth]
180
+
181
+ res = [method.freeze, url, spec]
182
+ else
183
+ raise LogStash::ConfigurationError, "Invalid URL or request spec: '#{url_or_spec}', expected a String or Hash!"
184
+ end
185
+
186
+ validate_request!(url_or_spec, res)
187
+ res
188
+ end
189
+
190
+ private
191
+
192
+ def validate_request!(url_or_spec, request)
193
+ method, url, spec = request
194
+
195
+ raise LogStash::ConfigurationError, "No URL provided for request! #{url_or_spec}" unless url
196
+ raise LogStash::ConfigurationError, "Not supported request method #{method}" unless [ :get, :post ].include?( method )
197
+
198
+ if spec && spec[:auth]
199
+ raise LogStash::ConfigurationError, "Auth was specified, but 'user' was not!" unless spec[:auth][:user]
200
+ raise LogStash::ConfigurationError, "Auth was specified, but 'password' was not!" unless spec[:auth][:pass]
201
+ end
202
+
203
+ request
204
+ end
205
+
206
+ private
207
+
208
+ def find_sprintf(config)
209
+ field_matcher = /%\{[^}]+\}/
210
+ if config.is_a?(Hash)
211
+ config.keep_if do |_k, v|
212
+ find_sprintf(v)
213
+ end.compact
214
+ elsif config.is_a?(Array)
215
+ config.keep_if do |v|
216
+ find_sprintf(v)
217
+ end.compact
218
+ elsif config.is_a?(String) && config =~ field_matcher
219
+ config
220
+ end
221
+ end
222
+
223
+ private
224
+
225
+ def request_http(request)
226
+ if request[2].key?(:body) && @json
227
+ request[2][:body] = LogStash::Json.dump(request[2][:body])
228
+ end
229
+ @logger.debug? && @logger.debug('fetching request',
230
+ :request => request)
231
+
232
+ method, url, *request_opts = request
233
+ response = client.http(method, url, *request_opts)
234
+ [response.code, response.body]
235
+ end
236
+
237
+ private
238
+
239
+ def process_response(response, event)
240
+ if @json
241
+ begin
242
+ parsed = LogStash::Json.load(response)
243
+ if parsed.empty?
244
+ @logger.warn('rest response empty',
245
+ :response => response, :event => event)
246
+ @tag_on_rest_failure.each { |tag| event.tag(tag) }
247
+ else
248
+ event.set(@target, parsed)
249
+ end
250
+ rescue
251
+ if @fallback.empty?
252
+ @logger.warn('JSON parsing error',
253
+ :response => response, :event => event)
254
+ @tag_on_json_failure.each { |tag| event.tag(tag) }
255
+ else
256
+ event.set(@target, @fallback)
257
+ end
258
+ end
259
+ else
260
+ event.set(@target, response.strip)
261
+ end
262
+ end
263
+
264
+ private
265
+
266
+ def field_intrpl(intrpl_fields, event)
267
+ case intrpl_fields
268
+ when String
269
+ result = event.sprintf(intrpl_fields)
270
+ when Array
271
+ result = []
272
+ intrpl_fields.each do |v|
273
+ result << field_intrpl(v, event)
274
+ end
275
+ when Hash
276
+ result = {}
277
+ intrpl_fields.each do |k, v|
278
+ result[k] = field_intrpl(v, event)
279
+ end
280
+ else
281
+ result = intrpl_fields
282
+ end
283
+ result
284
+ end
285
+
286
+ public
287
+
288
+ def filter(event)
289
+ return unless filter?(event)
290
+ request = LogStash::Util.deep_clone(@request)
291
+ @logger.debug? && @logger.debug('processing request',
292
+ :request => request,
293
+ :sprintf_needed => @sprintf_needed)
294
+
295
+ if @sprintf_needed
296
+ request = field_intrpl(request, event)
297
+ @logger.debug? && @logger.debug('interpolated request',
298
+ :request => request)
299
+ end
300
+
301
+ client_error = nil
302
+ begin
303
+ code, body = request_http(request)
304
+ rescue StandardError => e
305
+ client_error = e
306
+ end
307
+
308
+ if !client_error && code.between?(200, 299)
309
+ @logger.debug? && @logger.debug('success received',
310
+ :code => code, :body => body)
311
+ process_response(body, event)
312
+ else
313
+ @logger.debug? && @logger.debug('http error received',
314
+ :code => code, :body => body,
315
+ :client_error => client_error)
316
+ if @fallback.empty?
317
+ @logger.error('error in rest filter',
318
+ :request => request, :json => @json,
319
+ :code => code, :body => body,
320
+ :client_error => client_error)
321
+ @tag_on_rest_failure.each { |tag| event.tag(tag) }
322
+ else
323
+ @logger.debug? && @logger.debug('setting fallback',
324
+ :fallback => @fallback)
325
+ event.set(@target, @fallback)
326
+ end
327
+ end
328
+ filter_matched(event)
329
+ end # def filter
330
+ end # class LogStash::Filters::Rest
@@ -0,0 +1,33 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-filter-rest-sbt'
3
+ s.version = '0.5.5'
4
+ s.licenses = ['Apache-2.0']
5
+ s.summary = 'This filter requests data from a RESTful Web Service.'
6
+ s.description = 'This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install logstash-filter-rest2. This gem is not a stand-alone program'
7
+ s.authors = ['Lucas Henning', 'Gandalf Buscher', 'Boris Gorbylev']
8
+ s.email = 'ekho@ekho.name'
9
+ s.homepage = 'https://github.com/ekho/logstash-filter-rest/'
10
+ s.require_paths = ['lib']
11
+
12
+ # Files
13
+ s.files = Dir['lib/**/*',
14
+ 'spec/**/*',
15
+ 'vendor/**/*',
16
+ '*.gemspec',
17
+ '*.md',
18
+ 'CONTRIBUTORS',
19
+ 'Gemfile',
20
+ 'LICENSE',
21
+ 'NOTICE.TXT']
22
+ # Tests
23
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
24
+
25
+ # Special flag to let us know this is actually a logstash plugin
26
+ s.metadata = { 'logstash_plugin' => 'true', 'logstash_group' => 'filter' }
27
+
28
+ # Gem dependencies
29
+ s.add_runtime_dependency 'logstash-core-plugin-api', '>= 1.60', '<= 2.99'
30
+ s.add_runtime_dependency 'logstash-mixin-http_client', '>= 2.2.4', '< 6.0.0'
31
+
32
+ s.add_development_dependency 'logstash-devutils', '>= 0', '< 2.0.0'
33
+ end
@@ -0,0 +1,406 @@
1
+ require 'logstash/devutils/rspec/spec_helper'
2
+ require 'logstash/filters/rest'
3
+
4
+ describe LogStash::Filters::Rest do
5
+ describe 'Set to Rest Filter Get without params' do
6
+ let(:config) do <<-CONFIG
7
+ filter {
8
+ rest {
9
+ request => {
10
+ url => 'http://jsonplaceholder.typicode.com/users/10'
11
+ }
12
+ json => true
13
+ target => 'rest'
14
+ }
15
+ }
16
+ CONFIG
17
+ end
18
+
19
+ sample('message' => 'some text') do
20
+ expect(subject).to include('rest')
21
+ expect(subject.get('rest')).to include('id')
22
+ expect(subject.get('[rest][id]')).to eq(10)
23
+ expect(subject.get('rest')).to_not include('fallback')
24
+ end
25
+ end
26
+ describe 'Set to Rest Filter Get without params custom target' do
27
+ let(:config) do <<-CONFIG
28
+ filter {
29
+ rest {
30
+ request => {
31
+ url => 'http://jsonplaceholder.typicode.com/users/10'
32
+ }
33
+ json => true
34
+ target => 'testing'
35
+ }
36
+ }
37
+ CONFIG
38
+ end
39
+
40
+ sample('message' => 'some text') do
41
+ expect(subject).to include('testing')
42
+ expect(subject.get('testing')).to include('id')
43
+ expect(subject.get('[testing][id]')).to eq(10)
44
+ expect(subject.get('testing')).to_not include('fallback')
45
+ end
46
+ end
47
+ describe 'Set to Rest Filter Get without params and sprintf' do
48
+ let(:config) do <<-CONFIG
49
+ filter {
50
+ rest {
51
+ request => {
52
+ url => "http://jsonplaceholder.typicode.com/users/%{message}"
53
+ }
54
+ json => true
55
+ sprintf => true
56
+ target => 'rest'
57
+ }
58
+ }
59
+ CONFIG
60
+ end
61
+
62
+ sample('message' => '10') do
63
+ expect(subject).to include('rest')
64
+ expect(subject.get('rest')).to include('id')
65
+ expect(subject.get('[rest][id]')).to eq(10)
66
+ expect(subject.get('rest')).to_not include('fallback')
67
+ end
68
+ sample('message' => '9') do
69
+ expect(subject).to include('rest')
70
+ expect(subject.get('rest')).to include('id')
71
+ expect(subject.get('[rest][id]')).to eq(9)
72
+ expect(subject.get('rest')).to_not include('fallback')
73
+ end
74
+ end
75
+ describe 'Set to Rest Filter Get without params http error' do
76
+ let(:config) do <<-CONFIG
77
+ filter {
78
+ rest {
79
+ request => {
80
+ url => 'http://httpstat.us/404'
81
+ }
82
+ json => true
83
+ target => 'rest'
84
+ }
85
+ }
86
+ CONFIG
87
+ end
88
+
89
+ sample('message' => 'some text') do
90
+ expect(subject).to_not include('rest')
91
+ expect(subject.get('tags')).to include('_restfailure')
92
+ end
93
+ end
94
+ describe 'Set to Rest Filter Get with params' do
95
+ let(:config) do <<-CONFIG
96
+ filter {
97
+ rest {
98
+ request => {
99
+ url => 'https://jsonplaceholder.typicode.com/posts'
100
+ params => {
101
+ userId => 10
102
+ }
103
+ headers => {
104
+ 'Content-Type' => 'application/json'
105
+ }
106
+ }
107
+ json => true
108
+ target => 'rest'
109
+ }
110
+ }
111
+ CONFIG
112
+ end
113
+
114
+ sample('message' => 'some text') do
115
+ expect(subject).to include('rest')
116
+ expect(subject.get('[rest][0]')).to include('userId')
117
+ expect(subject.get('[rest][0][userId]')).to eq(10)
118
+ expect(subject.get('rest')).to_not include('fallback')
119
+ end
120
+ end
121
+ describe 'empty response' do
122
+ let(:config) do <<-CONFIG
123
+ filter {
124
+ rest {
125
+ request => {
126
+ url => 'https://jsonplaceholder.typicode.com/posts'
127
+ params => {
128
+ userId => 0
129
+ }
130
+ headers => {
131
+ 'Content-Type' => 'application/json'
132
+ }
133
+ }
134
+ target => 'rest'
135
+ }
136
+ }
137
+ CONFIG
138
+ end
139
+
140
+ sample('message' => 'some text') do
141
+ expect(subject).to_not include('rest')
142
+ expect(subject.get('tags')).to include('_restfailure')
143
+ end
144
+ end
145
+ describe 'Set to Rest Filter Get with params sprintf' do
146
+ let(:config) do <<-CONFIG
147
+ filter {
148
+ rest {
149
+ request => {
150
+ url => 'https://jsonplaceholder.typicode.com/posts'
151
+ params => {
152
+ userId => "%{message}"
153
+ id => "%{message}"
154
+ }
155
+ headers => {
156
+ 'Content-Type' => 'application/json'
157
+ }
158
+ }
159
+ json => true
160
+ target => 'rest'
161
+ }
162
+ }
163
+ CONFIG
164
+ end
165
+
166
+ sample('message' => '1') do
167
+ expect(subject).to include('rest')
168
+ expect(subject.get('[rest][0]')).to include('userId')
169
+ expect(subject.get('[rest][0][userId]')).to eq(1)
170
+ expect(subject.get('[rest][0][id]')).to eq(1)
171
+ expect(subject.get('rest').length).to eq(1)
172
+ expect(subject.get('rest')).to_not include('fallback')
173
+ end
174
+ end
175
+ describe 'Set to Rest Filter Post with params' do
176
+ let(:config) do <<-CONFIG
177
+ filter {
178
+ rest {
179
+ request => {
180
+ url => 'https://jsonplaceholder.typicode.com/posts'
181
+ method => 'post'
182
+ params => {
183
+ title => 'foo'
184
+ body => 'bar'
185
+ userId => 42
186
+ }
187
+ headers => {
188
+ 'Content-Type' => 'application/json'
189
+ }
190
+ }
191
+ json => true
192
+ target => 'rest'
193
+ }
194
+ }
195
+ CONFIG
196
+ end
197
+
198
+ sample('message' => 'some text') do
199
+ expect(subject).to include('rest')
200
+ expect(subject.get('rest')).to include('id')
201
+ expect(subject.get('[rest][userId]')).to eq(42)
202
+ expect(subject.get('rest')).to_not include('fallback')
203
+ end
204
+ end
205
+ describe 'Set to Rest Filter Post with params sprintf' do
206
+ let(:config) do <<-CONFIG
207
+ filter {
208
+ rest {
209
+ request => {
210
+ url => 'https://jsonplaceholder.typicode.com/posts'
211
+ method => 'post'
212
+ params => {
213
+ title => '%{message}'
214
+ body => 'bar'
215
+ userId => "%{message}"
216
+ }
217
+ headers => {
218
+ 'Content-Type' => 'application/json'
219
+ }
220
+ }
221
+ json => true
222
+ target => 'rest'
223
+ }
224
+ }
225
+ CONFIG
226
+ end
227
+
228
+ sample('message' => '42') do
229
+ expect(subject).to include('rest')
230
+ expect(subject.get('rest')).to include('id')
231
+ expect(subject.get('[rest][title]')).to eq(42)
232
+ expect(subject.get('[rest][userId]')).to eq(42)
233
+ expect(subject.get('rest')).to_not include('fallback')
234
+ end
235
+ sample('message' => ':5e?#!-_') do
236
+ expect(subject).to include('rest')
237
+ expect(subject.get('rest')).to include('id')
238
+ expect(subject.get('[rest][title]')).to eq(':5e?#!-_')
239
+ expect(subject.get('[rest][userId]')).to eq(':5e?#!-_')
240
+ expect(subject.get('rest')).to_not include('fallback')
241
+ end
242
+ sample('message' => ':4c43=>') do
243
+ expect(subject).to include('rest')
244
+ expect(subject.get('rest')).to include('id')
245
+ expect(subject.get('[rest][title]')).to eq(':4c43=>')
246
+ expect(subject.get('[rest][userId]')).to eq(':4c43=>')
247
+ expect(subject.get('rest')).to_not include('fallback')
248
+ end
249
+ end
250
+ describe 'Set to Rest Filter Post with body sprintf' do
251
+ let(:config) do <<-CONFIG
252
+ filter {
253
+ rest {
254
+ request => {
255
+ url => 'https://jsonplaceholder.typicode.com/posts'
256
+ method => 'post'
257
+ body => {
258
+ title => 'foo'
259
+ body => 'bar'
260
+ userId => "%{message}"
261
+ }
262
+ headers => {
263
+ 'Content-Type' => 'application/json'
264
+ }
265
+ }
266
+ json => true
267
+ target => 'rest'
268
+ }
269
+ }
270
+ CONFIG
271
+ end
272
+
273
+ sample('message' => '42') do
274
+ expect(subject).to include('rest')
275
+ expect(subject.get('rest')).to include('id')
276
+ expect(subject.get('[rest][userId]')).to eq(42)
277
+ expect(subject.get('rest')).to_not include('fallback')
278
+ end
279
+ end
280
+ describe 'Set to Rest Filter Post with body sprintf nested params' do
281
+ let(:config) do <<-CONFIG
282
+ filter {
283
+ rest {
284
+ request => {
285
+ url => 'https://jsonplaceholder.typicode.com/posts'
286
+ method => 'post'
287
+ body => {
288
+ key1 => [
289
+ {
290
+ "filterType" => "text"
291
+ "text" => "salmon"
292
+ "boolean" => false
293
+ },
294
+ {
295
+ "filterType" => "unique"
296
+ }
297
+ ]
298
+ key2 => [
299
+ {
300
+ "message" => "123%{message}"
301
+ "boolean" => true
302
+ }
303
+ ]
304
+ key3 => [
305
+ {
306
+ "text" => "%{message}123"
307
+ "filterType" => "text"
308
+ "number" => 44
309
+ },
310
+ {
311
+ "filterType" => "unique"
312
+ "null" => nil
313
+ }
314
+ ]
315
+ userId => "%{message}"
316
+ }
317
+ headers => {
318
+ 'Content-Type' => 'application/json'
319
+ }
320
+ }
321
+ target => 'rest'
322
+ }
323
+ }
324
+ CONFIG
325
+ end
326
+
327
+ sample('message' => '42') do
328
+ expect(subject).to include('rest')
329
+ expect(subject.get('rest')).to include('key1')
330
+ expect(subject.get('[rest][key1][0][boolean]')).to eq('false')
331
+ expect(subject.get('[rest][key1][1][filterType]')).to eq('unique')
332
+ expect(subject.get('[rest][key2][0][message]')).to eq('12342')
333
+ expect(subject.get('[rest][key2][0][boolean]')).to eq('true')
334
+ expect(subject.get('[rest][key3][0][text]')).to eq('42123')
335
+ expect(subject.get('[rest][key3][0][filterType]')).to eq('text')
336
+ expect(subject.get('[rest][key3][0][number]')).to eq(44)
337
+ expect(subject.get('[rest][key3][1][filterType]')).to eq('unique')
338
+ expect(subject.get('[rest][key3][1][null]')).to eq('nil')
339
+ expect(subject.get('[rest][userId]')).to eq(42)
340
+ expect(subject.get('rest')).to_not include('fallback')
341
+ end
342
+ end
343
+ describe 'fallback' do
344
+ let(:config) do <<-CONFIG
345
+ filter {
346
+ rest {
347
+ request => {
348
+ url => 'http://jsonplaceholder.typicode.com/users/0'
349
+ }
350
+ json => true
351
+ fallback => {
352
+ 'fallback1' => true
353
+ 'fallback2' => true
354
+ }
355
+ target => 'rest'
356
+ }
357
+ }
358
+ CONFIG
359
+ end
360
+
361
+ sample('message' => 'some text') do
362
+ expect(subject).to include('rest')
363
+ expect(subject.get('rest')).to include('fallback1')
364
+ expect(subject.get('rest')).to include('fallback2')
365
+ expect(subject.get('rest')).to_not include('id')
366
+ end
367
+ end
368
+ describe 'empty target exception' do
369
+ let(:config) do <<-CONFIG
370
+ filter {
371
+ rest {
372
+ request => {
373
+ url => 'http://jsonplaceholder.typicode.com/users/0'
374
+ }
375
+ json => true
376
+ fallback => {
377
+ 'fallback1' => true
378
+ 'fallback2' => true
379
+ }
380
+ target => ''
381
+ }
382
+ }
383
+ CONFIG
384
+ end
385
+ sample('message' => 'some text') do
386
+ expect { subject }.to raise_error(LogStash::ConfigurationError)
387
+ end
388
+ end
389
+ describe 'http client throws exception' do
390
+ let(:config) do <<-CONFIG
391
+ filter {
392
+ rest {
393
+ request => {
394
+ url => 'invalid_url'
395
+ }
396
+ target => 'rest'
397
+ }
398
+ }
399
+ CONFIG
400
+ end
401
+ sample('message' => 'some text') do
402
+ expect(subject).to_not include('rest')
403
+ expect(subject.get('tags')).to include('_restfailure')
404
+ end
405
+ end
406
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logstash-filter-rest-sbt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.5
5
+ platform: ruby
6
+ authors:
7
+ - Lucas Henning
8
+ - Gandalf Buscher
9
+ - Boris Gorbylev
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2017-10-23 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: logstash-core-plugin-api
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '1.60'
22
+ - - <=
23
+ - !ruby/object:Gem::Version
24
+ version: '2.99'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - '>='
30
+ - !ruby/object:Gem::Version
31
+ version: '1.60'
32
+ - - <=
33
+ - !ruby/object:Gem::Version
34
+ version: '2.99'
35
+ - !ruby/object:Gem::Dependency
36
+ name: logstash-mixin-http_client
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: 2.2.4
42
+ - - <
43
+ - !ruby/object:Gem::Version
44
+ version: 6.0.0
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - '>='
50
+ - !ruby/object:Gem::Version
51
+ version: 2.2.4
52
+ - - <
53
+ - !ruby/object:Gem::Version
54
+ version: 6.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: logstash-devutils
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - - <
63
+ - !ruby/object:Gem::Version
64
+ version: 2.0.0
65
+ type: :development
66
+ prerelease: false
67
+ version_requirements: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ - - <
73
+ - !ruby/object:Gem::Version
74
+ version: 2.0.0
75
+ description: This gem is a logstash plugin required to be installed on top of the
76
+ Logstash core pipeline using $LS_HOME/bin/logstash-plugin install logstash-filter-rest2.
77
+ This gem is not a stand-alone program
78
+ email: ekho@ekho.name
79
+ executables: []
80
+ extensions: []
81
+ extra_rdoc_files: []
82
+ files:
83
+ - lib/logstash/filters/rest.rb
84
+ - spec/filters/rest_spec.rb
85
+ - logstash-filter-rest.gemspec
86
+ - CHANGELOG.md
87
+ - README.md
88
+ - CONTRIBUTORS
89
+ - Gemfile
90
+ - LICENSE
91
+ homepage: https://github.com/ekho/logstash-filter-rest/
92
+ licenses:
93
+ - Apache-2.0
94
+ metadata:
95
+ logstash_plugin: 'true'
96
+ logstash_group: filter
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.0.14.1
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: This filter requests data from a RESTful Web Service.
117
+ test_files:
118
+ - spec/filters/rest_spec.rb