diff --git a/app_go/README.md b/app_go/README.md new file mode 100644 index 0000000000..8d6fcf25ca --- /dev/null +++ b/app_go/README.md @@ -0,0 +1,164 @@ +# Lab01 — DevOps Info Service + +## Overview + +**DevOps Info Service (Go version)** is a lightweight web application written in Go that provides detailed information about the service itself, the system it runs on, and its runtime environment. + +This implementation is part of the **bonus task** for Lab 01 and is intended to demonstrate the advantages of using a compiled language in DevOps workflows, especially for containerization and multi-stage Docker builds. + +**Features:** + +* `GET /` — returns service, system, runtime, and request information +* `GET /health` — simple health check endpoint +* Configurable via environment variables + +--- + +## Prerequisites + +* Go **1.24.5** + +--- + +## Installation + +1. Clone the repository: + +```bash +git clone https://github.com/Daniil20xx/DevOps-Core-Course.git +``` + +2. Navigate to the Go application directory: + +```bash +cd app_go +``` + +3. Initialize Go module (if not already initialized): + +```bash +go mod init devops-info-service +``` + +--- + +## Running the Application + +### Run directly + +```bash +cd app_go +go run main.go +``` + +By default, the service runs on: + +``` +http://0.0.0.0:5000 +``` + +### Run with custom configuration + +```bash +HOST=127.0.0.1 PORT=8080 go run main.go +``` + +--- + +## API Endpoints + +### `GET /` + +Returns detailed information about the service and the system. + +**Example:** + +```json +{ + "endpoints": [ + { + "description": "Service information", + "method": "GET", + "path": "/" + }, + { + "description": "Health check", + "method": "GET", + "path": "/health" + } + ], + "request": { + "client_ip": "::1", + "method": "GET", + "path": "/", + "user_agent": "Mozilla/5.0 (Windows NT; Windows NT 10.0; ru-RU) WindowsPowerShell/5.1.26100.7462" + }, + "runtime": { + "current_time": "2026-01-28T08:32:20Z", + "timezone": "UTC", + "uptime_human": "0 hours, 24 minutes", + "uptime_seconds": 1452 + }, + "service": { + "description": "DevOps course info service", + "framework": "net/http", + "name": "devops-info-service", + "version": "1.0.0" + }, + "system": { + "architecture": "amd64", + "cpu_count": 16, + "go_version": "go1.24.5", + "hostname": "Daniil", + "platform": "windows", + "platform_version": "go1.24.5" + } +} +``` + +--- + +### `GET /health` + +Returns service health status. + +**Example:** + +```bash +curl http://localhost:8080/health +``` + +**Response:** + +```json +{ + "status": "healthy", + "timestamp": "2026-01-28T08:33:26Z", + "uptime_seconds": 1518 +} +``` + +--- + +## Configuration + +The application can be configured using environment variables: + +| Environment Variable | Default | Description | +| -------------------- | --------- | ---------------------------------- | +| `HOST` | `0.0.0.0` | Host address to bind the server | +| `PORT` | `8080` | Port to run the application on | + +--- + +## Project Structure + +``` +app_go/ +├── main.go +├── go.mod +├── README.md +└── docs/ + ├── LAB01.md + └── screenshots/ +``` diff --git a/app_go/docs/GO.md b/app_go/docs/GO.md new file mode 100644 index 0000000000..222904676b --- /dev/null +++ b/app_go/docs/GO.md @@ -0,0 +1,60 @@ +# GO — Language Selection Justification + +## Overview + +For the bonus part of Lab 01, the DevOps Info Service was reimplemented using **Go**, a compiled programming language widely used in modern DevOps. +The goal of this implementation is to demonstrate the advantages of compiled languages in terms of performance, deployment, and containerization. + +--- + +## Why Go? + +Go was selected for the following reasons: + +### 1. Compiled Language + +Go compiles source code into a **single native binary**, which eliminates the need for a runtime interpreter (unlike Python). +This results in: + +* Faster application startup +* Lower runtime overhead +* Simpler deployment process + +--- + +### 2. Standard Library for Web Services + +Go provides a powerful and production-ready HTTP server through the standard `net/http` package. +This allows building web services without relying on external frameworks, reducing dependencies and potential security risks. + +--- + +### 3. Performance and Resource Efficiency + +Compared to interpreted languages, Go applications: + +* Use less memory +* Handle concurrent requests efficiently +* Scale well under load + +This makes Go a popular choice for infrastructure tools, monitoring systems, and backend services. + +--- + +## Comparison with Python Implementation + +| Aspect | Python (Flask) | Go | +| --------------------- | ----------------------- | ----------------------- | +| Language Type | Interpreted | Compiled | +| Startup Time | Slower | Faster | +| Deployment | Requires Python runtime | Single binary | +| Docker Image Size | Larger | Smaller | +| Performance | Good for small services | High | +| Dependency Management | External packages | Mostly standard library | + +--- + +## Conclusion + +Go was chosen for the bonus implementation because it provides a clean, efficient, and production-ready approach to building web services. +Using Go alongside Python in this lab demonstrates the trade-offs between interpreted and compiled languages and prepares the project for future DevOps tasks such as containerization, CI/CD pipelines, and Kubernetes deployments. diff --git a/app_go/docs/LAB01.md b/app_go/docs/LAB01.md new file mode 100644 index 0000000000..a9ff176781 --- /dev/null +++ b/app_go/docs/LAB01.md @@ -0,0 +1,143 @@ +# LAB01 — DevOps Info Service (Go Version) + +## 1. Framework / Language Selection + +**Chosen Language:** Go (Golang) 1.24.5 + +**Justification:** +Go is a compiled language suitable for building lightweight, high-performance web services. + +--- + +## 2. Best Practices Applied + +**1. Clean Code Structure** + +* Separation of concerns: utility functions, handlers, and main server logic +* `getUptime()` function calculates runtime +* Route handlers: `mainHandler` for `/`, `healthHandler` for `/health` +* Consistent logging of requests + +**2. Error Handling** + +* Returns default page `/` if error happens + +**3. Logging** + +* Uses Go’s standard `log` package +* Optional verbose logging via `DEBUG` environment variable + +**4. Environment Configuration** + +* `HOST`, `PORT`, `DEBUG` are configurable through environment variables + +**5. Dependency Management** + +* Uses Go modules (`go.mod`) to track dependencies + +--- + +## 3. API Documentation + +### `GET /` + +Returns service, system, runtime, and request information. + +**Example Request:** + +```bash +curl http://localhost:8080/ +``` + +**Sample Response:** + +```json +{ + "endpoints": [ + { + "description": "Service information", + "method": "GET", + "path": "/" + }, + { + "description": "Health check", + "method": "GET", + "path": "/health" + } + ], + "request": { + "client_ip": "::1", + "method": "GET", + "path": "/", + "user_agent": "Mozilla/5.0 (Windows NT; Windows NT 10.0; ru-RU) WindowsPowerShell/5.1.26100.7462" + }, + "runtime": { + "current_time": "2026-01-28T08:32:20Z", + "timezone": "UTC", + "uptime_human": "0 hours, 24 minutes", + "uptime_seconds": 1452 + }, + "service": { + "description": "DevOps course info service", + "framework": "net/http", + "name": "devops-info-service", + "version": "1.0.0" + }, + "system": { + "architecture": "amd64", + "cpu_count": 16, + "go_version": "go1.24.5", + "hostname": "Daniil", + "platform": "windows", + "platform_version": "go1.24.5" + } +} +``` + +--- + +### `GET /health` + +Returns health status. + +**Example Request:** + +```bash +curl http://localhost:8080/health +``` + +**Sample Response:** + +```json +{ + "status": "healthy", + "timestamp": "2026-01-28T08:33:26Z", + "uptime_seconds": 1518 +} +``` + +--- + +## 4. Testing Evidence + +* **Main Endpoint:** + ![Main endpoint](screenshots/01-main-endpoint.png) + +* **Health Check:** + ![Health endpoint](screenshots/02-health-check.png) + +* **Command-line Test Example:** + +```bash +curl http://localhost:8080/ +curl http://localhost:8080/health +``` + +--- + +## 6. Summary + +The Go implementation mirrors the Python (Flask) version of the DevOps Info Service: + +* Same endpoints and JSON structure +* Faster startup and compiled binary \ No newline at end of file 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..87aa89e304 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..f41883ad78 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..fef60e9aa8 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..2281baaf71 --- /dev/null +++ b/app_go/go.mod @@ -0,0 +1,3 @@ +module app_go + +go 1.24.5 diff --git a/app_go/main.go b/app_go/main.go new file mode 100644 index 0000000000..69d4a5f3ed --- /dev/null +++ b/app_go/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "os" + "runtime" + "time" +) + +var startTime = time.Now().UTC() + +func getEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} + +func getUptime() (int64, string) { + seconds := int64(time.Since(startTime).Seconds()) + hours := seconds / 3600 + minutes := (seconds % 3600) / 60 + return seconds, + formatUptime(hours, minutes) +} + +func formatUptime(hours, minutes int64) string { + return fmt.Sprintf("%d hours, %d minutes", hours, minutes) +} + +func getHostname() string { + hostname, err := os.Hostname() + if err != nil { + return "unknown" + } + return hostname +} + +func getClientIP(r *http.Request) string { + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return ip +} + +func healthHandler(w http.ResponseWriter, r *http.Request) { + uptimeSeconds, _ := getUptime() + + response := map[string]interface{}{ + "status": "healthy", + "timestamp": time.Now().UTC().Format(time.RFC3339), + "uptime_seconds": uptimeSeconds, + } + + log.Printf("Health check from %s", getClientIP(r)) + writeJSON(w, http.StatusOK, response) +} + +func mainHandler(w http.ResponseWriter, r *http.Request) { + uptimeSeconds, uptimeHuman := getUptime() + + response := map[string]interface{}{ + "service": map[string]interface{}{ + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "net/http", + }, + "system": map[string]interface{}{ + "hostname": getHostname(), + "platform": runtime.GOOS, + "platform_version": runtime.Version(), + "architecture": runtime.GOARCH, + "cpu_count": runtime.NumCPU(), + "go_version": runtime.Version(), + }, + "runtime": map[string]interface{}{ + "uptime_seconds": uptimeSeconds, + "uptime_human": uptimeHuman, + "current_time": time.Now().UTC().Format(time.RFC3339), + "timezone": "UTC", + }, + "request": map[string]interface{}{ + "client_ip": getClientIP(r), + "user_agent": r.UserAgent(), + "method": r.Method, + "path": r.URL.Path, + }, + "endpoints": []map[string]string{ + { + "path": "/", + "method": "GET", + "description": "Service information", + }, + { + "path": "/health", + "method": "GET", + "description": "Health check", + }, + }, + } + + log.Printf("%s %s from %s", r.Method, r.URL.Path, getClientIP(r)) + writeJSON(w, http.StatusOK, response) +} + +func writeJSON(w http.ResponseWriter, status int, data interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + _ = json.NewEncoder(w).Encode(data) +} + +func main() { + host := getEnv("HOST", "0.0.0.0") + port := getEnv("PORT", "5000") + + log.Printf("Starting Go DevOps Info Service on %s:%s", host, port) + + http.HandleFunc("/", mainHandler) + http.HandleFunc("/health", healthHandler) + + err := http.ListenAndServe(host+":"+port, nil) + if err != nil { + log.Fatalf("Server failed to start: %v", err) + } +} diff --git a/app_python/.dockerignore b/app_python/.dockerignore new file mode 100644 index 0000000000..e96a348868 --- /dev/null +++ b/app_python/.dockerignore @@ -0,0 +1,19 @@ +__pycache__/ +*.pyc +*.pyo +*.pyd + +.git/ +.gitignore + +.venv/ +venv/ + +.env +.idea/ +.vscode/ + +docs/ +tests/ + +README.md diff --git a/app_python/.gitignore b/app_python/.gitignore new file mode 100644 index 0000000000..7868ae2596 Binary files /dev/null and b/app_python/.gitignore differ diff --git a/app_python/Dockerfile b/app_python/Dockerfile new file mode 100644 index 0000000000..64eabc356e --- /dev/null +++ b/app_python/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.12-slim + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +RUN useradd -m dockeruser + +WORKDIR /app + +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +RUN chown -R dockeruser:dockeruser /app +USER dockeruser + +EXPOSE 5000 + +CMD ["python", "app.py"] diff --git a/app_python/README.md b/app_python/README.md new file mode 100644 index 0000000000..aec6a7205d --- /dev/null +++ b/app_python/README.md @@ -0,0 +1,125 @@ +# Lab02 - Docker + +## Overview +- Daniil Mayorov +- d.mayorov@innopolis.university +- CBS-01 +- 2026 year + +**DevOps Info Service** — is a web application that provides detailed information about a service and the system on which it runs. + +**Features:** +- The main endpoint `/` returns information about the service, the system, and the current request +- Endpoint `/health` returns the health status of the service +- Easy configuration via environment variables +- Logging and error handling + +--- + +## Prerequisites + +- Python==3.12.0 +- Flask==3.1.2 +- pip + + +--- + +## Installation + +1. Clone repository from GitHub: +```bash +git clone https://github.com/Daniil20xx/DevOps-Core-Course.git +``` + +2. Go to the folder with the code: +```bash +cd app_python +``` + +3. Prepare the environment + +Linux +```bash +python -m venv venv +source venv/bin/activates +``` + +Windows +```bash +python -m venv venv +.\venv\Scripts\activate +``` + +4. Install dependences: +```bash +pip install -r requirements.txt +``` + +## Running the Application + +1. Run application: +```bash +python app.py +``` + +## API Endpoints + +`GET /` - Service and system information + +`GET /health` - Health check + +## Configuration +| Environment Variable | Default | Description | +| -------------------- | --------- | --------------------------- | +| `HOST` | `0.0.0.0` | Host to run the application | +| `PORT` | `8080` | Port to run the application | +| `DEBUG` | `False` | Debug mode (True/False) | + + +## Docker + +This application can be run inside a Docker container. + +### Build Docker image locally + +To build the Docker image locally, use the Docker build command from the `app_python` directory, specifying the Dockerfile and an image name with a tag. + +**Command pattern:** +```bash +docker build -t lab02-python . +``` + +--- + +### Run Docker container + +After building the image, run the container and map the application port to the host machine so the service is accessible. + +**Command pattern:** + +```bash +docker run -p : lab02-python +``` + +Once the container is running, the application will be available via the mapped port on the host. + +--- + +### Pull image from Docker Hub + +The Docker image is also available on Docker Hub and can be pulled directly without building it locally. + +**Command pattern:** + +Pull: +```bash +docker pull daniil20xx/lab02-python:1.0.0 +``` + +Run: +```bash +docker run -p : daniil20xx/lab02-python:1.0.0 +``` + + diff --git a/app_python/app.py b/app_python/app.py new file mode 100644 index 0000000000..f51e2c92f9 --- /dev/null +++ b/app_python/app.py @@ -0,0 +1,110 @@ +import os +import socket +from flask import Flask, jsonify, request +import platform +import logging +from datetime import datetime, timezone + +HOST = os.getenv('HOST', '0.0.0.0') +PORT = int(os.getenv('PORT', 5000)) +DEBUG = os.getenv('DEBUG', 'False').lower() in ('true', '1', 't') + +app = Flask(__name__) + +logging.basicConfig( + level=logging.DEBUG if DEBUG else logging.INFO, + format="%(asctime)s | %(levelname)s | %(message)s" +) +logger = logging.getLogger(__name__) + +START_TIME = datetime.now(timezone.utc) + +def get_uptime(): + 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_response(): + uptime = get_uptime() + + response = { + "service": { + "name": "devops-info-service", + "version": "1.0.0", + "description": "DevOps course info service", + "framework": "Flask" + }, + "system": { + "hostname": socket.gethostname(), + "platform": platform.system(), + "platform_version": platform.version(), + "architecture": platform.machine(), + "cpu_count": os.cpu_count(), + "python_version": platform.python_version(), + }, + "runtime": { + "uptime_seconds": uptime["seconds"], + "uptime_human": uptime["human"], + "current_time": datetime.now(timezone.utc).isoformat(), + "timezone": "UTC" + }, + "request": { + "client_ip": request.remote_addr, + "user_agent": request.headers.get("User-Agent"), + "method": request.method, + "path": request.path + }, + "endpoints": [ + {"path": "/", "method": "GET", "description": "Service information"}, + {"path": "/health", "method": "GET", "description": "Health check"} + ] + } + return response + +@app.route('/health') +def health(): + logger.info(f"Health check from {request.remote_addr}") + return jsonify({ + 'status': 'healthy', + 'timestamp': datetime.now(timezone.utc).isoformat(), + 'uptime_seconds': get_uptime()['seconds'] + }) + +@app.route("/", methods=["GET"]) +def index(): + logger.info(f"{request.method} {request.path} from {request.remote_addr}") + return jsonify(get_response()) + +@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": "Unexpected server error" + }), 500 + +@app.errorhandler(Exception) +def handle_exception(e): + logger.exception("Unexpected error") + return jsonify({ + "error": "Internal Server Error", + "message": str(e) + }), 500 + +if __name__ == "__main__": + logger.info("Starting application") + app.run(host=HOST, port=PORT, debug=DEBUG) + + + diff --git a/app_python/docs/LAB01.md b/app_python/docs/LAB01.md new file mode 100644 index 0000000000..a2e791554f Binary files /dev/null and b/app_python/docs/LAB01.md differ diff --git a/app_python/docs/LAB02.md b/app_python/docs/LAB02.md new file mode 100644 index 0000000000..0a00c46b48 --- /dev/null +++ b/app_python/docs/LAB02.md @@ -0,0 +1,130 @@ +# LAB02 — Docker Containerization + +## 1. Docker Best Practices Applied + +### Non-root user + +The container runs under a non-root user instead of the default root user. This significantly improves security because even if an attacker gains access to the container, they will not have full administrative privileges. + +In the Dockerfile, a dedicated user is created and activated using the `USER` directive. This follows Docker security best practices and reduces the potential impact of vulnerabilities. + +### Specific base image version + +The image is based on `python:3.12-slim`. Using a specific version instead of `latest` ensures build reproducibility and prevents unexpected breaking changes when the base image is updated. + +The `slim` variant was chosen because it provides a good balance between minimal size and compatibility with Python dependencies. + +### Layer caching optimization + +Dependencies are installed before copying the application source code. This allows Docker to reuse cached layers when only the application code changes, which significantly speeds up rebuilds during development. + +### .dockerignore usage + +A `.dockerignore` file is used to exclude unnecessary files such as virtual environments, Git metadata, cache files, and IDE configuration. This reduces the build context size, speeds up the build process, and helps keep the final image smaller and cleaner. + +--- + +## 2. Image Information & Decisions + +### Base image choice + +The base image used is `python:3.12-slim`. + +**Justification:** + +* Matches the Python version used during local development +* Smaller image size compared to full Python images +* Official image with regular security updates + +### Final image size + +The final image size is approximately **42.68 MB**, which is acceptable for a Python web application with Flask and demonstrates reasonable optimization. + +### Layer structure + +The image layers are structured as follows: + +1. Base Python image +2. System setup and non-root user creation +3. Dependency installation (`requirements.txt`) +4. Application source code + +This structure maximizes cache reuse and minimizes rebuild time. + +### Optimization choices + +* Used `python:slim` instead of a full image +* Excluded unnecessary files using `.dockerignore` +* Installed only required dependencies + +--- + +## 3. Build & Run Process + +### Build process + +The image was built locally using Docker. Below is the terminal output from the build process: + +![Build Stage](screenshots/lab02-docker-build.png) + +### Run process + +The container was started with port mapping so the service is accessible from the host: + +```bash +$ docker run -p 5000:5000 lab02-python:1.0.0 +``` +![Docker Run](screenshots/lab02-docker-run.png) + +### Endpoint testing + +The application endpoints were tested using browser: + +![Testing Docker](screenshots/lab02-docker-testing.png) + +### Docker Hub + +The image was pushed to Docker Hub and is publicly available: + +**Repository URL:** + +``` +https://hub.docker.com/r/daniil20xx/lab02-python +``` + +--- + +## 4. Technical Analysis + +### Dockerfile behavior + +The Dockerfile works by first preparing a secure and minimal runtime environment, then installing dependencies, and finally copying the application code. This ensures both security and efficiency. + +### Layer order importance + +If the application code were copied before installing dependencies, any code change would invalidate the cache and force dependency reinstallation, significantly slowing down rebuilds. + +### Security considerations + +* The container does not run as root +* Uses an official Python base image +* Minimal image size reduces attack surface + +### .dockerignore benefits + +By excluding unnecessary files from the build context, `.dockerignore` improves build speed, reduces image size, and prevents accidental inclusion of sensitive or irrelevant files. + +--- + +## 5. Challenges & Solutions + +### Issue: Port not accessible + +Initially, the application was not accessible from the host machine because the container port was not correctly mapped. + +**Solution:** +The issue was resolved by explicitly mapping the container port to the host port using the `-p` option in `docker run`. + +### Learning outcome + +Through this lab, I gained a deeper understanding of Dockerfile structure, image optimization, security best practices, and the full workflow of building, running, and publishing Docker images. 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..f1fc65b032 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..ec2a50f2c9 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..92851d9afe Binary files /dev/null and b/app_python/docs/screenshots/03-formatted-output.png differ diff --git a/app_python/docs/screenshots/lab02-docker-build.png b/app_python/docs/screenshots/lab02-docker-build.png new file mode 100644 index 0000000000..78d189e0a3 Binary files /dev/null and b/app_python/docs/screenshots/lab02-docker-build.png differ diff --git a/app_python/docs/screenshots/lab02-docker-run.png b/app_python/docs/screenshots/lab02-docker-run.png new file mode 100644 index 0000000000..e200df43c0 Binary files /dev/null and b/app_python/docs/screenshots/lab02-docker-run.png differ diff --git a/app_python/docs/screenshots/lab02-docker-testing.png b/app_python/docs/screenshots/lab02-docker-testing.png new file mode 100644 index 0000000000..5ea7c52d72 Binary files /dev/null and b/app_python/docs/screenshots/lab02-docker-testing.png differ diff --git a/app_python/requirements.txt b/app_python/requirements.txt new file mode 100644 index 0000000000..1833886eef Binary files /dev/null and b/app_python/requirements.txt differ diff --git a/app_python/tests/__init__.py b/app_python/tests/__init__.py new file mode 100644 index 0000000000..49cc8ef0e1 Binary files /dev/null and b/app_python/tests/__init__.py differ