From d7067f7062f9d620825fef301b942e280cb439b7 Mon Sep 17 00:00:00 2001 From: Andrew Halaney Date: Tue, 5 May 2026 14:21:19 -0500 Subject: [PATCH] Fix InstanceMetadataRegionFetcher for local zones InstanceMetadataRegionFetcher queries latest/meta-data/placement/availability-zone/ and strips the trailing character to derive the region. This works for standard AZs (us-east-1a becomes us-east-1) but breaks for local zones (us-east-1-atl-2a becomes us-east-1-atl-2). Switch to latest/meta-data/placement/region, which returns the parent region directly. This has been around since[0] 2020 so it _should_ be ok to use. [0]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html#instancedata-data-categories --- botocore/utils.py | 6 ++---- tests/unit/test_utils.py | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/botocore/utils.py b/botocore/utils.py index 9553e0c82a..47b5d88c04 100644 --- a/botocore/utils.py +++ b/botocore/utils.py @@ -783,7 +783,7 @@ def _create_fetcher(self): class InstanceMetadataRegionFetcher(IMDSFetcher): - _URL_PATH = 'latest/meta-data/placement/availability-zone/' + _URL_PATH = 'latest/meta-data/placement/region' def retrieve_region(self): """Get the current region from the instance metadata service. @@ -815,9 +815,7 @@ def _get_region(self): retry_func=self._default_retry, token=token, ) - availability_zone = response.text - region = availability_zone[:-1] - return region + return response.text def merge_dicts(dict1, dict2, append_lists=False): diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 96a485a1c2..d5cf536a51 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -3372,6 +3372,40 @@ def test_expiry_extension_with_bad_datetime(self): assert results['expiry_time'] == bad_datetime +class TestInstanceMetadataRegionFetcher(unittest.TestCase): + _IMDS_RESPONSES = { + 'api/token': b'my-token', + 'placement/region': b'us-east-1', + 'placement/availability-zone': b'us-east-1-atl-2a', + } + + def setUp(self): + urllib3_session_send = 'botocore.httpsession.URLLib3Session.send' + self._urllib3_patch = mock.patch(urllib3_session_send) + self._send = self._urllib3_patch.start() + self._send.side_effect = self._get_imds_response + + def tearDown(self): + self._urllib3_patch.stop() + + def _get_imds_response(self, request): + for path, body in self._IMDS_RESPONSES.items(): + if path in request.url: + return botocore.awsrequest.AWSResponse( + url=request.url, + status_code=200, + headers={}, + raw=RawResponse(body), + ) + raise ValueError(f'Unexpected IMDS request: {request.url}') + + def _make_fetcher(self): + return InstanceMetadataRegionFetcher() + + def test_retrieve_region_local_zone_instance(self): + self.assertEqual(self._make_fetcher().retrieve_region(), 'us-east-1') + + class TestIMDSRegionProvider(unittest.TestCase): def setUp(self): self.environ = {}