Skip to content
Open
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
13 changes: 13 additions & 0 deletions Lab-1/app_python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Python
__pycache__/
*.py[cod]
venv/
*.log
.env

# IDE
.vscode/
.idea/

# OS
.DS_Store
57 changes: 57 additions & 0 deletions Lab-1/app_python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# DevOps Info Service (Flask)

## Overview
A small Flask web service that reports service metadata, system information, runtime details, and request context. It also exposes a health check endpoint and Swagger UI.

## Prerequisites
- Python 3.11+
- pip

## Installation
```bash
python -m venv venv
# Linux/macOS
source venv/bin/activate
# Windows PowerShell
.\venv\Scripts\Activate.ps1

pip install -r requirements.txt
```

## Configuration via .env (optional)
Create a `.env` file in `app_python/`:
```env
HOST=0.0.0.0
PORT=5000
DEBUG=false
```

## Running the Application
```bash
python app.py
```

With custom configuration:
```bash
PORT=8080 python app.py
HOST=127.0.0.1 PORT=3000 DEBUG=true python app.py
```

Windows PowerShell:
```powershell
$env:PORT=8080; python app.py
$env:HOST='127.0.0.1'; $env:PORT=3000; $env:DEBUG='true'; python app.py
```

## API Endpoints
- `GET /` - Service and system information
- `GET /health` - Health check
- `GET /swagger.json` - OpenAPI spec
- `GET /docs` - Swagger UI

## Configuration
| Variable | Default | Description |
|---|---|---|
| `HOST` | `0.0.0.0` | Bind address |
| `PORT` | `5000` | HTTP port |
| `DEBUG` | `False` | Flask debug mode (`true`/`false`) |
203 changes: 203 additions & 0 deletions Lab-1/app_python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
from __future__ import annotations

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

from dotenv import load_dotenv
from flask import Flask, jsonify, request
from flask_swagger_ui import get_swaggerui_blueprint

app = Flask(__name__)

# conf
load_dotenv()
HOST = os.getenv('HOST', '0.0.0.0')
PORT = int(os.getenv('PORT', '5000'))
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'

# start time
START_TIME = datetime.now(timezone.utc)

# Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
logger.info('Application starting...')

# swagger info
SWAGGER_URL = '/docs'
SWAGGER_API_URL = '/swagger.json'

swaggerui_blueprint = get_swaggerui_blueprint(
SWAGGER_URL,
SWAGGER_API_URL,
config={'app_name': 'DevOps Info Service'}
)
app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL)


def _iso_utc_now() -> str:
return datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace('+00:00', 'Z')


def get_uptime() -> dict:
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"
}


def get_platform_version() -> str:
system = platform.system()
if system == 'Linux':
try:
os_release = platform.freedesktop_os_release()
return os_release.get('PRETTY_NAME') or os_release.get('NAME') or platform.release()
except (OSError, AttributeError):
return platform.release()
if system == 'Windows':
return platform.version()
return platform.release()


def get_system_info() -> dict:
return {
'hostname': socket.gethostname(),
'platform': platform.system(),
'platform_version': get_platform_version(),
'architecture': platform.machine(),
'cpu_count': os.cpu_count() or 0,
'python_version': platform.python_version()
}


def get_request_info() -> dict:
client_ip = request.headers.get('X-Forwarded-For', request.remote_addr or '')
if ',' in client_ip:
client_ip = client_ip.split(',')[0].strip()

return {
'client_ip': client_ip,
'user_agent': request.headers.get('User-Agent', ''),
'method': request.method,
'path': request.path
}


def get_service_info() -> dict:
"""return metadata"""
return {
'name': 'devops-info-service',
'version': '1.0.0',
'description': 'DevOps course info service',
'framework': 'Flask'
}


def get_endpoints() -> list[dict]:
"""return a list of available endpoints"""
return [
{'path': '/', 'method': 'GET', 'description': 'Service information'},
{'path': '/health', 'method': 'GET', 'description': 'Health check'}
]

#API
OPENAPI_SPEC = {
'openapi': '3.0.3',
'info': {
'title': 'DevOps Info Service',
'version': '1.0.0',
'description': 'Service and system information API'
},
'paths': {
'/': {
'get': {
'summary': 'Service information',
'responses': {
'200': {
'description': 'Service and system information'
}
}
}
},
'/health': {
'get': {
'summary': 'Health check',
'responses': {
'200': {
'description': 'Health status'
}
}
}
}
}
}


@app.before_request
def log_request() -> None:
logger.debug('Request: %s %s', request.method, request.path)


@app.route('/')
def index():
"""main endpoint"""
uptime = get_uptime()
payload = {
'service': get_service_info(),
'system': get_system_info(),
'runtime': {
'uptime_seconds': uptime['seconds'],
'uptime_human': uptime['human'],
'current_time': _iso_utc_now(),
'timezone': 'UTC'
},
'request': get_request_info(),
'endpoints': get_endpoints()
}
return jsonify(payload)


@app.route('/health')
def health():
"""health check endpoint"""
uptime = get_uptime()
return jsonify({
'status': 'healthy',
'timestamp': _iso_utc_now(),
'uptime_seconds': uptime['seconds']
})


@app.route('/swagger.json')
def swagger_json():
return jsonify(OPENAPI_SPEC)


@app.errorhandler(404)
def not_found(error):
return jsonify({
'error': 'Not Found',
'message': 'Endpoint does not exist'
}), 404


@app.errorhandler(500)
def internal_error(error):
return jsonify({
'error': 'Internal Server Error',
'message': 'An unexpected error occurred'
}), 500


if __name__ == '__main__':
app.run(host=HOST, port=PORT, debug=DEBUG)
110 changes: 110 additions & 0 deletions Lab-1/app_python/docs/LAB01.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# LAB01 - DevOps Info Service (Python / Flask)

## 1. Framework Selection
**Chosen framework:** Flask

**Why Flask:**
- Minimal setup and easy to understand for a first lab
- Clear request/response handling without extra abstractions)
- I tried Django, regretted it

**Comparison Table**
| Framework | Pros | Cons | Why Not Chosen |
|---|---|---|---|
| Flask | Lightweight, simple, widely used | Fewer built-in features | Selected due to simplicity |
| FastAPI | Async, auto-docs, type hints | Slightly more setup | Didn't try it, because of luck of time |
| Django | Full-featured, includes ORM | Heavy for small API | Too much for the first time|

## 2. Best Practices Applied
1. **Clean code organization** - helper functions for system, runtime, and request info
2. **Error handling** - custom 404 and 500 responses
3. **Logging** - structured logging with timestamp and level
4. **Configuration via environment variables** - HOST, PORT, DEBUG
5. **Pinned dependencies** - exact versions in `requirements.txt`

**Code examples (from `app.py`):**
```python
HOST = os.getenv('HOST', '0.0.0.0')
PORT = int(os.getenv('PORT', '5000'))
DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
```

```python
@app.errorhandler(404)
def not_found(error):
return jsonify({
'error': 'Not Found',
'message': 'Endpoint does not exist'
}), 404
```

```python
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
```

## 3. API Documentation
### 3.1 `GET /`
**Description:** Returns service, system, runtime, request info, and endpoints.

**Example request:**
```bash
curl http://127.0.0.1:5000/
```

**Example response (truncated):**
```json
{
"service": {
"name": "devops-info-service",
"version": "1.0.0",
"description": "DevOps course info service",
"framework": "Flask"
}
}
```

### 3.2 `GET /health`
**Description:** Health check endpoint for monitoring.

**Example request:**
```bash
curl http://127.0.0.1:5000/health
```

**Example response:**
```json
{
"status": "healthy",
"timestamp": "2026-01-28T17:31:00.456Z",
"uptime_seconds": 180
}
```

### 3.3 Swagger UI
**OpenAPI spec:**
```
GET /swagger.json
```

**Swagger UI:**
```
GET /docs
```
*it was easier to check app with swagger

## 4. Testing Evidence
Add screenshots to `docs/screenshots/` and embed them here.

- **Main endpoint:** `screenshots/01-main-endpoint.png`
- **Health check:** `screenshots/02-health-check.png`
- **Pretty-printed output:** `screenshots/03-formatted-output.png`

## 5. Challenges & Solutions
- **Timezone formatting:** Used UTC with ISO 8601 and `Z` suffix for consistency.
- **Client IP handling:** Added `X-Forwarded-For` fallback for proxy setups.

## 6. GitHub Community
Starring repositories helps to find useful tools and bookmaer them. Following developers improves collaboration by keeping you aware of classmates' and instructors' work, which supports learning and teamwork.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions Lab-1/app_python/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Web Framework
Flask==3.1.0
# Swagger UI
flask-swagger-ui==4.11.1
# Env support
python-dotenv==1.0.1
Empty file.