Skip to content

Commit 7961526

Browse files
committed
Initial project setup
1 parent d42141a commit 7961526

File tree

12 files changed

+404
-0
lines changed

12 files changed

+404
-0
lines changed

.github/workflows/deploy.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
name: Deploy Python App to ECS
2+
3+
on:
4+
push:
5+
branches: [ main ]
6+
workflow_dispatch:
7+
8+
jobs:
9+
build-and-push:
10+
name: Build and Push Docker Image
11+
runs-on: ubuntu-latest
12+
permissions:
13+
id-token: write
14+
contents: read
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v3
19+
20+
- name: Configure AWS Credentials
21+
uses: aws-actions/configure-aws-credentials@v4
22+
with:
23+
role-to-assume: arn:aws:iam::615201069679:role/GitHubActions_ECS_Role
24+
aws-region: ap-south-1
25+
26+
- name: Login to Amazon ECR
27+
id: login-ecr
28+
uses: aws-actions/amazon-ecr-login@v2
29+
30+
- name: Build, tag, and push image to Amazon ECR
31+
id: build-image
32+
env:
33+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
34+
ECR_REPOSITORY: demo-assignment-repo
35+
IMAGE_TAG: ${{ github.sha }}
36+
run: |
37+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
38+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
39+
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
40+
41+
deploy:
42+
name: Deploy Infrastructure
43+
runs-on: ubuntu-latest
44+
needs: build-and-push
45+
permissions:
46+
id-token: write
47+
contents: read
48+
49+
steps:
50+
- name: Checkout code
51+
uses: actions/checkout@v3
52+
53+
- name: Configure AWS credentials
54+
uses: aws-actions/configure-aws-credentials@v4
55+
with:
56+
# PASTE YOUR NEW ROLE ARN HERE
57+
role-to-assume: arn:aws:iam::615201069679:role/GitHubActions_ECSDemo_Role
58+
aws-region: ap-south-1
59+
60+
- name: Setup Terraform
61+
uses: hashicorp/setup-terraform@v3
62+
63+
- name: Terraform Init
64+
run: terraform -chdir=iac init
65+
66+
- name: Terraform Apply
67+
run: |
68+
terraform -chdir=iac apply -auto-approve -var="docker_image_url=${{ needs.build-and-push.outputs.image }}"

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
**/.terraform/*
2+
*.tfstate
3+
*.tfstate.*
4+
terraform.tfvars
5+
crash.log
6+
*.auto.tfvars

Dockerfile

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
FROM python:3.12-slim
2+
3+
WORKDIR /app
4+
5+
COPY app/requirements.txt .
6+
7+
RUN python -m pip install --no-cache-dir -r requirements.txt
8+
9+
COPY ./app .
10+
11+
EXPOSE 8080
12+
13+
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "main:app"]

app/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from flask import Flask, render_template, request, redirect, url_for
2+
3+
app = Flask(__name__)
4+
5+
@app.route('/', methods=['GET', 'POST'])
6+
def login():
7+
error = None
8+
if request.method == 'POST':
9+
email = request.form['email']
10+
password = request.form['password']
11+
if email == 'hire-me@anshumat.org' and password == 'HireMe@2025!':
12+
return redirect(url_for('dashboard'))
13+
else:
14+
error = 'Invalid credentials. Please try again.'
15+
return render_template('login.html', error=error)
16+
17+
@app.route('/dashboard')
18+
def dashboard():
19+
return render_template('dashboard.html')
20+
21+
if __name__ == '__main__':
22+
app.run(host='0.0.0.0', port=8080)

app/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Flask==3.0.0
2+
gunicorn==21.2.0

app/templates/dashboard.html

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<!DOCTYPE html>
2+
<html><head><title>Dashboard</title></head>
3+
<body>
4+
<h1>Login Successful!</h1>
5+
<p>This Python Flask application was deployed as a Docker container on AWS ECS Fargate.</p>
6+
<p>The entire process was automated using GitHub Actions (CI/CD) and Terraform (IaC).</p>
7+
</body></html>

app/templates/login.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html><head><title>Login</title></head>
3+
<body>
4+
<h2>Demo Login</h2>
5+
<form method="POST">
6+
<input type="email" name="email" placeholder="Email" required>
7+
<input type="password" name="password" placeholder="Password" required>
8+
<button type="submit">Log In</button>
9+
</form>
10+
{% if error %}
11+
<p style="color:red;">{{ error }}</p>
12+
{% endif %}
13+
</body></html>

iac/.terraform.lock.hcl

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

iac/ecs.tf

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
resource "aws_security_group" "lb_sg" {
2+
name = "demo-lb-sg"
3+
description = "Allow HTTP inbound traffic"
4+
vpc_id = aws_vpc.main.id
5+
6+
ingress {
7+
from_port = 80
8+
to_port = 80
9+
protocol = "tcp"
10+
cidr_blocks = ["0.0.0.0/0"]
11+
}
12+
13+
egress {
14+
from_port = 0
15+
to_port = 0
16+
protocol = "-1"
17+
cidr_blocks = ["0.0.0.0/0"]
18+
}
19+
}
20+
21+
resource "aws_security_group" "ecs_sg" {
22+
name = "demo-ecs-tasks-sg"
23+
description = "Allow inbound traffic from the LB"
24+
vpc_id = aws_vpc.main.id
25+
26+
ingress {
27+
from_port = 8080
28+
to_port = 8080
29+
protocol = "tcp"
30+
security_groups = [aws_security_group.lb_sg.id]
31+
}
32+
33+
egress {
34+
from_port = 0
35+
to_port = 0
36+
protocol = "-1"
37+
cidr_blocks = ["0.0.0.0/0"]
38+
}
39+
}
40+
41+
# ECR
42+
resource "aws_ecr_repository" "app_repo" {
43+
name = "demo-assignment-repo"
44+
image_tag_mutability = "MUTABLE"
45+
}
46+
47+
# ALB
48+
resource "aws_lb" "main" {
49+
name = "demo-app-lb"
50+
internal = false
51+
load_balancer_type = "application"
52+
security_groups = [aws_security_group.lb_sg.id]
53+
subnets = [aws_subnet.public_a.id, aws_subnet.public_b.id]
54+
}
55+
56+
# Target Group for ALB
57+
resource "aws_lb_target_group" "main" {
58+
name = "demo-app-tg"
59+
port = 8080
60+
protocol = "HTTP"
61+
vpc_id = aws_vpc.main.id
62+
target_type = "ip"
63+
64+
health_check {
65+
path = "/" # Flask app's login page is at the root
66+
}
67+
}
68+
69+
# Listener for ALB on port 80
70+
resource "aws_lb_listener" "http" {
71+
load_balancer_arn = aws_lb.main.arn
72+
port = 80
73+
protocol = "HTTP"
74+
75+
default_action {
76+
type = "forward"
77+
target_group_arn = aws_lb_target_group.main.arn
78+
}
79+
}
80+
81+
# ECS Cluster
82+
resource "aws_ecs_cluster" "main" {
83+
name = "demo-cluster"
84+
}
85+
86+
resource "aws_iam_role" "ecs_task_execution_role" {
87+
name = "demo-ecs-task-execution-role"
88+
assume_role_policy = jsonencode({
89+
Version = "2012-10-17"
90+
Statement = [{
91+
Action = "sts:AssumeRole"
92+
Effect = "Allow"
93+
Principal = {
94+
Service = "ecs-tasks.amazonaws.com"
95+
}
96+
}]
97+
})
98+
}
99+
100+
101+
resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {
102+
role = aws_iam_role.ecs_task_execution_role.name
103+
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
104+
}
105+
106+
# CloudWatch Log Group
107+
resource "aws_cloudwatch_log_group" "app_logs" {
108+
name = "/ecs/demo-app"
109+
}
110+
111+
# ECS Task Definition
112+
resource "aws_ecs_task_definition" "app_task" {
113+
family = "demo-app-task"
114+
network_mode = "awsvpc"
115+
requires_compatibilities = ["FARGATE"]
116+
cpu = 256
117+
memory = 512
118+
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
119+
120+
container_definitions = jsonencode([
121+
{
122+
name = "demo-app-container"
123+
image = var.docker_image_url
124+
essential = true
125+
portMappings = [
126+
{
127+
containerPort = 8080
128+
hostPort = 8080
129+
}
130+
]
131+
logConfiguration = {
132+
logDriver = "awslogs"
133+
options = {
134+
"awslogs-group" = aws_cloudwatch_log_group.app_logs.name
135+
"awslogs-region" = "ap-south-1"
136+
"awslogs-stream-prefix" = "ecs"
137+
}
138+
}
139+
}
140+
])
141+
}
142+
143+
# ECS Service
144+
resource "aws_ecs_service" "main" {
145+
name = "demo-app-service"
146+
cluster = aws_ecs_cluster.main.id
147+
task_definition = aws_ecs_task_definition.app_task.arn
148+
desired_count = 1
149+
launch_type = "FARGATE"
150+
151+
network_configuration {
152+
subnets = [aws_subnet.public_a.id, aws_subnet.public_b.id]
153+
security_groups = [aws_security_group.ecs_sg.id]
154+
assign_public_ip = true
155+
}
156+
157+
load_balancer {
158+
target_group_arn = aws_lb_target_group.main.arn
159+
container_name = "demo-app-container"
160+
container_port = 8080
161+
}
162+
163+
depends_on = [aws_lb_listener.http]
164+
}
165+
166+
output "app_url" {
167+
value = aws_lb.main.dns_name
168+
}

iac/main.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
terraform {
2+
required_providers {
3+
aws = {
4+
source = "hashicorp/aws"
5+
version = "~> 6.0"
6+
}
7+
}
8+
9+
backend "s3" {
10+
bucket = "aravind-terraform-state-bucket-ap-south-1"
11+
key = "DevOpsEngineerDemoAssignment/terraform.tfstate"
12+
region = "ap-south-1"
13+
}
14+
}
15+
16+
provider "aws" {
17+
region = "ap-south-1"
18+
}

0 commit comments

Comments
 (0)