logstash-filter-elasticsearch 3.19.0 → 4.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -11
- data/docs/index.asciidoc +23 -240
- data/lib/logstash/filters/elasticsearch/client.rb +2 -27
- data/lib/logstash/filters/elasticsearch.rb +124 -177
- data/logstash-filter-elasticsearch.gemspec +3 -6
- data/spec/filters/elasticsearch_spec.rb +272 -163
- data/spec/filters/elasticsearch_ssl_spec.rb +17 -0
- data/spec/filters/integration/elasticsearch_spec.rb +2 -9
- metadata +3 -59
- data/lib/logstash/filters/elasticsearch/dsl_executor.rb +0 -140
- data/lib/logstash/filters/elasticsearch/esql_executor.rb +0 -178
- data/spec/filters/elasticsearch_dsl_spec.rb +0 -372
- data/spec/filters/elasticsearch_esql_spec.rb +0 -211
- data/spec/filters/integration/elasticsearch_esql_spec.rb +0 -167
@@ -1,211 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require "logstash/devutils/rspec/spec_helper"
|
3
|
-
require "logstash/filters/elasticsearch"
|
4
|
-
|
5
|
-
describe LogStash::Filters::Elasticsearch::EsqlExecutor do
|
6
|
-
let(:client) { instance_double(LogStash::Filters::ElasticsearchClient) }
|
7
|
-
let(:logger) { double("logger") }
|
8
|
-
let(:plugin) { LogStash::Filters::Elasticsearch.new(plugin_config) }
|
9
|
-
let(:plugin_config) do
|
10
|
-
{
|
11
|
-
"query_type" => "esql",
|
12
|
-
"query" => "FROM test-index | STATS count() BY field | LIMIT 10"
|
13
|
-
}
|
14
|
-
end
|
15
|
-
let(:esql_executor) { described_class.new(plugin, logger) }
|
16
|
-
|
17
|
-
context "when initializes" do
|
18
|
-
it "sets up the ESQL executor with correct parameters" do
|
19
|
-
allow(logger).to receive(:debug)
|
20
|
-
allow(logger).to receive(:warn)
|
21
|
-
expect(esql_executor.instance_variable_get(:@query)).to eq(plugin_config["query"])
|
22
|
-
expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({})
|
23
|
-
expect(esql_executor.instance_variable_get(:@static_params)).to eq([])
|
24
|
-
expect(esql_executor.instance_variable_get(:@tag_on_failure)).to eq(["_elasticsearch_lookup_failure"])
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
context "when processes" do
|
29
|
-
let(:plugin_config) {
|
30
|
-
super()
|
31
|
-
.merge(
|
32
|
-
{
|
33
|
-
"query" => "FROM my-index | WHERE field = ?foo | LIMIT 5",
|
34
|
-
"query_params" => { "foo" => "[bar]" }
|
35
|
-
})
|
36
|
-
}
|
37
|
-
let(:event) { LogStash::Event.new({}) }
|
38
|
-
let(:response) {
|
39
|
-
{
|
40
|
-
'values' => [["foo", "bar", nil]],
|
41
|
-
'columns' => [{ 'name' => 'id', 'type' => 'keyword' }, { 'name' => 'val', 'type' => 'keyword' }, { 'name' => 'odd', 'type' => 'keyword' }]
|
42
|
-
}
|
43
|
-
}
|
44
|
-
|
45
|
-
before do
|
46
|
-
allow(logger).to receive(:debug)
|
47
|
-
allow(logger).to receive(:warn)
|
48
|
-
end
|
49
|
-
|
50
|
-
it "resolves parameters" do
|
51
|
-
expect(event).to receive(:get).with("[bar]").and_return("resolved_value")
|
52
|
-
resolved_params = esql_executor.send(:resolve_parameters, event)
|
53
|
-
expect(resolved_params).to include("foo" => "resolved_value")
|
54
|
-
end
|
55
|
-
|
56
|
-
it "executes the query with resolved parameters" do
|
57
|
-
allow(logger).to receive(:debug)
|
58
|
-
expect(event).to receive(:get).with("[bar]").and_return("resolved_value")
|
59
|
-
expect(client).to receive(:esql_query).with(
|
60
|
-
{ body: { query: plugin_config["query"], params: [{ "foo" => "resolved_value" }] }, format: 'json', drop_null_columns: true, })
|
61
|
-
resolved_params = esql_executor.send(:resolve_parameters, event)
|
62
|
-
esql_executor.send(:execute_query, client, resolved_params)
|
63
|
-
end
|
64
|
-
|
65
|
-
it "informs warning if received warning" do
|
66
|
-
allow(response).to receive(:headers).and_return({ "warning" => "some warning" })
|
67
|
-
expect(logger).to receive(:warn).with("ES|QL executor received warning", { :message => "some warning" })
|
68
|
-
esql_executor.send(:inform_warning, response)
|
69
|
-
end
|
70
|
-
|
71
|
-
it "processes the response and adds metadata" do
|
72
|
-
expect(event).to receive(:set).with("[@metadata][total_values]", 1)
|
73
|
-
# [id], [val] aren't resolved via sprintf, use as it is
|
74
|
-
expect(event).to receive(:set).with("[id]", "foo")
|
75
|
-
expect(event).to receive(:set).with("[val]", "bar")
|
76
|
-
esql_executor.send(:process_response, event, response)
|
77
|
-
end
|
78
|
-
|
79
|
-
it "executes chain of processes" do
|
80
|
-
allow(plugin).to receive(:decorate)
|
81
|
-
allow(logger).to receive(:debug)
|
82
|
-
allow(response).to receive(:headers).and_return({})
|
83
|
-
expect(client).to receive(:esql_query).with(
|
84
|
-
{
|
85
|
-
body: { query: plugin_config["query"], params: [{"foo"=>"resolve_me"}] },
|
86
|
-
format: 'json',
|
87
|
-
drop_null_columns: true,
|
88
|
-
}).and_return(response)
|
89
|
-
|
90
|
-
event = LogStash::Event.new({ "hello" => "world", "bar" => "resolve_me" })
|
91
|
-
expect { esql_executor.process(client, event) }.to_not raise_error
|
92
|
-
expect(event.get("[@metadata][total_values]")).to eq(1)
|
93
|
-
expect(event.get("hello")).to eq("world")
|
94
|
-
expect(event.get("val")).to eq("bar")
|
95
|
-
expect(event.get("odd")).to be_nil # filters out non-exist fields
|
96
|
-
end
|
97
|
-
|
98
|
-
it "tags on plugin failures" do
|
99
|
-
expect(event).to receive(:get).with("[bar]").and_raise("Event#get Invalid FieldReference error")
|
100
|
-
|
101
|
-
expect(logger).to receive(:error).with("Failed to process ES|QL filter", exception: instance_of(RuntimeError))
|
102
|
-
expect(event).to receive(:tag).with("_elasticsearch_lookup_failure")
|
103
|
-
esql_executor.process(client, event)
|
104
|
-
end
|
105
|
-
|
106
|
-
it "tags on query execution failures" do
|
107
|
-
allow(logger).to receive(:debug)
|
108
|
-
allow(client).to receive(:esql_query).and_raise("Query execution error")
|
109
|
-
|
110
|
-
expect(logger).to receive(:error).with("Failed to process ES|QL filter", exception: instance_of(RuntimeError))
|
111
|
-
expect(event).to receive(:tag).with("_elasticsearch_lookup_failure")
|
112
|
-
esql_executor.process(client, event)
|
113
|
-
end
|
114
|
-
|
115
|
-
describe "#target" do
|
116
|
-
let(:event) { LogStash::Event.new({ "hello" => "world", "bar" => "resolve_me" }) }
|
117
|
-
let(:response) {
|
118
|
-
super().merge({ 'values' => [["foo", "bar", nil], %w[hello again world], %w[another value here]] })
|
119
|
-
}
|
120
|
-
before(:each) do
|
121
|
-
expect(client).to receive(:esql_query).with(any_args).and_return(response)
|
122
|
-
allow(plugin).to receive(:decorate)
|
123
|
-
allow(logger).to receive(:debug)
|
124
|
-
allow(response).to receive(:headers).and_return({})
|
125
|
-
end
|
126
|
-
|
127
|
-
context "when specified" do
|
128
|
-
let(:plugin_config) {
|
129
|
-
super().merge({ "target" => "my-target" })
|
130
|
-
}
|
131
|
-
|
132
|
-
it "sets all query results into event" do
|
133
|
-
expected_result = [
|
134
|
-
{"id"=>"foo", "val"=>"bar"},
|
135
|
-
{"id"=>"hello", "val"=>"again", "odd"=>"world"},
|
136
|
-
{"id"=>"another", "val"=>"value", "odd"=>"here"}
|
137
|
-
]
|
138
|
-
expect { esql_executor.process(client, event) }.to_not raise_error
|
139
|
-
expect(event.get("[@metadata][total_values]")).to eq(3)
|
140
|
-
expect(event.get("my-target").size).to eq(3)
|
141
|
-
expect(event.get("my-target")).to eq(expected_result)
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
context "when not specified" do
|
146
|
-
shared_examples "first result into the event" do
|
147
|
-
it "sets" do
|
148
|
-
expect { esql_executor.process(client, event) }.to_not raise_error
|
149
|
-
expect(event.get("[@metadata][total_values]")).to eq(3)
|
150
|
-
expect(event.get("id")).to eq("foo")
|
151
|
-
expect(event.get("val")).to eq("bar")
|
152
|
-
expect(event.get("odd")).to eq(nil)
|
153
|
-
end
|
154
|
-
end
|
155
|
-
context "when limit is included in the query" do
|
156
|
-
let(:plugin_config) {
|
157
|
-
super().merge({ "query" => "FROM my-index | LIMIT 555" })
|
158
|
-
}
|
159
|
-
it_behaves_like "first result into the event"
|
160
|
-
end
|
161
|
-
|
162
|
-
context "when limit is not included in the query" do
|
163
|
-
let(:plugin_config) {
|
164
|
-
super().merge({ "query" => "FROM my-index" })
|
165
|
-
}
|
166
|
-
it_behaves_like "first result into the event"
|
167
|
-
end
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|
171
|
-
|
172
|
-
describe "#query placeholders" do
|
173
|
-
before(:each) do
|
174
|
-
allow(logger).to receive(:debug)
|
175
|
-
allow(logger).to receive(:warn)
|
176
|
-
plugin.send(:validate_esql_query_and_params!)
|
177
|
-
end
|
178
|
-
|
179
|
-
context "when `query_params` is an Array contains {key => val} entries" do
|
180
|
-
let(:plugin_config) {
|
181
|
-
super()
|
182
|
-
.merge(
|
183
|
-
{
|
184
|
-
"query" => "FROM my-index | LIMIT 1",
|
185
|
-
"query_params" => [{ "a" => "b" }, { "c" => "[b]" }, { "e" => 1 }, { "f" => "[g]" }],
|
186
|
-
})
|
187
|
-
}
|
188
|
-
|
189
|
-
it "separates references and static params at initialization" do
|
190
|
-
expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({"c" => "[b]", "f" => "[g]"})
|
191
|
-
expect(esql_executor.instance_variable_get(:@static_params)).to eq([{"a" => "b"}, {"e" => 1}])
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
context "when `query_params` is a Hash" do
|
196
|
-
let(:plugin_config) {
|
197
|
-
super()
|
198
|
-
.merge(
|
199
|
-
{
|
200
|
-
"query" => "FROM my-index | LIMIT 1",
|
201
|
-
"query_params" => { "a" => "b", "c" => "[b]", "e" => 1, "f" => "[g]" },
|
202
|
-
})
|
203
|
-
}
|
204
|
-
|
205
|
-
it "separates references and static params at initialization" do
|
206
|
-
expect(esql_executor.instance_variable_get(:@referenced_params)).to eq({"c" => "[b]", "f" => "[g]"})
|
207
|
-
expect(esql_executor.instance_variable_get(:@static_params)).to eq([{"a" => "b"}, {"e" => 1}])
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
end if LOGSTASH_VERSION >= LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION
|
@@ -1,167 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
require "logstash/devutils/rspec/spec_helper"
|
3
|
-
require "logstash/filters/elasticsearch"
|
4
|
-
require "elasticsearch"
|
5
|
-
require_relative "../../../spec/es_helper"
|
6
|
-
|
7
|
-
describe LogStash::Filters::Elasticsearch, integration: true do
|
8
|
-
|
9
|
-
ELASTIC_SECURITY_ENABLED = ENV['ELASTIC_SECURITY_ENABLED'].eql? 'true'
|
10
|
-
SECURE_INTEGRATION = ENV['SECURE_INTEGRATION'].eql? 'true'
|
11
|
-
ES_HOSTS = ["http#{SECURE_INTEGRATION ? 's' : nil}://#{ESHelper.get_host_port}"]
|
12
|
-
CA_PATH = File.expand_path('../fixtures/test_certs/ca.crt', File.dirname(__FILE__))
|
13
|
-
|
14
|
-
let(:plugin) { described_class.new(config) }
|
15
|
-
let(:es_index) { "es-filter-plugin-esql-integration-#{rand(1000)}" }
|
16
|
-
let(:test_documents) do
|
17
|
-
[
|
18
|
-
{ "message" => "test message 1", "type" => "a", "count" => 1 },
|
19
|
-
{ "message" => "test message 2", "type" => "a", "count" => 2 },
|
20
|
-
{ "message" => "test message 3", "type" => "b", "count" => 3 },
|
21
|
-
{ "message" => "test message 4", "type" => "b", "count" => 4 },
|
22
|
-
{ "message" => "test message 5", "type" => "c", "count" => 5 },
|
23
|
-
{ "message" => "odd test message", "type" => "t" }
|
24
|
-
]
|
25
|
-
end
|
26
|
-
|
27
|
-
let(:base_config) do
|
28
|
-
{
|
29
|
-
"query_type" => "esql",
|
30
|
-
"hosts" => ES_HOSTS,
|
31
|
-
"ssl_enabled" => SECURE_INTEGRATION
|
32
|
-
}
|
33
|
-
end
|
34
|
-
|
35
|
-
let(:credentials) do
|
36
|
-
if SECURE_INTEGRATION
|
37
|
-
{ 'user' => 'tests', 'password' => 'Tests123' }
|
38
|
-
else
|
39
|
-
{ 'user' => 'elastic', 'password' => ENV['ELASTIC_PASSWORD'] }
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
let(:config) do
|
44
|
-
config = ELASTIC_SECURITY_ENABLED ? base_config.merge(credentials) : base_config
|
45
|
-
config = { 'ssl_certificate_authorities' => CA_PATH }.merge(config) if SECURE_INTEGRATION
|
46
|
-
config
|
47
|
-
end
|
48
|
-
|
49
|
-
let(:event) { LogStash::Event.new({}) }
|
50
|
-
|
51
|
-
def es_client
|
52
|
-
@es_client ||= begin
|
53
|
-
user = SECURE_INTEGRATION ? 'tests' : 'elastic'
|
54
|
-
password = SECURE_INTEGRATION ? 'Tests123' : ENV['ELASTIC_PASSWORD']
|
55
|
-
|
56
|
-
es_client_config = { hosts: ES_HOSTS }
|
57
|
-
es_client_config = es_client_config.merge({ user: user, password: password }) if ELASTIC_SECURITY_ENABLED || SECURE_INTEGRATION
|
58
|
-
es_client_config = es_client_config.merge({ transport_options: { ssl: { ca_path: CA_PATH, verify: false }}}) if SECURE_INTEGRATION
|
59
|
-
|
60
|
-
Elasticsearch::Client.new(es_client_config)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
before(:all) do
|
65
|
-
is_ls_with_esql_supported_client = Gem::Version.create(LOGSTASH_VERSION) >= Gem::Version.create(LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
|
66
|
-
# Skip tests if an ES version doesn't support ES|QL
|
67
|
-
skip "LS version does not have ES client which supports ES|QL" unless is_ls_with_esql_supported_client
|
68
|
-
|
69
|
-
es_version_info = es_client.info["version"]
|
70
|
-
es_gem_version = Gem::Version.create(es_version_info["number"])
|
71
|
-
skip "ES version does not support ES|QL" if es_gem_version.nil? || es_gem_version < Gem::Version.create(LogStash::Filters::Elasticsearch::ES_ESQL_SUPPORT_VERSION)
|
72
|
-
end
|
73
|
-
|
74
|
-
before(:each) do
|
75
|
-
# Create index with test documents
|
76
|
-
es_client.indices.create(index: es_index, body: {}) unless es_client.indices.exists?(index: es_index)
|
77
|
-
|
78
|
-
test_documents.each do |doc|
|
79
|
-
es_client.index(index: es_index, body: doc, refresh: true)
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
after(:each) do
|
84
|
-
es_client.indices.delete(index: es_index) if es_client.indices.exists?(index: es_index)
|
85
|
-
end
|
86
|
-
|
87
|
-
describe "run ES|QL queries" do
|
88
|
-
|
89
|
-
before do
|
90
|
-
stub_const("LOGSTASH_VERSION", LogStash::Filters::Elasticsearch::LS_ESQL_SUPPORT_VERSION)
|
91
|
-
end
|
92
|
-
|
93
|
-
before(:each) do
|
94
|
-
plugin.register
|
95
|
-
end
|
96
|
-
|
97
|
-
shared_examples "ESQL query execution" do |expected_count, fields|
|
98
|
-
it "processes the event" do
|
99
|
-
plugin.filter(event)
|
100
|
-
expect(event.get("[@metadata][total_values]")).to eq(expected_count)
|
101
|
-
fields&.each do | field |
|
102
|
-
expect(event.get(field)).not_to be(nil)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
describe "with simple FROM query with LIMIT" do
|
108
|
-
let(:config) do
|
109
|
-
super().merge("query" => "FROM #{es_index} | LIMIT 99")
|
110
|
-
end
|
111
|
-
|
112
|
-
include_examples "ESQL query execution", 6
|
113
|
-
end
|
114
|
-
|
115
|
-
describe "with simple FROM and WHERE query combinations" do
|
116
|
-
let(:config) do
|
117
|
-
super().merge("query" => "FROM #{es_index} | WHERE type==\"b\" | LIMIT 99")
|
118
|
-
end
|
119
|
-
|
120
|
-
include_examples "ESQL query execution", 2
|
121
|
-
end
|
122
|
-
|
123
|
-
describe "with query params" do
|
124
|
-
let(:config) do
|
125
|
-
super().merge("query" => "FROM #{es_index} | WHERE type==?type", "query_params" => { "type" => "b" })
|
126
|
-
end
|
127
|
-
|
128
|
-
include_examples "ESQL query execution", 2
|
129
|
-
end
|
130
|
-
|
131
|
-
describe "when invalid query used" do
|
132
|
-
let(:config) do
|
133
|
-
super().merge("query" => "FROM undefined index | LIMIT 1")
|
134
|
-
end
|
135
|
-
|
136
|
-
it "tags on failure" do
|
137
|
-
plugin.filter(event)
|
138
|
-
expect(event.to_hash["tags"]).to include("_elasticsearch_lookup_failure")
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
describe "when field enrichment requested" do
|
143
|
-
let(:config) do
|
144
|
-
super().merge("query" => "FROM #{es_index} | WHERE type==\"b\" | LIMIT 99")
|
145
|
-
end
|
146
|
-
|
147
|
-
include_examples "ESQL query execution", 2, %w[message count]
|
148
|
-
end
|
149
|
-
|
150
|
-
describe "when non-exist field value appear" do
|
151
|
-
let(:config) do
|
152
|
-
super().merge("query" => "FROM #{es_index}", "target" => "target_field")
|
153
|
-
end
|
154
|
-
|
155
|
-
it "processes the event" do
|
156
|
-
plugin.filter(event)
|
157
|
-
expect(event.get("[@metadata][total_values]")).to eq(6)
|
158
|
-
expect(event.get("target_field").size).to eq(6)
|
159
|
-
values = event.get("target_field")
|
160
|
-
counts = values.count { |entry| entry.key?("count") }
|
161
|
-
messages = values.count { |entry| entry.key?("message") }
|
162
|
-
expect(counts).to eq(5)
|
163
|
-
expect(messages).to eq(6)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|