elasticsearch 8.8.0 → 8.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c72b5eb32153d9598565613a720c140bef43a1794cebcd5f9f75f977a96700ef
4
- data.tar.gz: 07b1eb4c84634993423f26519d87115c84e70f8a67f952469149209169a4701a
3
+ metadata.gz: 2e2183bdba09aed6383309a842c989713effa04d48af7385b43bcb80e2c01b1b
4
+ data.tar.gz: f5b9971c94be976c32a4bb7f298832659b6da4db9f11aaf5d5f1deb5878110f6
5
5
  SHA512:
6
- metadata.gz: 3e0ff385f6834e5a5c8f07db48caaf65e9eb08797f307dc8b9e6869b2202006bbdcb6cd64c9d9d3d4dd4c735340e18aaa019a44f2511ae8b34d07dba74106f41
7
- data.tar.gz: 7b2bed8e82f2c1e421951910143b7455567d6c78dc74775c8502233422c2d20bd6c8249a03d543f82be379dfaab498085f6ae7fab27745788456f68eb0940158
6
+ metadata.gz: da2903828837d1286fa486f8f2e8458b4cf82b44a885f1742976527159f22291aa9d6a9ccbd1d7e6011222f68ec11fe26392f25d3357cc08b4c967f6c83d0d6f
7
+ data.tar.gz: c95be1ee39a3d182e085a50d34de1d0650ef170333e1405c1161e28c0153f5a1e3f9349cedecd62e3f1598776e510436477c7583f879ed11af77f9d76a3dc5ca
@@ -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
 
@@ -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.8.0'
49
+ s.add_dependency 'elasticsearch-api', '8.9.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
@@ -16,5 +16,5 @@
16
16
  # under the License.
17
17
 
18
18
  module Elasticsearch
19
- VERSION = '8.8.0'.freeze
19
+ VERSION = '8.9.0'.freeze
20
20
  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 api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
40
- # joined by a colon as a String, or a hash with the `id` and `api_key` values.
41
- # @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client.
42
- # This will be prepended to the id you set before each request
43
- # if you're using X-Opaque-Id
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
- arguments[:hosts] = setup_cloud_host(
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
- verify_elasticsearch unless @verified
71
- @transport.perform_request(*args, &block)
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 = elasticsearch_validation_request
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
- return
92
- rescue Elastic::Transport::Transport::Error
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
- return
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: 200, body: nil, headers: {})
25
+ .to_return(status: status, body: nil, headers: headers)
30
26
  end
31
27
  let(:status) { 200 }
32
- let(:body) { {}.to_json }
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
- valid_requests_and_expectations
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
- verify_request_stub
61
+ expect(client.instance_variable_get('@verified')).to be false
87
62
  count_request_stub
88
- valid_requests_and_expectations
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
- assert_not_requested :get, host
108
- verify_request_stub
109
- expect { client.info }.to raise_error Elastic::Transport::Transport::Errors::RequestEntityTooLarge
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
- assert_not_requested :get, host
131
- verify_request_stub
132
- expect { client.info }.to raise_error Elastic::Transport::Transport::Errors::ServiceUnavailable
133
- assert_not_requested :post, "#{host}/_count"
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
- context 'When the Elasticsearch version is >= 8.0.0' do
152
- context 'With a valid Elasticsearch response' do
153
- let(:body) { { 'version' => { 'number' => '8.0.0' } }.to_json }
154
- let(:headers) do
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 doing a yaml content-type request' do
289
- let(:client) do
290
- Elasticsearch::Client.new(transport_options: {headers: { accept: 'application/yaml', content_type: 'application/yaml' }})
291
- end
292
-
293
- let(:headers) { { 'content-type' => 'application/yaml', 'X-Elastic-Product' => 'Elasticsearch' } }
294
- let(:body) { "---\nversion:\n number: \"8.0.0-SNAPSHOT\"\n" }
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,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elasticsearch
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.8.0
4
+ version: 8.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karel Minarik
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-25 00:00:00.000000000 Z
11
+ date: 2023-07-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: elastic-transport
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 8.8.0
33
+ version: 8.9.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 8.8.0
40
+ version: 8.9.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -199,9 +199,13 @@ files:
199
199
  - elasticsearch.gemspec
200
200
  - lib/elasticsearch-ruby.rb
201
201
  - lib/elasticsearch.rb
202
+ - lib/elasticsearch/helpers/bulk_helper.rb
203
+ - lib/elasticsearch/helpers/scroll_helper.rb
202
204
  - lib/elasticsearch/version.rb
203
205
  - spec/integration/characters_escaping_spec.rb
204
206
  - spec/integration/client_integration_spec.rb
207
+ - spec/integration/helpers/bulk_helper_spec.rb
208
+ - spec/integration/helpers/scroll_helper_spec.rb
205
209
  - spec/spec_helper.rb
206
210
  - spec/unit/api_key_spec.rb
207
211
  - spec/unit/cloud_credentials_spec.rb
@@ -209,6 +213,7 @@ files:
209
213
  - spec/unit/elasticsearch_product_validation_spec.rb
210
214
  - spec/unit/headers_spec.rb
211
215
  - spec/unit/opaque_id_spec.rb
216
+ - spec/unit/user_agent_spec.rb
212
217
  - spec/unit/wrapper_gem_spec.rb
213
218
  homepage: https://www.elastic.co/guide/en/elasticsearch/client/ruby-api/current/index.html
214
219
  licenses:
@@ -241,6 +246,8 @@ summary: Ruby integrations for Elasticsearch
241
246
  test_files:
242
247
  - spec/integration/characters_escaping_spec.rb
243
248
  - spec/integration/client_integration_spec.rb
249
+ - spec/integration/helpers/bulk_helper_spec.rb
250
+ - spec/integration/helpers/scroll_helper_spec.rb
244
251
  - spec/spec_helper.rb
245
252
  - spec/unit/api_key_spec.rb
246
253
  - spec/unit/cloud_credentials_spec.rb
@@ -248,4 +255,5 @@ test_files:
248
255
  - spec/unit/elasticsearch_product_validation_spec.rb
249
256
  - spec/unit/headers_spec.rb
250
257
  - spec/unit/opaque_id_spec.rb
258
+ - spec/unit/user_agent_spec.rb
251
259
  - spec/unit/wrapper_gem_spec.rb