Skip to content

Commit b28a181

Browse files
pepegapepega
authored andcommitted
feat: complete lab03 ci pipeline
1 parent cb5bacf commit b28a181

5 files changed

Lines changed: 221 additions & 0 deletions

File tree

.github/workflows/python-ci.yml

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: Python CI
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
- lab03
8+
paths:
9+
- "app_python/**"
10+
- ".github/workflows/python-ci.yml"
11+
pull_request:
12+
branches:
13+
- master
14+
paths:
15+
- "app_python/**"
16+
- ".github/workflows/python-ci.yml"
17+
18+
concurrency:
19+
group: python-ci-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
test:
24+
runs-on: ubuntu-latest
25+
26+
steps:
27+
- name: Checkout repository
28+
uses: actions/checkout@v4
29+
30+
- name: Set up Python
31+
uses: actions/setup-python@v5
32+
with:
33+
python-version: "3.12"
34+
cache: "pip"
35+
cache-dependency-path: app_python/requirements.txt
36+
37+
- name: Install dependencies
38+
working-directory: app_python
39+
run: |
40+
python -m pip install --upgrade pip
41+
pip install -r requirements.txt
42+
43+
- name: Lint with ruff
44+
working-directory: app_python
45+
run: ruff check .
46+
47+
- name: Run tests
48+
working-directory: app_python
49+
run: pytest -q
50+
51+
- name: Snyk security scan
52+
if: ${{ secrets.SNYK_TOKEN != '' }}
53+
uses: snyk/actions/python@master
54+
env:
55+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
56+
with:
57+
args: --severity-threshold=high
58+
59+
docker:
60+
needs: test
61+
if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/lab03') }}
62+
runs-on: ubuntu-latest
63+
64+
env:
65+
DOCKER_IMAGE: ${{ secrets.DOCKERHUB_USERNAME }}/devops-info-service
66+
67+
steps:
68+
- name: Checkout repository
69+
uses: actions/checkout@v4
70+
71+
- name: Set up Docker Buildx
72+
uses: docker/setup-buildx-action@v3
73+
74+
- name: Log in to Docker Hub
75+
uses: docker/login-action@v3
76+
with:
77+
username: ${{ secrets.DOCKERHUB_USERNAME }}
78+
password: ${{ secrets.DOCKERHUB_TOKEN }}
79+
80+
- name: Generate version
81+
run: echo "VERSION=$(date +'%Y.%m.%d')" >> $GITHUB_ENV
82+
83+
- name: Build and push Docker image
84+
uses: docker/build-push-action@v6
85+
with:
86+
context: app_python
87+
file: app_python/Dockerfile
88+
push: true
89+
tags: |
90+
${{ env.DOCKER_IMAGE }}:${{ env.VERSION }}
91+
${{ env.DOCKER_IMAGE }}:latest

app_python/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# DevOps Info Service
22

3+
[![Python CI](https://github.com/pepegx/DevOps-Core-Course/actions/workflows/python-ci.yml/badge.svg)](https://github.com/pepegx/DevOps-Core-Course/actions/workflows/python-ci.yml)
4+
35
> A web service that provides comprehensive system and runtime information for DevOps monitoring and diagnostics.
46
57
## Overview
@@ -112,6 +114,20 @@ HOST=127.0.0.1 PORT=3000 DEBUG=true python app.py
112114
gunicorn -w 4 -b 0.0.0.0:5000 app:app
113115
```
114116

117+
## Test Suite
118+
119+
Run the test suite locally with:
120+
121+
```bash
122+
pytest -q
123+
```
124+
125+
Run linting locally with:
126+
127+
```bash
128+
ruff check .
129+
```
130+
115131
## Docker
116132

117133
### Build Image (pattern)

app_python/docs/LAB03.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Lab 3 — Continuous Integration (CI/CD): Implementation Report
2+
3+
**Student:** Danil Fishchenko
4+
**Date:** January 31, 2026
5+
**App:** DevOps Info Service (Flask)
6+
7+
---
8+
9+
## 1. Overview
10+
11+
- **Testing framework:** pytest — concise syntax, strong fixtures ecosystem, widely used for Flask apps.
12+
- **Coverage:** Tests validate `GET /`, `GET /health`, and error responses (404, 405).
13+
- **Workflow triggers:** Runs on push to `master` and `lab03`, and on PRs to `master`, only when `app_python/**` or workflow file changes.
14+
- **Versioning strategy:** **CalVer** (`YYYY.MM.DD`) for Docker tags — simple and time-based for a continuously deployed service.
15+
16+
---
17+
18+
## 2. Workflow Evidence
19+
20+
-**Successful workflow run:** TODO — add GitHub Actions run link after push.
21+
-**Tests passing locally (terminal output):**
22+
23+
```
24+
.... [100%]
25+
4 passed in 0.07s
26+
```
27+
28+
-**Local run + endpoint checks:**
29+
30+
```
31+
{"endpoints":[{"description":"Service and system information","method":"GET","path":"/"},{"description":"Health check endpoint","method":"GET","path":"/health"}],"request":{"client_ip":"127.0.0.1","me
32+
{"status":"healthy","timestamp":"2026-01-31T11:00:38.653301+00:00","uptime_seconds":1}
33+
```
34+
35+
-**Docker image on Docker Hub:** https://hub.docker.com/r/pepegx/devops-info-service
36+
-**Status badge in README:** Added to the top of the app README.
37+
38+
---
39+
40+
## 3. Best Practices Implemented
41+
42+
- **Dependency caching:** `actions/setup-python` with pip cache speeds up repeated installs.
43+
- **Job dependencies:** Docker build/push runs only after tests pass (`needs: test`).
44+
- **Conditional publishing:** Docker push runs only on `master` or `lab03` branch pushes.
45+
- **Workflow concurrency:** Cancels outdated runs on the same branch.
46+
- **Path filters:** CI runs only when `app_python/` changes.
47+
- **Snyk scanning:** Optional dependency vulnerability scan with `SNYK_TOKEN` secret.
48+
49+
**Caching speed improvement:** TODO — record cache hit/miss times from Actions.
50+
51+
**Snyk results:** TODO — record findings or note “no issues found.”
52+
53+
---
54+
55+
## 4. Key Decisions
56+
57+
- **Versioning Strategy:** CalVer aligns with frequent service updates without manual SemVer management.
58+
- **Docker Tags:** `${VERSION}` (CalVer) and `latest` for stable consumers.
59+
- **Workflow Triggers:** Focused on `app_python/` to avoid unnecessary CI in a monorepo.
60+
- **Test Coverage:** Core endpoints and error cases tested; system-specific values are asserted by structure, not exact host values.
61+
62+
---
63+
64+
## 5. Challenges (Optional)
65+
66+
- **None encountered** during local test setup.

app_python/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ gunicorn==21.2.0
77
# Development and Testing
88
pytest==7.4.3
99
pytest-flask==1.3.0
10+
ruff==0.6.9

app_python/tests/test_app.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import app as app_module
2+
3+
4+
class TestDevOpsInfoService:
5+
def setup_method(self):
6+
app_module.app.config.update({
7+
"TESTING": True
8+
})
9+
self.client = app_module.app.test_client()
10+
11+
def test_root_endpoint_success(self):
12+
response = self.client.get("/")
13+
assert response.status_code == 200
14+
15+
data = response.get_json()
16+
assert "service" in data
17+
assert "system" in data
18+
assert "runtime" in data
19+
assert "request" in data
20+
assert "endpoints" in data
21+
22+
assert data["service"]["name"] == "devops-info-service"
23+
assert data["service"]["framework"] == "Flask"
24+
assert isinstance(data["endpoints"], list)
25+
assert any(ep["path"] == "/" for ep in data["endpoints"])
26+
assert any(ep["path"] == "/health" for ep in data["endpoints"])
27+
28+
def test_health_endpoint_success(self):
29+
response = self.client.get("/health")
30+
assert response.status_code == 200
31+
32+
data = response.get_json()
33+
assert data["status"] == "healthy"
34+
assert "timestamp" in data
35+
assert "uptime_seconds" in data
36+
37+
def test_not_found_endpoint_returns_404(self):
38+
response = self.client.get("/missing")
39+
assert response.status_code == 404
40+
41+
data = response.get_json()
42+
assert data["error"] == "Not Found"
43+
assert data["status_code"] == 404
44+
45+
def test_method_not_allowed_returns_405(self):
46+
response = self.client.post("/")
47+
assert response.status_code == 405

0 commit comments

Comments
 (0)