logstash-filter-rest2 0.5.3

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5093c9df4843c58afdcf73d5340d27b56b2df0e13df0af8f34e9d83f4346653a
4
+ data.tar.gz: ea0db1681f4c9e948c9bf02e8b628575e9319c3e1ac23855aa904cbb3f7dba0d
5
+ SHA512:
6
+ metadata.gz: 705f79dae08026d09fd0355f9caa229573cc794cfec2380d2773e34dbdd8d7c3d1b804d0ac054e95306ed772ddf5af937d9bb1f148b214d22acb1b4601e1a986
7
+ data.tar.gz: 77eed5e5c69da9a35b928f9db1f9909f36d4f42c682f49ccf58121d2e258553eaf94e3dff51999e563933d528819ce162f6a8c6b403c4ea345e0639285705fc5
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ ## 0.5.3
2
+ - freeze all instance variables
3
+ - fix parallel processing by creating a `deep_clone` for each event
4
+ - use `LogStash::Util.deep_clone` for object cloning
5
+ - only dump body as json, if json is enabled in config (default)
6
+ - delete empty target testcase, as catched by upper logstash `LogStash::ConfigurationError`
7
+ - fix `sprintf` find and merge for more complex structures
8
+
9
+ ## 0.5.2
10
+ - Fix behavior, where a referenced field (`%{...}`) has `ruby` chars
11
+ (i.e., consisting of `:`)
12
+ - Field interpolation is done by assigning explicit values instead
13
+ of converting the `sprintf` string back into a `hash`
14
+
15
+ ## 0.5.0
16
+ - Relax constraint on logstash-core-plugin-api to >= 1.60 <= 2.99
17
+ - Require devutils >= 0 to make `bundler` update the package
18
+ - Use Event API for LS-5
19
+ - Implicit `sprintf`, deprecating the setting
20
+ - `target` is now required, dropping support to write into top-level in favor of only using new Event API
21
+ - this follows other logstash-plugins like `logstash-filter-json`
22
+ - if the response is empty, add the restfailure tags
23
+ - Some logging moved before code
24
+ - 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,320 @@
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
+ public
132
+
133
+ def register
134
+ @request = normalize_request(@request).deep_freeze
135
+ @sprintf_fields = find_sprintf(
136
+ LogStash::Util.deep_clone(@request)
137
+ ).deep_freeze
138
+ @sprintf_needed = !@sprintf_fields.empty?
139
+ @target = normalize_target(@target).freeze
140
+ end # def register
141
+
142
+ private
143
+
144
+ def normalize_target(target)
145
+ # make sure @target is in the format [field name] if defined,
146
+ # i.e. not empty and surrounded by brakets
147
+ raise LogStash::ConfigurationError, 'target config string is empty, please set a valid field name' if target.empty?
148
+ target = "[#{target}]" if target && target !~ /^\[[^\[\]]+\]$/
149
+ target
150
+ end
151
+
152
+ private
153
+
154
+ def normalize_request(url_or_spec)
155
+ if url_or_spec.is_a?(String)
156
+ res = [:get, url_or_spec]
157
+ elsif url_or_spec.is_a?(Hash)
158
+ # The client will expect keys / values
159
+ spec = Hash[url_or_spec.clone.map { |k, v| [k.to_sym, v] }]
160
+
161
+ # method and url aren't really part of the options, so we pull them out
162
+ method = (spec.delete(:method) || :get).to_sym.downcase
163
+ url = spec.delete(:url)
164
+
165
+ # if it is a post and json, it is used as body string, not params
166
+ spec[:body] = spec.delete(:params) if method == :post && spec[:params]
167
+
168
+ # We need these strings to be keywords!
169
+ spec[:auth] = { user: spec[:auth]['user'], pass: spec[:auth]['password'] } if spec[:auth]
170
+
171
+ res = [method.freeze, url, spec]
172
+ else
173
+ raise LogStash::ConfigurationError, "Invalid URL or request spec: '#{url_or_spec}', expected a String or Hash!"
174
+ end
175
+
176
+ validate_request!(url_or_spec, res)
177
+ res
178
+ end
179
+
180
+ private
181
+
182
+ def validate_request!(url_or_spec, request)
183
+ method, url, spec = request
184
+
185
+ raise LogStash::ConfigurationError, "No URL provided for request! #{url_or_spec}" unless url
186
+ raise LogStash::ConfigurationError, "Not supported request method #{method}" unless [ :get, :post ].include?( method )
187
+
188
+ if spec && spec[:auth]
189
+ raise LogStash::ConfigurationError, "Auth was specified, but 'user' was not!" unless spec[:auth][:user]
190
+ raise LogStash::ConfigurationError, "Auth was specified, but 'password' was not!" unless spec[:auth][:pass]
191
+ end
192
+
193
+ request
194
+ end
195
+
196
+ private
197
+
198
+ def find_sprintf(config)
199
+ field_matcher = /%\{[^}]+\}/
200
+ if config.is_a?(Hash)
201
+ config.keep_if do |_k, v|
202
+ find_sprintf(v)
203
+ end.compact
204
+ elsif config.is_a?(Array)
205
+ config.keep_if do |v|
206
+ find_sprintf(v)
207
+ end.compact
208
+ elsif config.is_a?(String) && config =~ field_matcher
209
+ config
210
+ end
211
+ end
212
+
213
+ private
214
+
215
+ def request_http(request)
216
+ if request[2].key?(:body) && @json
217
+ request[2][:body] = LogStash::Json.dump(request[2][:body])
218
+ end
219
+ @logger.debug? && @logger.debug('fetching request',
220
+ :request => request)
221
+
222
+ method, url, *request_opts = request
223
+ response = client.http(method, url, *request_opts)
224
+ [response.code, response.body]
225
+ end
226
+
227
+ private
228
+
229
+ def process_response(response, event)
230
+ if @json
231
+ begin
232
+ parsed = LogStash::Json.load(response)
233
+ if parsed.empty?
234
+ @logger.warn('rest response empty',
235
+ :response => response, :event => event)
236
+ @tag_on_rest_failure.each { |tag| event.tag(tag) }
237
+ else
238
+ event.set(@target, parsed)
239
+ end
240
+ rescue
241
+ if @fallback.empty?
242
+ @logger.warn('JSON parsing error',
243
+ :response => response, :event => event)
244
+ @tag_on_json_failure.each { |tag| event.tag(tag) }
245
+ else
246
+ event.set(@target, @fallback)
247
+ end
248
+ end
249
+ else
250
+ event.set(@target, response.strip)
251
+ end
252
+ end
253
+
254
+ private
255
+
256
+ def field_intrpl(intrpl_fields, event)
257
+ case intrpl_fields
258
+ when String
259
+ result = event.sprintf(intrpl_fields)
260
+ when Array
261
+ result = []
262
+ intrpl_fields.each do |v|
263
+ result << field_intrpl(v, event)
264
+ end
265
+ when Hash
266
+ result = {}
267
+ intrpl_fields.each do |k, v|
268
+ result[k] = field_intrpl(v, event)
269
+ end
270
+ else
271
+ result = intrpl_fields
272
+ end
273
+ result
274
+ end
275
+
276
+ public
277
+
278
+ def filter(event)
279
+ return unless filter?(event)
280
+ request = LogStash::Util.deep_clone(@request)
281
+ @logger.debug? && @logger.debug('processing request',
282
+ :request => request,
283
+ :sprintf_needed => @sprintf_needed)
284
+
285
+ if @sprintf_needed
286
+ request = field_intrpl(request, event)
287
+ @logger.debug? && @logger.debug('interpolated request',
288
+ :request => request)
289
+ end
290
+
291
+ client_error = nil
292
+ begin
293
+ code, body = request_http(request)
294
+ rescue StandardError => e
295
+ client_error = e
296
+ end
297
+
298
+ if !client_error && code.between?(200, 299)
299
+ @logger.debug? && @logger.debug('success received',
300
+ :code => code, :body => body)
301
+ process_response(body, event)
302
+ else
303
+ @logger.debug? && @logger.debug('http error received',
304
+ :code => code, :body => body,
305
+ :client_error => client_error)
306
+ if @fallback.empty?
307
+ @logger.error('error in rest filter',
308
+ :request => request, :json => @json,
309
+ :code => code, :body => body,
310
+ :client_error => client_error)
311
+ @tag_on_rest_failure.each { |tag| event.tag(tag) }
312
+ else
313
+ @logger.debug? && @logger.debug('setting fallback',
314
+ :fallback => @fallback)
315
+ event.set(@target, @fallback)
316
+ end
317
+ end
318
+ filter_matched(event)
319
+ end # def filter
320
+ end # class LogStash::Filters::Rest
@@ -0,0 +1,33 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'logstash-filter-rest2'
3
+ s.version = '0.5.3'
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', '< 5.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-rest2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.3
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-07-04 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '1.60'
21
+ - - "<="
22
+ - !ruby/object:Gem::Version
23
+ version: '2.99'
24
+ name: logstash-core-plugin-api
25
+ prerelease: false
26
+ type: :runtime
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
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 2.2.4
41
+ - - "<"
42
+ - !ruby/object:Gem::Version
43
+ version: 5.0.0
44
+ name: logstash-mixin-http_client
45
+ prerelease: false
46
+ type: :runtime
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: 5.0.0
55
+ - !ruby/object:Gem::Dependency
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - - "<"
62
+ - !ruby/object:Gem::Version
63
+ version: 2.0.0
64
+ name: logstash-devutils
65
+ prerelease: false
66
+ type: :development
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
+ - CHANGELOG.md
84
+ - CONTRIBUTORS
85
+ - Gemfile
86
+ - LICENSE
87
+ - README.md
88
+ - lib/logstash/filters/rest.rb
89
+ - logstash-filter-rest.gemspec
90
+ - spec/filters/rest_spec.rb
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.6.11
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