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

on:
[push, pull_request]

permissions:
contents: read

jobs:

test:
name: Lint & Tests
runs-on: ubuntu-latest
timeout-minutes: 10

strategy:
fail-fast: true

steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.13"

- name: Cache pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r app_python/requirements.txt
pip install pytest ruff

- name: Lint
run: ruff check .

- name: Run tests
run: pytest

- name: Setup Snyk
uses: snyk/actions/setup@master

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


docker:
name: Build & Push Docker
needs: test
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set version (CalVer)
id: version
run: |
echo "VERSION=$(date +'%Y.%m')" >> $GITHUB_OUTPUT

- 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@v5
with:
context: ./app_python
file: ./app_python/Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/app_python:2026.02
${{ secrets.DOCKERHUB_USERNAME }}/app_python:latest
8 changes: 8 additions & 0 deletions app_python/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.venv
venv
__pycache__
.git
.gitignore
.env
*.pyc
.idea
16 changes: 16 additions & 0 deletions app_python/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:3.13-slim

WORKDIR /app

RUN adduser --disabled-password --gecos "" appuser

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

COPY . .

RUN chown -R appuser:appuser /app

USER appuser

CMD ["python", "app.py"]
32 changes: 32 additions & 0 deletions app_python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,35 @@ GET /health - Health check
| `PORT` | Port number the application listens on | integer | `5000` | `8000` |
| `DEBUG` | Enables debug mode | boolean | `False` | `True` |

## Docker

1. Building the image
example:
```bash
docker build -t <image_name>:<tag> <context>
```

to build our service used:
```bash
docker duild -t devops-info-service:latest .
```
2. Running a container
example:
```bash
docker run <options> <image_name>
```

to run our service used:
```bash
docker run -d -p 5000:5000 devops-info-service
```

3. Pulling from Docker Hub example:
```bash
docker pull <repo_name>
```

to pull our repo used:
```bash
docker pull th1ef/devops-info-service:latest
```
5 changes: 3 additions & 2 deletions app_python/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from config import DEBUG, PORT, HOST
from health_check.router import router
from routes import health_router, root_router
from logger_config import setup_logger

setup_logger()
app = FastAPI(debug=DEBUG)
app.include_router(router=router)
for router in [health_router, root_router]:
app.include_router(router=router)

app.add_middleware(
CORSMiddleware,
Expand Down
71 changes: 35 additions & 36 deletions app_python/docs/LAB01.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
I chose FastApi because it's simple, easy to create endpoints, and has automatic documentation.

| Framework | Pros | Cons | Reason Not Chosen |
|-------------| ----------------------------------------------------- | ------------------------------------------- | --------------------------------- |
|-------------|-------------------------------------------------------|---------------------------------------------|-----------------------------------|
| **FastAPI** | Async support, type safety, OpenAPI, high performance | Slight learning curve | **Chosen** |
| Flask | Simple, minimal | No async by default, no built-in validation | Less suitable for structured APIs |
| Django | Full-featured, mature | Heavy, overkill for small service | Too complex for this task |
Expand All @@ -12,46 +12,45 @@ I chose FastApi because it's simple, easy to create endpoints, and has automatic

1. Environment-based Configuration

```python
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", 5000))
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
```
it important because it enables configuration without code changes.
```text
HOST = os.getenv("HOST", "0.0.0.0")
PORT = int(os.getenv("PORT", 5000))
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
```

it important because it enables configuration without code changes.

2. Separation of Concerns

```python
class HealthCheckService:
async def get_info(self, request: Request) -> InfoResponse:
...

```

it important because it easier testing, cleaner routing layer
```text
class HealthCheckService:
async def get_info(self, request: Request) -> InfoResponse:
pass
```

it important because it easier testing, cleaner routing layer

3. Typed Responses with Pydantic

```python
class InfoResponse(BaseModel):
service: ServiceInfo
system: SystemInfo
runtime: RuntimeInfo
request: RequestInfo
endpoints: list[EndpointInfo]
```
it important because guarantees response structure and improves readability
```text
class InfoResponse(BaseModel):
service: ServiceInfo
system: SystemInfo
runtime: RuntimeInfo
request: RequestInfo
endpoints: list[EndpointInfo]
```

it important because guarantees response structure and improves readability

4. Logging

```python
logger = logging.getLogger(__name__)
logger.info("Handling info request")
```
it important because it centralized observability and works seamlessly with Uvicorn
```text
logger = logging.getLogger(__name__)
logger.info("Handling info request")
```

it important because it centralized observability and works seamlessly with Uvicorn

## API Documentation

Expand Down Expand Up @@ -111,21 +110,21 @@ I chose FastApi because it's simple, easy to create endpoints, and has automatic
"uptime_seconds": 7390
}
```

3. Testing Commands

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

or auto generated documentation:

```bash
http://localhost:5000/docs
```

## Testing Evidence

- Successful responses from `/` and `/health`
Expand Down
Loading