Skip to content

Commit 5c1c890

Browse files
authored
Abstract away service name (opensearch-project#268)
* Abstract away service name Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Compuute x-amz-content-256 header Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Fix async signing Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Adds types-six to dependencies Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Optionally remove Content-Length Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Fix dict typo Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Remove requirement for x-amz-content-sha256 header Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Remove deletion of content-length Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Fix capitalization Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> * Adding unit tests Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com> Signed-off-by: Harsha Vamsi Kalluri <harshavamsi096@gmail.com>
1 parent c73e463 commit 5c1c890

File tree

11 files changed

+132
-36
lines changed

11 files changed

+132
-36
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
2626
- Updated getting started to user guide ([#233](https://github.com/opensearch-project/opensearch-py/pull/233))
2727
- Updated CA certificate handling to check OpenSSL environment variables before defaulting to certifi ([#196](https://github.com/opensearch-project/opensearch-py/pull/196))
2828
- Updates `master` to `cluster_manager` to be inclusive ([#242](https://github.com/opensearch-project/opensearch-py/pull/242))
29+
- Support a custom signing service name for AWS SigV4 ([#268](https://github.com/opensearch-project/opensearch-py/pull/268))
2930
### Deprecated
3031

3132
### Removed

USER_GUIDE.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -407,8 +407,9 @@ import boto3
407407

408408
host = '' # cluster endpoint, for example: my-test-domain.us-east-1.es.amazonaws.com
409409
region = 'us-west-2'
410+
service = 'es' # 'aoss' for OpenSearch Serverless
410411
credentials = boto3.Session().get_credentials()
411-
auth = AWSV4SignerAuth(credentials, region)
412+
auth = AWSV4SignerAuth(credentials, region, service)
412413
index_name = 'python-test-index3'
413414

414415
client = OpenSearch(
@@ -450,8 +451,9 @@ import boto3
450451

451452
host = '' # cluster endpoint, for example: my-test-domain.us-east-1.es.amazonaws.com
452453
region = 'us-west-2'
454+
service = 'es' # 'aoss' for OpenSearch Serverless
453455
credentials = boto3.Session().get_credentials()
454-
auth = AWSV4SignerAsyncAuth(credentials, region)
456+
auth = AWSV4SignerAsyncAuth(credentials, region, service)
455457
index_name = 'python-test-index3'
456458

457459
client = OpenSearch(

docs/source/conf.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@
1717

1818
# -- Project information -----------------------------------------------------
1919

20-
project = 'OpenSearch Python Client'
21-
copyright = 'OpenSearch Project Contributors'
22-
author = 'OpenSearch Project Contributors'
20+
project = "OpenSearch Python Client"
21+
copyright = "OpenSearch Project Contributors"
22+
author = "OpenSearch Project Contributors"
2323

2424

2525
# -- General configuration ---------------------------------------------------
@@ -38,7 +38,7 @@
3838
]
3939

4040
# Add any paths that contain templates here, relative to this directory.
41-
templates_path = ['_templates']
41+
templates_path = ["_templates"]
4242

4343
# List of patterns, relative to source directory, that match files and
4444
# directories to ignore when looking for source files.
@@ -51,12 +51,12 @@
5151
# The theme to use for HTML and HTML Help pages. See the documentation for
5252
# a list of builtin themes.
5353
#
54-
html_theme = 'sphinx_rtd_theme'
54+
html_theme = "sphinx_rtd_theme"
5555

5656
# Add any paths that contain custom static files (such as style sheets) here,
5757
# relative to this directory. They are copied after the builtin static files,
5858
# so a file named "default.css" will overwrite the builtin "default.css".
59-
html_static_path = ['_static']
59+
html_static_path = ["_static"]
6060

6161
# -- additional settings -------------------------------------------------
6262
intersphinx_mapping = {

noxfile.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def format(session):
5757

5858
@nox.session()
5959
def lint(session):
60-
session.install("flake8", "black", "mypy", "isort", "types-requests")
60+
session.install("flake8", "black", "mypy", "isort", "types-requests", "types-six")
6161

6262
session.run("isort", "--check", "--profile=black", *SOURCE_FILES)
6363
session.run("black", "--target-version=py33", "--check", *SOURCE_FILES)

opensearchpy/_async/helpers.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ async def map_actions():
204204
raise_on_error,
205205
ignore_status,
206206
*args,
207-
**kwargs
207+
**kwargs,
208208
),
209209
):
210210

@@ -471,5 +471,5 @@ async def _change_doc_index(hits, index):
471471
target_client,
472472
_change_doc_index(docs, target_index),
473473
chunk_size=chunk_size,
474-
**kwargs
474+
**kwargs,
475475
)

opensearchpy/helpers/asyncsigner.py

+8-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@
99

1010
import sys
1111

12-
OPENSEARCH_SERVICE = "es"
13-
1412
PY3 = sys.version_info[0] == 3
1513

1614

@@ -19,7 +17,7 @@ class AWSV4SignerAsyncAuth:
1917
AWS V4 Request Signer for Async Requests.
2018
"""
2119

22-
def __init__(self, credentials, region): # type: ignore
20+
def __init__(self, credentials, region, service="es"): # type: ignore
2321
if not credentials:
2422
raise ValueError("Credentials cannot be empty")
2523
self.credentials = credentials
@@ -28,6 +26,10 @@ def __init__(self, credentials, region): # type: ignore
2826
raise ValueError("Region cannot be empty")
2927
self.region = region
3028

29+
if not service:
30+
raise ValueError("Service name cannot be empty")
31+
self.service = service
32+
3133
def __call__(self, method, url, query_string, body): # type: ignore
3234
return self._sign_request(method, url, query_string, body) # type: ignore
3335

@@ -37,17 +39,17 @@ def _sign_request(self, method, url, query_string, body):
3739
:param prepared_request: unsigned headers
3840
:return: signed headers
3941
"""
42+
4043
from botocore.auth import SigV4Auth
4144
from botocore.awsrequest import AWSRequest
4245

4346
# create an AWS request object and sign it using SigV4Auth
44-
print("".join([url, query_string]))
4547
aws_request = AWSRequest(
4648
method=method,
4749
url="".join([url, query_string]),
48-
data=body,
4950
)
50-
sig_v4_auth = SigV4Auth(self.credentials, OPENSEARCH_SERVICE, self.region)
51+
52+
sig_v4_auth = SigV4Auth(self.credentials, self.service, self.region)
5153
sig_v4_auth.add_auth(aws_request)
5254

5355
# copy the headers from AWS request object into the prepared_request

opensearchpy/helpers/signer.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,6 @@
1111

1212
import requests
1313

14-
OPENSEARCH_SERVICE = "es"
15-
1614
PY3 = sys.version_info[0] == 3
1715

1816
if PY3:
@@ -50,7 +48,7 @@ class AWSV4SignerAuth(requests.auth.AuthBase):
5048
AWS V4 Request Signer for Requests.
5149
"""
5250

53-
def __init__(self, credentials, region): # type: ignore
51+
def __init__(self, credentials, region, service="es"): # type: ignore
5452
if not credentials:
5553
raise ValueError("Credentials cannot be empty")
5654
self.credentials = credentials
@@ -59,6 +57,10 @@ def __init__(self, credentials, region): # type: ignore
5957
raise ValueError("Region cannot be empty")
6058
self.region = region
6159

60+
if not service:
61+
raise ValueError("Service name cannot be empty")
62+
self.service = service
63+
6264
def __call__(self, request): # type: ignore
6365
return self._sign_request(request) # type: ignore
6466

@@ -78,9 +80,9 @@ def _sign_request(self, prepared_request): # type: ignore
7880
aws_request = AWSRequest(
7981
method=prepared_request.method.upper(),
8082
url=url,
81-
data=prepared_request.body,
8283
)
83-
sig_v4_auth = SigV4Auth(self.credentials, OPENSEARCH_SERVICE, self.region)
84+
85+
sig_v4_auth = SigV4Auth(self.credentials, self.service, self.region)
8486
sig_v4_auth.add_auth(aws_request)
8587

8688
# copy the headers from AWS request object into the prepared_request

out/opensearchpy/plugins/__init__.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,4 @@
55
# compatible open source license.
66
#
77
# Modifications Copyright OpenSearch Contributors. See
8-
# GitHub history for details.
8+
# GitHub history for details.

out/opensearchpy/plugins/alerting.pyi

+61-13
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,67 @@
77
# Modifications Copyright OpenSearch Contributors. See
88
# GitHub history for details.
99

10-
from ..client.utils import NamespacedClient as NamespacedClient, query_params as query_params
10+
from ..client.utils import (
11+
NamespacedClient as NamespacedClient,
12+
query_params as query_params,
13+
)
1114
from typing import Any, Union
1215

1316
class AlertingClient(NamespacedClient):
14-
def search_monitor(self, body, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
15-
def get_monitor(self, monitor_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
16-
def run_monitor(self, monitor_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
17-
def create_monitor(self, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
18-
def update_monitor(self, monitor_id, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
19-
def delete_monitor(self, monitor_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
20-
def get_destination(self, destination_id: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
21-
def create_destination(self, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
22-
def update_destination(self, destination_id, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
23-
def delete_destination(self, destination_id, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
24-
def get_alerts(self, params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
25-
def acknowledge_alert(self, monitor_id, body: Any | None = ..., params: Any | None = ..., headers: Any | None = ...) -> Union[bool, Any]: ...
17+
def search_monitor(
18+
self, body, params: Any | None = ..., headers: Any | None = ...
19+
) -> Union[bool, Any]: ...
20+
def get_monitor(
21+
self, monitor_id, params: Any | None = ..., headers: Any | None = ...
22+
) -> Union[bool, Any]: ...
23+
def run_monitor(
24+
self, monitor_id, params: Any | None = ..., headers: Any | None = ...
25+
) -> Union[bool, Any]: ...
26+
def create_monitor(
27+
self,
28+
body: Any | None = ...,
29+
params: Any | None = ...,
30+
headers: Any | None = ...,
31+
) -> Union[bool, Any]: ...
32+
def update_monitor(
33+
self,
34+
monitor_id,
35+
body: Any | None = ...,
36+
params: Any | None = ...,
37+
headers: Any | None = ...,
38+
) -> Union[bool, Any]: ...
39+
def delete_monitor(
40+
self, monitor_id, params: Any | None = ..., headers: Any | None = ...
41+
) -> Union[bool, Any]: ...
42+
def get_destination(
43+
self,
44+
destination_id: Any | None = ...,
45+
params: Any | None = ...,
46+
headers: Any | None = ...,
47+
) -> Union[bool, Any]: ...
48+
def create_destination(
49+
self,
50+
body: Any | None = ...,
51+
params: Any | None = ...,
52+
headers: Any | None = ...,
53+
) -> Union[bool, Any]: ...
54+
def update_destination(
55+
self,
56+
destination_id,
57+
body: Any | None = ...,
58+
params: Any | None = ...,
59+
headers: Any | None = ...,
60+
) -> Union[bool, Any]: ...
61+
def delete_destination(
62+
self, destination_id, params: Any | None = ..., headers: Any | None = ...
63+
) -> Union[bool, Any]: ...
64+
def get_alerts(
65+
self, params: Any | None = ..., headers: Any | None = ...
66+
) -> Union[bool, Any]: ...
67+
def acknowledge_alert(
68+
self,
69+
monitor_id,
70+
body: Any | None = ...,
71+
params: Any | None = ...,
72+
headers: Any | None = ...,
73+
) -> Union[bool, Any]: ...

test_opensearchpy/test_async/test_asyncsigner.py

+15
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,18 @@ async def test_aws_signer_async_when_credentials_is_null(self):
6363

6464
with pytest.raises(ValueError) as e:
6565
assert str(e.value) == "Credentials cannot be empty"
66+
67+
@pytest.mark.skipif(
68+
sys.version_info < (3, 6), reason="AWSV4SignerAsyncAuth requires python3.6+"
69+
)
70+
async def test_aws_signer_async_when_service_is_specified(self):
71+
region = "us-west-2"
72+
service = "aoss"
73+
74+
from opensearchpy.helpers.asyncsigner import AWSV4SignerAsyncAuth
75+
76+
auth = AWSV4SignerAsyncAuth(self.mock_session(), region, service)
77+
headers = auth("GET", "http://localhost")
78+
self.assertIn("Authorization", headers)
79+
self.assertIn("X-Amz-Date", headers)
80+
self.assertIn("X-Amz-Security-Token", headers)

test_opensearchpy/test_connection.py

+26
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,9 @@ def test_aws_signer_as_http_auth(self):
333333
self.assertIn("X-Amz-Date", prepared_request.headers)
334334
self.assertIn("X-Amz-Security-Token", prepared_request.headers)
335335

336+
@pytest.mark.skipif(
337+
sys.version_info < (3, 6), reason="AWSV4SignerAuth requires python3.6+"
338+
)
336339
def test_aws_signer_when_region_is_null(self):
337340
session = self.mock_session()
338341

@@ -346,6 +349,9 @@ def test_aws_signer_when_region_is_null(self):
346349
AWSV4SignerAuth(session, "")
347350
assert str(e.value) == "Region cannot be empty"
348351

352+
@pytest.mark.skipif(
353+
sys.version_info < (3, 6), reason="AWSV4SignerAuth requires python3.6+"
354+
)
349355
def test_aws_signer_when_credentials_is_null(self):
350356
region = "us-west-1"
351357

@@ -359,6 +365,26 @@ def test_aws_signer_when_credentials_is_null(self):
359365
AWSV4SignerAuth("", region)
360366
assert str(e.value) == "Credentials cannot be empty"
361367

368+
@pytest.mark.skipif(
369+
sys.version_info < (3, 6), reason="AWSV4SignerAuth requires python3.6+"
370+
)
371+
def test_aws_signer_when_service_is_specified(self):
372+
region = "us-west-1"
373+
service = "aoss"
374+
375+
import requests
376+
377+
from opensearchpy.helpers.signer import AWSV4SignerAuth
378+
379+
auth = AWSV4SignerAuth(self.mock_session(), region, service)
380+
con = RequestsHttpConnection(http_auth=auth)
381+
prepared_request = requests.Request("GET", "http://localhost").prepare()
382+
auth(prepared_request)
383+
self.assertEqual(auth, con.session.auth)
384+
self.assertIn("Authorization", prepared_request.headers)
385+
self.assertIn("X-Amz-Date", prepared_request.headers)
386+
self.assertIn("X-Amz-Security-Token", prepared_request.headers)
387+
362388
def mock_session(self):
363389
access_key = uuid.uuid4().hex
364390
secret_key = uuid.uuid4().hex

0 commit comments

Comments
 (0)