in serverless-rest-api/python-http-cdk/lib/serverless_api_stack.py [0:0]
def __init__(self, scope: cdk.Construct, construct_id: str, cognito_stack_name="", **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
self.cognito_stack_name = cognito_stack_name
# check if Cognito stack name was passed as a stack parameter instead
if cognito_stack_name == "":
upload_bucket_name = cdk.CfnParameter(self, "CognitoStackName", type="String",
description="The name of the Cognito stack to be used by API Lambda Authorizer.")
# use default value if nothing else works
if cognito_stack_name == "":
self.cognito_stack_name = "apigw - samples - cognito - shared"
# Create DynamoDB tables and GSIs for the API
locations_table = ddb.Table(self, 'LocationsTable',
partition_key=ddb.Attribute(name='locationid', type=ddb.AttributeType.STRING)
)
resources_table = ddb.Table(self, 'ResourcesTable',
partition_key=ddb.Attribute(name='resourceid', type=ddb.AttributeType.STRING)
)
resources_table.add_global_secondary_index(index_name='locationidGSI',
partition_key=ddb.Attribute(name='locationid',
type=ddb.AttributeType.STRING),
projection_type=ddb.ProjectionType.ALL
)
bookings_table = ddb.Table(self, 'BookingsTable',
partition_key=ddb.Attribute(name='bookingid', type=ddb.AttributeType.STRING)
)
bookings_table.add_global_secondary_index(index_name='useridGSI',
partition_key=ddb.Attribute(name='userid',
type=ddb.AttributeType.STRING),
projection_type=ddb.ProjectionType.ALL
)
bookings_table.add_global_secondary_index(index_name='bookingsByUserByTimeGSI',
partition_key=ddb.Attribute(name='userid',
type=ddb.AttributeType.STRING),
sort_key=ddb.Attribute(name='starttimeepochtime',
type=ddb.AttributeType.NUMBER),
projection_type=ddb.ProjectionType.ALL
)
bookings_table.add_global_secondary_index(index_name='bookingsByResourceByTimeGSI',
partition_key=ddb.Attribute(name='resourceid',
type=ddb.AttributeType.STRING),
sort_key=ddb.Attribute(name='starttimeepochtime',
type=ddb.AttributeType.NUMBER),
projection_type=ddb.ProjectionType.ALL
)
# Create CRUD and search Lambda functions for APIs
locations_lambda_function = PythonFunction(self, 'LocationsFunction',
entry='src/api',
index='locations.py',
handler='lambda_handler',
runtime=lmbd.Runtime.PYTHON_3_8,
memory_size=1024,
tracing=lmbd.Tracing.ACTIVE,
environment={
'LOCATIONS_TABLE': locations_table.table_name,
'AWS_EMF_NAMESPACE': self.stack_name,
'AWS_XRAY_TRACING_NAME': self.stack_name,
'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR'
}
)
locations_table.grant_read_write_data(locations_lambda_function)
resources_lambda_function = PythonFunction(self, 'ResourcesFunction',
entry='src/api',
index='resources.py',
handler='lambda_handler',
runtime=lmbd.Runtime.PYTHON_3_8,
memory_size=1024,
tracing=lmbd.Tracing.ACTIVE,
environment={
'RESOURCES_TABLE': resources_table.table_name,
'AWS_EMF_NAMESPACE': self.stack_name,
'AWS_XRAY_TRACING_NAME': self.stack_name,
'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR'
}
)
resources_table.grant_read_write_data(resources_lambda_function)
bookings_lambda_function = PythonFunction(self, 'BookingsFunction',
entry='src/api',
index='bookings.py',
handler='lambda_handler',
runtime=lmbd.Runtime.PYTHON_3_8,
memory_size=1024,
tracing=lmbd.Tracing.ACTIVE,
environment={
'BOOKINGS_TABLE': bookings_table.table_name,
'AWS_EMF_NAMESPACE': self.stack_name,
'AWS_XRAY_TRACING_NAME': self.stack_name,
'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR'
}
)
bookings_table.grant_read_write_data(bookings_lambda_function)
# Create API with proper authentication/authorization
cognito_stack_name_prefix = self.cognito_stack_name.replace('-', '')
authorizer_lambda_function = PythonFunction(self, 'APIAuthorizerFunction',
entry='src/api',
index='authorizer.py',
handler='lambda_handler',
runtime=lmbd.Runtime.PYTHON_3_8,
memory_size=1024,
tracing=lmbd.Tracing.ACTIVE,
environment={
'USER_POOL_ID': cdk.Fn.import_value(
cognito_stack_name_prefix + 'UserPool'),
'APPLICATION_CLIENT_ID': cdk.Fn.import_value(
cognito_stack_name_prefix + 'UserPoolClient'),
'ADMIN_GROUP_NAME': cdk.Fn.import_value(
cognito_stack_name_prefix + 'UserPoolAdminGroupName'),
'AWS_XRAY_TRACING_NAME': self.stack_name,
'AWS_XRAY_CONTEXT_MISSING': 'LOG_ERROR'
}
)
api_lambda_authorizer = HttpLambdaAuthorizer(authorizer_name='ApiLambdaAuthorizer',
handler=authorizer_lambda_function,
identity_source=['$request.header.Authorization'])
api_log_group = logs.LogGroup(self, 'ApiLogs', retention=logs.RetentionDays.ONE_MONTH)
api = httpapi.HttpApi(self, 'ServiceApi',
default_authorizer=api_lambda_authorizer,
cors_preflight={
'allow_headers': ['Content-Type', 'Authorization', 'X-Forwarded-For', 'X-Api-Key', 'X-Amz-Date', 'X-Amz-Security-Token'],
'allow_methods': [httpapi.CorsHttpMethod.GET, httpapi.CorsHttpMethod.PUT, httpapi.CorsHttpMethod.DELETE, httpapi.CorsHttpMethod.OPTIONS],
'allow_origins': ['*']
}
)
api.default_stage.node.default_child.AccessLogSettingsProperty = {
'destination_arn': api_log_group.log_group_arn,
'format': '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","routeKey":"$context.routeKey", "status":"$context.status","protocol":"$context.protocol", "integrationStatus": $context.integrationStatus, "integrationLatency": $context.integrationLatency, "responseLength":"$context.responseLength" }'
}
api.add_routes(path='/locations',
methods=[httpapi.HttpMethod.GET, httpapi.HttpMethod.PUT],
integration=LambdaProxyIntegration(handler=locations_lambda_function)
)
api.add_routes(path='/locations/{locationid}',
methods=[httpapi.HttpMethod.GET, httpapi.HttpMethod.DELETE],
integration=LambdaProxyIntegration(handler=locations_lambda_function)
)
api.add_routes(path='/locations/{locationid}/resources',
methods=[httpapi.HttpMethod.GET, httpapi.HttpMethod.PUT],
integration=LambdaProxyIntegration(handler=resources_lambda_function)
)
api.add_routes(path='/locations/{locationid}/resources/{resourceid}',
methods=[httpapi.HttpMethod.GET, httpapi.HttpMethod.DELETE],
integration=LambdaProxyIntegration(handler=resources_lambda_function)
)
api.add_routes(path='/locations/{locationid}/resources/{resourceid}/bookings',
methods=[httpapi.HttpMethod.GET],
integration=LambdaProxyIntegration(handler=bookings_lambda_function)
)
api.add_routes(path='/users/{userid}/bookings',
methods=[httpapi.HttpMethod.GET, httpapi.HttpMethod.PUT],
integration=LambdaProxyIntegration(handler=bookings_lambda_function)
)
api.add_routes(path='/users/{userid}/bookings/{bookingid}',
methods=[httpapi.HttpMethod.GET, httpapi.HttpMethod.DELETE],
integration=LambdaProxyIntegration(handler=bookings_lambda_function)
)
# Create alarms for the API and backend
alarms_topic = sns.Topic(self, 'AlarmsTopic')
api_errors_alarm = cwl.Alarm(self, 'HttpApiErrorsAlarm',
metric=cwl.Metric(namespace='AWS/ApiGateway',
metric_name='5XXError',
dimensions={'ApiName': api.api_id}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
api_errors_alarm.add_alarm_action(SnsAction(alarms_topic))
authorizer_lambda_errors_alarm = cwl.Alarm(self, 'AuthorizerFunctionErrorsAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': authorizer_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
authorizer_lambda_errors_alarm.add_alarm_action(SnsAction(alarms_topic))
authorizer_lambda_throttling_alarm = cwl.Alarm(self, 'AuthorizerFunctionThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': authorizer_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
authorizer_lambda_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
locations_lambda_errors_alarm = cwl.Alarm(self, 'LocationsFunctionErrorsAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': locations_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
locations_lambda_errors_alarm.add_alarm_action(SnsAction(alarms_topic))
locations_lambda_throttling_alarm = cwl.Alarm(self, 'LocationsFunctionThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': locations_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
locations_lambda_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
resources_lambda_errors_alarm = cwl.Alarm(self, 'ResourcesFunctionErrorsAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': resources_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
resources_lambda_errors_alarm.add_alarm_action(SnsAction(alarms_topic))
resources_lambda_throttling_alarm = cwl.Alarm(self, 'ResourcesFunctionThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': resources_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
resources_lambda_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
bookings_lambda_errors_alarm = cwl.Alarm(self, 'BookingsFunctionErrorsAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': bookings_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
bookings_lambda_errors_alarm.add_alarm_action(SnsAction(alarms_topic))
bookings_lambda_throttling_alarm = cwl.Alarm(self, 'BookingsFunctionThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': bookings_lambda_function.function_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
bookings_lambda_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
locations_table_throttling_alarm = cwl.Alarm(self, 'LocationsDynamoDBThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/DynamoDB',
metric_name='ThrottledRequests',
dimensions={
'FunctionName': locations_table.table_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
locations_table_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
resources_table_throttling_alarm = cwl.Alarm(self, 'ResourcesDynamoDBThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/DynamoDB',
metric_name='ThrottledRequests',
dimensions={
'FunctionName': resources_table.table_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
resources_table_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
bookings_table_throttling_alarm = cwl.Alarm(self, 'BookingsDynamoDBThrottlingAlarm',
metric=cwl.Metric(namespace='AWS/DynamoDB',
metric_name='ThrottledRequests',
dimensions={
'FunctionName': bookings_table.table_name}),
evaluation_periods=1,
period=cdk.Duration.minutes(1),
statistic='Sum',
threshold=1.0)
bookings_table_throttling_alarm.add_alarm_action(SnsAction(alarms_topic))
# Create dashboard
application_dashboard = cwl.Dashboard(self, 'ApplicationDashboard',
dashboard_name=self.stack_name + '-Dashboard')
application_dashboard.add_widgets(
cwl.GraphWidget(title='Business Metrics',
width=12,
height=6,
left=[
cwl.Metric(
namespace=self.stack_name,
metric_name='ProcessedBookings',
dimensions={
'ServiceName': bookings_lambda_function.function_name,
'LogGroup': bookings_lambda_function.function_name,
'ServiceType': 'AWS::Lambda::Function',
'Service': 'Bookings'
},
label='Processed Bookings',
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace=self.stack_name,
metric_name='ProcessedLocations',
dimensions={
'ServiceName': locations_lambda_function.function_name,
'LogGroup': locations_lambda_function.function_name,
'ServiceType': 'AWS::Lambda::Function',
'Service': 'Locations'
},
label='Processed Locations',
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace=self.stack_name,
metric_name='ProcessedResources',
dimensions={
'ServiceName': resources_lambda_function.function_name,
'LogGroup': resources_lambda_function.function_name,
'ServiceType': 'AWS::Lambda::Function',
'Service': 'Resources'
},
label='Processed Resources',
statistic='Sum',
period=cdk.Duration.minutes(1)
),
]
),
cwl.GraphWidget(title='API Gateway',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/ApiGateway',
metric_name='DataProcessed',
dimensions={'ApiId': api.api_id},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/ApiGateway',
metric_name='IntegrationLatency',
dimensions={'ApiId': api.api_id},
statistic='Average',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/ApiGateway',
metric_name='Latency',
dimensions={'ApiId': api.api_id},
statistic='Average',
period=cdk.Duration.minutes(1)
),
],
right=[
cwl.Metric(
namespace='AWS/ApiGateway',
metric_name='4xx',
dimensions={'ApiId': api.api_id},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/ApiGateway',
metric_name='5xx',
dimensions={'ApiId': api.api_id},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/ApiGateway',
metric_name='Count',
dimensions={'ApiId': api.api_id},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
]
)
)
application_dashboard.add_widgets(
cwl.GraphWidget(title='Locations Lambda',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Invocations',
dimensions={
'FunctionName': locations_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': locations_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': locations_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Duration',
dimensions={
'FunctionName': locations_lambda_function.function_name},
statistic='Average',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='ConcurrentExecutions',
dimensions={
'FunctionName': locations_lambda_function.function_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
)
]
),
cwl.GraphWidget(title='Resources Lambda',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Invocations',
dimensions={
'FunctionName': resources_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': resources_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': resources_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Duration',
dimensions={
'FunctionName': resources_lambda_function.function_name},
statistic='Average',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='ConcurrentExecutions',
dimensions={
'FunctionName': resources_lambda_function.function_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
)
]
),
cwl.GraphWidget(title='Bookings Lambda',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Invocations',
dimensions={
'FunctionName': bookings_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Errors',
dimensions={
'FunctionName': bookings_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Throttles',
dimensions={
'FunctionName': bookings_lambda_function.function_name},
statistic='Sum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='Duration',
dimensions={
'FunctionName': bookings_lambda_function.function_name},
statistic='Average',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/Lambda',
metric_name='ConcurrentExecutions',
dimensions={
'FunctionName': bookings_lambda_function.function_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
)
]
)
)
application_dashboard.add_widgets(
cwl.GraphWidget(title='DynamoDB - Locations',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ConsumedReadCapacityUnits',
dimensions={
'TableName': locations_table.table_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ConsumedWriteCapacityUnits',
dimensions={
'TableName': locations_table.table_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ProvisionedReadCapacityUnits',
dimensions={
'TableName': locations_table.table_name},
statistic='Average',
period=cdk.Duration.minutes(5)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ProvisionedWriteCapacityUnits',
dimensions={
'TableName': locations_table.table_name},
statistic='Average',
period=cdk.Duration.minutes(5)
)
]
),
cwl.GraphWidget(title='DynamoDB - Resources',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ConsumedReadCapacityUnits',
dimensions={
'TableName': resources_table.table_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ConsumedWriteCapacityUnits',
dimensions={
'TableName': resources_table.table_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ProvisionedReadCapacityUnits',
dimensions={
'TableName': resources_table.table_name},
statistic='Average',
period=cdk.Duration.minutes(5)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ProvisionedWriteCapacityUnits',
dimensions={
'TableName': resources_table.table_name},
statistic='Average',
period=cdk.Duration.minutes(5)
),
]
),
cwl.GraphWidget(title='DynamoDB - Bookings',
width=6,
height=6,
left=[
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ConsumedReadCapacityUnits',
dimensions={
'TableName': bookings_table.table_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ConsumedWriteCapacityUnits',
dimensions={
'TableName': bookings_table.table_name},
statistic='Maximum',
period=cdk.Duration.minutes(1)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ProvisionedReadCapacityUnits',
dimensions={
'TableName': bookings_table.table_name},
statistic='Average',
period=cdk.Duration.minutes(5)
),
cwl.Metric(
namespace='AWS/DynamoDB',
metric_name='ProvisionedWriteCapacityUnits',
dimensions={
'TableName': bookings_table.table_name},
statistic='Average',
period=cdk.Duration.minutes(5)
),
]
)
)
# Get stack outputs
cdk.CfnOutput(self, 'LocationsTableName', export_name=self.stack_name + '-LocationsTableName',
value=locations_table.table_name, description='DynamoDB Locations table')
cdk.CfnOutput(self, 'ResourcesTableName', export_name=self.stack_name + '-ResourcesTableName',
value=resources_table.table_name, description='DynamoDB Resources table')
cdk.CfnOutput(self, 'BookingsTableName', export_name=self.stack_name + '-BookingsTableName',
value=bookings_table.table_name, description='DynamoDB Bookings table')
cdk.CfnOutput(self, 'AlarmsTopicARN', export_name=self.stack_name + '-AlarmsTopicARN',
value=alarms_topic.topic_arn, description='SNS Topic to be used for the alarms subscriptions')
cdk.CfnOutput(self, 'AccessLogsGroupARN', export_name=self.stack_name + '-AccessLogsGroupARN',
value=api_log_group.log_group_arn,
description='CloudWatch Logs group for API Gateway access logs')
cdk.CfnOutput(self, 'DashboardURL', export_name=self.stack_name + '-DashboardURL',
value=f'https://console.aws.amazon.com/cloudwatch/home?region={self.region}#dashboards:name={application_dashboard.node.default_child.dashboard_name}',
description='Dashboard URL')
cdk.CfnOutput(self, 'APIEndpointURL', export_name=self.stack_name + '-APIEndpointURL', value=api.url,
description='API Gateway endpoint URL')
cdk.CfnOutput(self, 'ApiAuthCognitoStackName', export_name=self.stack_name + '-ApiAuthCognitoStackName',
value=self.cognito_stack_name, description='Cognito stack used by the API')