elasticsearch 8.8.0 → 8.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|