Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
26d74d3
Created a hello lambda funtion and test the funtion locally
Emmy-github-webdev Dec 11, 2025
39bfe57
Added the Pyhton hello lambda funtion and run step to README file
Emmy-github-webdev Dec 11, 2025
8210762
Created the repository folder structure
Emmy-github-webdev Dec 11, 2025
a3023c2
Added terraform provider
Emmy-github-webdev Dec 11, 2025
cf2d372
Added required version to the terraform provider
Emmy-github-webdev Dec 11, 2025
48b3032
Install boto3, and import needed dependencies
Emmy-github-webdev Dec 12, 2025
b049233
Created the Logged event details to DynamoDB
Emmy-github-webdev Dec 12, 2025
03a0d26
Modified the terraform module structure
Emmy-github-webdev Dec 12, 2025
9622414
Added terraform provider
Emmy-github-webdev Dec 12, 2025
776affd
Added AWS S3 bucket for storing tfstate
Emmy-github-webdev Dec 12, 2025
83480d5
Created the aws dynamodb request
Emmy-github-webdev Dec 12, 2025
c175f88
Added the iam role and policy
Emmy-github-webdev Dec 12, 2025
0d6d9b6
Added API Gateway module
Emmy-github-webdev Dec 12, 2025
9474e0d
Added lambda module
Emmy-github-webdev Dec 12, 2025
3c48257
Run the application locally and tested
Emmy-github-webdev Dec 12, 2025
c065819
Added Github action deploy workflows for CICD pipeline
Emmy-github-webdev Dec 12, 2025
f37da73
testing the github action deployment file
Emmy-github-webdev Dec 12, 2025
0e680f0
Fixed terraform format issue
Emmy-github-webdev Dec 12, 2025
a2cdcd1
Added destroy workflow
Emmy-github-webdev Dec 12, 2025
d0093f5
Correct naming detroy trigger file
Emmy-github-webdev Dec 12, 2025
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
70 changes: 70 additions & 0 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
@@ -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
48 changes: 48 additions & 0 deletions .github/workflows/destroy.yaml
Original file line number Diff line number Diff line change
@@ -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
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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.*
36 changes: 34 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,34 @@
# 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 locally using VS Code Run Button
- Click the “Run Python File” button in the top right corner.

```
# Output

{'statusCode': 200, 'body': 'Hello, Emmanuel Ogah!'}"
```

- Create the terraform folder structure

- Deploy with: terraform init then terraform apply -var-file="staging.tfvars" (or prod.tfvars)
46 changes: 46 additions & 0 deletions lambda/lambda_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import uuid
import json
import boto3
import os
import logging
from datetime import datetime


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):
# 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": json.dumps({"status": "healthy", "message": "Request processed and saved.", "id": request_id}),
"headers": {"Content-Type": "application/json"}
}
5 changes: 5 additions & 0 deletions terraform/backend.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# bucket = "serverlesshealthcheckapi"
# key = "health-check-app/terraform.tfstate"
# region = "us-east-1"
# encrypt = true
# dynamodb_table = "terraform-locks"
64 changes: 64 additions & 0 deletions terraform/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
terraform {
required_version = ">= 1.0"

required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}

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()
}
}

# DynamoDB Module
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
}

# 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]
}

62 changes: 62 additions & 0 deletions terraform/modules/api-gateway/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
resource "aws_apigatewayv2_api" "health_api" {
name = "${var.environment}-serverless-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}-serverless-health-check-api"
retention_in_days = 7

tags = var.common_tags
}
14 changes: 14 additions & 0 deletions terraform/modules/api-gateway/outputs.tf
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions terraform/modules/api-gateway/variables.tf
Original file line number Diff line number Diff line change
@@ -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)
}
Loading
Loading