Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions aws_lambda_powertools/utilities/parameters/ssm.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,17 +622,16 @@ def _get_parameters_by_name(
batch_errors: list[str] = []
parameter_names = list(parameters.keys())

# All params in the batch must be decrypted
# we return early if we hit an unrecoverable exception like InvalidKeyId/InternalServerError
# everything else should technically be recoverable as GetParameters is non-atomic
# Handle request-level failures according to raise_on_error.
# Successful responses may still report failures in InvalidParameters.
try:
if decrypt:
response = self.client.get_parameters(Names=parameter_names, WithDecryption=True)
else:
response = self.client.get_parameters(Names=parameter_names)
except (self.client.exceptions.InvalidKeyId, self.client.exceptions.InternalServerError) as exc:
except Exception as exc:
if raise_on_error:
raise GetParameterError(f"Failed to fetch parameters: {parameter_names}") from exc
raise GetParameterError(f"Failed to fetch parameters: {parameter_names}: {exc}") from exc
return ret, parameter_names

batch_errors = self._handle_any_invalid_get_parameter_errors(response, raise_on_error)
Expand Down
51 changes: 51 additions & 0 deletions tests/functional/parameters/_boto3/test_utilities_parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,57 @@ def test_ssm_provider_get_parameters_by_name_do_not_raise_on_failure_with_decryp
stubber.deactivate()


@pytest.mark.parametrize("decrypt", [False, True])
@pytest.mark.parametrize("error_code", ["ThrottlingException", "InvalidKeyId", "InternalServerError"])
def test_ssm_provider_get_parameters_by_name_wraps_client_error(mock_name, config, error_code, decrypt):
# GIVEN the GetParameters call fails with an SDK error
param = f"/{mock_name}"
expected_params = {"Names": [param], "WithDecryption": True} if decrypt else {"Names": [param]}

provider = parameters.SSMProvider(boto_config=config)
stubber = stub.Stubber(provider.client)
stubber.add_client_error("get_parameters", error_code, expected_params=expected_params)
stubber.activate()

# WHEN fail-fast is enabled in get_parameters_by_name
# THEN the SDK error should be wrapped in GetParameterError
try:
with pytest.raises(parameters.exceptions.GetParameterError, match=error_code):
provider.get_parameters_by_name(parameters={param: {}}, decrypt=decrypt)
stubber.assert_no_pending_responses()
finally:
stubber.deactivate()


def test_ssm_provider_get_parameters_by_name_do_not_raise_on_client_error(mock_name, mock_value, config):
# GIVEN 11 parameters, fetched in two GetParameters chunks
# the first chunk has one invalid parameter and the second chunk is throttled
names = [f"/{mock_name}/{i}" for i in range(11)]
first_chunk = dict.fromkeys(names[:10], mock_value)
invalid, throttled = names[9], names[10]

provider = parameters.SSMProvider(boto_config=config)
stubber = stub.Stubber(provider.client)
stubber.add_response(
"get_parameters",
build_get_parameters_stub(params=first_chunk, invalid_parameters=[invalid]),
{"Names": names[:10]},
)
stubber.add_client_error("get_parameters", "ThrottlingException", expected_params={"Names": [throttled]})
stubber.activate()

# WHEN fail-fast is disabled in get_parameters_by_name
try:
ret = provider.get_parameters_by_name(parameters={name: {} for name in names}, raise_on_error=False)
stubber.assert_no_pending_responses()

# THEN both failures should be reported under `_errors` key and the successful values kept
assert ret["_errors"] == [invalid, throttled]
assert all(ret[name] == mock_value for name in names[:9])
finally:
stubber.deactivate()


def test_ssm_provider_get_parameters_by_name_do_not_raise_on_failure_batch_decrypt_combined(
mock_value,
mock_version,
Expand Down
Loading