Skip to content

Commit 0dcc6bd

Browse files
author
Mohamed El Mouctar HAIDARA
committed
feat: Add project files
1 parent ff942e7 commit 0dcc6bd

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+31379
-2
lines changed

.gitignore

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
### Terraform template
2+
# Local .terraform directories
3+
**/.terraform/*
4+
5+
# .tfstate files
6+
*.tfstate
7+
*.tfstate.*
8+
9+
# Crash log files
10+
crash.log
11+
12+
# Exclude all .tfvars files, which are likely to contain sentitive data, such as
13+
# password, private keys, and other secrets. These should not be part of version
14+
# control as they are data points which are potentially sensitive and subject
15+
# to change depending on the environment.
16+
#
17+
*.tfvars
18+
19+
# Ignore override files as they are usually used to override resources locally and so
20+
# are not checked in
21+
override.tf
22+
override.tf.json
23+
*_override.tf
24+
*_override.tf.json
25+
26+
# Include override files you do wish to add to version control using negated pattern
27+
#
28+
# !example_override.tf
29+
30+
# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
31+
# example: *tfplan*
32+
33+
# Ignore CLI configuration files
34+
.terraformrc
35+
terraform.rc
36+
37+
.idea
38+
backend/lambda.zip

README.md

Lines changed: 263 additions & 2 deletions
Large diffs are not rendered by default.

backend.tf

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
locals {
2+
users_raw = jsondecode(file("${path.root}/users.json"))
3+
# change users list to a map of users suitable for Terraform for_each
4+
users_map = { for u in local.users_raw : u["id"] => {
5+
name = u["name"]
6+
address = u["address"]
7+
} }
8+
}
9+
10+
# Package the lambda in a zip file
11+
data "archive_file" "lambda_package" {
12+
output_path = "${var.lambda_directory}/lambda.zip"
13+
source_file = "${var.lambda_directory}/main.py"
14+
type = "zip"
15+
}
16+
17+
resource "aws_iam_role" "lambda_role" {
18+
name = "${var.prefix}-api-backend"
19+
description = "IAM Role for the API Backend"
20+
assume_role_policy = data.aws_iam_policy_document.lambda_role_policy.json
21+
}
22+
23+
data "aws_iam_policy_document" "lambda_role_policy" {
24+
statement {
25+
actions = ["sts:AssumeRole"]
26+
27+
principals {
28+
type = "Service"
29+
identifiers = ["lambda.amazonaws.com"]
30+
}
31+
}
32+
}
33+
34+
resource "aws_iam_role_policy" "lambda_dynamodb_access" {
35+
name = "dynamodb-access"
36+
policy = data.aws_iam_policy_document.lambda_dynamodb_access.json
37+
role = aws_iam_role.lambda_role.id
38+
}
39+
40+
data "aws_iam_policy_document" "lambda_dynamodb_access" {
41+
statement {
42+
sid = "AllowAccessToDynamoDB"
43+
actions = ["dynamodb:Scan"]
44+
resources = [aws_dynamodb_table.users.arn]
45+
}
46+
}
47+
48+
49+
resource "aws_iam_role_policy_attachment" "lambda_cloudwatch_access" {
50+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
51+
role = aws_iam_role.lambda_role.id
52+
}
53+
54+
resource "aws_iam_role_policy_attachment" "lambda_xray_access" {
55+
policy_arn = "arn:aws:iam::aws:policy/AWSXRayDaemonWriteAccess"
56+
role = aws_iam_role.lambda_role.id
57+
}
58+
59+
60+
resource "aws_lambda_function" "api_backend" {
61+
function_name = "${var.prefix}-api-backend"
62+
description = "API Backend for the DevOps challenge project"
63+
role = aws_iam_role.lambda_role.arn
64+
handler = "main.handler"
65+
runtime = "python3.8"
66+
timeout = 29
67+
filename = data.archive_file.lambda_package.output_path
68+
source_code_hash = filebase64sha256(data.archive_file.lambda_package.output_path)
69+
70+
tracing_config {
71+
mode = "Active"
72+
}
73+
74+
environment {
75+
variables = {
76+
DYNAMODB_TABLE = aws_dynamodb_table.users.name
77+
}
78+
}
79+
}
80+
81+
resource "aws_cloudwatch_log_group" "log_group" {
82+
name = "/aws/lambda/${aws_lambda_function.api_backend.function_name}"
83+
retention_in_days = 14
84+
}
85+
86+
resource "aws_dynamodb_table" "users" {
87+
name = "${var.prefix}-users"
88+
billing_mode = "PROVISIONED"
89+
hash_key = "id"
90+
read_capacity = 3
91+
write_capacity = 3
92+
93+
94+
attribute {
95+
name = "id"
96+
type = "S"
97+
}
98+
}
99+
100+
resource "aws_dynamodb_table_item" "users" {
101+
for_each = local.users_map
102+
table_name = aws_dynamodb_table.users.name
103+
hash_key = aws_dynamodb_table.users.hash_key
104+
range_key = aws_dynamodb_table.users.range_key
105+
106+
item = <<ITEM
107+
{
108+
"id": {"S": "${each.key}"},
109+
"name": {"S": "${each.value["name"]}"},
110+
"address": {"S": "${each.value["address"]}"}
111+
}
112+
113+
ITEM
114+
}
115+
116+
resource "aws_apigatewayv2_api" "http_api" {
117+
name = "${var.prefix}-http-api"
118+
description = "A simple HTTP API for the devops challenge"
119+
protocol_type = "HTTP"
120+
121+
cors_configuration {
122+
allow_origins = ["*"]
123+
allow_methods = ["GET", "HEAD"]
124+
}
125+
}
126+
127+
resource "aws_lambda_permission" "allow_apigateway_to_invoke_lambda" {
128+
action = "lambda:InvokeFunction"
129+
function_name = aws_lambda_function.api_backend.function_name
130+
principal = "apigateway.amazonaws.com"
131+
source_arn = "${aws_apigatewayv2_api.http_api.execution_arn}/*"
132+
}
133+
134+
resource "aws_apigatewayv2_integration" "lambda_integration" {
135+
api_id = aws_apigatewayv2_api.http_api.id
136+
integration_type = "AWS_PROXY"
137+
integration_method = "POST"
138+
integration_uri = aws_lambda_function.api_backend.invoke_arn
139+
passthrough_behavior = "WHEN_NO_MATCH"
140+
}
141+
142+
resource "aws_apigatewayv2_stage" "default_stage" {
143+
api_id = aws_apigatewayv2_api.http_api.id
144+
auto_deploy = true
145+
name = "$default"
146+
}
147+
148+
resource "aws_apigatewayv2_route" "users" {
149+
api_id = aws_apigatewayv2_api.http_api.id
150+
route_key = "GET /users"
151+
target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
152+
}

backend/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Backend Lambda function
2+
3+
A lambda scanning a return the content of a dynamodb table. It used as an integration target for an HTTP API.
4+
5+
**Note: It is NOT a best practice to scan all the content of dynamodb table. You should consider adding some index or
6+
use another database if you need to such kind of access to your data.**
7+
8+
## Requirements
9+
10+
- An environment variable `DYNAMODB_TABLE` containing the name of the dynamodb table
11+
- An IAM role granting `dynamodb:Scan` to this table

backend/main.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import json
2+
import logging
3+
import os
4+
from typing import List, Dict
5+
6+
import boto3
7+
8+
# Init these resources here to take advantage of lambda context re-utilization.
9+
dynamodb = boto3.resource("dynamodb")
10+
table = dynamodb.Table(os.environ["DYNAMODB_TABLE"])
11+
12+
logger = logging.getLogger(__name__)
13+
logger.setLevel(logging.DEBUG)
14+
15+
16+
def get_users(db_table) -> List[Dict]:
17+
"""
18+
Get all users
19+
Note: It is NOT a best practice to scan all the content of dynamodb table. You should consider adding some index
20+
or use another database if you need to such kind of access to your data.
21+
:param db_table:
22+
:return:
23+
"""
24+
scan = db_table.scan() # Bad practice
25+
return [item for item in scan["Items"]]
26+
27+
28+
def handler(event, context):
29+
logger.debug(event)
30+
users = json.dumps(get_users(table))
31+
logger.info(f"{len(users)} user(s) retrieved.")
32+
33+
return {
34+
"statusCode": "200",
35+
'headers': {
36+
'Content-Type': 'application/json',
37+
},
38+
"body": users
39+
}
40+
41+
42+
# For testing purpose
43+
if __name__ == '__main__':
44+
print(handler({}, None))

0 commit comments

Comments
 (0)