Skip to content

Transport extraction #1345

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 0 additions & 12 deletions elasticsearch-transport/lib/elasticsearch/transport.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,3 @@
require "elasticsearch/transport/redacted"

require "elasticsearch/transport/version"

module Elasticsearch
module Client

# A convenience wrapper for {::Elasticsearch::Transport::Client#initialize}.
#
def new(arguments={}, &block)
Elasticsearch::Transport::Client.new(arguments, &block)
end
extend self
end
end
89 changes: 20 additions & 69 deletions elasticsearch-transport/lib/elasticsearch/transport/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ class Client
# @since 7.0.0
DEFAULT_HOST = 'localhost:9200'.freeze

# The default port to use if connecting using a Cloud ID.
# Updated from 9243 to 443 in client version 7.10.1
#
# @since 7.2.0
DEFAULT_CLOUD_PORT = 443

# The default port to use if not otherwise specified.
#
# @since 7.2.0
Expand Down Expand Up @@ -120,8 +114,6 @@ class Client
# The default is false. Responses will automatically be inflated if they are compressed.
# If a custom transport object is used, it must handle the request compression and response inflation.
#
# @option api_key [String, Hash] :api_key Use API Key Authentication, either the base64 encoding of `id` and `api_key`
# joined by a colon as a String, or a hash with the `id` and `api_key` values.
# @option opaque_id_prefix [String] :opaque_id_prefix set a prefix for X-Opaque-Id when initializing the client.
# This will be prepended to the id you set before each request
# if you're using X-Opaque-Id
Expand All @@ -130,8 +122,8 @@ class Client
#
# @yield [faraday] Access and configure the `Faraday::Connection` instance directly with a block
#
def initialize(arguments={}, &block)
@options = arguments.each_with_object({}){ |(k,v), args| args[k.to_sym] = v }
def initialize(arguments = {}, &block)
@options = arguments.transform_keys(&:to_sym)
@arguments = @options
@arguments[:logger] ||= @arguments[:log] ? DEFAULT_LOGGER.call() : nil
@arguments[:tracer] ||= @arguments[:trace] ? DEFAULT_TRACER.call() : nil
Expand All @@ -141,19 +133,17 @@ def initialize(arguments={}, &block)
@arguments[:randomize_hosts] ||= false
@arguments[:transport_options] ||= {}
@arguments[:http] ||= {}
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header) { true }
@arguments[:enable_meta_header] = arguments.fetch(:enable_meta_header, true)
@options[:http] ||= {}

set_api_key if (@api_key = @arguments[:api_key])
set_compatibility_header if ENV['ELASTIC_CLIENT_APIVERSIONING']

@seeds = extract_cloud_creds(@arguments)
@seeds ||= __extract_hosts(@arguments[:hosts] ||
@arguments[:host] ||
@arguments[:url] ||
@arguments[:urls] ||
ENV['ELASTICSEARCH_URL'] ||
DEFAULT_HOST)
@hosts = __extract_hosts(@arguments[:hosts] ||
@arguments[:host] ||
@arguments[:url] ||
@arguments[:urls] ||
ENV['ELASTICSEARCH_URL'] ||
DEFAULT_HOST)

@send_get_body_as = @arguments[:send_get_body_as] || 'GET'
@opaque_id_prefix = @arguments[:opaque_id_prefix] || nil
Expand All @@ -169,17 +159,16 @@ def initialize(arguments={}, &block)
@transport = if @transport_class == Transport::HTTP::Faraday
@arguments[:adapter] ||= __auto_detect_adapter
set_meta_header # from include MetaHeader
@transport_class.new(hosts: @seeds, options: @arguments) do |faraday|
@transport_class.new(hosts: @hosts, options: @arguments) do |faraday|
faraday.adapter(@arguments[:adapter])
block&.call faraday
end
else
set_meta_header # from include MetaHeader
@transport_class.new(hosts: @seeds, options: @arguments)
@transport_class.new(hosts: @hosts, options: @arguments)
end
end
end

# Performs a request through delegation to {#transport}.
#
def perform_request(method, path, params = {}, body = nil, headers = nil)
Expand All @@ -194,13 +183,6 @@ def perform_request(method, path, params = {}, body = nil, headers = nil)

private

def set_api_key
@api_key = __encode(@api_key) if @api_key.is_a? Hash
add_header('Authorization' => "ApiKey #{@api_key}")
@arguments.delete(:user)
@arguments.delete(:password)
end

def set_compatibility_header
return unless ['1', 'true'].include?(ENV['ELASTIC_CLIENT_APIVERSIONING'])

Expand All @@ -220,30 +202,6 @@ def add_header(header)
)
end

def extract_cloud_creds(arguments)
return unless arguments[:cloud_id] && !arguments[:cloud_id].empty?

name = arguments[:cloud_id].split(':')[0]
cloud_url, elasticsearch_instance = Base64.decode64(arguments[:cloud_id].gsub("#{name}:", '')).split('$')

if cloud_url.include?(':')
url, port = cloud_url.split(':')
host = "#{elasticsearch_instance}.#{url}"
else
host = "#{elasticsearch_instance}.#{cloud_url}"
port = arguments[:port] || DEFAULT_CLOUD_PORT
end
[
{
scheme: 'https',
user: arguments[:user],
password: arguments[:password],
host: host,
port: port.to_i
}
]
end

# Normalizes and returns hosts configuration.
#
# Arrayifies the `hosts_config` argument and extracts `host` and `port` info from strings.
Expand All @@ -258,15 +216,15 @@ def extract_cloud_creds(arguments)
#
def __extract_hosts(hosts_config)
hosts = case hosts_config
when String
hosts_config.split(',').map { |h| h.strip! || h }
when Array
hosts_config
when Hash, URI
[ hosts_config ]
else
Array(hosts_config)
end
when String
hosts_config.split(',').map { |h| h.strip! || h }
when Array
hosts_config
when Hash, URI
[ hosts_config ]
else
Array(hosts_config)
end

host_list = hosts.map { |host| __parse_host(host) }
@options[:randomize_hosts] ? host_list.shuffle! : host_list
Expand Down Expand Up @@ -342,13 +300,6 @@ def __auto_detect_adapter
::Faraday.default_adapter
end
end

# Encode credentials for the Authorization Header
# Credentials is the base64 encoding of id and api_key joined by a colon
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html
def __encode(api_key)
Base64.strict_encode64([api_key[:id], api_key[:api_key]].join(':'))
end
end
end
end
192 changes: 0 additions & 192 deletions elasticsearch-transport/spec/elasticsearch/transport/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,48 +70,6 @@
end
end

context 'when an encoded api_key is provided' do
let(:client) do
described_class.new(api_key: 'an_api_key')
end
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'Adds the ApiKey header to the connection' do
expect(authorization_header).to eq('ApiKey an_api_key')
end
end

context 'when an un-encoded api_key is provided' do
let(:client) do
described_class.new(api_key: { id: 'my_id', api_key: 'my_api_key' })
end
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'Adds the ApiKey header to the connection' do
expect(authorization_header).to eq("ApiKey #{Base64.strict_encode64('my_id:my_api_key')}")
end
end

context 'when basic auth and api_key are provided' do
let(:client) do
described_class.new(
api_key: { id: 'my_id', api_key: 'my_api_key' },
host: 'http://elastic:password@localhost:9200'
)
end
let(:authorization_header) do
client.transport.connections.first.connection.headers['Authorization']
end

it 'removes basic auth credentials' do
expect(authorization_header).not_to match(/^Basic/)
expect(authorization_header).to match(/^ApiKey/)
end
end

context 'when a user-agent header is specified as client option in lower-case' do

Expand Down Expand Up @@ -329,156 +287,6 @@
end
end

context 'when cloud credentials are provided' do

let(:client) do
described_class.new(
cloud_id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==',
user: 'elastic',
password: 'changeme'
)
end

let(:hosts) do
client.transport.hosts
end

it 'extracts the cloud credentials' do
expect(hosts[0][:host]).to eq('abcd.localhost')
expect(hosts[0][:protocol]).to eq('https')
expect(hosts[0][:user]).to eq('elastic')
expect(hosts[0][:password]).to eq('changeme')
expect(hosts[0][:port]).to eq(443)
end

it 'creates the correct full url' do
expect(
client.transport.__full_url(client.transport.hosts[0])
).to eq('https://elastic:[email protected]:443')
end

context 'when a port is specified' do

let(:client) do
described_class.new(cloud_id: 'name:bG9jYWxob3N0JGFiY2QkZWZnaA==', user: 'elastic', password: 'changeme', port: 9250)
end

it 'sets the specified port along with the cloud credentials' do
expect(hosts[0][:host]).to eq('abcd.localhost')
expect(hosts[0][:protocol]).to eq('https')
expect(hosts[0][:user]).to eq('elastic')
expect(hosts[0][:password]).to eq('changeme')
expect(hosts[0][:port]).to eq(9250)
end

it 'creates the correct full url' do
expect(client.transport.__full_url(client.transport.hosts[0])).to eq('https://elastic:[email protected]:9250')
end
end

context 'when the cluster has alternate names' do

let(:client) do
described_class.new(
cloud_id: 'myCluster:bG9jYWxob3N0JGFiY2QkZWZnaA==',
user: 'elasticfantastic',
password: 'tobechanged'
)
end

let(:hosts) do
client.transport.hosts
end

it 'extracts the cloud credentials' do
expect(hosts[0][:host]).to eq('abcd.localhost')
expect(hosts[0][:protocol]).to eq('https')
expect(hosts[0][:user]).to eq('elasticfantastic')
expect(hosts[0][:password]).to eq('tobechanged')
expect(hosts[0][:port]).to eq(443)
end

it 'creates the correct full url' do
expect(
client.transport.__full_url(client.transport.hosts[0])
).to eq('https://elasticfantastic:[email protected]:443')
end
end

context 'when decoded cloud id has a trailing dollar sign' do
let(:client) do
described_class.new(
cloud_id: 'a_cluster:bG9jYWxob3N0JGFiY2Qk',
user: 'elasticfantastic',
password: 'changeme'
)
end

let(:hosts) do
client.transport.hosts
end

it 'extracts the cloud credentials' do
expect(hosts[0][:host]).to eq('abcd.localhost')
expect(hosts[0][:protocol]).to eq('https')
expect(hosts[0][:user]).to eq('elasticfantastic')
expect(hosts[0][:password]).to eq('changeme')
expect(hosts[0][:port]).to eq(443)
end

it 'creates the correct full url' do
expect(
client.transport.__full_url(client.transport.hosts[0])
).to eq('https://elasticfantastic:[email protected]:443')
end
end

context 'when the cloud host provides a port' do
let(:client) do
described_class.new(
cloud_id: 'name:ZWxhc3RpY19zZXJ2ZXI6OTI0MyRlbGFzdGljX2lk',
user: 'elastic',
password: 'changeme'
)
end

let(:hosts) do
client.transport.hosts
end

it 'creates the correct full url' do
expect(hosts[0][:host]).to eq('elastic_id.elastic_server')
expect(hosts[0][:protocol]).to eq('https')
expect(hosts[0][:user]).to eq('elastic')
expect(hosts[0][:password]).to eq('changeme')
expect(hosts[0][:port]).to eq(9243)
end
end

context 'when the cloud host provides a port and the port is also specified' do
let(:client) do
described_class.new(
cloud_id: 'name:ZWxhc3RpY19zZXJ2ZXI6OTI0MyRlbGFzdGljX2lk',
user: 'elastic',
password: 'changeme',
port: 9200
)
end

let(:hosts) do
client.transport.hosts
end

it 'creates the correct full url' do
expect(hosts[0][:host]).to eq('elastic_id.elastic_server')
expect(hosts[0][:protocol]).to eq('https')
expect(hosts[0][:user]).to eq('elastic')
expect(hosts[0][:password]).to eq('changeme')
expect(hosts[0][:port]).to eq(9243)
end
end
end

shared_examples_for 'a client that extracts hosts' do

context 'when the host is a String' do
Expand Down
4 changes: 3 additions & 1 deletion elasticsearch/Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@
# specific language governing permissions and limitations
# under the License.

require "bundler/gem_tasks"
require 'bundler/gem_tasks'

task(:default) { system 'rake --tasks' }

desc 'Run unit tests'
task test: 'test:spec'
Expand Down
Loading