TooManyRequestsException for Boto3 Client Organization - python-3.x

I am fetch all child account from the Master AWS Account by boto3 Organization.
Code is working fine. I am able to get child account list.
But if you run my AWS Lambda function again then it fail to get Child Accounts.
Getting following error:
Error while getting AWS Accounts : An error occurred (TooManyRequestsException) when calling the ListAccounts operation: AWS Organizations can't complete your request because another request is already in progress. Try again later
After 20 to 30 minutes, I can see my code work for once and again raise above exception.
I am Run this code by AWS Gateway + AWS Lambda.
Any idea?
Code:
import boto3
class Organizations(object):
"""AWS Organization"""
def __init__(self, access_key, secret_access_key, session_token=None):
self.client = boto3.client('organizations',
aws_access_key_id=access_key,
aws_secret_access_key=secret_access_key,
aws_session_token=session_token
)
def get_accounts(self, next_token=None, max_results=None):
"""Get Accounts List"""
if next_token and max_results:
result = self.client.list_accounts(NextToken=next_token,
MaxResults=max_results)
elif next_token:
result = self.client.list_accounts(NextToken=next_token)
elif max_results:
result = self.client.list_accounts(MaxResults=max_results)
else:
result = self.client.list_accounts()
return result
class AWSAccounts(object):
""" Return AWS Accounts information. """
def get_aws_accounts(self, access_key, secret_access_key, session_token):
""" Return List of AWS account Details."""
org_obj = Organizations(access_key=access_key,
secret_access_key=secret_access_key,
session_token=session_token)
aws_accounts = []
next_token = None
next_result = None
while True:
response = org_obj.get_accounts(next_token, next_result)
for account in response['Accounts']:
account_details = {"name": account["Name"],
"id": account["Id"],
"admin_role_name": self.account_role_name
}
aws_accounts.append(account_details)
if "NextToken" not in response:
break
next_token = response["NextToken"]
return aws_accounts

By Exception Handling, my code is running successfully.
Catch TooManyRequestsException exception by ClientError exception and retry to call AWS list_accounts API by boto3.
We can add time sleep of 0.1 seconds.
Code:
class AWSAccounts(object):
""" Return AWS Accounts information. """
def get_accounts(self, next_token=None, max_results=None):
"""Get Accounts List"""
# If Master AWS account contain more child accounts(150+) then
# Too-Many-Request Exception is raised by the AWS API(boto3).
# So to fix this issue, we are calling API again by Exception Handling.
result = None
while True:
try:
if next_token and max_results:
result = self.client.list_accounts(NextToken=next_token,
MaxResults=max_results)
elif next_token:
result = self.client.list_accounts(NextToken=next_token)
elif max_results:
result = self.client.list_accounts(MaxResults=max_results)
else:
result = self.client.list_accounts()
except botocore.exceptions.ClientError as err:
response = err.response
print("Failed to list accounts:", response)
if (response and response.get("Error", {}).get("Code") ==
"TooManyRequestsException"):
print("Continue for TooManyRequestsException exception.")
continue
break
return result

Configure your boto3 client to use the built-in standard retry mode:
import boto3
from botocore.config import Config
config = Config(
retries = {
'max_attempts': 10,
'mode': 'standard'
}
)
ec2 = boto3.client('ec2', config=config)
Per the documentation, the default mode is 'legacy' which doesn't handle TooManyRequestsException.
See boto3 documentation about retry configuration here: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/retries.html

Related

Boto3 InvalidParameterException while executing the lambda function

I'm getting a Boto3 InvalidParameterException while running the lambda function.
I'm trying to find out a way to handle this exception.
I came across the below solution:
from boto.exception import BotoServerError
class InvalidParameterException(BotoServerError):
pass
I'm using python3 and understood that boto is deprecated now and is replaced by boto3.
But i could not find an equivalent solution in boto3.
Can anyone help me out with this ?
As boto is deprecated all the modeled exceptions are available on the client. You can look it up same in the API docs as well , basically the code for the boto3 is straight away generated from the APIs. Earlier approach with boto was hard coded stuff and writing code for the same.
As you can see here
For example
import boto3
from botocore.exceptions import ClientError
def get_secret():
secret_name = "MySecretName"
region_name = "us-west-2"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name,
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
print("The requested secret " + secret_name + " was not found")
elif e.response['Error']['Code'] == 'InvalidRequestException':
print("The request was invalid due to:", e)
elif e.response['Error']['Code'] == 'InvalidParameterException':
print("The request had invalid params:", e)
elif e.response['Error']['Code'] == 'DecryptionFailure':
print("The requested secret can't be decrypted using the provided KMS key:", e)
elif e.response['Error']['Code'] == 'InternalServiceError':
print("An error occurred on service side:", e)
AWS Secrets Manager Example From the docss
How to handle errors with boto3

Slack API request, limiting to 1 request per DAG failure (Airflow)

Hello jr data engineer here!
For some strange reason my task_fail_slack_alert module is triggering the Slack API request a ridiculous amount of times, which is then showing up in our Slack channel that many times and is really annoying. My module should only run and show up in in Slack channel the same amount as the number of tasks that failed.
What am I missing?
import os
from airflow.models
import Variable
import json import requests
def get_channel_name():
channel = '#airflow_alerts_local'
env = Variable.get('env', None)
if env == 'prod':
channel = '#airflow_alerts'
elif env == 'dev':
channel = '#airflow_alerts_dev'
return channel
def task_fail_slack_alert(context):
webhook_url = os.environ.get('SLACK_URL')
slack_data = {
'channel': get_channel_name(),
'text':
""" :red_circle: Task Failed.
*Task*: {task}
*Dag*: {dag}
*Execution Time*: {exec_date}
*Log Url*: {log_url}
""".format(
task=context.get('task_instance').task_id,
dag=context.get('task_instance').dag_id,
ti=context.get('task_instance'),
exec_date=context.get('execution_date'),
log_url=context.get('task_instance').log_url,
)}
response = requests.post(webhook_url, data=json.dumps(slack_data),
headers={'Content-Type': 'application/json'})
if response.status_code != 200:
raise ValueError( 'Request to slack returned an error %s,
the response is:\n%s'(response.status_code, response.text))
task_fail_slack_alert(context)
This is how I have it showing up in the arguments for each dag:
default_args = {
'on_failure_callback': task_fail_slack_alert,
}
The code you provided is recursive:
def task_fail_slack_alert(context):
......
task_fail_slack_alert(context)
Remove the recursion as it's not needed.

Custom exceptions in python starlette

I am trying to raise the custom exception using the starlette framework in python. I have the API call which checks some condtions depends on the result, it should raise exception.
I have two files app.py and error.py
#app.py
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from error import EmptyParamError
async def homepage(request):
a=1
b=0
if a == 1:
raise EmptyParamError(400, "status_code")
return JSONResponse({'hello': 'world'})
routes = [
Route("/", endpoint=homepage)
]
app = Starlette(routes=routes,debug=True)`
#error.py ```
from starlette.responses import JSONResponse
class BaseError(Exception):
def __init__(self, status_code: int, detail: str = None) -> None:
if detail is None:
detail = "http.HTTPStatus(status_code).phrase"
self.status_code = status_code
self.detail = detail
async def not_found(self):
return JSONResponse(content=self.title, status_code=self.status_code)
class EmptyParamError(BaseError):
""" Error is raised when group name is not provided. """
status_code = 400
title = "Missing Group Name"
When the condition is true, i want to raise the exception but its not returning the jsonrespnse but its returning the stacktrace on the console.
Please let me know if anything is wrong here
adding try block resolved the issue
try:
if a==1:
raise InvalidUsage(100,"invalid this one")
if b == 0:
raise EmptyParamError("this is empty paramuvi")
except InvalidUsage as e:
return JSONResponse({'hello': str(e.message)})
except EmptyParamError as e:
return JSONResponse({'hello': str(e.message)})

How to pass variables to my SSM Run Command Document from my Lambda function

I am trying to pass my secrets value to my SSM document from my lambda function. Though I am able to read from my lambda's output - I am not able to put it into my document to call it as a variable. Please Suggest.
import boto3 # Required to interact with AWS
import json # Required for return object parsing
from botocore.exceptions import ClientError
# Set required variables
secret_name = "***/***/***"
endpoint_url = "https://secretsmanager.eu-west-1.amazonaws.com"
region_name = "eu-west-1"
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name,
endpoint_url=endpoint_url
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
except ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFoundException':
print("The requested secret " + secret_name + " was not found")
elif e.response['Error']['Code'] == 'InvalidRequestException':
print("The request was invalid due to:", e)
elif e.response['Error']['Code'] == 'InvalidParameterException':
print("The request had invalid params:", e)
else:
# Decrypted secret using the associated KMS CMK
# Depending on whether the secret was a string or binary, one of these fields will be populated
if 'SecretString' in get_secret_value_response:
secret = json.loads(get_secret_value_response['SecretString'])
else:
binary_secret_data = get_secret_value_response['SecretBinary']
access_key = secret['AWS_ACCESS_KEY_ID']
secret_key = secret['AWS_SECRET_ACCESS_KEY']
region = secret['AWS_DEFAULT_REGION']
ssm = boto3.client('ssm')
ec2 = boto3.resource('ec2')
def lambda_handler(event, context):
running_with = []
running_without = []
for instance in ec2.instances.all():
if instance.state['Name'] != 'running':
continue
has_tag = False
for tag in instance.tags:
if tag['Key'] == 'AutoDiskGrowth' and tag['Value'] == 'True':
has_tag = True
break
if has_tag:
running_with.append(instance.id)
else:
running_without.append(instance.id)
print("access_key: %s" % access_key)
print("Instances found with AutoDiskGrowth Tag: %s" % running_with)
print("Instances without AutoDiskGrowth Tag: %s" % running_without)
ssmCommand = ssm.send_command(
Targets = [
{'Key': 'tag:AutoDiskGrowth',
'Values': [
'True']
}
],
DocumentName = 'Secrets_Management',
TimeoutSeconds = 6000,
Comment = 'Extending disk volume by 50%',
Parameters={
'AWS_ACCESS_KEY_ID': [
'secret_key',
]
}
)
Here, in the above print secret_key, I am able to see the value of the secret stored. But I need it to be sent to the Secrets_Management document as a variable.Here's what I get when I run this.
Current Output:
Response:
{
"errorMessage": "An error occurred (InvalidParameters) when calling the SendCommand operation: ",
"errorType": "InvalidParameters",
The InvalidParameters response indicates that your code is not sending the Parameters expected by the document.
If you go to the document in the Systems Manager console and look in the Parameters tab, you will see the list of Parameters that are permitted. They are case-sensitive.

Mocking boto3 Cloudwatch Log client

A Cloudwatch log is an object with Log Group > Log Stream > Log Events on AWS. I am trying to write tests for this, but the moto mocking raises a client error when applied to boto3.client('logs'). I am looking at other ways to mock the behavior of the log. How would you write a test for this function?
For example:
client = boto3.client('logs')
def get_recent_log_stream_name(logGroupName):
response = client.describe_log_streams(
logGroupName=logGroupName,
orderBy='LastEventTime',
descending=True,
limit=1)
logStreamName = response['logStreams'][0]['logStreamName']
return logStreamName
I would write the test using moto like this:
import boto3
from moto import mock_logs
def get_recent_log_stream_name(logs, logGroupName):
"""Function under test"""
response = logs.describe_log_streams(
logGroupName=logGroupName,
orderBy='LastEventTime',
descending=True,
limit=1)
log_stream_name = response['logStreams'][0]['logStreamName']
return log_stream_name
#mock_logs
def test_get_recent_log_stream_name():
"""Test function"""
log_group_name = 'test-group'
log_stream_name = 'test-stream'
logs = boto3.client('logs')
logs.create_log_group(logGroupName=log_group_name)
logs.create_log_stream(
logGroupName=log_group_name,
logStreamName=log_stream_name,
)
assert get_recent_log_stream_name(logs, log_group_name) == log_stream_name
test_get_recent_log_stream_name()

Resources