Skip to content
This repository was archived by the owner on May 22, 2026. It is now read-only.
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
57 changes: 57 additions & 0 deletions .github/actions/python-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Python Poetry Setup
description: Set up Python + Poetry, cache dependencies, and install project deps

inputs:
python-version:
description: Python version to install
required: false
default: "3.14"
poetry-version:
description: Poetry version to install
required: false
default: "2.3.2"
working-directory:
description: Project directory containing pyproject.toml
required: false
default: "app_python"
lockfile-path:
description: Path to poetry.lock for cache key invalidation
required: false
default: "app_python/poetry.lock"
install-args:
description: Extra arguments passed to poetry install
required: false
default: "--with dev --no-interaction --no-ansi"

runs:
using: composite
steps:
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: ${{ inputs.python-version }}

- name: Install Poetry
uses: snok/install-poetry@v1
with:
version: ${{ inputs.poetry-version }}

- name: Configure Poetry virtualenv location
shell: bash
working-directory: ${{ inputs.working-directory }}
run: poetry config virtualenvs.in-project true

- name: Cache Poetry dependencies
uses: actions/cache@v4
with:
path: |
~/.cache/pypoetry
${{ inputs.working-directory }}/.venv
key: ${{ runner.os }}-py${{ inputs.python-version }}-poetry${{ inputs.poetry-version }}-${{ hashFiles(inputs.lockfile-path) }}
restore-keys: |
${{ runner.os }}-py${{ inputs.python-version }}-poetry${{ inputs.poetry-version }}-

- name: Install dependencies
shell: bash
working-directory: ${{ inputs.working-directory }}
run: poetry install ${{ inputs.install-args }}
56 changes: 56 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Python CI

on:
push:
paths:
- app_python/**
- .github/actions/python-setup/**
- .github/workflows/python-ci.yml
pull_request:
branches:
- master
paths:
- app_python/**
- .github/actions/python-setup/**
- .github/workflows/python-ci.yml

jobs:
test:
strategy:
fail-fast: false
matrix:
python-version: [3.14]
poetry-version: [2.3.2]
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
working-directory: ./app_python
steps:
- uses: actions/checkout@v2
- name: Setup Python tooling and dependencies
uses: ./.github/actions/python-setup
with:
python-version: ${{ matrix.python-version }}
poetry-version: ${{ matrix.poetry-version }}
working-directory: app_python
lockfile-path: app_python/poetry.lock
install-args: --with dev --no-interaction --no-ansi
- name: Lint with flake8
run: poetry run flake8 src tests
- name: Test using pytest with coverage report
run: |
mkdir -p test-results
poetry run pytest \
--junitxml=test-results/pytest-report.xml \
--cov=src \
--cov-report=term-missing \
--cov-report=xml:test-results/coverage.xml
- name: Upload pytest and coverage reports
if: always()
uses: actions/upload-artifact@v4
with:
name: python-test-reports
path: |
app_python/test-results/pytest-report.xml
app_python/test-results/coverage.xml
82 changes: 82 additions & 0 deletions .github/workflows/python-docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Python Docker Publish

on:
push:
branches:
- "lab*"
paths:
- app_python/**
- .github/workflows/python-docker.yml
pull_request:
branches:
- master
types:
- closed
paths:
- app_python/**
- .github/workflows/python-docker.yml

jobs:
build-and-push-branch:
if: github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Derive lab+sha tag from branch
id: version
run: |
source_branch="${{ github.ref_name }}"
if [[ "$source_branch" =~ ([0-9]+) ]]; then
lab_number="${BASH_REMATCH[1]}"
lab_number=$((10#$lab_number))
short_sha="${GITHUB_SHA::7}"
echo "branch_tag=1.${lab_number}.${short_sha}" >> "$GITHUB_OUTPUT"
else
echo "Failed to extract lab number from branch: $source_branch" >&2
exit 1
fi
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image (branch)
uses: docker/build-push-action@v6
with:
context: ./app_python
file: ./app_python/Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/devops-app-py:${{ steps.version.outputs.branch_tag }}

build-and-push:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Derive lab version tag from merged branch
id: version
run: |
source_branch="${{ github.event.pull_request.head.ref }}"
if [[ "$source_branch" =~ ([0-9]+) ]]; then
lab_number="${BASH_REMATCH[1]}"
lab_number=$((10#$lab_number))
echo "version_tag=1.${lab_number}" >> "$GITHUB_OUTPUT"
else
echo "Failed to extract lab number from merged branch: $source_branch" >&2
exit 1
fi
- name: Log in 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
file: ./app_python/Dockerfile
push: true
tags: |
${{ secrets.DOCKERHUB_USERNAME }}/devops-app-py:${{ steps.version.outputs.version_tag }}
${{ secrets.DOCKERHUB_USERNAME }}/devops-app-py:latest
37 changes: 37 additions & 0 deletions .github/workflows/python-snyk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Python Snyk Scan

on:
push:
paths:
- app_python/**
- .github/actions/python-setup/**
- .github/workflows/python-snyk.yml
pull_request:
branches:
- master
paths:
- app_python/**
- .github/actions/python-setup/**
- .github/workflows/python-snyk.yml

jobs:
snyk:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./app_python
steps:
- uses: actions/checkout@v4
- name: Setup Python tooling and dependencies
uses: ./.github/actions/python-setup
- name: Setup Snyk CLI
uses: snyk/actions/setup@master
- name: Run Snyk dependency scan (or skip)
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
run: |
if [ -z "${SNYK_TOKEN:-}" ]; then
echo "SNYK_TOKEN secret not set; skipping Snyk dependency scan."
exit 0
fi
snyk test --severity-threshold=high
6 changes: 3 additions & 3 deletions app_python/.dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
*
!app.py
!requirements.txt
!tests/*
!src/**
!pyproject.toml
!poetry.lock
4 changes: 4 additions & 0 deletions app_python/.flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[flake8]
max-line-length = 100
max-complexity = 10
exclude = .*,docs,*/__pycache__
22 changes: 16 additions & 6 deletions app_python/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
FROM python:3.14-alpine
RUN addgroup appgroup && adduser --disabled-password --gecos "" --no-create-home -s /bin/sh appuser -G appgroup

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
ENV POETRY_VERSION=2.3.2

RUN pip install --no-cache-dir "poetry==$POETRY_VERSION" \
&& addgroup appgroup \
&& adduser --disabled-password --gecos "" --no-create-home -s /bin/sh appuser -G appgroup

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN rm requirements.txt

COPY pyproject.toml poetry.lock ./
RUN poetry config virtualenvs.create false \
&& poetry install --only main --no-interaction --no-ansi --no-root

COPY src ./src

ENV PORT=5000
ENV HOST="0.0.0.0"

USER appuser
CMD ["python", "app.py"]
CMD ["sh", "-c", "gunicorn --bind ${HOST:-0.0.0.0}:${PORT:-5000} src.flask_instance:app"]
42 changes: 35 additions & 7 deletions app_python/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# DevOps Info Service

[![Python CI](https://github.com/LocalT0aster/DevOps-Core-S26/actions/workflows/python-ci.yml/badge.svg)](https://github.com/LocalT0aster/DevOps-Core-S26/actions/workflows/python-ci.yml)

## Overview

Small Flask web service that reports service metadata, system information, runtime uptime, and basic request details. Includes a simple health check endpoint for monitoring.

## Prerequisites

- Python 3.14
- Dependencies from `requirements.txt`
- Python 3.13+
- Poetry

## Installation

```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
poetry install
```

### Docker
Expand All @@ -27,13 +27,19 @@ pip install -r requirements.txt
```bash
docker build -t localt0aster/devops-app-py .
```
The Docker build installs dependencies with:
```bash
poetry install --only main --no-root
```

## Running the Application

Production-style local run with Gunicorn:

```bash
python app.py
poetry run gunicorn --bind 0.0.0.0:5000 src.flask_instance:app
# Or with custom config
PORT=8080 HOST=127.0.0.1 python app.py
HOST=127.0.0.1 PORT=8080 poetry run gunicorn --bind 127.0.0.1:8080 src.flask_instance:app
```

### Docker
Expand All @@ -55,3 +61,25 @@ PORT=8080 HOST=127.0.0.1 python app.py
| `HOST` | `0.0.0.0` | Bind address for the server |
| `PORT` | `5000` | Port to listen on |
| `DEBUG` | `False` | Enable Flask debug mode (`true`/`false`) |

## Testing

The project uses `pytest` for unit tests.

```bash
poetry install --with dev
poetry run pytest --cov=src --cov-report=term-missing
```

## Linting

```bash
poetry run flake8 src tests
```

Current test coverage includes:

- `GET /` successful response schema and types
- `GET /health` successful response schema and types
- `404` JSON error handling for unknown routes
- `500` JSON error handling for simulated internal failures
Loading