diff --git a/.changes/next-release/bugfix-endpoints-90314.json b/.changes/next-release/bugfix-endpoints-90314.json new file mode 100644 index 000000000000..1bdfba29535d --- /dev/null +++ b/.changes/next-release/bugfix-endpoints-90314.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." +} diff --git a/awscli/botocore/client.py b/awscli/botocore/client.py index 770568ebdca4..0dcc3b832a50 100644 --- a/awscli/botocore/client.py +++ b/awscli/botocore/client.py @@ -602,7 +602,6 @@ def _resolve_signature_version(self, service_name, resolved): class BaseClient(object): - # 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 @@ -682,6 +681,12 @@ def _make_api_call(self, operation_name, api_params): '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, @@ -756,8 +761,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) if not self._client_config.inject_host_prefix: diff --git a/awscli/botocore/signers.py b/awscli/botocore/signers.py index 57df023fff47..ad940e7db592 100644 --- a/awscli/botocore/signers.py +++ b/awscli/botocore/signers.py @@ -614,6 +614,11 @@ def generate_presigned_url(self, ClientMethod, Params=None, ExpiresIn=3600, 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, @@ -738,7 +743,11 @@ def generate_presigned_post(self, Bucket, Key, Fields=None, Conditions=None, # 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, diff --git a/tests/functional/botocore/test_context_params.py b/tests/functional/botocore/test_context_params.py index 833b67833760..b8a852eed50e 100644 --- a/tests/functional/botocore/test_context_params.py +++ b/tests/functional/botocore/test_context_params.py @@ -330,7 +330,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: @@ -475,3 +475,37 @@ 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', + ) diff --git a/tests/unit/botocore/test_signers.py b/tests/unit/botocore/test_signers.py index e06e0c1b0390..53bde1321caf 100644 --- a/tests/unit/botocore/test_signers.py +++ b/tests/unit/botocore/test_signers.py @@ -868,6 +868,33 @@ 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.us-east-1.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):