diff --git a/app_python/.gitignore b/app_python/.gitignore new file mode 100644 index 0000000000..ba1db0ed69 --- /dev/null +++ b/app_python/.gitignore @@ -0,0 +1,12 @@ +# Python +__pycache__/ +*.py[cod] +venv/ +*.log + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store diff --git a/app_python/README.md b/app_python/README.md new file mode 100644 index 0000000000..36b6327e4b --- /dev/null +++ b/app_python/README.md @@ -0,0 +1,40 @@ +# FastAPi web api + +## Overview + +This app have been build for the lab01 of the "Devops Core course". It give service and +system information and do health check for monitoring + +## Prerequisites + +```markdown +python 3.14.4 +``` + +## Installation + +```bash +python -m venv venv +source venv/bin/activate +pip install -r requirements.txt +``` + +## Running the Application + +```bash +python app.py +# Or with custom config +PORT=4999 HOST 127.0.0.1 python app.py + ``` + +## API Endpoints + +- GET / - Service and system information +- GET /health - Health check + +## Configuration + +|HOST|PORT| +|--------|----------| +|Host ip|tcp port number| + diff --git a/app_python/__pycache__/app.cpython-314.pyc b/app_python/__pycache__/app.cpython-314.pyc new file mode 100644 index 0000000000..96da8501f8 Binary files /dev/null and b/app_python/__pycache__/app.cpython-314.pyc differ diff --git a/app_python/app.py b/app_python/app.py new file mode 100644 index 0000000000..c24b003e4b --- /dev/null +++ b/app_python/app.py @@ -0,0 +1,149 @@ +import os +import json +import logging +import socket + + + +from datetime import datetime + +import uvicorn +import platform +from fastapi import FastAPI, Request, HTTPException +from fastapi.responses import JSONResponse + +#------------------Fonctions--------------------------------- +start_time = datetime.now() + + +def getSystemInformation(): + hostname = socket.gethostname() + plaform_version = platform.version() + platform_name = platform.system() + architecture = platform.machine() + python_version = platform.python_version() + cpu_count = os.cpu_count() + + return { + "hostname": hostname, + "platform": platform_name, + "platform_version": plaform_version, + "architecture": architecture, + "cpu_count": cpu_count, + "python_version": python_version + } + + +def getService(): + return { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + } + + +def get_uptime(): + delta = datetime.now() - start_time + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + return { + 'seconds': seconds, + 'human': f"{hours} hours, {minutes} minutes" + } + + +def getRuntime(): + delta = datetime.now() - start_time + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + + # la timezone + time_now = datetime.now() + local_now = time_now.astimezone() + local_tz = local_now.tzinfo + local_tzname = local_tz.tzname(local_now) + + return { + "uptime_seconds": seconds, + "uptime_human": f"{hours} hours, {minutes} minutes", + "current_time": time_now, + "timezone": local_tzname + } + + +def getRequestInfo(request: Request): + return { + "client_ip": request.client.host, + "user_agent": request.headers.get('user-agent'), + "method": request.method, + "path": request.url.path + } + + +#--------------------logging---------------------------- +def getLogging(request: Request): + logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + filename="debug.log" + ) + logger = logging.getLogger(__name__) + + logger.info('Application starting...') + logger.info('Man-debugger') + logger.debug(f'Request: Ola{request.method} {request.url.path}') + + +#--------------------app---------------------------------------- + +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' +app = FastAPI(debug=DEBUG) + +@app.get("/") +def read_root(request:Request): + getLogging(request) + return { + "service": getService(), + "system": getSystemInformation(), + "runtime": getRuntime(), + "request": getRequestInfo(request), + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] + } + +@app.get("/health") +def read_health(request: Request): + getLogging(request) + return { + 'status': 'healthy', + 'timestamp': datetime.now().isoformat(), + 'uptime_seconds': get_uptime()['seconds'] + } +#---------------------Error handlers---------------- + +@app.exception_handler(404) +def custom_404_handler(request: Request, __): + getLogging(request) + return JSONResponse({ + "error": "404 not Found", + "message": "Endpoint does not exist" + }) + +@app.exception_handler(500) +def custom_500_handler(request: Request, __): + getLogging(request) + return JSONResponse({ + "error": "505 internal Server Error", + "message": "An unexpected error occurred" + }) + + +if __name__ == "__main__": + uvicorn.run(app, host=HOST, port=PORT) diff --git a/app_python/app.py~ b/app_python/app.py~ new file mode 100644 index 0000000000..b2622f8a50 --- /dev/null +++ b/app_python/app.py~ @@ -0,0 +1,136 @@ +import os +import socket +from datetime import datetime + +import uvicorn +import platform +from fastapi import FastAPI, Request, HTTPException +from fastapi.responses import HTMLResponse + + +#------------------constantes---------------------------- +response_404 = """ + + + + Not Found + + +

The file you requested was not found.

+ + +""" + +#------------------Fonctions--------------------------------- +start_time = datetime.now() + + +def getSystemInformation(): + hostname = socket.gethostname() + plaform_version = platform.version() + platform_name = platform.system() + architecture = platform.machine() + python_version = platform.python_version() + cpu_count = os.cpu_count() + + return { + "hostname": hostname, + "platform": platform_name, + "platform_version": plaform_version, + "architecture": architecture, + "cpu_count": cpu_count, + "python_version": python_version + } + + +def getService(): + return { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + } + + +def get_uptime(): + delta = datetime.now() - start_time + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + return { + 'seconds': seconds, + 'human': f"{hours} hours, {minutes} minutes" + } + + +def getRuntime(): + delta = datetime.now() - start_time + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + + # la timezone + time_now = datetime.now() + local_now = time_now.astimezone() + local_tz = local_now.tzinfo + local_tzname = local_tz.tzname(local_now) + + return { + "uptime_seconds": seconds, + "uptime_human": f"{hours} hours, {minutes} minutes", + "current_time": time_now, + "timezone": local_tzname + } + + +def getRequestInfo(request: Request): + return { + "client_ip": request.client.host, + "user_agent": request.headers.get('user-agent'), + "method": request.method, + "path": request.url.path + } + + + +#--------------------app---------------------------------------- + +app = FastAPI() +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' + +@app.get("/") +def read_root(request:Request): + return { + "service": getService(), + "system": getSystemInformation(), + "runtime": getRuntime(), + "request": getRequestInfo(request), + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] + } + +@app.get("/health") +def read_health(): + return { + 'status': 'healthy', + 'timestamp': datetime.now().isoformat(), + 'uptime_seconds': get_uptime()['seconds'] + } +#---------------------Error handlers---------------- + +@app.exception_handler(404) +def custom_404_handler(_, __): + return HTMLResponse(response_404) + +@app.exception_handler(500) +def custom_500_handler(_, __): + return { + "error": "505 internal Server Error", + "message": "" + } +if __name__ == "__main__": + uvicorn.run(app, host=HOST, port=PORT) diff --git a/app_python/docs/LAB01.md b/app_python/docs/LAB01.md new file mode 100644 index 0000000000..ba2fcb8b2d --- /dev/null +++ b/app_python/docs/LAB01.md @@ -0,0 +1,44 @@ +# LAB01 + +## Framework Selection + +I choose fastapi because I wanted to, plus I had already tried Flask some times ago. + +## Best practices Applied + +- Clear function names + +![Image function](screenshots/cleanfunction) + +- Proper import grouping + +![Image proper import](screenshots/properImport) + +- Follow PEP 8 + +![Image respect PEP 8](screenshots/pep8) + +## API examples + +- **/ route** + +![Image respect PEP 8](screenshots/api_test_root) + +- **/health route** + +![Image respect PEP 8](screenshots/api_test_health) + +## Testing Evidences + +![Image respect PEP 8](screenshots/root_route) + +![Image respect PEP 8](screenshots/health_route) + +## Challenges and solutions + +The main challenge was writing the documentation, I'm not really use to be detailed in my readmes. So the instructions in the lab helped a lot. + +## Github Community + +Starring repositories and following developers help in adding visibility to projects and help developers to get credit for their works. + diff --git a/app_python/docs/screenshots/api_test_health b/app_python/docs/screenshots/api_test_health new file mode 100644 index 0000000000..d5b6effbf4 Binary files /dev/null and b/app_python/docs/screenshots/api_test_health differ diff --git a/app_python/docs/screenshots/api_test_root b/app_python/docs/screenshots/api_test_root new file mode 100644 index 0000000000..e4e6201c83 Binary files /dev/null and b/app_python/docs/screenshots/api_test_root differ diff --git a/app_python/docs/screenshots/cleanfunction b/app_python/docs/screenshots/cleanfunction new file mode 100644 index 0000000000..4faa20c063 Binary files /dev/null and b/app_python/docs/screenshots/cleanfunction differ diff --git a/app_python/docs/screenshots/health_route b/app_python/docs/screenshots/health_route new file mode 100644 index 0000000000..fcc476ab57 Binary files /dev/null and b/app_python/docs/screenshots/health_route differ diff --git a/app_python/docs/screenshots/pep8 b/app_python/docs/screenshots/pep8 new file mode 100644 index 0000000000..c91aef3715 Binary files /dev/null and b/app_python/docs/screenshots/pep8 differ diff --git a/app_python/docs/screenshots/properImport b/app_python/docs/screenshots/properImport new file mode 100644 index 0000000000..fee7949548 Binary files /dev/null and b/app_python/docs/screenshots/properImport differ diff --git a/app_python/docs/screenshots/root_route b/app_python/docs/screenshots/root_route new file mode 100644 index 0000000000..52df8ef990 Binary files /dev/null and b/app_python/docs/screenshots/root_route differ diff --git a/app_python/requirements.txt b/app_python/requirements.txt new file mode 100644 index 0000000000..45bdb597cc --- /dev/null +++ b/app_python/requirements.txt @@ -0,0 +1,3 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 # Includes performance extras +