Skip to content

Commit

Permalink
Added endpoint to the API for universal spin table (#199)
Browse files Browse the repository at this point in the history
* Added endpoint to the API for universal spin table
* Added tests for APILambda
  • Loading branch information
tech3371 authored Dec 5, 2023
1 parent dafdb26 commit 0492806
Show file tree
Hide file tree
Showing 12 changed files with 243 additions and 71 deletions.
19 changes: 1 addition & 18 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ optional = true
opensearch-py = "^2.1.1"
requests = "^2.28.2"
requests-aws4auth = "^1.2.3"
urllib3 = "<2.0.0"

# Used for installing with pip
[tool.poetry.extras]
Expand Down
8 changes: 8 additions & 0 deletions sds_data_manager/lambda_code/SDSCode/spin_table_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import json


def lambda_handler(event, context):
print(event)
# TODO: extend this lambda code once we finish creating
# spin table schema
return {"statusCode": 200, "body": json.dumps("Hello from Spin Table Lambda!")}
6 changes: 3 additions & 3 deletions sds_data_manager/lambda_code/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,6 @@ requests==2.31.0 ; python_version >= "3.9" and python_version < "4" \
six==1.16.0 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
--hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
urllib3==2.0.7 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84 \
--hash=sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e
urllib3==1.26.18 ; python_version >= "3.9" and python_version < "4" \
--hash=sha256:34b97092d7e0a3a8cf7cd10e386f401b3737364026c45e622aa02903dffe0f07 \
--hash=sha256:f8ecc1bba5667413457c529ab955bf8c67b45db799d159066261719e328580a0
114 changes: 95 additions & 19 deletions sds_data_manager/stacks/api_gateway_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@
An example of the format of the url:
https://api.prod.imap-mission.com/query
"""
from pathlib import Path
from typing import Optional

from aws_cdk import Duration, Stack, aws_sns
from aws_cdk import aws_apigateway as apigw
from aws_cdk import aws_cloudwatch as cloudwatch
from aws_cdk import aws_cloudwatch_actions as cloudwatch_actions
from aws_cdk import aws_ec2 as ec2
from aws_cdk import aws_lambda as lambda_
from aws_cdk import aws_lambda_python_alpha as lambda_alpha_
from aws_cdk import aws_route53 as route53
from aws_cdk import aws_route53_targets as targets
from aws_cdk import aws_secretsmanager as secrets
from constructs import Construct

from sds_data_manager.stacks.domain_stack import DomainStack
Expand All @@ -22,7 +29,6 @@ def __init__(
self,
scope: Construct,
construct_id: str,
lambda_functions: dict,
domain_stack: DomainStack = None,
**kwargs,
) -> None:
Expand All @@ -34,16 +40,11 @@ def __init__(
Parent construct.
construct_id : str
A unique string identifier for this construct.
lambda_functions : dict
Lambda functions
domain_stack : DomainStack, Optional
Custom domain, hosted zone, and certificate
"""
super().__init__(scope, construct_id, **kwargs)

# Define routes
routes = lambda_functions.keys()

# Create a single API Gateway
self.api = apigw.RestApi(
self,
Expand Down Expand Up @@ -82,19 +83,6 @@ def __init__(
),
)

# Loop through the lambda functions to create resources (routes)
for route in routes:
# Get the lambda function and its HTTP method
lambda_info = lambda_functions[route]
lambda_fn = lambda_info["function"]
http_method = lambda_info["httpMethod"]

# Define the API Gateway Resources
resource = self.api.root.add_resource(route)

# Create a new method that is linked to the Lambda function
resource.add_method(http_method, apigw.LambdaIntegration(lambda_fn))

def deliver_to_sns(self, sns_topic: aws_sns.Topic):
"""Deliver API Gateway alerts to an SNS topic.
Expand Down Expand Up @@ -137,3 +125,91 @@ def deliver_to_sns(self, sns_topic: aws_sns.Topic):
)
# Send notification to the SNS Topic
cloudwatch_alarm.add_alarm_action(cloudwatch_actions.SnsAction(sns_topic))

def add_route(
self, route: str, http_method: str, lambda_function: lambda_.Function
):
"""Add a route to the API Gateway.
Parameters
----------
route : str
Route name. Eg. /download, /query, /upload, etc.
http_method : str
HTTP method. Eg. GET, POST, etc.
lambda_function : lambda_.Function
Lambda function to trigger when this route is hit.
"""
# Define the API Gateway Resources
resource = self.api.root.add_resource(route)

# Create a new method that is linked to the Lambda function
resource.add_method(http_method, apigw.LambdaIntegration(lambda_function))


class APILambda(Stack):
"""Generic Stack to create API handler Lambda."""

def __init__(
self,
scope: Construct,
construct_id: str,
lambda_name: str,
code_path: Path,
lambda_handler: str,
timeout: Duration,
rds_security_group: ec2.SecurityGroup,
db_secret_name: str,
vpc: ec2.Vpc,
environment: Optional[dict] = None,
**kwargs,
):
"""
Lambda Constructor.
Parameters
----------
scope : Construct
Parent construct.
construct_id : str
A unique string identifier for this construct.
lambda_name : str
Lambda name
code_path : Path
Path to the Lambda code directory
lambda_handler : str
Lambda handler's function name
timeout : Duration
Lambda timeout
rds_security_group : ec2.SecurityGroup
RDS security group
db_secret_name : str
RDS secret name for secret manager access
vpc : ec2.Vpc
VPC into which to put the resources that require networking.
environment: dict
Lambda's environment variables.
"""

super().__init__(scope, construct_id, **kwargs)

self.lambda_function = lambda_alpha_.PythonFunction(
self,
id=lambda_name,
function_name=lambda_name,
entry=str(code_path.parent), # This gives folder path
index=str(code_path.name), # This gives file name
handler=lambda_handler, # This points to function inside the file
runtime=lambda_.Runtime.PYTHON_3_11,
timeout=timeout,
memory_size=512,
environment=environment,
vpc=vpc,
security_groups=[rds_security_group],
allow_public_subnet=True,
)

rds_secret = secrets.Secret.from_secret_name_v2(
self, "rds_secret", db_secret_name
)
rds_secret.grant_read(grantee=self.lambda_function)
8 changes: 8 additions & 0 deletions sds_data_manager/stacks/database_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ def __init__(
)

# Secrets manager credentials
# NOTE:
# If credentials already exists, then we will need to delete
# the secret before recreating it.
#
# Use this command with <> value replaced:
# aws --profile <aws profile> secretsmanager delete-secret \
# --secret-id <secret name> \
# --force-delete-without-recovery
rds_creds = rds.DatabaseSecret(
self, "RdsCredentials", secret_name=self.secret_name, username=username
)
Expand Down
8 changes: 7 additions & 1 deletion sds_data_manager/stacks/domain_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@


class DomainStack(Stack):
"""Acquires hosted_zone and certificate"""
"""Acquires hosted_zone and certificate
NOTE: Please make sure domain_name is registered
in AWS account. This step is manual. And
follow rest of manual setup steps documented in
<doc path>.
"""

def __init__(
self,
Expand Down
1 change: 1 addition & 0 deletions sds_data_manager/stacks/ecr_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def __init__(
self.container_repo.grant_pull_push(ecr_authenticators)

# Add each of the SDC devs to the newly created group
# TODO: should we remove this?
for username in self.node.try_get_context("usernames"):
user = iam.User.from_user_name(self, username, user_name=username)
ecr_authenticators.add_user(user)
Expand Down
28 changes: 23 additions & 5 deletions sds_data_manager/stacks/sds_data_manager_stack.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
)
from constructs import Construct

from .api_gateway_stack import ApiGateway

# Local
from .dynamodb_stack import DynamoDB
from .opensearch_stack import OpenSearch
Expand All @@ -42,6 +44,7 @@ def __init__(
construct_id: str,
opensearch: OpenSearch,
dynamodb_stack: DynamoDB,
api: ApiGateway,
env: cdk.Environment,
**kwargs,
) -> None:
Expand All @@ -58,6 +61,9 @@ def __init__(
dynamodb_stack: DynamoDb
This class depends on dynamodb_stack, which is built with
opensearch_stack.py
api: ApiGateway
This class has created API resources. This function uses it to add
route that points to targe Lambda.
"""
super().__init__(scope, construct_id, env=env, **kwargs)
# Get the current account number so we can use it in the bucket names
Expand Down Expand Up @@ -327,6 +333,12 @@ def __init__(
upload_api_lambda.add_to_role_policy(s3_read_policy)
upload_api_lambda.apply_removal_policy(cdk.RemovalPolicy.DESTROY)

api.add_route(
route="upload",
http_method="GET",
lambda_function=upload_api_lambda,
)

# query API lambda
query_api_lambda = lambda_alpha_.PythonFunction(
self,
Expand All @@ -351,6 +363,12 @@ def __init__(
)
query_api_lambda.add_to_role_policy(opensearch.opensearch_read_only_policy)

api.add_route(
route="query",
http_method="GET",
lambda_function=query_api_lambda,
)

opensearch_secret.grant_read(grantee=query_api_lambda)

# download query API lambda
Expand All @@ -371,8 +389,8 @@ def __init__(
)
download_query_api.add_to_role_policy(s3_read_policy)

self.lambda_functions = {
"upload": {"function": upload_api_lambda, "httpMethod": "GET"},
"query": {"function": query_api_lambda, "httpMethod": "GET"},
"download": {"function": download_query_api, "httpMethod": "GET"},
}
api.add_route(
route="download",
http_method="GET",
lambda_function=download_query_api,
)
Loading

0 comments on commit 0492806

Please sign in to comment.