Skip to content

Commit

Permalink
Include params set in provide-client-param event handlers in dynamic …
Browse files Browse the repository at this point in the history
…context params for endpoint resolution (#2920)

* emit provide-client-params.* and before-parameter-build.* before endpoint resolution

* test coverage for dynamic ctx params set in provide-client-param event handler

* consolidate repeated code in _convert_to_request_dict

* test coverage for provide-client-param event handler + presigned URLs

* changelog

* Revert "consolidate repeated code in _convert_to_request_dict"

* bring back empty lines as per PR review comment
  • Loading branch information
jonemo committed May 11, 2023
1 parent 3879f9a commit 4f5bc57
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-endpoints-38658.json
@@ -0,0 +1,5 @@
{
"type": "bugfix",
"category": "endpoints",
"description": "Include params set in provide-client-param event handlers in dynamic context params for endpoint resolution."
}
9 changes: 5 additions & 4 deletions botocore/client.py
Expand Up @@ -819,7 +819,6 @@ def _resolve_signature_version(self, service_name, resolved):


class BaseClient:

# This is actually reassigned with the py->op_name mapping
# when the client creator creates the subclass. This value is used
# because calls such as client.get_paginator('list_objects') use the
Expand Down Expand Up @@ -913,6 +912,11 @@ def _make_api_call(self, operation_name, api_params):
'has_streaming_input': operation_model.has_streaming_input,
'auth_type': operation_model.auth_type,
}
api_params = self._emit_api_params(
api_params=api_params,
operation_model=operation_model,
context=request_context,
)
endpoint_url, additional_headers = self._resolve_endpoint_ruleset(
operation_model, api_params, request_context
)
Expand Down Expand Up @@ -984,9 +988,6 @@ def _convert_to_request_dict(
headers=None,
set_user_agent_header=True,
):
api_params = self._emit_api_params(
api_params, operation_model, context
)
request_dict = self._serializer.serialize_to_request(
api_params, operation_model
)
Expand Down
11 changes: 10 additions & 1 deletion botocore/signers.py
Expand Up @@ -656,6 +656,11 @@ def generate_presigned_url(
raise UnknownClientMethodError(method_name=client_method)

operation_model = self.meta.service_model.operation_model(operation_name)
params = self._emit_api_params(
api_params=params,
operation_model=operation_model,
context=context,
)
bucket_is_arn = ArnParser.is_arn(params.get('Bucket', ''))
endpoint_url, additional_headers = self._resolve_endpoint_ruleset(
operation_model,
Expand Down Expand Up @@ -778,7 +783,11 @@ def generate_presigned_post(
# We choose the CreateBucket operation model because its url gets
# serialized to what a presign post requires.
operation_model = self.meta.service_model.operation_model('CreateBucket')
params = {'Bucket': bucket}
params = self._emit_api_params(
api_params={'Bucket': bucket},
operation_model=operation_model,
context=context,
)
bucket_is_arn = ArnParser.is_arn(params.get('Bucket', ''))
endpoint_url, additional_headers = self._resolve_endpoint_ruleset(
operation_model,
Expand Down
41 changes: 40 additions & 1 deletion tests/functional/test_context_params.py
Expand Up @@ -343,7 +343,7 @@ def test_client_context_param_sent_to_endpoint_resolver(
),
)

# Stub client to prevent a request from getting sent and asceertain that
# Stub client to prevent a request from getting sent and ascertain that
# only a single request would get sent. Wrap the EndpointProvider's
# resolve_endpoint method for inspecting the arguments it gets called with.
with ClientHTTPStubber(client, strict=True) as http_stubber:
Expand Down Expand Up @@ -488,3 +488,42 @@ def test_dynamic_context_param_sent_to_endpoint_resolver(
)
else:
mock_resolve_endpoint.assert_called_once_with(Region='us-east-1')


def test_dynamic_context_param_from_event_handler_sent_to_endpoint_resolver(
monkeypatch,
patched_session,
):
# patch loader to return fake service model and fake endpoint ruleset
patch_load_service_model(
patched_session,
monkeypatch,
FAKE_MODEL_WITH_DYNAMIC_CONTEXT_PARAM,
FAKE_RULESET_WITH_DYNAMIC_CONTEXT_PARAM,
)

# event handler for provide-client-params that modifies the value of the
# MockOpParam parameter
def change_param(params, **kwargs):
params['MockOpParam'] = 'mock-op-param-value-2'

client = patched_session.create_client(
'otherservice', region_name='us-east-1'
)
client.meta.events.register_last(
'provide-client-params.other-service.*', change_param
)

with ClientHTTPStubber(client, strict=True) as http_stubber:
http_stubber.add_response(status=200)
with mock.patch.object(
client._ruleset_resolver._provider,
'resolve_endpoint',
wraps=client._ruleset_resolver._provider.resolve_endpoint,
) as mock_resolve_endpoint:
client.mock_operation(MockOpParam='mock-op-param-value-1')

mock_resolve_endpoint.assert_called_once_with(
Region='us-east-1',
FooDynamicContextParamName='mock-op-param-value-2',
)
31 changes: 31 additions & 0 deletions tests/unit/test_signers.py
Expand Up @@ -991,6 +991,37 @@ def test_generate_presign_url_emits_is_presign_in_context(self):
'the following kwargs emitted: %s' % kwargs,
)

def test_context_param_from_event_handler_sent_to_endpoint_resolver(self):
def change_bucket_param(params, **kwargs):
params['Bucket'] = 'mybucket-bar'

self.client.meta.events.register_last(
'provide-client-params.s3.*', change_bucket_param
)

self.client.generate_presigned_url(
'get_object', Params={'Bucket': 'mybucket-foo', 'Key': self.key}
)

ref_request_dict = {
'body': b'',
# If the bucket name set in the provide-client-params event handler
# was correctly passed to the endpoint provider as a dynamic context
# parameter, it will appear in the URL and the auth_path:
'url': 'https://mybucket-bar.s3.amazonaws.com/mykey',
'headers': {},
'auth_path': '/mybucket-bar/mykey',
'query_string': {},
'url_path': '/mykey',
'method': 'GET',
'context': mock.ANY,
}
self.generate_url_mock.assert_called_with(
request_dict=ref_request_dict,
expires_in=3600,
operation_name='GetObject',
)


class TestGeneratePresignedPost(unittest.TestCase):
def setUp(self):
Expand Down

0 comments on commit 4f5bc57

Please sign in to comment.