Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 0 additions & 61 deletions cdn_lambda/functions/map_to_s3.py

This file was deleted.

Empty file.
6 changes: 6 additions & 0 deletions cdn_lambda/functions/map_to_s3/map_to_s3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"table": {
"name": "exodus",
"region": "us-east-1"
}
}
90 changes: 90 additions & 0 deletions cdn_lambda/functions/map_to_s3/map_to_s3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import json
import logging
from datetime import datetime, timezone

import boto3

LOG = logging.getLogger("map-to-s3-lambda")


class LambdaClient(object):
def __init__(self, conf_file="map_to_s3.json"):
self._conf_file = conf_file

self._conf = None
self._db_client = None

@property
def conf(self):
if not self._conf:
with open(self._conf_file, "r") as json_file:
self._conf = json.load(json_file)

return self._conf

@property
def db_client(self):
if not self._db_client:
self._db_client = boto3.client(
"dynamodb", region_name=self.conf["table"]["region"]
)

return self._db_client

def handler(self, event, context):
# pylint: disable=unused-argument

request = event["Records"][0]["cf"]["request"]

LOG.info(
"Querying '%s' table for '%s'...",
self.conf["table"]["name"],
request["uri"],
)

query_result = self.db_client.query(
TableName=self.conf["table"]["name"],
Limit=1,
ScanIndexForward=False,
KeyConditionExpression="web_uri = :u and from_date <= :d",
ExpressionAttributeValues={
":u": {"S": request["uri"]},
":d": {
"S": str(
datetime.now(timezone.utc).isoformat(
timespec="milliseconds"
)
)
},
},
)

if query_result["Items"]:
LOG.info("Item found for '%s'", request["uri"])

try:
# Update request uri to point to S3 object key
request["uri"] = (
"/" + query_result["Items"][0]["object_key"]["S"]
)

return request
except Exception as err:
LOG.exception(
"Exception occurred while processing %s",
json.dumps(query_result["Items"][0]),
)

raise err
else:
LOG.info("No item found for '%s'", request["uri"])

# Report 404 to prevent attempts on S3
return {
"status": "404",
"statusDescription": "Not Found",
}


# Make handler available at module level
lambda_handler = LambdaClient().handler
24 changes: 11 additions & 13 deletions docs/functions/map_to_s3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,22 @@ Deployment
----------

This function may be deployed like any other AWS Lambda function using the
following event and environment variables.
following event and configuration file.

Event
^^^^^
The event for this function must be a CloudFront distribution origin-request to
an S3 bucket.

Environment Variables
^^^^^^^^^^^^^^^^^^^^^
- DB_TABLE_NAME
(Required)
Configuration
^^^^^^^^^^^^^
The map_to_s3 function must be deployed with the map_to_s3.json configuration
file.

The name of the DynamoDB table from which to derive mapping.
- DB_TABLE_REGION
(Optional)
- table
The DynamoDB table from which to derive path to key mapping.

The AWS region in which the table resides.
If omitted, the region used is that of the function.

`AWS Lambda Environment Variables
<https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html>`_
- name
The name of the DynamoDB table.
- region
The AWS region in which the table resides.
26 changes: 10 additions & 16 deletions tests/functions/test_map_to_s3.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from cdn_lambda.functions.map_to_s3 import lambda_handler
import os
from cdn_lambda.functions.map_to_s3.map_to_s3 import LambdaClient
import pytest
import mock
import json

TEST_PATH = "www.example.com/content/file.ext"
MOCKED_DT = "2020-02-17T15:38:05.864+00:00"
CONF_PATH = "cdn_lambda/functions/map_to_s3/map_to_s3.json"


@mock.patch("boto3.client")
@mock.patch("cdn_lambda.functions.map_to_s3.datetime")
@mock.patch("cdn_lambda.functions.map_to_s3.map_to_s3.datetime")
def test_map_to_s3(mocked_datetime, mocked_boto3_client):
mocked_datetime.now().isoformat.return_value = MOCKED_DT
mocked_boto3_client().query.return_value = {
Expand All @@ -22,32 +22,28 @@ def test_map_to_s3(mocked_datetime, mocked_boto3_client):
]
}

env_vars = {"DB_TABLE_NAME": "test_table", "DB_TABLE_REGION": "us-east-1"}
event = {"Records": [{"cf": {"request": {"uri": TEST_PATH}}}]}

with mock.patch.dict(os.environ, env_vars):
request = lambda_handler(event, context=None)
request = LambdaClient(conf_file=CONF_PATH).handler(event, context=None)

assert request == {"uri": "e4a3f2sum"}
assert request == {"uri": "/e4a3f2sum"}


@mock.patch("boto3.client")
@mock.patch("cdn_lambda.functions.map_to_s3.datetime")
@mock.patch("cdn_lambda.functions.map_to_s3.map_to_s3.datetime")
def test_map_to_s3_no_item(mocked_datetime, mocked_boto3_client):
mocked_datetime.now().isoformat.return_value = MOCKED_DT
mocked_boto3_client().query.return_value = {"Items": []}

env_vars = {"DB_TABLE_NAME": "test_table", "DB_TABLE_REGION": "us-east-1"}
event = {"Records": [{"cf": {"request": {"uri": TEST_PATH}}}]}

with mock.patch.dict(os.environ, env_vars):
request = lambda_handler(event, context=None)
request = LambdaClient(conf_file=CONF_PATH).handler(event, context=None)

assert request == {"status": "404", "statusDescription": "Not Found"}


@mock.patch("boto3.client")
@mock.patch("cdn_lambda.functions.map_to_s3.datetime")
@mock.patch("cdn_lambda.functions.map_to_s3.map_to_s3.datetime")
def test_map_to_s3_invalid_item(mocked_datetime, mocked_boto3_client, caplog):
mocked_datetime.now().isoformat.return_value = MOCKED_DT
mocked_boto3_client().query.return_value = {
Expand All @@ -59,12 +55,10 @@ def test_map_to_s3_invalid_item(mocked_datetime, mocked_boto3_client, caplog):
]
}

env_vars = {"DB_TABLE_NAME": "test_table", "DB_TABLE_REGION": "us-east-1"}
event = {"Records": [{"cf": {"request": {"uri": TEST_PATH}}}]}

with mock.patch.dict(os.environ, env_vars):
with pytest.raises(KeyError):
lambda_handler(event, context=None)
with pytest.raises(KeyError):
LambdaClient(conf_file=CONF_PATH).handler(event, context=None)

assert (
"Exception occurred while processing %s"
Expand Down