diff --git a/app_go/README.md b/app_go/README.md new file mode 100644 index 0000000000..292daf65ab --- /dev/null +++ b/app_go/README.md @@ -0,0 +1,97 @@ +# DevOps Info Service (Go) +## Overview +A Go-based web service designed to furnish details about itself and its operational environment. This compiled version of the service is optimized for containerization and multi-stage Docker builds, offering improved performance and smaller deployment footprints compared to interpreted languages. + +## Prerequisites +- Go 1.21 or higher +- Git (for dependency management) + +## Installation +1. Clone repository: + +```bash +# Clone the project +git clone https://github.com/s3rap1s/DevOps-Core-Course.git +cd DevOps-Core-Course/app_go + +# Download dependencies +go mod download + +# Build the application +go build -o devops-info-service +``` + +## Running the Application +```bash +# Default configuration +./devops-info-service + +# With custom port +PORT=8080 ./devops-info-service + +# With custom port and host +HOST=127.0.0.1 PORT=3000 ./devops-info-service + +# Run directly without building +go run main.go +Building for Different Platforms +bash +# Build for current platform +go build -o devops-info-service +``` + +## API Endpoints +### GET / +Return comprehensive service and system information: + +```json +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Go" + }, + "system": { + "hostname": "my-laptop", + "platform": "linux", + "platform_version": "Linux Kernel", + "architecture": "amd64", + "cpu_count": 8, + "go_version": "go1.21.4" + }, + "runtime": { + "uptime_seconds": 3600, + "uptime_human": "1 hour, 0 minutes", + "current_time": "2026-01-07T14:30:00.000Z", + "timezone": "UTC" + }, + "request": { + "client_ip": "127.0.0.1:12345", + "user_agent": "curl/7.81.0", + "method": "GET", + "path": "/" + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] +} +``` + +### GET /health +Simple health endpoint for monitoring: + +```json +{ + "status": "healthy", + "timestamp": "2024-01-15T14:30:00.000Z", + "uptime_seconds": 3600 +} +``` + +## Configuration +| Variable | Default | Description | +| -------- | --------- | ---------------------------- | +| `HOST` | `0.0.0.0` | Network interface to bind | +| `PORT` | `5000` | Port to listen on | \ No newline at end of file diff --git a/app_go/docs/GO.md b/app_go/docs/GO.md new file mode 100644 index 0000000000..b42a88d6a8 --- /dev/null +++ b/app_go/docs/GO.md @@ -0,0 +1,18 @@ +# Language Justification +## My Choice: Go +I selected **Go** as the compiled language for the bonus task implementation. Here's why: + +**Comparison Table:** + +| Criteria | Go | Rust | Java | C# | +|----------|----|------|------|----| +|Learning Curve | Low | High | Medium | Medium | +|Development Speed | High | Low | Medium | Medium | +|Standard Library | Excellent | Good | Extensive | Extensive | +|Performance | Excellent | Outstanding | Good | Good | +|Binary Size | ~7 MB | ~3 MB | ~40 MB | ~30 MB | +|Memory Safety | GC | Compile-time | GC | GC | +| **Choice for Bonus** | **✓** | | | | + +**Justification:** +Go offers the perfect balance for a DevOps service: it compiles to a single static binary with no runtime dependencies, has excellent concurrency support, and provides a rich standard library including HTTP server functionality. Its simplicity and fast compilation make it ideal for the iterative development required in this course. Go is also widely used in the DevOps ecosystem (Docker, Kubernetes, Prometheus), making it a relevant choice. \ No newline at end of file diff --git a/app_go/docs/LAB01.md b/app_go/docs/LAB01.md new file mode 100644 index 0000000000..4b93018d26 --- /dev/null +++ b/app_go/docs/LAB01.md @@ -0,0 +1,293 @@ +# Lab 1 — DevOps Info Service: Web Application Development on Go +## Language Selection +### My Choice: Go +I selected **Go** as the compiled language for the bonus task implementation. Here's why: + +**Comparison Table:** + +| Criteria | Go | Rust | Java | C# | +|----------|----|------|------|----| +|Learning Curve | Low | High | Medium | Medium | +|Development Speed | High | Low | Medium | Medium | +|Standard Library | Excellent | Good | Extensive | Extensive | +|Performance | Excellent | Outstanding | Good | Good | +|Binary Size | ~7 MB | ~3 MB | ~40 MB | ~30 MB | +|Memory Safety | GC | Compile-time | GC | GC | +| **Choice for Bonus** | **✓** | | | | + +**Justification:** +Go offers the perfect balance for a DevOps service: it compiles to a single static binary with no runtime dependencies, has excellent concurrency support, and provides a rich standard library including HTTP server functionality. Its simplicity and fast compilation make it ideal for the iterative development required in this course. Go is also widely used in the DevOps ecosystem (Docker, Kubernetes, Prometheus), making it a relevant choice. + +## Best Practices Applied +### 1. Clean Code Organization +```go +// Clear imports grouping +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "runtime" + "time" +) + +// Descriptive function names +func getSystemInfo() System { + hostname, err := os.Hostname() + if err != nil { + hostname = "unknown" + } + + return System{ + Hostname: hostname, + Platform: runtime.GOOS, + PlatformVersion: getOSVersion(), + Architecture: runtime.GOARCH, + CPUCount: runtime.NumCPU(), + GoVersion: runtime.Version(), + } +} +``` +**Importance:** Clean organization with clear separation of concerns makes the code maintainable and testable. Following Go conventions (camelCase, exported/unexported identifiers) ensures consistency. + +### 2. Comprehensive Error Handling +```go +func notFoundHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{ + "error": "Not Found", + "message": "Endpoint does not exist", + }) + + log.Printf("404 Not Found: %s", r.URL.Path) +} +``` +**Importance:** Proper error handling prevents application crashes and provides meaningful feedback to API consumers. Each error type returns appropriate HTTP status codes and structured JSON responses. + +### 3. Structured Logging +```go +func main() { + // Read environment variables + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + host := os.Getenv("HOST") + if host == "" { + host = "0.0.0.0" + } + + log.Printf("Starting DevOps Info Service on %s:%s", host, port) + log.Fatal(http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), nil)) +} +``` +**Importance:** Logging provides visibility into application behavior and startup configuration. The standard log package is sufficient for this simple service, though larger applications might use structured logging libraries. + +### 4. Configuration via Environment Variables +```go +port := os.Getenv("PORT") +if port == "" { + port = "5000" +} +``` +**Importance:** Following the 12-factor app methodology, configuration via environment variables makes the application portable across different environments without recompilation. + +### 5. Minimal Dependencies +```go +// go.mod - only Go standard library is used +module devops-info-service + +go 1.21 +``` +**Importance:** Using only the standard library eliminates dependency management overhead and reduces security vulnerabilities. The resulting binary is self-contained. + +### 6. Static Typing and Compile-Time Safety +```go +type ServiceInfo struct { + Service Service `json:"service"` + System System `json:"system"` + Runtime Runtime `json:"runtime"` + Request Request `json:"request"` + Endpoints []Endpoint `json:"endpoints"` +} +``` +**Importance:** Static typing catches many errors at compile time rather than runtime, improving reliability. Struct tags provide clear mapping between Go structs and JSON output. + +## API Documentation +### Endpoint 1: GET / +**Description:** Returns comprehensive service information, system details, runtime data, and request metadata. + +**Request:** +```bash +curl http://localhost:8080/ +``` +**Response (example):** +```json +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Go" + }, + "system": { + "hostname": "ubuntu-dev", + "platform": "linux", + "platform_version": "Linux Kernel", + "architecture": "amd64", + "cpu_count": 8, + "go_version": "go1.21.4" + }, + "runtime": { + "uptime_seconds": 125, + "uptime_human": "0 hours, 2 minutes", + "current_time": "2026-01-27T10:30:00.000Z", + "timezone": "UTC" + }, + "request": { + "client_ip": "127.0.0.1:54321", + "user_agent": "curl/7.88.1", + "method": "GET", + "path": "/" + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] +} +``` +### Endpoint 2: GET /health +**Description:** Health check endpoint for monitoring system. Always returns HTTP 200 with service status. + +**Request:** +```bash +curl http://localhost:8080/health +``` +**Response:** +```json +{ + "status": "healthy", + "timestamp": "2026-01-27T10:30:00.000Z", + "uptime_seconds": 125 +} +``` +### Testing Commands +1. **Basic endpoint test:** + +```bash +curl http://localhost:8080/ +``` + +2. **Health check test:** +```bash +curl http://localhost:8080/health +``` +3. **Pretty-printed output:** +```bash +curl http://localhost:8080/ | jq . +``` +4. **Custom configuration:** +```bash +PORT=8080 ./devops-info-service +curl http://localhost:8080/health +``` + +5. **Error simulation:** +```bash +curl -v http://localhost:8080/nonexistent +# Should return 404 error +``` +## Build Process +### Compilation +```bash +# Initialize Go module +go mod init devops-info-service + +# Build standard binary +go build -o devops-info-service +``` +###Running +```bash +# Run the compiled binary +./devops-info-service + +# Run with custom configuration +HOST=127.0.0.1 PORT=3000 ./devops-info-service + +# Run directly (without building) +go run main.go +``` +## Testing Evidence +### Main endpoint: +![Main Endpoint](screenshots/01-main-endpoint.png) + +### Health check: +![Health Check](screenshots/02-health-check.png) + +### Formatted output: +![Formatted output](screenshots/03-formatted-output.png) + +## Challenges & Solutions +### Challenge 1: HTTP Handler Registration +**Problem:** Go's http.HandleFunc doesn't allow multiple registrations for the same path, unlike Flask's decorator pattern. + +**Solution:** Implemented a routing check within the main handler: +```go +func mainHandler(w http.ResponseWriter, r *http.Request) { + // Handle only root path + if r.URL.Path != "/" { + notFoundHandler(w, r) + return + } + // ... rest of handler +} +``` +### Challenge 2: Platform Version Detection +**Problem:** Go's standard library doesn't provide detailed OS version information like Python's platform.release(). + +**Solution:** Created a simple mapping function: +```go +func getOSVersion() string { + switch runtime.GOOS { + case "linux": + return "Linux Kernel" + case "darwin": + return "macOS" + case "windows": + return "Windows" + default: + return runtime.GOOS + } +} +``` +### Challenge 3: Uptime Formatting +**Problem:** Converting seconds to human-readable format required manual calculation. + +**Solution:** Implemented a reusable function: +```go +func getUptime() (int, string) { + duration := time.Since(startTime) + seconds := int(duration.Seconds()) + + hours := seconds / 3600 + minutes := (seconds % 3600) / 60 + + return seconds, fmt.Sprintf("%d hours, %d minutes", hours, minutes) +} +``` +### Challenge 4: JSON Serialization +**Problem:** Ensuring proper JSON field naming and null handling. + +**Solution:** Used struct tags and proper initialization: +```go +type System struct { + Hostname string `json:"hostname"` + Platform string `json:"platform"` + PlatformVersion string `json:"platform_version"` + // ... other fields +} +``` + diff --git a/app_go/docs/screenshots/01-main-endpoint.png b/app_go/docs/screenshots/01-main-endpoint.png new file mode 100644 index 0000000000..6acabfa3cf Binary files /dev/null and b/app_go/docs/screenshots/01-main-endpoint.png differ diff --git a/app_go/docs/screenshots/02-health-check.png b/app_go/docs/screenshots/02-health-check.png new file mode 100644 index 0000000000..f905599cf0 Binary files /dev/null and b/app_go/docs/screenshots/02-health-check.png differ diff --git a/app_go/docs/screenshots/03-formatted-output.png b/app_go/docs/screenshots/03-formatted-output.png new file mode 100644 index 0000000000..f4dd6737db Binary files /dev/null and b/app_go/docs/screenshots/03-formatted-output.png differ diff --git a/app_go/go.mod b/app_go/go.mod new file mode 100644 index 0000000000..307ce0d1c5 --- /dev/null +++ b/app_go/go.mod @@ -0,0 +1,3 @@ +module devops-info-service + +go 1.21 diff --git a/app_go/main.go b/app_go/main.go new file mode 100644 index 0000000000..7b0e355b6c --- /dev/null +++ b/app_go/main.go @@ -0,0 +1,205 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" + "os" + "runtime" + "time" +) + +// Data structures +type ServiceInfo struct { + Service Service `json:"service"` + System System `json:"system"` + Runtime Runtime `json:"runtime"` + Request Request `json:"request"` + Endpoints []Endpoint `json:"endpoints"` +} + +type Service struct { + Name string `json:"name"` + Version string `json:"version"` + Description string `json:"description"` + Framework string `json:"framework"` +} + +type System struct { + Hostname string `json:"hostname"` + Platform string `json:"platform"` + PlatformVersion string `json:"platform_version"` + Architecture string `json:"architecture"` + CPUCount int `json:"cpu_count"` + GoVersion string `json:"go_version"` +} + +type Runtime struct { + UptimeSeconds int `json:"uptime_seconds"` + UptimeHuman string `json:"uptime_human"` + CurrentTime string `json:"current_time"` + Timezone string `json:"timezone"` +} + +type Request struct { + ClientIP string `json:"client_ip"` + UserAgent string `json:"user_agent"` + Method string `json:"method"` + Path string `json:"path"` +} + +type Endpoint struct { + Path string `json:"path"` + Method string `json:"method"` + Description string `json:"description"` +} + +type HealthResponse struct { + Status string `json:"status"` + Timestamp string `json:"timestamp"` + UptimeSeconds int `json:"uptime_seconds"` +} + +// Global variables +var startTime time.Time + +func init() { + startTime = time.Now() +} + +// Helper functions +func getSystemInfo() System { + hostname, err := os.Hostname() + if err != nil { + hostname = "unknown" + } + + return System{ + Hostname: hostname, + Platform: runtime.GOOS, + PlatformVersion: getOSVersion(), + Architecture: runtime.GOARCH, + CPUCount: runtime.NumCPU(), + GoVersion: runtime.Version(), + } +} + +func getOSVersion() string { + switch runtime.GOOS { + case "linux": + return "Linux Kernel" + case "darwin": + return "macOS" + case "windows": + return "Windows" + default: + return runtime.GOOS + } +} + +func getUptime() (int, string) { + duration := time.Since(startTime) + seconds := int(duration.Seconds()) + + hours := seconds / 3600 + minutes := (seconds % 3600) / 60 + + return seconds, fmt.Sprintf("%d hours, %d minutes", hours, minutes) +} + +func getCurrentTime() string { + return time.Now().UTC().Format(time.RFC3339) +} + +// HTTP handlers +func mainHandler(w http.ResponseWriter, r *http.Request) { + // Handle only root path + if r.URL.Path != "/" { + notFoundHandler(w, r) + return + } + + systemInfo := getSystemInfo() + uptimeSeconds, uptimeHuman := getUptime() + + response := ServiceInfo{ + Service: Service{ + Name: "devops-info-service", + Version: "1.0.0", + Description: "DevOps course info service", + Framework: "Go", + }, + System: systemInfo, + Runtime: Runtime{ + UptimeSeconds: uptimeSeconds, + UptimeHuman: uptimeHuman, + CurrentTime: getCurrentTime(), + Timezone: "UTC", + }, + Request: Request{ + ClientIP: r.RemoteAddr, + UserAgent: r.UserAgent(), + Method: r.Method, + Path: r.URL.Path, + }, + Endpoints: []Endpoint{ + {Path: "/", Method: "GET", Description: "Service information"}, + {Path: "/health", Method: "GET", Description: "Health check"}, + }, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + + log.Printf("Request: %s %s from %s", r.Method, r.URL.Path, r.RemoteAddr) +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + uptimeSeconds, _ := getUptime() + + response := HealthResponse{ + Status: "healthy", + Timestamp: getCurrentTime(), + UptimeSeconds: uptimeSeconds, + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + + log.Printf("Health check from %s", r.RemoteAddr) +} + +func notFoundHandler(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{ + "error": "Not Found", + "message": "Endpoint does not exist", + }) + + log.Printf("404 Not Found: %s", r.URL.Path) +} + +// Main function +func main() { + // Read environment variables + port := os.Getenv("PORT") + if port == "" { + port = "5000" + } + + host := os.Getenv("HOST") + if host == "" { + host = "0.0.0.0" + } + + // Setup HTTP routes + http.HandleFunc("/", mainHandler) + http.HandleFunc("/health", healthHandler) + + // Start server + addr := fmt.Sprintf("%s:%s", host, port) + log.Printf("Starting DevOps Info Service on %s", addr) + log.Fatal(http.ListenAndServe(addr, nil)) +} \ No newline at end of file diff --git a/app_python/.gitignore b/app_python/.gitignore new file mode 100644 index 0000000000..4de420a8f7 --- /dev/null +++ b/app_python/.gitignore @@ -0,0 +1,12 @@ +# Python +__pycache__/ +*.py[cod] +venv/ +*.log + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store \ No newline at end of file diff --git a/app_python/README.md b/app_python/README.md new file mode 100644 index 0000000000..4eca6a7ec0 --- /dev/null +++ b/app_python/README.md @@ -0,0 +1,97 @@ +# DevOps Info Service + +## Overview +A Python-based web service designed to furnish details about itself and its operational environment. This service serves as a foundation for subsequent experiments in containerization, continuous integration and continuous deployment (CI/CD), monitoring, and deployment processes. + +## Prerequisites +- Python 3.11 or higher +- pip (Python Package manager) + +## Installation +1. Clone repository: +```bash +# Clone the project +git clone https://github.com/s3rap1s/DevOps-Core-Course.git +cd DevOps-Core-Course/app_python + +# Create virtual environment +python3 -m venv venv +source venv/bin/activate # on Linux / macOs or .\venv\Scripts\Activate.ps1 on windows + +# Install dependencies +pip install -r requirements.txt +``` + +## Running the Application + +```bash +# Default configuration +python app.py + +# With custom port +PORT=8080 python app.py + +# With custom port and host +HOST=127.0.0.1 PORT=3000 python app.py +``` + +## API Endpoints + +### `GET /` +Return comprehensive service and system information: + +```json +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + }, + "system": { + "hostname": "my-laptop", + "platform": "Linux", + "platform_version": "Ubuntu 24.04", + "architecture": "x86_64", + "cpu_count": 8, + "python_version": "3.13.1" + }, + "runtime": { + "uptime_seconds": 3600, + "uptime_human": "1 hour, 0 minutes", + "current_time": "2026-01-07T14:30:00.000Z", + "timezone": "UTC" + }, + "request": { + "client_ip": "127.0.0.1", + "user_agent": "curl/7.81.0", + "method": "GET", + "path": "/" + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] +} +``` + +### `GET /health` + +Simple health endpoint for monitoring: + +```json +{ + "status": "healthy", + "timestamp": "2024-01-15T14:30:00.000Z", + "uptime_seconds": 3600 +} +``` + + +## Configuration + +| Variable | Default | Description | +| -------- | --------- | ---------------------------- | +| `HOST` | `0.0.0.0` | Network interface to bind | +| `PORT` | `5000` | Port to listen on | +| `DEBUG` | `false` | Enable debug mode | diff --git a/app_python/app.py b/app_python/app.py new file mode 100644 index 0000000000..1157cccc35 --- /dev/null +++ b/app_python/app.py @@ -0,0 +1,126 @@ +""" +DevOps Info Service +Main application module +""" +import os +import socket +import platform +import logging +from datetime import datetime, timezone +from flask import Flask, jsonify, request + +# Flask app initialization +app = Flask(__name__) + +# Configuration via environment variables +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' + +# Application startup time +START_TIME = datetime.now(timezone.utc) + +# Setting up logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +def get_system_info(): + """Collecting information about the system. + + Returns: + dict: System configuration + """ + return { + 'hostname': socket.gethostname(), + 'platform': platform.system(), + 'platform_version': platform.release(), + 'architecture': platform.machine(), + 'cpu_count': os.cpu_count() or 0, + 'python_version': platform.python_version() + } + +def get_uptime(): + """Calculating the running time of the application. + + Returns: + dict: Uptime in seconds and human-readable format + """ + delta = datetime.now(timezone.utc) - START_TIME + seconds = int(delta.total_seconds()) + hours = seconds // 3600 + minutes = (seconds % 3600) // 60 + return { + 'seconds': seconds, + 'human': f"{hours} hours, {minutes} minutes" + } + +@app.route('/') +def index(): + """The main endpoint - Information about the service and the system.""" + client_ip = request.remote_addr + user_agent = request.headers.get('User-Agent', 'Unknown') + + # Forming a response + response = { + 'service': { + 'name': 'devops-info-service', + 'version': '1.0.0', + 'description': 'DevOps course info service', + 'framework': 'Flask' + }, + 'system': get_system_info(), + 'runtime': { + 'uptime_seconds': get_uptime()['seconds'], + 'uptime_human': get_uptime()['human'], + 'current_time': datetime.now(timezone.utc).isoformat(), + 'timezone': 'UTC' + }, + 'request': { + 'client_ip': client_ip, + 'user_agent': user_agent, + 'method': request.method, + 'path': request.path + }, + 'endpoints': [ + {'path': '/', 'method': 'GET', 'description': 'Service information'}, + {'path': '/health', 'method': 'GET', 'description': 'Health check'} + ] + } + logger.info(f"Request: {request.method} {request.path} from {client_ip}") + return jsonify(response) + +@app.route('/health') +def health(): + """Endpoint for health check.""" + response = { + 'status': 'healthy', + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'uptime_seconds': get_uptime()['seconds'] + } + logger.debug(f"Health check: {response}") + return jsonify(response), 200 + +@app.errorhandler(404) +def not_found(error): + """Error handler 404.""" + logger.warning(f"404 Not Found: {request.path}") + return jsonify({ + 'error': 'Not Found', + 'message': 'Endpoint does not exist' + }), 404 + +@app.errorhandler(500) +def internal_error(error): + """Error handler 500.""" + logger.error(f"500 Internal Server Error: {str(error)}") + return jsonify({ + 'error': 'Internal Server Error', + 'message': 'An unexpected error occurred' + }), 500 + +if __name__ == '__main__': + logger.info(f'Starting DevOps Info Service on {HOST}:{PORT} (debug={DEBUG})') + app.run(host=HOST, port=PORT, debug=DEBUG) \ No newline at end of file diff --git a/app_python/docs/LAB01.md b/app_python/docs/LAB01.md new file mode 100644 index 0000000000..90f920df57 --- /dev/null +++ b/app_python/docs/LAB01.md @@ -0,0 +1,271 @@ +# Lab 1 — DevOps Info Service: Web Application Development + +## Framework Selection + +### My Choice: Flask + +I selected **Flask** as the web framework for this DevOps Info Service. Here's why: + +**Comparison Table:** +| Criteria | Flask | FastAPI | Django | +|----------|-------|---------|--------| +| Learning Curve | Very low | Moderate | Steep | +| Development Speed | High | High | Medium | +| Built-in Features | Minimal | Moderate | Extensive | +| Auto-documentation | Requires extensions | Built-in (OpenAPI) | Requires extensions | +| Performance | Good | Excellent (async) | Good | +| Complexity | Low | Medium | High | +| **Choice for Lab 1** | **✓** | | | + +**Justification:** +Flask is a lightweight, minimalistic framework that perfectly suits our simple service with only two endpoints. For a DevOps monitoring tool foundation, we don't need the complexity of Django or the async capabilities of FastAPI yet. Flask allows rapid development with clean, understandable code, making it ideal for this educational project. Its simplicity aligns with the Unix philosophy of "do one thing well" - in this case, serve system information via HTTP. + +## Best Practices Applied + +### 1. Clean Code Organization +```python +# Clear imports grouping +import os +import socket +import platform +import logging +from datetime import datetime, timezone +from flask import Flask, jsonify, request + +# Descriptive function names with docstrings +def get_system_info(): + """Collecting information about the system. + Returns: + dict: System configuration + """ + return { + 'hostname': socket.gethostname(), + 'platform': platform.system(), + # ... more fields + } +``` + +**Importance:** Clean organization makes code maintainable, readable, and easier to debug. Following PEP 8 ensures consistency across Python projects. + +### 2. Comprehensive Error Handling +```python +@app.errorhandler(404) +def not_found(error): + """Error handler 404.""" + logger.warning(f"404 Not Found: {request.path}") + return jsonify({ + 'error': 'Not Found', + 'message': 'Endpoint does not exist' + }), 404 + +@app.errorhandler(500) +def internal_error(error): + """Error handler 500.""" + logger.error(f"500 Internal Server Error: {str(error)}") + return jsonify({ + 'error': 'Internal Server Error', + 'message': 'An unexpected error occurred' + }), 500 +``` + +**Importance:** Proper error handling prevents application crashes and provides meaningful feedback to API consumers. Each error type returns appropriate HTTP status codes and structured JSON responses. + +### 3. Structured Logging +```python +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +logger.info(f'Starting DevOps Info Service on {HOST}:{PORT} (debug={DEBUG})') +logger.info(f"Request: {request.method} {request.path} from {client_ip}") +``` + +**Importance:** Logging provides visibility into application behavior, helps with debugging in production, and allows monitoring of API usage patterns. + +### 4. Configuration via Environment Variables +```python +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' +``` + +**Importance:** Following the 12-factor app methodology, configuration via environment variables makes the application portable across different environments (development, testing, production) without code changes. + +### 5. Version-Pinned Dependencies +```txt +# Web framework +Flask==3.1.0 + +# Virtual environment for python +python-dotenv==1.0.1 +``` + +**Importance:** Pinning exact versions ensures consistent behavior across all deployments and prevents "works on my machine" issues due to dependency version mismatches. + +### 6. Git Ignore for Development Artifacts +```gitignore +# Python +__pycache__/ +*.py[cod] +venv/ +*.log + +# IDE +.vscode/ +.idea/ + +# OS +.DS_Store +``` + +**Importance:** Prevents accidental commits of generated files, virtual environments, and sensitive data, keeping the repository clean and focused on source code. + +## API Documentation + +### Endpoint 1: `GET /` + +**Description:** Returns comprehensive service information, system details, runtime data, and request metadata. + +**Request:** +```bash +curl http://localhost:5000/ +``` + +**Response (example):** +```json +{ + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + }, + "system": { + "hostname": "my-laptop", + "platform": "Linux", + "platform_version": "Ubuntu 24.04", + "architecture": "x86_64", + "cpu_count": 8, + "python_version": "3.13.1" + }, + "runtime": { + "uptime_seconds": 3600, + "uptime_human": "1 hour, 0 minutes", + "current_time": "2026-01-07T14:30:00.000Z", + "timezone": "UTC" + }, + "request": { + "client_ip": "127.0.0.1", + "user_agent": "curl/7.81.0", + "method": "GET", + "path": "/" + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] +} +``` + +### Endpoint 2: `GET /health` + +**Description:** Health check endpoint for monitoring system. Always returns HTTP 200 with service status. + +**Request:** +```bash +curl http://localhost:5000/health +``` + +**Response (example):** +```json +{ + "status": "healthy", + "timestamp": "2024-01-15T14:30:00.000Z", + "uptime_seconds": 3600 +} +``` + +### Testing Commands + +1. **Basic endpoint test:** + ```bash + curl http://localhost:5000/ + ``` + +2. **Health check test:** + ```bash + curl http://localhost:5000/health + ``` + +3. **Pretty-printed output:** + ```bash + curl http://localhost:5000/ | jq . + ``` + +4. **Custom configuration:** + ```bash + PORT=8080 python app.py + curl http://localhost:8080/health + ``` + +5. **Error simulation:** + ```bash + curl http://localhost:5000/nonexistent + # Should return 404 error + ``` + +## Testing Evidence + +### Main endpoint: +![Main Endpoint](screenshots/01-main-endpoint.png) + +### Health check: +![Health Check](screenshots/02-health-check.png) + +### Formatted output: +![Formatted output](screenshots/03-formatted-output.png) + + +## Challenges & Solutions + +### Challenge 1: Timezone-Aware Timestamps +**Problem:** `datetime.now()` without timezone creates naive datetime objects, which can cause issues with serialization and timezone calculations. + +**Solution:** Used `timezone.utc` consistently: +```python +from datetime import datetime, timezone +START_TIME = datetime.now(timezone.utc) +# ... +datetime.now(timezone.utc).isoformat() +``` + +### Challenge 2: Logging Configuration +**Problem:** Determining the appropriate log level and format for different types of messages. + +**Solution:** Configured logging with INFO level for normal operations, DEBUG for health checks, and WARNING/ERROR for error handlers: +```python +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' +) +logger.info(f"Request: {request.method} {request.path} from {client_ip}") +logger.debug(f"Health check: {response}") +logger.warning(f"404 Not Found: {request.path}") +``` + +### Challenge 3: CPU Count Handling +**Problem:** `os.cpu_count()` can return None on some systems or when the count cannot be determined. + +**Solution:** Added a fallback value: +```python +'cpu_count': os.cpu_count() or 0 +``` + +## GitHub Community +### Why starring repositories matters in open source: +Starring repositories serves as both a bookmarking tool for personal reference and a public endorsement that helps projects gain visibility, attracting more contributors and showing appreciation to maintainers for their work. + +### How following developers helps in team projects and professional growth: +Following developers enables you to stay updated on their projects and insights, fostering collaboration and knowledge sharing that accelerates team productivity and your own skill development in the tech community. \ No newline at end of file diff --git a/app_python/docs/screenshots/01-main-endpoint.png b/app_python/docs/screenshots/01-main-endpoint.png new file mode 100644 index 0000000000..c374142b86 Binary files /dev/null and b/app_python/docs/screenshots/01-main-endpoint.png differ diff --git a/app_python/docs/screenshots/02-health-check.png b/app_python/docs/screenshots/02-health-check.png new file mode 100644 index 0000000000..ae97ae06f3 Binary files /dev/null and b/app_python/docs/screenshots/02-health-check.png differ diff --git a/app_python/docs/screenshots/03-formatted-output.png b/app_python/docs/screenshots/03-formatted-output.png new file mode 100644 index 0000000000..0c99336f5c Binary files /dev/null and b/app_python/docs/screenshots/03-formatted-output.png differ diff --git a/app_python/requirements.txt b/app_python/requirements.txt new file mode 100644 index 0000000000..ae865aa112 --- /dev/null +++ b/app_python/requirements.txt @@ -0,0 +1,5 @@ +# Web framework +Flask==3.1.0 + +# Virtual environment for python +python-dotenv==1.0.1 \ No newline at end of file diff --git a/app_python/tests/__init__.py b/app_python/tests/__init__.py new file mode 100644 index 0000000000..1fd4ec7ed4 --- /dev/null +++ b/app_python/tests/__init__.py @@ -0,0 +1 @@ +# Unit tests (Lab 3) \ No newline at end of file