Skip to content

Commit ba81c4c

Browse files
Merge pull request #1 from Emmy-github-webdev/staging
Add the setup to main
2 parents 54c614a + d0093f5 commit ba81c4c

23 files changed

Lines changed: 619 additions & 2 deletions

.github/workflows/deploy.yaml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Terraform Deploy
2+
3+
on:
4+
push:
5+
branches:
6+
- staging
7+
- main
8+
workflow_dispatch:
9+
10+
env:
11+
TF_WORKING_DIR: terraform/
12+
13+
jobs:
14+
terraform:
15+
name: Terraform (plan & apply)
16+
runs-on: ubuntu-latest
17+
environment: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }}
18+
permissions:
19+
contents: read
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Setup Terraform
26+
uses: hashicorp/setup-terraform@v2
27+
with:
28+
terraform_version: 1.5.6
29+
30+
- name: Configure AWS Credentials
31+
uses: aws-actions/configure-aws-credentials@v2
32+
with:
33+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
34+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
35+
aws-region: ${{ secrets.AWS_REGION }}
36+
37+
- name: Terraform Init
38+
working-directory: ${{ env.TF_WORKING_DIR }}
39+
run: terraform init -input=false
40+
41+
- name: Terraform Validate & Format
42+
working-directory: ${{ env.TF_WORKING_DIR }}
43+
run: |
44+
terraform fmt -check
45+
terraform validate
46+
47+
- name: Terraform Plan
48+
id: plan
49+
working-directory: ${{ env.TF_WORKING_DIR }}
50+
run: |
51+
if [ "${{ github.ref }}" = "refs/heads/staging" ]; then
52+
terraform plan -var-file="staging.tfvars" -out=tfplan
53+
else
54+
terraform plan -var-file="prod.tfvars" -out=tfplan
55+
fi
56+
57+
- name: Terraform Apply
58+
if: github.ref == 'refs/heads/staging'
59+
working-directory: ${{ env.TF_WORKING_DIR }}
60+
run: terraform apply -input=false -auto-approve tfplan
61+
62+
- name: Terraform Apply (prod) - requires env approval
63+
if: github.ref == 'refs/heads/main'
64+
working-directory: ${{ env.TF_WORKING_DIR }}
65+
run: terraform apply -input=false -auto-approve tfplan
66+
67+
- name: Show outputs
68+
if: success()
69+
working-directory: ${{ env.TF_WORKING_DIR }}
70+
run: terraform output -json

.github/workflows/destroy.yaml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Terraform Destroy
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
environment:
7+
description: "Environment to destroy"
8+
required: true
9+
type: choice
10+
options:
11+
- staging
12+
- prod
13+
14+
env:
15+
TF_WORKING_DIR: terraform/
16+
17+
jobs:
18+
destroy:
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@v4
24+
25+
- name: Setup Terraform
26+
uses: hashicorp/setup-terraform@v2
27+
with:
28+
terraform_version: 1.5.6
29+
30+
- name: Configure AWS Credentials
31+
uses: aws-actions/configure-aws-credentials@v2
32+
with:
33+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
34+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
35+
aws-region: ${{ secrets.AWS_REGION }}
36+
37+
- name: Terraform Init
38+
working-directory: ${{ env.TF_WORKING_DIR }}
39+
run: terraform init -input=false
40+
41+
- name: Terraform Destroy
42+
working-directory: ${{ env.TF_WORKING_DIR }}
43+
run: |
44+
if [ "${{ github.event.inputs.environment }}" = "staging" ]; then
45+
terraform destroy -var-file="staging.tfvars" -auto-approve
46+
else
47+
terraform destroy -var-file="prod.tfvars" -auto-approve
48+
fi

.gitignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.terraform
2+
*.tfstate
3+
*.tfstate.backup
4+
.terraform.tfstate.lock.info
5+
crash.log
6+
override.tf
7+
override.tf.json
8+
*_override.tf
9+
*_override.tf.json
10+
.terragrunt-cache
11+
.terragrunt-tfstate-backups
12+
.terraform.lock.hcl
13+
.terraformrc
14+
.terraform.*

README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,34 @@
1-
# Serverless-Health-Check-API-with-CI-CD
2-
Serverless Health Check API with CI/CD
1+
# Serverless Health Check API with CI/CD
2+
3+
create hello lambda funtion using Python
4+
5+
```
6+
# Hello lambda function
7+
def lambda_handler(event, context):
8+
name = event.get("name", "World")
9+
message = f"Hello, {name}!"
10+
11+
return {
12+
"statusCode": 200,
13+
"body": message
14+
}
15+
16+
# Run the funtion locally for testing
17+
if __name__ == "__main__":
18+
test_event = {"name": "Emmanuel Ogah"}
19+
result = lambda_handler(test_event, None)
20+
print(result)
21+
```
22+
23+
Run the python funtion locally using VS Code Run Button
24+
- Click the “Run Python File” button in the top right corner.
25+
26+
```
27+
# Output
28+
29+
{'statusCode': 200, 'body': 'Hello, Emmanuel Ogah!'}"
30+
```
31+
32+
- Create the terraform folder structure
33+
34+
- Deploy with: terraform init then terraform apply -var-file="staging.tfvars" (or prod.tfvars)

lambda/lambda_function.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import uuid
2+
import json
3+
import boto3
4+
import os
5+
import logging
6+
from datetime import datetime
7+
8+
9+
TABLE_NAME = os.environ.get("REQUESTS_TABLE", "unknown-table")
10+
11+
dynamodb = boto3.resource("dynamodb")
12+
table = dynamodb.Table(TABLE_NAME)
13+
14+
logger = logging.getLogger()
15+
logger.setLevel(logging.INFO)
16+
17+
def lambda_handler(event, context):
18+
# Incoming event logging
19+
logger.info("Received event: %s", json.dumps(event))
20+
21+
# Create request id
22+
request_id = str(uuid.uuid4())
23+
item = {
24+
"id": request_id,
25+
"timestamp": datetime.now().isoformat() + "Z",
26+
"event": event
27+
}
28+
29+
# Save request to DynamoDB
30+
try:
31+
table.put_item(Item=item)
32+
logger.info("Saved item %s to %s", request_id, TABLE_NAME)
33+
except Exception as e:
34+
logger.exception("Failed to write to DynamoDB: %s", e)
35+
return {
36+
"statusCode": 500,
37+
"body": json.dumps({"status": "error", "message": "Failed saving request."}),
38+
"headers": {"Content-Type": "application/json"}
39+
}
40+
41+
# Return success
42+
return {
43+
"statusCode": 200,
44+
"body": json.dumps({"status": "healthy", "message": "Request processed and saved.", "id": request_id}),
45+
"headers": {"Content-Type": "application/json"}
46+
}

terraform/backend.tfvars

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# bucket = "serverlesshealthcheckapi"
2+
# key = "health-check-app/terraform.tfstate"
3+
# region = "us-east-1"
4+
# encrypt = true
5+
# dynamodb_table = "terraform-locks"

terraform/main.tf

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
aws = {
6+
source = "hashicorp/aws"
7+
version = "~> 5.0"
8+
}
9+
}
10+
}
11+
12+
provider "aws" {
13+
region = var.aws_region
14+
15+
default_tags {
16+
tags = local.common_tags
17+
}
18+
}
19+
20+
locals {
21+
common_tags = {
22+
Environment = var.environment
23+
Project = var.project_name
24+
ManagedBy = "Terraform"
25+
CreatedAt = timestamp()
26+
}
27+
}
28+
29+
# DynamoDB Module
30+
module "dynamodb" {
31+
source = "./modules/dynamodb"
32+
environment = var.environment
33+
common_tags = local.common_tags
34+
}
35+
36+
# IAM Module
37+
module "iam" {
38+
source = "./modules/iam"
39+
40+
environment = var.environment
41+
dynamodb_table_arn = module.dynamodb.table_arn
42+
common_tags = local.common_tags
43+
}
44+
45+
# API Gateway Module
46+
module "api_gateway" {
47+
source = "./modules/api-gateway"
48+
environment = var.environment
49+
lambda_invoke_arn = module.lambda.function_invoke_arn
50+
common_tags = local.common_tags
51+
}
52+
53+
# Lambda Module
54+
module "lambda" {
55+
source = "./modules/lambda"
56+
environment = var.environment
57+
lambda_role_arn = module.iam.lambda_role_arn
58+
dynamodb_table_name = module.dynamodb.table_name
59+
api_gateway_execution_arn = module.api_gateway.execution_arn
60+
lambda_funtion_dir = var.lambda_funtion_dir
61+
common_tags = local.common_tags
62+
# depends_on = [module.api_gateway]
63+
}
64+
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
resource "aws_apigatewayv2_api" "health_api" {
2+
name = "${var.environment}-serverless-health-check-api"
3+
protocol_type = "HTTP"
4+
5+
cors_configuration {
6+
allow_origins = ["*"]
7+
allow_methods = ["GET", "POST", "OPTIONS"]
8+
allow_headers = ["*"]
9+
}
10+
11+
tags = var.common_tags
12+
}
13+
14+
resource "aws_apigatewayv2_integration" "lambda_integration" {
15+
api_id = aws_apigatewayv2_api.health_api.id
16+
integration_type = "AWS_PROXY"
17+
integration_method = "POST"
18+
payload_format_version = "2.0"
19+
integration_uri = var.lambda_invoke_arn
20+
depends_on = []
21+
}
22+
23+
resource "aws_apigatewayv2_route" "health_route_get" {
24+
api_id = aws_apigatewayv2_api.health_api.id
25+
route_key = "GET /health"
26+
target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
27+
}
28+
29+
resource "aws_apigatewayv2_route" "health_route_post" {
30+
api_id = aws_apigatewayv2_api.health_api.id
31+
route_key = "POST /health"
32+
target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
33+
}
34+
35+
resource "aws_apigatewayv2_stage" "default" {
36+
api_id = aws_apigatewayv2_api.health_api.id
37+
name = "$default"
38+
auto_deploy = true
39+
40+
access_log_settings {
41+
destination_arn = aws_cloudwatch_log_group.api_gateway_logs.arn
42+
format = jsonencode({
43+
requestId = "$context.requestId"
44+
ip = "$context.identity.sourceIp"
45+
requestTime = "$context.requestTime"
46+
httpMethod = "$context.httpMethod"
47+
resourcePath = "$context.resourcePath"
48+
status = "$context.status"
49+
protocol = "$context.protocol"
50+
responseLength = "$context.responseLength"
51+
})
52+
}
53+
54+
tags = var.common_tags
55+
}
56+
57+
resource "aws_cloudwatch_log_group" "api_gateway_logs" {
58+
name = "/aws/apigateway/${var.environment}-serverless-health-check-api"
59+
retention_in_days = 7
60+
61+
tags = var.common_tags
62+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
output "api_endpoint" {
2+
description = "The endpoint URL of the API Gateway"
3+
value = "${aws_apigatewayv2_api.health_api.api_endpoint}/"
4+
}
5+
6+
output "api_id" {
7+
description = "The ID of the API Gateway"
8+
value = aws_apigatewayv2_api.health_api.id
9+
}
10+
11+
output "execution_arn" {
12+
description = "The execution ARN of the API Gateway"
13+
value = aws_apigatewayv2_api.health_api.execution_arn
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
variable "environment" {
2+
description = "Environment name"
3+
type = string
4+
}
5+
6+
variable "lambda_invoke_arn" {
7+
description = "Invoke ARN of the Lambda function"
8+
type = string
9+
}
10+
11+
variable "common_tags" {
12+
description = "Common tags for all resources"
13+
type = map(string)
14+
}

0 commit comments

Comments
 (0)