diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a98b15e..aeef9d0 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -18,6 +18,16 @@ jobs: 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 -r requirements.txt + - name: Ruff lint + run: ruff check app/ tests/ test: name: Run Tests @@ -27,6 +37,16 @@ jobs: 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 -r requirements.txt + - name: Run tests with coverage + run: pytest --cov=app --cov-report=term-missing --cov-fail-under=80 build: name: Build Docker Image @@ -40,3 +60,26 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - 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 image (push) + if: github.event_name == 'push' + run: | + IMAGE=ghcr.io/${{ github.repository }} + TAG=${{ github.sha }} + docker build -f docker/Dockerfile -t $IMAGE:$TAG . + docker push $IMAGE:$TAG + - name: Build image (release) + if: github.event_name == 'release' + run: | + IMAGE=ghcr.io/${{ github.repository }} + VERSION=${{ github.ref_name }} + docker build -f docker/Dockerfile -t $IMAGE:$VERSION -t $IMAGE:latest . + docker push $IMAGE:$VERSION + docker push $IMAGE:latest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d337d10..548b0f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,3 +3,35 @@ repos: rev: v5.0.0 hooks: - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + args: ["--maxkb=500"] + - id: check-yaml + exclude: ^helm/fastapi-gitops-starter/templates/ + + - repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.4 + hooks: + - id: ruff + args: ["--fix"] + + - repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + + - repo: https://github.com/PyCQA/bandit + rev: 1.7.10 + hooks: + - id: bandit + args: ["-q", "-r", "app", "tests"] + + - repo: https://github.com/Yelp/detect-secrets + rev: v1.5.0 + hooks: + - id: detect-secrets diff --git a/app/main.py b/app/main.py index ae8adc6..5d5fdff 100644 --- a/app/main.py +++ b/app/main.py @@ -1,4 +1,5 @@ import os +from datetime import datetime, timezone import uvicorn from fastapi import FastAPI @@ -11,6 +12,8 @@ root_path=os.getenv("ROOT_PATH", "/GitOps-Starter"), ) +STARTED_AT = datetime.now(timezone.utc) + @app.get("/") async def root(): @@ -27,6 +30,17 @@ async def health_check(): ) +@app.get("/info") +async def service_info(): + """Service metadata endpoint.""" + return { + "service": "fastapi-gitops-starter", + "version": app.version, + "started_at": STARTED_AT.isoformat(), + "root_path": app.root_path, + } + + @app.get("/api/items") async def list_items(): """Example endpoint to list items.""" @@ -39,6 +53,17 @@ async def list_items(): } +@app.post("/api/items") +async def create_item(name: str, description: str): + """Create a new item.""" + return { + "id": 999, + "name": name, + "description": description, + "created": True, + } + + @app.get("/api/items/{item_id}") async def get_item(item_id: int): """Example endpoint to get a specific item by ID.""" diff --git a/tests/test_main.py b/tests/test_main.py index db89e2f..d80e636 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -41,3 +41,27 @@ def test_get_item(): assert data["id"] == 5 assert data["name"] == "Item 5" assert "item number 5" in data["description"] + + +def test_create_item(): + """Test the create item endpoint.""" + response = client.post( + "/api/items", + params={"name": "New Item", "description": "Created item"}, + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == 999 + assert data["name"] == "New Item" + assert data["description"] == "Created item" + assert data["created"] is True + +def test_service_info(): + """Test the service info endpoint.""" + response = client.get("/info") + assert response.status_code == 200 + data = response.json() + assert data["service"] == "fastapi-gitops-starter" + assert data["version"] == "1.0.0" + assert data["root_path"] == "/GitOps-Starter" + assert "T" in data["started_at"]