From 76f5308e74aa22e1cd9fbd0954c84eb6171e116d Mon Sep 17 00:00:00 2001 From: Theo Nam Truong Date: Fri, 20 Jan 2023 15:22:38 -0700 Subject: [PATCH] Added Support for Amazon OpenSearch Serverless (#135) Signed-off-by: Theo Truong Signed-off-by: Theo Truong --- opensearch-aws-sigv4/CHANGELOG.md | 2 + opensearch-aws-sigv4/README.md | 41 +++++++++++++- .../lib/opensearch-aws-sigv4.rb | 11 ++-- .../lib/opensearch-aws-sigv4/version.rb | 2 +- .../spec/unit/sigv4_client_spec.rb | 53 +++++-------------- 5 files changed, 60 insertions(+), 49 deletions(-) diff --git a/opensearch-aws-sigv4/CHANGELOG.md b/opensearch-aws-sigv4/CHANGELOG.md index c0e341ee7..7adc6c802 100644 --- a/opensearch-aws-sigv4/CHANGELOG.md +++ b/opensearch-aws-sigv4/CHANGELOG.md @@ -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 diff --git a/opensearch-aws-sigv4/README.md b/opensearch-aws-sigv4/README.md index 2329369a9..3b0eb6978 100644 --- a/opensearch-aws-sigv4/README.md +++ b/opensearch-aws-sigv4/README.md @@ -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' @@ -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 @@ -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. diff --git a/opensearch-aws-sigv4/lib/opensearch-aws-sigv4.rb b/opensearch-aws-sigv4/lib/opensearch-aws-sigv4.rb index d6f32517d..a936a89a3 100644 --- a/opensearch-aws-sigv4/lib/opensearch-aws-sigv4.rb +++ b/opensearch-aws-sigv4/lib/opensearch-aws-sigv4.rb @@ -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 diff --git a/opensearch-aws-sigv4/lib/opensearch-aws-sigv4/version.rb b/opensearch-aws-sigv4/lib/opensearch-aws-sigv4/version.rb index b7566222a..a4257f375 100644 --- a/opensearch-aws-sigv4/lib/opensearch-aws-sigv4/version.rb +++ b/opensearch-aws-sigv4/lib/opensearch-aws-sigv4/version.rb @@ -10,7 +10,7 @@ module OpenSearch module Aws module Sigv4 - VERSION = '1.0.0'.freeze + VERSION = '1.1.0'.freeze end end end diff --git a/opensearch-aws-sigv4/spec/unit/sigv4_client_spec.rb b/opensearch-aws-sigv4/spec/unit/sigv4_client_spec.rb index bb834069c..1dfe2a3ed 100644 --- a/opensearch-aws-sigv4/spec/unit/sigv4_client_spec.rb +++ b/opensearch-aws-sigv4/spec/unit/sigv4_client_spec.rb @@ -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) @@ -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 @@ -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