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

on: [push, pull_request]

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
runs-on: ubuntu-latest
defaults:
run:
working-directory: labs/app_python

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
cache-dependency-path: labs/app_python/requirements.txt

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

- name: Run linter
run: flake8 .

- name: Run tests with coverage
run: pytest --cov=app --cov-report=xml --cov-fail-under=70

docker:
needs: test
runs-on: ubuntu-latest

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Generate version
run: echo "VERSION=$(date +'%Y.%m').${{ github.run_number }}" >> $GITHUB_ENV

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

- name: Build and Push Docker Image
uses: docker/build-push-action@v6
with:
context: ./labs/app_python
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:${{ env.VERSION }}
${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:latest
132 changes: 12 additions & 120 deletions labs/app_python/README.md
Original file line number Diff line number Diff line change
@@ -1,133 +1,25 @@
````markdown
# DevOps Info Service

## Overview
DevOps Info Service is a web application that provides detailed information about the service itself and its runtime environment. This service will be developed throughout the DevOps course into a comprehensive monitoring tool.
![CI](https://github.com/qobz1e/DevOps-Core-Course/actions/workflows/python-ci.yml/badge.svg)

## Prerequisites
- Python 3.11 or higher
- pip package manager
## Run locally

## Installation

1. Create and activate virtual environment:
```bash
python -m venv venv
source venv/bin/activate
```

2. Install dependencies
```bash
pip install -r requirements.txt
```

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

# With custom configuration
PORT=8080 python app.py

# With all environment variables
HOST=127.0.0.1 PORT=3000 DEBUG=True python app.py
```

## API Endpoints

### GET `/`
Returns comprehensive information about the service, system, and current request.

**Example response:**
```json
{
"service": {
"name": "devops-info-service",
"version": "1.0.0",
"description": "DevOps course info service",
"framework": "Flask"
},
"system": {
"hostname": "my-pc",
"platform": "Linux",
"cpu_count": 8,
"python_version": "3.11.5"
},
"runtime": {
"uptime_seconds": 120,
"uptime_human": "2 minutes",
"current_time": "2024-01-27T10:30:00.000Z"
}
}
```

### GET `/health`
Returns the health status of the service. Used for monitoring and Kubernetes probes.

**Example response:**
```json
{
"status": "healthy",
"timestamp": "2024-01-27T10:30:00.000Z",
"uptime_seconds": 120
}
```

## Configuration

The application can be configured using environment variables:
## Run tests

| Variable | Description | Default Value |
|----------|-------------|---------------|
| `HOST` | Host to bind the server to | `0.0.0.0` |
| `PORT` | Port to run the server on | `5000` |
| `DEBUG` | Enable Flask debug mode | `False` |


## 🐳 Docker

### Building the Image
To build the Docker image locally, navigate to the application directory and use:
```bash
docker build -t qobz1e/devops-info-service:lab2 .
```

The Dockerfile follows security best practices including non-root user execution and optimized layer caching.

### Running the Container
Run the container with port mapping to access the service:
```bash
docker run -d -p 5000:5000 --name devops-app qobz1e/devops-info-service:lab2
```

### Pulling from Docker Hub
Pull the pre-built image from Docker Hub:
```bash
docker pull qobz1e/devops-info-service:lab2
```

### Quick Commands
```bash
# Build
docker build -t qobz1e/devops-info-service:lab2 .

# Run
docker run -d -p 5000:5000 --name devops-app qobz1e/devops-info-service:lab2

# Test
curl http://localhost:5000/
curl http://localhost:5000/health
cd app_python
pip install -r requirements.txt
pip install -r requirements-dev.txt
pytest -v
```

### Configuration
Set environment variables when running:
- `HOST`: Binding address (default: 0.0.0.0)
- `PORT`: Application port (default: 5000)
- `DEBUG`: Debug mode (default: False)
## Run with coverage

Example:
```bash
docker run -d -p 8080:5000 -e PORT=5000 -e DEBUG=True qobz1e/devops-info-service:lab2
```

### Health Check
The container includes a health endpoint at `/health` for monitoring and orchestration systems.
pytest --cov=app --cov-report=term
```
3 changes: 3 additions & 0 deletions labs/app_python/requirements-dev.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pytest==8.2.0
pytest-cov==5.0.0
flake8==7.0.0
9 changes: 9 additions & 0 deletions labs/app_python/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import pytest
from app import app


@pytest.fixture
def client():
app.config['TESTING'] = True
with app.test_client() as client:
yield client
9 changes: 9 additions & 0 deletions labs/app_python/tests/test_docker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
def test_docker_endpoint(client):
response = client.get("/docker")
assert response.status_code == 200

data = response.get_json()

assert "is_docker" in data
assert "container_id" in data
assert "message" in data
8 changes: 8 additions & 0 deletions labs/app_python/tests/test_errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def test_404_handler(client):
response = client.get("/unknown-endpoint")
assert response.status_code == 404

data = response.get_json()

assert data["error"] == "Not Found"
assert "available_endpoints" in data
13 changes: 13 additions & 0 deletions labs/app_python/tests/test_health.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
def test_health_status_code(client):
response = client.get("/health")
assert response.status_code == 200


def test_health_structure(client):
response = client.get("/health")
data = response.get_json()

assert data["status"] == "healthy"
assert "timestamp" in data
assert "uptime_seconds" in data
assert "environment" in data
17 changes: 17 additions & 0 deletions labs/app_python/tests/test_main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
def test_main_endpoint_status(client):
response = client.get("/")
assert response.status_code == 200


def test_main_endpoint_structure(client):
response = client.get("/")
data = response.get_json()

assert "service" in data
assert "system" in data
assert "runtime" in data
assert "request" in data
assert "endpoints" in data

assert data["service"]["name"] == "devops-info-service"
assert data["service"]["framework"] == "Flask"
Loading