From 26d74d304314714fa942ef163934af2d9ec57967 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Thu, 11 Dec 2025 20:05:25 +0100 Subject: [PATCH 01/20] Created a hello lambda funtion and test the funtion locally --- hello.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 hello.py diff --git a/hello.py b/hello.py new file mode 100644 index 0000000..3426cbd --- /dev/null +++ b/hello.py @@ -0,0 +1,15 @@ +# Hello lambda function +def lambda_handler(event, context): + name = event.get("name", "World") + message = f"Hello, {name}!" + + return { + "statusCode": 200, + "body": message + } + +# Run the funtion locally for testing +if __name__ == "__main__": + test_event = {"name": "Emmanuel Ogah"} + result = lambda_handler(test_event, None) + print(result) \ No newline at end of file From 39bfe57c5ba24469aaf4b43583f67d5b344fe776 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Thu, 11 Dec 2025 20:10:23 +0100 Subject: [PATCH 02/20] Added the Pyhton hello lambda funtion and run step to README file --- README.md | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e323310..da41edd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,29 @@ -# Serverless-Health-Check-API-with-CI-CD -Serverless Health Check API with CI/CD +# Serverless Health Check API with CI/CD + +create hello lambda funtion using Python + +``` +# Hello lambda function +def lambda_handler(event, context): + name = event.get("name", "World") + message = f"Hello, {name}!" + + return { + "statusCode": 200, + "body": message + } + +# Run the funtion locally for testing +if __name__ == "__main__": + test_event = {"name": "Emmanuel Ogah"} + result = lambda_handler(test_event, None) + print(result) +``` + +Run the python funtion + +``` +# Output + +{'statusCode': 200, 'body': 'Hello, Emmanuel Ogah!'}" +``` From 8210762f9edf3127d8c13fe6ed4a9986bfa15340 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Thu, 11 Dec 2025 21:24:32 +0100 Subject: [PATCH 03/20] Created the repository folder structure --- .gitignore | 0 README.md | 3 ++- hello.py => lambda/hello.py | 0 terraform/apigw.tf | 0 terraform/iam.tf | 0 terraform/lambda.tf | 0 terraform/main.tf | 0 terraform/outputs.tf | 0 terraform/prod.tfvars | 0 terraform/provider.tf | 0 terraform/staging.tfvars | 0 terraform/variables.tf | 0 tests/local_runner.py | 0 13 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .gitignore rename hello.py => lambda/hello.py (100%) create mode 100644 terraform/apigw.tf create mode 100644 terraform/iam.tf create mode 100644 terraform/lambda.tf create mode 100644 terraform/main.tf create mode 100644 terraform/outputs.tf create mode 100644 terraform/prod.tfvars create mode 100644 terraform/provider.tf create mode 100644 terraform/staging.tfvars create mode 100644 terraform/variables.tf create mode 100644 tests/local_runner.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index da41edd..21a6024 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ if __name__ == "__main__": print(result) ``` -Run the python funtion +Run the python funtion locally using VS Code Run Button +- Click the “Run Python File” button in the top right corner. ``` # Output diff --git a/hello.py b/lambda/hello.py similarity index 100% rename from hello.py rename to lambda/hello.py diff --git a/terraform/apigw.tf b/terraform/apigw.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/iam.tf b/terraform/iam.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/lambda.tf b/terraform/lambda.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/main.tf b/terraform/main.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/outputs.tf b/terraform/outputs.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/prod.tfvars b/terraform/prod.tfvars new file mode 100644 index 0000000..e69de29 diff --git a/terraform/provider.tf b/terraform/provider.tf new file mode 100644 index 0000000..e69de29 diff --git a/terraform/staging.tfvars b/terraform/staging.tfvars new file mode 100644 index 0000000..e69de29 diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 0000000..e69de29 diff --git a/tests/local_runner.py b/tests/local_runner.py new file mode 100644 index 0000000..e69de29 From a3023c26415e9812bfa4fa4022696fbbcccb540c Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Thu, 11 Dec 2025 21:41:56 +0100 Subject: [PATCH 04/20] Added terraform provider --- terraform/provider.tf | 12 ++++++++++++ terraform/variables.tf | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/terraform/provider.tf b/terraform/provider.tf index e69de29..3e3bddb 100644 --- a/terraform/provider.tf +++ b/terraform/provider.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf index e69de29..d3157d2 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -0,0 +1,5 @@ +variable "aws_region" { + default = "us-east-1" + description = "AWS region" + type = string +} \ No newline at end of file From cf2d372733267b4e3c37d715b51940b9d3a3d550 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Thu, 11 Dec 2025 21:54:38 +0100 Subject: [PATCH 05/20] Added required version to the terraform provider --- .gitignore | 14 ++++++++++++++ terraform/provider.tf | 1 + 2 files changed, 15 insertions(+) diff --git a/.gitignore b/.gitignore index e69de29..0d9015b 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1,14 @@ +.terraform +*.tfstate +*.tfstate.backup +.terraform.tfstate.lock.info +crash.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terragrunt-cache +.terragrunt-tfstate-backups +.terraform.lock.hcl +.terraformrc +.terraform.* \ No newline at end of file diff --git a/terraform/provider.tf b/terraform/provider.tf index 3e3bddb..e4abf53 100644 --- a/terraform/provider.tf +++ b/terraform/provider.tf @@ -5,6 +5,7 @@ terraform { version = "~> 5.0" } } + required_version = ">= 1.2.0" } provider "aws" { From 48b3032ad32d8c76035eac30473df14eaa3e4c10 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 15:12:13 +0100 Subject: [PATCH 06/20] Install boto3, and import needed dependencies --- README.md | 4 ++++ lambda/hello.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/README.md b/README.md index 21a6024..bacfaf6 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,7 @@ Run the python funtion locally using VS Code Run Button {'statusCode': 200, 'body': 'Hello, Emmanuel Ogah!'}" ``` + +- Create the terraform folder structure + +- imp \ No newline at end of file diff --git a/lambda/hello.py b/lambda/hello.py index 3426cbd..20c06b7 100644 --- a/lambda/hello.py +++ b/lambda/hello.py @@ -1,3 +1,10 @@ +import json +import logging +import boto3 +import os +import uuid +from datetime import datetime + # Hello lambda function def lambda_handler(event, context): name = event.get("name", "World") From b049233c3977c63821d3991052da695ed462994b Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 15:27:09 +0100 Subject: [PATCH 07/20] Created the Logged event details to DynamoDB --- lambda/hello.py | 48 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/lambda/hello.py b/lambda/hello.py index 20c06b7..43a7b84 100644 --- a/lambda/hello.py +++ b/lambda/hello.py @@ -1,22 +1,46 @@ +import uuid import json -import logging import boto3 import os -import uuid +import logging from datetime import datetime -# Hello lambda function + +TABLE_NAME = os.environ.get("REQUESTS_TABLE", "unknown-table") + +dynamodb = boto3.resource("dynamodb") +table = dynamodb.Table(TABLE_NAME) + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + def lambda_handler(event, context): - name = event.get("name", "World") - message = f"Hello, {name}!" + # Incoming event logging + logger.info("Received event: %s", json.dumps(event)) + + # Create request id + request_id = str(uuid.uuid4()) + item = { + "id": request_id, + "timestamp": datetime.now().isoformat() + "Z", + "event": event + } + + # Save request to DynamoDB + try: + table.put_item(Item=item) + logger.info("Saved item %s to %s", request_id, TABLE_NAME) + except Exception as e: + logger.exception("Failed to write to DynamoDB: %s", e) + return { + "statusCode": 500, + "body": json.dumps({"status": "error", "message": "Failed saving request."}), + "headers": {"Content-Type": "application/json"} + } + # Return success return { "statusCode": 200, - "body": message + "body": json.dumps({"status": "healthy", "message": "Request processed and saved.", "id": request_id}), + "headers": {"Content-Type": "application/json"} } - -# Run the funtion locally for testing -if __name__ == "__main__": - test_event = {"name": "Emmanuel Ogah"} - result = lambda_handler(test_event, None) - print(result) \ No newline at end of file From 03a0d267f31c4a3cdfa0061c528eb385722a261d Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 15:35:51 +0100 Subject: [PATCH 08/20] Modified the terraform module structure --- terraform/apigw.tf | 0 terraform/{provider.tf => backend.tfvars} | 0 terraform/iam.tf | 0 terraform/lambda.tf | 0 tests/local_runner.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 terraform/apigw.tf rename terraform/{provider.tf => backend.tfvars} (100%) delete mode 100644 terraform/iam.tf delete mode 100644 terraform/lambda.tf delete mode 100644 tests/local_runner.py diff --git a/terraform/apigw.tf b/terraform/apigw.tf deleted file mode 100644 index e69de29..0000000 diff --git a/terraform/provider.tf b/terraform/backend.tfvars similarity index 100% rename from terraform/provider.tf rename to terraform/backend.tfvars diff --git a/terraform/iam.tf b/terraform/iam.tf deleted file mode 100644 index e69de29..0000000 diff --git a/terraform/lambda.tf b/terraform/lambda.tf deleted file mode 100644 index e69de29..0000000 diff --git a/tests/local_runner.py b/tests/local_runner.py deleted file mode 100644 index e69de29..0000000 From 96224145854e986d51f39328559f09c627e0a312 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 16:02:53 +0100 Subject: [PATCH 09/20] Added terraform provider --- terraform/main.tf | 33 +++++++++++++++++++++++++++++++++ terraform/variables.tf | 26 ++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/terraform/main.tf b/terraform/main.tf index e69de29..bf1344d 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -0,0 +1,33 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + backend "s3" { + bucket = var.terraform_backend_bucket + key = "${var.project_name}/${var.environment}/tfstate" + region = var.aws_region + } +} + +provider "aws" { + region = var.aws_region + + default_tags { + tags = local.common_tags + } +} + +locals { + common_tags = { + Environment = var.environment + Project = var.project_name + ManagedBy = "Terraform" + CreatedAt = timestamp() + } +} diff --git a/terraform/variables.tf b/terraform/variables.tf index d3157d2..21c7478 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -2,4 +2,30 @@ variable "aws_region" { default = "us-east-1" description = "AWS region" type = string +} + +variable "terraform_backend_bucket" { + default = "" + description = "AWS Bucket for Terraform backend" + type = string +} + +variable "environment" { + validation { + condition = contains(["staging", "prod"], var.environment) + error_message = "Environment must be either staging or prod" + } + description = "Deployment environment name (staging or prod)" + type = string +} + +variable "lambda_funtion_dir" { + default = "../lambda" + description = "Directory containing Lambda function source code" + type = string +} +variable "project_name" { + default = "serverless-health-check-api" + description = "Project name for tagging and resource naming" + type = string } \ No newline at end of file From 776affd88fd78350510341839b1689d7c045569f Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 16:10:37 +0100 Subject: [PATCH 10/20] Added AWS S3 bucket for storing tfstate --- terraform/variables.tf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/terraform/variables.tf b/terraform/variables.tf index 21c7478..05269a4 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -5,8 +5,8 @@ variable "aws_region" { } variable "terraform_backend_bucket" { - default = "" - description = "AWS Bucket for Terraform backend" + default = "serverlesshealthcheckapi" + description = "AWS S3 Bucket for Terraform backend" type = string } From 83480d52fa5cb02bf531b0a31f6c79591f431ba3 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 17:04:16 +0100 Subject: [PATCH 11/20] Created the aws dynamodb request --- terraform/backend.tfvars | 18 +++++------------- terraform/main.tf | 13 +++++++------ terraform/modules/dynamodb/main.tf | 17 +++++++++++++++++ terraform/modules/dynamodb/outputs.tf | 9 +++++++++ terraform/modules/dynamodb/variables.tf | 9 +++++++++ 5 files changed, 47 insertions(+), 19 deletions(-) create mode 100644 terraform/modules/dynamodb/main.tf create mode 100644 terraform/modules/dynamodb/outputs.tf create mode 100644 terraform/modules/dynamodb/variables.tf diff --git a/terraform/backend.tfvars b/terraform/backend.tfvars index e4abf53..71aaf90 100644 --- a/terraform/backend.tfvars +++ b/terraform/backend.tfvars @@ -1,13 +1,5 @@ -terraform { - required_providers { - aws = { - source = "hashicorp/aws" - version = "~> 5.0" - } - } - required_version = ">= 1.2.0" -} - -provider "aws" { - region = "us-east-1" -} \ No newline at end of file +# bucket = "serverlesshealthcheckapi" +# key = "health-check-app/terraform.tfstate" +# region = "us-east-1" +# encrypt = true +# dynamodb_table = "terraform-locks" \ No newline at end of file diff --git a/terraform/main.tf b/terraform/main.tf index bf1344d..9bcce38 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -7,12 +7,6 @@ terraform { version = "~> 5.0" } } - - backend "s3" { - bucket = var.terraform_backend_bucket - key = "${var.project_name}/${var.environment}/tfstate" - region = var.aws_region - } } provider "aws" { @@ -31,3 +25,10 @@ locals { CreatedAt = timestamp() } } + +# DynamoDB Module +module "dynamodb" { + source = "./modules/dynamodb" + environment = var.environment + common_tags = local.common_tags +} \ No newline at end of file diff --git a/terraform/modules/dynamodb/main.tf b/terraform/modules/dynamodb/main.tf new file mode 100644 index 0000000..b606585 --- /dev/null +++ b/terraform/modules/dynamodb/main.tf @@ -0,0 +1,17 @@ +resource "aws_dynamodb_table" "requests" { + name = "${var.environment}-requests-db" + billing_mode = "PAY_PER_REQUEST" + hash_key = "request_id" + + attribute { + name = "request_id" + type = "S" + } + + ttl { + attribute_name = "expiration_time" + enabled = true + } + + tags = var.common_tags +} diff --git a/terraform/modules/dynamodb/outputs.tf b/terraform/modules/dynamodb/outputs.tf new file mode 100644 index 0000000..0d5bc73 --- /dev/null +++ b/terraform/modules/dynamodb/outputs.tf @@ -0,0 +1,9 @@ +output "table_name" { + description = "Name of the DynamoDB" + value = aws_dynamodb_table.requests.name +} + +output "table_arn" { + description = "ARN of the DynamoDB" + value = aws_dynamodb_table.requests.arn +} \ No newline at end of file diff --git a/terraform/modules/dynamodb/variables.tf b/terraform/modules/dynamodb/variables.tf new file mode 100644 index 0000000..705b670 --- /dev/null +++ b/terraform/modules/dynamodb/variables.tf @@ -0,0 +1,9 @@ +variable "environment" { + description = "Environment name" + type = string +} + +variable "common_tags" { + description = "Common tags for all resources" + type = map(string) +} From c175f881b8389da8dfb7f850c9a821fc607cb317 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 17:16:22 +0100 Subject: [PATCH 12/20] Added the iam role and policy --- terraform/modules/iam/main.tf | 58 ++++++++++++++++++++++++++++++ terraform/modules/iam/outputs.tf | 9 +++++ terraform/modules/iam/variables.tf | 14 ++++++++ 3 files changed, 81 insertions(+) create mode 100644 terraform/modules/iam/main.tf create mode 100644 terraform/modules/iam/outputs.tf create mode 100644 terraform/modules/iam/variables.tf diff --git a/terraform/modules/iam/main.tf b/terraform/modules/iam/main.tf new file mode 100644 index 0000000..f837f7a --- /dev/null +++ b/terraform/modules/iam/main.tf @@ -0,0 +1,58 @@ +# IAM Role for Lambda function +resource "aws_iam_role" "lambda_role" { + name = "${var.environment}-serverless-health-check-api-lambda-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) + + tags = var.common_tags +} + +# IAM Policy for CloudWatch Logs +resource "aws_iam_role_policy" "lambda_cloudwatch_policy" { + name = "${var.environment}-serverless-health-check-api-lambda-cloudwatch-policy" + role = aws_iam_role.lambda_role.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Resource = "arn:aws:logs:*:*:*" + } + ] + }) +} + +# IAM Policy for DynamoDB +resource "aws_iam_role_policy" "lambda_dynamodb_policy" { + name = "${var.environment}-serverless-health-check-api-lambda-dynamodb-policy" + role = aws_iam_role.lambda_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = [ + "dynamodb:PutItem" + ] + Resource = var.dynamodb_table_arn + } + ] + }) +} diff --git a/terraform/modules/iam/outputs.tf b/terraform/modules/iam/outputs.tf new file mode 100644 index 0000000..b63be17 --- /dev/null +++ b/terraform/modules/iam/outputs.tf @@ -0,0 +1,9 @@ +output "lambda_role_arn" { + description = "ARN of the Lambda execution role" + value = aws_iam_role.lambda_role.arn +} + +output "lambda_role_name" { + description = "Name of the Lambda execution role" + value = aws_iam_role.lambda_role.name +} diff --git a/terraform/modules/iam/variables.tf b/terraform/modules/iam/variables.tf new file mode 100644 index 0000000..a688f79 --- /dev/null +++ b/terraform/modules/iam/variables.tf @@ -0,0 +1,14 @@ +variable "environment" { + description = "Environment name" + type = string +} + +variable "dynamodb_table_arn" { + description = "ARN of the DynamoDB" + type = string +} + +variable "common_tags" { + description = "Common tags for all resources" + type = map(string) +} From 0d6d9b60f8a93f5b005fe081bd22386cf19d7bb3 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 18:20:19 +0100 Subject: [PATCH 13/20] Added API Gateway module --- terraform/main.tf | 18 +++++++ terraform/modules/api-gateway/main.tf | 62 ++++++++++++++++++++++ terraform/modules/api-gateway/outputs.tf | 14 +++++ terraform/modules/api-gateway/variables.tf | 14 +++++ 4 files changed, 108 insertions(+) create mode 100644 terraform/modules/api-gateway/main.tf create mode 100644 terraform/modules/api-gateway/outputs.tf create mode 100644 terraform/modules/api-gateway/variables.tf diff --git a/terraform/main.tf b/terraform/main.tf index 9bcce38..aeb1d5b 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -31,4 +31,22 @@ module "dynamodb" { source = "./modules/dynamodb" environment = var.environment common_tags = local.common_tags +} + +# IAM Module +module "iam" { + source = "./modules/iam" + + environment = var.environment + dynamodb_table_arn = module.dynamodb.table_arn + common_tags = local.common_tags +} + +# API Gateway Module +module "api_gateway" { + source = "./modules/api_gateway" + + environment = var.environment + lambda_invoke_arn = module.lambda.function_invoke_arn + common_tags = local.common_tags } \ No newline at end of file diff --git a/terraform/modules/api-gateway/main.tf b/terraform/modules/api-gateway/main.tf new file mode 100644 index 0000000..8f2568d --- /dev/null +++ b/terraform/modules/api-gateway/main.tf @@ -0,0 +1,62 @@ +resource "aws_apigatewayv2_api" "health_api" { + name = "${var.environment}-health-check-api" + protocol_type = "HTTP" + + cors_configuration { + allow_origins = ["*"] + allow_methods = ["GET", "POST", "OPTIONS"] + allow_headers = ["*"] + } + + tags = var.common_tags +} + +resource "aws_apigatewayv2_integration" "lambda_integration" { + api_id = aws_apigatewayv2_api.health_api.id + integration_type = "AWS_PROXY" + integration_method = "POST" + payload_format_version = "2.0" + integration_uri = var.lambda_invoke_arn + depends_on = [] +} + +resource "aws_apigatewayv2_route" "health_route_get" { + api_id = aws_apigatewayv2_api.health_api.id + route_key = "GET /health" + target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" +} + +resource "aws_apigatewayv2_route" "health_route_post" { + api_id = aws_apigatewayv2_api.health_api.id + route_key = "POST /health" + target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}" +} + +resource "aws_apigatewayv2_stage" "default" { + api_id = aws_apigatewayv2_api.health_api.id + name = "$default" + auto_deploy = true + + access_log_settings { + destination_arn = aws_cloudwatch_log_group.api_gateway_logs.arn + format = jsonencode({ + requestId = "$context.requestId" + ip = "$context.identity.sourceIp" + requestTime = "$context.requestTime" + httpMethod = "$context.httpMethod" + resourcePath = "$context.resourcePath" + status = "$context.status" + protocol = "$context.protocol" + responseLength = "$context.responseLength" + }) + } + + tags = var.common_tags +} + +resource "aws_cloudwatch_log_group" "api_gateway_logs" { + name = "/aws/apigateway/${var.environment}-health-check-api" + retention_in_days = 7 + + tags = var.common_tags +} diff --git a/terraform/modules/api-gateway/outputs.tf b/terraform/modules/api-gateway/outputs.tf new file mode 100644 index 0000000..fc6da1c --- /dev/null +++ b/terraform/modules/api-gateway/outputs.tf @@ -0,0 +1,14 @@ +output "api_endpoint" { + description = "The endpoint URL of the API Gateway" + value = "${aws_apigatewayv2_api.health_api.api_endpoint}/" +} + +output "api_id" { + description = "The ID of the API Gateway" + value = aws_apigatewayv2_api.health_api.id +} + +output "execution_arn" { + description = "The execution ARN of the API Gateway" + value = aws_apigatewayv2_api.health_api.execution_arn +} diff --git a/terraform/modules/api-gateway/variables.tf b/terraform/modules/api-gateway/variables.tf new file mode 100644 index 0000000..af84d72 --- /dev/null +++ b/terraform/modules/api-gateway/variables.tf @@ -0,0 +1,14 @@ +variable "environment" { + description = "Environment name" + type = string +} + +variable "lambda_invoke_arn" { + description = "Invoke ARN of the Lambda function" + type = string +} + +variable "common_tags" { + description = "Common tags for all resources" + type = map(string) +} From 9474e0da498a809e54fca89f8c6cff6ede0f42e3 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 18:53:10 +0100 Subject: [PATCH 14/20] Added lambda module --- terraform/main.tf | 12 ++++++++++ terraform/modules/lambda/main.tf | 33 +++++++++++++++++++++++++++ terraform/modules/lambda/outputs.tf | 14 ++++++++++++ terraform/modules/lambda/variables.tf | 29 +++++++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 terraform/modules/lambda/main.tf create mode 100644 terraform/modules/lambda/outputs.tf create mode 100644 terraform/modules/lambda/variables.tf diff --git a/terraform/main.tf b/terraform/main.tf index aeb1d5b..536b31f 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -49,4 +49,16 @@ module "api_gateway" { environment = var.environment lambda_invoke_arn = module.lambda.function_invoke_arn common_tags = local.common_tags +} + +# Lambda Module +module "lambda" { + source = "./modules/lambda" + environment = var.environment + lambda_role_arn = module.iam.lambda_role_arn + dynamodb_table_name = module.dynamodb.table_name + api_gateway_execution_arn = module.api_gateway.execution_arn + lambda_funtion_dir = var.lambda_funtion_dir + common_tags = local.common_tags + depends_on = [module.api_gateway] } \ No newline at end of file diff --git a/terraform/modules/lambda/main.tf b/terraform/modules/lambda/main.tf new file mode 100644 index 0000000..7746bb2 --- /dev/null +++ b/terraform/modules/lambda/main.tf @@ -0,0 +1,33 @@ +data "archive_file" "lambda_zip" { + type = "zip" + source_dir = var.lambda_funtion_dir + output_path = "${path.module}/${var.environment}_lambda_function.zip" +} + +resource "aws_lambda_function" "health_check" { + function_name = "${var.environment}-health-check-function" + role = var.lambda_role_arn + handler = "lambda_function.lambda_handler" + source_code_hash = data.archive_file.lambda_zip.output_base64sha256 + runtime = "python3.11" + filename = data.archive_file.lambda_zip.output_path + + environment { + variables = { + DYNAMODB_TABLE_NAME = var.dynamodb_table_name + ENVIRONMENT = var.environment + } + } + + tags = var.common_tags + + depends_on = [data.archive_file.lambda_zip] +} + +resource "aws_lambda_permission" "api_gateway" { + statement_id = "AllowAPIGatewayInvoke" + action = "lambda:InvokeFunction" + function_name = aws_lambda_function.health_check.function_name + principal = "apigateway.amazonaws.com" + source_arn = "${var.api_gateway_execution_arn}/*/*" +} diff --git a/terraform/modules/lambda/outputs.tf b/terraform/modules/lambda/outputs.tf new file mode 100644 index 0000000..c8b0bb7 --- /dev/null +++ b/terraform/modules/lambda/outputs.tf @@ -0,0 +1,14 @@ +output "function_arn" { + description = "ARN of the Lambda function" + value = aws_lambda_function.health_check.arn +} + +output "function_name" { + description = "Name of the Lambda function" + value = aws_lambda_function.health_check.function_name +} + +output "function_invoke_arn" { + description = "Invoke ARN of the Lambda function" + value = aws_lambda_function.health_check.invoke_arn +} diff --git a/terraform/modules/lambda/variables.tf b/terraform/modules/lambda/variables.tf new file mode 100644 index 0000000..b595507 --- /dev/null +++ b/terraform/modules/lambda/variables.tf @@ -0,0 +1,29 @@ +variable "environment" { + description = "Environment name" + type = string +} + +variable "lambda_role_arn" { + description = "ARN of the IAM role for Lambda" + type = string +} + +variable "dynamodb_table_name" { + description = "Name of the DynamoDB table" + type = string +} + +variable "api_gateway_execution_arn" { + description = "Execution ARN of the API Gateway" + type = string +} + +variable "lambda_funtion_dir" { + description = "Path to the Lambda function source code directory" + type = string +} + +variable "common_tags" { + description = "Common tags for all resources" + type = map(string) +} From 3c482577362e390c34ca612aca49aaf4be23f8ea Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 20:41:19 +0100 Subject: [PATCH 15/20] Run the application locally and tested --- terraform/main.tf | 8 ++++---- .../lambda/staging_lambda_function.zip | Bin 0 -> 745 bytes terraform/outputs.tf | 19 ++++++++++++++++++ terraform/prod.tfvars | 3 +++ terraform/staging.tfvars | 3 +++ 5 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 terraform/modules/lambda/staging_lambda_function.zip diff --git a/terraform/main.tf b/terraform/main.tf index 536b31f..c729e40 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -44,8 +44,7 @@ module "iam" { # API Gateway Module module "api_gateway" { - source = "./modules/api_gateway" - + source = "./modules/api-gateway" environment = var.environment lambda_invoke_arn = module.lambda.function_invoke_arn common_tags = local.common_tags @@ -60,5 +59,6 @@ module "lambda" { api_gateway_execution_arn = module.api_gateway.execution_arn lambda_funtion_dir = var.lambda_funtion_dir common_tags = local.common_tags - depends_on = [module.api_gateway] -} \ No newline at end of file + # depends_on = [module.api_gateway] +} + diff --git a/terraform/modules/lambda/staging_lambda_function.zip b/terraform/modules/lambda/staging_lambda_function.zip new file mode 100644 index 0000000000000000000000000000000000000000..6304432ddfade3ab012bddd63a2c3602246f97b0 GIT binary patch literal 745 zcmWIWW@Zs#-~d7f2E{HQ0S7`rR!(AWQc7ZcT4`Q#NoIbYUP0xYVE^pfegb=?|H!ue z6^!^;eC2|@cq9MM#|{ddY;Z%OHH+1P` z?HOi0LH!4>+PQ1)5)nVfH^ZrXn%)nGZ=aPVg5CKq`!Rp6=t(zvxhd13>WHk^Che+< zUH>l?95fYK6_dI3j@D}KFGA%RnM^x3x2&I%@>r1JK>de=;&7?mjS&~_OQzqpJhpDG zNb#AAZvxC)C1Y3&JKdJ-(p!Cj$K%ltwx8D)E3x$N&*EXpkypP}k!j+iIAQvmoKx}g zs@scu6?GE1@_mo=8$_iv-#e*rRF5qvRdv(i)hkQ&jXZq|vY&gq8a4iRtPwxF??gmO z?}BH4E?lk;+da?5az}UD-Mc20pMI}t?^?Dj)Ym&rPoCqn=6==MlP6=1w&jJyv9R?| zw+vuc|9#-^k6FpC2Hp0@A_Eu=elyGO-Xrq;l*kInzelAHEo{4HEA!#A$JXtKwV%7Q z1ckImFAq%l@bXnw(X@EWz2Y|u`0w44o7m{^e(v-C-kpjoc#})EG~N1mZR@PyLXUF` zx34g%I#wyXeb2UOdg8yenpUjR@>v)0!g`)AU+w1uhbxt}u2l9Zmi#W)`x5fxxA4Sz z!5yxl7s~3@3h(9lK9xQ1%6rP~=!#%H+uKitU;bD5Z6NoTkAdO;{{U}xj+qAEe1e%6 v7}QxA7y`T*nM9Zoi4s{3lqg|fTO){tnM4D;S=m4`j6moBq$dN-WncgR`ldj# literal 0 HcmV?d00001 diff --git a/terraform/outputs.tf b/terraform/outputs.tf index e69de29..8b3a508 100644 --- a/terraform/outputs.tf +++ b/terraform/outputs.tf @@ -0,0 +1,19 @@ +output "api_endpoint" { + description = "The endpoint URL of the API Gateway" + value = module.api_gateway.api_endpoint +} + +output "dynamodb_table_name" { + description = "The name of the DynamoDB table" + value = module.dynamodb.table_name +} + +output "lambda_function_name" { + description = "The name of the Lambda function" + value = module.lambda.function_name +} + +output "environment" { + description = "The environment name" + value = var.environment +} diff --git a/terraform/prod.tfvars b/terraform/prod.tfvars index e69de29..26f4f3d 100644 --- a/terraform/prod.tfvars +++ b/terraform/prod.tfvars @@ -0,0 +1,3 @@ +environment = "prod" +aws_region = "us-east-1" +project_name = "serverless-health-check-api" \ No newline at end of file diff --git a/terraform/staging.tfvars b/terraform/staging.tfvars index e69de29..9b0a55c 100644 --- a/terraform/staging.tfvars +++ b/terraform/staging.tfvars @@ -0,0 +1,3 @@ +environment = "staging" +aws_region = "us-east-1" +project_name = "serverless-health-check-api" \ No newline at end of file From c065819b3e6cee2071d736d4130a45763ed10812 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 20:54:14 +0100 Subject: [PATCH 16/20] Added Github action deploy workflows for CICD pipeline --- .github/workflows/deploy.yaml | 70 ++++++++++++++++++ README.md | 2 +- lambda/{hello.py => lambda_function.py} | 0 terraform/modules/api-gateway/main.tf | 4 +- terraform/modules/lambda/main.tf | 2 +- .../lambda/staging_lambda_function.zip | Bin 745 -> 0 bytes 6 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/deploy.yaml rename lambda/{hello.py => lambda_function.py} (100%) delete mode 100644 terraform/modules/lambda/staging_lambda_function.zip diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..8f7560c --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,70 @@ +name: Terraform Deploy + +on: + push: + branches: + - staging + - main + workflow_dispatch: + +env: + TF_WORKING_DIR: terraform + +jobs: + terraform: + name: Terraform (plan & apply) + runs-on: ubuntu-latest + environment: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }} + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.6 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Terraform Init + working-directory: ${{ env.TF_WORKING_DIR }} + run: terraform init -input=false + + - name: Terraform Validate & Format + working-directory: ${{ env.TF_WORKING_DIR }} + run: | + terraform fmt -check + terraform validate + + - name: Terraform Plan + id: plan + working-directory: ${{ env.TF_WORKING_DIR }} + run: | + if [ "${{ github.ref }}" = "refs/heads/staging" ]; then + terraform plan -var-file="staging.tfvars" -out=tfplan + else + terraform plan -var-file="prod.tfvars" -out=tfplan + fi + + - name: Terraform Apply + if: github.ref == 'refs/heads/staging' + working-directory: ${{ env.TF_WORKING_DIR }} + run: terraform apply -input=false -auto-approve tfplan + + - name: Terraform Apply (prod) - requires env approval + if: github.ref == 'refs/heads/main' + working-directory: ${{ env.TF_WORKING_DIR }} + run: terraform apply -input=false -auto-approve tfplan + + - name: Show outputs + if: success() + working-directory: ${{ env.TF_WORKING_DIR }} + run: terraform output -json diff --git a/README.md b/README.md index bacfaf6..34aae76 100644 --- a/README.md +++ b/README.md @@ -31,4 +31,4 @@ Run the python funtion locally using VS Code Run Button - Create the terraform folder structure -- imp \ No newline at end of file +- Deploy with: terraform init then terraform apply -var-file="staging.tfvars" (or prod.tfvars) \ No newline at end of file diff --git a/lambda/hello.py b/lambda/lambda_function.py similarity index 100% rename from lambda/hello.py rename to lambda/lambda_function.py diff --git a/terraform/modules/api-gateway/main.tf b/terraform/modules/api-gateway/main.tf index 8f2568d..8152db3 100644 --- a/terraform/modules/api-gateway/main.tf +++ b/terraform/modules/api-gateway/main.tf @@ -1,5 +1,5 @@ resource "aws_apigatewayv2_api" "health_api" { - name = "${var.environment}-health-check-api" + name = "${var.environment}-serverless-health-check-api" protocol_type = "HTTP" cors_configuration { @@ -55,7 +55,7 @@ resource "aws_apigatewayv2_stage" "default" { } resource "aws_cloudwatch_log_group" "api_gateway_logs" { - name = "/aws/apigateway/${var.environment}-health-check-api" + name = "/aws/apigateway/${var.environment}-serverless-health-check-api" retention_in_days = 7 tags = var.common_tags diff --git a/terraform/modules/lambda/main.tf b/terraform/modules/lambda/main.tf index 7746bb2..054e2aa 100644 --- a/terraform/modules/lambda/main.tf +++ b/terraform/modules/lambda/main.tf @@ -5,7 +5,7 @@ data "archive_file" "lambda_zip" { } resource "aws_lambda_function" "health_check" { - function_name = "${var.environment}-health-check-function" + function_name = "${var.environment}-serverless-health-check-api" role = var.lambda_role_arn handler = "lambda_function.lambda_handler" source_code_hash = data.archive_file.lambda_zip.output_base64sha256 diff --git a/terraform/modules/lambda/staging_lambda_function.zip b/terraform/modules/lambda/staging_lambda_function.zip deleted file mode 100644 index 6304432ddfade3ab012bddd63a2c3602246f97b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 745 zcmWIWW@Zs#-~d7f2E{HQ0S7`rR!(AWQc7ZcT4`Q#NoIbYUP0xYVE^pfegb=?|H!ue z6^!^;eC2|@cq9MM#|{ddY;Z%OHH+1P` z?HOi0LH!4>+PQ1)5)nVfH^ZrXn%)nGZ=aPVg5CKq`!Rp6=t(zvxhd13>WHk^Che+< zUH>l?95fYK6_dI3j@D}KFGA%RnM^x3x2&I%@>r1JK>de=;&7?mjS&~_OQzqpJhpDG zNb#AAZvxC)C1Y3&JKdJ-(p!Cj$K%ltwx8D)E3x$N&*EXpkypP}k!j+iIAQvmoKx}g zs@scu6?GE1@_mo=8$_iv-#e*rRF5qvRdv(i)hkQ&jXZq|vY&gq8a4iRtPwxF??gmO z?}BH4E?lk;+da?5az}UD-Mc20pMI}t?^?Dj)Ym&rPoCqn=6==MlP6=1w&jJyv9R?| zw+vuc|9#-^k6FpC2Hp0@A_Eu=elyGO-Xrq;l*kInzelAHEo{4HEA!#A$JXtKwV%7Q z1ckImFAq%l@bXnw(X@EWz2Y|u`0w44o7m{^e(v-C-kpjoc#})EG~N1mZR@PyLXUF` zx34g%I#wyXeb2UOdg8yenpUjR@>v)0!g`)AU+w1uhbxt}u2l9Zmi#W)`x5fxxA4Sz z!5yxl7s~3@3h(9lK9xQ1%6rP~=!#%H+uKitU;bD5Z6NoTkAdO;{{U}xj+qAEe1e%6 v7}QxA7y`T*nM9Zoi4s{3lqg|fTO){tnM4D;S=m4`j6moBq$dN-WncgR`ldj# From f37da735ba3eacc89f3b94e27d41bfd41ea59940 Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 21:20:09 +0100 Subject: [PATCH 17/20] testing the github action deployment file --- .github/workflows/deploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 8f7560c..7e0ace5 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -8,7 +8,7 @@ on: workflow_dispatch: env: - TF_WORKING_DIR: terraform + TF_WORKING_DIR: terraform/ jobs: terraform: From 0e680f04c248ed411492483b8fb77d8525e500eb Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 21:26:59 +0100 Subject: [PATCH 18/20] Fixed terraform format issue --- terraform/main.tf | 24 ++++++++++++------------ terraform/variables.tf | 20 ++++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/terraform/main.tf b/terraform/main.tf index c729e40..60f09a3 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -1,6 +1,6 @@ terraform { required_version = ">= 1.0" - + required_providers { aws = { source = "hashicorp/aws" @@ -28,9 +28,9 @@ locals { # DynamoDB Module module "dynamodb" { - source = "./modules/dynamodb" - environment = var.environment - common_tags = local.common_tags + source = "./modules/dynamodb" + environment = var.environment + common_tags = local.common_tags } # IAM Module @@ -44,7 +44,7 @@ module "iam" { # API Gateway Module module "api_gateway" { - source = "./modules/api-gateway" + source = "./modules/api-gateway" environment = var.environment lambda_invoke_arn = module.lambda.function_invoke_arn common_tags = local.common_tags @@ -52,13 +52,13 @@ module "api_gateway" { # Lambda Module module "lambda" { - source = "./modules/lambda" - environment = var.environment - lambda_role_arn = module.iam.lambda_role_arn - dynamodb_table_name = module.dynamodb.table_name - api_gateway_execution_arn = module.api_gateway.execution_arn - lambda_funtion_dir = var.lambda_funtion_dir - common_tags = local.common_tags + source = "./modules/lambda" + environment = var.environment + lambda_role_arn = module.iam.lambda_role_arn + dynamodb_table_name = module.dynamodb.table_name + api_gateway_execution_arn = module.api_gateway.execution_arn + lambda_funtion_dir = var.lambda_funtion_dir + common_tags = local.common_tags # depends_on = [module.api_gateway] } diff --git a/terraform/variables.tf b/terraform/variables.tf index 05269a4..0754d5d 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -1,31 +1,31 @@ variable "aws_region" { - default = "us-east-1" + default = "us-east-1" description = "AWS region" - type = string + type = string } variable "terraform_backend_bucket" { - default = "serverlesshealthcheckapi" + default = "serverlesshealthcheckapi" description = "AWS S3 Bucket for Terraform backend" - type = string + type = string } variable "environment" { validation { - condition = contains(["staging", "prod"], var.environment) + condition = contains(["staging", "prod"], var.environment) error_message = "Environment must be either staging or prod" } description = "Deployment environment name (staging or prod)" - type = string + type = string } variable "lambda_funtion_dir" { - default = "../lambda" + default = "../lambda" description = "Directory containing Lambda function source code" - type = string + type = string } variable "project_name" { - default = "serverless-health-check-api" + default = "serverless-health-check-api" description = "Project name for tagging and resource naming" - type = string + type = string } \ No newline at end of file From a2cdcd1de8cf747b4bcd2adab12c428a7b5e95af Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 21:33:56 +0100 Subject: [PATCH 19/20] Added destroy workflow --- .github/workflows/destroy.yml | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 .github/workflows/destroy.yml diff --git a/.github/workflows/destroy.yml b/.github/workflows/destroy.yml new file mode 100644 index 0000000..6836253 --- /dev/null +++ b/.github/workflows/destroy.yml @@ -0,0 +1,48 @@ +name: Terraform Destroy + +on: + workflow_dispatch: + inputs: + environment: + description: "Environment to destroy" + required: true + type: choice + options: + - staging + - prod + +env: + TF_WORKING_DIR: terraform/ + +jobs: + destroy: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v2 + with: + terraform_version: 1.5.6 + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ secrets.AWS_REGION }} + + - name: Terraform Init + working-directory: ${{ env.TF_WORKING_DIR }} + run: terraform init -input=false + + - name: Terraform Destroy + working-directory: ${{ env.TF_WORKING_DIR }} + run: | + if [ "${{ github.event.inputs.environment }}" = "staging" ]; then + terraform destroy -var-file="staging.tfvars" -auto-approve + else + terraform destroy -var-file="prod.tfvars" -auto-approve + fi From d0093f5794aa043800fe8a696d9981ca5c35d61a Mon Sep 17 00:00:00 2001 From: emmy-github-webdev Date: Fri, 12 Dec 2025 21:54:20 +0100 Subject: [PATCH 20/20] Correct naming detroy trigger file --- .github/workflows/{destroy.yml => destroy.yaml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{destroy.yml => destroy.yaml} (100%) diff --git a/.github/workflows/destroy.yml b/.github/workflows/destroy.yaml similarity index 100% rename from .github/workflows/destroy.yml rename to .github/workflows/destroy.yaml