elasticsearch 8.8.0 → 8.10.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/bin/elastic_ruby_console +2 -0
- data/elasticsearch.gemspec +3 -3
- data/lib/elasticsearch/helpers/bulk_helper.rb +127 -0
- data/lib/elasticsearch/helpers/scroll_helper.rb +95 -0
- data/lib/elasticsearch/version.rb +1 -1
- data/lib/elasticsearch.rb +59 -44
- data/spec/integration/helpers/bulk_helper_spec.rb +211 -0
- data/spec/integration/helpers/scroll_helper_spec.rb +91 -0
- data/spec/unit/elasticsearch_product_validation_spec.rb +35 -189
- data/spec/unit/user_agent_spec.rb +69 -0
- metadata +16 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01703b098a99215d5b9b9591ed0e978843b55314b9dfa2d8fcf4abeebbd7dd1e
|
4
|
+
data.tar.gz: 907bf243f4dbc446d962b4055e80a08688b2a28c0fe3a087c8c4e8b648600380
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c1f8db8b41885f95e07b12e28f7d402c2695cb2497d0e1de348918c0f0ca7740731dfad4770bd3319e9df2c216b23e00e73ba4ca51e5be2686ee86f4b3b4889
|
7
|
+
data.tar.gz: 4e770597919914ff503d30c3a81bfa38d0361d626eb04e75a7168728baf402ea479bc7a84febde17abf1e7ca85f0a6efdafde429f2d28755966e2ddb06383334
|
data/bin/elastic_ruby_console
CHANGED
@@ -2,9 +2,11 @@
|
|
2
2
|
|
3
3
|
$LOAD_PATH.unshift(File.expand_path('../../elasticsearch/lib', __dir__))
|
4
4
|
$LOAD_PATH.unshift(File.expand_path('../../elasticsearch-api/lib', __dir__))
|
5
|
+
$LOAD_PATH.unshift(File.expand_path('../../elasticsearch/lib/elasticsearch/helpers', __dir__))
|
5
6
|
|
6
7
|
require 'elasticsearch'
|
7
8
|
require 'elasticsearch-api'
|
9
|
+
require 'elasticsearch/helpers/bulk_helper'
|
8
10
|
|
9
11
|
include Elasticsearch
|
10
12
|
|
data/elasticsearch.gemspec
CHANGED
@@ -22,8 +22,8 @@ require 'elasticsearch/version'
|
|
22
22
|
Gem::Specification.new do |s|
|
23
23
|
s.name = 'elasticsearch'
|
24
24
|
s.version = Elasticsearch::VERSION
|
25
|
-
s.authors = ['Karel Minarik']
|
26
|
-
s.email = ['
|
25
|
+
s.authors = ['Karel Minarik', 'Emily Stolfo', 'Fernando Briano']
|
26
|
+
s.email = ['clients-team@elastic.co']
|
27
27
|
s.summary = 'Ruby integrations for Elasticsearch'
|
28
28
|
s.homepage = 'https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/index.html'
|
29
29
|
s.license = 'Apache-2.0'
|
@@ -46,7 +46,7 @@ Gem::Specification.new do |s|
|
|
46
46
|
s.required_ruby_version = '>= 2.5'
|
47
47
|
|
48
48
|
s.add_dependency 'elastic-transport', '~> 8'
|
49
|
-
s.add_dependency 'elasticsearch-api', '8.
|
49
|
+
s.add_dependency 'elasticsearch-api', '8.10.0'
|
50
50
|
|
51
51
|
s.add_development_dependency 'bundler'
|
52
52
|
s.add_development_dependency 'byebug' unless defined?(JRUBY_VERSION) || defined?(Rubinius)
|
@@ -0,0 +1,127 @@
|
|
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
|
+
module Elasticsearch
|
19
|
+
module Helpers
|
20
|
+
# Elasticsearch Client Helper for the Bulk API
|
21
|
+
#
|
22
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/master/docs-bulk.html
|
23
|
+
#
|
24
|
+
class BulkHelper
|
25
|
+
attr_accessor :index
|
26
|
+
|
27
|
+
# Create a BulkHelper
|
28
|
+
#
|
29
|
+
# @param [Elasticsearch::Client] client Instance of Elasticsearch client to use.
|
30
|
+
# @param [String] index Index on which to perform the Bulk actions.
|
31
|
+
# @param [Hash] params Parameters to re-use in every bulk call
|
32
|
+
#
|
33
|
+
def initialize(client, index, params = {})
|
34
|
+
@client = client
|
35
|
+
@index = index
|
36
|
+
@params = params
|
37
|
+
end
|
38
|
+
|
39
|
+
# Index documents using the Bulk API.
|
40
|
+
#
|
41
|
+
# @param [Array<Hash>] docs The documents to be indexed.
|
42
|
+
# @param [Hash] params Parameters to use in the bulk ingestion. See the official Elastic documentation for Bulk API for parameters to send to the Bulk API.
|
43
|
+
# @option params [Integer] slice number of documents to send to the Bulk API for eatch batch of ingestion.
|
44
|
+
# @param block [Block] Optional block to run after ingesting a batch of documents.
|
45
|
+
# @yieldparam response [Elasticsearch::Transport::Response] The response object from calling the Bulk API.
|
46
|
+
# @yieldparam ingest_docs [Array<Hash>] The collection of documents sent in the bulk request.
|
47
|
+
#
|
48
|
+
def ingest(docs, params = {}, body = {}, &block)
|
49
|
+
ingest_docs = docs.map { |doc| { index: { _index: @index, data: doc} } }
|
50
|
+
if (slice = params.delete(:slice))
|
51
|
+
ingest_docs.each_slice(slice) { |items| ingest(items, params, &block) }
|
52
|
+
else
|
53
|
+
bulk_request(ingest_docs, params, &block)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Delete documents using the Bulk API
|
58
|
+
#
|
59
|
+
# @param [Array] ids Array of id's for documents to delete.
|
60
|
+
# @param [Hash] params Parameters to send to bulk delete.
|
61
|
+
#
|
62
|
+
def delete(ids, params = {}, body = {})
|
63
|
+
delete_docs = ids.map { |id| { delete: { _index: @index, _id: id} } }
|
64
|
+
@client.bulk({ body: delete_docs }.merge(params.merge(@params)))
|
65
|
+
end
|
66
|
+
|
67
|
+
# Update documents using the Bulk API
|
68
|
+
#
|
69
|
+
# @param [Array<Hash>] docs (Required) The documents to be updated.
|
70
|
+
# @option params [Integer] slice number of documents to send to the Bulk API for eatch batch of updates.
|
71
|
+
# @param block [Block] Optional block to run after ingesting a batch of documents.
|
72
|
+
#
|
73
|
+
# @yieldparam response [Elasticsearch::Transport::Response] The response object from calling the Bulk API.
|
74
|
+
# @yieldparam ingest_docs [Array<Hash>] The collection of documents sent in the bulk request.
|
75
|
+
#
|
76
|
+
def update(docs, params = {}, body = {}, &block)
|
77
|
+
ingest_docs = docs.map do |doc|
|
78
|
+
{ update: { _index: @index, _id: doc.delete('id'), data: { doc: doc } } }
|
79
|
+
end
|
80
|
+
if (slice = params.delete(:slice))
|
81
|
+
ingest_docs.each_slice(slice) { |items| update(items, params, &block) }
|
82
|
+
else
|
83
|
+
bulk_request(ingest_docs, params, &block)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Ingest data directly from a JSON file
|
88
|
+
#
|
89
|
+
# @param [String] file (Required) The file path.
|
90
|
+
# @param [Hash] params Parameters to use in the bulk ingestion.
|
91
|
+
# @option params [Integer] slice number of documents to send to the Bulk API for eatch batch of updates.
|
92
|
+
# @option params [Array|String] keys If the data needs to be digged from the JSON file, the
|
93
|
+
# keys can be passed in with this parameter to find it.
|
94
|
+
#
|
95
|
+
# E.g.: If the data in the parsed JSON Hash is found in
|
96
|
+
# +json_parsed['data']['items']+, keys would be passed
|
97
|
+
# like this (as an Array):
|
98
|
+
#
|
99
|
+
# +bulk_helper.ingest_json(file, { keys: ['data', 'items'] })+
|
100
|
+
#
|
101
|
+
# or as a String:
|
102
|
+
#
|
103
|
+
# +bulk_helper.ingest_json(file, { keys: 'data, items' })+
|
104
|
+
#
|
105
|
+
# @yieldparam response [Elasticsearch::Transport::Response] The response object from calling the Bulk API.
|
106
|
+
# @yieldparam ingest_docs [Array<Hash>] The collection of documents sent in the bulk request.
|
107
|
+
#
|
108
|
+
def ingest_json(file, params = {}, &block)
|
109
|
+
data = JSON.parse(File.read(file))
|
110
|
+
if (keys = params.delete(:keys))
|
111
|
+
keys = keys.split(',') if keys.is_a?(String)
|
112
|
+
data = data.dig(*keys)
|
113
|
+
end
|
114
|
+
|
115
|
+
ingest(data, params, &block)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def bulk_request(ingest_docs, params, &block)
|
121
|
+
response = @client.bulk({ body: ingest_docs }.merge(params.merge(@params)))
|
122
|
+
yield response, ingest_docs if block_given?
|
123
|
+
response
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,95 @@
|
|
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
|
+
module Elasticsearch
|
19
|
+
module Helpers
|
20
|
+
# Elasticsearch Client Helper for the Scroll API
|
21
|
+
#
|
22
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/scroll-api.html
|
23
|
+
#
|
24
|
+
class ScrollHelper
|
25
|
+
include Enumerable
|
26
|
+
|
27
|
+
# Create a ScrollHelper
|
28
|
+
#
|
29
|
+
# @param [Elasticsearch::Client] client (Required) Instance of Elasticsearch client to use.
|
30
|
+
# @param [String] index (Required) Index on which to perform the Bulk actions.
|
31
|
+
# @param [Hash] body Body parameters to re-use in every scroll request
|
32
|
+
# @param [Time] scroll Specify how long a consistent view of the index should be maintained for scrolled search
|
33
|
+
#
|
34
|
+
def initialize(client, index, body, scroll = '1m')
|
35
|
+
@index = index
|
36
|
+
@client = client
|
37
|
+
@scroll = scroll
|
38
|
+
@body = body
|
39
|
+
end
|
40
|
+
|
41
|
+
# Implementation of +each+ for Enumerable module inclusion
|
42
|
+
#
|
43
|
+
# @yieldparam document [Hash] yields a document found in the search hits.
|
44
|
+
#
|
45
|
+
def each(&block)
|
46
|
+
@docs = []
|
47
|
+
@scroll_id = nil
|
48
|
+
refresh_docs
|
49
|
+
for doc in @docs do
|
50
|
+
refresh_docs
|
51
|
+
yield doc
|
52
|
+
end
|
53
|
+
clear
|
54
|
+
end
|
55
|
+
|
56
|
+
# Results from a scroll.
|
57
|
+
# Can be called repeatedly (e.g. in a loop) to get the scroll pages.
|
58
|
+
#
|
59
|
+
def results
|
60
|
+
if @scroll_id
|
61
|
+
scroll_request
|
62
|
+
else
|
63
|
+
initial_search
|
64
|
+
end
|
65
|
+
rescue StandardError => e
|
66
|
+
raise e
|
67
|
+
end
|
68
|
+
|
69
|
+
# Clear Scroll and resets inner documents collection
|
70
|
+
#
|
71
|
+
def clear
|
72
|
+
@client.clear_scroll(body: { scroll_id: @scroll_id }) if @scroll_id
|
73
|
+
@docs = []
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def refresh_docs
|
79
|
+
@docs ||= []
|
80
|
+
@docs << results
|
81
|
+
@docs.flatten!
|
82
|
+
end
|
83
|
+
|
84
|
+
def initial_search
|
85
|
+
response = @client.search(index: @index, scroll: @scroll, body: @body)
|
86
|
+
@scroll_id = response['_scroll_id']
|
87
|
+
response['hits']['hits']
|
88
|
+
end
|
89
|
+
|
90
|
+
def scroll_request
|
91
|
+
@client.scroll(body: {scroll: @scroll, scroll_id: @scroll_id})['hits']['hits']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
data/lib/elasticsearch.rb
CHANGED
@@ -35,24 +35,22 @@ module Elasticsearch
|
|
35
35
|
|
36
36
|
# Create a client connected to an Elasticsearch cluster.
|
37
37
|
#
|
38
|
+
# @param [Hash] arguments - initializer arguments
|
38
39
|
# @option arguments [String] :cloud_id - The Cloud ID to connect to Elastic Cloud
|
39
|
-
# @option
|
40
|
-
#
|
41
|
-
# @option
|
42
|
-
#
|
43
|
-
#
|
40
|
+
# @option arguments [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
|
41
|
+
# joined by a colon as a String, or a hash with the `id` and `api_key` values.
|
42
|
+
# @option arguments [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client.
|
43
|
+
# This will be prepended to the id you set before each request
|
44
|
+
# if you're using X-Opaque-Id
|
45
|
+
# @option arguments [Hash] :headers Custom HTTP Request Headers
|
46
|
+
#
|
44
47
|
def initialize(arguments = {}, &block)
|
45
48
|
@verified = false
|
49
|
+
@warned = false
|
46
50
|
@opaque_id_prefix = arguments[:opaque_id_prefix] || nil
|
47
51
|
api_key(arguments) if arguments[:api_key]
|
48
|
-
if arguments[:cloud_id]
|
49
|
-
|
50
|
-
arguments[:cloud_id],
|
51
|
-
arguments[:user],
|
52
|
-
arguments[:password],
|
53
|
-
arguments[:port]
|
54
|
-
)
|
55
|
-
end
|
52
|
+
setup_cloud(arguments) if arguments[:cloud_id]
|
53
|
+
set_user_agent!(arguments) unless sent_user_agent?(arguments)
|
56
54
|
@transport = Elastic::Transport::Client.new(arguments, &block)
|
57
55
|
end
|
58
56
|
|
@@ -67,8 +65,11 @@ module Elasticsearch
|
|
67
65
|
opaque_id = @opaque_id_prefix ? "#{@opaque_id_prefix}#{opaque_id}" : opaque_id
|
68
66
|
args[4] = headers.merge('X-Opaque-Id' => opaque_id)
|
69
67
|
end
|
70
|
-
|
71
|
-
|
68
|
+
unless @verified
|
69
|
+
verify_elasticsearch(*args, &block)
|
70
|
+
else
|
71
|
+
@transport.perform_request(*args, &block)
|
72
|
+
end
|
72
73
|
else
|
73
74
|
@transport.send(name, *args, &block)
|
74
75
|
end
|
@@ -80,42 +81,28 @@ module Elasticsearch
|
|
80
81
|
|
81
82
|
private
|
82
83
|
|
83
|
-
def verify_elasticsearch
|
84
|
+
def verify_elasticsearch(*args, &block)
|
84
85
|
begin
|
85
|
-
response =
|
86
|
+
response = @transport.perform_request(*args, &block)
|
86
87
|
rescue Elastic::Transport::Transport::Errors::Unauthorized,
|
87
88
|
Elastic::Transport::Transport::Errors::Forbidden,
|
88
|
-
Elastic::Transport::Transport::Errors::RequestEntityTooLarge
|
89
|
-
@verified = true
|
89
|
+
Elastic::Transport::Transport::Errors::RequestEntityTooLarge => e
|
90
90
|
warn(SECURITY_PRIVILEGES_VALIDATION_WARNING)
|
91
|
-
|
92
|
-
|
91
|
+
@verified = true
|
92
|
+
raise e
|
93
|
+
rescue Elastic::Transport::Transport::Error => e
|
94
|
+
unless @warned
|
95
|
+
warn(VALIDATION_WARNING)
|
96
|
+
@warned = true
|
97
|
+
end
|
98
|
+
raise e
|
99
|
+
rescue StandardError => e
|
93
100
|
warn(VALIDATION_WARNING)
|
94
|
-
|
101
|
+
raise e
|
95
102
|
end
|
96
|
-
|
97
|
-
body = if response.headers['content-type'] == 'application/yaml'
|
98
|
-
require 'yaml'
|
99
|
-
YAML.safe_load(response.body)
|
100
|
-
else
|
101
|
-
response.body
|
102
|
-
end
|
103
|
-
version = body.dig('version', 'number')
|
104
|
-
verify_with_version_or_header(version, response.headers)
|
105
|
-
rescue StandardError => e
|
106
|
-
warn(VALIDATION_WARNING)
|
107
|
-
raise e
|
108
|
-
end
|
109
|
-
|
110
|
-
def verify_with_version_or_header(version, headers)
|
111
|
-
if version.nil? ||
|
112
|
-
Gem::Version.new(version) < Gem::Version.new('8.0.0.pre') && version != '8.0.0-SNAPSHOT' ||
|
113
|
-
headers['x-elastic-product'] != 'Elasticsearch'
|
114
|
-
|
115
|
-
raise Elasticsearch::UnsupportedProductError
|
116
|
-
end
|
117
|
-
|
103
|
+
raise Elasticsearch::UnsupportedProductError unless response.headers['x-elastic-product'] == 'Elasticsearch'
|
118
104
|
@verified = true
|
105
|
+
response
|
119
106
|
end
|
120
107
|
|
121
108
|
def setup_cloud_host(cloud_id, user, password, port)
|
@@ -149,6 +136,15 @@ module Elasticsearch
|
|
149
136
|
end
|
150
137
|
end
|
151
138
|
|
139
|
+
def setup_cloud(arguments)
|
140
|
+
arguments[:hosts] = setup_cloud_host(
|
141
|
+
arguments[:cloud_id],
|
142
|
+
arguments[:user],
|
143
|
+
arguments[:password],
|
144
|
+
arguments[:port]
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
152
148
|
# Encode credentials for the Authorization Header
|
153
149
|
# Credentials is the base64 encoding of id and api_key joined by a colon
|
154
150
|
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
|
@@ -159,6 +155,25 @@ module Elasticsearch
|
|
159
155
|
def elasticsearch_validation_request
|
160
156
|
@transport.perform_request('GET', '/')
|
161
157
|
end
|
158
|
+
|
159
|
+
def sent_user_agent?(arguments)
|
160
|
+
return unless (headers = arguments&.[](:transport_options)&.[](:headers))
|
161
|
+
!!headers.keys.detect { |h| h =~ /user-?_?agent/ }
|
162
|
+
end
|
163
|
+
|
164
|
+
def set_user_agent!(arguments)
|
165
|
+
user_agent = [
|
166
|
+
"elasticsearch-ruby/#{Elasticsearch::VERSION}",
|
167
|
+
"elastic-transport-ruby/#{Elastic::Transport::VERSION}",
|
168
|
+
"RUBY_VERSION: #{RUBY_VERSION}"
|
169
|
+
]
|
170
|
+
if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
|
171
|
+
user_agent << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
|
172
|
+
end
|
173
|
+
arguments[:transport_options] ||= {}
|
174
|
+
arguments[:transport_options][:headers] ||= {}
|
175
|
+
arguments[:transport_options][:headers].merge!({ user_agent: user_agent.join('; ')})
|
176
|
+
end
|
162
177
|
end
|
163
178
|
|
164
179
|
# Error class for when we detect an unsupported version of Elasticsearch
|
@@ -0,0 +1,211 @@
|
|
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
|
+
ELASTICSEARCH_URL = ENV['TEST_ES_SERVER'] || "http://localhost:#{(ENV['PORT'] || 9200)}"
|
18
|
+
raise URI::InvalidURIError unless ELASTICSEARCH_URL =~ /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
|
19
|
+
|
20
|
+
require 'elasticsearch/helpers/bulk_helper'
|
21
|
+
require 'spec_helper'
|
22
|
+
require 'tempfile'
|
23
|
+
|
24
|
+
context 'Elasticsearch client helpers' do
|
25
|
+
context 'Bulk helper' do
|
26
|
+
let(:client) do
|
27
|
+
Elasticsearch::Client.new(
|
28
|
+
host: ELASTICSEARCH_URL,
|
29
|
+
user: 'elastic',
|
30
|
+
password: 'changeme'
|
31
|
+
)
|
32
|
+
end
|
33
|
+
let(:index) { 'bulk_animals' }
|
34
|
+
let(:params) { { refresh: 'wait_for' } }
|
35
|
+
let(:bulk_helper) { Elasticsearch::Helpers::BulkHelper.new(client, index, params) }
|
36
|
+
let(:docs) do
|
37
|
+
[
|
38
|
+
{ scientific_name: 'Lama guanicoe', name:'Guanaco' },
|
39
|
+
{ scientific_name: 'Tayassu pecari', name:'White-lipped peccary' },
|
40
|
+
{ scientific_name: 'Snycerus caffer', name:'Buffalo, african' },
|
41
|
+
{ scientific_name: 'Coluber constrictor', name:'Snake, racer' },
|
42
|
+
{ scientific_name: 'Thalasseus maximus', name:'Royal tern' },
|
43
|
+
{ scientific_name: 'Centrocercus urophasianus', name:'Hen, sage' },
|
44
|
+
{ scientific_name: 'Sitta canadensis', name:'Nuthatch, red-breasted' },
|
45
|
+
{ scientific_name: 'Aegypius tracheliotus', name:'Vulture, lappet-faced' },
|
46
|
+
{ scientific_name: 'Bucephala clangula', name:'Common goldeneye' },
|
47
|
+
{ scientific_name: 'Felis pardalis', name:'Ocelot' }
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
after do
|
52
|
+
client.indices.delete(index: index, ignore: 404)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'Ingests documents' do
|
56
|
+
response = bulk_helper.ingest(docs)
|
57
|
+
expect(response).to be_an_instance_of Elasticsearch::API::Response
|
58
|
+
expect(response.status).to eq(200)
|
59
|
+
expect(response['items'].map { |a| a['index']['status'] }.uniq.first).to eq 201
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'Updates documents' do
|
63
|
+
docs = [
|
64
|
+
{ scientific_name: 'Otocyon megalotos', name: 'Bat-eared fox' },
|
65
|
+
{ scientific_name: 'Herpestes javanicus', name: 'Small Indian mongoose' }
|
66
|
+
]
|
67
|
+
bulk_helper.ingest(docs)
|
68
|
+
# Get the ingested documents, add id and modify them to update them:
|
69
|
+
animals = client.search(index: index)['hits']['hits']
|
70
|
+
# Add id to each doc
|
71
|
+
docs = animals.map { |animal| animal['_source'].merge({'id' => animal['_id'] }) }
|
72
|
+
docs.map { |doc| doc['scientific_name'].upcase! }
|
73
|
+
response = bulk_helper.update(docs)
|
74
|
+
expect(response.status).to eq(200)
|
75
|
+
expect(response['items'].map { |i| i['update']['result'] }.uniq.first).to eq('updated')
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'Deletes documents' do
|
79
|
+
response = bulk_helper.ingest(docs)
|
80
|
+
ids = response.body['items'].map { |a| a['index']['_id'] }
|
81
|
+
response = bulk_helper.delete(ids)
|
82
|
+
expect(response.status).to eq 200
|
83
|
+
expect(response['items'].map { |item| item['delete']['result'] }.uniq.first).to eq('deleted')
|
84
|
+
expect(client.count(index: index)['count']).to eq(0)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'Ingests documents and yields response and docs' do
|
88
|
+
slice = 2
|
89
|
+
response = bulk_helper.ingest(docs, {slice: slice}) do |response, docs|
|
90
|
+
expect(response).to be_an_instance_of Elasticsearch::API::Response
|
91
|
+
expect(docs.count).to eq slice
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'JSON File helper' do
|
96
|
+
let(:file) { Tempfile.new('test-data.json') }
|
97
|
+
let(:json) do
|
98
|
+
json = <<~JSON
|
99
|
+
[
|
100
|
+
{
|
101
|
+
"character_name": "Anallese Lonie",
|
102
|
+
"species": "mouse",
|
103
|
+
"catchphrase": "Seamless regional definition",
|
104
|
+
"favorite_food": "pizza"
|
105
|
+
},
|
106
|
+
{
|
107
|
+
"character_name": "Janey Davidovsky",
|
108
|
+
"species": "cat",
|
109
|
+
"catchphrase": "Down-sized responsive pricing structure",
|
110
|
+
"favorite_food": "pizza"
|
111
|
+
},
|
112
|
+
{
|
113
|
+
"character_name": "Morse Mountford",
|
114
|
+
"species": "cat",
|
115
|
+
"catchphrase": "Ameliorated modular data-warehouse",
|
116
|
+
"favorite_food": "carrots"
|
117
|
+
},
|
118
|
+
{
|
119
|
+
"character_name": "Saundra Kauble",
|
120
|
+
"species": "dog",
|
121
|
+
"catchphrase": "Synchronised 24/7 support",
|
122
|
+
"favorite_food": "carrots"
|
123
|
+
},
|
124
|
+
{
|
125
|
+
"character_name": "Kain Viggars",
|
126
|
+
"species": "cat",
|
127
|
+
"catchphrase": "Open-architected asymmetric circuit",
|
128
|
+
"favorite_food": "carrots"
|
129
|
+
}
|
130
|
+
]
|
131
|
+
JSON
|
132
|
+
end
|
133
|
+
|
134
|
+
before do
|
135
|
+
file.write(json)
|
136
|
+
file.rewind
|
137
|
+
end
|
138
|
+
|
139
|
+
after do
|
140
|
+
file.close
|
141
|
+
file.unlink
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'Ingests a JSON file' do
|
145
|
+
response = bulk_helper.ingest_json(file)
|
146
|
+
|
147
|
+
expect(response).to be_an_instance_of Elasticsearch::API::Response
|
148
|
+
expect(response.status).to eq(200)
|
149
|
+
end
|
150
|
+
|
151
|
+
context 'with data not in root of JSON file' do
|
152
|
+
let(:json) do
|
153
|
+
json = <<~JSON
|
154
|
+
{
|
155
|
+
"field": "value",
|
156
|
+
"status": 200,
|
157
|
+
"data": {
|
158
|
+
"items": [
|
159
|
+
{
|
160
|
+
"character_name": "Anallese Lonie",
|
161
|
+
"species": "mouse",
|
162
|
+
"catchphrase": "Seamless regional definition",
|
163
|
+
"favorite_food": "pizza"
|
164
|
+
},
|
165
|
+
{
|
166
|
+
"character_name": "Janey Davidovsky",
|
167
|
+
"species": "cat",
|
168
|
+
"catchphrase": "Down-sized responsive pricing structure",
|
169
|
+
"favorite_food": "pizza"
|
170
|
+
},
|
171
|
+
{
|
172
|
+
"character_name": "Morse Mountford",
|
173
|
+
"species": "cat",
|
174
|
+
"catchphrase": "Ameliorated modular data-warehouse",
|
175
|
+
"favorite_food": "carrots"
|
176
|
+
},
|
177
|
+
{
|
178
|
+
"character_name": "Saundra Kauble",
|
179
|
+
"species": "dog",
|
180
|
+
"catchphrase": "Synchronised 24/7 support",
|
181
|
+
"favorite_food": "carrots"
|
182
|
+
},
|
183
|
+
{
|
184
|
+
"character_name": "Kain Viggars",
|
185
|
+
"species": "cat",
|
186
|
+
"catchphrase": "Open-architected asymmetric circuit",
|
187
|
+
"favorite_food": "carrots"
|
188
|
+
}
|
189
|
+
]
|
190
|
+
}
|
191
|
+
}
|
192
|
+
JSON
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'Ingests a JSON file passing keys as Array' do
|
196
|
+
response = bulk_helper.ingest_json(file, { keys: ['data', 'items'] })
|
197
|
+
expect(response).to be_an_instance_of Elasticsearch::API::Response
|
198
|
+
expect(response.status).to eq(200)
|
199
|
+
expect(response['items'].map { |a| a['index']['status'] }.uniq.first).to eq 201
|
200
|
+
end
|
201
|
+
|
202
|
+
it 'Ingests a JSON file passing keys as String' do
|
203
|
+
response = bulk_helper.ingest_json(file, { keys: 'data,items' })
|
204
|
+
expect(response).to be_an_instance_of Elasticsearch::API::Response
|
205
|
+
expect(response.status).to eq(200)
|
206
|
+
expect(response['items'].map { |a| a['index']['status'] }.uniq.first).to eq 201
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,91 @@
|
|
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
|
+
ELASTICSEARCH_URL = ENV['TEST_ES_SERVER'] || "http://localhost:#{(ENV['PORT'] || 9200)}"
|
18
|
+
raise URI::InvalidURIError unless ELASTICSEARCH_URL =~ /\A#{URI::DEFAULT_PARSER.make_regexp}\z/
|
19
|
+
|
20
|
+
require 'spec_helper'
|
21
|
+
require 'elasticsearch/helpers/scroll_helper'
|
22
|
+
|
23
|
+
context 'Elasticsearch client helpers' do
|
24
|
+
let(:client) do
|
25
|
+
Elasticsearch::Client.new(
|
26
|
+
host: ELASTICSEARCH_URL,
|
27
|
+
user: 'elastic',
|
28
|
+
password: 'changeme'
|
29
|
+
)
|
30
|
+
end
|
31
|
+
let(:index) { 'books' }
|
32
|
+
let(:body) { { size: 12, query: { match_all: {} } } }
|
33
|
+
let(:scroll_helper) { Elasticsearch::Helpers::ScrollHelper.new(client, index, body) }
|
34
|
+
|
35
|
+
before do
|
36
|
+
documents = [
|
37
|
+
{ index: { _index: index, data: {name: "Leviathan Wakes", "author": "James S.A. Corey", "release_date": "2011-06-02", "page_count": 561} } },
|
38
|
+
{ index: { _index: index, data: {name: "Hyperion", "author": "Dan Simmons", "release_date": "1989-05-26", "page_count": 482} } },
|
39
|
+
{ index: { _index: index, data: {name: "Dune", "author": "Frank Herbert", "release_date": "1965-06-01", "page_count": 604} } },
|
40
|
+
{ index: { _index: index, data: {name: "Dune Messiah", "author": "Frank Herbert", "release_date": "1969-10-15", "page_count": 331} } },
|
41
|
+
{ index: { _index: index, data: {name: "Children of Dune", "author": "Frank Herbert", "release_date": "1976-04-21", "page_count": 408} } },
|
42
|
+
{ index: { _index: index, data: {name: "God Emperor of Dune", "author": "Frank Herbert", "release_date": "1981-05-28", "page_count": 454} } },
|
43
|
+
{ index: { _index: index, data: {name: "Consider Phlebas", "author": "Iain M. Banks", "release_date": "1987-04-23", "page_count": 471} } },
|
44
|
+
{ index: { _index: index, data: {name: "Pandora's Star", "author": "Peter F. Hamilton", "release_date": "2004-03-02", "page_count": 768} } },
|
45
|
+
{ index: { _index: index, data: {name: "Revelation Space", "author": "Alastair Reynolds", "release_date": "2000-03-15", "page_count": 585} } },
|
46
|
+
{ index: { _index: index, data: {name: "A Fire Upon the Deep", "author": "Vernor Vinge", "release_date": "1992-06-01", "page_count": 613} } },
|
47
|
+
{ index: { _index: index, data: {name: "Ender's Game", "author": "Orson Scott Card", "release_date": "1985-06-01", "page_count": 324} } },
|
48
|
+
{ index: { _index: index, data: {name: "1984", "author": "George Orwell", "release_date": "1985-06-01", "page_count": 328} } },
|
49
|
+
{ index: { _index: index, data: {name: "Fahrenheit 451", "author": "Ray Bradbury", "release_date": "1953-10-15", "page_count": 227} } },
|
50
|
+
{ index: { _index: index, data: {name: "Brave New World", "author": "Aldous Huxley", "release_date": "1932-06-01", "page_count": 268} } },
|
51
|
+
{ index: { _index: index, data: {name: "Foundation", "author": "Isaac Asimov", "release_date": "1951-06-01", "page_count": 224} } },
|
52
|
+
{ index: { _index: index, data: {name: "The Giver", "author": "Lois Lowry", "release_date": "1993-04-26", "page_count": 208} } },
|
53
|
+
{ index: { _index: index, data: {name: "Slaughterhouse-Five", "author": "Kurt Vonnegut", "release_date": "1969-06-01", "page_count": 275} } },
|
54
|
+
{ index: { _index: index, data: {name: "The Hitchhiker's Guide to the Galaxy", "author": "Douglas Adams", "release_date": "1979-10-12", "page_count": 180} } },
|
55
|
+
{ index: { _index: index, data: {name: "Snow Crash", "author": "Neal Stephenson", "release_date": "1992-06-01", "page_count": 470} } },
|
56
|
+
{ index: { _index: index, data: {name: "Neuromancer", "author": "William Gibson", "release_date": "1984-07-01", "page_count": 271} } },
|
57
|
+
{ index: { _index: index, data: {name: "The Handmaid's Tale", "author": "Margaret Atwood", "release_date": "1985-06-01", "page_count": 311} } },
|
58
|
+
{ index: { _index: index, data: {name: "Starship Troopers", "author": "Robert A. Heinlein", "release_date": "1959-12-01", "page_count": 335} } },
|
59
|
+
{ index: { _index: index, data: {name: "The Left Hand of Darkness", "author": "Ursula K. Le Guin", "release_date": "1969-06-01", "page_count": 304} } },
|
60
|
+
{ index: { _index: index, data: {name: "The Moon is a Harsh Mistress", "author": "Robert A. Heinlein", "release_date": "1966-04-01", "page_count": 288 } } }
|
61
|
+
]
|
62
|
+
client.bulk(body: documents, refresh: 'wait_for')
|
63
|
+
end
|
64
|
+
|
65
|
+
after do
|
66
|
+
client.indices.delete(index: index)
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'instantiates a scroll helper' do
|
70
|
+
expect(scroll_helper).to be_an_instance_of Elasticsearch::Helpers::ScrollHelper
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'searches an index' do
|
74
|
+
my_documents = []
|
75
|
+
while !(documents = scroll_helper.results).empty?
|
76
|
+
my_documents << documents
|
77
|
+
end
|
78
|
+
|
79
|
+
expect(my_documents.flatten.size).to eq 24
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'uses enumerable' do
|
83
|
+
count = 0
|
84
|
+
scroll_helper.each { |a| count += 1 }
|
85
|
+
expect(count).to eq 24
|
86
|
+
expect(scroll_helper).to respond_to(:count)
|
87
|
+
expect(scroll_helper).to respond_to(:reject)
|
88
|
+
expect(scroll_helper).to respond_to(:uniq)
|
89
|
+
expect(scroll_helper.map { |a| a['_id'] }.uniq.count).to eq 24
|
90
|
+
end
|
91
|
+
end
|
@@ -20,52 +20,28 @@ require 'webmock/rspec'
|
|
20
20
|
|
21
21
|
describe 'Elasticsearch: Validation' do
|
22
22
|
let(:host) { 'http://localhost:9200' }
|
23
|
-
let(:verify_request_stub) do
|
24
|
-
stub_request(:get, host)
|
25
|
-
.to_return(status: status, body: body, headers: headers)
|
26
|
-
end
|
27
23
|
let(:count_request_stub) do
|
28
24
|
stub_request(:get, "#{host}/_count")
|
29
|
-
.to_return(status:
|
25
|
+
.to_return(status: status, body: nil, headers: headers)
|
30
26
|
end
|
31
27
|
let(:status) { 200 }
|
32
|
-
let(:body) {
|
28
|
+
let(:body) { nil }
|
29
|
+
let(:headers) { {} }
|
33
30
|
let(:client) { Elasticsearch::Client.new }
|
34
|
-
let(:headers) do
|
35
|
-
{ 'content-type' => 'application/json' }
|
36
|
-
end
|
37
|
-
|
38
|
-
def error_requests_and_expectations(message = Elasticsearch::NOT_ELASTICSEARCH_WARNING)
|
39
|
-
expect { client.count }.to raise_error Elasticsearch::UnsupportedProductError, message
|
40
|
-
assert_requested :get, host
|
41
|
-
assert_not_requested :post, "#{host}/_count"
|
42
|
-
expect { client.cluster.health }.to raise_error Elasticsearch::UnsupportedProductError, message
|
43
|
-
expect(client.instance_variable_get('@verified')).to be false
|
44
|
-
expect { client.cluster.health }.to raise_error Elasticsearch::UnsupportedProductError, message
|
45
|
-
end
|
46
|
-
|
47
|
-
def valid_requests_and_expectations
|
48
|
-
expect(client.instance_variable_get('@verified')).to be false
|
49
|
-
assert_not_requested :get, host
|
50
|
-
|
51
|
-
client.count
|
52
|
-
expect(client.instance_variable_get('@verified'))
|
53
|
-
assert_requested :get, host
|
54
|
-
assert_requested :get, "#{host}/_count"
|
55
|
-
end
|
56
31
|
|
57
32
|
context 'When Elasticsearch replies with status 401' do
|
58
33
|
let(:status) { 401 }
|
59
|
-
let(:body) { {}.to_json }
|
60
34
|
|
61
35
|
it 'Verifies the request but shows a warning' do
|
62
36
|
stderr = $stderr
|
63
37
|
fake_stderr = StringIO.new
|
64
38
|
$stderr = fake_stderr
|
65
|
-
|
66
|
-
verify_request_stub
|
39
|
+
expect(client.instance_variable_get('@verified')).to be false
|
67
40
|
count_request_stub
|
68
|
-
|
41
|
+
expect do
|
42
|
+
client.count
|
43
|
+
end.to raise_error Elastic::Transport::Transport::Errors::Unauthorized
|
44
|
+
expect(client.instance_variable_get('@verified')).to be true
|
69
45
|
|
70
46
|
fake_stderr.rewind
|
71
47
|
expect(fake_stderr.string).to eq("#{Elasticsearch::SECURITY_PRIVILEGES_VALIDATION_WARNING}\n")
|
@@ -76,16 +52,18 @@ describe 'Elasticsearch: Validation' do
|
|
76
52
|
|
77
53
|
context 'When Elasticsearch replies with status 403' do
|
78
54
|
let(:status) { 403 }
|
79
|
-
let(:body) { {}.to_json }
|
80
55
|
|
81
56
|
it 'Verifies the request but shows a warning' do
|
82
57
|
stderr = $stderr
|
83
58
|
fake_stderr = StringIO.new
|
84
59
|
$stderr = fake_stderr
|
85
60
|
|
86
|
-
|
61
|
+
expect(client.instance_variable_get('@verified')).to be false
|
87
62
|
count_request_stub
|
88
|
-
|
63
|
+
expect do
|
64
|
+
client.count
|
65
|
+
end.to raise_error Elastic::Transport::Transport::Errors::Forbidden
|
66
|
+
expect(client.instance_variable_get('@verified')).to be true
|
89
67
|
|
90
68
|
fake_stderr.rewind
|
91
69
|
expect(fake_stderr.string).to eq("#{Elasticsearch::SECURITY_PRIVILEGES_VALIDATION_WARNING}\n")
|
@@ -96,7 +74,6 @@ describe 'Elasticsearch: Validation' do
|
|
96
74
|
|
97
75
|
context 'When Elasticsearch replies with status 413' do
|
98
76
|
let(:status) { 413 }
|
99
|
-
let(:body) { {}.to_json }
|
100
77
|
|
101
78
|
it 'Verifies the request and shows a warning' do
|
102
79
|
stderr = $stderr
|
@@ -104,9 +81,10 @@ describe 'Elasticsearch: Validation' do
|
|
104
81
|
$stderr = fake_stderr
|
105
82
|
|
106
83
|
expect(client.instance_variable_get('@verified')).to be false
|
107
|
-
|
108
|
-
|
109
|
-
|
84
|
+
count_request_stub
|
85
|
+
expect do
|
86
|
+
client.count
|
87
|
+
end.to raise_error Elastic::Transport::Transport::Errors::RequestEntityTooLarge
|
110
88
|
expect(client.instance_variable_get('@verified')).to be true
|
111
89
|
|
112
90
|
fake_stderr.rewind
|
@@ -127,10 +105,10 @@ describe 'Elasticsearch: Validation' do
|
|
127
105
|
$stderr = fake_stderr
|
128
106
|
|
129
107
|
expect(client.instance_variable_get('@verified')).to be false
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
108
|
+
count_request_stub
|
109
|
+
expect do
|
110
|
+
client.count
|
111
|
+
end.to raise_error Elastic::Transport::Transport::Errors::ServiceUnavailable
|
134
112
|
expect(client.instance_variable_get('@verified')).to be false
|
135
113
|
|
136
114
|
fake_stderr.rewind
|
@@ -147,156 +125,24 @@ describe 'Elasticsearch: Validation' do
|
|
147
125
|
end
|
148
126
|
end
|
149
127
|
|
128
|
+
context 'When the header is present' do
|
129
|
+
let(:headers) { { 'X-Elastic-Product' => 'Elasticsearch' } }
|
150
130
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
'X-Elastic-Product' => 'Elasticsearch',
|
157
|
-
'content-type' => 'json'
|
158
|
-
}
|
159
|
-
end
|
160
|
-
|
161
|
-
it 'Makes requests and passes validation' do
|
162
|
-
verify_request_stub
|
163
|
-
count_request_stub
|
164
|
-
|
165
|
-
valid_requests_and_expectations
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
context 'When the header is not present' do
|
170
|
-
it 'Fails validation' do
|
171
|
-
verify_request_stub
|
172
|
-
|
173
|
-
expect(client.instance_variable_get('@verified')).to be false
|
174
|
-
assert_not_requested :get, host
|
175
|
-
|
176
|
-
error_requests_and_expectations
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
context 'When the Elasticsearch version is >= 8.1.0' do
|
182
|
-
context 'With a valid Elasticsearch response' do
|
183
|
-
let(:body) { { 'version' => { 'number' => '8.1.0' } }.to_json }
|
184
|
-
let(:headers) do
|
185
|
-
{
|
186
|
-
'X-Elastic-Product' => 'Elasticsearch',
|
187
|
-
'content-type' => 'json'
|
188
|
-
}
|
189
|
-
end
|
190
|
-
|
191
|
-
it 'Makes requests and passes validation' do
|
192
|
-
verify_request_stub
|
193
|
-
count_request_stub
|
194
|
-
|
195
|
-
valid_requests_and_expectations
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
context 'When the header is not present' do
|
200
|
-
it 'Fails validation' do
|
201
|
-
verify_request_stub
|
202
|
-
|
203
|
-
expect(client.instance_variable_get('@verified')).to be false
|
204
|
-
assert_not_requested :get, host
|
205
|
-
|
206
|
-
error_requests_and_expectations
|
207
|
-
end
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
|
212
|
-
context 'When the Elasticsearch version is 8.0.0.pre' do
|
213
|
-
context 'With a valid Elasticsearch response' do
|
214
|
-
let(:body) { { 'version' => { 'number' => '8.0.0.pre' } }.to_json }
|
215
|
-
let(:headers) do
|
216
|
-
{
|
217
|
-
'X-Elastic-Product' => 'Elasticsearch',
|
218
|
-
'content-type' => 'json'
|
219
|
-
}
|
220
|
-
end
|
221
|
-
|
222
|
-
it 'Makes requests and passes validation' do
|
223
|
-
verify_request_stub
|
224
|
-
count_request_stub
|
225
|
-
|
226
|
-
valid_requests_and_expectations
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
context 'When the header is not present' do
|
231
|
-
it 'Fails validation' do
|
232
|
-
verify_request_stub
|
233
|
-
|
234
|
-
expect(client.instance_variable_get('@verified')).to be false
|
235
|
-
assert_not_requested :get, host
|
236
|
-
|
237
|
-
error_requests_and_expectations
|
238
|
-
end
|
239
|
-
end
|
240
|
-
end
|
241
|
-
|
242
|
-
context 'When the version is 8.0.0-SNAPSHOT' do
|
243
|
-
let(:body) { { 'version' => { 'number' => '8.0.0-SNAPSHOT' } }.to_json }
|
244
|
-
|
245
|
-
context 'When the header is not present' do
|
246
|
-
it 'Fails validation' do
|
247
|
-
verify_request_stub
|
248
|
-
count_request_stub
|
249
|
-
|
250
|
-
error_requests_and_expectations
|
251
|
-
end
|
252
|
-
end
|
253
|
-
|
254
|
-
context 'With a valid Elasticsearch response' do
|
255
|
-
let(:headers) do
|
256
|
-
{
|
257
|
-
'X-Elastic-Product' => 'Elasticsearch',
|
258
|
-
'content-type' => 'json'
|
259
|
-
}
|
260
|
-
end
|
261
|
-
|
262
|
-
it 'Makes requests and passes validation' do
|
263
|
-
verify_request_stub
|
264
|
-
count_request_stub
|
265
|
-
|
266
|
-
valid_requests_and_expectations
|
267
|
-
end
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
context 'When Elasticsearch version is < 8.0.0' do
|
272
|
-
let(:body) { { 'version' => { 'number' => '7.16.0' } }.to_json }
|
273
|
-
|
274
|
-
it 'Raises an exception and client doesnae work' do
|
275
|
-
verify_request_stub
|
276
|
-
error_requests_and_expectations
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
|
-
context 'When there is no version data' do
|
281
|
-
let(:body) { {}.to_json }
|
282
|
-
it 'Raises an exception and client doesnae work' do
|
283
|
-
verify_request_stub
|
284
|
-
error_requests_and_expectations
|
131
|
+
it 'Makes requests and passes validation' do
|
132
|
+
expect(client.instance_variable_get('@verified')).to be false
|
133
|
+
count_request_stub
|
134
|
+
client.count
|
135
|
+
expect(client.instance_variable_get('@verified')).to be true
|
285
136
|
end
|
286
137
|
end
|
287
138
|
|
288
|
-
context 'When
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
it 'validates' do
|
297
|
-
verify_request_stub
|
298
|
-
count_request_stub
|
299
|
-
valid_requests_and_expectations
|
139
|
+
context 'When the header is not present' do
|
140
|
+
it 'Fails validation' do
|
141
|
+
expect(client.instance_variable_get('@verified')).to be false
|
142
|
+
stub_request(:get, "#{host}/_cluster/health")
|
143
|
+
.to_return(status: status, body: nil, headers: {})
|
144
|
+
expect { client.cluster.health }.to raise_error Elasticsearch::UnsupportedProductError, Elasticsearch::NOT_ELASTICSEARCH_WARNING
|
145
|
+
expect(client.instance_variable_get('@verified')).to be false
|
300
146
|
end
|
301
147
|
end
|
302
148
|
end
|
@@ -0,0 +1,69 @@
|
|
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
|
+
require 'spec_helper'
|
18
|
+
|
19
|
+
describe Elasticsearch::Client do
|
20
|
+
let(:user_agent) {
|
21
|
+
"elasticsearch-ruby/#{Elasticsearch::VERSION}; elastic-transport-ruby/#{Elastic::Transport::VERSION}; RUBY_VERSION: #{RUBY_VERSION}; #{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
|
22
|
+
}
|
23
|
+
|
24
|
+
context 'when no user-agent is set on initialization' do
|
25
|
+
let(:client) { described_class.new }
|
26
|
+
|
27
|
+
it 'has the expected header' do
|
28
|
+
expect(client.transport.options[:transport_options][:headers][:user_agent]).to eq user_agent
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when a header is specified on initialization' do
|
33
|
+
let(:client) do
|
34
|
+
described_class.new(
|
35
|
+
transport_options: { headers: { 'X-Test-Header' => 'Test' } }
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'has the expected header' do
|
40
|
+
expect(client.transport.options[:transport_options][:headers][:user_agent]).to eq user_agent
|
41
|
+
expect(client.transport.options[:transport_options][:headers]['X-Test-Header']).to eq 'Test'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'when other transport_options are specified on initialization' do
|
46
|
+
let(:client) do
|
47
|
+
described_class.new(
|
48
|
+
transport_options: { params: { format: 'yaml' } }
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'has the expected header' do
|
53
|
+
expect(client.transport.options[:transport_options][:headers][:user_agent]).to eq user_agent
|
54
|
+
expect(client.transport.options[:transport_options][:params][:format]).to eq 'yaml'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'when :user_agent is specified on initialization' do
|
59
|
+
let(:client) do
|
60
|
+
described_class.new(
|
61
|
+
transport_options: { headers: { user_agent: 'TestApp' } }
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'has the expected header' do
|
66
|
+
expect(client.transport.options[:transport_options][:headers][:user_agent]).to eq 'TestApp'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: elasticsearch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.
|
4
|
+
version: 8.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karel Minarik
|
8
|
+
- Emily Stolfo
|
9
|
+
- Fernando Briano
|
8
10
|
autorequire:
|
9
11
|
bindir: bin
|
10
12
|
cert_chain: []
|
11
|
-
date: 2023-
|
13
|
+
date: 2023-09-12 00:00:00.000000000 Z
|
12
14
|
dependencies:
|
13
15
|
- !ruby/object:Gem::Dependency
|
14
16
|
name: elastic-transport
|
@@ -30,14 +32,14 @@ dependencies:
|
|
30
32
|
requirements:
|
31
33
|
- - '='
|
32
34
|
- !ruby/object:Gem::Version
|
33
|
-
version: 8.
|
35
|
+
version: 8.10.0
|
34
36
|
type: :runtime
|
35
37
|
prerelease: false
|
36
38
|
version_requirements: !ruby/object:Gem::Requirement
|
37
39
|
requirements:
|
38
40
|
- - '='
|
39
41
|
- !ruby/object:Gem::Version
|
40
|
-
version: 8.
|
42
|
+
version: 8.10.0
|
41
43
|
- !ruby/object:Gem::Dependency
|
42
44
|
name: bundler
|
43
45
|
requirement: !ruby/object:Gem::Requirement
|
@@ -182,7 +184,7 @@ description: 'Ruby integrations for Elasticsearch (client, API, etc.)
|
|
182
184
|
|
183
185
|
'
|
184
186
|
email:
|
185
|
-
-
|
187
|
+
- clients-team@elastic.co
|
186
188
|
executables:
|
187
189
|
- elastic_ruby_console
|
188
190
|
extensions: []
|
@@ -199,9 +201,13 @@ files:
|
|
199
201
|
- elasticsearch.gemspec
|
200
202
|
- lib/elasticsearch-ruby.rb
|
201
203
|
- lib/elasticsearch.rb
|
204
|
+
- lib/elasticsearch/helpers/bulk_helper.rb
|
205
|
+
- lib/elasticsearch/helpers/scroll_helper.rb
|
202
206
|
- lib/elasticsearch/version.rb
|
203
207
|
- spec/integration/characters_escaping_spec.rb
|
204
208
|
- spec/integration/client_integration_spec.rb
|
209
|
+
- spec/integration/helpers/bulk_helper_spec.rb
|
210
|
+
- spec/integration/helpers/scroll_helper_spec.rb
|
205
211
|
- spec/spec_helper.rb
|
206
212
|
- spec/unit/api_key_spec.rb
|
207
213
|
- spec/unit/cloud_credentials_spec.rb
|
@@ -209,6 +215,7 @@ files:
|
|
209
215
|
- spec/unit/elasticsearch_product_validation_spec.rb
|
210
216
|
- spec/unit/headers_spec.rb
|
211
217
|
- spec/unit/opaque_id_spec.rb
|
218
|
+
- spec/unit/user_agent_spec.rb
|
212
219
|
- spec/unit/wrapper_gem_spec.rb
|
213
220
|
homepage: https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/index.html
|
214
221
|
licenses:
|
@@ -234,13 +241,15 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
234
241
|
- !ruby/object:Gem::Version
|
235
242
|
version: '0'
|
236
243
|
requirements: []
|
237
|
-
rubygems_version: 3.4.
|
244
|
+
rubygems_version: 3.4.19
|
238
245
|
signing_key:
|
239
246
|
specification_version: 4
|
240
247
|
summary: Ruby integrations for Elasticsearch
|
241
248
|
test_files:
|
242
249
|
- spec/integration/characters_escaping_spec.rb
|
243
250
|
- spec/integration/client_integration_spec.rb
|
251
|
+
- spec/integration/helpers/bulk_helper_spec.rb
|
252
|
+
- spec/integration/helpers/scroll_helper_spec.rb
|
244
253
|
- spec/spec_helper.rb
|
245
254
|
- spec/unit/api_key_spec.rb
|
246
255
|
- spec/unit/cloud_credentials_spec.rb
|
@@ -248,4 +257,5 @@ test_files:
|
|
248
257
|
- spec/unit/elasticsearch_product_validation_spec.rb
|
249
258
|
- spec/unit/headers_spec.rb
|
250
259
|
- spec/unit/opaque_id_spec.rb
|
260
|
+
- spec/unit/user_agent_spec.rb
|
251
261
|
- spec/unit/wrapper_gem_spec.rb
|