Skip to content
Closed
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
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-lab03

on:
pull_request:
types: [opened, reopened]
branches:
- master
paths:
- app_python/
- '!app_python/docs/**'
- '!app_python/README.md'

jobs:
Check-code-and-docker-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.14'
cache: pip

- name: Install dependencies
run: cd app_python/; pip install -r requirements.txt

- name: Lint
run: flake8 app_python/app.py app_python/tests/

- name: Test
run: pytest app_python/tests/

# snyk can't find required packages, even after installing dependencies, don't know the reason
# - name: Check for vulnerabilities
# uses: snyk/actions/python@master
# env:
# SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# with:
# args: app_python/

- name: Generate version
id: gen-ver
run: echo "VERSION=$(date +%Y.%m.%d)" >> $GITHUB_OUTPUT

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- uses: docker/setup-buildx-action@v3
- name: Docker build and push
uses: docker/build-push-action@v6
with:
context: ./app_python
push: true
tags: kosmogor/devops:latest,kosmogor/devops:${{ steps.gen-ver.outputs.VERSION }}
53 changes: 53 additions & 0 deletions .github/workflows/rust-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: rust-lab03

on:
pull_request:
types: [opened, reopened]
branches:
- master
paths:
- app_rust/
- '!app_rust/docs/**'
- '!app_rust/README.md'

jobs:
Check-rust-code-and-docker-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Cache
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
app_rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Build
run: cargo build --manifest-path app_rust/Cargo.toml --verbose

- name: Lint
run: cargo clippy --manifest-path app_rust/Cargo.toml

- name: Test
run: cargo test --manifest-path app_rust/Cargo.toml --verbose

- name: Generate version
id: gen-ver
run: echo "VERSION=$(date +%Y.%m.%d)" >> $GITHUB_OUTPUT

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- uses: docker/setup-buildx-action@v3
- name: Docker build and push
uses: docker/build-push-action@v6
with:
context: ./app_rust
push: true
tags: kosmogor/devops_rust:latest,kosmogor/devops_rust:${{ steps.gen-ver.outputs.VERSION }}
1 change: 1 addition & 0 deletions app_python/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ __pycache__/
.venv/
*.py[cod]
*.log
.pytest_cache/

# IDE
.vscode/
12 changes: 12 additions & 0 deletions app_python/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Python Web Server

[![lab03](https://github.com/KOSMOGOR/DevOps-Core-Course/actions/workflows/python-ci.yml/badge.svg)](https://github.com/KOSMOGOR/DevOps-Core-Course/actions/workflows/python-ci.yml)

---

## Overview

This is a web application providing detailed information about itself and its runtime environment
Expand Down Expand Up @@ -46,6 +50,14 @@ To run simply run:
docker run devops:latest
```

## Testing

App can be easily tested after installation by simply running:

```bash
pytest /tests
```

## API Endpoints

- `GET /` - Service and system information
Expand Down
10 changes: 6 additions & 4 deletions app_python/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ def get_time_info():
delta = now - start_time
return {
"uptime_seconds": delta.seconds,
"uptime_human": f"{delta.seconds // 3600} hour, {delta.seconds % 3600 // 60} minutes",
"uptime_human": f"{delta.seconds // 3600} hour,\
{delta.seconds % 3600 // 60} minutes",
"current_time": datetime.now(timezone.utc).isoformat(),
"timezone": "UTC"
}
Expand Down Expand Up @@ -77,7 +78,8 @@ def app_root(request: Request):
"path": request.url.path
},
"endpoints": [
{"path": "/", "method": "GET", "description": "Service information"},
{"path": "/", "method": "GET",
"description": "Service information"},
{"path": "/health", "method": "GET", "description": "Health check"}
]
}
Expand Down Expand Up @@ -106,7 +108,7 @@ def not_found(request: Request, exception: Exception):


@app.exception_handler(500)
def not_found(request: Request, exception: Exception):
def internal_server_error(request: Request, exception: Exception):
return JSONResponse(
{
"error": "Internal Server Error",
Expand All @@ -117,5 +119,5 @@ def not_found(request: Request, exception: Exception):


if __name__ == "__main__":
logger.info('Application starting...')
logger.info("Application starting...")
uvicorn.run("app:app", host=HOST, port=PORT, reload=DEBUG)
6 changes: 3 additions & 3 deletions app_python/docs/lab01.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ API documentation can be found on `http://[HOST]:[PORT]/docs` (defaults to <http
## Testing Evidence

`/` endpoint:
![/](./screenshots/01-main-endpoint.png)
![/](./screenshots/lab01/01-main-endpoint.png)

`/health` endpoint:
![/health](./screenshots/02-health-check.png)
![/health](./screenshots/lab01/02-health-check.png)

some terminal output:
![output](./screenshots/03-formatted-output.png)
![output](./screenshots/lab01/03-formatted-output.png)

## Challenges & Solutions

Expand Down
79 changes: 79 additions & 0 deletions app_python/docs/lab03.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Lab 03

## Testing

### Choosing Test Framework

I chose pytest as test framework for some reasons:

- It supports different kind of tests
- It has simple syntax
- It supports powerfull fixtures
- It has rich plugin architecture

### Tests Structure

File `test_endpoints.py` consists of 3 test functions:

- `test_root()` verifies status code, structure, and fields of request to `/`
- `test_health()` verifies status code, structure and fields of request to `/health`
- `test_404()` verifies status code of request to not existing path

### Tests Terminal Output

```bash
$ pytest tests/
========================= test session starts =========================
platform win32 -- Python 3.14.0, pytest-9.0.2, pluggy-1.6.0
rootdir: C:\DevOps\app_python
plugins: anyio-4.12.1
collected 3 items

tests\test_endpoints.py ... [100%]

========================== 3 passed in 0.54s ==========================
```

## Actions Workflow

### Workflow Trigger Strategy

Workflow triggers on opening (or reopening) pull requests to master, since we need to check code before merging.

### Action Choice

I chose official GitHub and Docker actions, since they are trustworthy.

### Docker Tagging Strategy

Fro tagging I have used current date, since it is enough for version tracking.

### Proofs

Link to successful workflow: <https://github.com/KOSMOGOR/DevOps-Core-Course/actions/runs/21801589090>

Screenshot:

![Workflow](./screenshots/lab03/successful-actions-workflow.png)

## Continious Integration

### Successful working badge

![badge](/docs/screenshots/lab03/successful-actions-badge.png)

### Caching Implemantation

Caching was implemented using `cache: pip`

### Best Practices

I used practices like:

- Optimize pipeline stages - I optimized pipeline stages
- Use failures to improve processes - tasks can fail due to not compliting some requirements
- Use secrets - I used GitHub secrets for tokens in workflow

### Snyk Integration Results

Snyk found vulnarability in package `python-multipart` and suggested upgrading it, and I have done it.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 10 additions & 2 deletions app_python/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,33 @@ fastapi==0.128.0
fastapi-cli==0.0.20
fastapi-cloud-cli==0.11.0
fastar==0.8.0
flake8==7.3.0
h11==0.16.0
httpcore==1.0.9
httptools==0.7.1
httpx==0.28.1
idna==3.11
iniconfig==2.3.0
Jinja2==3.1.6
markdown-it-py==4.0.0
MarkupSafe==3.0.3
mccabe==0.7.0
mdurl==0.1.2
packaging==26.0
pluggy==1.6.0
pycodestyle==2.14.0
pydantic==2.12.5
pydantic-extra-types==2.11.0
pydantic-settings==2.12.0
pydantic_core==2.41.5
pyflakes==3.4.0
Pygments==2.19.2
pytest==9.0.2
python-dotenv==1.2.1
python-multipart==0.0.21
python-multipart==0.0.22
PyYAML==6.0.3
rich==14.3.0
rich-toolkit==0.17.1
rich-toolkit==0.18.1
rignore==0.7.6
sentry-sdk==2.50.0
shellingham==1.5.4
Expand Down
39 changes: 39 additions & 0 deletions app_python/tests/test_endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from fastapi.testclient import TestClient
from app import app


client = TestClient(app)


def test_root():
response = client.get("/")
assert response.status_code == 200
res_json: dict = response.json()
assert "service" in res_json
assert all(x in res_json["service"] for x in
["name", "version", "description", "framework"])
assert "system" in res_json
assert all(x in res_json["system"] for x in
["hostname", "platform", "platform_version",
"architecture", "cpu_count", "python_version"])
assert "runtime" in res_json
assert all(x in res_json["runtime"] for x in
["uptime_seconds", "uptime_human", "current_time", "timezone"])
assert "request" in res_json
assert all(x in res_json["request"] for x in
["client_ip", "user_agent", "method", "path"])
assert "endpoints" in res_json
assert type(res_json["endpoints"]) is list


def test_health():
response = client.get("/health")
assert response.status_code == 200
res_json: dict = response.json()
assert all(x in res_json for x in
["status", "timestamp", "uptime_seconds"])


def test_404():
response = client.get("/definitely/wrong/path")
assert response.status_code == 404
6 changes: 3 additions & 3 deletions app_rust/docs/lab01.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ As web framework I chose Actix Web for some reasons:
## Testing Evidence

`/` endpoint:
![/](./screenshots/01-main-endpoint.png)
![/](./screenshots/lab01/01-main-endpoint.png)

`/health` endpoint:
![/health](./screenshots/02-health-check.png)
![/health](./screenshots/lab01/02-health-check.png)

some terminal output:
![output](./screenshots/03-formatted-output.png)
![output](./screenshots/lab01/03-formatted-output.png)

## Challenges & Solutions

Expand Down
2 changes: 1 addition & 1 deletion app_rust/docs/lab02.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Lab 01 Bonus Task
# Lab 02 Bonus Task

## Multi-Stage Build Strategy

Expand Down
Loading