diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index a98b15e..92ec171 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -19,6 +19,18 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Ruff + run: pip install ruff + + # Check code style and formatting issues + - name: Run Ruff linter + run: ruff check . + test: name: Run Tests runs-on: ubuntu-latest @@ -28,6 +40,22 @@ jobs: - name: Checkout code uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + # Install all req packages + - name: Install dependencies + run: | + pip install -r requirements.txt + pip install pytest pytest-cov + + # Run tests and check that at least 80% of code is tested + # If coverage is below 80%, this step will fail + - name: Run tests with coverage + run: pytest --cov=app --cov-report=term-missing --cov-fail-under=80 + build: name: Build Docker Image runs-on: ubuntu-latest @@ -40,3 +68,40 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + + # Set up Docker Buildx for building images + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + # Log in to GitHub Container Registry so we can push images + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Prepare image tags based on whether this is a release or regular push + - name: Prepare Docker image tags + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=ref,event=branch + type=sha,prefix={{branch}}- + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ github.event_name == 'release' }} + + # Build the Docker image and push it if this is a release + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: docker/Dockerfile + push: ${{ github.event_name == 'release' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d337d10..48f7f6f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,47 @@ repos: + # Basic checks - these catch common mistakes - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: + # Removes extra spaces at the end of lines - id: trailing-whitespace + + # Stops you from committing files bigger than 500KB + - id: check-added-large-files + args: ['--maxkb=500'] + + # Checks if YAML files have correct syntax + # Skips Helm chart files because they have special template syntax + - id: check-yaml + exclude: ^helm/ + args: ['--unsafe'] + + # Sorts your Python imports alphabetically so they look neat + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ['--profile', 'black'] + + # Formats your Python code to look clean and consistent + - repo: https://github.com/psf/black + rev: 24.2.0 + hooks: + - id: black + language_version: python3 + + # Checks your Python code for security problems + - repo: https://github.com/PyCQA/bandit + rev: 1.7.7 + hooks: + - id: bandit + args: ['-c', 'pyproject.toml'] + additional_dependencies: ['bandit[toml]'] + + # Makes sure you don't accidentally commit passwords or API keys + - repo: https://github.com/Yelp/detect-secrets + rev: v1.4.0 + hooks: + - id: detect-secrets + args: ['--baseline', '.secrets.baseline'] + exclude: package.lock.json diff --git a/README.md b/README.md index 79bb814..dd2933b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + GitOps with FastAPI ============== diff --git a/app/main.py b/app/main.py index ae8adc6..364bed9 100644 --- a/app/main.py +++ b/app/main.py @@ -49,5 +49,16 @@ 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) diff --git a/custom-values.yaml b/custom-values.yaml new file mode 100644 index 0000000..fdd948e --- /dev/null +++ b/custom-values.yaml @@ -0,0 +1,42 @@ +# Custom values for deploying with HPA +# This file overrides the default values.yaml + +# Use the locally built image +image: + repository: fastapi-gitops-starter + tag: local + pullPolicy: Never # Never pull from registry, use local image + +# Disable image pull secret since we're using a local image +registry: + createImagePullSecret: false + +imagePullSecrets: [] + +# Configure the app's ingress host +ingress: + enabled: true + className: "nginx" + hosts: + - host: minikube.test + paths: + - path: /GitOps-Starter + pathType: Prefix + +# Set resource limits and requests +resources: + limits: + cpu: 500m + memory: 512Mi + requests: + cpu: 100m + memory: 128Mi + +# Enable Horizontal Pod Autoscaler (HPA) +# HPA automatically scales the number of pods based on CPU/memory usage +autoscaling: + enabled: true + minReplicas: 2 # Start with at least 2 pods + maxReplicas: 10 # Scale up to maximum 10 pods + targetCPUUtilizationPercentage: 10 # Scale up when CPU usage exceeds 10% + targetMemoryUtilizationPercentage: 80 # Scale up when memory usage exceeds 80% diff --git a/tests/test_main.py b/tests/test_main.py index db89e2f..87d1ca3 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -41,3 +41,21 @@ 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.""" + # Send a POST request + response = client.post( + "/api/items", + params={"name": "Test Item", "description": "This is a test item"} + ) + # Check request + assert response.status_code == 200 + # Get the response data + data = response.json() + # Check expected fields are in the response + assert data["id"] == 999 + assert data["name"] == "Test Item" + assert data["description"] == "This is a test item" + assert data["created"]