Skip to content
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
9 changes: 2 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]

steps:
- name: Checkout code
Expand Down Expand Up @@ -116,12 +116,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install safety bandit

- name: Run safety check
run: |
pip install -e ".[dev]"
safety check --json || true
pip install bandit

- name: Run bandit security scan
run: |
Expand Down
22 changes: 17 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ jobs:
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "version=$VERSION" >> $GITHUB_OUTPUT
# Extract changelog section for this version
sed -n "/^### v$VERSION/,/^### v/p" README.md | sed '$ d' > release_notes.md
# Create simple release notes
echo "## Release v$VERSION" > release_notes.md
echo "" >> release_notes.md
echo "See [README.md](https://github.com/${{ github.repository }}/blob/main/README.md) for full documentation." >> release_notes.md

- name: Create GitHub Release
uses: softprops/action-gh-release@v1
Expand All @@ -57,8 +59,18 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Build and tag Docker images
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push Docker images
run: |
VERSION=${GITHUB_REF#refs/tags/v}
docker build --target runtime -t routing-table-api:$VERSION -t routing-table-api:latest .
echo "Built version $VERSION"
IMAGE_NAME=ghcr.io/${{ github.repository_owner }}/routing-table-api
docker build --target runtime -t $IMAGE_NAME:$VERSION -t $IMAGE_NAME:latest .
docker push $IMAGE_NAME:$VERSION
docker push $IMAGE_NAME:latest
echo "Pushed $IMAGE_NAME:$VERSION and $IMAGE_NAME:latest"
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,59 @@ git commit -m "feat: add amazing feature"

---

## 💖 Sponsor
## � Releases

### Latest Release: v0.2.0

**Release Date:** January 2026

**What's New:**
- ✨ Radix tree implementation with O(k) lookup complexity
- ⚡ LRU caching for sub-5μs cached lookups
- 🔒 Thread-safe concurrent operations
- 📊 Prometheus metrics integration
- 🌐 Full IPv4 and IPv6 support
- 🧪 Comprehensive test suite (29 tests, 39% coverage)
- 🤖 CI/CD pipeline with automated testing

### Download

**Docker/Podman:**
```bash
podman pull ghcr.io/weekmo/routing-table-api:latest
podman pull ghcr.io/weekmo/routing-table-api:v0.2.0
```

**Source:**
```bash
git clone --branch v0.2.0 https://github.com/weekmo/routing-table-api.git
```

**PyPI (Coming Soon):**
```bash
pip install routing-table-api
```

### Release Notes

**All Releases:** [GitHub Releases](https://github.com/weekmo/routing-table-api/releases)

**Changelog:** See [CHANGELOG.md](CHANGELOG.md) for detailed version history

### Versioning

This project follows [Semantic Versioning](https://semver.org/):

- **MAJOR** version: Breaking API changes
- **MINOR** version: New features (backward compatible)
- **PATCH** version: Bug fixes (backward compatible)

**Current:** `0.2.0` (Beta - API may change)
**Stable:** `1.0.0` (Coming Q2 2026)

---

## �💖 Sponsor

[![Sponsor on GitHub](https://img.shields.io/badge/Sponsor-❤️_on_GitHub-ff69b4?logo=github)](https://github.com/sponsors/weekmo)

Expand Down
132 changes: 132 additions & 0 deletions bandit-report.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
{
"errors": [],
"generated_at": "2026-01-12T19:59:34Z",
"metrics": {
"_totals": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 1,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 935,
"nosec": 0,
"skipped_tests": 0
},
"service/__init__.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 0,
"SEVERITY.UNDEFINED": 0,
"loc": 0,
"nosec": 0,
"skipped_tests": 0
},
"service/config.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 1,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 1,
"SEVERITY.UNDEFINED": 0,
"loc": 16,
"nosec": 0,
"skipped_tests": 0
},
"service/lib/__init__.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 0,
"SEVERITY.UNDEFINED": 0,
"loc": 22,
"nosec": 0,
"skipped_tests": 0
},
"service/lib/data.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 0,
"SEVERITY.UNDEFINED": 0,
"loc": 189,
"nosec": 0,
"skipped_tests": 0
},
"service/lib/models.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 0,
"SEVERITY.UNDEFINED": 0,
"loc": 101,
"nosec": 0,
"skipped_tests": 0
},
"service/lib/radix_tree.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 0,
"SEVERITY.UNDEFINED": 0,
"loc": 180,
"nosec": 0,
"skipped_tests": 0
},
"service/main.py": {
"CONFIDENCE.HIGH": 0,
"CONFIDENCE.LOW": 0,
"CONFIDENCE.MEDIUM": 0,
"CONFIDENCE.UNDEFINED": 0,
"SEVERITY.HIGH": 0,
"SEVERITY.LOW": 0,
"SEVERITY.MEDIUM": 0,
"SEVERITY.UNDEFINED": 0,
"loc": 427,
"nosec": 0,
"skipped_tests": 0
}
},
"results": [
{
"code": "9 def __init__(self):\n10 self.host: str = os.getenv(\"HOST\", \"0.0.0.0\")\n11 self.port: int = int(os.getenv(\"PORT\", \"5000\"))\n",
"col_offset": 43,
"end_col_offset": 52,
"filename": "service/config.py",
"issue_confidence": "MEDIUM",
"issue_cwe": {
"id": 605,
"link": "https://cwe.mitre.org/data/definitions/605.html"
},
"issue_severity": "MEDIUM",
"issue_text": "Possible binding to all interfaces.",
"line_number": 10,
"line_range": [
10
],
"more_info": "https://bandit.readthedocs.io/en/1.9.2/plugins/b104_hardcoded_bind_all_interfaces.html",
"test_id": "B104",
"test_name": "hardcoded_bind_all_interfaces"
}
]
}
7 changes: 2 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,14 @@ version = "0.2.0"
description = "FastAPI service for routing table lookups with LPM (Longest Prefix Match)"
readme = "README.md"
license = {text = "GPL-3.0-or-later"}
requires-python = ">=3.8"
requires-python = ">=3.10"
authors = [
{name = "Your Name", email = "your.email@example.com"},
]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand Down Expand Up @@ -77,7 +74,7 @@ ignore = [
known-first-party = ["service"]

[tool.mypy]
python_version = "3.8"
python_version = "3.10"
warn_return_any = true
warn_unused_configs = true
disallow_untyped_defs = false
Expand Down
2 changes: 1 addition & 1 deletion service/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class Settings:
"""Application settings with environment variable support."""

def __init__(self):
self.host: str = os.getenv("HOST", "0.0.0.0")
self.host: str = os.getenv("HOST", "0.0.0.0") # nosec B104
self.port: int = int(os.getenv("PORT", "5000"))
self.proc_num: int = int(os.getenv("PROC_NUM", "5"))
self.routes_file: str = os.getenv("ROUTES_FILE", "routes.txt")
Expand Down
18 changes: 11 additions & 7 deletions service/lib/radix_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,10 @@ def update_metric(self, prefix: str, next_hop: str, metric: int, match_type: str
current = root
for bit_pos in range(max_bits - 1, max_bits - prefix_len - 1, -1):
bit = (addr_int >> bit_pos) & 1
current = current.left if bit == 0 else current.right
if current is None:
next_node = current.left if bit == 0 else current.right
if next_node is None:
return 0
current = next_node

# Update matching routes at this node
for route in current.routes:
Expand All @@ -202,9 +203,10 @@ def update_metric(self, prefix: str, next_hop: str, metric: int, match_type: str
current = root
for bit_pos in range(max_bits - 1, max_bits - prefix_len - 1, -1):
bit = (addr_int >> bit_pos) & 1
current = current.left if bit == 0 else current.right
if current is None:
next_node = current.left if bit == 0 else current.right
if next_node is None:
return 0
current = next_node

# Recursively update all routes in subtree
updated_count = self._update_subtree(current, next_hop, metric)
Expand All @@ -225,14 +227,16 @@ def _update_subtree(self, node: RadixNode, next_hop: str, metric: int) -> int:
count += 1

# Recursively update children
count += self._update_subtree(node.left, next_hop, metric)
count += self._update_subtree(node.right, next_hop, metric)
if node.left is not None:
count += self._update_subtree(node.left, next_hop, metric)
if node.right is not None:
count += self._update_subtree(node.right, next_hop, metric)

return count

def get_all_routes(self) -> List[RouteInfo]:
"""Get all routes in the tree (for debugging/testing)."""
routes = []
routes: List[RouteInfo] = []
self._collect_routes(self.ipv4_root, routes)
self._collect_routes(self.ipv6_root, routes)
return routes
Expand Down
Loading
Loading