Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
42 changes: 42 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
<!-- Raymond Chan -->
GitOps with FastAPI
==============

Expand Down
11 changes: 11 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
42 changes: 42 additions & 0 deletions custom-values.yaml
Original file line number Diff line number Diff line change
@@ -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%
18 changes: 18 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]