logstash-output-amazon_es 2.0.1-java → 6.4.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +5 -5
  2. data/CONTRIBUTORS +12 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +10 -199
  5. data/README.md +34 -65
  6. data/lib/logstash/outputs/amazon_es.rb +218 -423
  7. data/lib/logstash/outputs/amazon_es/common.rb +347 -0
  8. data/lib/logstash/outputs/amazon_es/common_configs.rb +141 -0
  9. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es2x.json +95 -0
  10. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es5x.json +46 -0
  11. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es6x.json +45 -0
  12. data/lib/logstash/outputs/amazon_es/elasticsearch-template-es7x.json +46 -0
  13. data/lib/logstash/outputs/amazon_es/http_client.rb +359 -74
  14. data/lib/logstash/outputs/amazon_es/http_client/manticore_adapter.rb +169 -0
  15. data/lib/logstash/outputs/amazon_es/http_client/pool.rb +457 -0
  16. data/lib/logstash/outputs/amazon_es/http_client_builder.rb +164 -0
  17. data/lib/logstash/outputs/amazon_es/template_manager.rb +36 -0
  18. data/logstash-output-amazon_es.gemspec +13 -22
  19. data/spec/es_spec_helper.rb +37 -0
  20. data/spec/unit/http_client_builder_spec.rb +189 -0
  21. data/spec/unit/outputs/elasticsearch/http_client/manticore_adapter_spec.rb +105 -0
  22. data/spec/unit/outputs/elasticsearch/http_client/pool_spec.rb +198 -0
  23. data/spec/unit/outputs/elasticsearch/http_client_spec.rb +222 -0
  24. data/spec/unit/outputs/elasticsearch/template_manager_spec.rb +25 -0
  25. data/spec/unit/outputs/elasticsearch_spec.rb +615 -0
  26. data/spec/unit/outputs/error_whitelist_spec.rb +60 -0
  27. metadata +49 -110
  28. data/lib/logstash/outputs/amazon_es/aws_transport.rb +0 -109
  29. data/lib/logstash/outputs/amazon_es/aws_v4_signer.rb +0 -7
  30. data/lib/logstash/outputs/amazon_es/aws_v4_signer_impl.rb +0 -62
  31. data/lib/logstash/outputs/amazon_es/elasticsearch-template.json +0 -41
  32. data/spec/amazon_es_spec_helper.rb +0 -69
  33. data/spec/unit/outputs/amazon_es_spec.rb +0 -50
  34. data/spec/unit/outputs/elasticsearch/protocol_spec.rb +0 -36
  35. data/spec/unit/outputs/elasticsearch_proxy_spec.rb +0 -58
@@ -0,0 +1,164 @@
1
+ require 'cgi'
2
+
3
+ module LogStash; module Outputs; class ElasticSearch;
4
+ module HttpClientBuilder
5
+ def self.build(logger, hosts, params)
6
+ client_settings = {
7
+ :pool_max => params["pool_max"],
8
+ :pool_max_per_route => params["pool_max_per_route"],
9
+ :check_connection_timeout => params["validate_after_inactivity"],
10
+ :http_compression => params["http_compression"],
11
+ :headers => params["custom_headers"]
12
+ }
13
+
14
+ client_settings[:proxy] = params["proxy"] if params["proxy"]
15
+
16
+ common_options = {
17
+ :client_settings => client_settings,
18
+ :metric => params["metric"],
19
+ :resurrect_delay => params["resurrect_delay"]
20
+ }
21
+
22
+ if params["sniffing"]
23
+ common_options[:sniffing] = true
24
+ common_options[:sniffer_delay] = params["sniffing_delay"]
25
+ end
26
+
27
+ common_options[:timeout] = params["timeout"] if params["timeout"]
28
+
29
+ if params["path"]
30
+ client_settings[:path] = dedup_slashes("/#{params["path"]}/")
31
+ end
32
+
33
+ common_options[:bulk_path] = if params["bulk_path"]
34
+ dedup_slashes("/#{params["bulk_path"]}")
35
+ else
36
+ dedup_slashes("/#{params["path"]}/_bulk")
37
+ end
38
+
39
+ common_options[:sniffing_path] = if params["sniffing_path"]
40
+ dedup_slashes("/#{params["sniffing_path"]}")
41
+ else
42
+ dedup_slashes("/#{params["path"]}/_nodes/http")
43
+ end
44
+
45
+ common_options[:healthcheck_path] = if params["healthcheck_path"]
46
+ dedup_slashes("/#{params["healthcheck_path"]}")
47
+ else
48
+ dedup_slashes("/#{params["path"]}")
49
+ end
50
+
51
+ if params["parameters"]
52
+ client_settings[:parameters] = params["parameters"]
53
+ end
54
+
55
+ logger.debug? && logger.debug("Normalizing http path", :path => params["path"], :normalized => client_settings[:path])
56
+
57
+ client_settings.merge! setup_ssl(logger, params)
58
+ common_options.merge! setup_basic_auth(logger, params)
59
+
60
+ external_version_types = ["external", "external_gt", "external_gte"]
61
+ # External Version validation
62
+ raise(
63
+ LogStash::ConfigurationError,
64
+ "External versioning requires the presence of a version number."
65
+ ) if external_version_types.include?(params.fetch('version_type', '')) and params.fetch("version", nil) == nil
66
+
67
+
68
+ # Create API setup
69
+ raise(
70
+ LogStash::ConfigurationError,
71
+ "External versioning is not supported by the create action."
72
+ ) if params['action'] == 'create' and external_version_types.include?(params.fetch('version_type', ''))
73
+
74
+ # Update API setup
75
+ raise( LogStash::ConfigurationError,
76
+ "doc_as_upsert and scripted_upsert are mutually exclusive."
77
+ ) if params["doc_as_upsert"] and params["scripted_upsert"]
78
+
79
+ raise(
80
+ LogStash::ConfigurationError,
81
+ "Specifying action => 'update' needs a document_id."
82
+ ) if params['action'] == 'update' and params.fetch('document_id', '') == ''
83
+
84
+ raise(
85
+ LogStash::ConfigurationError,
86
+ "External versioning is not supported by the update action. See https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html."
87
+ ) if params['action'] == 'update' and external_version_types.include?(params.fetch('version_type', ''))
88
+
89
+ # Update API setup
90
+ update_options = {
91
+ :doc_as_upsert => params["doc_as_upsert"],
92
+ :script_var_name => params["script_var_name"],
93
+ :script_type => params["script_type"],
94
+ :script_lang => params["script_lang"],
95
+ :scripted_upsert => params["scripted_upsert"]
96
+ }
97
+ common_options.merge! update_options if params["action"] == 'update'
98
+ create_http_client(common_options.merge(:hosts => hosts,
99
+ :logger => logger,
100
+ :protocol => params["protocol"],
101
+ :port => params["port"],
102
+ :region => params["region"],
103
+ :aws_access_key_id => params["aws_access_key_id"],
104
+ :aws_secret_access_key => params["aws_secret_access_key"]))
105
+ end
106
+
107
+ def self.create_http_client(options)
108
+ LogStash::Outputs::ElasticSearch::HttpClient.new(options)
109
+ end
110
+
111
+ def self.setup_ssl(logger, params)
112
+ params["ssl"] = true if params["hosts"].any? {|h| h.scheme == "https" }
113
+ return {} if params["ssl"].nil?
114
+
115
+ return {:ssl => {:enabled => false}} if params["ssl"] == false
116
+
117
+ cacert, truststore, truststore_password, keystore, keystore_password =
118
+ params.values_at('cacert', 'truststore', 'truststore_password', 'keystore', 'keystore_password')
119
+
120
+ if cacert && truststore
121
+ raise(LogStash::ConfigurationError, "Use either \"cacert\" or \"truststore\" when configuring the CA certificate") if truststore
122
+ end
123
+
124
+ ssl_options = {:enabled => true}
125
+
126
+ if cacert
127
+ ssl_options[:ca_file] = cacert
128
+ elsif truststore
129
+ ssl_options[:truststore_password] = truststore_password.value if truststore_password
130
+ end
131
+
132
+ ssl_options[:truststore] = truststore if truststore
133
+ if keystore
134
+ ssl_options[:keystore] = keystore
135
+ ssl_options[:keystore_password] = keystore_password.value if keystore_password
136
+ end
137
+ if !params["ssl_certificate_verification"]
138
+ logger.warn [
139
+ "** WARNING ** Detected UNSAFE options in amazon_es output configuration!",
140
+ "** WARNING ** You have enabled encryption but DISABLED certificate verification.",
141
+ "** WARNING ** To make sure your data is secure change :ssl_certificate_verification to true"
142
+ ].join("\n")
143
+ ssl_options[:verify] = false
144
+ end
145
+ { ssl: ssl_options }
146
+ end
147
+
148
+ def self.setup_basic_auth(logger, params)
149
+ user, password = params["user"], params["password"]
150
+
151
+ return {} unless user && password && password.value
152
+
153
+ {
154
+ :user => CGI.escape(user),
155
+ :password => CGI.escape(password.value)
156
+ }
157
+ end
158
+
159
+ private
160
+ def self.dedup_slashes(url)
161
+ url.gsub(/\/+/, "/")
162
+ end
163
+ end
164
+ end; end; end
@@ -0,0 +1,36 @@
1
+ module LogStash; module Outputs; class ElasticSearch
2
+ class TemplateManager
3
+ # To be mixed into the amazon_es plugin base
4
+ def self.install_template(plugin)
5
+ return unless plugin.manage_template
6
+ plugin.logger.info("Using mapping template from", :path => plugin.template)
7
+ template = get_template(plugin.template, plugin.maximum_seen_major_version)
8
+ plugin.logger.info("Attempting to install template", :manage_template => template)
9
+ install(plugin.client, plugin.template_name, template, plugin.template_overwrite)
10
+ rescue => e
11
+ plugin.logger.error("Failed to install template.", :message => e.message, :class => e.class.name, :backtrace => e.backtrace)
12
+ end
13
+
14
+ private
15
+ def self.get_template(path, es_major_version)
16
+ template_path = path || default_template_path(es_major_version)
17
+ read_template_file(template_path)
18
+ end
19
+
20
+ def self.install(client, template_name, template, template_overwrite)
21
+ client.template_install(template_name, template, template_overwrite)
22
+ end
23
+
24
+ def self.default_template_path(es_major_version)
25
+ template_version = es_major_version == 1 ? 2 : es_major_version
26
+ default_template_name = "elasticsearch-template-es#{template_version}x.json"
27
+ ::File.expand_path(default_template_name, ::File.dirname(__FILE__))
28
+ end
29
+
30
+ def self.read_template_file(template_path)
31
+ raise ArgumentError, "Template file '#{@template_path}' could not be found!" unless ::File.exists?(template_path)
32
+ template_data = ::IO.read(template_path)
33
+ LogStash::Json.load(template_data)
34
+ end
35
+ end
36
+ end end end
@@ -1,17 +1,18 @@
1
1
  Gem::Specification.new do |s|
2
-
3
2
  s.name = 'logstash-output-amazon_es'
4
- s.version = '2.0.1'
5
- s.licenses = ['apache-2.0']
3
+ s.version = '6.4.0'
4
+ s.licenses = ['Apache-2.0']
6
5
  s.summary = "Logstash Output to Amazon Elasticsearch Service"
7
6
  s.description = "Output events to Amazon Elasticsearch Service with V4 signing"
8
7
  s.authors = ["Amazon"]
9
8
  s.email = 'feedback-prod-elasticsearch@amazon.com'
10
- s.homepage = "http://logstash.net/"
9
+ s.homepage = "https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/index.html"
11
10
  s.require_paths = ["lib"]
12
11
 
12
+ s.platform = RUBY_PLATFORM
13
+
13
14
  # Files
14
- s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
15
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "vendor/jar-dependencies/**/*.jar", "vendor/jar-dependencies/**/*.rb", "VERSION", "docs/**/*"]
15
16
 
16
17
  # Tests
17
18
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
@@ -19,25 +20,15 @@ Gem::Specification.new do |s|
19
20
  # Special flag to let us know this is actually a logstash plugin
20
21
  s.metadata = { "logstash_plugin" => "true", "logstash_group" => "output" }
21
22
 
22
- # Gem dependencies
23
- s.add_runtime_dependency 'concurrent-ruby'
24
- s.add_runtime_dependency 'elasticsearch', '>= 1.0.10', '< 6.0.0'
23
+ s.add_runtime_dependency "manticore", '>= 0.5.4', '< 1.0.0'
25
24
  s.add_runtime_dependency 'stud', ['>= 0.0.17', '~> 0.0']
26
25
  s.add_runtime_dependency 'cabin', ['~> 0.6']
27
- s.add_runtime_dependency 'logstash-core-plugin-api', '>= 1.60', '<= 2.99'
26
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
28
27
  s.add_runtime_dependency 'aws-sdk', '>= 2.3.22', '~> 2'
29
- s.add_runtime_dependency "faraday", '~> 0.9.1'
30
- s.add_runtime_dependency "faraday_middleware", '~> 0.10.0'
31
-
32
- s.add_development_dependency 'addressable', '< 2.5.0'
33
- s.add_development_dependency 'ftw', '~> 0.0.42'
34
- s.add_development_dependency 'logstash-input-generator'
35
-
36
- if RUBY_PLATFORM == 'java'
37
- s.platform = RUBY_PLATFORM
38
- s.add_runtime_dependency "manticore", '>= 0.5.2', '< 1.0.0'
39
- end
40
28
 
41
- s.add_development_dependency 'logstash-devutils'
42
- s.add_development_dependency 'longshoreman'
29
+ s.add_development_dependency 'logstash-codec-plain', '~> 0'
30
+ s.add_development_dependency 'logstash-devutils', '~> 0'
31
+ s.add_development_dependency 'flores', '~> 0'
32
+ # Still used in some specs, we should remove this ASAP
33
+ s.add_development_dependency 'elasticsearch', '~> 0'
43
34
  end
@@ -0,0 +1,37 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require 'manticore'
3
+ require 'elasticsearch'
4
+
5
+ # by default exclude secure_integration tests unless requested
6
+ # normal integration specs are already excluded by devutils' spec helper
7
+ RSpec.configure do |config|
8
+ config.filter_run_excluding config.exclusion_filter.add(:secure_integration => true)
9
+ end
10
+
11
+ module ESHelper
12
+ def get_host_port
13
+ "127.0.0.1"
14
+ end
15
+
16
+ def get_client
17
+ Elasticsearch::Client.new(:hosts => [get_host_port])
18
+ end
19
+
20
+ def self.es_version
21
+ RSpec.configuration.filter[:es_version] || ENV['ES_VERSION']
22
+ end
23
+
24
+ def self.es_version_satisfies?(*requirement)
25
+ es_version = RSpec.configuration.filter[:es_version] || ENV['ES_VERSION']
26
+ if es_version.nil?
27
+ puts "Info: ES_VERSION environment or 'es_version' tag wasn't set. Returning false to all `es_version_satisfies?` call."
28
+ return false
29
+ end
30
+ es_release_version = Gem::Version.new(es_version).release
31
+ Gem::Requirement.new(requirement).satisfied_by?(es_release_version)
32
+ end
33
+ end
34
+
35
+ RSpec.configure do |config|
36
+ config.include ESHelper
37
+ end
@@ -0,0 +1,189 @@
1
+ require "logstash/devutils/rspec/spec_helper"
2
+ require "logstash/outputs/amazon_es"
3
+ require "logstash/outputs/amazon_es/http_client"
4
+ require "logstash/outputs/amazon_es/http_client_builder"
5
+
6
+ describe LogStash::Outputs::ElasticSearch::HttpClientBuilder do
7
+ describe "auth setup with url encodable passwords" do
8
+ let(:klass) { LogStash::Outputs::ElasticSearch::HttpClientBuilder }
9
+ let(:user) { "foo@bar"}
10
+ let(:password) {"baz@blah" }
11
+ let(:password_secured) do
12
+ secured = double("password")
13
+ allow(secured).to receive(:value).and_return(password)
14
+ secured
15
+ end
16
+ let(:options) { {"user" => user, "password" => password} }
17
+ let(:logger) { mock("logger") }
18
+ let(:auth_setup) { klass.setup_basic_auth(double("logger"), {"user" => user, "password" => password_secured}) }
19
+
20
+ it "should return the user escaped" do
21
+ expect(auth_setup[:user]).to eql(CGI.escape(user))
22
+ end
23
+
24
+ it "should return the password escaped" do
25
+ expect(auth_setup[:password]).to eql(CGI.escape(password))
26
+ end
27
+ end
28
+
29
+ describe "customizing action paths" do
30
+ let(:hosts) { [ ::LogStash::Util::SafeURI.new("http://localhost:9200") ] }
31
+ let(:options) { {"hosts" => hosts ,
32
+ "protocol" => "http",
33
+ "port" => 9200,
34
+ "aws_access_key_id" => "AAAAAAAAAAAAAAAAAAAA",
35
+ "aws_secret_access_key" => "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"} }
36
+ let(:logger) { double("logger") }
37
+ before :each do
38
+ [:debug, :debug?, :info?, :info, :warn].each do |level|
39
+ allow(logger).to receive(level)
40
+ end
41
+ end
42
+
43
+ describe "healthcheck_path" do
44
+
45
+ context "when setting bulk_path" do
46
+ let(:bulk_path) { "/meh" }
47
+ let(:options) { super.merge("bulk_path" => bulk_path) }
48
+
49
+ context "when using path" do
50
+ let(:options) { super.merge("path" => "/path") }
51
+ it "ignores the path setting" do
52
+ expect(described_class).to receive(:create_http_client) do |options|
53
+ expect(options[:bulk_path]).to eq(bulk_path)
54
+ end
55
+ described_class.build(logger, hosts, options)
56
+ end
57
+ end
58
+ context "when not using path" do
59
+
60
+ it "uses the bulk_path setting" do
61
+ expect(described_class).to receive(:create_http_client) do |options|
62
+ expect(options[:bulk_path]).to eq(bulk_path)
63
+ end
64
+ described_class.build(logger, hosts, options)
65
+ end
66
+ end
67
+ end
68
+
69
+ context "when not setting bulk_path" do
70
+
71
+ context "when using path" do
72
+ let(:path) { "/meh" }
73
+ let(:options) { super.merge("path" => path) }
74
+ it "sets bulk_path to path+_bulk" do
75
+ expect(described_class).to receive(:create_http_client) do |options|
76
+ expect(options[:bulk_path]).to eq("#{path}/_bulk")
77
+ end
78
+ described_class.build(logger, hosts, options)
79
+ end
80
+ end
81
+
82
+ context "when not using path" do
83
+ it "sets the bulk_path to _bulk" do
84
+ expect(described_class).to receive(:create_http_client) do |options|
85
+ expect(options[:bulk_path]).to eq("/_bulk")
86
+ end
87
+ described_class.build(logger, hosts, options)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ describe "healthcheck_path" do
93
+ context "when setting healthcheck_path" do
94
+ let(:healthcheck_path) { "/meh" }
95
+ let(:options) { super.merge("healthcheck_path" => healthcheck_path) }
96
+
97
+ context "when using path" do
98
+ let(:options) { super.merge("path" => "/path") }
99
+ it "ignores the path setting" do
100
+ expect(described_class).to receive(:create_http_client) do |options|
101
+ expect(options[:healthcheck_path]).to eq(healthcheck_path)
102
+ end
103
+ described_class.build(logger, hosts, options)
104
+ end
105
+ end
106
+ context "when not using path" do
107
+
108
+ it "uses the healthcheck_path setting" do
109
+ expect(described_class).to receive(:create_http_client) do |options|
110
+ expect(options[:healthcheck_path]).to eq(healthcheck_path)
111
+ end
112
+ described_class.build(logger, hosts, options)
113
+ end
114
+ end
115
+ end
116
+
117
+ context "when not setting healthcheck_path" do
118
+
119
+ context "when using path" do
120
+ let(:path) { "/meh" }
121
+ let(:options) { super.merge("path" => path) }
122
+ it "sets healthcheck_path to path" do
123
+ expect(described_class).to receive(:create_http_client) do |options|
124
+ expect(options[:healthcheck_path]).to eq(path)
125
+ end
126
+ described_class.build(logger, hosts, options)
127
+ end
128
+ end
129
+
130
+ context "when not using path" do
131
+ it "sets the healthcheck_path to root" do
132
+ expect(described_class).to receive(:create_http_client) do |options|
133
+ expect(options[:healthcheck_path]).to eq("/")
134
+ end
135
+ described_class.build(logger, hosts, options)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ describe "sniffing_path" do
141
+ context "when setting sniffing_path" do
142
+ let(:sniffing_path) { "/meh" }
143
+ let(:options) { super.merge("sniffing_path" => sniffing_path) }
144
+
145
+ context "when using path" do
146
+ let(:options) { super.merge("path" => "/path") }
147
+ it "ignores the path setting" do
148
+ expect(described_class).to receive(:create_http_client) do |options|
149
+ expect(options[:sniffing_path]).to eq(sniffing_path)
150
+ end
151
+ described_class.build(logger, hosts, options)
152
+ end
153
+ end
154
+ context "when not using path" do
155
+
156
+ it "uses the sniffing_path setting" do
157
+ expect(described_class).to receive(:create_http_client) do |options|
158
+ expect(options[:sniffing_path]).to eq(sniffing_path)
159
+ end
160
+ described_class.build(logger, hosts, options)
161
+ end
162
+ end
163
+ end
164
+
165
+ context "when not setting sniffing_path" do
166
+
167
+ context "when using path" do
168
+ let(:path) { "/meh" }
169
+ let(:options) { super.merge("path" => path) }
170
+ it "sets sniffing_path to path+_nodes/http" do
171
+ expect(described_class).to receive(:create_http_client) do |options|
172
+ expect(options[:sniffing_path]).to eq("#{path}/_nodes/http")
173
+ end
174
+ described_class.build(logger, hosts, options)
175
+ end
176
+ end
177
+
178
+ context "when not using path" do
179
+ it "sets the sniffing_path to _nodes/http" do
180
+ expect(described_class).to receive(:create_http_client) do |options|
181
+ expect(options[:sniffing_path]).to eq("/_nodes/http")
182
+ end
183
+ described_class.build(logger, hosts, options)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end