Skip to content

Commit 80a95f8

Browse files
Merge pull request #2 from Emmy-github-webdev/staging
Staging
2 parents ba81c4c + c8d5c35 commit 80a95f8

28 files changed

Lines changed: 286 additions & 396 deletions

.github/workflows/deploy.yaml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ on:
88
workflow_dispatch:
99

1010
env:
11-
TF_WORKING_DIR: terraform/
11+
TF_WORKING_DIR: terraform
1212

1313
jobs:
1414
terraform:
1515
name: Terraform (plan & apply)
1616
runs-on: ubuntu-latest
1717
environment: ${{ github.ref == 'refs/heads/main' && 'prod' || 'staging' }}
18+
1819
permissions:
1920
contents: read
2021

@@ -34,9 +35,15 @@ jobs:
3435
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
3536
aws-region: ${{ secrets.AWS_REGION }}
3637

38+
# Backend selection based on branch
3739
- name: Terraform Init
3840
working-directory: ${{ env.TF_WORKING_DIR }}
39-
run: terraform init -input=false
41+
run: |
42+
if [ "${{ github.ref }}" = "refs/heads/staging" ]; then
43+
terraform init -reconfigure -backend-config=backend-staging.tfvars -input=false
44+
else
45+
terraform init -reconfigure -backend-config=backend-prod.tfvars -input=false
46+
fi
4047
4148
- name: Terraform Validate & Format
4249
working-directory: ${{ env.TF_WORKING_DIR }}
@@ -54,17 +61,17 @@ jobs:
5461
terraform plan -var-file="prod.tfvars" -out=tfplan
5562
fi
5663
57-
- name: Terraform Apply
64+
- name: Terraform Apply (staging)
5865
if: github.ref == 'refs/heads/staging'
5966
working-directory: ${{ env.TF_WORKING_DIR }}
6067
run: terraform apply -input=false -auto-approve tfplan
6168

62-
- name: Terraform Apply (prod) - requires env approval
69+
- name: Terraform Apply (prod)
6370
if: github.ref == 'refs/heads/main'
6471
working-directory: ${{ env.TF_WORKING_DIR }}
6572
run: terraform apply -input=false -auto-approve tfplan
6673

67-
- name: Show outputs
74+
- name: Show Outputs
6875
if: success()
6976
working-directory: ${{ env.TF_WORKING_DIR }}
7077
run: terraform output -json

.github/workflows/destroy.yaml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ on:
1212
- prod
1313

1414
env:
15-
TF_WORKING_DIR: terraform/
15+
TF_WORKING_DIR: terraform
1616

1717
jobs:
1818
destroy:
19+
name: Terraform Destroy
1920
runs-on: ubuntu-latest
21+
environment: ${{ github.event.inputs.environment }}
22+
23+
permissions:
24+
contents: read
2025

2126
steps:
2227
- name: Checkout
@@ -34,9 +39,15 @@ jobs:
3439
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
3540
aws-region: ${{ secrets.AWS_REGION }}
3641

42+
# Backend selection based on input
3743
- name: Terraform Init
3844
working-directory: ${{ env.TF_WORKING_DIR }}
39-
run: terraform init -input=false
45+
run: |
46+
if [ "${{ github.event.inputs.environment }}" = "staging" ]; then
47+
terraform init -reconfigure -backend-config=backend-staging.tfvars -input=false
48+
else
49+
terraform init -reconfigure -backend-config=backend-prod.tfvars -input=false
50+
fi
4051
4152
- name: Terraform Destroy
4253
working-directory: ${{ env.TF_WORKING_DIR }}

README.md

Lines changed: 96 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,91 @@
11
# Serverless Health Check API with CI/CD
22

3+
The goal of this project is to build, configure, and automate the deployment of a simple serverless application on AWS. Created a health check endpoint that logs requests and stores them in a database, with a CI/CD pipeline to manage deployments for both staging and production environments, fully provisioned via Terraform and deployed automatically using GitHub Actions.
4+
5+
## Architectural desig
6+
7+
### Core Components
8+
9+
- Amazon API Gateway (HTTP API)
10+
- AWS Lambda (Python)
11+
- Amazon DynamoDB
12+
- AWS IAM
13+
- Amazon CloudWatch Logs
14+
15+
Each environment (staging, prod) is isolated by naming convention and Terraform variables.
16+
17+
### Runtime Request Flow
18+
19+
1. _Client_: sends a GET or POST request to:
20+
21+
```
22+
https://<api-id>.execute-api.<region>.amazonaws.com/health
23+
```
24+
2. _API Gateway_:
25+
- Matches the /health route
26+
- Forwards the request using AWS_PROXY integration
27+
3. _Lambda Function (env-health-check-function)_:
28+
- Logs the full request event to CloudWatch Logs
29+
- Generates a UUID
30+
- Stores request metadata in DynamoDB (env-requests-db)
31+
- Returns a JSON response
32+
4. _DynamoDB_:
33+
- Stores the request record (ID, timestamp, request payload)
34+
35+
### Pipeline Flow
36+
1. Developer pushes code
37+
- staging branch → auto deploy
38+
- main branch → production deploy
39+
2. GitHub Actions workflow: The GitHub action workflow contain both terraform deploy and terraform destroy.
40+
- Configures AWS credentials (GitHub Secrets)
41+
- Terraform deploy - deploy.yaml
42+
- Checks out code
43+
- Runs:
44+
- terraform fmt
45+
- terraform validate
46+
- terraform plan
47+
- terraform apply
48+
49+
- Terraform destroy - destroy.yaml
50+
- On GitHub console, manually trigger the destroy pipeline from the actions
51+
- Runs:
52+
- terraform int
53+
- terraform destroy
54+
55+
### Environment separation
56+
57+
| Aspect | Staging | Production |
58+
| -------------- | ------------------------------- | ---------------------------- |
59+
| Branch | `staging` | `main` |
60+
| Terraform vars | `staging.tfvars` | `prod.tfvars` |
61+
| Lambda | `staging-health-check-function` | `prod-health-check-function` |
62+
| DynamoDB | `staging-requests-db` | `prod-requests-db` |
63+
| API Gateway | `staging-health-check-api` | `prod-health-check-api` |
64+
| Approval | None | Required |
65+
66+
67+
### Security and IAM Role
68+
Each Lambda function has one dedicated IAM role with:
69+
- _Allowed permissions_
70+
- dynamodb:PutItem → specific DynamoDB table ARN
71+
- logs:CreateLogGroup
72+
- logs:CreateLogStream
73+
- logs:PutLogEvents
74+
- Denied by default
75+
- No read access to DynamoDB
76+
- No access to other AWS services
77+
- No wildcard write permissions
78+
- secrets Handling
79+
- AWS credentials stored in GitHub Secrets
80+
- No credentials committed to repository
81+
82+
83+
84+
85+
86+
87+
88+
389
create hello lambda funtion using Python
490

591
```
@@ -31,4 +117,13 @@ Run the python funtion locally using VS Code Run Button
31117

32118
- Create the terraform folder structure
33119

34-
- Deploy with: terraform init then terraform apply -var-file="staging.tfvars" (or prod.tfvars)
120+
- Deploy with: terraform init then terraform apply -var-file="staging.tfvars" (or prod.tfvars)
121+
122+
123+
endpoint - https://nrbefv9bcj.execute-api.us-east-1.amazonaws.com/health
124+
125+
126+
127+
terraform init -backend-config=backend-staging.tfvars for staging environment
128+
129+
terraform init -backend-config=backend-prod.tfvars for prod environment

terraform/apigw.tf

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
resource "aws_apigatewayv2_api" "http_api" {
2+
name = "${var.env}-health-check-api"
3+
protocol_type = "HTTP"
4+
}
5+
6+
resource "aws_apigatewayv2_integration" "lambda_integration" {
7+
api_id = aws_apigatewayv2_api.http_api.id
8+
integration_type = "AWS_PROXY"
9+
integration_uri = aws_lambda_function.health_check.arn
10+
payload_format_version = "2.0"
11+
}
12+
13+
resource "aws_apigatewayv2_route" "health_route" {
14+
api_id = aws_apigatewayv2_api.http_api.id
15+
route_key = "GET /health"
16+
target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
17+
}
18+
19+
resource "aws_apigatewayv2_route" "health_route_post" {
20+
api_id = aws_apigatewayv2_api.http_api.id
21+
route_key = "POST /health"
22+
target = "integrations/${aws_apigatewayv2_integration.lambda_integration.id}"
23+
}
24+
25+
resource "aws_apigatewayv2_stage" "default_stage" {
26+
api_id = aws_apigatewayv2_api.http_api.id
27+
name = "$default"
28+
auto_deploy = true
29+
}
30+
31+
resource "aws_lambda_permission" "allow_apigw" {
32+
statement_id = "${var.env}-allow-apigw"
33+
action = "lambda:InvokeFunction"
34+
function_name = aws_lambda_function.health_check.function_name
35+
principal = "apigateway.amazonaws.com"
36+
source_arn = "${aws_apigatewayv2_api.http_api.execution_arn}/*/*"
37+
}

terraform/backend-prod.tfvars

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bucket = "serverlesshealthcheckapi"
2+
key = "serverless-health-check-api/prod/tfstate"
3+
region = "us-east-1"
4+
encrypt = true

terraform/backend-staging.tfvars

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
bucket = "serverlesshealthcheckapi"
2+
key = "serverless-health-check-api/staging/tfstate"
3+
region = "us-east-1"
4+
encrypt = true

terraform/backend.tfvars

Lines changed: 0 additions & 5 deletions
This file was deleted.

terraform/iam.tf

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
data "aws_iam_policy_document" "lambda_assume_role" {
2+
statement {
3+
effect = "Allow"
4+
principals {
5+
type = "Service"
6+
identifiers = ["lambda.amazonaws.com"]
7+
}
8+
actions = ["sts:AssumeRole"]
9+
}
10+
}
11+
12+
resource "aws_iam_role" "lambda_role" {
13+
name = "${var.env}-health-check-lambda-role"
14+
assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
15+
tags = {
16+
Environment = var.env
17+
}
18+
}
19+
20+
resource "aws_iam_role_policy" "lambda_policy" {
21+
name = "${var.env}-health-check-lambda-policy"
22+
role = aws_iam_role.lambda_role.id
23+
24+
policy = jsonencode({
25+
Version = "2012-10-17"
26+
Statement = [
27+
{
28+
Effect = "Allow"
29+
Action = [
30+
"dynamodb:PutItem"
31+
]
32+
Resource = aws_dynamodb_table.requests.arn
33+
},
34+
{
35+
Effect = "Allow"
36+
Action = [
37+
"logs:CreateLogGroup",
38+
"logs:CreateLogStream",
39+
"logs:PutLogEvents"
40+
]
41+
Resource = "arn:aws:logs:*:*:*"
42+
}
43+
]
44+
})
45+
}

terraform/lambda.tf

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
data "archive_file" "lambda_zip" {
2+
type = "zip"
3+
output_path = "${path.module}/lambda_package/${var.env}-lambda.zip"
4+
5+
source {
6+
content = file("${path.module}/../lambda/lambda_function.py")
7+
filename = "lambda_function.py"
8+
}
9+
}
10+
11+
resource "aws_lambda_function" "health_check" {
12+
function_name = "${var.env}-health-check-function"
13+
filename = data.archive_file.lambda_zip.output_path
14+
handler = var.lambda_handler
15+
runtime = var.lambda_runtime
16+
role = aws_iam_role.lambda_role.arn
17+
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
18+
19+
environment {
20+
variables = {
21+
REQUESTS_TABLE = aws_dynamodb_table.requests.name
22+
}
23+
}
24+
25+
tags = {
26+
Environment = var.env
27+
}
28+
}
745 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)