Skip to content

Commit

Permalink
ART: fn with dyndb
Browse files Browse the repository at this point in the history
  • Loading branch information
rehanhaider committed Jan 4, 2025
1 parent c93e13b commit 47da79d
Show file tree
Hide file tree
Showing 4 changed files with 262 additions and 22 deletions.
2 changes: 1 addition & 1 deletion content/aws/50001030-cdk-fn-lambda-python-deps.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ from aws_cdk import (
Stack,
)
# πŸ‘‡πŸ½ import the python_alpha module
from aws_cdk import aws_lambda_python_alpha as _lambda
from aws_cdk import aws_lambda_python_alpha as python

from constructs import Construct

Expand Down
50 changes: 29 additions & 21 deletions content/aws/50001060-cdk-fn-s3-permission.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,12 @@ pip install aws-cdk.aws-lambda-python-alpha

```python
#filename: cdk_app/lambda_stack.py
# filename: cdk_app/my_stack.py
from aws_cdk import (
Stack,
aws_s3 as s3,
aws_lambda_python_alpha as lambda_, # πŸ‘ˆπŸ½ import the PythonFunction module
aws_lambda_python_alpha as python_lambda,
aws_lambda as lambda_,
RemovalPolicy,
)
from constructs import Construct
Expand All @@ -86,24 +88,24 @@ class MyStack(Stack):
self,
"MyBucket",
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True,
)

# πŸ‘‡πŸ½ create a lambda function
fn = lambda_.PythonFunction(

# πŸ‘‡πŸ½ Create a Lambda function
fn = python_lambda.PythonFunction(
self,
id="MyFunction",
"MyS3Function",
entry="cdk_app/fn",
runtime=lambda_.Runtime.PYTHON_3_12,
index="index.py",
handler="handler",
runtime=lambda_.Runtime.PYTHON_3_12,
# πŸ‘‡πŸ½ pass the bucket name to the lambda function
environment={"BUCKET_NAME": bucket.bucket_name},
environment={"BUCKET_NAME": bucket.bucket_name},
)


```

**Step 3**: Now we the Lambda function code:
**Step 3**: Now we write the Lambda function code:

```python
# filename: cdk_app/fn/index.py
Expand All @@ -130,10 +132,12 @@ boto3
Finally, we need to grant the Lambda function permissions to access the S3 bucket.

```python
# filename: cdk_app/my_stack.py
from aws_cdk import (
Stack,
aws_s3 as s3,
aws_lambda_python_alpha as lambda_,
aws_lambda_python_alpha as python_lambda,
aws_lambda as lambda_,
RemovalPolicy,
)
from constructs import Construct
Expand All @@ -147,22 +151,21 @@ class MyStack(Stack):
self,
"MyBucket",
removal_policy=RemovalPolicy.DESTROY,
auto_delete_objects=True,
)

# πŸ‘‡πŸ½ create a lambda function
fn = lambda_.PythonFunction(
fn = python_lambda.PythonFunction(
self,
id="MyFunction",
"MyS3Function",
entry="cdk_app/fn",
runtime=lambda_.Runtime.PYTHON_3_12,
index="index.py",
handler="handler",
runtime=lambda_.Runtime.PYTHON_3_12,
environment={"BUCKET_NAME": bucket.bucket_name},
)

# πŸ‘‡πŸ½ grant the lambda function access to the bucket
# πŸ‘‡πŸ½ Grant the Lambda function access to the S3 bucket
bucket.grant_read_write_data(fn)

```

In the above code, we use the `grant_read_write_data` method to grant the Lambda function read and write access to the S3 bucket.
Expand All @@ -178,28 +181,33 @@ Now that we have granted the Lambda function permissions to access the S3 bucket
```python
# filename: cdk_app/fn/index.py

import os
import boto3
import requests
import json

s3 = boto3.client("s3")


def handler(event, context):
# πŸ‘‡πŸ½ get the bucket name from the environment variables
bucket_name = event["BUCKET_NAME"]
bucket_name = os.environ["BUCKET_NAME"]

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

# πŸ‘‡πŸ½ write the response to a file in the bucket
s3.put_object(Bucket=bucket_name, Key="todos-1.json", Body=response.json())
# Convert the JSON response to a string before storing
json_string = json.dumps(response.json())
s3.put_object(Bucket=bucket_name, Key="todos-1.json", Body=json_string)

# πŸ‘‡πŸ½ list all the objects in the bucket
response = s3.list_objects_v2(Bucket=bucket_name)

# πŸ‘‡πŸ½ read the file and send the contexts as response
# πŸ‘‡πŸ½ read the file and parse the contents as JSON
stored_response = s3.get_object(Bucket=bucket_name, Key="todos-1.json")
stored_data = json.loads(stored_response["Body"].read().decode("utf-8"))


return {"statusCode": 200, "body": stored_response}
return {"statusCode": 200, "body": json.dumps(stored_data)}
```

In the above code, we use the `boto3` library to interact with the S3 bucket. We first write the response from the API to a file in the bucket and then read the file and send the contents as a response. This method can be modified based on your exact use case.
232 changes: 232 additions & 0 deletions content/aws/50001070-cdk-fn-dynamodb-permission.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
Title: Granting Lambda function permission to access DynamoDB using AWS CDK in Python
Date: 2025-01-03
Category: AWS Academy
Series: AWS CDK
series_index: 1070
Tags: aws, cdk, python
Author: Rehan Haider
Summary: Learn how to grant Lambda permissions to access DynamoDB using AWS CDK in Python
Keywords: lambda, cdk, s3, python, aws


Part of the serverless design pattern is to have a Lambda function that interacts with a DynamoDB table. The DynamoDB can be access only through APIs using appropriate credentials. In this article, we will look at how to grant a Lambda function permission to access a DynamoDB table using AWS CDK in Python.


## Prerequisites

1. Ensure that you have [AWS CDK and SAM CLI installed]({filename}00000100-cdk-installing-cdk-sam-cli.md).
2. If needed [create a new CDK application]({filename}50000020-cdk-new-app.md).


## Granting Lambda function permissions to access DynamoDB

We will need to do the following:

1. Create an DynamoDB table and insert some data.
2. Create a Lambda function.
3. Grant the Lambda function permissions to access the DynamoDB table.

### 1. Create an DynamoDB table

First, let's create an DynamoDB table in the stack.

```python
# filename: cdk_app/my_stack.py
from aws_cdk import (
Stack,
aws_dynamodb as dynamodb,
RemovalPolicy,
)
from constructs import Construct


class MyStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)

# πŸ‘‡πŸ½ Create a DynamoDB table
table = dynamodb.Table(
self,
"MyTable",
partition_key={"name": "pk", "type": dynamodb.AttributeType.STRING},
sort_key={"name": "sk", "type": dynamodb.AttributeType.STRING},
)

```

### 2. Create a Lambda function

We can create a simple Lambda function using any of the methods we have discussed in the previous posts. For this example we will use the CDK provided `PythonFunction` feature [that allows us to specify the python dependencies in AWS Lambda with ease]({filename}50001030-cdk-fn-lambda-python-deps.md).

**Step 1**: First we install the `aws-cdk.aws-lambda-python-alpha` module:

```bash
pip install aws-cdk.aws-lambda-python-alpha
```

**Step 2**: Then we create a Lambda function. We can also pass the name of the bucket to Lambda through the environment variables.

```python
# filename: cdk_app/my_stack.py
from aws_cdk import (
Stack,
aws_dynamodb as dynamodb,
aws_lambda_python_alpha as python_lambda, # πŸ‘ˆπŸ½ This is the python lambda construct
aws_lambda as lambda_, # πŸ‘ˆπŸ½ This is needed for runtime
RemovalPolicy,
)
from constructs import Construct


class MyStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)

table = dynamodb.Table(
self,
"MyTable",
partition_key={"name": "pk", "type": dynamodb.AttributeType.STRING},
sort_key={"name": "sk", "type": dynamodb.AttributeType.STRING},
removal_policy=RemovalPolicy.DESTROY,
)

# πŸ‘‡πŸ½ Create a Lambda function
fn = python_lambda.PythonFunction(
self,
"MyDynamoDBFunction",
entry="cdk_app/fn",
runtime=lambda_.Runtime.PYTHON_3_12,
index="index.py",
handler="handler",
)
```

**Step 3**: Now we write the Lambda function code:

```python
# filename: cdk_app/fn/index.py

import requests

def handler(event, context):
response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

return {"statusCode": 200, "body": response.json()}
```


**Step 4**: Add the Python dependencies in the `cdk_app/fn/requirements.txt` file:

```txt
requests
boto3
```


### 3. Grant the Lambda function permissions to access the S3 bucket

Finally, we need to grant the Lambda function necessary permissions to access the DynamoDB table.

```python
# filename: cdk_app/my_stack.py
from aws_cdk import (
Stack,
aws_dynamodb as dynamodb,
aws_lambda_python_alpha as python_lambda,
aws_lambda as lambda_,
RemovalPolicy,
)
from constructs import Construct


class MyStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)

table = dynamodb.Table(
self,
"MyTable",
partition_key={"name": "pk", "type": dynamodb.AttributeType.STRING},
sort_key={"name": "sk", "type": dynamodb.AttributeType.STRING},
removal_policy=RemovalPolicy.DESTROY,
)

fn = python_lambda.PythonFunction(
self,
"MyDynamoDBFunction",
entry="cdk_app/fn",
runtime=lambda_.Runtime.PYTHON_3_12,
index="index.py",
handler="handler",
)

# πŸ‘‡πŸ½ Grant the Lambda function access to the DynamoDB table
table.grant_read_write_data(fn)
```

In the above code, we use the `grant_read_write_data` method to grant the Lambda function read and write access to the DynamoDB table.

Alternatively, you can use the `grant_read` or `grant_write` methods to grant only read or write access respectively.


## Accessing DynamoDB table using the Lambda function


Now that we have granted the Lambda function permissions to access the DynamoDB table, we can read and write data to the table. To do so, we need to use the `boto3` library to interact with the DynamoDB table.

```python
# filename: cdk_app/fn/index.py

import os
import boto3
import requests

dynamodb = boto3.resource("dynamodb")
# πŸ‘‡πŸ½ get the table name from the environment variables
table_name = os.environ["TABLE_NAME"]
table = dynamodb.Table(table_name)

def handler(event, context):

response = requests.get("https://jsonplaceholder.typicode.com/todos/1")

# πŸ‘‡πŸ½ write the response to a file in the bucket
table.put_item(Key={"pk": "TODO", "sk": "1"}, Item={"data": response.json()})

# πŸ‘‡πŸ½ read the file and send the contexts as response
stored_response = table.get_item(Key={"pk": "TODO", "sk": "1"})


return {"statusCode": 200, "body": stored_response}
```

In the above code, we use the `boto3` library to interact with the DynamoDB table. We first write the response from the API to a file in the bucket and then read the file and send the contents as a response. This method can be modified based on your exact use case.


## Testing the Lambda function

To test the Lambda function we need its name. We can get the name of the lambda function from the stack output.

First list the stack outputs:

```bash
function_name=$(aws lambda list-functions --query "Functions[?contains(FunctionName, 'MyDynamoDBFunction')].[FunctionName]" --output text)
```

Then invoke the lambda function:

```bash
aws lambda invoke \
--function-name $function_name \
--cli-binary-format raw-in-base64-out \
--payload '{ "key": "value" }' \
/dev/stdout | jq
```

!!! note
The `jq` command is used to format the JSON output. If you don't have it installed, you can install it by running `brew install jq` on macOS or `apt-get install jq` on Linux.


This will invoke the Lambda function and print the response in the terminal.

![invoke-lambda-function]({static}/images/aws/50001070-01-lambda-response.png)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 47da79d

Please sign in to comment.