Source code for rootski_backend_cdk.lambda_rest_api.stacks.lambda_rest_api
"""Stack defining an API Gateway mapping to a Lambda function with the FastAPI app."""fromenumimportEnumfrompathlibimportPathimportaws_cdkascdkfromaws_cdkimportaws_apigatewayasapi_gatewayfromaws_cdkimportaws_certificatemanagerascertificatemanagerfromaws_cdkimportaws_iamasiamfromaws_cdkimportaws_lambdaaslambda_fromaws_cdkimportaws_route53asroute53fromaws_cdkimportaws_route53_targetsasroute53_targetsfromaws_cdkimportaws_s3ass3fromconstructsimportConstructAPI_SUBDOMAIN="api.rootski.io"THIS_DIR=Path(__file__).parent# ROOTSKI_LAMBDA_CODE_DIR = THIS_DIR / "resources"ROOTSKI_LAMBDA_CODE_DIR=THIS_DIR/"../../../../../../../rootski_api"# Must be the same version as in the fast api root pathROOTSKI_API_VERSION="v1"
[docs]classStackOutputs(str,Enum):"""Stack output keys for the :py:class:`RootskiLambdaRestApiStack`."""# pylint: disable=invalid-nameapi_gateway_url="ApiGatewayUrl"subdomain="ApiSubdomain"
[docs]classRootskiLambdaRestApiStack(cdk.Stack):"""An API Gateway mapping to a Lambda function with the backend code inside."""def__init__(self,scope:Construct,construct_id:str,**kwargs,):super().__init__(scope,construct_id,**kwargs)#: lambda function containing the rootski FastAPI application codeself.fast_api_function:lambda_.Function=self.make_fast_api_function()#: API Gateway that proxies all incoming requests to the fast_api_functionself.lambda_rest_api=api_gateway.LambdaRestApi(self,"Rootski-Lambda-REST-API",handler=self.fast_api_function,description="Proxy to the Rootski FastAPI backend 'lambda-lith'.",domain_name=api_gateway.DomainNameOptions(certificate=certificatemanager.DnsValidatedCertificate(self,id="Rootski-Lambda-DNS-Validated-ACM-Cert",domain_name=API_SUBDOMAIN,hosted_zone=route53.HostedZone.from_lookup(self,id="Rootski-IO-Hosted-Zone",domain_name="rootski.io"),),domain_name=API_SUBDOMAIN,),)#: DNS rule routing the ``API_SUBDOMAIN`` to the rootski API Gatewayself.rootski_api_subdomain=route53.ARecord(self,id="Rootski-IO-API-Gateway-A-Record",zone=route53.HostedZone.from_lookup(self,id="Rootski-IO-API-Gateway-HostedZone",domain_name="rootski.io",),target=route53.RecordTarget.from_alias(route53_targets.ApiGateway(self.lambda_rest_api)),record_name=API_SUBDOMAIN,)#: VPC Endpoint allowing the lambda function to access AWS SSM without leaving the VPC# self.ssm_vpc_endpoint = SsmVpcEndpoint(# self,# "SSM-VPC-Endpoint"# )cdk.CfnOutput(self,id=StackOutputs.subdomain.value,value=API_SUBDOMAIN,description=f"Map {self.lambda_rest_api} to the URL of the API Gateway",export_name=StackOutputs.subdomain.value,)cdk.CfnOutput(scope=self,value=self.lambda_rest_api.url,description="ARN of bucket for database backups.",id=StackOutputs.api_gateway_url.value,export_name=StackOutputs.api_gateway_url.value,)
[docs]defmake_fast_api_function(self)->lambda_.Function:""" Create a lambda function with the FastAPI app. To prepare the python depencies for the lambda function, this stack will essentially run the following command: .. code:: bash docker run \ --rm \ -v "path/to/rootski_api:/assets_input" \ -v "path/to/cdk.out/asset.<some hash>:/assets_output" \ lambci/lambda:build-python3.8 \ /bin/bash -c "... several commands to install the requirements to /assets_output ..." The reason for using docker to install the requirements is because the "lambci/lambda:build-pythonX.X" image uses the same underlying operating system as is used in the real AWS Lambda runtime. This means that python packages that rely on compiled C/C++ binaries will be compiled correctly for the AWS Lambda runtime. If we did not do it this way, packages such as pandas, numpy, psycopg2-binary, asyncpg, sqlalchemy, and others relying on C/C++ bindings would not work when uploaded to lambda. We use the ``lambci/*`` images instead of the images maintained by AWS CDK because the AWS CDK images were failing to correctly install C/C++ based python packages. An extra benefit of using ``lambci/*`` over the AWS CDK images is that the ``lambci/*`` images are in docker hub so they can be pulled without doing any sort of ``docker login`` command before executing this script. The AWS CDK images are stored in public.ecr.aws which requires a ``docker login`` command to be run first. """# create a bucket to cache the morphemes.json objectmorphemes_json_bucket=s3.Bucket(self,id="Rootski-Backend-Cache-Bucket",removal_policy=cdk.RemovalPolicy.DESTROY,)fast_api_function=lambda_.Function(self,"Rootski-FastAPI-Lambda",timeout=cdk.Duration.seconds(30),memory_size=512,runtime=lambda_.Runtime.PYTHON_3_8,handler="index.handler",code=lambda_.Code.from_asset(path=str(ROOTSKI_LAMBDA_CODE_DIR),bundling=cdk.BundlingOptions(# learn about this here:# https://docs.aws.amazon.com/cdk/api/v1/python/aws_cdk.aws_lambda/README.html#bundling-asset-code# Using this lambci image makes it so that dependencies with C-binaries compile correctly for the lambda runtime.# The AWS CDK python images were not doing this. Relevant dependencies are: pandas, asyncpg, and psycogp2-binary.image=cdk.DockerImage.from_registry(image="lambci/lambda:build-python3.8"),command=["bash","-c","mkdir -p /asset-output"+"&& pip install -r ./aws-lambda/requirements.txt -t /asset-output"+"&& pip install . -t /asset-output"+"&& cp -r ./src/rootski/resources /asset-output/rootski/resources/"# TODO: Check that this works+"&& cp aws-lambda/index.py /asset-output"+"&& rm -rf /asset-output/boto3 /asset-output/botocore",],),),environment={"ROOTSKI__FETCH_VALUES_FROM_AWS_SSM":"true","ROOTSKI__ENVIRONMENT":"prod",# /tmp is the only writable location in the lambda file system"ROOTSKI__STATIC_ASSETS_DIR":"/tmp","ROOTSKI__OBJECT_CACHE_BUCKET_NAME":morphemes_json_bucket.bucket_name,},)# allow lambda to access the Dynamodb rootski-dynamodb-tablefast_api_function.role.attach_inline_policy(policy=iam.Policy(self,id="Allow-Lambda-Full-Access-To-DynamoDB",statements=[iam.PolicyStatement(effect=iam.Effect.ALLOW,resources=["arn:aws:dynamodb:{region}:{account}:table/rootski*".format(region=cdk.Stack.of(self).region,account=cdk.Stack.of(self).account,)],actions=["dynamodb:GetItem","dynamodb:Query","dynamodb:BatchGetItem","dynamodb:PutItem","dynamodb:CreateBackup",],)],))morphemes_json_bucket.grant_read_write(fast_api_function)# allow lambda to perform GetParametersByPath operation on the /rootski* parametersfast_api_function.role.attach_inline_policy(policy=iam.Policy(self,id="Allow-Lambda-Access-To-SSM-Params",statements=[iam.PolicyStatement(effect=iam.Effect.ALLOW,resources=["arn:aws:ssm:{region}:{account}:parameter/rootski*".format(region=cdk.Stack.of(self).region,account=cdk.Stack.of(self).account,)],actions=["ssm:Get*"],)],))fast_api_function.role.add_managed_policy(policy=iam.ManagedPolicy.from_managed_policy_arn(self,id="Allow-Lambda-To-Connect-To-Lightsail-VPC-via-VPC-Peering",managed_policy_arn="arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",))returnfast_api_function