opensearch-transport 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +3 -0
  3. data/.gitignore +17 -0
  4. data/Gemfile +47 -0
  5. data/LICENSE +202 -0
  6. data/README.md +551 -0
  7. data/Rakefile +89 -0
  8. data/lib/opensearch/transport/client.rb +354 -0
  9. data/lib/opensearch/transport/redacted.rb +84 -0
  10. data/lib/opensearch/transport/transport/base.rb +450 -0
  11. data/lib/opensearch/transport/transport/connections/collection.rb +136 -0
  12. data/lib/opensearch/transport/transport/connections/connection.rb +169 -0
  13. data/lib/opensearch/transport/transport/connections/selector.rb +101 -0
  14. data/lib/opensearch/transport/transport/errors.rb +100 -0
  15. data/lib/opensearch/transport/transport/http/curb.rb +140 -0
  16. data/lib/opensearch/transport/transport/http/faraday.rb +101 -0
  17. data/lib/opensearch/transport/transport/http/manticore.rb +188 -0
  18. data/lib/opensearch/transport/transport/loggable.rb +94 -0
  19. data/lib/opensearch/transport/transport/response.rb +46 -0
  20. data/lib/opensearch/transport/transport/serializer/multi_json.rb +62 -0
  21. data/lib/opensearch/transport/transport/sniffer.rb +111 -0
  22. data/lib/opensearch/transport/version.rb +31 -0
  23. data/lib/opensearch/transport.rb +46 -0
  24. data/lib/opensearch-transport.rb +27 -0
  25. data/opensearch-transport.gemspec +92 -0
  26. data/spec/opensearch/connections/collection_spec.rb +275 -0
  27. data/spec/opensearch/connections/selector_spec.rb +183 -0
  28. data/spec/opensearch/transport/base_spec.rb +313 -0
  29. data/spec/opensearch/transport/client_spec.rb +1818 -0
  30. data/spec/opensearch/transport/sniffer_spec.rb +284 -0
  31. data/spec/spec_helper.rb +99 -0
  32. data/test/integration/transport_test.rb +108 -0
  33. data/test/profile/client_benchmark_test.rb +141 -0
  34. data/test/test_helper.rb +97 -0
  35. data/test/unit/connection_test.rb +145 -0
  36. data/test/unit/response_test.rb +41 -0
  37. data/test/unit/serializer_test.rb +42 -0
  38. data/test/unit/transport_base_test.rb +673 -0
  39. data/test/unit/transport_curb_test.rb +143 -0
  40. data/test/unit/transport_faraday_test.rb +237 -0
  41. data/test/unit/transport_manticore_test.rb +191 -0
  42. data.tar.gz.sig +1 -0
  43. metadata +456 -0
  44. metadata.gz.sig +1 -0
@@ -0,0 +1,169 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Elasticsearch B.V. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Elasticsearch B.V. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ module OpenSearch
28
+ module Transport
29
+ module Transport
30
+ module Connections
31
+ # Wraps the connection information and logic.
32
+ #
33
+ # The Connection instance wraps the host information (hostname, port, attributes, etc),
34
+ # as well as the "session" (a transport client object, such as a {HTTP::Faraday} instance).
35
+ #
36
+ # It provides methods to construct and properly encode the URLs and paths for passing them
37
+ # to the transport client object.
38
+ #
39
+ # It provides methods to handle connection livecycle (dead, alive, healthy).
40
+ #
41
+ class Connection
42
+ DEFAULT_RESURRECT_TIMEOUT = 60
43
+
44
+ attr_reader :host, :connection, :options, :failures, :dead_since
45
+
46
+ # @option arguments [Hash] :host Host information (example: `{host: 'localhost', port: 9200}`)
47
+ # @option arguments [Object] :connection The transport-specific physical connection or "session"
48
+ # @option arguments [Hash] :options Options (usually passed in from transport)
49
+ #
50
+ def initialize(arguments={})
51
+ @host = arguments[:host].is_a?(Hash) ? Redacted.new(arguments[:host]) : arguments[:host]
52
+ @connection = arguments[:connection]
53
+ @options = arguments[:options] || {}
54
+ @state_mutex = Mutex.new
55
+
56
+ @options[:resurrect_timeout] ||= DEFAULT_RESURRECT_TIMEOUT
57
+ @dead = false
58
+ @failures = 0
59
+ end
60
+
61
+ # Returns the complete endpoint URL with host, port, path and serialized parameters.
62
+ #
63
+ # @return [String]
64
+ #
65
+ def full_url(path, params = {})
66
+ url = "#{host[:protocol]}://"
67
+ url += "#{CGI.escape(host[:user])}:#{CGI.escape(host[:password])}@" if host[:user]
68
+ url += "#{host[:host]}:#{host[:port]}"
69
+ url += "#{host[:path]}" if host[:path]
70
+ full_path = full_path(path, params)
71
+ url += '/' unless full_path.match?(/^\//)
72
+ url += full_path
73
+ end
74
+
75
+ # Returns the complete endpoint path with serialized parameters.
76
+ #
77
+ # @return [String]
78
+ #
79
+ def full_path(path, params={})
80
+ path + (params.empty? ? '' : "?#{::Faraday::Utils::ParamsHash[params].to_query}")
81
+ end
82
+
83
+ # Returns true when this connection has been marked dead, false otherwise.
84
+ #
85
+ # @return [Boolean]
86
+ #
87
+ def dead?
88
+ @dead || false
89
+ end
90
+
91
+ # Marks this connection as dead, incrementing the `failures` counter and
92
+ # storing the current time as `dead_since`.
93
+ #
94
+ # @return [self]
95
+ #
96
+ def dead!
97
+ @state_mutex.synchronize do
98
+ @dead = true
99
+ @failures += 1
100
+ @dead_since = Time.now
101
+ end
102
+ self
103
+ end
104
+
105
+ # Marks this connection as alive, ie. it is eligible to be returned from the pool by the selector.
106
+ #
107
+ # @return [self]
108
+ #
109
+ def alive!
110
+ @state_mutex.synchronize do
111
+ @dead = false
112
+ end
113
+ self
114
+ end
115
+
116
+ # Marks this connection as healthy, ie. a request has been successfully performed with it.
117
+ #
118
+ # @return [self]
119
+ #
120
+ def healthy!
121
+ @state_mutex.synchronize do
122
+ @dead = false
123
+ @failures = 0
124
+ end
125
+ self
126
+ end
127
+
128
+ # Marks this connection as alive, if the required timeout has passed.
129
+ #
130
+ # @return [self,nil]
131
+ # @see DEFAULT_RESURRECT_TIMEOUT
132
+ # @see #resurrectable?
133
+ #
134
+ def resurrect!
135
+ alive! if resurrectable?
136
+ end
137
+
138
+ # Returns true if the connection is eligible to be resurrected as alive, false otherwise.
139
+ #
140
+ # @return [Boolean]
141
+ #
142
+ def resurrectable?
143
+ @state_mutex.synchronize {
144
+ Time.now > @dead_since + ( @options[:resurrect_timeout] * 2 ** (@failures-1) )
145
+ }
146
+ end
147
+
148
+ # Equality operator based on connection protocol, host, port and attributes
149
+ #
150
+ # @return [Boolean]
151
+ #
152
+ def ==(other)
153
+ self.host[:protocol] == other.host[:protocol] && \
154
+ self.host[:host] == other.host[:host] && \
155
+ self.host[:port].to_i == other.host[:port].to_i && \
156
+ self.host[:attributes] == other.host[:attributes]
157
+ end
158
+
159
+ # @return [String]
160
+ #
161
+ def to_s
162
+ "<#{self.class.name} host: #{host} (#{dead? ? 'dead since ' + dead_since.to_s : 'alive'})>"
163
+ end
164
+ end
165
+
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,101 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Elasticsearch B.V. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Elasticsearch B.V. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ module OpenSearch
28
+ module Transport
29
+ module Transport
30
+ module Connections
31
+ module Selector
32
+
33
+ # @abstract Common functionality for connection selector implementations.
34
+ #
35
+ module Base
36
+ attr_reader :connections
37
+
38
+ # @option arguments [Connections::Collection] :connections Collection with connections.
39
+ #
40
+ def initialize(arguments={})
41
+ @connections = arguments[:connections]
42
+ end
43
+
44
+ # @abstract Selector strategies implement this method to
45
+ # select and return a connection from the pool.
46
+ #
47
+ # @return [Connection]
48
+ #
49
+ def select(options={})
50
+ raise NoMethodError, "Implement this method in the selector implementation."
51
+ end
52
+ end
53
+
54
+ # "Random connection" selector strategy.
55
+ #
56
+ class Random
57
+ include Base
58
+
59
+ # Returns a random connection from the collection.
60
+ #
61
+ # @return [Connections::Connection]
62
+ #
63
+ def select(options={})
64
+ connections.to_a.send( defined?(RUBY_VERSION) && RUBY_VERSION > '1.9' ? :sample : :choice)
65
+ end
66
+ end
67
+
68
+ # "Round-robin" selector strategy (default).
69
+ #
70
+ class RoundRobin
71
+ include Base
72
+
73
+ # @option arguments [Connections::Collection] :connections Collection with connections.
74
+ #
75
+ def initialize(arguments = {})
76
+ super
77
+ @mutex = Mutex.new
78
+ @current = nil
79
+ end
80
+
81
+ # Returns the next connection from the collection, rotating them in round-robin fashion.
82
+ #
83
+ # @return [Connections::Connection]
84
+ #
85
+ def select(options={})
86
+ @mutex.synchronize do
87
+ conns = connections
88
+ if @current && (@current < conns.size-1)
89
+ @current += 1
90
+ else
91
+ @current = 0
92
+ end
93
+ conns[@current]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,100 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Elasticsearch B.V. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Elasticsearch B.V. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ module OpenSearch
28
+ module Transport
29
+ module Transport
30
+
31
+ # Generic client error
32
+ #
33
+ class Error < StandardError; end
34
+
35
+ # Reloading connections timeout (1 sec by default)
36
+ #
37
+ class SnifferTimeoutError < Timeout::Error; end
38
+
39
+ # Elasticsearch server error (HTTP status 5xx)
40
+ #
41
+ class ServerError < Error; end
42
+
43
+ module Errors; end
44
+
45
+ HTTP_STATUSES = {
46
+ 300 => 'MultipleChoices',
47
+ 301 => 'MovedPermanently',
48
+ 302 => 'Found',
49
+ 303 => 'SeeOther',
50
+ 304 => 'NotModified',
51
+ 305 => 'UseProxy',
52
+ 307 => 'TemporaryRedirect',
53
+ 308 => 'PermanentRedirect',
54
+
55
+ 400 => 'BadRequest',
56
+ 401 => 'Unauthorized',
57
+ 402 => 'PaymentRequired',
58
+ 403 => 'Forbidden',
59
+ 404 => 'NotFound',
60
+ 405 => 'MethodNotAllowed',
61
+ 406 => 'NotAcceptable',
62
+ 407 => 'ProxyAuthenticationRequired',
63
+ 408 => 'RequestTimeout',
64
+ 409 => 'Conflict',
65
+ 410 => 'Gone',
66
+ 411 => 'LengthRequired',
67
+ 412 => 'PreconditionFailed',
68
+ 413 => 'RequestEntityTooLarge',
69
+ 414 => 'RequestURITooLong',
70
+ 415 => 'UnsupportedMediaType',
71
+ 416 => 'RequestedRangeNotSatisfiable',
72
+ 417 => 'ExpectationFailed',
73
+ 418 => 'ImATeapot',
74
+ 421 => 'TooManyConnectionsFromThisIP',
75
+ 426 => 'UpgradeRequired',
76
+ 429 => 'TooManyRequests',
77
+ 450 => 'BlockedByWindowsParentalControls',
78
+ 494 => 'RequestHeaderTooLarge',
79
+ 497 => 'HTTPToHTTPS',
80
+ 499 => 'ClientClosedRequest',
81
+
82
+ 500 => 'InternalServerError',
83
+ 501 => 'NotImplemented',
84
+ 502 => 'BadGateway',
85
+ 503 => 'ServiceUnavailable',
86
+ 504 => 'GatewayTimeout',
87
+ 505 => 'HTTPVersionNotSupported',
88
+ 506 => 'VariantAlsoNegotiates',
89
+ 510 => 'NotExtended'
90
+ }
91
+
92
+ ERRORS = HTTP_STATUSES.inject({}) do |sum, error|
93
+ status, name = error
94
+ sum[status] = Errors.const_set name, Class.new(ServerError)
95
+ sum
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,140 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Elasticsearch B.V. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Elasticsearch B.V. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ module OpenSearch
28
+ module Transport
29
+ module Transport
30
+ module HTTP
31
+
32
+ # Alternative HTTP transport implementation, using the [_Curb_](https://rubygems.org/gems/curb) client.
33
+ #
34
+ # @see Transport::Base
35
+ #
36
+ class Curb
37
+ include Base
38
+
39
+ # Performs the request by invoking {Transport::Base#perform_request} with a block.
40
+ #
41
+ # @return [Response]
42
+ # @see Transport::Base#perform_request
43
+ #
44
+ def perform_request(method, path, params={}, body=nil, headers=nil, opts={})
45
+ super do |connection, url|
46
+ connection.connection.url = connection.full_url(path, params)
47
+
48
+ case method
49
+ when 'HEAD'
50
+ connection.connection.set :nobody, true
51
+ when 'GET', 'POST', 'PUT', 'DELETE'
52
+ connection.connection.set :nobody, false
53
+ connection.connection.put_data = __convert_to_json(body) if body
54
+ if headers
55
+ if connection.connection.headers
56
+ connection.connection.headers.merge!(headers)
57
+ else
58
+ connection.connection.headers = headers
59
+ end
60
+ end
61
+ else
62
+ raise ArgumentError, "Unsupported HTTP method: #{method}"
63
+ end
64
+
65
+ connection.connection.http(method.to_sym)
66
+
67
+ Response.new(
68
+ connection.connection.response_code,
69
+ decompress_response(connection.connection.body_str),
70
+ headers(connection)
71
+ )
72
+ end
73
+ end
74
+
75
+ def headers(connection)
76
+ headers_string = connection.connection.header_str
77
+ return nil if headers_string.nil?
78
+
79
+ response_headers = headers_string&.split(/\\r\\n|\r\n/).reject(&:empty?)
80
+ response_headers.shift # Removes HTTP status string
81
+ processed_header = response_headers.flat_map { |s| s.scan(/^(\S+): (.+)/) }
82
+ headers_hash = Hash[processed_header].transform_keys(&:downcase)
83
+ if headers_hash['content-type']&.match?(/application\/json/)
84
+ headers_hash['content-type'] = 'application/json'
85
+ end
86
+ headers_hash
87
+ end
88
+
89
+ # Builds and returns a connection
90
+ #
91
+ # @return [Connections::Connection]
92
+ #
93
+ def __build_connection(host, options={}, block=nil)
94
+ client = ::Curl::Easy.new
95
+ apply_headers(client, options)
96
+ client.url = __full_url(host)
97
+
98
+ if host[:user]
99
+ client.http_auth_types = host[:auth_type] || :basic
100
+ client.username = host[:user]
101
+ client.password = host[:password]
102
+ end
103
+
104
+ client.instance_eval(&block) if block
105
+
106
+ Connections::Connection.new :host => host, :connection => client
107
+ end
108
+
109
+ # Returns an array of implementation specific connection errors.
110
+ #
111
+ # @return [Array]
112
+ #
113
+ def host_unreachable_exceptions
114
+ [
115
+ ::Curl::Err::HostResolutionError,
116
+ ::Curl::Err::ConnectionFailedError,
117
+ ::Curl::Err::GotNothingError,
118
+ ::Curl::Err::RecvError,
119
+ ::Curl::Err::SendError,
120
+ ::Curl::Err::TimeoutError
121
+ ]
122
+ end
123
+
124
+ private
125
+
126
+ def user_agent_header(client)
127
+ @user_agent ||= begin
128
+ meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
129
+ if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
130
+ meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
131
+ end
132
+ meta << "Curb #{Curl::CURB_VERSION}"
133
+ "opensearch-ruby/#{VERSION} (#{meta.join('; ')})"
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,101 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ #
3
+ # The OpenSearch Contributors require contributions made to
4
+ # this file be licensed under the Apache-2.0 license or a
5
+ # compatible open source license.
6
+ #
7
+ # Modifications Copyright OpenSearch Contributors. See
8
+ # GitHub history for details.
9
+ #
10
+ # Licensed to Elasticsearch B.V. under one or more contributor
11
+ # license agreements. See the NOTICE file distributed with
12
+ # this work for additional information regarding copyright
13
+ # ownership. Elasticsearch B.V. licenses this file to you under
14
+ # the Apache License, Version 2.0 (the "License"); you may
15
+ # not use this file except in compliance with the License.
16
+ # You may obtain a copy of the License at
17
+ #
18
+ # http://www.apache.org/licenses/LICENSE-2.0
19
+ #
20
+ # Unless required by applicable law or agreed to in writing,
21
+ # software distributed under the License is distributed on an
22
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
23
+ # KIND, either express or implied. See the License for the
24
+ # specific language governing permissions and limitations
25
+ # under the License.
26
+
27
+ module OpenSearch
28
+ module Transport
29
+ module Transport
30
+ module HTTP
31
+ # The default transport implementation, using the [_Faraday_](https://rubygems.org/gems/faraday)
32
+ # library for abstracting the HTTP client.
33
+ #
34
+ # @see Transport::Base
35
+ #
36
+ class Faraday
37
+ include Base
38
+
39
+ # Performs the request by invoking {Transport::Base#perform_request} with a block.
40
+ #
41
+ # @return [Response]
42
+ # @see Transport::Base#perform_request
43
+ #
44
+ def perform_request(method, path, params = {}, body = nil, headers = nil, opts = {})
45
+ super do |connection, url|
46
+ headers = if connection.connection.headers
47
+ if !headers.nil?
48
+ connection.connection.headers.merge(headers)
49
+ else
50
+ connection.connection.headers
51
+ end
52
+ else
53
+ headers
54
+ end
55
+
56
+ response = connection.connection.run_request(
57
+ method.downcase.to_sym,
58
+ url,
59
+ (body ? __convert_to_json(body) : nil),
60
+ headers
61
+ )
62
+
63
+ Response.new response.status, decompress_response(response.body), response.headers
64
+ end
65
+ end
66
+
67
+ # Builds and returns a connection
68
+ #
69
+ # @return [Connections::Connection]
70
+ #
71
+ def __build_connection(host, options={}, block=nil)
72
+ client = ::Faraday.new(__full_url(host), options, &block)
73
+ apply_headers(client, options)
74
+ Connections::Connection.new :host => host, :connection => client
75
+ end
76
+
77
+ # Returns an array of implementation specific connection errors.
78
+ #
79
+ # @return [Array]
80
+ #
81
+ def host_unreachable_exceptions
82
+ [::Faraday::ConnectionFailed, ::Faraday::TimeoutError]
83
+ end
84
+
85
+ private
86
+
87
+ def user_agent_header(client)
88
+ @user_agent ||= begin
89
+ meta = ["RUBY_VERSION: #{RUBY_VERSION}"]
90
+ if RbConfig::CONFIG && RbConfig::CONFIG['host_os']
91
+ meta << "#{RbConfig::CONFIG['host_os'].split('_').first[/[a-z]+/i].downcase} #{RbConfig::CONFIG['target_cpu']}"
92
+ end
93
+ meta << "#{client.headers[USER_AGENT_STR]}"
94
+ "opensearch-ruby/#{VERSION} (#{meta.join('; ')})"
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end