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

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

jobs:
test-build:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: "pip"

- name: Install dependencies
run: |
pip install -r app_python/requirements.txt
pip install -r app_python/requirements-dev.txt

- name: Lint
run: ruff check app_python

- name: Run tests
run: pytest app_python/tests

- name: Generate version
run: echo "VERSION=$(date +%Y.%m.%d)" >> $GITHUB_ENV

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

- name: Snyk Scan
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: snyk test --file=app_python/requirements.txt

- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: ./app_python
push: true
tags: |
maksimmenshikh/devops-info-service:${{ env.VERSION }}
maksimmenshikh/devops-info-service:latest
9 changes: 9 additions & 0 deletions app_python/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
__pycache__/
*.pyc
venv/
.venv/
.git
.gitignore
README.md
docs/
.env
12 changes: 12 additions & 0 deletions app_python/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Python
__pycache__/
*.py[cod]
venv/
*.log

# IDE
.vscode/
.idea/

# OS
.DS_Store
17 changes: 17 additions & 0 deletions app_python/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.13-slim

RUN useradd -m appuser

WORKDIR /app

COPY requirements.txt .

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

COPY app.py .

USER appuser

EXPOSE 5000

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

A lightweight web service that provides detailed information about
itself and its runtime environment.\

------------------------------------------------------------------------

## Overview

DevOps Info Service is a Python-based web application that exposes REST
API endpoints for retrieving:

- Service metadata
- System information (OS, CPU, Python version, hostname)
- Runtime statistics (uptime, current time, timezone)
- Request details (client IP, HTTP method, user agent)

The service is designed to be simple, extensible, and production-ready,
following best DevOps and software engineering practices.

------------------------------------------------------------------------

## Prerequisites

- Python **3.11+**
- pip
- Virtual environment support (`venv`)

### Dependencies

All dependencies are listed in `requirements.txt`:

- Flask 3.1.0

------------------------------------------------------------------------

## Installation

Create and activate a virtual environment, then install dependencies:

``` bash
python -m venv venv
source venv/bin/activate # Linux / macOS
# venv\Scripts\activate # Windows
pip install -r requirements.txt
```

------------------------------------------------------------------------

## Running the Application

Run the service using the default configuration:

``` bash
python app.py
```

Run with custom configuration:

``` bash
PORT=8080 python app.py
HOST=127.0.0.1 PORT=3000 python app.py
```

Once running, the service will be available at:

http://localhost:5000

------------------------------------------------------------------------

## API Endpoints

### GET /

Returns detailed service, system, runtime, and request information.

**Example:**

``` bash
curl http://localhost:5000/
```

------------------------------------------------------------------------

### GET /health

Simple health-check endpoint used for monitoring and readiness probes.

**Example:**

``` bash
curl http://localhost:5000/health
```

------------------------------------------------------------------------

## Configuration

The application is configured via environment variables:

Variable Default Description
---------- --------- -------------------------
HOST 0.0.0.0 Server bind address
PORT 5000 Server listening port
DEBUG False Enable Flask debug mode

**Example:**

``` bash
HOST=127.0.0.1 PORT=8080 DEBUG=true python app.py
```

## Docker

### Build Image

docker build -t maksimmenshikh/devops-info-service .

### Run Container

docker run -p 5000:5000 maksimmenshikh/devops-info-service

### Pull from Docker Hub

docker pull maksimmenshikh/devops-info-service

## Running Tests

cd app_python
pip install -r requirements-dev.txt
pytest -v

![CI](https://github.com/MMenshikh/DevOps-Core-Course/actions/workflows/python-ci.yml/badge.svg)
109 changes: 109 additions & 0 deletions app_python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import os
import socket
import platform
import logging
from datetime import datetime, timezone
from flask import Flask, jsonify, request

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

app = Flask(__name__)

# Configuration
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", 5000))
DEBUG = os.getenv("DEBUG", "False").lower() == "true"

# Application start time
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_system_info():
return {
"hostname": socket.gethostname(),
"platform": platform.system(),
"platform_version": platform.version(),
"architecture": platform.machine(),
"cpu_count": os.cpu_count(),
"python_version": platform.python_version()
}


@app.route("/")
def index():
logger.info(f"Request: {request.method} {request.path}")

uptime = get_uptime()

return jsonify({
"service": {
"name": "devops-info-service",
"version": "1.0.0",
"description": "DevOps course info service",
"framework": "Flask"
},
"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.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"}
]
})


@app.route("/health")
def health():
uptime = get_uptime()
return jsonify({
"status": "healthy",
"timestamp": datetime.now(timezone.utc).isoformat(),
"uptime_seconds": uptime["seconds"]
})


@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__":
logger.info("Starting DevOps Info Service...")
app.run(host=HOST, port=PORT, debug=DEBUG)
Loading