utils.py 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. """
  2. Scanner Utilities
  3. Common utilities for AWS resource scanners including retry logic.
  4. """
  5. import time
  6. import logging
  7. from functools import wraps
  8. from botocore.exceptions import ClientError, BotoCoreError
  9. logger = logging.getLogger(__name__)
  10. # Retry configuration
  11. MAX_RETRIES = 3
  12. BASE_DELAY = 1.0 # seconds
  13. MAX_DELAY = 30.0 # seconds
  14. EXPONENTIAL_BASE = 2.0
  15. def retry_with_backoff(max_retries: int = MAX_RETRIES):
  16. """
  17. Decorator for retrying AWS API calls with exponential backoff.
  18. Requirements:
  19. - 5.5: Retry with exponential backoff up to 3 times
  20. """
  21. def decorator(func):
  22. @wraps(func)
  23. def wrapper(*args, **kwargs):
  24. last_exception = None
  25. for attempt in range(max_retries):
  26. try:
  27. return func(*args, **kwargs)
  28. except (ClientError, BotoCoreError) as e:
  29. last_exception = e
  30. # Check if error is retryable
  31. if isinstance(e, ClientError):
  32. error_code = e.response.get('Error', {}).get('Code', '')
  33. # Non-retryable errors
  34. if error_code in ['AccessDenied', 'UnauthorizedAccess',
  35. 'InvalidParameterValue', 'ValidationError']:
  36. raise
  37. if attempt < max_retries - 1:
  38. delay = min(
  39. BASE_DELAY * (EXPONENTIAL_BASE ** attempt),
  40. MAX_DELAY
  41. )
  42. logger.warning(
  43. f"Retry {attempt + 1}/{max_retries} for {func.__name__} "
  44. f"after {delay:.1f}s: {str(e)}"
  45. )
  46. time.sleep(delay)
  47. else:
  48. logger.error(
  49. f"All {max_retries} retries failed for {func.__name__}: {str(e)}"
  50. )
  51. raise last_exception
  52. return wrapper
  53. return decorator