elasticsearch-transport 7.9.0 → 7.17.10

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -10
  3. data/Gemfile-faraday1.gemfile +47 -0
  4. data/README.md +23 -17
  5. data/Rakefile +47 -10
  6. data/elasticsearch-transport.gemspec +16 -18
  7. data/lib/elasticsearch/transport/client.rb +133 -56
  8. data/lib/elasticsearch/transport/meta_header.rb +135 -0
  9. data/lib/elasticsearch/transport/transport/base.rb +41 -22
  10. data/lib/elasticsearch/transport/transport/connections/collection.rb +2 -5
  11. data/lib/elasticsearch/transport/transport/connections/connection.rb +6 -4
  12. data/lib/elasticsearch/transport/transport/errors.rb +1 -0
  13. data/lib/elasticsearch/transport/transport/http/curb.rb +44 -32
  14. data/lib/elasticsearch/transport/transport/http/faraday.rb +15 -5
  15. data/lib/elasticsearch/transport/transport/http/manticore.rb +41 -29
  16. data/lib/elasticsearch/transport/transport/response.rb +1 -1
  17. data/lib/elasticsearch/transport/version.rb +1 -1
  18. data/lib/elasticsearch/transport.rb +19 -30
  19. data/spec/elasticsearch/connections/collection_spec.rb +12 -0
  20. data/spec/elasticsearch/transport/base_spec.rb +90 -33
  21. data/spec/elasticsearch/transport/client_spec.rb +569 -164
  22. data/spec/elasticsearch/transport/http/curb_spec.rb +126 -0
  23. data/spec/elasticsearch/transport/http/faraday_spec.rb +141 -0
  24. data/spec/elasticsearch/transport/http/manticore_spec.rb +161 -0
  25. data/spec/elasticsearch/transport/meta_header_spec.rb +301 -0
  26. data/spec/spec_helper.rb +13 -5
  27. data/test/integration/jruby_test.rb +43 -0
  28. data/test/integration/transport_test.rb +93 -43
  29. data/test/test_helper.rb +10 -22
  30. data/test/unit/adapters_test.rb +88 -0
  31. data/test/unit/connection_test.rb +7 -2
  32. data/test/unit/response_test.rb +1 -1
  33. data/test/unit/transport_base_test.rb +17 -8
  34. data/test/unit/transport_curb_test.rb +0 -1
  35. data/test/unit/transport_faraday_test.rb +2 -2
  36. data/test/unit/transport_manticore_test.rb +242 -155
  37. metadata +62 -84
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9248a875060a4bd8e85ffb8f146beff61553150c155b0144e6c1c85112cdc640
4
- data.tar.gz: 517e365d3b98a44646daea7e89ae1d457220b2be00f5a432c780e6f556b36728
3
+ metadata.gz: 9e0c37098be528ebc37efec244285f3601ba8251c355195d5d64b5d3d6146b01
4
+ data.tar.gz: 7dd46af46b3ae346e4f76a6f8cae669e27964cddd8cd9e91ca4329cf34f8fb74
5
5
  SHA512:
6
- metadata.gz: 4921027c53dcf8b98a0d7e2051c4dfe879f4d169f1e0d1c02c0aefe874f9f5b649f1ed6b6f82409e045582cccb8e69c87ab00dcd73a9e2f200f16ef07a04f59e
7
- data.tar.gz: 7951d5d57c08454ddaa514fb2af1e12703d4028149853d928609af9f3fae8d6252fd50f18514e78a9d335bf47da6660a272445d4cd4b5c93fe3f6ba71422808f
6
+ metadata.gz: 7c1d381899e45182eb000f45f1e014c2944741f817276424cefcbce05e26878da524ecc0c8d64c2dba3b290e755b30a7428b11fb6d65fd417528d92007418ed6
7
+ data.tar.gz: f2639368b74d2884376420cccb89e591dae6ecbfdc4632224d1f073ba34000ea54c498e5cb6256a60dda8f9a9a02a218158fbef64d62e1b6118b13b6912a2334
data/Gemfile CHANGED
@@ -20,23 +20,23 @@ source 'https://rubygems.org'
20
20
  # Specify your gem's dependencies in elasticsearch-transport.gemspec
21
21
  gemspec
22
22
 
23
- if File.exist? File.expand_path('../../elasticsearch-api/elasticsearch-api.gemspec', __FILE__)
24
- gem 'elasticsearch-api', path: File.expand_path('../../elasticsearch-api', __FILE__), require: false
23
+ if File.exist? File.expand_path('../elasticsearch-api/elasticsearch-api.gemspec', __dir__)
24
+ gem 'elasticsearch-api', path: File.expand_path('../elasticsearch-api', __dir__), require: false
25
25
  end
26
26
 
27
- if File.exist? File.expand_path('../../elasticsearch-extensions/elasticsearch-extensions.gemspec', __FILE__)
28
- gem 'elasticsearch-extensions', path: File.expand_path('../../elasticsearch-extensions', __FILE__), require: false
27
+ if File.exist? File.expand_path('../elasticsearch/elasticsearch.gemspec', __dir__)
28
+ gem 'elasticsearch', path: File.expand_path('../elasticsearch', __dir__), require: false
29
29
  end
30
30
 
31
- if File.exist? File.expand_path('../../elasticsearch/elasticsearch.gemspec', __FILE__)
32
- gem 'elasticsearch', path: File.expand_path('../../elasticsearch', __FILE__), require: false
33
- end
34
-
35
- group :development do
31
+ group :development, :test do
32
+ gem 'faraday-httpclient'
33
+ gem 'faraday-net_http_persistent'
34
+ gem 'faraday-patron' unless defined? JRUBY_VERSION
35
+ gem 'faraday-typhoeus'
36
36
  gem 'rspec'
37
37
  if defined?(JRUBY_VERSION)
38
38
  gem 'pry-nav'
39
39
  else
40
- gem 'pry-byebug'
40
+ gem 'pry-byebug', '~> 3.9'
41
41
  end
42
42
  end
@@ -0,0 +1,47 @@
1
+ # Licensed to Elasticsearch B.V. under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with
3
+ # this work for additional information regarding copyright
4
+ # ownership. Elasticsearch B.V. licenses this file to you under
5
+ # the Apache License, Version 2.0 (the "License"); you may
6
+ # not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing,
12
+ # software distributed under the License is distributed on an
13
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
+ # KIND, either express or implied. See the License for the
15
+ # specific language governing permissions and limitations
16
+ # under the License.
17
+
18
+ source 'https://rubygems.org'
19
+
20
+ # Usage:
21
+ #
22
+ # $ BUNDLE_GEMFILE=./Gemfile-faraday1.gemfile bundle install
23
+ # $ BUNDLE_GEMFILE=./Gemfile-faraday1.gemfile bundle exec rake test:faraday1:unit
24
+
25
+ gem 'faraday', '~> 1'
26
+ gemspec path: './'
27
+
28
+ if File.exist? File.expand_path('../elasticsearch-api/elasticsearch-api.gemspec', __dir__)
29
+ gem 'elasticsearch-api', path: File.expand_path('../elasticsearch-api', __dir__), require: false
30
+ end
31
+
32
+ if File.exist? File.expand_path('../elasticsearch/elasticsearch.gemspec', __dir__)
33
+ gem 'elasticsearch', path: File.expand_path('../elasticsearch', __dir__), require: false
34
+ end
35
+
36
+ group :development, :test do
37
+ gem 'httpclient'
38
+ gem 'net-http-persistent'
39
+ gem 'patron' unless defined? JRUBY_VERSION
40
+ gem 'typhoeus'
41
+ gem 'rspec'
42
+ if defined?(JRUBY_VERSION)
43
+ gem 'pry-nav'
44
+ else
45
+ gem 'pry-byebug'
46
+ end
47
+ end
data/README.md CHANGED
@@ -136,7 +136,7 @@ Please see below for an exception to this when connecting using an Elastic Cloud
136
136
 
137
137
  If you are using [Elastic Cloud](https://www.elastic.co/cloud), you can provide your cloud id to the client.
138
138
  You must supply your username and password separately, and optionally a port. If no port is supplied,
139
- port 9243 will be used.
139
+ port 443 will be used.
140
140
 
141
141
  Note: Do not enable sniffing when using Elastic Cloud. The nodes are behind a load balancer so
142
142
  Elastic Cloud will take care of everything for you.
@@ -187,18 +187,24 @@ Elasticsearch::Client.new(
187
187
 
188
188
  ### Logging
189
189
 
190
- To log requests and responses to standard output with the default logger (an instance of Ruby's {::Logger} class),
191
- set the `log` argument:
190
+ To log requests and responses to standard output with the default logger (an instance of Ruby's {::Logger} class), set the `log` argument to true:
192
191
 
193
192
  ```ruby
194
- Elasticsearch::Client.new log: true
193
+ Elasticsearch::Client.new(log: true)
194
+ ```
195
+
196
+ You can also use [ecs-logging](https://github.com/elastic/ecs-logging-ruby). `ecs-logging` is a set of libraries that allows you to transform your application logs to structured logs that comply with the [Elastic Common Schema (ECS)](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html):
197
+
198
+ ```ruby
199
+ logger = EcsLogging::Logger.new($stdout)
200
+ Elasticsearch::Client.new(logger: logger)
195
201
  ```
196
202
 
197
203
 
198
204
  To trace requests and responses in the _Curl_ format, set the `trace` argument:
199
205
 
200
206
  ```ruby
201
- Elasticsearch::Client.new trace: true
207
+ Elasticsearch::Client.new(trace: true)
202
208
  ```
203
209
 
204
210
  You can customize the default logger or tracer:
@@ -211,7 +217,7 @@ You can customize the default logger or tracer:
211
217
  Or, you can use a custom `::Logger` instance:
212
218
 
213
219
  ```ruby
214
- Elasticsearch::Client.new logger: Logger.new(STDERR)
220
+ Elasticsearch::Client.new(logger: Logger.new(STDERR))
215
221
  ```
216
222
 
217
223
  You can pass the client any conforming logger implementation:
@@ -223,7 +229,7 @@ log = Logging.logger['elasticsearch']
223
229
  log.add_appenders Logging.appenders.stdout
224
230
  log.level = :info
225
231
 
226
- client = Elasticsearch::Client.new logger: log
232
+ client = Elasticsearch::Client.new(logger: log)
227
233
  ```
228
234
 
229
235
  ### Custom HTTP Headers
@@ -293,11 +299,16 @@ on a different host:
293
299
 
294
300
  Elasticsearch::Client.new hosts: ['localhost:9200', 'localhost:9201'], retry_on_failure: true
295
301
 
296
- You can specify how many times should the client retry the request before it raises an exception
297
- (the default is 3 times):
302
+ By default, the client will retry the request 3 times. You can specify how many times to retry before it raises an exception by passing a number to `retry_on_failure`:
298
303
 
299
304
  Elasticsearch::Client.new hosts: ['localhost:9200', 'localhost:9201'], retry_on_failure: 5
300
305
 
306
+ These two parameters can also be used together:
307
+
308
+ ```ruby
309
+ Elasticsearch::Client.new hosts: ['localhost:9200', 'localhost:9201'], retry_on_status: [502, 503], retry_on_failure: 10
310
+ ```
311
+
301
312
  ### Reloading Hosts
302
313
 
303
314
  Elasticsearch by default dynamically discovers new nodes in the cluster. You can leverage this
@@ -413,10 +424,7 @@ To configure the _Faraday_ instance directly, use a block:
413
424
  f.adapter :patron
414
425
  end
415
426
 
416
- You can use any standard Faraday middleware and plugins in the configuration block, for example sign the requests for the [AWS Elasticsearch service](https://aws.amazon.com/elasticsearch-service/). See [the AWS documentation](https://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-request-signing.html#es-request-signing-ruby) for an example.
417
-
418
- You can also initialize the transport class yourself, and pass it to the client constructor
419
- as the `transport` argument:
427
+ You can use any standard Faraday middleware and plugins in the configuration block. You can also initialize the transport class yourself, and pass it to the client constructor as the `transport` argument:
420
428
 
421
429
  ```ruby
422
430
  require 'patron'
@@ -550,16 +558,14 @@ Github's pull requests and issues are used to communicate, send bug reports and
550
558
  To work on the code, clone and bootstrap the main repository first --
551
559
  please see instructions in the main [README](../README.md#development).
552
560
 
553
- To run tests, launch a testing cluster -- again, see instructions
554
- in the main [README](../README.md#development) -- and use the Rake tasks:
561
+ To run tests, launch a testing cluster and use the Rake tasks:
555
562
 
556
563
  ```
557
564
  time rake test:unit
558
565
  time rake test:integration
559
566
  ```
560
567
 
561
- Unit tests have to use Ruby 1.8 compatible syntax, integration tests
562
- can use Ruby 2.x syntax and features.
568
+ Use `COVERAGE=true` before running a test task to check coverage with Simplecov.
563
569
 
564
570
  ## License
565
571
 
data/Rakefile CHANGED
@@ -25,40 +25,77 @@ task :test => 'test:unit'
25
25
 
26
26
  require 'rake/testtask'
27
27
  require 'rspec/core/rake_task'
28
+ FARADAY1_GEMFILE = 'Gemfile-faraday1.gemfile'.freeze
29
+ GEMFILES = ['Gemfile', FARADAY1_GEMFILE].freeze
28
30
 
29
- namespace :test do
31
+ task :install do
32
+ GEMFILES.each do |gemfile|
33
+ gemfile = File.expand_path("../#{gemfile}", __FILE__)
34
+ sh "bundle install --gemfile #{gemfile}"
35
+ end
36
+ end
30
37
 
31
- desc "Wait for Elasticsearch to be in a green state"
38
+ namespace :test do
39
+ desc 'Wait for Elasticsearch to be in a green state'
32
40
  task :wait_for_green do
33
41
  sh '../scripts/wait-cluster.sh'
34
42
  end
35
43
 
36
- task :spec => :wait_for_green
37
44
  RSpec::Core::RakeTask.new(:spec)
38
45
 
39
46
  Rake::TestTask.new(:unit) do |test|
40
47
  test.libs << 'lib' << 'test'
41
- test.test_files = FileList["test/unit/**/*_test.rb"]
48
+ test.test_files = FileList['test/unit/**/*_test.rb']
42
49
  test.verbose = false
43
50
  test.warning = false
44
51
  end
45
52
 
46
53
  Rake::TestTask.new(:integration) do |test|
47
54
  test.libs << 'lib' << 'test'
48
- test.test_files = FileList["test/integration/**/*_test.rb"]
49
- test.deps = [ 'test:wait_for_green', 'test:spec' ]
55
+ test.test_files = FileList['test/integration/**/*_test.rb']
56
+ test.deps = ['test:wait_for_green', 'test:spec']
50
57
  test.verbose = false
51
58
  test.warning = false
52
59
  end
53
60
 
54
- Rake::TestTask.new(:all) do |test|
55
- test.libs << 'lib' << 'test'
56
- test.test_files = FileList["test/unit/**/*_test.rb", "test/integration/**/*_test.rb"]
61
+ desc 'Run all tests'
62
+ task :all do
63
+ Rake::Task['test:unit'].invoke
64
+ Rake::Task['test:spec'].invoke
65
+ Rake::Task['test:integration'].invoke
57
66
  end
58
67
 
59
68
  Rake::TestTask.new(:profile) do |test|
60
69
  test.libs << 'lib' << 'test'
61
- test.test_files = FileList["test/profile/**/*_test.rb"]
70
+ test.test_files = FileList['test/profile/**/*_test.rb']
71
+ end
72
+
73
+ namespace :faraday1 do
74
+ desc 'Faraday 1: Run RSpec with dependency on Faraday 1'
75
+ task :spec do
76
+ sh "BUNDLE_GEMFILE=#{FARADAY1_GEMFILE} bundle exec rspec"
77
+ end
78
+
79
+ desc 'Faraday 1: Run unit tests with dependency on Faraday 1'
80
+ task :unit do
81
+ Dir.glob('./test/unit/**/**.rb').each do |test|
82
+ sh "BUNDLE_GEMFILE=#{FARADAY1_GEMFILE} ruby -Ilib:test #{test}"
83
+ end
84
+ end
85
+
86
+ desc 'Faraday 1: Run integration tests with dependency on Faraday 1'
87
+ task :integration do
88
+ Dir.glob('./test/integration/**/**.rb').each do |test|
89
+ sh "BUNDLE_GEMFILE=#{FARADAY1_GEMFILE} ruby -Ilib:test #{test}"
90
+ end
91
+ end
92
+
93
+ desc 'Faraday 1: Run all tests'
94
+ task :all do
95
+ Rake::Task['test:faraday1:unit'].invoke
96
+ Rake::Task['test:faraday1:spec'].invoke
97
+ Rake::Task['test:faraday1:integration'].invoke
98
+ end
62
99
  end
63
100
 
64
101
  namespace :cluster do
@@ -26,12 +26,12 @@ Gem::Specification.new do |s|
26
26
  s.authors = ['Karel Minarik']
27
27
  s.email = ['karel.minarik@elasticsearch.org']
28
28
  s.summary = 'Ruby client for Elasticsearch.'
29
- s.homepage = 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/7.x/index.html'
29
+ s.homepage = 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/7.16/index.html'
30
30
  s.license = 'Apache-2.0'
31
31
  s.metadata = {
32
- 'homepage_uri' => 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/7.x/index.html',
33
- 'changelog_uri' => 'https://github.com/elastic/elasticsearch-ruby/blob/7.x/CHANGELOG.md',
34
- 'source_code_uri' => 'https://github.com/elastic/elasticsearch-ruby/tree/7.x/elasticsearch-transport',
32
+ 'homepage_uri' => 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/7.16/index.html',
33
+ 'changelog_uri' => 'https://github.com/elastic/elasticsearch-ruby/blob/7.16/CHANGELOG.md',
34
+ 'source_code_uri' => 'https://github.com/elastic/elasticsearch-ruby/tree/7.16/elasticsearch-transport',
35
35
  'bug_tracker_uri' => 'https://github.com/elastic/elasticsearch-ruby/issues'
36
36
  }
37
37
  s.files = `git ls-files`.split($/)
@@ -45,31 +45,29 @@ Gem::Specification.new do |s|
45
45
  s.required_ruby_version = '>= 2.4'
46
46
 
47
47
  s.add_dependency 'multi_json'
48
- s.add_dependency 'faraday', '~> 1'
48
+ s.add_dependency 'faraday', '>= 1', '< 3'
49
49
 
50
+ # Faraday Adapters
51
+ s.add_development_dependency 'manticore' if defined? JRUBY_VERSION
52
+ s.add_development_dependency 'curb' unless defined? JRUBY_VERSION
53
+ s.add_development_dependency 'ansi'
54
+ s.add_development_dependency 'bundler'
50
55
  s.add_development_dependency 'cane'
51
- s.add_development_dependency 'curb' unless defined? JRUBY_VERSION
56
+ s.add_development_dependency 'elasticsearch', ['>= 7', '< 8.0.0']
52
57
  s.add_development_dependency 'elasticsearch-extensions'
58
+ s.add_development_dependency 'hashie'
53
59
  s.add_development_dependency 'minitest'
54
60
  s.add_development_dependency 'minitest-reporters'
55
- s.add_development_dependency 'rake', '~> 13'
56
- s.add_development_dependency 'require-prof' unless defined?(JRUBY_VERSION) || defined?(Rubinius)
57
- s.add_development_dependency 'ruby-prof' unless defined?(JRUBY_VERSION) || defined?(Rubinius)
58
- s.add_development_dependency 'simplecov', '~> 0.17', '< 0.18'
59
- s.add_development_dependency 'simplecov-rcov'
60
- s.add_development_dependency 'ansi'
61
- s.add_development_dependency 'hashie'
62
- s.add_development_dependency 'httpclient'
63
- s.add_development_dependency 'manticore', '~> 0.6' if defined? JRUBY_VERSION
64
61
  s.add_development_dependency 'mocha'
65
- s.add_development_dependency 'net-http-persistent'
66
62
  s.add_development_dependency 'patron' unless defined? JRUBY_VERSION
67
63
  s.add_development_dependency 'pry'
64
+ s.add_development_dependency 'rake', '~> 13'
65
+ s.add_development_dependency 'require-prof' unless defined?(JRUBY_VERSION) || defined?(Rubinius)
66
+ s.add_development_dependency 'ruby-prof' unless defined?(JRUBY_VERSION) || defined?(Rubinius)
68
67
  s.add_development_dependency 'shoulda-context'
68
+ s.add_development_dependency 'simplecov'
69
69
  s.add_development_dependency 'test-unit', '~> 2'
70
- s.add_development_dependency 'typhoeus', '~> 1.4'
71
70
  s.add_development_dependency 'yard'
72
- s.add_development_dependency 'bundler'
73
71
 
74
72
  s.description = <<-DESC.gsub(/^ /, '')
75
73
  Ruby client for Elasticsearch. See the `elasticsearch` gem for full integration.
@@ -16,16 +16,17 @@
16
16
  # under the License.
17
17
 
18
18
  require 'base64'
19
+ require 'elasticsearch/transport/meta_header'
19
20
 
20
21
  module Elasticsearch
21
22
  module Transport
22
-
23
23
  # Handles communication with an Elasticsearch cluster.
24
24
  #
25
25
  # See {file:README.md README} for usage and code examples.
26
26
  #
27
27
  class Client
28
- DEFAULT_TRANSPORT_CLASS = Transport::HTTP::Faraday
28
+ include MetaHeader
29
+ DEFAULT_TRANSPORT_CLASS = Transport::HTTP::Faraday
29
30
 
30
31
  DEFAULT_LOGGER = lambda do
31
32
  require 'logger'
@@ -49,9 +50,15 @@ module Elasticsearch
49
50
  DEFAULT_HOST = 'localhost:9200'.freeze
50
51
 
51
52
  # The default port to use if connecting using a Cloud ID.
53
+ # Updated from 9243 to 443 in client version 7.10.1
52
54
  #
53
55
  # @since 7.2.0
54
- DEFAULT_CLOUD_PORT = 9243
56
+ DEFAULT_CLOUD_PORT = 443
57
+
58
+ # The default port to use if not otherwise specified.
59
+ #
60
+ # @since 7.2.0
61
+ DEFAULT_PORT = 9200
55
62
 
56
63
  # Returns the transport object.
57
64
  #
@@ -85,6 +92,8 @@ module Elasticsearch
85
92
  #
86
93
  # @option arguments [Boolean,Number] :retry_on_failure Retry X times when request fails before raising and
87
94
  # exception (false by default)
95
+ # @option arguments [Number] :delay_on_retry Delay in milliseconds between each retry (0 by default)
96
+ #
88
97
  # @option arguments Array<Number> :retry_on_status Retry when specific status codes are returned
89
98
  #
90
99
  # @option arguments [Boolean] :reload_on_failure Reload connections after failure (false by default)
@@ -114,8 +123,12 @@ module Elasticsearch
114
123
  #
115
124
  # @option api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
116
125
  # joined by a colon as a String, or a hash with the `id` and `api_key` values.
117
- # @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client. This
118
- # will be prepended to the id you set before each request if you're using X-Opaque-Id
126
+ # @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client.
127
+ # This will be prepended to the id you set before each request
128
+ # if you're using X-Opaque-Id
129
+ # @option enable_meta_header [Boolean] :enable_meta_header Enable sending the meta data header to Cloud.
130
+ # (Default: true)
131
+ # @option ca_fingerprint [String] :ca_fingerprint provide this value to only trust certificates that are signed by a specific CA certificate
119
132
  #
120
133
  # @yield [faraday] Access and configure the `Faraday::Connection` instance directly with a block
121
134
  #
@@ -126,24 +139,28 @@ module Elasticsearch
126
139
  @arguments[:tracer] ||= @arguments[:trace] ? DEFAULT_TRACER.call() : nil
127
140
  @arguments[:reload_connections] ||= false
128
141
  @arguments[:retry_on_failure] ||= false
142
+ @arguments[:delay_on_retry] ||= 0
129
143
  @arguments[:reload_on_failure] ||= false
130
144
  @arguments[:randomize_hosts] ||= false
131
145
  @arguments[:transport_options] ||= {}
132
146
  @arguments[:http] ||= {}
147
+ @arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header) { true }
133
148
  @options[:http] ||= {}
134
149
 
135
150
  set_api_key if (@api_key = @arguments[:api_key])
151
+ set_compatibility_header if ENV['ELASTIC_CLIENT_APIVERSIONING']
136
152
 
137
153
  @seeds = extract_cloud_creds(@arguments)
138
154
  @seeds ||= __extract_hosts(@arguments[:hosts] ||
139
- @arguments[:host] ||
140
- @arguments[:url] ||
141
- @arguments[:urls] ||
142
- ENV['ELASTICSEARCH_URL'] ||
143
- DEFAULT_HOST)
155
+ @arguments[:host] ||
156
+ @arguments[:url] ||
157
+ @arguments[:urls] ||
158
+ ENV['ELASTICSEARCH_URL'] ||
159
+ DEFAULT_HOST)
144
160
 
145
161
  @send_get_body_as = @arguments[:send_get_body_as] || 'GET'
146
162
  @opaque_id_prefix = @arguments[:opaque_id_prefix] || nil
163
+ @ca_fingerprint = @arguments.delete(:ca_fingerprint)
147
164
 
148
165
  if @arguments[:request_timeout]
149
166
  @arguments[:transport_options][:request] = { timeout: @arguments[:request_timeout] }
@@ -152,15 +169,18 @@ module Elasticsearch
152
169
  if @arguments[:transport]
153
170
  @transport = @arguments[:transport]
154
171
  else
155
- transport_class = @arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
156
- if transport_class == Transport::HTTP::Faraday
157
- @transport = transport_class.new(hosts: @seeds, options: @arguments) do |faraday|
158
- faraday.adapter(@arguments[:adapter] || __auto_detect_adapter)
159
- block&.call faraday
160
- end
161
- else
162
- @transport = transport_class.new(hosts: @seeds, options: @arguments)
163
- end
172
+ @transport_class = @arguments[:transport_class] || DEFAULT_TRANSPORT_CLASS
173
+ @transport = if @transport_class == Transport::HTTP::Faraday
174
+ @arguments[:adapter] ||= __auto_detect_adapter
175
+ set_meta_header # from include MetaHeader
176
+ @transport_class.new(hosts: @seeds, options: @arguments) do |faraday|
177
+ faraday.adapter(@arguments[:adapter])
178
+ block&.call faraday
179
+ end
180
+ else
181
+ set_meta_header # from include MetaHeader
182
+ @transport_class.new(hosts: @seeds, options: @arguments)
183
+ end
164
184
  end
165
185
  end
166
186
 
@@ -173,6 +193,7 @@ module Elasticsearch
173
193
  opaque_id = @opaque_id_prefix ? "#{@opaque_id_prefix}#{opaque_id}" : opaque_id
174
194
  headers.merge!('X-Opaque-Id' => opaque_id)
175
195
  end
196
+ validate_ca_fingerprints if @ca_fingerprint
176
197
  transport.perform_request(method, path, params, body, headers)
177
198
  end
178
199
 
@@ -180,24 +201,78 @@ module Elasticsearch
180
201
 
181
202
  def set_api_key
182
203
  @api_key = __encode(@api_key) if @api_key.is_a? Hash
204
+ add_header('Authorization' => "ApiKey #{@api_key}")
205
+ @arguments.delete(:user)
206
+ @arguments.delete(:password)
207
+ end
208
+
209
+ def set_compatibility_header
210
+ return unless ['1', 'true'].include?(ENV['ELASTIC_CLIENT_APIVERSIONING'])
211
+ return if instance_variable_get('@options').dig(:transport_options, :headers, 'Accept')
212
+
213
+ add_header(
214
+ {
215
+ 'Accept' => 'application/vnd.elasticsearch+json; compatible-with=7',
216
+ 'Content-Type' => 'application/vnd.elasticsearch+json; compatible-with=7'
217
+ }
218
+ )
219
+ end
220
+
221
+ def validate_ca_fingerprints
222
+ transport.connections.connections.each do |connection|
223
+ unless connection.host[:scheme] == 'https'
224
+ raise Elasticsearch::Transport::Transport::Error, 'CA fingerprinting can\'t be configured over http'
225
+ end
226
+
227
+ next if connection.verified
228
+
229
+ ctx = OpenSSL::SSL::SSLContext.new
230
+ socket = TCPSocket.new(connection.host[:host], connection.host[:port])
231
+ ssl = OpenSSL::SSL::SSLSocket.new(socket, ctx)
232
+ ssl.connect
233
+ cert_store = ssl.peer_cert_chain
234
+ matching_certs = cert_store.select do |cert|
235
+ OpenSSL::Digest::SHA256.hexdigest(cert.to_der).upcase == @ca_fingerprint.upcase.gsub(':', '')
236
+ end
237
+ if matching_certs.empty?
238
+ raise Elasticsearch::Transport::Transport::Error,
239
+ 'Server certificate CA fingerprint does not match the value configured in ca_fingerprint'
240
+ end
241
+
242
+ connection.verified = true
243
+ end
244
+ end
245
+
246
+ def add_header(header)
183
247
  headers = @arguments[:transport_options]&.[](:headers) || {}
184
- headers.merge!('Authorization' => "ApiKey #{@api_key}")
248
+ headers.merge!(header)
185
249
  @arguments[:transport_options].merge!(
186
250
  headers: headers
187
251
  )
188
- @arguments.delete(:user)
189
- @arguments.delete(:password)
190
252
  end
191
253
 
192
254
  def extract_cloud_creds(arguments)
193
- return unless arguments[:cloud_id]
255
+ return unless arguments[:cloud_id] && !arguments[:cloud_id].empty?
256
+
194
257
  name = arguments[:cloud_id].split(':')[0]
195
258
  cloud_url, elasticsearch_instance = Base64.decode64(arguments[:cloud_id].gsub("#{name}:", '')).split('$')
196
- [ { scheme: 'https',
259
+
260
+ if cloud_url.include?(':')
261
+ url, port = cloud_url.split(':')
262
+ host = "#{elasticsearch_instance}.#{url}"
263
+ else
264
+ host = "#{elasticsearch_instance}.#{cloud_url}"
265
+ port = arguments[:port] || DEFAULT_CLOUD_PORT
266
+ end
267
+ [
268
+ {
269
+ scheme: 'https',
197
270
  user: arguments[:user],
198
271
  password: arguments[:password],
199
- host: "#{elasticsearch_instance}.#{cloud_url}",
200
- port: arguments[:port] || DEFAULT_CLOUD_PORT } ]
272
+ host: host,
273
+ port: port.to_i
274
+ }
275
+ ]
201
276
  end
202
277
 
203
278
  # Normalizes and returns hosts configuration.
@@ -230,36 +305,38 @@ module Elasticsearch
230
305
 
231
306
  def __parse_host(host)
232
307
  host_parts = case host
233
- when String
234
- if host =~ /^[a-z]+\:\/\//
235
- # Construct a new `URI::Generic` directly from the array returned by URI::split.
236
- # This avoids `URI::HTTP` and `URI::HTTPS`, which supply default ports.
237
- uri = URI::Generic.new(*URI.split(host))
238
-
239
- { :scheme => uri.scheme,
240
- :user => uri.user,
241
- :password => uri.password,
242
- :host => uri.host,
243
- :path => uri.path,
244
- :port => uri.port }
245
- else
246
- host, port = host.split(':')
247
- { :host => host,
248
- :port => port }
249
- end
250
- when URI
251
- { :scheme => host.scheme,
252
- :user => host.user,
253
- :password => host.password,
254
- :host => host.host,
255
- :path => host.path,
256
- :port => host.port }
257
- when Hash
258
- host
259
- else
260
- raise ArgumentError, "Please pass host as a String, URI or Hash -- #{host.class} given."
261
- end
262
-
308
+ when String
309
+ if host =~ /^[a-z]+\:\/\//
310
+ # Construct a new `URI::Generic` directly from the array returned by URI::split.
311
+ # This avoids `URI::HTTP` and `URI::HTTPS`, which supply default ports.
312
+ uri = URI::Generic.new(*URI.split(host))
313
+ default_port = uri.scheme == 'https' ? 443 : DEFAULT_PORT
314
+ {
315
+ scheme: uri.scheme,
316
+ user: uri.user,
317
+ password: uri.password,
318
+ host: uri.host,
319
+ path: uri.path,
320
+ port: uri.port || default_port
321
+ }
322
+ else
323
+ host, port = host.split(':')
324
+ { host: host, port: port }
325
+ end
326
+ when URI
327
+ {
328
+ scheme: host.scheme,
329
+ user: host.user,
330
+ password: host.password,
331
+ host: host.host,
332
+ path: host.path,
333
+ port: host.port
334
+ }
335
+ when Hash
336
+ host
337
+ else
338
+ raise ArgumentError, "Please pass host as a String, URI or Hash -- #{host.class} given."
339
+ end
263
340
  if @api_key
264
341
  # Remove Basic Auth if using API KEY
265
342
  host_parts.delete(:user)