Skip to content

Commit

Permalink
Added Support for Amazon OpenSearch Serverless (#135)
Browse files Browse the repository at this point in the history
Signed-off-by: Theo Truong <[email protected]>

Signed-off-by: Theo Truong <[email protected]>
  • Loading branch information
nhtruong authored Jan 20, 2023
1 parent fe0ee31 commit 76f5308
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 49 deletions.
2 changes: 2 additions & 0 deletions opensearch-aws-sigv4/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)

## [Unreleased]
### Added
- Added support for Amazon OpenSearch Serverless ([#131](https://github.com/opensearch-project/opensearch-ruby/issues/131))

### Changed
### Deprecated
### Removed
Expand Down
41 changes: 40 additions & 1 deletion opensearch-aws-sigv4/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ This library is an AWS Sigv4 wrapper for
which is a Ruby client for OpenSearch. The `OpenSearch::Aws::Sigv4Client` is, therefore, has all features of `OpenSearch::Client`.
And since `opensearch-ruby` is a dependency of `opensearch-aws-sigv4`, you only need to install `opensearch-aws-sigv4`.

### Amazon Managed OpenSearch
Via the Sigv4 Client, you can interact with an Amazon Managed OpenSearch cluster just like would with a self-managed cluster:

```ruby
require 'opensearch-aws-sigv4'
require 'aws-sigv4'
Expand All @@ -43,7 +46,10 @@ signer = Aws::Sigv4::Signer.new(service: 'es',
access_key_id: 'key_id',
secret_access_key: 'secret')

client = OpenSearch::Aws::Sigv4Client.new({ log: true }, signer)
client = OpenSearch::Aws::Sigv4Client.new(
{ host: 'https://your.amz-managed-opensearch.domain',
log: true },
signer)

client.cluster.health

Expand All @@ -54,6 +60,39 @@ client.search q: 'test'

Please refer to [opensearch-ruby](https://github.com/opensearch-project/opensearch-ruby/blob/main/opensearch-ruby/README.md) documentation for further details.

### Amazon OpenSearch Serverless
You can also use this client to connect to Amazon OpenSearch Serverless (AOSS). Remember to change the service for the signer to `aoss`:

```ruby
require 'opensearch-aws-sigv4'
require 'aws-sigv4'

signer = Aws::Sigv4::Signer.new(service: 'aoss',
region: 'us-west-2',
access_key_id: 'key_id',
secret_access_key: 'secret')

client = OpenSearch::Aws::Sigv4Client.new(
{ host: 'https://your.amz-opensearch-serverless.endpoint',
log: true },
signer)

index = 'prime'
client.indices.create(index: index)
client.index(index: index, id: '1', body: { name: 'Amazon Echo',
msrp: '5999',
year: 2011 })
client.search(body: { query: { match: { name: 'Echo' } } })
client.delete(index: index, id: '1')
client.indices.delete(index: index)

# Most administrative commands like the ones below will result in a 404 error for AOSS
client.cluster.stats
client.cat.health
```

*NOTES:* AOSS does NOT support all API endpoints provided by a standard OpenSearch cluster. Refer to [AOSS Developer's Guide](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-genref.html) for more detail.

## Development

You can run `rake -T` to check the test tasks. Use `COVERAGE=true` before running a test task to check the coverage with Simplecov.
Expand Down
11 changes: 4 additions & 7 deletions opensearch-aws-sigv4/lib/opensearch-aws-sigv4.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,16 @@ def perform_request(method, path, params = {}, body = nil, headers = nil)

private

def verify_open_search
@verified = true
end

def signature_url(path, params)
host = @transport.transport.hosts.dig(0, :host)
path = '/' + path unless path.start_with?('/')
query_string = params.empty? ? '' : "#{Faraday::Utils::ParamsHash[params].to_query}"
URI::HTTP.build(host: host, path: path, query: query_string)
end

def open_search_validation_request
verify_signature = sigv4_signer.sign_request(
http_method: 'GET',
url: signature_url('/', {}))
@transport.perform_request('GET', '/', {}, nil, verify_signature.headers)
end
end
end
end
2 changes: 1 addition & 1 deletion opensearch-aws-sigv4/lib/opensearch-aws-sigv4/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
module OpenSearch
module Aws
module Sigv4
VERSION = '1.0.0'.freeze
VERSION = '1.1.0'.freeze
end
end
end
53 changes: 13 additions & 40 deletions opensearch-aws-sigv4/spec/unit/sigv4_client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

describe OpenSearch::Aws::Sigv4Client do
subject(:client) do
OpenSearch::Aws::Sigv4Client.new(
described_class.new(
{ host: 'http://localhost:9200',
transport_options: { ssl: { verify: false } } },
signer)
Expand Down Expand Up @@ -45,19 +45,17 @@
describe '#perform_request' do
let(:response) { { body: 'Response Body' } }
let(:transport_double) do
_double = instance_double('OpenSearch::Transport::Client')
_double = instance_double('OpenSearch::Transport::Client', perform_request: response)
_double.stub_chain(:transport, :hosts, :dig).and_return('localhost')
_double
end
let(:signed_headers) do
{
'authorization' => 'AWS4-HMAC-SHA256 Credential=key_id/20220101/us-west-2/es/aws4_request, '\
'SignedHeaders=host;x-amz-content-sha256;x-amz-date, ' \
'Signature=5c04a328341dbdaf5c74d329d814815fda6ea53ba1e7191cdbc4cd21df834c3f',
{ 'authorization' => 'AWS4-HMAC-SHA256 Credential=key_id/20220101/us-west-2/es/aws4_request, '\
'SignedHeaders=host;x-amz-content-sha256;x-amz-date, ' \
'Signature=9c4c690110483308f62a91c2ca873857750bca2607ba1aabdae0d2303950310a',
'host' => 'localhost',
'x-amz-content-sha256' => 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855',
'x-amz-date' => '20220101T000000Z'
}
'x-amz-date' => '20220101T000000Z' }
end

before(:each) do
Expand All @@ -67,40 +65,15 @@

after(:each) { Timecop.return }

context 'with verified opensearch distribution' do
before(:each) do
client.instance_variable_set(:@verified, true)
allow(transport_double).to receive(:perform_request).with('GET', '/_stats', {}, '', signed_headers) { response }
end

it 'does not verify opensearch distribution again' do
expect(client).to_not receive(:verify_open_search)
output = client.perform_request('GET', '/_stats', {}, '', {})
end

it 'signs the request before passing it to @transport' do
expect(transport_double).to receive(:perform_request).with('GET', '/_stats', {}, '', signed_headers)
output = client.perform_request('GET', '/_stats', {}, '', {})
expect(output).to eq(response)
end
it 'signs the request before passing it to @transport' do
output = client.perform_request('GET', '/', {}, '', {})
expect(output).to eq(response)
expect(transport_double).to have_received(:perform_request).with('GET', '/', {}, '', signed_headers)
end

context 'with unverified opensearch distribution' do
before(:each) do
stub_sigv4_signer = double
allow(stub_sigv4_signer).to receive(:sign_request) { OpenStruct.new(headers: signed_headers) }
client.sigv4_signer = stub_sigv4_signer
end

it 'verifies opensearch distribution' do
verification_response = OpenStruct.new({
headers: {},
body: { 'version' => { 'number' => '1.0.0', 'distribution' => 'opensearch' } },
})
expect(transport_double).to receive(:perform_request).with('GET', '/', {}, nil, signed_headers).ordered { verification_response }
expect(transport_double).to receive(:perform_request).with('GET', '/_stats', {}, '', signed_headers).ordered { response }
output = client.perform_request('GET', '/_stats', {}, '', {})
end
it 'skips the opensearch verification' do
expect(client).to_not receive(:open_search_validation_request)
client.perform_request('GET', '/_stats', {}, '', {})
end
end
end

0 comments on commit 76f5308

Please sign in to comment.