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

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.
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