Add CI/CD workflows and security policy

This commit is contained in:
Aaron D. Lee
2025-12-30 00:08:22 -05:00
parent a7c2fcc1da
commit 37a60d7174
6 changed files with 716 additions and 0 deletions

228
.github/CI_CD_PRIMER.md vendored Normal file
View File

@@ -0,0 +1,228 @@
# CI/CD Primer for Stegasoo
## What is CI/CD?
**CI** = Continuous Integration
**CD** = Continuous Deployment
Think of it as a robot assistant that automatically:
1. **Tests your code** every time you push
2. **Checks formatting** so code stays consistent
3. **Publishes releases** when you tag a version
```
You push code → GitHub runs workflows → You get ✓ or ✗
```
---
## How It Works
### The Trigger
When you `git push` or create a Pull Request, GitHub looks for workflow files in:
```
.github/workflows/*.yml
```
Each `.yml` file defines a workflow - a series of steps to run.
### The Runners
GitHub provides free Linux/Mac/Windows VMs that:
1. Clone your repo
2. Set up Python
3. Run your commands
4. Report success/failure
You don't manage servers - GitHub does.
---
## Your Workflows
### 1. `test.yml` - Run Tests on Every Push
**When it runs:** Every push, every PR
**What it does:**
```
1. Spins up Ubuntu VM
2. Installs Python 3.10, 3.11, 3.12
3. Installs your package + dependencies
4. Runs pytest
5. Reports pass/fail
```
**You'll see:** Green ✓ or red ✗ on your commits
### 2. `lint.yml` - Check Code Style
**When it runs:** Every push, every PR
**What it does:**
```
1. Runs ruff (fast Python linter)
2. Checks black formatting
3. Fails if code isn't formatted
```
**Why:** Keeps code consistent, catches common bugs
### 3. `release.yml` - Publish to PyPI
**When it runs:** Only when you create a version tag
**What it does:**
```
1. Builds the package (wheel + sdist)
2. Uploads to PyPI
```
**You trigger it by:**
```bash
git tag v2.2.0
git push origin v2.2.0
```
---
## Day-to-Day Usage
### Normal Development
```bash
# Make changes
git add .
git commit -m "Add new feature"
git push
```
Then check GitHub → Actions tab → See if tests pass.
### If Tests Fail
1. Click the failed workflow
2. Click the failed job
3. Read the error log
4. Fix locally, push again
### Making a Release
```bash
# 1. Update version in pyproject.toml and constants.py
# 2. Commit the version bump
git add .
git commit -m "Bump version to 2.2.1"
git push
# 3. Create and push a tag
git tag v2.2.1
git push origin v2.2.1
# 4. GitHub automatically publishes to PyPI
```
---
## Reading the GitHub UI
### Actions Tab
```
Repository → Actions → [List of workflow runs]
```
Each run shows:
- ✓ Green checkmark = passed
- ✗ Red X = failed
- 🟡 Yellow dot = running
### Pull Request Checks
When you open a PR, you'll see:
```
┌─────────────────────────────────────────┐
│ All checks have passed │
│ ✓ test (3.10) — 45s │
│ ✓ test (3.11) — 42s │
│ ✓ test (3.12) — 44s │
│ ✓ lint — 12s │
└─────────────────────────────────────────┘
```
---
## Setting Up PyPI Publishing
For `release.yml` to work, you need to add a PyPI API token:
### One-Time Setup
1. **Create PyPI account** at https://pypi.org/account/register/
2. **Generate API token:**
- PyPI → Account Settings → API tokens
- Create token (scope: entire account or just stegasoo)
- Copy the token (starts with `pypi-`)
3. **Add to GitHub:**
- GitHub repo → Settings → Secrets and variables → Actions
- New repository secret
- Name: `PYPI_API_TOKEN`
- Value: paste the token
Now `release.yml` can publish automatically.
---
## Common Scenarios
### "Tests pass locally but fail in CI"
Usually means:
- Missing dependency in `pyproject.toml`
- Hardcoded path that doesn't exist in CI
- Test relies on local file
### "Lint is failing"
Run locally to see/fix:
```bash
# Check issues
ruff check src/
# Auto-fix what's possible
ruff check --fix src/
# Format code
black src/
```
### "I want to skip CI for a commit"
Add `[skip ci]` to commit message:
```bash
git commit -m "Update README [skip ci]"
```
---
## Costs
GitHub Actions is **free** for public repos.
For private repos: 2,000 minutes/month free, then paid.
---
## Summary
| Action | What Happens |
|--------|--------------|
| `git push` | Tests + lint run automatically |
| Open PR | Tests must pass before merge |
| `git tag v*` | Publishes to PyPI |
| Check results | GitHub → Actions tab |
That's it! Push code, check for green checkmarks.

63
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
# Check code style and formatting
name: Lint
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
jobs:
lint:
runs-on: ubuntu-latest
steps:
# 1. Get the code
- name: Checkout code
uses: actions/checkout@v4
# 2. Set up Python
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
# 3. Install linting tools
- name: Install linters
run: |
python -m pip install --upgrade pip
pip install ruff black
# 4. Run ruff (fast linter - catches bugs and style issues)
- name: Run ruff
run: |
ruff check src/ tests/ frontends/
# 5. Check black formatting (doesn't modify, just checks)
- name: Check black formatting
run: |
black --check src/ tests/ frontends/
# Type checking (optional but helpful)
typecheck:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
pip install mypy
- name: Run mypy
run: |
mypy src/stegasoo --ignore-missing-imports
continue-on-error: true # Don't fail build on type errors (yet)

95
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
# Publish to PyPI when a version tag is pushed
name: Release
on:
push:
tags:
- 'v*' # Triggers on v1.0.0, v2.1.0, etc.
jobs:
# First, run tests to make sure everything works
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libzbar0
- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- name: Run tests
run: pytest
# Then build and publish
publish:
needs: test # Only run if tests pass
runs-on: ubuntu-latest
# Required for PyPI trusted publishing (recommended)
permissions:
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
# Option 1: Trusted Publishing (recommended, no token needed)
# Set this up at: https://pypi.org/manage/project/stegasoo/settings/publishing/
- name: Publish to PyPI (Trusted Publishing)
uses: pypa/gh-action-pypi-publish@release/v1
# No token needed if you configure trusted publishing on PyPI
# Option 2: API Token (uncomment if not using trusted publishing)
# - name: Publish to PyPI (API Token)
# env:
# TWINE_USERNAME: __token__
# TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
# run: twine upload dist/*
# Create GitHub Release with changelog
github-release:
needs: publish
runs-on: ubuntu-latest
permissions:
contents: write # Needed to create releases
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
files: |
dist/*

53
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
# Run tests on every push and pull request
name: Tests
on:
push:
branches: [main, master, develop]
pull_request:
branches: [main, master, develop]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false # Don't cancel other jobs if one fails
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
# 1. Get the code
- name: Checkout code
uses: actions/checkout@v4
# 2. Set up Python
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
# 3. Install system dependencies (for pyzbar QR reading)
- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y libzbar0
# 4. Install the package with all dependencies
- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
# 5. Run tests with coverage
- name: Run tests
run: |
pytest --cov=stegasoo --cov-report=xml --cov-report=term-missing
# 6. Upload coverage report (optional - integrates with codecov.io)
- name: Upload coverage
uses: codecov/codecov-action@v4
if: matrix.python-version == '3.11' # Only upload once
with:
files: ./coverage.xml
fail_ci_if_error: false # Don't fail if codecov is down