logstash-filter-elasticsearch 4.2.0 → 4.3.1

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.
@@ -61,7 +61,7 @@ describe LogStash::Filters::Elasticsearch do
61
61
  allow(filter_client).to receive(:serverless?).and_return(true)
62
62
  allow(filter_client).to receive(:client).and_return(es_client)
63
63
 
64
- if elastic_ruby_v8_client_available?
64
+ if defined?(Elastic::Transport)
65
65
  allow(es_client).to receive(:info)
66
66
  .with(a_hash_including(
67
67
  :headers => LogStash::Filters::ElasticsearchClient::DEFAULT_EAV_HEADER))
@@ -93,306 +93,6 @@ describe LogStash::Filters::Elasticsearch do
93
93
  end
94
94
  end
95
95
 
96
- describe "data fetch" do
97
- let(:config) do
98
- {
99
- "hosts" => ["localhost:9200"],
100
- "query" => "response: 404",
101
- "fields" => { "response" => "code" },
102
- "docinfo_fields" => { "_index" => "es_index" },
103
- "aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
104
- }
105
- end
106
-
107
- let(:response) do
108
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_1.json")))
109
- end
110
-
111
- let(:client) { double(:client) }
112
-
113
- before(:each) do
114
- allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
115
- if elastic_ruby_v8_client_available?
116
- allow(client).to receive(:es_transport_client_type).and_return('elastic_transport')
117
- else
118
- allow(client).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
119
- end
120
- allow(client).to receive(:search).and_return(response)
121
- allow(plugin).to receive(:test_connection!)
122
- allow(plugin).to receive(:setup_serverless)
123
- plugin.register
124
- end
125
-
126
- after(:each) do
127
- Thread.current[:filter_elasticsearch_client] = nil
128
- end
129
-
130
- it "should enhance the current event with new data" do
131
- plugin.filter(event)
132
- expect(event.get("code")).to eq(404)
133
- expect(event.get("es_index")).to eq("logstash-2014.08.26")
134
- expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
135
- end
136
-
137
- it "should receive all necessary params to perform the search" do
138
- expect(client).to receive(:search).with({:q=>"response: 404", :size=>1, :index=>"", :sort=>"@timestamp:desc"})
139
- plugin.filter(event)
140
- end
141
-
142
- context "when asking to hit specific index" do
143
-
144
- let(:config) do
145
- {
146
- "index" => "foo*",
147
- "hosts" => ["localhost:9200"],
148
- "query" => "response: 404",
149
- "fields" => { "response" => "code" }
150
- }
151
- end
152
-
153
- it "should receive all necessary params to perform the search" do
154
- expect(client).to receive(:search).with({:q=>"response: 404", :size=>1, :index=>"foo*", :sort=>"@timestamp:desc"})
155
- plugin.filter(event)
156
- end
157
- end
158
-
159
- context "when asking for more than one result" do
160
-
161
- let(:config) do
162
- {
163
- "hosts" => ["localhost:9200"],
164
- "query" => "response: 404",
165
- "fields" => { "response" => "code" },
166
- "result_size" => 10
167
- }
168
- end
169
-
170
- let(:response) do
171
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_10.json")))
172
- end
173
-
174
- it "should enhance the current event with new data" do
175
- plugin.filter(event)
176
- expect(event.get("code")).to eq([404]*10)
177
- end
178
- end
179
-
180
- context 'when Elasticsearch 7.x gives us a totals object instead of an integer' do
181
- let(:config) do
182
- {
183
- "hosts" => ["localhost:9200"],
184
- "query" => "response: 404",
185
- "fields" => { "response" => "code" },
186
- "result_size" => 10
187
- }
188
- end
189
-
190
- let(:response) do
191
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "elasticsearch_7.x_hits_total_as_object.json")))
192
- end
193
-
194
- it "should enhance the current event with new data" do
195
- plugin.filter(event)
196
- expect(event.get("[@metadata][total_hits]")).to eq(13476)
197
- end
198
- end
199
-
200
- context "if something wrong happen during connection" do
201
-
202
- before(:each) do
203
- allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
204
- allow(client).to receive(:search).and_raise("connection exception")
205
- plugin.register
206
- end
207
-
208
- it "tag the event as something happened, but still deliver it" do
209
- expect(plugin.logger).to receive(:warn)
210
- plugin.filter(event)
211
- expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
212
- end
213
- end
214
-
215
- # Tagging test for positive results
216
- context "Tagging should occur if query returns results" do
217
- let(:config) do
218
- {
219
- "index" => "foo*",
220
- "hosts" => ["localhost:9200"],
221
- "query" => "response: 404",
222
- "add_tag" => ["tagged"]
223
- }
224
- end
225
-
226
- let(:response) do
227
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_10.json")))
228
- end
229
-
230
- it "should tag the current event if results returned" do
231
- plugin.filter(event)
232
- expect(event.to_hash["tags"]).to include("tagged")
233
- end
234
- end
235
-
236
- context "an aggregation search with size 0 that matches" do
237
- let(:config) do
238
- {
239
- "index" => "foo*",
240
- "hosts" => ["localhost:9200"],
241
- "query" => "response: 404",
242
- "add_tag" => ["tagged"],
243
- "result_size" => 0,
244
- "aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
245
- }
246
- end
247
-
248
- let(:response) do
249
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_size0_agg.json")))
250
- end
251
-
252
- it "should tag the current event" do
253
- plugin.filter(event)
254
- expect(event.get("tags")).to include("tagged")
255
- expect(event.get("bytes_avg_ls_field")["value"]).to eq(294)
256
- end
257
- end
258
-
259
- # Tagging test for negative results
260
- context "Tagging should not occur if query has no results" do
261
- let(:config) do
262
- {
263
- "index" => "foo*",
264
- "hosts" => ["localhost:9200"],
265
- "query" => "response: 404",
266
- "add_tag" => ["tagged"]
267
- }
268
- end
269
-
270
- let(:response) do
271
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
272
- end
273
-
274
- it "should not tag the current event" do
275
- plugin.filter(event)
276
- expect(event.to_hash["tags"]).to_not include("tagged")
277
- end
278
- end
279
- context "testing a simple query template" do
280
- let(:config) do
281
- {
282
- "hosts" => ["localhost:9200"],
283
- "query_template" => File.join(File.dirname(__FILE__), "fixtures", "query_template.json"),
284
- "fields" => { "response" => "code" },
285
- "result_size" => 1
286
- }
287
- end
288
-
289
- let(:response) do
290
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_x_1.json")))
291
- end
292
-
293
- it "should enhance the current event with new data" do
294
- plugin.filter(event)
295
- expect(event.get("code")).to eq(404)
296
- end
297
-
298
- end
299
-
300
- context "testing a simple index substitution" do
301
- let(:event) {
302
- LogStash::Event.new(
303
- {
304
- "subst_field" => "subst_value"
305
- }
306
- )
307
- }
308
- let(:config) do
309
- {
310
- "index" => "foo_%{subst_field}*",
311
- "hosts" => ["localhost:9200"],
312
- "query" => "response: 404",
313
- "fields" => { "response" => "code" }
314
- }
315
- end
316
-
317
- it "should receive substituted index name" do
318
- expect(client).to receive(:search).with({:q => "response: 404", :size => 1, :index => "foo_subst_value*", :sort => "@timestamp:desc"})
319
- plugin.filter(event)
320
- end
321
- end
322
-
323
- context "if query result errored but no exception is thrown" do
324
- let(:response) do
325
- LogStash::Json.load(File.read(File.join(File.dirname(__FILE__), "fixtures", "request_error.json")))
326
- end
327
-
328
- before(:each) do
329
- allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
330
- allow(client).to receive(:search).and_return(response)
331
- plugin.register
332
- end
333
-
334
- it "tag the event as something happened, but still deliver it" do
335
- expect(plugin.logger).to receive(:warn)
336
- plugin.filter(event)
337
- expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
338
- end
339
- end
340
-
341
- context 'with client-level retries' do
342
- let(:config) do
343
- super().merge(
344
- "retry_on_failure" => 3,
345
- "retry_on_status" => [500]
346
- )
347
- end
348
- end
349
-
350
- context "with custom headers" do
351
- let(:config) do
352
- {
353
- "query" => "*",
354
- "custom_headers" => { "Custom-Header-1" => "Custom Value 1", "Custom-Header-2" => "Custom Value 2" }
355
- }
356
- end
357
-
358
- let(:plugin) { LogStash::Filters::Elasticsearch.new(config) }
359
- let(:client_double) { double("client") }
360
- let(:transport_double) { double("transport", options: { transport_options: { headers: config["custom_headers"] } }) }
361
-
362
- before do
363
- allow(plugin).to receive(:get_client).and_return(client_double)
364
- if elastic_ruby_v8_client_available?
365
- allow(client_double).to receive(:es_transport_client_type).and_return('elastic_transport')
366
- else
367
- allow(client_double).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
368
- end
369
- allow(client_double).to receive(:client).and_return(transport_double)
370
- end
371
-
372
- it "sets custom headers" do
373
- plugin.register
374
- client = plugin.send(:get_client).client
375
- expect(client.options[:transport_options][:headers]).to match(hash_including(config["custom_headers"]))
376
- end
377
- end
378
-
379
- context "if query is on nested field" do
380
- let(:config) do
381
- {
382
- "hosts" => ["localhost:9200"],
383
- "query" => "response: 404",
384
- "fields" => [ ["[geoip][ip]", "ip_address"] ]
385
- }
386
- end
387
-
388
- it "should enhance the current event with new data" do
389
- plugin.filter(event)
390
- expect(event.get("ip_address")).to eq("66.249.73.185")
391
- end
392
-
393
- end
394
- end
395
-
396
96
  class StoppableServer
397
97
 
398
98
  attr_reader :port
@@ -525,7 +225,7 @@ describe LogStash::Filters::Elasticsearch do
525
225
  # this spec is a safeguard to trigger an assessment of thread-safety should
526
226
  # we choose a different transport adapter in the future.
527
227
  transport_class = extract_transport(client).options.fetch(:transport_class)
528
- if elastic_ruby_v8_client_available?
228
+ if defined?(Elastic::Transport)
529
229
  allow(client).to receive(:es_transport_client_type).and_return("elastic_transport")
530
230
  expect(transport_class).to equal ::Elastic::Transport::Transport::HTTP::Manticore
531
231
  else
@@ -624,14 +324,28 @@ describe LogStash::Filters::Elasticsearch do
624
324
  end
625
325
 
626
326
  context "with ssl" do
627
- let(:config) { super().merge({ 'api_key' => LogStash::Util::Password.new('foo:bar'), "ssl_enabled" => true }) }
327
+ let(:api_key_value) { nil }
328
+ let(:config) { super().merge("ssl_enabled" => true, 'api_key' => LogStash::Util::Password.new(api_key_value)) }
329
+ let(:encoded_api_key) { Base64.strict_encode64('foo:bar') }
628
330
 
629
- it "should set authorization" do
630
- plugin.register
631
- client = plugin.send(:get_client).client
632
- auth_header = extract_transport(client).options[:transport_options][:headers]['Authorization']
331
+ shared_examples "a plugin that sets the ApiKey authorization header" do
332
+ it "correctly sets the Authorization header" do
333
+ plugin.register
334
+ client= plugin.send(:get_client).client
335
+ auth_header = extract_transport(client).options[:transport_options][:headers]['Authorization']
633
336
 
634
- expect( auth_header ).to eql "ApiKey #{Base64.strict_encode64('foo:bar')}"
337
+ expect(auth_header).to eql("ApiKey #{encoded_api_key}")
338
+ end
339
+ end
340
+
341
+ context "with a non-encoded API key" do
342
+ let(:api_key_value) { "foo:bar" }
343
+ it_behaves_like "a plugin that sets the ApiKey authorization header"
344
+ end
345
+
346
+ context "with an encoded API key" do
347
+ let(:api_key_value) { encoded_api_key }
348
+ it_behaves_like "a plugin that sets the ApiKey authorization header"
635
349
  end
636
350
 
637
351
  context 'user also set' do
@@ -845,7 +559,7 @@ describe LogStash::Filters::Elasticsearch do
845
559
 
846
560
  before(:each) do
847
561
  allow(LogStash::Filters::ElasticsearchClient).to receive(:new).and_return(client)
848
- if elastic_ruby_v8_client_available?
562
+ if defined?(Elastic::Transport)
849
563
  allow(client).to receive(:es_transport_client_type).and_return('elastic_transport')
850
564
  else
851
565
  allow(client).to receive(:es_transport_client_type).and_return('elasticsearch_transport')
@@ -864,31 +578,141 @@ describe LogStash::Filters::Elasticsearch do
864
578
  end
865
579
  end
866
580
 
867
- describe "#set_to_event_target" do
581
+ describe "ES|QL" do
582
+
583
+ describe "compatibility" do
584
+ let(:config) {{ "hosts" => ["localhost:9200"], "query_type" => "esql", "query" => "FROM my-index" }}
868
585
 
869
- context "when `@target` is nil, default behavior" do
870
- let(:config) {{ }}
586
+ context "when LS doesn't support ES|QL" do
587
+ let(:ls_version) { LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION }
588
+ before(:each) do
589
+ stub_const("LOGSTASH_VERSION", "8.17.0")
590
+ end
871
591
 
872
- it "sets the value directly to the top-level event field" do
873
- plugin.send(:set_to_event_target, event, "new_field", %w[value1 value2])
874
- expect(event.get("new_field")).to eq(%w[value1 value2])
592
+ it "raises a runtime error" do
593
+ expect { plugin.send(:validate_ls_version_for_esql_support!) }
594
+ .to raise_error(RuntimeError, /Current version of Logstash does not include Elasticsearch client which supports ES|QL. Please upgrade Logstash to at least #{ls_version}/)
595
+ end
875
596
  end
876
- end
877
597
 
878
- context "when @target is defined" do
879
- let(:config) {{ "target" => "nested" }}
598
+ context "when ES doesn't support ES|QL" do
599
+ let(:es_version) { LogStash::Filters::Elasticsearch::ES_ESQL_SUPPORT_VERSION }
600
+ let(:client) { double(:client) }
601
+
602
+ it "raises a runtime error" do
603
+ allow(plugin).to receive(:get_client).twice.and_return(client)
604
+ allow(client).to receive(:es_version).and_return("8.8.0")
880
605
 
881
- it "creates a nested structure under the target field" do
882
- plugin.send(:set_to_event_target, event, "new_field", %w[value1 value2])
883
- expect(event.get("nested")).to eq({ "new_field" => %w[value1 value2] })
606
+ expect { plugin.send(:validate_es_for_esql_support!) }
607
+ .to raise_error(RuntimeError, /Connected Elasticsearch 8.8.0 version does not supports ES|QL. ES|QL feature requires at least Elasticsearch #{es_version} version./)
608
+ end
884
609
  end
610
+ end
885
611
 
886
- it "overwrites existing target field with new data" do
887
- event.set("nested", { "existing_field" => "existing_value", "new_field" => "value0" })
888
- plugin.send(:set_to_event_target, event, "new_field", ["value1"])
889
- expect(event.get("nested")).to eq({ "existing_field" => "existing_value", "new_field" => ["value1"] })
612
+ context "when non-ES|QL params applied" do
613
+ let(:config) do
614
+ {
615
+ "hosts" => ["localhost:9200"],
616
+ "query_type" => "esql",
617
+ "query" => "FROM my-index",
618
+ "index" => "some-index",
619
+ "docinfo_fields" => { "_index" => "es_index" },
620
+ "sort" => "@timestamp:desc",
621
+ "enable_sort" => true,
622
+ "aggregation_fields" => { "bytes_avg" => "bytes_avg_ls_field" }
623
+ }
624
+ end
625
+ it "raises a config error" do
626
+ invalid_params_with_esql = %w(index docinfo_fields sort enable_sort aggregation_fields)
627
+ error_text = /Configured #{invalid_params_with_esql} params cannot be used with ES|QL query/i
628
+ expect { plugin.register }.to raise_error LogStash::ConfigurationError, error_text
890
629
  end
891
630
  end
631
+
632
+ describe "#query placeholder" do
633
+ let(:config) do
634
+ {
635
+ "hosts" => ["localhost:9200"],
636
+ "query_type" => "esql"
637
+ }
638
+ end
639
+
640
+ context "when query placeholder doesn't exist in the query" do
641
+ let(:config) {
642
+ super()
643
+ .merge(
644
+ {
645
+ "query" => "FROM my-index",
646
+ "query_params" => { "a" => "b" },
647
+ })
648
+ }
649
+
650
+ it "doesn't complain since not used" do
651
+ expect { plugin.send(:validate_esql_query_and_params!) }.not_to raise_error
652
+ end
653
+ end
654
+
655
+ context "when illegal placeholders appear" do
656
+ let(:config) {
657
+ super()
658
+ .merge(
659
+ {
660
+ "query" => "FROM my-index | WHERE type = ?type",
661
+ "query_params" => { "1abcd_efg1" => "1", "$abcd_efg1" => 2, "type" => 3 },
662
+ })
663
+ }
664
+ it "raises a config error" do
665
+ message = 'Illegal ["1abcd_efg1", "$abcd_efg1"] placeholder names in `query_params`. A valid parameter name starts with a letter and contains letters, digits and underscores only;'
666
+ expect { plugin.register }.to raise_error LogStash::ConfigurationError, message
667
+ end
668
+ end
669
+
670
+ context "when query placeholders and `query_params` do not match" do
671
+ let(:config) {
672
+ super()
673
+ .merge(
674
+ {
675
+ "query" => "FROM my-index | WHERE type = ?type",
676
+ "query_params" => {"b" => "c"},
677
+ })
678
+ }
679
+ it "raises a config error" do
680
+ expect { plugin.register }.to raise_error LogStash::ConfigurationError, /Placeholder type not found in query/
681
+ end
682
+ end
683
+
684
+ context "when `query_params` is an Array contains {key => val} entries" do
685
+ let(:config) {
686
+ super()
687
+ .merge(
688
+ {
689
+ "query" => "FROM my-index",
690
+ "query_params" => [{ "a" => "b" }, { "c" => "[b]" }, { "e" => 1 }, { "f" => "[g]" }],
691
+ })
692
+ }
693
+
694
+ it "doesn't complain since not used" do
695
+ expect { plugin.send(:validate_esql_query_and_params!) }.not_to raise_error
696
+ expect(plugin.query_params).to eq({ "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" })
697
+ end
698
+ end
699
+
700
+ context "when `query_params` is a Hash" do
701
+ let(:config) {
702
+ super()
703
+ .merge(
704
+ {
705
+ "query" => "FROM my-index",
706
+ "query_params" => { "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" },
707
+ })
708
+ }
709
+
710
+ it "doesn't complain since not used" do
711
+ expect { plugin.send(:validate_esql_query_and_params!) }.not_to raise_error
712
+ expect(plugin.query_params).to eq({ "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" })
713
+ end
714
+ end
715
+ end if LOGSTASH_VERSION >= '8.17.4'
892
716
  end
893
717
 
894
718
  def extract_transport(client)
@@ -897,13 +721,6 @@ describe LogStash::Filters::Elasticsearch do
897
721
  client.transport.respond_to?(:transport) ? client.transport.transport : client.transport
898
722
  end
899
723
 
900
- def elastic_ruby_v8_client_available?
901
- Elasticsearch::Transport
902
- false
903
- rescue NameError # NameError: uninitialized constant Elasticsearch::Transport if Elastic Ruby client is not available
904
- true
905
- end
906
-
907
724
  class MockResponse
908
725
  attr_reader :code, :headers
909
726