Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
83 changes: 10 additions & 73 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,6 @@ jobs:
name: Run Django Tests
runs-on: ubuntu-latest

services:
postgres:
image: postgres:13
env:
POSTGRES_DB: store
POSTGRES_USER: postgres
POSTGRES_PASSWORD: Admin
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- name: Checkout Repository
uses: actions/checkout@v3
Expand All @@ -50,7 +35,6 @@ jobs:
echo "AT_USERNAME=$(echo '${{ secrets.AT_USERNAME }}' | base64 -d)" >> $GITHUB_ENV
echo "AT_SHORT_CODE=$(echo '${{ secrets.AT_SHORT_CODE }}' | base64 -d)" >> $GITHUB_ENV
echo "AT_API_KEY=$(echo '${{ secrets.AT_API_KEY }}' | base64 -d)" >> $GITHUB_ENV


- name: Run Tests
env:
Expand All @@ -68,7 +52,7 @@ jobs:
[ -z "$DB_USER" ] && echo "DB_USER is not set" || echo "DB_USER is set"
[ -z "$DB_HOST" ] && echo "DB_HOST is not set" || echo "DB_HOST is set"
[ -z "$DB_PORT" ] && echo "DB_PORT is not set" || echo "DB_PORT is set"
pytest -v
pytest -v

build-and-push:
name: Build and Push Docker Image
Expand All @@ -86,8 +70,8 @@ jobs:

- name: Build and Push Docker Image
run: |
docker build -t ${{ secrets.DOCKER_USERNAME }}/ecommerce-app:latest .
docker push ${{ secrets.DOCKER_USERNAME }}/ecommerce-app:latest
docker build -t ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest .
docker push ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest

deploy:
name: Deploy to Kubernetes
Expand All @@ -103,8 +87,6 @@ jobs:
with:
version: 'latest'



- name: Validate KUBECONFIG Secret
run: |
if [ -z "${{ secrets.KUBECONFIG }}" ]; then
Expand All @@ -114,62 +96,17 @@ jobs:

- name: Configure Kubeconfig
run: |
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig.yaml || { echo "Error decoding kubeconfig"; exit 1; }
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig.yaml
export KUBECONFIG=$(pwd)/kubeconfig.yaml
echo "KUBECONFIG=$(pwd)/kubeconfig.yaml" >> $GITHUB_ENV
if [ ! -f kubeconfig.yaml ]; then
echo "Error: kubeconfig.yaml not created!"
exit 1
fi

- name: Start Minikube
run: |
echo "Checking if Minikube is running..."
if ! minikube status; then
echo "Starting Minikube..."
minikube start --wait=all --driver=docker
echo "Minikube started successfully."
else
echo "Minikube is already running."
fi

- name: Configure Minikube Without Certificate Check
- name: Deploy Postgres to Kubernetes
run: |
kubectl config set-cluster minikube \
--server=https://192.168.49.2:8443 \
--insecure-skip-tls-verify=true
kubectl config set-context minikube --cluster=minikube --user=minikube
kubectl config use-context minikube

kubectl apply -f k8s/postgres.yaml
kubectl rollout status statefulset/postgres --timeout=120s

- name: Test Minikube Connection
run: |
minikube status
minikube ip
kubectl cluster-info
minikube logs
kubectl get nodes

- name: Clean Up Existing Deployment
run: |
kubectl scale deployment ecommerce-app --replicas=0 || true
kubectl wait --for=delete pod -l app=ecommerce-app --timeout=30s || true

kubectl delete deployment ecommerce-app --ignore-not-found=true
kubectl wait --for=delete deployment/ecommerce-app --timeout=60s || true
kubectl delete pods -l app=ecommerce-app --force --grace-period=0 || true

echo "Verifying all pods are gone..."
kubectl get pods -l app=ecommerce-app

- name: Recreate Deployment
- name: Deploy Application to Kubernetes
run: |
kubectl apply -f k8s/deployment.yaml
kubectl set image deployment/ecommerce-app ecommerce-app=${{ secrets.DOCKER_USERNAME }}/ecommerce-app:latest

kubectl rollout status deployment/ecommerce-app
echo "Checking deployment status..."
kubectl describe deployment ecommerce-app

echo "Checking pod status..."
kubectl get pods -l app=ecommerce-app -o wide
kubectl set image deployment/ecommerce-app ecommerce-app=${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest
kubectl rollout status deployment/ecommerce-app --timeout=120s
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ RUN pip install --no-cache-dir -r requirements.txt

COPY . .

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

EXPOSE 8000

ENTRYPOINT ["/entrypoint.sh"]
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "src.wsgi:application"]
4 changes: 3 additions & 1 deletion api/urls.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import OrderViewSet, ProductViewSet, CategoryViewSet,CustomerViewSet
from .views import OrderViewSet, ProductViewSet, CategoryViewSet,CustomerViewSet, health_check

router = DefaultRouter()
router.register(r'products', ProductViewSet, basename='products')
router.register(r'orders', OrderViewSet, basename='orders')
router.register(r'categories', CategoryViewSet, basename='categories')
router.register(r'customers',CustomerViewSet, basename='customers')


app_name = 'api'

urlpatterns = [
path('', include(router.urls)),
path('health/', health_check, name='health_check'),
]
36 changes: 35 additions & 1 deletion api/views.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
from django.db import connections
from django.http import JsonResponse
from django.shortcuts import render
from rest_framework.decorators import action
from rest_framework import viewsets, status
from rest_framework.permissions import IsAuthenticated,AllowAny
from rest_framework.response import Response
from django.db.models import Avg
from django.db.utils import OperationalError

from api.auth import Auth0Authentication
from api.models.accounts import User
Expand Down Expand Up @@ -74,4 +77,35 @@ def perform_create(self, serializer):

class CategoryViewSet(viewsets.ModelViewSet):
queryset = Category.objects.all()
serializer_class = CategorySerializer
serializer_class = CategorySerializer


class HealthCheckView(viewsets.ViewSet):
"""
Health check endpoint to verify application and database connectivity
"""
def get(self, request):
try:
connections['default'].cursor()
except OperationalError:
return JsonResponse({'status': 'error', 'message': 'Database connection failed'}, status=500)

return JsonResponse({
'status': 'healthy',
'message': 'Application is running and database is accessible'
})

#convert this to class
def health_check(request):
"""
Health check endpoint to verify application and database connectivity
"""
try:
connections['default'].cursor()
except OperationalError:
return JsonResponse({'status': 'error', 'message': 'Database connection failed'}, status=500)

return JsonResponse({
'status': 'healthy',
'message': 'Application is running and database is accessible'
})
9 changes: 9 additions & 0 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/bin/sh

echo "Waiting for database to be ready..."
while ! nc -z "$DATABASE_HOST" "$DATABASE_PORT"; do
sleep 2
done

echo "Database is up, starting the application..."
exec "$@"
22 changes: 15 additions & 7 deletions k8s/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ spec:
labels:
app: ecommerce-app
spec:
initContainers:
- name: wait-for-db
image: busybox
command: ['sh', '-c', 'until nc -z postgres-service 5432; do echo waiting for db; sleep 2; done;']
containers:
- name: ecommerce-app
image: jwiki/e-commerce-service:latest
image: ${{ secrets.DOCKER_USERNAME }}/e-commerce-service:latest
imagePullPolicy: Always
ports:
- containerPort: 8000
envFrom:
Expand All @@ -33,13 +38,16 @@ spec:
cpu: "500m"
readinessProbe:
httpGet:
path: /
path: /api/v1/health/
port: 8000
initialDelaySeconds: 10
periodSeconds: 3
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /
path: /api/v1/health/
port: 8000
initialDelaySeconds: 15
periodSeconds: 20
initialDelaySeconds: 45
periodSeconds: 20
failureThreshold: 5
9 changes: 8 additions & 1 deletion postgres.yaml → k8s/postgres.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ spec:
spec:
containers:
- name: postgres
image: postgres:13
image: postgres:14
ports:
- containerPort: 5432
envFrom:
Expand All @@ -35,6 +35,13 @@ spec:
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-storage
readinessProbe:
exec:
command: ["pg_isready", "-U", "postgres"]
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
volumes:
- name: postgres-storage
persistentVolumeClaim:
Expand Down
2 changes: 1 addition & 1 deletion src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['*']


# Application definition
Expand Down
Loading