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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ coverage.xml
.git
.gitignore
.pytest_cache
.ruff_cache
htmlcov/
.venv
venv/
ENV/
Expand Down
42 changes: 37 additions & 5 deletions .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ name: CI/CD Pipeline

on:
release:
types: [ published ]
types: [published]
push:
branches:
- '**'
- "**"
tags-ignore:
- '**'
- "**"

jobs:
lint:
Expand All @@ -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"
cache: "pip"
cache-dependency-path: requirements.txt
- name: Install dependencies
run: python -m pip install --upgrade pip && python -m pip install -r requirements.txt
- name: Run Ruff
run: ruff check .

test:
name: Run Tests
Expand All @@ -27,16 +37,38 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
cache: "pip"
cache-dependency-path: requirements.txt
- name: Install dependencies
run: python -m pip install --upgrade pip && python -m pip install -r requirements.txt
- name: Run pytest with coverage threshold
run: pytest --cov-fail-under=80

build:
name: Build Docker Image
runs-on: ubuntu-latest
needs: [lint, test]
if: github.event_name == 'push' || github.event_name == 'release'
permissions:
contents: read
actions: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build Docker image
run: docker build -f docker/Dockerfile -t fastapi-gitops-starter:ci .
- name: Log in to GitHub Container Registry
if: github.event_name == 'release'
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
- name: Tag and push release image
if: github.event_name == 'release'
run: |
IMAGE_NAME="ghcr.io/${GITHUB_REPOSITORY,,}"
VERSION_TAG="${{ github.event.release.tag_name }}"
docker tag fastapi-gitops-starter:ci "$IMAGE_NAME:$VERSION_TAG"
docker tag fastapi-gitops-starter:ci "$IMAGE_NAME:latest"
docker push "$IMAGE_NAME:$VERSION_TAG"
docker push "$IMAGE_NAME:latest"
2 changes: 1 addition & 1 deletion .github/workflows/markdown2pdf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:

- name: Replace links
run: |
cp README.md README_WITH_LINKS.md
cp README.md README_WITH_LINKS.md
sed -i -e "s#\(^\!\[[^]]\+\](\)\(images/\)#\1$URL/\2#g" README_WITH_LINKS.md
for file in sources/*; do sed -i -e "s#($file)#($URL/$file)#g" README_WITH_LINKS.md ; done

Expand Down
19 changes: 19 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,22 @@ repos:
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: check-added-large-files
- id: check-yaml
exclude: ^helm/.*/templates/
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.4
hooks:
- id: ruff
args: [--fix]
files: ^(app|tests)/.*\.py$
- repo: https://github.com/PyCQA/bandit
rev: 1.7.10
hooks:
- id: bandit
args: [-q]
files: ^app/.*\.py$
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
Binary file added 2-GitOps with FastAPI.pdf
Binary file not shown.
28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ GitOps with FastAPI

***University of Amsterdam***

# 1. Introduction
# 1. Introduction

In this tutorial, we use GitOps practices with FastAPI, including CI/CD pipelines, code quality tools, and automated testing.

Expand Down Expand Up @@ -35,7 +35,7 @@ In this tutorial, we use GitOps practices with FastAPI, including CI/CD pipeline



# 2. Tutorial
# 2. Tutorial

The steps of this tutorial are as follows:
- [Building REST APIs with FastAPI](#21-setting-up-the-project)
Expand All @@ -60,17 +60,17 @@ Prerequisites:
```

* Set Up the Python Environmentt:

```bash
# Create a virtual environment
python -m venv venv

# Activate the virtual environment
# On Linux/MacOS:
source venv/bin/activate
# On Windows:
venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt
```
Expand Down Expand Up @@ -110,7 +110,7 @@ Prerequisites:
```bash
# Check for issues
ruff check app/ tests/

# Fix auto-fixable issues
ruff check app/ tests/ --fix
```
Expand All @@ -120,7 +120,7 @@ Prerequisites:
```bash
# Check formatting
black --check app/ tests/

# Format code
black app/ tests/
```
Expand All @@ -134,19 +134,19 @@ Pre-commit hooks automatically run checks before each commit to ensure consisten
```bash
# Install pre-commit
pip install pre-commit

# Install the git hooks
pre-commit install
```

* Using Pre-commit:

Pre-commit will now run automatically on `git commit`. You can also run it manually:

```bash
# Run on all files
pre-commit run --all-files

# Run on staged files
pre-commit run
```
Expand Down Expand Up @@ -200,14 +200,14 @@ This repository includes a Helm chart for deploying the application to Kubernete
- Kubernetes 1.19+
- Helm 3.0+

* Install the Helm Chart:
* Install the Helm Chart:

```bash
helm install my-release ./helm/fastapi-gitops-starter
```

* Uninstall the Helm Chart:
* Uninstall the Helm Chart:

```bash
helm uninstall my-release
```
Expand Down Expand Up @@ -239,7 +239,7 @@ including host and paths.
* To make sure we do not commit secrets
* To check code style


## 3.2 Add a New Endpoint

1. Open `app/main.py`
Expand Down
12 changes: 11 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ async def get_item(item_id: int):
"description": f"This is item number {item_id}",
}

@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=os.getenv("UVICORN_HOST", "127.0.0.1"), port=8000)
27 changes: 27 additions & 0 deletions custom-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
image:
repository: fastapi-gitops-starter
tag: "local"
pullPolicy: Never

registry:
createImagePullSecret: false

imagePullSecrets: []

ingress:
enabled: true
className: nginx
hosts:
- host: minikube.test
paths:
- path: /GitOps-Starter
pathType: Prefix

app:
rootPath: "/GitOps-Starter"

autoscaling:
enabled: true
minReplicas: 1
maxReplicas: 3
targetCPUUtilizationPercentage: 10
4 changes: 2 additions & 2 deletions external-services-values/argo-cd-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ global:

configs:
secret:
argocdServerAdminPassword: "$2a$10$f4euqaB7AZX41WFALuxIEu7FakyF03ir.zh48m0oVRi9B7zttHztq"
dex.config: |
argocdServerAdminPassword: "$2a$10$f4euqaB7AZX41WFALuxIEu7FakyF03ir.zh48m0oVRi9B7zttHztq" # pragma: allowlist secret
dex.config: | # pragma: allowlist secret
connectors:
- type: github
id: github
Expand Down
2 changes: 1 addition & 1 deletion external-services-values/monitoring-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ grafana:
enabled: true
initChownData:
enabled: false
adminPassword: password
adminPassword: password # pragma: allowlist secret
grafana.ini:
server:
domain: naavre-dev.minikube.test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ image:

registry:
createImagePullSecret: true
secretName: image-pull-secret
secretName: image-pull-secret # pragma: allowlist secret
server: ghcr.io
token: ""

Expand Down
4 changes: 2 additions & 2 deletions helm/fastapi-gitops-starter/secret-example-canary-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ image:

registry:
createImagePullSecret: true
secretName: image-pull-secret
secretName: image-pull-secret # pragma: allowlist secret
server: ghcr.io
token: "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"
token: ""

# Enable Argo Rollouts with Canary strategy and automated analysis
rollout:
Expand Down
4 changes: 2 additions & 2 deletions helm/fastapi-gitops-starter/secret-example-values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ image:

registry:
createImagePullSecret: true
secretName: image-pull-secret
secretName: image-pull-secret # pragma: allowlist secret
server: ghcr.io
token: "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"
token: ""

imagePullSecrets:
- name: image-pull-secret
Expand Down
2 changes: 1 addition & 1 deletion helm/fastapi-gitops-starter/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ image:

registry:
createImagePullSecret: true
secretName: image-pull-secret
secretName: image-pull-secret # pragma: allowlist secret
server: ghcr.io
token: ""

Expand Down
16 changes: 16 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""
response = client.post(
"/api/items",
params={
"name": "New Item",
"description": "A newly created item"
}
)
assert response.status_code == 200
data = response.json()
assert data["id"] == 999
assert data["name"] == "New Item"
assert data["description"] == "A newly created item"
assert data["created"] is True