Skip to content
Open

Lab6 #2864

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
062de87
app.py done
CacucoH Jan 28, 2026
2cdc1c1
feat: complete lab-01
CacucoH Jan 28, 2026
22d4fa9
added containers
CacucoH Feb 4, 2026
5226c9c
docs
CacucoH Feb 4, 2026
5ac0043
more docs
CacucoH Feb 4, 2026
3142f65
screensots fix
CacucoH Feb 4, 2026
f59a45b
fixed images; README supplemented
CacucoH Feb 4, 2026
1bcef77
lab3: added pytests
CacucoH Feb 11, 2026
aed6a6d
feat: Added CI
CacucoH Feb 12, 2026
57004af
fix: CI/CD folder renamed fix
CacucoH Feb 12, 2026
0ed10b1
Updated worflow
CacucoH Feb 12, 2026
0edb73c
fix: fixed requirements.txt
CacucoH Feb 12, 2026
e64ad24
fix: fixed tests folder name in workflow
CacucoH Feb 12, 2026
7c2f173
fix: fixed docker metadata ver in CI pipeline
CacucoH Feb 12, 2026
8ca15bc
fix: fixed CI/CD flavor
CacucoH Feb 12, 2026
57f10cd
Updated readme
CacucoH Feb 12, 2026
05c067b
Updated readme
CacucoH Feb 12, 2026
da6b615
fix: fixed dockerfile location in CI pipeline
CacucoH Feb 12, 2026
46dac75
Updated README, added status badge
CacucoH Feb 12, 2026
d5affdf
feat: added security pipeline
CacucoH Feb 12, 2026
52ecda1
fix: fixed pipeline
CacucoH Feb 12, 2026
10e2c67
fix: fixed security pipeline
CacucoH Feb 12, 2026
e04ff4d
fix: security pipeline
CacucoH Feb 12, 2026
e741c8f
fix: fixed pipeline .yml syntax
CacucoH Feb 12, 2026
7595f27
lab 3 is done
CacucoH Feb 12, 2026
0d6ca7a
lab3 is done: added beautiful badge
CacucoH Feb 12, 2026
1ab40d5
feat: lab4 done
CacucoH Feb 19, 2026
0b333c6
lab 5 done
CacucoH Feb 26, 2026
f490a45
lab6 done
CacucoH Mar 5, 2026
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
106 changes: 106 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Python CI & Docker Build

on:
push:
branches: [ main, dev, lab3 ]
tags: [ 'v*' ]
pull_request:
branches: [ main ]

permissions:
contents: read
packages: write

jobs:
test:
name: Test & Lint
runs-on: ubuntu-latest

strategy:
matrix:
python-version: ["3.9", "3.10", "3.11"]

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

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

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

- name: Lint with Ruff
run: ruff check .

- name: Run tests
run: pytest app_python/tests/ --verbose -v

- name: Format check
run: ruff format --check .

security:
name: Snyk Security Scan
runs-on: ubuntu-latest
if: github.event_name != 'pull_request'
defaults:
run:
working-directory: ./app_python
steps:
- uses: actions/checkout@v4
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Snyk CLI
uses: snyk/actions/python-3.11@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=critical --skip-unresolved
continue-on-error: true


docker:
name: Build & Push Docker
needs: [ test, security ] # Runs only if test & security passed
runs-on: ubuntu-latest
if: github.event_name != 'pull_request' # Dont push pr to docker hub

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

- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ secrets.DOCKER_USERNAME }}/testiks
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value={{date 'YYYY.MM'}},enable={{is_default_branch}}
type=ref,event=branch


- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

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

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./app_python/
push: true
tags: ${{ steps.meta.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
test
test
.*
11 changes: 11 additions & 0 deletions ansible/ansible.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[defaults]
inventory = inventory/hosts.ini
roles_path = roles
host_key_checking = False
remote_user = debil
retry_files_enabled = False

[privilege_escalation]
become = True
become_method = sudo
become_user = root
22 changes: 22 additions & 0 deletions ansible/app_python/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

# Non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Install deps first
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt


COPY app.py .
RUN chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

# Run app finally
CMD ["python", "app.py"]
55 changes: 55 additions & 0 deletions ansible/app_python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# DevOps Info Service
A lightweight demo Python web application that system information via HTTP endpoints

### Prerequisites
Python 3.10+
Flask 3.1.0

### Installation
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```

### Running the Application
```bash
python3 app.py
# Or with custom config
PORT=8080 python3 app.py
```

### API Endpoints
There are few main endpoints:
- `GET /` - Service and system information
- `GET /health` - Health check.

### Configuration

| Variable | Value | Purpose |
| -------- | ------ | ------------------------------------ |
| Host | string | A host to run app on |
| Port | int | A port to assign for web application |
| Debug | bool | Should debug output be enabled |

## Docker
This application can be run in a containerized environment with Docker

### Build the image locally
To build the Docker image, use the Docker build command from the project directory, specifying the Dockerfile and an image name with a tag
```bash
cd app_python
docker build -t <image-name> .
```

### Run a container
To run the application, start a container from the built image and map the container port to a port on the host machine so the application can be accessed locally
```bash
docker run -p<any-port-on-your-machine>:5000 <created-image-name>
```

### Pull from Docker Hub
The pre-built image is also available on Docker Hub and can be pulled using the standard Docker pull command with the repository name and desired tag
```bash
docker pull cacucoh/testiks:1.0
```
112 changes: 112 additions & 0 deletions ansible/app_python/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import os
import socket
import platform
import logging
from datetime import datetime, timezone

from flask import Flask, jsonify, request

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


app = Flask(__name__)

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

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("/health", methods=["GET"])
def health():
uptime = get_uptime()
return jsonify(
{
"status": "healthy",
"timestamp": datetime.now(timezone.utc).isoformat(),
"uptime_seconds": uptime["seconds"],
}
)


@app.route("/", methods=["GET"])
def default_route():
logger.info(f"Request: {request.method} {request.path}")
uptime = get_uptime()

response = {
"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"},
],
}

return jsonify(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": "An unexpected error occurred",
}
),
500,
)


if __name__ == "__main__":
logger.info("[+] Starting...")
try:
app.run(host=HOST, port=PORT, debug=DEBUG)
finally:
logger.info("[i] Shutting down")
Loading