From 2b14f579e28b853f3660bf875c77a47f17473346 Mon Sep 17 00:00:00 2001 From: Fanis Zinnurov Date: Mon, 2 Mar 2026 19:55:52 +0300 Subject: [PATCH 1/4] lab03 --- .../.github/workflows/python-ci.yml | 72 ++++++++++ labs/app_python/README.md | 132 ++---------------- labs/app_python/requirements-dev.txt | 3 + labs/app_python/tests/conftest.py | 8 ++ labs/app_python/tests/test_docker.py | 9 ++ labs/app_python/tests/test_errors.py | 8 ++ labs/app_python/tests/test_health.py | 13 ++ labs/app_python/tests/test_main.py | 17 +++ 8 files changed, 142 insertions(+), 120 deletions(-) create mode 100644 labs/app_python/.github/workflows/python-ci.yml create mode 100644 labs/app_python/requirements-dev.txt create mode 100644 labs/app_python/tests/conftest.py create mode 100644 labs/app_python/tests/test_docker.py create mode 100644 labs/app_python/tests/test_errors.py create mode 100644 labs/app_python/tests/test_health.py create mode 100644 labs/app_python/tests/test_main.py diff --git a/labs/app_python/.github/workflows/python-ci.yml b/labs/app_python/.github/workflows/python-ci.yml new file mode 100644 index 0000000000..3d6dfa40b4 --- /dev/null +++ b/labs/app_python/.github/workflows/python-ci.yml @@ -0,0 +1,72 @@ +name: Python CI/CD + +on: + push: + branches: ["main", "lab03"] + paths: + - "app_python/**" + - ".github/workflows/python-ci.yml" + pull_request: + branches: ["main"] + paths: + - "app_python/**" + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: | + cd app_python + pip install -r requirements.txt + pip install -r requirements-dev.txt + + - name: Run linter + run: | + cd app_python + flake8 . + + - name: Run tests with coverage + run: | + cd app_python + 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: ./app_python + push: true + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:${{ env.VERSION }} + ${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:latest \ No newline at end of file diff --git a/labs/app_python/README.md b/labs/app_python/README.md index 1d5f66dfd4..892c45ae14 100644 --- a/labs/app_python/README.md +++ b/labs/app_python/README.md @@ -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 +``` \ No newline at end of file diff --git a/labs/app_python/requirements-dev.txt b/labs/app_python/requirements-dev.txt new file mode 100644 index 0000000000..cccf9f7a3e --- /dev/null +++ b/labs/app_python/requirements-dev.txt @@ -0,0 +1,3 @@ +pytest==8.2.0 +pytest-cov==5.0.0 +flake8==7.0.0 \ No newline at end of file diff --git a/labs/app_python/tests/conftest.py b/labs/app_python/tests/conftest.py new file mode 100644 index 0000000000..7dfab16196 --- /dev/null +++ b/labs/app_python/tests/conftest.py @@ -0,0 +1,8 @@ +import pytest +from app import app + +@pytest.fixture +def client(): + app.config['TESTING'] = True + with app.test_client() as client: + yield client \ No newline at end of file diff --git a/labs/app_python/tests/test_docker.py b/labs/app_python/tests/test_docker.py new file mode 100644 index 0000000000..298e4e680b --- /dev/null +++ b/labs/app_python/tests/test_docker.py @@ -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 \ No newline at end of file diff --git a/labs/app_python/tests/test_errors.py b/labs/app_python/tests/test_errors.py new file mode 100644 index 0000000000..c1c3a7cfb1 --- /dev/null +++ b/labs/app_python/tests/test_errors.py @@ -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 \ No newline at end of file diff --git a/labs/app_python/tests/test_health.py b/labs/app_python/tests/test_health.py new file mode 100644 index 0000000000..6cec6d5739 --- /dev/null +++ b/labs/app_python/tests/test_health.py @@ -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 \ No newline at end of file diff --git a/labs/app_python/tests/test_main.py b/labs/app_python/tests/test_main.py new file mode 100644 index 0000000000..ec8945e500 --- /dev/null +++ b/labs/app_python/tests/test_main.py @@ -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" \ No newline at end of file From 9ded835fe0c4f4517fb22edf327ec254a9569ac3 Mon Sep 17 00:00:00 2001 From: Fanis Zinnurov Date: Mon, 2 Mar 2026 20:00:34 +0300 Subject: [PATCH 2/4] lil fix --- {labs/app_python/.github => .github}/workflows/python-ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {labs/app_python/.github => .github}/workflows/python-ci.yml (100%) diff --git a/labs/app_python/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml similarity index 100% rename from labs/app_python/.github/workflows/python-ci.yml rename to .github/workflows/python-ci.yml From 8d3c1338230e92d794297a0c70ef511224bb36d6 Mon Sep 17 00:00:00 2001 From: Fanis Zinnurov Date: Mon, 2 Mar 2026 20:08:20 +0300 Subject: [PATCH 3/4] fix of fix --- .github/workflows/python-ci.yml | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/python-ci.yml b/.github/workflows/python-ci.yml index 3d6dfa40b4..2b9f413041 100644 --- a/.github/workflows/python-ci.yml +++ b/.github/workflows/python-ci.yml @@ -1,15 +1,6 @@ name: Python CI/CD -on: - push: - branches: ["main", "lab03"] - paths: - - "app_python/**" - - ".github/workflows/python-ci.yml" - pull_request: - branches: ["main"] - paths: - - "app_python/**" +on: [push, pull_request] concurrency: group: ci-${{ github.ref }} @@ -18,6 +9,9 @@ concurrency: jobs: test: runs-on: ubuntu-latest + defaults: + run: + working-directory: labs/app_python steps: - name: Checkout repository @@ -28,22 +22,18 @@ jobs: with: python-version: "3.11" cache: "pip" + cache-dependency-path: labs/app_python/requirements.txt - name: Install dependencies run: | - cd app_python pip install -r requirements.txt pip install -r requirements-dev.txt - name: Run linter - run: | - cd app_python - flake8 . + run: flake8 . - name: Run tests with coverage - run: | - cd app_python - pytest --cov=app --cov-report=xml --cov-fail-under=70 + run: pytest --cov=app --cov-report=xml --cov-fail-under=70 docker: needs: test @@ -65,7 +55,7 @@ jobs: - name: Build and Push Docker Image uses: docker/build-push-action@v6 with: - context: ./app_python + context: ./labs/app_python push: true tags: | ${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service:${{ env.VERSION }} From b9a7f34877bc62fc4ea00b0126fe23a2d38bb767 Mon Sep 17 00:00:00 2001 From: Fanis Zinnurov Date: Mon, 2 Mar 2026 20:10:26 +0300 Subject: [PATCH 4/4] linter fixes --- labs/app_python/tests/conftest.py | 3 ++- labs/app_python/tests/test_docker.py | 2 +- labs/app_python/tests/test_errors.py | 2 +- labs/app_python/tests/test_health.py | 2 +- labs/app_python/tests/test_main.py | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/labs/app_python/tests/conftest.py b/labs/app_python/tests/conftest.py index 7dfab16196..06a978b9a6 100644 --- a/labs/app_python/tests/conftest.py +++ b/labs/app_python/tests/conftest.py @@ -1,8 +1,9 @@ import pytest from app import app + @pytest.fixture def client(): app.config['TESTING'] = True with app.test_client() as client: - yield client \ No newline at end of file + yield client diff --git a/labs/app_python/tests/test_docker.py b/labs/app_python/tests/test_docker.py index 298e4e680b..5de2d1019e 100644 --- a/labs/app_python/tests/test_docker.py +++ b/labs/app_python/tests/test_docker.py @@ -6,4 +6,4 @@ def test_docker_endpoint(client): assert "is_docker" in data assert "container_id" in data - assert "message" in data \ No newline at end of file + assert "message" in data diff --git a/labs/app_python/tests/test_errors.py b/labs/app_python/tests/test_errors.py index c1c3a7cfb1..84bc2eefa3 100644 --- a/labs/app_python/tests/test_errors.py +++ b/labs/app_python/tests/test_errors.py @@ -5,4 +5,4 @@ def test_404_handler(client): data = response.get_json() assert data["error"] == "Not Found" - assert "available_endpoints" in data \ No newline at end of file + assert "available_endpoints" in data diff --git a/labs/app_python/tests/test_health.py b/labs/app_python/tests/test_health.py index 6cec6d5739..697a567d81 100644 --- a/labs/app_python/tests/test_health.py +++ b/labs/app_python/tests/test_health.py @@ -10,4 +10,4 @@ def test_health_structure(client): assert data["status"] == "healthy" assert "timestamp" in data assert "uptime_seconds" in data - assert "environment" in data \ No newline at end of file + assert "environment" in data diff --git a/labs/app_python/tests/test_main.py b/labs/app_python/tests/test_main.py index ec8945e500..7d315cc68e 100644 --- a/labs/app_python/tests/test_main.py +++ b/labs/app_python/tests/test_main.py @@ -14,4 +14,4 @@ def test_main_endpoint_structure(client): assert "endpoints" in data assert data["service"]["name"] == "devops-info-service" - assert data["service"]["framework"] == "Flask" \ No newline at end of file + assert data["service"]["framework"] == "Flask"