diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a98b15e..4bd8d1a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -1,5 +1,9 @@ name: CI/CD Pipeline +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + on: release: types: [ published ] @@ -19,6 +23,20 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - &install-python + name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - &install-packages + 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 runs-on: ubuntu-latest @@ -28,6 +46,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - *install-python + - *install-packages + + - name: Execute tests + run: pytest --cov=app --cov-report=html --cov-fail-under=80 + build: name: Build Docker Image runs-on: ubuntu-latest @@ -40,3 +64,33 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + + - name: Docker build + if: github.event_name == 'push' + run: docker build -t fastapi-gitops-starter . + + # Documentation: https://docs.github.com/en/actions/tutorials/publish-packages/publish-docker-images#publishing-images-to-github-packages + - name: Log in to the Container registry + if: github.event_name == 'release' + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + if: github.event_name == 'release' + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + if: github.event_name == 'release' + id: push + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d337d10..c5770d4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,43 @@ +# Used https://pre-commit.com/hooks.html repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: + # To prevent committing large files + - id: check-added-large-files + + # To check YAML files for syntax errors (excluding Helm charts) + - id: check-yaml + exclude: ^helm/ + + # To check code style - id: trailing-whitespace + - id: end-of-file-fixer + + # To check for security issues + - id: detect-private-key + + # To sort imports in Python files + - repo: https://github.com/pycqa/isort + rev: 8.0.0 + hooks: + - id: isort + + # To check for security issues + - repo: https://github.com/PyCQA/bandit + rev: 1.9.3 + hooks: + - id: bandit + exclude: ^tests/ + + # To check code style + - repo: https://github.com/psf/black + rev: 26.1.0 + hooks: + - id: black + + # To make sure we do not commit secrets + - 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..ea377e1 100644 --- a/app/main.py +++ b/app/main.py @@ -49,5 +49,11 @@ async def get_item(item_id: int): } +@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} + + if __name__ == "__main__": - uvicorn.run(app, host="0.0.0.0", port=8000) + uvicorn.run(app, host="127.0.0.1", port=8000) diff --git a/tests/test_main.py b/tests/test_main.py index db89e2f..f90c483 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -41,3 +41,19 @@ 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.""" + name = "Basketball" + description = "A round object" + + response = client.post( + "/api/items", params={"name": name, "description": description} + ) + assert response.status_code == 200 + data = response.json() + assert data["id"] == 999 + assert data["name"] == name + assert data["description"] == description + assert data["created"]