Skip to content
Open

Lab03 #2432

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
87 changes: 87 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: Python CI (app_python)

on:
push:
branches: ["master"]
paths:
- "app_python/**"
- ".github/workflows/python-ci.yml"
pull_request:
paths:
- "app_python/**"
- ".github/workflows/python-ci.yml"

concurrency:
group: python-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test-and-lint:
runs-on: ubuntu-latest
defaults:
run:
working-directory: app_python

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: "pip"
cache-dependency-path: |
app_python/requirements.txt
app_python/requirements-dev.txt

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt -r requirements-dev.txt

- name: Lint (ruff)
run: |
ruff check .

- name: Run tests (pytest)
run: |
pytest -q

- name: Install Snyk CLI
run: npm install -g snyk

- name: Snyk scan (dependencies)
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: snyk test --file=requirements.txt --severity-threshold=high || true


docker-build-and-push:
runs-on: ubuntu-latest
needs: test-and-lint
if: github.event_name == 'push' && github.ref == 'refs/heads/master'

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Generate CalVer version
run: |
echo "VERSION=$(date -u +%Y.%m.%d)-${GITHUB_RUN_NUMBER}" >> $GITHUB_ENV

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: ./app_python
file: ./app_python/Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:${{ env.VERSION }}
${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:latest
13 changes: 13 additions & 0 deletions app_python/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
pycache/
*.py[cod]
venv/
.env
*.log

.vscode/
.idea/
.git
.gitignore

docs/
tests/
14 changes: 14 additions & 0 deletions app_python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Python
__pycache__/
*.py[cod]
venv/
.env
*.log

# IDE
.vscode/
.idea/

# OS
.DS_Store

20 changes: 20 additions & 0 deletions app_python/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

WORKDIR /app

RUN adduser --disabled-password --gecos "" appuser

COPY requirements.txt .

RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

USER appuser

EXPOSE 5000

CMD ["python", "app.py"]
80 changes: 80 additions & 0 deletions app_python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# DevOps Info Service (FastAPI)

[![Python CI (app_python)](https://github.com/fayz131/DevOps-Core-Course/actions/workflows/python-ci.yml/badge.svg)](https://github.com/fayzullin/DevOps-Core-Course/actions/workflows/python-ci.yml)


## Overview
DevOps Info Service is a web application that provides information about the running service and the system it is running on. The application is designed as a foundation for future DevOps labs, including containerization, CI/CD, and monitoring.

## Prerequisites
- Python 3.11 or newer
- pip
- Python virtual environment (venv)

## Installation
Navigate to the application directory:

cd app_python

Create and activate a virtual environment:

python3 -m venv venv
source venv/bin/activate

Install dependencies:

pip install -r requirements.txt

## Running the Application
Start the application:

python app.py

Run with custom configuration:

HOST=127.0.0.1 PORT=8080 python app.py

## API Endpoints

GET /
Returns service, system, runtime, and request information.

GET /health
Returns application health status and uptime.

## Configuration

Environment variables:

HOST — server host (default: 0.0.0.0)
PORT — server port (default: 5000)

## Docker

### Build image

```bash
docker build -t devops-info-service:lab2 .
```

Run container
```bash
docker run --rm -p 5000:5000 devops-info-service:lab2
```

From Docker Hub
```bash
docker pull fayzullin/devops-info-service:lab2
docker run --rm -p 5000:5000 fayzullin/devops-info-service:lab2
```

## Testing

Install dev dependencies and run tests:

```bash
pip install -r requirements.txt -r requirements-dev.txt
pytest
```


122 changes: 122 additions & 0 deletions app_python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"""
DevOps Info Service
FastAPI web application providing system and runtime information.
"""

import os
import socket
import platform
import logging
from datetime import datetime, timezone

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import uvicorn


HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", 5000))

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
)
logger = logging.getLogger(__name__)


START_TIME = datetime.now(timezone.utc)
app = FastAPI(title="DevOps Info Service")

logger.info("Application initialized")


def get_uptime():
"""Calculate application uptime."""
delta = datetime.now(timezone.utc) - START_TIME
seconds = int(delta.total_seconds())
hours = seconds // 3600
minutes = (seconds % 3600) // 60
return seconds, f"{hours} hours, {minutes} minutes"


def get_system_info():
"""Collect system information."""
return {
"hostname": socket.gethostname(),
"platform": platform.system(),
"platform_version": platform.release(),
"architecture": platform.machine(),
"cpu_count": os.cpu_count(),
"python_version": platform.python_version(),
}


@app.get("/")
async def index(request: Request):
"""Main endpoint returning service and system information."""
logger.info("Handling request to '/'")

uptime_seconds, uptime_human = get_uptime()

return {
"service": {
"name": "devops-info-service",
"version": "1.0.0",
"description": "DevOps course info service",
"framework": "FastAPI",
},
"system": get_system_info(),
"runtime": {
"uptime_seconds": uptime_seconds,
"uptime_human": uptime_human,
"current_time": datetime.now(timezone.utc).isoformat(),
"timezone": "UTC",
},
"request": {
"client_ip": request.client.host,
"user_agent": request.headers.get("user-agent"),
"method": request.method,
"path": request.url.path,
},
"endpoints": [
{"path": "/", "method": "GET", "description": "Service information"},
{"path": "/health", "method": "GET", "description": "Health check"},
],
}


@app.get("/health")
async def health():
"""Health check endpoint for monitoring."""
logger.info("Health check requested")

uptime_seconds, _ = get_uptime()
return {
"status": "healthy",
"timestamp": datetime.now(timezone.utc).isoformat(),
"uptime_seconds": uptime_seconds,
}


@app.exception_handler(404)
async def not_found(request: Request, exc):
"""Handle 404 errors."""
return JSONResponse(
status_code=404,
content={"error": "Not Found", "message": "Endpoint does not exist"},
)


@app.exception_handler(500)
async def internal_error(request: Request, exc):
"""Handle unexpected server errors."""
logger.error(f"Internal server error: {exc}")
return JSONResponse(
status_code=500,
content={"error": "Internal Server Error", "message": "An unexpected error occurred"},
)

if __name__ == "__main__":
logger.info(f"Starting server on {HOST}:{PORT}")
uvicorn.run("app:app", host=HOST, port=PORT)

Loading