Skip to content

Commit

Permalink
feat: Add project files
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohamed El Mouctar HAIDARA committed Aug 9, 2021
1 parent ff942e7 commit 0dcc6bd
Show file tree
Hide file tree
Showing 42 changed files with 31,379 additions and 2 deletions.
38 changes: 38 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
### Terraform template
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log

# Exclude all .tfvars files, which are likely to contain sentitive data, such as
# password, private keys, and other secrets. These should not be part of version
# control as they are data points which are potentially sensitive and subject
# to change depending on the environment.
#
*.tfvars

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include override files you do wish to add to version control using negated pattern
#
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc

.idea
backend/lambda.zip
265 changes: 263 additions & 2 deletions README.md

Large diffs are not rendered by default.

152 changes: 152 additions & 0 deletions backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
locals {
users_raw = jsondecode(file("${path.root}/users.json"))
# change users list to a map of users suitable for Terraform for_each
users_map = { for u in local.users_raw : u["id"] => {
name = u["name"]
address = u["address"]
} }
}

# Package the lambda in a zip file
data "archive_file" "lambda_package" {
output_path = "${var.lambda_directory}/lambda.zip"
source_file = "${var.lambda_directory}/main.py"
type = "zip"
}

resource "aws_iam_role" "lambda_role" {
name = "${var.prefix}-api-backend"
description = "IAM Role for the API Backend"
assume_role_policy = data.aws_iam_policy_document.lambda_role_policy.json
}

data "aws_iam_policy_document" "lambda_role_policy" {
statement {
actions = ["sts:AssumeRole"]

principals {
type = "Service"
identifiers = ["lambda.amazonaws.com"]
}
}
}

resource "aws_iam_role_policy" "lambda_dynamodb_access" {
name = "dynamodb-access"
policy = data.aws_iam_policy_document.lambda_dynamodb_access.json
role = aws_iam_role.lambda_role.id
}

data "aws_iam_policy_document" "lambda_dynamodb_access" {
statement {
sid = "AllowAccessToDynamoDB"
actions = ["dynamodb:Scan"]
resources = [aws_dynamodb_table.users.arn]
}
}


resource "aws_iam_role_policy_attachment" "lambda_cloudwatch_access" {
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.lambda_role.id
}

resource "aws_iam_role_policy_attachment" "lambda_xray_access" {
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
role = aws_iam_role.lambda_role.id
}


resource "aws_lambda_function" "api_backend" {
function_name = "${var.prefix}-api-backend"
description = "API Backend for the DevOps challenge project"
role = aws_iam_role.lambda_role.arn
handler = "main.handler"
runtime = "python3.8"
timeout = 29
filename = data.archive_file.lambda_package.output_path
source_code_hash = filebase64sha256(data.archive_file.lambda_package.output_path)

tracing_config {
mode = "Active"
}

environment {
variables = {
DYNAMODB_TABLE = aws_dynamodb_table.users.name
}
}
}

resource "aws_cloudwatch_log_group" "log_group" {
name = "/aws/lambda/${aws_lambda_function.api_backend.function_name}"
retention_in_days = 14
}

resource "aws_dynamodb_table" "users" {
name = "${var.prefix}-users"
billing_mode = "PROVISIONED"
hash_key = "id"
read_capacity = 3
write_capacity = 3


attribute {
name = "id"
type = "S"
}
}

resource "aws_dynamodb_table_item" "users" {
for_each = local.users_map
table_name = aws_dynamodb_table.users.name
hash_key = aws_dynamodb_table.users.hash_key
range_key = aws_dynamodb_table.users.range_key

item = <<ITEM
{
"id": {"S": "${each.key}"},
"name": {"S": "${each.value["name"]}"},
"address": {"S": "${each.value["address"]}"}
}
ITEM
}

resource "aws_apigatewayv2_api" "http_api" {
name = "${var.prefix}-http-api"
description = "A simple HTTP API for the devops challenge"
protocol_type = "HTTP"

cors_configuration {
allow_origins = ["*"]
allow_methods = ["GET", "HEAD"]
}
}

resource "aws_lambda_permission" "allow_apigateway_to_invoke_lambda" {
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.api_backend.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_apigatewayv2_api.http_api.execution_arn}/*"
}

resource "aws_apigatewayv2_integration" "lambda_integration" {
api_id = aws_apigatewayv2_api.http_api.id
integration_type = "AWS_PROXY"
integration_method = "POST"
integration_uri = aws_lambda_function.api_backend.invoke_arn
passthrough_behavior = "WHEN_NO_MATCH"
}

resource "aws_apigatewayv2_stage" "default_stage" {
api_id = aws_apigatewayv2_api.http_api.id
auto_deploy = true
name = "$default"
}

resource "aws_apigatewayv2_route" "users" {
api_id = aws_apigatewayv2_api.http_api.id
route_key = "GET /users"
target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
}
11 changes: 11 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Backend Lambda function

A lambda scanning a return the content of a dynamodb table. It used as an integration target for an HTTP API.

**Note: It is NOT a best practice to scan all the content of dynamodb table. You should consider adding some index or
use another database if you need to such kind of access to your data.**

## Requirements

- An environment variable `DYNAMODB_TABLE` containing the name of the dynamodb table
- An IAM role granting `dynamodb:Scan` to this table
44 changes: 44 additions & 0 deletions backend/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import json
import logging
import os
from typing import List, Dict

import boto3

# Init these resources here to take advantage of lambda context re-utilization.
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table(os.environ["DYNAMODB_TABLE"])

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)


def get_users(db_table) -> List[Dict]:
"""
Get all users
Note: It is NOT a best practice to scan all the content of dynamodb table. You should consider adding some index
or use another database if you need to such kind of access to your data.
:param db_table:
:return:
"""
scan = db_table.scan() # Bad practice
return [item for item in scan["Items"]]


def handler(event, context):
logger.debug(event)
users = json.dumps(get_users(table))
logger.info(f"{len(users)} user(s) retrieved.")

return {
"statusCode": "200",
'headers': {
'Content-Type': 'application/json',
},
"body": users
}


# For testing purpose
if __name__ == '__main__':
print(handler({}, None))
Loading

0 comments on commit 0dcc6bd

Please sign in to comment.