Compare commits

..

3 Commits

Author SHA1 Message Date
Flea Flicker 599a9c4060 ci: push Docker images to Gitea registry (git.farh.net)
CI / lint (pull_request) Has been cancelled
CI / typecheck (pull_request) Has been cancelled
CI / test (pull_request) Has been cancelled
CI / build-and-push (pull_request) Has been cancelled
CI / deploy-dev (pull_request) Has been cancelled
CI / deploy-uat (pull_request) Has been cancelled
2026-05-23 15:37:02 +00:00
cartsnitch-ceo[bot] cb180b511f release: promote API migration to production
Production merge approved by CEO (Coupon Carl). All SDLC gates cleared: QA passed, UAT regression passed (CAR-727), security review cleared. Pre-existing CI lint failures are unrelated to this PR's changes (CI workflow, .grype.yaml, CLAUDE.md only).
2026-04-19 12:27:19 +00:00
savannah-savings-cto[bot] 556b43b424 Merge pull request #2 from cartsnitch/dev
chore: promote dev to uat
2026-04-19 12:11:48 +00:00
4 changed files with 52 additions and 43 deletions
@@ -2,9 +2,9 @@ name: CI
on:
push:
branches: [main, dev, uat]
branches: [main, dev]
pull_request:
branches: [main, dev, uat]
branches: [main, dev]
concurrency:
group: ci-${{ github.ref }}
@@ -20,12 +20,13 @@ env:
jobs:
lint:
runs-on: ubuntu-latest
runs-on: runners-cartsnitch
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- run: pip install ruff
- name: Ruff lint
run: ruff check .
@@ -33,13 +34,14 @@ jobs:
run: ruff format --check .
typecheck:
runs-on: ubuntu-latest
runs-on: runners-cartsnitch
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpq-dev build-essential
- run: pip install -e ".[dev]" mypy
@@ -47,10 +49,13 @@ jobs:
run: mypy src/cartsnitch_api
test:
runs-on: ubuntu-latest
runs-on: runners-cartsnitch
services:
postgres:
image: postgres:15-alpine
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
env:
POSTGRES_USER: cartsnitch
POSTGRES_PASSWORD: cartsnitch_test
@@ -64,6 +69,9 @@ jobs:
--health-retries 5
redis:
image: redis:7-alpine
credentials:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
ports:
- 6379:6379
options: >-
@@ -75,13 +83,12 @@ jobs:
CARTSNITCH_DATABASE_URL: postgresql+asyncpg://cartsnitch:cartsnitch_test@localhost:5432/cartsnitch_test
CARTSNITCH_REDIS_URL: redis://localhost:6379/0
CARTSNITCH_JWT_SECRET_KEY: test-secret-do-not-use-in-prod
CARTSNITCH_SERVICE_KEY: test-service-key-do-not-use-in-prod
CARTSNITCH_FERNET_KEY: wXWQsC0FZlhSz2t_tfVQjNUSP8vgAGG3o3pkjrX8Bw0=
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
cache: pip
- name: Install system dependencies
run: sudo apt-get update && sudo apt-get install -y libpq-dev build-essential
- run: pip install -e ".[dev]"
@@ -89,7 +96,7 @@ jobs:
run: pytest --tb=short -q
build-and-push:
runs-on: ubuntu-latest
runs-on: runners-cartsnitch
needs: [lint, test]
outputs:
calver_tag: ${{ steps.calver.outputs.version }}
@@ -117,7 +124,12 @@ jobs:
echo "CalVer tag: $VERSION"
- name: Log in to Gitea Container Registry
run: echo "${{ github.token }}" | docker login git.farh.net -u ${{ github.actor }} --password-stdin
if: github.event_name == 'push'
uses: docker/login-action@v3
with:
registry: git.farh.net
username: cartsnitch
password: ${{ secrets.GITEA_TOKEN }}
- name: Extract metadata
id: meta
@@ -154,7 +166,11 @@ jobs:
only-fixed: "true"
output-format: sarif
- name: Upload api scan results to GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: ${{ steps.scan.outputs.sarif }}
- name: Push Docker image
if: github.event_name == 'push'
@@ -176,15 +192,24 @@ jobs:
git push origin "v${{ steps.calver.outputs.version }}"
deploy-dev:
runs-on: ubuntu-latest
runs-on: runners-cartsnitch
needs: [build-and-push]
if: always() && !cancelled() && github.event_name == 'push' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main')
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CARTSNITCH_APP_ID }}
private-key: ${{ secrets.CARTSNITCH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: infra
- name: Checkout infra repo
uses: actions/checkout@v4
with:
repository: cartsnitch/infra
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ steps.app-token.outputs.token }}
ref: main
path: infra
@@ -207,7 +232,7 @@ jobs:
if: needs.build-and-push.result == 'success'
run: |
cd infra/apps/overlays/dev
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.api_tag.outputs.tag }}
kustomize edit set image ghcr.io/cartsnitch/api:${{ steps.api_tag.outputs.tag }}
- name: Commit and push to infra
run: |
@@ -220,15 +245,24 @@ jobs:
git push origin main
deploy-uat:
runs-on: ubuntu-latest
runs-on: runners-cartsnitch
needs: [build-and-push]
if: always() && !cancelled() && github.event_name == 'push' && (github.ref == 'refs/heads/uat' || github.ref == 'refs/heads/main')
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CARTSNITCH_APP_ID }}
private-key: ${{ secrets.CARTSNITCH_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
repositories: infra
- name: Checkout infra repo
uses: actions/checkout@v4
with:
repository: cartsnitch/infra
token: ${{ secrets.GITEA_TOKEN }}
token: ${{ steps.app-token.outputs.token }}
ref: main
path: infra
@@ -251,7 +285,7 @@ jobs:
if: needs.build-and-push.result == 'success'
run: |
cd infra/apps/overlays/uat
kustomize edit set image ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.api_tag.outputs.tag }}
kustomize edit set image ghcr.io/cartsnitch/api:${{ steps.api_tag.outputs.tag }}
- name: Commit and push to infra
run: |
+1 -6
View File
@@ -23,12 +23,7 @@ class Settings(BaseSettings):
auth_service_url: str = "http://auth:3001"
cors_origins: list[str] = [
"http://localhost:3000",
"https://cartsnitch.com",
"https://dev.cartsnitch.com",
"https://uat.cartsnitch.com",
]
cors_origins: list[str] = ["http://localhost:3000", "https://cartsnitch.com"]
receiptwitness_url: str = "http://receiptwitness:8001"
stickershock_url: str = "http://stickershock:8002"
+1 -8
View File
@@ -6,6 +6,7 @@ from fastapi import APIRouter, FastAPI
from cartsnitch_api.auth.routes import router as auth_router
from cartsnitch_api.cache import cache_client
from cartsnitch_api.database import dispose_engine
from cartsnitch_api.middleware.cors import add_cors_middleware
from cartsnitch_api.middleware.error_handler import add_error_handlers, add_error_monitor_middleware
from cartsnitch_api.middleware.rate_limit import add_rate_limit_middleware
@@ -25,14 +26,6 @@ from cartsnitch_api.routes.user import router as user_router
@asynccontextmanager
async def lifespan(app: FastAPI):
# Lazy import: keep `dispose_engine` out of the top-level imports so a
# stale or partially-built database.py never breaks module load on
# container start. The function is required for graceful pool cleanup
# on shutdown; if the import fails, the cache_client.close() that
# follows the yield would mask it. See CAR-1135 for the original
# ImportError that motivated this pattern.
from cartsnitch_api.database import dispose_engine
await cache_client.initialize()
yield
await cache_client.close()
-13
View File
@@ -3,21 +3,8 @@
import pytest
from httpx import ASGITransport, AsyncClient
from cartsnitch_api.database import dispose_engine
from cartsnitch_api.main import app
def test_dispose_engine_importable_from_database():
"""Regression for CAR-1135: api main.py used to import dispose_engine
at module level. A stale database.py (no dispose_engine) crashed the
container at import time with ImportError on line 9. The fix moved
the import inside the lifespan function, but `dispose_engine` must
still be importable from `cartsnitch_api.database` for the lifespan
teardown to actually close pooled connections.
"""
assert callable(dispose_engine)
assert dispose_engine.__name__ == "dispose_engine"
EXPECTED_ROUTES = [
# Auth (7)
("post", "/auth/register"),