Compare commits
66 Commits
a2eef9efde
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b394806552 | ||
|
|
5710df19d9 | ||
|
|
7a4c9eaacc | ||
|
|
6c4e40400a | ||
|
|
146f657bea | ||
|
|
a0dee1d499 | ||
|
|
b8a483db71 | ||
|
|
530fb3d906 | ||
|
|
84718e5039 | ||
|
|
3e6798a3e5 | ||
|
|
c5b597c7c1 | ||
|
|
694709ae9a | ||
|
|
f5a4071b71 | ||
|
|
e995482bfd | ||
|
|
f04862f6f7 | ||
|
|
305e0cc848 | ||
|
|
29ef0c65e5 | ||
|
|
10b3c2a480 | ||
|
|
8be1f85718 | ||
|
|
80ffa5e4fd | ||
|
|
aeafd8c035 | ||
|
|
a82f9d81d1 | ||
|
|
5ca3123ecf | ||
|
|
6f8e327210 | ||
|
|
05b013d378 | ||
|
|
104f7a21ff | ||
|
|
34ed68cb04 | ||
|
|
2255a469a4 | ||
|
|
27118b21f8 | ||
|
|
741117dab8 | ||
|
|
90c6f6fe62 | ||
|
|
b2e0d5bd10 | ||
|
|
c0832ff59b | ||
|
|
0935010f89 | ||
|
|
7ca15fe1a5 | ||
|
|
1b31601543 | ||
|
|
118f2c051c | ||
|
|
d17e356fcd | ||
|
|
2d695ba361 | ||
|
|
ed660dce5a | ||
|
|
78baa5ad21 | ||
|
|
e156b7c7a1 | ||
| b926845dbf | |||
| a9bcc71a72 | |||
| 741295150d | |||
| cc9a4fa8ab | |||
|
|
8f5e4f2776 | ||
|
|
daecf80731 | ||
|
|
11d116bdd1 | ||
|
|
20c9d2eaf4 | ||
|
|
a379b26808 | ||
|
|
f9d529ac87 | ||
|
|
fb396ac71a | ||
|
|
99fd37bfd8 | ||
|
|
cf2b786738 | ||
|
|
e9528217f8 | ||
|
|
733826890e | ||
|
|
fd832e9b42 | ||
|
|
d818ee6600 | ||
|
|
ceee0dcff8 | ||
|
|
ef93f4a35f | ||
|
|
073d652869 | ||
|
|
12ea09a520 | ||
|
|
99447b874b | ||
|
|
62135bbd12 | ||
|
|
ee5e7c698e |
@@ -1,7 +1,10 @@
|
||||
name: Build and Push Docker Images
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
@@ -14,31 +17,20 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.commandware.com
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Extract metadata for web image
|
||||
id: meta-web
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: git.commandware.com/demos/api7-demo/web
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
registry: ${{ vars.PACKAGES_REGISTRY || gitea.server_url }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
|
||||
- name: Build and push web image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./web
|
||||
file: ./web/Dockerfile
|
||||
tags: ${{ vars.PACKAGES_REGISTRY || gitea.server_url }}/${{ gitea.repository }}/web:${{ gitea.ref_name }}
|
||||
push: true
|
||||
tags: ${{ steps.meta-web.outputs.tags }}
|
||||
labels: ${{ steps.meta-web.outputs.labels }}
|
||||
cache-from: type=registry,ref=git.commandware.com/demos/api7-demo/web:buildcache
|
||||
cache-to: type=registry,ref=git.commandware.com/demos/api7-demo/web:buildcache,mode=max
|
||||
|
||||
build-api:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -49,28 +41,17 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to Gitea Container Registry
|
||||
- name: Log in to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: git.commandware.com
|
||||
username: ${{ gitea.actor }}
|
||||
password: ${{ secrets.GITEA_TOKEN }}
|
||||
|
||||
- name: Extract metadata for api image
|
||||
id: meta-api
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: git.commandware.com/demos/api7-demo/api
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
registry: ${{ vars.PACKAGES_REGISTRY || gitea.server_url }}
|
||||
username: ${{ secrets.USERNAME }}
|
||||
password: ${{ secrets.TOKEN }}
|
||||
|
||||
- name: Build and push api image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./api
|
||||
file: ./api/Dockerfile
|
||||
tags: ${{ vars.PACKAGES_REGISTRY || gitea.server_url }}/${{ gitea.repository }}/api:${{ gitea.ref_name }}
|
||||
push: true
|
||||
tags: ${{ steps.meta-api.outputs.tags }}
|
||||
labels: ${{ steps.meta-api.outputs.labels }}
|
||||
cache-from: type=registry,ref=git.commandware.com/demos/api7-demo/api:buildcache
|
||||
cache-to: type=registry,ref=git.commandware.com/demos/api7-demo/api:buildcache,mode=max
|
||||
|
||||
101
.gitea/workflows/helm-build.yml
Normal file
101
.gitea/workflows/helm-build.yml
Normal file
@@ -0,0 +1,101 @@
|
||||
name: Helm Chart Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-helm:
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on main branch pushes (not on PRs)
|
||||
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Fetch all history for proper versioning
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Get chart version
|
||||
run: |
|
||||
# Get version from Chart.yaml
|
||||
VERSION=$(grep '^version:' helm/api7ee-demo-k8s/Chart.yaml | awk '{print $2}')
|
||||
echo "📌 Chart version: ${VERSION}"
|
||||
echo "VERSION=${VERSION}" >> $GITHUB_ENV
|
||||
|
||||
- name: Lint Helm chart
|
||||
run: |
|
||||
helm lint helm/api7ee-demo-k8s/
|
||||
|
||||
- name: Prepare and package Helm chart
|
||||
run: |
|
||||
# Create a temporary copy of the chart for packaging
|
||||
cp -r helm/api7ee-demo-k8s /tmp/api7ee-demo-k8s-chart
|
||||
|
||||
# Update image registry and repository to match Gitea
|
||||
sed -i "s|registry: gitea.server_url|registry: ${{ vars.PACKAGES_REGISTRY || gitea.server_url }}|g" /tmp/api7ee-demo-k8s-chart/values.yaml
|
||||
sed -i "s|repository: gitea.repository/|repository: ${{ gitea.repository }}/|g" /tmp/api7ee-demo-k8s-chart/values.yaml
|
||||
|
||||
# Package the chart
|
||||
helm package /tmp/api7ee-demo-k8s-chart
|
||||
|
||||
# Store chart filename
|
||||
echo "CHART_FILE=api7ee-demo-k8s-${VERSION}.tgz" >> $GITHUB_ENV
|
||||
echo "📦 Packaged chart: api7ee-demo-k8s-${VERSION}.tgz"
|
||||
|
||||
- name: Push Helm chart to Gitea Package Registry
|
||||
run: |
|
||||
# Upload Helm chart to Gitea package registry
|
||||
echo "📤 Uploading chart to Gitea Package Registry..."
|
||||
|
||||
curl --fail-with-body \
|
||||
-H "Authorization: token ${{ secrets.TOKEN }}" \
|
||||
-X POST \
|
||||
-F "chart=@${CHART_FILE}" \
|
||||
https://${{ vars.PACKAGES_REGISTRY || gitea.server_url }}/api/packages/${{ gitea.repository_owner }}/helm/api/charts
|
||||
|
||||
echo "✅ Helm chart pushed successfully to Gitea Package Registry"
|
||||
echo "📦 Chart: ${CHART_FILE}"
|
||||
echo "🔗 Registry URL: https://${{ vars.PACKAGES_REGISTRY || gitea.server_url }}/api/packages/${{ gitea.repository_owner }}/helm"
|
||||
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "## 📦 Helm Chart Published"
|
||||
echo ""
|
||||
echo "- **Version:** ${VERSION}"
|
||||
echo "- **Chart:** ${CHART_FILE}"
|
||||
echo "- **Registry:** https://${{ vars.PACKAGES_REGISTRY || gitea.server_url }}/api/packages/${{ gitea.repository_owner }}/helm"
|
||||
echo ""
|
||||
echo "### Installation"
|
||||
echo '```bash'
|
||||
echo "helm repo add api7ee https://${{ vars.PACKAGES_REGISTRY || gitea.server_url }}/api/packages/${{ gitea.repository_owner }}/helm"
|
||||
echo "helm repo update"
|
||||
echo "helm install my-api7ee api7ee/api7ee-demo-k8s --version ${VERSION}"
|
||||
echo '```'
|
||||
|
||||
lint-only:
|
||||
runs-on: ubuntu-latest
|
||||
# Only run on PRs
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Helm
|
||||
uses: azure/setup-helm@v3
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Lint Helm chart
|
||||
run: |
|
||||
echo "🔍 Linting Helm chart for PR..."
|
||||
helm lint helm/api7ee-demo-k8s/
|
||||
echo "✅ Helm chart linting passed"
|
||||
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Production values (may contain sensitive data)
|
||||
/values.yaml
|
||||
|
||||
# Secret files with actual credentials
|
||||
api7-credentials.yaml
|
||||
*-credentials.yaml
|
||||
!*-credentials.yaml.template
|
||||
534
README.md
534
README.md
@@ -1,492 +1,86 @@
|
||||
# API7 Demo - FastAPI Applications
|
||||
# API7 Enterprise Demo
|
||||
|
||||
Demo project showcasing two FastAPI applications deployed on Kubernetes through API7 Enterprise Gateway with automated CI/CD using Gitea.
|
||||
Complete production-ready deployment of API7 Enterprise API Gateway on Kubernetes.
|
||||
|
||||
## 📁 Project Structure
|
||||
## Features
|
||||
|
||||
```
|
||||
api7-demo/
|
||||
├── web/ # Web application
|
||||
│ ├── main.py # FastAPI web app with HTML UI
|
||||
│ ├── Dockerfile # Container image
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── api/ # API application
|
||||
│ ├── main.py # FastAPI REST API with Swagger
|
||||
│ ├── Dockerfile # Container image
|
||||
│ └── requirements.txt # Python dependencies
|
||||
├── .gitea/
|
||||
│ └── workflows/
|
||||
│ └── build.yml # CI/CD pipeline
|
||||
└── README.md
|
||||
```
|
||||
- ✅ **API7 Enterprise Gateway** with advanced routing and plugins
|
||||
- ✅ **Kubernetes Service Discovery** for dynamic backend discovery
|
||||
- ✅ **Automatic TLS** with cert-manager and Let's Encrypt
|
||||
- ✅ **Rate Limiting** (standard + AI-based for LLM endpoints)
|
||||
- ✅ **CORS** policies for browser-based applications
|
||||
- ✅ **Secure Secret Management** with External Secrets Operator support
|
||||
- ✅ **CI/CD** integration with ArgoCD
|
||||
- ✅ **Complete Documentation** with MkDocs
|
||||
|
||||
## 🌐 Applications
|
||||
|
||||
### Web Application (`web/`)
|
||||
|
||||
Modern web interface with:
|
||||
- Responsive HTML dashboard
|
||||
- System information display
|
||||
- Health check endpoints
|
||||
|
||||
**Endpoints:**
|
||||
- `GET /` - Main webpage
|
||||
- `GET /health` - Health check
|
||||
|
||||
**Port:** 8000
|
||||
|
||||
### API Application (`api/`)
|
||||
|
||||
REST API with full documentation:
|
||||
- CRUD operations for Items and Users
|
||||
- Automatic Swagger/OpenAPI docs
|
||||
- Data validation with Pydantic
|
||||
|
||||
**Key Endpoints:**
|
||||
- `GET /docs` - Swagger UI
|
||||
- `GET /items` - List items
|
||||
- `POST /items` - Create item
|
||||
- `GET /users` - List users
|
||||
- `GET /health` - Health check
|
||||
|
||||
**Port:** 8001 (internally 8000)
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Local Development
|
||||
|
||||
**Web Application:**
|
||||
```bash
|
||||
cd web
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
# Access at http://localhost:8000
|
||||
```
|
||||
|
||||
**API Application:**
|
||||
```bash
|
||||
cd api
|
||||
pip install -r requirements.txt
|
||||
python main.py
|
||||
# Swagger at http://localhost:8000/docs
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
**Build and run Web:**
|
||||
```bash
|
||||
docker build -t web-app ./web
|
||||
docker run -p 8000:8000 web-app
|
||||
```
|
||||
|
||||
**Build and run API:**
|
||||
```bash
|
||||
docker build -t api-app ./api
|
||||
docker run -p 8001:8000 api-app
|
||||
```
|
||||
|
||||
## 🐳 CI/CD with Gitea
|
||||
|
||||
### Automated Pipeline
|
||||
|
||||
The `.gitea/workflows/build.yml` pipeline automatically:
|
||||
1. Builds Docker images for both applications
|
||||
2. Pushes to Gitea container registry
|
||||
3. Tags images with branch name
|
||||
4. Implements layer caching for faster builds
|
||||
|
||||
**Triggers:**
|
||||
- Any branch push
|
||||
- Manual dispatch
|
||||
|
||||
**Registry:** `git.commandware.com/demos/api7-demo`
|
||||
|
||||
**Images:**
|
||||
- `git.commandware.com/demos/api7-demo/web:<branch-name>`
|
||||
- `git.commandware.com/demos/api7-demo/api:<branch-name>`
|
||||
|
||||
### Setup
|
||||
|
||||
1. **Create `GITEA_TOKEN` secret:**
|
||||
- Repository Settings → Secrets → Add Secret
|
||||
- Name: `GITEA_TOKEN`
|
||||
- Generate token at: User Settings → Applications → Generate Token
|
||||
- Required scope: `write:package`
|
||||
|
||||
2. **Push to trigger build:**
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## ☸️ Kubernetes Deployment
|
||||
## Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Kubernetes cluster (v1.19+)
|
||||
- API7 Enterprise Gateway installed
|
||||
- Namespace: `api7ee`
|
||||
- Kubernetes cluster (1.24+)
|
||||
- Helm 3.x
|
||||
- kubectl configured
|
||||
- cert-manager installed
|
||||
- NGINX Ingress Controller
|
||||
|
||||
### Deployment Manifest
|
||||
### Installation
|
||||
|
||||
**k8s-deployments.yaml:**
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web
|
||||
namespace: api7ee
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: web
|
||||
spec:
|
||||
containers:
|
||||
- name: web
|
||||
image: git.commandware.com/demos/api7-demo/web:main
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web-service
|
||||
namespace: api7ee
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8000
|
||||
name: http
|
||||
selector:
|
||||
app: web
|
||||
---
|
||||
# Similar configuration for api deployment
|
||||
```
|
||||
|
||||
### Deploy
|
||||
|
||||
```bash
|
||||
kubectl apply -f k8s-deployments.yaml
|
||||
kubectl rollout status deployment/web -n api7ee
|
||||
kubectl rollout status deployment/api -n api7ee
|
||||
```
|
||||
|
||||
## 🔌 API7 Gateway Configuration
|
||||
|
||||
### Route Configuration (ADC)
|
||||
|
||||
**api7-routes.yaml:**
|
||||
```yaml
|
||||
services:
|
||||
- name: web-service
|
||||
upstream:
|
||||
name: web-upstream
|
||||
scheme: http
|
||||
type: roundrobin
|
||||
discovery_type: kubernetes
|
||||
service_name: api7ee/web-service:http
|
||||
routes:
|
||||
- name: web-route
|
||||
uris:
|
||||
- /*
|
||||
hosts:
|
||||
- demo.yourdomain.com
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
|
||||
- name: api-service
|
||||
upstream:
|
||||
name: api-upstream
|
||||
scheme: http
|
||||
type: roundrobin
|
||||
discovery_type: kubernetes
|
||||
service_name: api7ee/api-service:http
|
||||
routes:
|
||||
- name: api-route
|
||||
priority: 10
|
||||
uris:
|
||||
- /api
|
||||
- /api/*
|
||||
hosts:
|
||||
- demo.yourdomain.com
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
proxy-rewrite:
|
||||
regex_uri:
|
||||
- ^/api/(.*)
|
||||
- /$1
|
||||
|
||||
ssls:
|
||||
- snis:
|
||||
- "*.yourdomain.com"
|
||||
certificates:
|
||||
- certificate: |
|
||||
-----BEGIN CERTIFICATE-----
|
||||
<YOUR_CERTIFICATE>
|
||||
-----END CERTIFICATE-----
|
||||
key: |
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
<YOUR_KEY>
|
||||
-----END PRIVATE KEY-----
|
||||
```
|
||||
|
||||
### Sync Configuration
|
||||
|
||||
```bash
|
||||
adc sync -f api7-routes.yaml \
|
||||
--backend api7ee \
|
||||
--server https://api7-dashboard.yourdomain.com \
|
||||
--token <YOUR_API7_TOKEN> \
|
||||
--gateway-group default \
|
||||
--tls-skip-verify
|
||||
```
|
||||
|
||||
### Publish Routes
|
||||
|
||||
Routes must be published via API7 Dashboard to become active:
|
||||
|
||||
1. Navigate to **Services** → Select service
|
||||
2. Go to **Routes** tab
|
||||
3. Click **"Publish"** for each route
|
||||
4. Select gateway group: **"default"**
|
||||
5. Confirm
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Test Endpoints
|
||||
|
||||
```bash
|
||||
# Web application
|
||||
curl https://demo.yourdomain.com
|
||||
|
||||
# API health check
|
||||
curl https://demo.yourdomain.com/api/health
|
||||
|
||||
# API Swagger (if exposed)
|
||||
curl https://demo.yourdomain.com/api/docs
|
||||
|
||||
# Verify HTTP → HTTPS redirect
|
||||
curl -I http://demo.yourdomain.com
|
||||
```
|
||||
|
||||
### Verify Service Discovery
|
||||
|
||||
```bash
|
||||
# Scale deployment
|
||||
kubectl scale deployment web -n api7ee --replicas=5
|
||||
|
||||
# API7 automatically discovers new endpoints
|
||||
kubectl get endpoints -n api7ee web-service
|
||||
|
||||
# Scale back
|
||||
kubectl scale deployment web -n api7ee --replicas=2
|
||||
```
|
||||
|
||||
## 📊 Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
↓
|
||||
LoadBalancer (External IP)
|
||||
↓
|
||||
NGINX Ingress Controller
|
||||
↓
|
||||
TLS Termination (Let's Encrypt)
|
||||
↓
|
||||
API7 Gateway (ClusterIP)
|
||||
↓
|
||||
[Kubernetes Service Discovery]
|
||||
↓
|
||||
┌──────────────┬──────────────┐
|
||||
│ Web Service │ API Service │
|
||||
└──────────────┴──────────────┘
|
||||
```
|
||||
|
||||
### Traffic Routing
|
||||
|
||||
- `demo.yourdomain.com/api/*` → API Service (priority 10, strips `/api` prefix)
|
||||
- `demo.yourdomain.com/*` → Web Service (default priority)
|
||||
|
||||
## 🔧 API7 Enterprise Setup Guide
|
||||
|
||||
### Certificate Management
|
||||
|
||||
**Using cert-manager with Cloudflare:**
|
||||
|
||||
```yaml
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: cloudflare-acme-prod
|
||||
spec:
|
||||
acme:
|
||||
email: your-email@example.com
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod-key
|
||||
solvers:
|
||||
- dns01:
|
||||
cloudflare:
|
||||
apiTokenSecretRef:
|
||||
name: cloudflare-api-token-secret
|
||||
key: api-token
|
||||
```
|
||||
|
||||
**Create Cloudflare secret:**
|
||||
```bash
|
||||
kubectl create secret generic cloudflare-api-token-secret \
|
||||
-n cert-manager \
|
||||
--from-literal=api-token=YOUR_TOKEN
|
||||
```
|
||||
|
||||
### Kubernetes Service Discovery
|
||||
|
||||
**Configure in API7 Dashboard:**
|
||||
|
||||
1. **Settings → Service Registry → Add Service Registry**
|
||||
2. **Select:** Kubernetes
|
||||
3. **Configure:**
|
||||
```
|
||||
Name: kubernetes-cluster
|
||||
API Server: https://kubernetes.default.svc.cluster.local:443
|
||||
Token Path: /var/run/secrets/kubernetes.io/serviceaccount/token
|
||||
Namespace Filter: api7ee (optional)
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone https://git.commandware.com/demos/api7-demo.git
|
||||
cd api7-demo
|
||||
```
|
||||
|
||||
**Verify RBAC permissions:**
|
||||
```bash
|
||||
kubectl create clusterrolebinding api7-gateway-discovery \
|
||||
--clusterrole=view \
|
||||
--serviceaccount=<API7_NAMESPACE>:default
|
||||
```
|
||||
2. **Create API7 credentials secret:**
|
||||
```bash
|
||||
# Copy the template
|
||||
cp api7-credentials.yaml.template api7-credentials.yaml
|
||||
|
||||
### Ingress Configuration
|
||||
# Get the admin key from API7 installation
|
||||
kubectl get secret -n api7ee api7ee3-0-1759339083 \
|
||||
-o jsonpath='{.data.admin_key}' | base64 -d
|
||||
|
||||
**Example ingress for API7 Gateway:**
|
||||
# Edit api7-credentials.yaml and fill in the actual admin key
|
||||
vim api7-credentials.yaml
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api7-gateway-ingress
|
||||
namespace: api7ee
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- demo.yourdomain.com
|
||||
secretName: demo-tls
|
||||
rules:
|
||||
- host: demo.yourdomain.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api7-gateway-service
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
# Apply the secret
|
||||
kubectl apply -f api7-credentials.yaml -n api7ee
|
||||
```
|
||||
|
||||
**Note:** Ingress backend points to API7 Gateway service, not application services.
|
||||
3. **Update values.yaml to use the secret:**
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
existingSecret: "api7-credentials"
|
||||
```
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
4. **Deploy with Helm:**
|
||||
```bash
|
||||
helm upgrade --install api7ee-demo helm/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--create-namespace \
|
||||
--values values.yaml
|
||||
```
|
||||
|
||||
### Routes Return 404
|
||||
5. **Verify deployment:**
|
||||
```bash
|
||||
kubectl get all -n api7ee
|
||||
kubectl logs -n api7ee job/api7ee-demo-api7ee-demo-k8s-adc-sync
|
||||
curl https://commandware.it/api/health
|
||||
```
|
||||
|
||||
**Problem:** Routes synced but returning 404
|
||||
## Documentation
|
||||
|
||||
**Solution:** Routes not published. Publish via API7 Dashboard.
|
||||
View comprehensive documentation at: **[web/docs/](web/docs/)**
|
||||
|
||||
### Service Discovery Not Working
|
||||
- [Getting Started](web/docs/getting-started.md)
|
||||
- [Architecture](web/docs/architecture.md)
|
||||
- [Ingress & Routing](web/docs/ingress-routing.md)
|
||||
- [Service Discovery](web/docs/service-discovery.md)
|
||||
- [Secret Management](web/docs/secret-management.md)
|
||||
- [Troubleshooting](web/docs/troubleshooting.md)
|
||||
|
||||
**Problem:** `service registry not found` error
|
||||
## Support
|
||||
|
||||
**Checklist:**
|
||||
- ✅ Service registry configured in dashboard
|
||||
- ✅ Registry status shows "Connected"
|
||||
- ✅ RBAC permissions granted
|
||||
- ✅ Service ports have `name` field
|
||||
|
||||
### Certificate Issues
|
||||
|
||||
**Problem:** Certificate not trusted
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check certificate status
|
||||
kubectl get certificate -n api7ee
|
||||
kubectl describe certificate -n api7ee <cert-name>
|
||||
|
||||
# Check cert-manager logs
|
||||
kubectl logs -n cert-manager -l app.kubernetes.io/name=cert-manager --tail=50
|
||||
```
|
||||
|
||||
### Container Images Not Found
|
||||
|
||||
**Problem:** ImagePullBackOff in Kubernetes
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Create registry secret
|
||||
kubectl create secret docker-registry gitea-registry \
|
||||
--docker-server=git.commandware.com \
|
||||
--docker-username=<USERNAME> \
|
||||
--docker-password=<TOKEN> \
|
||||
-n api7ee
|
||||
|
||||
# Add to deployment
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: gitea-registry
|
||||
```
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- **API7 Documentation:** https://docs.api7.ai/
|
||||
- **APISIX Documentation:** https://apisix.apache.org/docs/
|
||||
- **FastAPI Documentation:** https://fastapi.tiangolo.com/
|
||||
- **Gitea Actions:** https://docs.gitea.com/usage/actions/overview
|
||||
|
||||
## 🔐 Best Practices
|
||||
|
||||
### Security
|
||||
- ✅ Use TLS for all external traffic
|
||||
- ✅ Implement rate limiting on public endpoints
|
||||
- ✅ Store secrets in Kubernetes Secrets or external vault
|
||||
- ✅ Use least-privilege RBAC permissions
|
||||
|
||||
### Performance
|
||||
- ✅ Enable service discovery for automatic scaling
|
||||
- ✅ Configure appropriate resource limits
|
||||
- ✅ Use Docker layer caching in CI/CD
|
||||
- ✅ Implement health checks and readiness probes
|
||||
|
||||
### Monitoring
|
||||
- ✅ Monitor gateway metrics in API7 Dashboard
|
||||
- ✅ Track API usage and performance
|
||||
- ✅ Set up alerts for certificate expiration
|
||||
- ✅ Review application logs regularly
|
||||
|
||||
## 📝 License
|
||||
|
||||
Demo project for API7 Enterprise Gateway evaluation.
|
||||
|
||||
---
|
||||
|
||||
**Repository:** https://git.commandware.com/demos/api7-demo.git
|
||||
- Issues: https://git.commandware.com/demos/api7-demo/issues
|
||||
- API7 Docs: https://docs.api7.ai/enterprise/
|
||||
|
||||
65
adc.yaml
Normal file
65
adc.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
services:
|
||||
- name: apache-service
|
||||
hosts:
|
||||
- commandware.it
|
||||
upstream:
|
||||
name: apache-upstream
|
||||
scheme: http
|
||||
type: roundrobin
|
||||
nodes:
|
||||
- host: apache-service.api7ee.svc.cluster.local
|
||||
port: 80
|
||||
weight: 100
|
||||
routes:
|
||||
- name: apache-route
|
||||
uris:
|
||||
- /*
|
||||
vars:
|
||||
- - uri
|
||||
- "~~"
|
||||
- "^(?!/api)"
|
||||
priority: 1
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
|
||||
- name: nginx-api-service
|
||||
hosts:
|
||||
- commandware.it
|
||||
upstream:
|
||||
name: nginx-upstream
|
||||
scheme: http
|
||||
type: roundrobin
|
||||
nodes:
|
||||
- host: nginx-service.api7ee.svc.cluster.local
|
||||
port: 80
|
||||
weight: 100
|
||||
routes:
|
||||
- name: nginx-api-llm-route
|
||||
uris:
|
||||
- /api/llm
|
||||
- /api/llm/*
|
||||
priority: 20
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
ai-rate-limiting:
|
||||
limit: 100
|
||||
time_window: 60
|
||||
rejected_code: 429
|
||||
limit_strategy: "total_tokens"
|
||||
|
||||
- name: nginx-api-route
|
||||
uris:
|
||||
- /api
|
||||
- /api/*
|
||||
priority: 10
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
limit-count:
|
||||
count: 100
|
||||
time_window: 60
|
||||
rejected_code: 429
|
||||
key_type: "var"
|
||||
key: "remote_addr"
|
||||
@@ -2,14 +2,18 @@ FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir fastapi uvicorn[standard] pydantic
|
||||
# Copy requirements first for better caching
|
||||
COPY requirements.txt .
|
||||
|
||||
# Install dependencies from requirements.txt
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application
|
||||
COPY db.json .
|
||||
COPY main.py .
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8001
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the application
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]
|
||||
|
||||
814
api/README.md
814
api/README.md
@@ -1,87 +1,787 @@
|
||||
# API Service - FastAPI
|
||||
# API Service - FastAPI Backend
|
||||
|
||||
A RESTful API service built with FastAPI featuring Swagger documentation.
|
||||
A RESTful API service built with FastAPI featuring automatic Swagger documentation, CRUD operations, and integrated LLM endpoints with OpenAI-compatible API support.
|
||||
|
||||
## Features
|
||||
## 📋 Overview
|
||||
|
||||
- 📚 Automatic Swagger/OpenAPI documentation
|
||||
- 🔄 RESTful API endpoints
|
||||
- ✅ Data validation with Pydantic
|
||||
- 📊 Statistics and monitoring endpoints
|
||||
- ❤️ Health check endpoints
|
||||
This API backend provides:
|
||||
|
||||
## Local Development
|
||||
- **RESTful CRUD API**: Items and Users management
|
||||
- **LLM Integration**: OpenAI-compatible chat endpoints for AI-powered features
|
||||
- **Automatic Documentation**: Swagger UI and ReDoc
|
||||
- **Data Validation**: Pydantic models with type checking
|
||||
- **Health Monitoring**: Health check endpoints
|
||||
- **Production Ready**: Designed for Kubernetes deployment with API7 Gateway
|
||||
|
||||
### Run with Python
|
||||
## ✨ Features
|
||||
|
||||
### REST API
|
||||
|
||||
- **Items Management**: Create, read, update, delete items
|
||||
- **Users Management**: User CRUD operations
|
||||
- **Pagination Support**: Query parameters for data filtering
|
||||
- **Validation**: Automatic request/response validation with Pydantic
|
||||
|
||||
### LLM Integration
|
||||
|
||||
- **Chat Endpoint**: OpenAI-compatible chat completions API
|
||||
- **Model Management**: List available LLM models
|
||||
- **Token Tracking**: Returns token usage per request
|
||||
- **Configurable**: Supports custom OpenAI-compatible backends (Open WebUI, Ollama, etc.)
|
||||
- **Rate Limited**: Designed to work with API7's AI rate limiting (100 tokens/60s)
|
||||
|
||||
### Documentation
|
||||
|
||||
- **Swagger UI**: Interactive API documentation at `/docs`
|
||||
- **ReDoc**: Alternative documentation at `/redoc`
|
||||
- **OpenAPI Schema**: JSON schema at `/openapi.json`
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Local Development
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
```bash
|
||||
# Install dependencies
|
||||
pip install -r requirements.txt
|
||||
python >= 3.8
|
||||
pip
|
||||
```
|
||||
|
||||
# Run the application
|
||||
#### Install Dependencies
|
||||
|
||||
```bash
|
||||
cd api
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
#### Run the Application
|
||||
|
||||
```bash
|
||||
# Basic run
|
||||
python main.py
|
||||
|
||||
# Or use uvicorn directly
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8001
|
||||
|
||||
# With custom port
|
||||
uvicorn main:app --reload --port 8080
|
||||
```
|
||||
|
||||
### Run with Docker
|
||||
#### Access the API
|
||||
|
||||
- **Root**: http://localhost:8001/
|
||||
- **Swagger UI**: http://localhost:8001/docs
|
||||
- **ReDoc**: http://localhost:8001/redoc
|
||||
- **Health Check**: http://localhost:8001/health
|
||||
|
||||
### Docker
|
||||
|
||||
#### Build Image
|
||||
|
||||
```bash
|
||||
# Build image
|
||||
docker build -t fastapi-api .
|
||||
|
||||
# Run container
|
||||
docker run -p 8000:8000 fastapi-api
|
||||
docker build -t api-service .
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
#### Run Container
|
||||
|
||||
### Items
|
||||
- `GET /items` - List all items (with pagination)
|
||||
- `GET /items/{item_id}` - Get specific item
|
||||
- `POST /items` - Create new item
|
||||
- `PUT /items/{item_id}` - Update item
|
||||
- `DELETE /items/{item_id}` - Delete item
|
||||
|
||||
### Users
|
||||
- `GET /users` - List all users
|
||||
- `GET /users/{user_id}` - Get specific user
|
||||
- `POST /users` - Create new user
|
||||
|
||||
### Other
|
||||
- `GET /` - API information
|
||||
- `GET /health` - Health check
|
||||
- `GET /info` - Service information
|
||||
- `GET /stats` - API statistics
|
||||
|
||||
## Documentation
|
||||
|
||||
- Swagger UI: http://localhost:8000/docs
|
||||
- ReDoc: http://localhost:8000/redoc
|
||||
- OpenAPI JSON: http://localhost:8000/openapi.json
|
||||
|
||||
## Example Requests
|
||||
|
||||
### Create Item
|
||||
```bash
|
||||
curl -X POST "http://localhost:8000/items" \
|
||||
# Basic run
|
||||
docker run -p 8001:8001 api-service
|
||||
|
||||
# With environment variables
|
||||
docker run -p 8001:8001 \
|
||||
-e OPENAI_API_BASE="http://host.docker.internal:11434/api" \
|
||||
-e OPENAI_API_KEY="your-api-key" \
|
||||
-e DEFAULT_LLM_MODEL="videogame-expert" \
|
||||
api-service
|
||||
```
|
||||
|
||||
## 🔌 API Endpoints
|
||||
|
||||
### Information Endpoints
|
||||
|
||||
#### `GET /`
|
||||
|
||||
Root endpoint with API information.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Welcome to API Demo",
|
||||
"version": "1.0.0",
|
||||
"docs": "/docs",
|
||||
"timestamp": "2025-10-07T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /health`
|
||||
|
||||
Health check endpoint.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "api",
|
||||
"timestamp": "2025-10-07T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
### Items Endpoints
|
||||
|
||||
#### `GET /items`
|
||||
|
||||
Get all items with optional pagination.
|
||||
|
||||
**Query Parameters**:
|
||||
|
||||
- None (returns all items)
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Laptop",
|
||||
"description": "High-performance laptop",
|
||||
"price": 999.99,
|
||||
"in_stock": true
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Mouse",
|
||||
"description": "Wireless mouse",
|
||||
"price": 29.99,
|
||||
"in_stock": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### `GET /items/{item_id}`
|
||||
|
||||
Get a specific item by ID.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Laptop",
|
||||
"description": "High-performance laptop",
|
||||
"price": 999.99,
|
||||
"in_stock": true
|
||||
}
|
||||
```
|
||||
|
||||
#### `POST /items`
|
||||
|
||||
Create a new item.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Monitor",
|
||||
"description": "4K Display",
|
||||
"price": 299.99,
|
||||
"in_stock": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Monitor",
|
||||
"description": "4K Display",
|
||||
"price": 299.99,
|
||||
"in_stock": true
|
||||
}
|
||||
```
|
||||
|
||||
#### `PUT /items/{item_id}`
|
||||
|
||||
Update an existing item.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Monitor",
|
||||
"description": "Updated 4K Display",
|
||||
"price": 279.99,
|
||||
"in_stock": true
|
||||
}
|
||||
```
|
||||
|
||||
#### `DELETE /items/{item_id}`
|
||||
|
||||
Delete an item.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"message": "Item deleted successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Users Endpoints
|
||||
|
||||
#### `GET /users`
|
||||
|
||||
Get all users.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### `GET /users/{user_id}`
|
||||
|
||||
Get a specific user by ID.
|
||||
|
||||
#### `POST /users`
|
||||
|
||||
Create a new user.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "jane_doe",
|
||||
"email": "jane@example.com",
|
||||
"active": true
|
||||
}
|
||||
```
|
||||
|
||||
### LLM Endpoints
|
||||
|
||||
#### `POST /llm/chat`
|
||||
|
||||
Send a chat message to the LLM.
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "What is The Legend of Zelda?",
|
||||
"max_tokens": 150,
|
||||
"temperature": 0.7,
|
||||
"model": "videogame-expert"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"response": "The Legend of Zelda is a high-fantasy action-adventure video game franchise created by Japanese game designers Shigeru Miyamoto and Takashi Tezuka...",
|
||||
"tokens_used": 85,
|
||||
"model": "videogame-expert",
|
||||
"timestamp": "2025-10-07T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**Rate Limiting**: When deployed with API7 Gateway, this endpoint is limited to **100 tokens per 60 seconds** using AI rate limiting.
|
||||
|
||||
#### `GET /llm/models`
|
||||
|
||||
List available LLM models.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"id": "videogame-expert",
|
||||
"name": "Videogame Expert",
|
||||
"max_tokens": 4096,
|
||||
"provider": "Open WebUI"
|
||||
}
|
||||
],
|
||||
"default_model": "videogame-expert",
|
||||
"timestamp": "2025-10-07T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /llm/health`
|
||||
|
||||
LLM service health check.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "llm-api",
|
||||
"provider": "Open WebUI",
|
||||
"endpoint": "http://localhost/api",
|
||||
"default_model": "videogame-expert",
|
||||
"rate_limit": "ai-rate-limiting enabled (100 tokens/60s)",
|
||||
"timestamp": "2025-10-07T10:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
Configure the API service using environment variables:
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
| ------------------- | ---------------------------------- | ---------------------- | -------- |
|
||||
| `OPENAI_API_BASE` | OpenAI-compatible API endpoint URL | `http://localhost/api` | No |
|
||||
| `OPENAI_API_KEY` | API key for LLM service | `your-api-key` | No |
|
||||
| `DEFAULT_LLM_MODEL` | Default LLM model ID | `your-model-id` | No |
|
||||
|
||||
### Example Configuration
|
||||
|
||||
**Development**:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_BASE="http://localhost:11434/api"
|
||||
export OPENAI_API_KEY="not-required-for-ollama"
|
||||
export DEFAULT_LLM_MODEL="llama2"
|
||||
```
|
||||
|
||||
**Production (Open WebUI)**:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_BASE="https://openwebui.example.com/api"
|
||||
export OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxx"
|
||||
export DEFAULT_LLM_MODEL="videogame-expert"
|
||||
```
|
||||
|
||||
**Kubernetes Deployment**:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: OPENAI_API_BASE
|
||||
value: "http://openwebui.ai:8080/api"
|
||||
- name: OPENAI_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: llm-secrets
|
||||
key: api-key
|
||||
- name: DEFAULT_LLM_MODEL
|
||||
value: "videogame-expert"
|
||||
```
|
||||
|
||||
## 📊 Data Models
|
||||
|
||||
### Item Model
|
||||
|
||||
```python
|
||||
class Item(BaseModel):
|
||||
id: Optional[int] = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
price: float
|
||||
in_stock: bool = True
|
||||
```
|
||||
|
||||
### User Model
|
||||
|
||||
```python
|
||||
class User(BaseModel):
|
||||
id: Optional[int] = None
|
||||
username: str
|
||||
email: str
|
||||
active: bool = True
|
||||
```
|
||||
|
||||
### LLM Request Model
|
||||
|
||||
```python
|
||||
class LLMRequest(BaseModel):
|
||||
prompt: str
|
||||
max_tokens: Optional[int] = 150
|
||||
temperature: Optional[float] = 0.7
|
||||
model: Optional[str] = DEFAULT_MODEL
|
||||
```
|
||||
|
||||
### LLM Response Model
|
||||
|
||||
```python
|
||||
class LLMResponse(BaseModel):
|
||||
response: str
|
||||
tokens_used: int
|
||||
model: str
|
||||
timestamp: str
|
||||
```
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### cURL Examples
|
||||
|
||||
**Create Item**:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8001/items" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"id": 4,
|
||||
"name": "Monitor",
|
||||
"description": "4K Display",
|
||||
"price": 299.99,
|
||||
"name": "Keyboard",
|
||||
"description": "Mechanical keyboard",
|
||||
"price": 79.99,
|
||||
"in_stock": true
|
||||
}'
|
||||
```
|
||||
|
||||
### Get Items
|
||||
**Get Items**:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8000/items?skip=0&limit=10&in_stock=true"
|
||||
curl "http://localhost:8001/items"
|
||||
```
|
||||
|
||||
### Get Statistics
|
||||
**Update Item**:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8000/stats"
|
||||
curl -X PUT "http://localhost:8001/items/1" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Laptop Pro",
|
||||
"description": "Updated laptop",
|
||||
"price": 1099.99,
|
||||
"in_stock": true
|
||||
}'
|
||||
```
|
||||
|
||||
**Delete Item**:
|
||||
|
||||
```bash
|
||||
curl -X DELETE "http://localhost:8001/items/3"
|
||||
```
|
||||
|
||||
**LLM Chat**:
|
||||
|
||||
```bash
|
||||
curl -X POST "http://localhost:8001/llm/chat" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"prompt": "Tell me about Mario Bros",
|
||||
"max_tokens": 100,
|
||||
"temperature": 0.7,
|
||||
"model": "videogame-expert"
|
||||
}'
|
||||
```
|
||||
|
||||
**Health Checks**:
|
||||
|
||||
```bash
|
||||
curl "http://localhost:8001/health"
|
||||
curl "http://localhost:8001/llm/health"
|
||||
```
|
||||
|
||||
### Python Testing
|
||||
|
||||
```python
|
||||
import httpx
|
||||
|
||||
# Test item creation
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8001/items",
|
||||
json={
|
||||
"name": "Monitor",
|
||||
"description": "4K Display",
|
||||
"price": 299.99,
|
||||
"in_stock": True
|
||||
}
|
||||
)
|
||||
print(response.json())
|
||||
|
||||
# Test LLM chat
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
"http://localhost:8001/llm/chat",
|
||||
json={
|
||||
"prompt": "What is Minecraft?",
|
||||
"max_tokens": 150,
|
||||
"model": "videogame-expert"
|
||||
}
|
||||
)
|
||||
print(response.json())
|
||||
```
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
### Dockerfile
|
||||
|
||||
The Dockerfile uses Python 3.11 slim image and installs dependencies directly:
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
RUN pip install --no-cache-dir fastapi uvicorn[standard] pydantic
|
||||
|
||||
# Copy application
|
||||
COPY db.json .
|
||||
COPY main.py .
|
||||
|
||||
EXPOSE 8001
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8001"]
|
||||
```
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
# Build
|
||||
docker build -t api-service:latest .
|
||||
|
||||
# Run with environment variables
|
||||
docker run -d \
|
||||
--name api-service \
|
||||
-p 8001:8001 \
|
||||
-e OPENAI_API_BASE="http://host.docker.internal:11434/api" \
|
||||
-e OPENAI_API_KEY="your-key" \
|
||||
api-service:latest
|
||||
|
||||
# View logs
|
||||
docker logs -f api-service
|
||||
|
||||
# Stop and remove
|
||||
docker stop api-service
|
||||
docker rm api-service
|
||||
```
|
||||
|
||||
## ☸️ Kubernetes Deployment
|
||||
|
||||
### Basic Deployment
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: api-service
|
||||
namespace: api7ee
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: api
|
||||
spec:
|
||||
containers:
|
||||
- name: api
|
||||
image: git.commandware.com/demos/api7-demo/api:main
|
||||
ports:
|
||||
- containerPort: 8001
|
||||
name: http
|
||||
env:
|
||||
- name: OPENAI_API_BASE
|
||||
value: "http://openwebui.ai:8080/api"
|
||||
- name: OPENAI_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: llm-secrets
|
||||
key: api-key
|
||||
- name: DEFAULT_LLM_MODEL
|
||||
value: "videogame-expert"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8001
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8001
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api-service
|
||||
namespace: api7ee
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8080
|
||||
targetPort: 8001
|
||||
name: http
|
||||
selector:
|
||||
app: api
|
||||
```
|
||||
|
||||
### With Helm
|
||||
|
||||
See the [Helm chart README](../helm/api7ee-demo-k8s/README.md) for full deployment options.
|
||||
|
||||
```bash
|
||||
helm install api7ee-demo ./helm/api7ee-demo-k8s \
|
||||
--set api.image.tag=v1.0.0 \
|
||||
--set api.replicaCount=5 \
|
||||
--namespace api7ee
|
||||
```
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Environment Variables**: Store sensitive data (API keys) in Kubernetes Secrets
|
||||
2. **Non-root User**: Container runs as non-root user (UID 1000)
|
||||
3. **Read-only Filesystem**: Root filesystem is read-only
|
||||
4. **Input Validation**: All requests validated with Pydantic
|
||||
5. **CORS**: Configure CORS policies in API7 Gateway
|
||||
6. **Rate Limiting**: API7 Gateway enforces rate limits
|
||||
|
||||
### Example Secret
|
||||
|
||||
```bash
|
||||
kubectl create secret generic llm-secrets \
|
||||
--from-literal=api-key='your-openai-api-key' \
|
||||
-n api7ee
|
||||
```
|
||||
|
||||
## 📦 Dependencies
|
||||
|
||||
### Python Requirements
|
||||
|
||||
```
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
pydantic==2.5.0
|
||||
httpx==0.26.0
|
||||
```
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 🚀 Production Deployment
|
||||
|
||||
### Recommended Configuration
|
||||
|
||||
```yaml
|
||||
api:
|
||||
replicaCount: 5
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 5
|
||||
maxReplicas: 30
|
||||
targetCPUUtilizationPercentage: 70
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "warn"
|
||||
- name: ENVIRONMENT
|
||||
value: "production"
|
||||
```
|
||||
|
||||
### API7 Gateway Integration
|
||||
|
||||
When deployed behind API7 Gateway:
|
||||
|
||||
**Rate Limiting**:
|
||||
|
||||
- `/api/*`: 100 requests/60s per IP (standard rate limiting)
|
||||
- `/api/llm/*`: 100 tokens/60s (AI rate limiting)
|
||||
|
||||
**Routing**:
|
||||
|
||||
- Priority 10: `/api/*` routes
|
||||
- Priority 20: `/api/llm/*` routes (higher priority)
|
||||
|
||||
**Plugins**:
|
||||
|
||||
- `redirect`: HTTP → HTTPS
|
||||
- `limit-count`: IP-based rate limiting
|
||||
- `ai-rate-limiting`: Token-based LLM rate limiting
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **FastAPI**: https://fastapi.tiangolo.com/
|
||||
- **Pydantic**: https://docs.pydantic.dev/
|
||||
- **Uvicorn**: https://www.uvicorn.org/
|
||||
- **OpenAI API**: https://platform.openai.com/docs/api-reference
|
||||
|
||||
### Related
|
||||
|
||||
- [Main README](../README.md)
|
||||
- [Web Application README](../web/README.md)
|
||||
- [Helm Chart README](../helm/api7ee-demo-k8s/README.md)
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue**: LLM endpoints return errors
|
||||
|
||||
```bash
|
||||
# Check environment variables
|
||||
echo $OPENAI_API_BASE
|
||||
echo $OPENAI_API_KEY
|
||||
|
||||
# Test LLM backend directly
|
||||
curl -X POST "$OPENAI_API_BASE/chat/completions" \
|
||||
-H "Authorization: Bearer $OPENAI_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"model":"videogame-expert","messages":[{"role":"user","content":"test"}]}'
|
||||
```
|
||||
|
||||
**Issue**: Rate limiting triggered
|
||||
|
||||
```bash
|
||||
# Check API7 Gateway logs
|
||||
kubectl logs -n api7ee -l app=api7-gateway
|
||||
|
||||
# Response: HTTP 429
|
||||
# Cause: Exceeded 100 tokens/60s or 100 req/60s
|
||||
# Solution: Wait for rate limit window to reset
|
||||
```
|
||||
|
||||
**Issue**: Health check fails
|
||||
|
||||
```bash
|
||||
# Check if service is running
|
||||
curl http://localhost:8001/health
|
||||
|
||||
# Check logs
|
||||
docker logs api-service
|
||||
# or
|
||||
kubectl logs -n api7ee -l app=api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Version**: 1.0.0 | **Port**: 8001 | **Framework**: FastAPI 0.104.1
|
||||
|
||||
199
api/db.json
Normal file
199
api/db.json
Normal file
@@ -0,0 +1,199 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Gaming Laptop RTX 4090",
|
||||
"description": "High-performance gaming laptop with RTX 4090",
|
||||
"price": 2999.99,
|
||||
"in_stock": true,
|
||||
"category": "electronics",
|
||||
"tags": ["gaming", "laptop", "nvidia"]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Mechanical Keyboard RGB",
|
||||
"description": "Cherry MX switches with RGB backlighting",
|
||||
"price": 149.99,
|
||||
"in_stock": true,
|
||||
"category": "peripherals",
|
||||
"tags": ["keyboard", "mechanical", "rgb"]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Wireless Gaming Mouse",
|
||||
"description": "25K DPI wireless gaming mouse",
|
||||
"price": 89.99,
|
||||
"in_stock": false,
|
||||
"category": "peripherals",
|
||||
"tags": ["mouse", "wireless", "gaming"]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "4K Gaming Monitor 32\"",
|
||||
"description": "144Hz refresh rate, HDR support",
|
||||
"price": 599.99,
|
||||
"in_stock": true,
|
||||
"category": "displays",
|
||||
"tags": ["monitor", "4k", "gaming"]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Gaming Headset 7.1",
|
||||
"description": "Surround sound gaming headset with noise cancellation",
|
||||
"price": 129.99,
|
||||
"in_stock": true,
|
||||
"category": "audio",
|
||||
"tags": ["headset", "audio", "gaming"]
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
{
|
||||
"id": 1,
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"active": true,
|
||||
"role": "user",
|
||||
"created_at": "2024-01-15T10:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"username": "jane_smith",
|
||||
"email": "jane@example.com",
|
||||
"active": true,
|
||||
"role": "admin",
|
||||
"created_at": "2024-02-20T14:22:00Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"username": "bob_wilson",
|
||||
"email": "bob@example.com",
|
||||
"active": false,
|
||||
"role": "user",
|
||||
"created_at": "2024-03-10T09:15:00Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"username": "alice_johnson",
|
||||
"email": "alice@example.com",
|
||||
"active": true,
|
||||
"role": "moderator",
|
||||
"created_at": "2024-04-05T16:45:00Z"
|
||||
}
|
||||
],
|
||||
"orders": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"items": [
|
||||
{"item_id": 1, "quantity": 1, "price": 2999.99},
|
||||
{"item_id": 2, "quantity": 1, "price": 149.99}
|
||||
],
|
||||
"total": 3149.98,
|
||||
"status": "shipped",
|
||||
"created_at": "2024-09-15T12:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 2,
|
||||
"items": [
|
||||
{"item_id": 4, "quantity": 2, "price": 599.99}
|
||||
],
|
||||
"total": 1199.98,
|
||||
"status": "delivered",
|
||||
"created_at": "2024-09-20T15:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"user_id": 4,
|
||||
"items": [
|
||||
{"item_id": 5, "quantity": 1, "price": 129.99},
|
||||
{"item_id": 3, "quantity": 1, "price": 89.99}
|
||||
],
|
||||
"total": 219.98,
|
||||
"status": "pending",
|
||||
"created_at": "2024-10-01T10:15:00Z"
|
||||
}
|
||||
],
|
||||
"reviews": [
|
||||
{
|
||||
"id": 1,
|
||||
"item_id": 1,
|
||||
"user_id": 1,
|
||||
"rating": 5,
|
||||
"comment": "Amazing laptop! Best purchase ever!",
|
||||
"created_at": "2024-09-20T14:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"item_id": 2,
|
||||
"user_id": 2,
|
||||
"rating": 4,
|
||||
"comment": "Great keyboard, but a bit loud",
|
||||
"created_at": "2024-09-22T09:45:00Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"item_id": 4,
|
||||
"user_id": 2,
|
||||
"rating": 5,
|
||||
"comment": "Crystal clear display, perfect for gaming",
|
||||
"created_at": "2024-09-25T18:20:00Z"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"item_id": 5,
|
||||
"user_id": 4,
|
||||
"rating": 4,
|
||||
"comment": "Good sound quality, comfortable to wear",
|
||||
"created_at": "2024-10-02T11:00:00Z"
|
||||
}
|
||||
],
|
||||
"categories": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "electronics",
|
||||
"description": "Electronic devices and gadgets"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "peripherals",
|
||||
"description": "Computer peripherals and accessories"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "displays",
|
||||
"description": "Monitors and display devices"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "audio",
|
||||
"description": "Audio devices and accessories"
|
||||
}
|
||||
],
|
||||
"llm_requests": [
|
||||
{
|
||||
"id": 1,
|
||||
"user_id": 1,
|
||||
"model": "videogame-expert",
|
||||
"prompt": "What are the best RPG games of 2024?",
|
||||
"tokens_used": 250,
|
||||
"timestamp": "2024-10-05T10:00:00Z"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"user_id": 2,
|
||||
"model": "videogame-expert",
|
||||
"prompt": "Recommend me games similar to Dark Souls",
|
||||
"tokens_used": 180,
|
||||
"timestamp": "2024-10-05T11:30:00Z"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"user_id": 1,
|
||||
"model": "videogame-expert",
|
||||
"prompt": "What's the best strategy for Elden Ring bosses?",
|
||||
"tokens_used": 320,
|
||||
"timestamp": "2024-10-05T14:15:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
458
api/main.py
458
api/main.py
@@ -1,28 +1,107 @@
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
import uvicorn
|
||||
from datetime import datetime
|
||||
from fastapi import FastAPI, HTTPException, status
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List
|
||||
import os
|
||||
import httpx
|
||||
|
||||
# OpenAI API configuration
|
||||
OPENAI_API_BASE = os.getenv("OPENAI_API_BASE", "http://localhost/api")
|
||||
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "your-api-key")
|
||||
DEFAULT_MODEL = os.getenv("DEFAULT_LLM_MODEL", "your-model-id")
|
||||
|
||||
app = FastAPI(
|
||||
title="API Demo Application",
|
||||
description="Demo API with Swagger documentation",
|
||||
version="1.0.0"
|
||||
title="API7 Enterprise Demo API",
|
||||
description="""
|
||||
## API7 Enterprise Demo Application
|
||||
|
||||
This API demonstrates the capabilities of API7 Enterprise Gateway with:
|
||||
|
||||
* **CRUD Operations** - Items and Users management
|
||||
* **LLM Integration** - AI-powered chat with rate limiting
|
||||
* **Health Checks** - Kubernetes-ready endpoints
|
||||
* **Rate Limiting** - Standard (100 req/min) and AI-based (100 tokens/min)
|
||||
* **CORS** - Cross-origin resource sharing enabled
|
||||
* **Proxy Rewrite** - Automatic /api prefix removal by API7 Gateway
|
||||
|
||||
### Architecture
|
||||
|
||||
\`\`\`
|
||||
Client → Ingress (NGINX) → API7 Gateway → Backend API
|
||||
↓
|
||||
• Rate Limiting
|
||||
• CORS
|
||||
• Proxy Rewrite (/api → /)
|
||||
• Service Discovery
|
||||
\`\`\`
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
- **Standard API** (\`/items\`, \`/users\`): 100 requests per 60 seconds per IP
|
||||
- **LLM API** (\`/llm/*\`): 100 tokens per 60 seconds (AI-based rate limiting)
|
||||
|
||||
### Documentation
|
||||
|
||||
- **Swagger UI**: [/docs](/docs)
|
||||
- **ReDoc**: [/redoc](/redoc)
|
||||
- **OpenAPI JSON**: [/openapi.json](/openapi.json)
|
||||
""",
|
||||
version="1.0.0",
|
||||
contact={
|
||||
"name": "CommandWare",
|
||||
"url": "https://commandware.it",
|
||||
},
|
||||
license_info={
|
||||
"name": "MIT",
|
||||
},
|
||||
servers=[
|
||||
{
|
||||
"url": "https://commandware.it/api",
|
||||
"description": "Production server (via API7 Gateway)"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8080",
|
||||
"description": "Local development server"
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
# Models
|
||||
class Item(BaseModel):
|
||||
id: Optional[int] = None
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
price: float
|
||||
in_stock: bool = True
|
||||
id: Optional[int] = Field(None, description="Item ID (auto-generated)")
|
||||
name: str = Field(..., description="Item name", example="Laptop")
|
||||
description: Optional[str] = Field(None, description="Item description", example="High-performance laptop")
|
||||
price: float = Field(..., description="Item price", example=999.99, gt=0)
|
||||
in_stock: bool = Field(True, description="Stock availability")
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"name": "Laptop",
|
||||
"description": "High-performance laptop",
|
||||
"price": 999.99,
|
||||
"in_stock": True
|
||||
}
|
||||
}
|
||||
|
||||
class User(BaseModel):
|
||||
id: Optional[int] = None
|
||||
username: str
|
||||
email: str
|
||||
active: bool = True
|
||||
id: Optional[int] = Field(None, description="User ID (auto-generated)")
|
||||
username: str = Field(..., description="Username", example="john_doe", min_length=3)
|
||||
email: str = Field(..., description="Email address", example="john@example.com")
|
||||
active: bool = Field(True, description="User active status")
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"active": True
|
||||
}
|
||||
}
|
||||
|
||||
# In-memory storage
|
||||
items_db = [
|
||||
@@ -37,48 +116,150 @@ users_db = [
|
||||
]
|
||||
|
||||
# Root endpoint
|
||||
@app.get("/")
|
||||
@app.get(
|
||||
"/",
|
||||
tags=["Root"],
|
||||
summary="API Information",
|
||||
response_description="API metadata and links"
|
||||
)
|
||||
async def root():
|
||||
"""Root endpoint with API information"""
|
||||
"""
|
||||
**Root endpoint** with API information and navigation links.
|
||||
|
||||
Returns API version, documentation links, and current timestamp.
|
||||
"""
|
||||
return {
|
||||
"message": "Welcome to API Demo",
|
||||
"message": "Welcome to API7 Enterprise Demo API",
|
||||
"version": "1.0.0",
|
||||
"docs": "/docs",
|
||||
"documentation": {
|
||||
"swagger": "/docs",
|
||||
"redoc": "/redoc",
|
||||
"openapi": "/openapi.json"
|
||||
},
|
||||
"endpoints": {
|
||||
"items": "/items",
|
||||
"users": "/users",
|
||||
"llm": "/llm"
|
||||
},
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Health check
|
||||
@app.get("/health")
|
||||
@app.get(
|
||||
"/health",
|
||||
tags=["Health"],
|
||||
summary="Health Check",
|
||||
response_description="Service health status"
|
||||
)
|
||||
async def health():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "service": "api", "timestamp": datetime.now().isoformat()}
|
||||
"""
|
||||
**Health check endpoint** for Kubernetes liveness probe.
|
||||
|
||||
Returns service health status and timestamp.
|
||||
"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "api",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Readiness check
|
||||
@app.get(
|
||||
"/ready",
|
||||
tags=["Health"],
|
||||
summary="Readiness Check",
|
||||
response_description="Service readiness status"
|
||||
)
|
||||
async def ready():
|
||||
"""
|
||||
**Readiness check endpoint** for Kubernetes readiness probe.
|
||||
|
||||
Returns service readiness status and timestamp.
|
||||
"""
|
||||
return {
|
||||
"status": "ready",
|
||||
"service": "api",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
# Items endpoints
|
||||
@app.get("/items", response_model=List[Item], tags=["Items"])
|
||||
@app.get(
|
||||
"/items",
|
||||
response_model=List[Item],
|
||||
tags=["Items"],
|
||||
summary="List all items",
|
||||
response_description="List of all items in inventory"
|
||||
)
|
||||
async def get_items():
|
||||
"""Get all items"""
|
||||
"""
|
||||
**Get all items** from the inventory.
|
||||
|
||||
Returns a list of all available items with their details.
|
||||
|
||||
**Rate Limit**: 100 requests per 60 seconds per IP (via API7 Gateway)
|
||||
"""
|
||||
return items_db
|
||||
|
||||
@app.get("/items/{item_id}", response_model=Item, tags=["Items"])
|
||||
@app.get(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
tags=["Items"],
|
||||
summary="Get item by ID",
|
||||
response_description="Item details",
|
||||
responses={404: {"description": "Item not found"}}
|
||||
)
|
||||
async def get_item(item_id: int):
|
||||
"""Get a specific item by ID"""
|
||||
"""
|
||||
**Get a specific item** by ID.
|
||||
|
||||
- **item_id**: The ID of the item to retrieve
|
||||
|
||||
Returns item details if found, otherwise 404 error.
|
||||
"""
|
||||
item = next((item for item in items_db if item["id"] == item_id), None)
|
||||
if item is None:
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
return item
|
||||
|
||||
@app.post("/items", response_model=Item, tags=["Items"])
|
||||
@app.post(
|
||||
"/items",
|
||||
response_model=Item,
|
||||
tags=["Items"],
|
||||
summary="Create new item",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_description="Created item with auto-generated ID"
|
||||
)
|
||||
async def create_item(item: Item):
|
||||
"""Create a new item"""
|
||||
"""
|
||||
**Create a new item** in the inventory.
|
||||
|
||||
The ID will be auto-generated. Provide:
|
||||
- **name**: Item name (required)
|
||||
- **description**: Item description (optional)
|
||||
- **price**: Item price (required, must be > 0)
|
||||
- **in_stock**: Stock availability (default: true)
|
||||
"""
|
||||
new_id = max([i["id"] for i in items_db]) + 1 if items_db else 1
|
||||
item_dict = item.dict()
|
||||
item_dict["id"] = new_id
|
||||
items_db.append(item_dict)
|
||||
return item_dict
|
||||
|
||||
@app.put("/items/{item_id}", response_model=Item, tags=["Items"])
|
||||
@app.put(
|
||||
"/items/{item_id}",
|
||||
response_model=Item,
|
||||
tags=["Items"],
|
||||
summary="Update item",
|
||||
response_description="Updated item",
|
||||
responses={404: {"description": "Item not found"}}
|
||||
)
|
||||
async def update_item(item_id: int, item: Item):
|
||||
"""Update an existing item"""
|
||||
"""
|
||||
**Update an existing item** by ID.
|
||||
|
||||
- **item_id**: The ID of the item to update
|
||||
- Provide updated item data (name, description, price, in_stock)
|
||||
"""
|
||||
for idx, existing_item in enumerate(items_db):
|
||||
if existing_item["id"] == item_id:
|
||||
item_dict = item.dict()
|
||||
@@ -87,37 +268,236 @@ async def update_item(item_id: int, item: Item):
|
||||
return item_dict
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
|
||||
@app.delete("/items/{item_id}", tags=["Items"])
|
||||
@app.delete(
|
||||
"/items/{item_id}",
|
||||
tags=["Items"],
|
||||
summary="Delete item",
|
||||
status_code=status.HTTP_200_OK,
|
||||
response_description="Deletion confirmation",
|
||||
responses={404: {"description": "Item not found"}}
|
||||
)
|
||||
async def delete_item(item_id: int):
|
||||
"""Delete an item"""
|
||||
"""
|
||||
**Delete an item** from the inventory.
|
||||
|
||||
- **item_id**: The ID of the item to delete
|
||||
|
||||
Returns confirmation message if successful.
|
||||
"""
|
||||
for idx, item in enumerate(items_db):
|
||||
if item["id"] == item_id:
|
||||
items_db.pop(idx)
|
||||
return {"message": "Item deleted successfully"}
|
||||
return {"message": "Item deleted successfully", "id": item_id}
|
||||
raise HTTPException(status_code=404, detail="Item not found")
|
||||
|
||||
# Users endpoints
|
||||
@app.get("/users", response_model=List[User], tags=["Users"])
|
||||
@app.get(
|
||||
"/users",
|
||||
response_model=List[User],
|
||||
tags=["Users"],
|
||||
summary="List all users",
|
||||
response_description="List of all users"
|
||||
)
|
||||
async def get_users():
|
||||
"""Get all users"""
|
||||
"""
|
||||
**Get all users** from the system.
|
||||
|
||||
Returns a list of all registered users.
|
||||
|
||||
**Rate Limit**: 100 requests per 60 seconds per IP (via API7 Gateway)
|
||||
"""
|
||||
return users_db
|
||||
|
||||
@app.get("/users/{user_id}", response_model=User, tags=["Users"])
|
||||
@app.get(
|
||||
"/users/{user_id}",
|
||||
response_model=User,
|
||||
tags=["Users"],
|
||||
summary="Get user by ID",
|
||||
response_description="User details",
|
||||
responses={404: {"description": "User not found"}}
|
||||
)
|
||||
async def get_user(user_id: int):
|
||||
"""Get a specific user by ID"""
|
||||
"""
|
||||
**Get a specific user** by ID.
|
||||
|
||||
- **user_id**: The ID of the user to retrieve
|
||||
|
||||
Returns user details if found, otherwise 404 error.
|
||||
"""
|
||||
user = next((user for user in users_db if user["id"] == user_id), None)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
return user
|
||||
|
||||
@app.post("/users", response_model=User, tags=["Users"])
|
||||
@app.post(
|
||||
"/users",
|
||||
response_model=User,
|
||||
tags=["Users"],
|
||||
summary="Create new user",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_description="Created user with auto-generated ID"
|
||||
)
|
||||
async def create_user(user: User):
|
||||
"""Create a new user"""
|
||||
"""
|
||||
**Create a new user** in the system.
|
||||
|
||||
The ID will be auto-generated. Provide:
|
||||
- **username**: Username (required, min 3 characters)
|
||||
- **email**: Email address (required)
|
||||
- **active**: User active status (default: true)
|
||||
"""
|
||||
new_id = max([u["id"] for u in users_db]) + 1 if users_db else 1
|
||||
user_dict = user.dict()
|
||||
user_dict["id"] = new_id
|
||||
users_db.append(user_dict)
|
||||
return user_dict
|
||||
|
||||
# LLM endpoints
|
||||
class LLMRequest(BaseModel):
|
||||
prompt: str = Field(..., description="The prompt to send to the LLM", example="What is API7 Enterprise?")
|
||||
max_tokens: Optional[int] = Field(150, description="Maximum tokens in response", example=150, ge=1, le=4096)
|
||||
temperature: Optional[float] = Field(0.7, description="Sampling temperature (0-2)", example=0.7, ge=0, le=2)
|
||||
model: Optional[str] = Field(DEFAULT_MODEL, description="Model to use", example="videogame-expert")
|
||||
|
||||
class Config:
|
||||
schema_extra = {
|
||||
"example": {
|
||||
"prompt": "What is API7 Enterprise?",
|
||||
"max_tokens": 150,
|
||||
"temperature": 0.7,
|
||||
"model": "videogame-expert"
|
||||
}
|
||||
}
|
||||
|
||||
class LLMResponse(BaseModel):
|
||||
response: str = Field(..., description="LLM generated response")
|
||||
tokens_used: int = Field(..., description="Total tokens used")
|
||||
model: str = Field(..., description="Model used for generation")
|
||||
timestamp: str = Field(..., description="Response timestamp")
|
||||
|
||||
@app.post(
|
||||
"/llm/chat",
|
||||
response_model=LLMResponse,
|
||||
tags=["LLM"],
|
||||
summary="LLM Chat Completion",
|
||||
response_description="LLM generated response",
|
||||
responses={
|
||||
429: {"description": "Rate limit exceeded (100 tokens per 60 seconds)"},
|
||||
500: {"description": "LLM service error"}
|
||||
}
|
||||
)
|
||||
async def llm_chat(request: LLMRequest):
|
||||
"""
|
||||
**LLM Chat endpoint** - connects to OpenAI-compatible API (Open WebUI).
|
||||
|
||||
This endpoint uses **AI-based rate limiting** via API7 Gateway:
|
||||
- **Limit**: 100 tokens per 60 seconds
|
||||
- **Strategy**: total_tokens (input + output)
|
||||
- **Error**: HTTP 429 when limit exceeded
|
||||
|
||||
Provide:
|
||||
- **prompt**: Your question or prompt
|
||||
- **max_tokens**: Maximum response length (default: 150)
|
||||
- **temperature**: Randomness level 0-2 (default: 0.7)
|
||||
- **model**: Model to use (default: configured model)
|
||||
"""
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.post(
|
||||
f"{OPENAI_API_BASE}/chat/completions",
|
||||
headers={
|
||||
"Authorization": f"Bearer {OPENAI_API_KEY}",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
json={
|
||||
"model": request.model,
|
||||
"messages": [
|
||||
{"role": "user", "content": request.prompt}
|
||||
],
|
||||
"max_tokens": request.max_tokens,
|
||||
"temperature": request.temperature
|
||||
},
|
||||
timeout=30.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
# Extract response and token usage
|
||||
llm_response = data["choices"][0]["message"]["content"]
|
||||
tokens_used = data.get("usage", {}).get("total_tokens", 0)
|
||||
|
||||
return LLMResponse(
|
||||
response=llm_response,
|
||||
tokens_used=tokens_used,
|
||||
model=request.model,
|
||||
timestamp=datetime.now().isoformat()
|
||||
)
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise HTTPException(status_code=e.response.status_code, detail=f"OpenAI API error: {e.response.text}")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"LLM service error: {str(e)}")
|
||||
|
||||
@app.get(
|
||||
"/llm/models",
|
||||
tags=["LLM"],
|
||||
summary="List available LLM models",
|
||||
response_description="List of available models"
|
||||
)
|
||||
async def list_llm_models():
|
||||
"""
|
||||
**List available LLM models**.
|
||||
|
||||
Returns the list of models available through the configured LLM provider (Open WebUI).
|
||||
"""
|
||||
return {
|
||||
"models": [
|
||||
{
|
||||
"id": "videogame-expert",
|
||||
"name": "Videogame Expert",
|
||||
"max_tokens": 4096,
|
||||
"provider": "Open WebUI",
|
||||
"description": "Specialized model for videogame-related questions"
|
||||
}
|
||||
],
|
||||
"default_model": DEFAULT_MODEL,
|
||||
"provider": "Open WebUI",
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
@app.get(
|
||||
"/llm/health",
|
||||
tags=["LLM"],
|
||||
summary="LLM service health check",
|
||||
response_description="LLM service health status"
|
||||
)
|
||||
async def llm_health():
|
||||
"""
|
||||
**LLM service health check**.
|
||||
|
||||
Returns the health status of the LLM integration, including:
|
||||
- Provider information
|
||||
- Endpoint configuration
|
||||
- Rate limit settings
|
||||
"""
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "llm-api",
|
||||
"provider": "Open WebUI",
|
||||
"endpoint": OPENAI_API_BASE,
|
||||
"default_model": DEFAULT_MODEL,
|
||||
"rate_limit": {
|
||||
"enabled": True,
|
||||
"limit": 100,
|
||||
"window": "60 seconds",
|
||||
"strategy": "total_tokens",
|
||||
"managed_by": "API7 Gateway (ai-rate-limiting plugin)"
|
||||
},
|
||||
"timestamp": datetime.now().isoformat()
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(app, host="0.0.0.0", port=8001)
|
||||
port = int(os.getenv("PORT", 8080))
|
||||
print(f"Starting API server on port {port}")
|
||||
print(f"Swagger UI: http://localhost:{port}/docs")
|
||||
print(f"ReDoc: http://localhost:{port}/redoc")
|
||||
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
fastapi==0.109.0
|
||||
uvicorn==0.27.0
|
||||
pydantic==2.5.0
|
||||
httpx==0.26.0
|
||||
|
||||
55
api7-credentials.yaml.template
Normal file
55
api7-credentials.yaml.template
Normal file
@@ -0,0 +1,55 @@
|
||||
# API7 Gateway Credentials Secret Template
|
||||
#
|
||||
# This is a template for creating the API7 Gateway admin credentials secret.
|
||||
# DO NOT commit this file with actual credentials filled in!
|
||||
#
|
||||
# Usage:
|
||||
# 1. Copy this template: cp api7-credentials.yaml.template api7-credentials.yaml
|
||||
# 2. Fill in the actual values (see instructions below)
|
||||
# 3. Apply to cluster: kubectl apply -f api7-credentials.yaml -n api7ee
|
||||
# 4. Update values.yaml to reference this secret:
|
||||
# api7.gateway.existingSecret: "api7-credentials"
|
||||
#
|
||||
# IMPORTANT: Add api7-credentials.yaml to .gitignore!
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: api7-credentials
|
||||
namespace: api7ee
|
||||
labels:
|
||||
app.kubernetes.io/name: api7ee-demo
|
||||
app.kubernetes.io/component: api7-gateway
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Dashboard Admin API URL
|
||||
# This is the HTTPS endpoint of the API7 Enterprise Dashboard
|
||||
# Default: https://api7ee3-0-1759339083-dashboard:7443
|
||||
admin-url: "https://api7ee3-0-1759339083-dashboard:7443"
|
||||
|
||||
# Admin API Key
|
||||
# Get the actual key from the API7 Enterprise installation:
|
||||
# kubectl get secret -n api7ee api7ee3-0-1759339083 -o jsonpath='{.data.admin_key}' | base64 -d
|
||||
#
|
||||
# REPLACE WITH YOUR ACTUAL KEY:
|
||||
admin-key: "YOUR_ADMIN_KEY_HERE"
|
||||
|
||||
# Gateway Group
|
||||
# Logical grouping of gateway instances
|
||||
# Default: default
|
||||
gateway-group: "default"
|
||||
|
||||
---
|
||||
# Example using base64 encoded values (alternative to stringData)
|
||||
# apiVersion: v1
|
||||
# kind: Secret
|
||||
# metadata:
|
||||
# name: api7-credentials
|
||||
# namespace: api7ee
|
||||
# type: Opaque
|
||||
# data:
|
||||
# # Base64 encoded values
|
||||
# admin-url: aHR0cHM6Ly9hcGk3ZWUzLTAtMTc1OTMzOTA4My1kYXNoYm9hcmQ6NzQ0Mw==
|
||||
# admin-key: WU9VUl9BRE1JTl9LRVlfSEVSRQ==
|
||||
# gateway-group: ZGVmYXVsdA==
|
||||
23
helm/api7ee-demo-k8s/.helmignore
Normal file
23
helm/api7ee-demo-k8s/.helmignore
Normal file
@@ -0,0 +1,23 @@
|
||||
# Patterns to ignore when building packages.
|
||||
# This supports shell glob matching, relative path matching, and
|
||||
# negation (prefixed with !). Only one pattern per line.
|
||||
.DS_Store
|
||||
# Common VCS dirs
|
||||
.git/
|
||||
.gitignore
|
||||
.bzr/
|
||||
.bzrignore
|
||||
.hg/
|
||||
.hgignore
|
||||
.svn/
|
||||
# Common backup files
|
||||
*.swp
|
||||
*.bak
|
||||
*.tmp
|
||||
*.orig
|
||||
*~
|
||||
# Various IDEs
|
||||
.project
|
||||
.idea/
|
||||
*.tmproj
|
||||
.vscode/
|
||||
17
helm/api7ee-demo-k8s/Chart.yaml
Normal file
17
helm/api7ee-demo-k8s/Chart.yaml
Normal file
@@ -0,0 +1,17 @@
|
||||
apiVersion: v2
|
||||
name: api7ee-demo-k8s
|
||||
description: A Helm chart for API7 Enterprise Edition demo application
|
||||
type: application
|
||||
version: 0.1.0
|
||||
appVersion: "1.0.0"
|
||||
keywords:
|
||||
- api7
|
||||
- api-gateway
|
||||
- web
|
||||
- api
|
||||
home: https://demo.commandware.it
|
||||
sources:
|
||||
- https://git.commandware.com/demos/api7-demo
|
||||
maintainers:
|
||||
- name: CommandWare
|
||||
email: support@commandware.com
|
||||
864
helm/api7ee-demo-k8s/README.md
Normal file
864
helm/api7ee-demo-k8s/README.md
Normal file
@@ -0,0 +1,864 @@
|
||||
# API7 Enterprise Edition Demo - Helm Chart
|
||||
|
||||
A comprehensive Helm chart for deploying the API7 Enterprise Edition demo platform on Kubernetes. This chart deploys both web frontend and API backend services with full API7 Gateway integration, including automatic ADC configuration, TLS management, and advanced rate limiting.
|
||||
|
||||
## 📋 Overview
|
||||
|
||||
This Helm chart provides:
|
||||
|
||||
- **Dual Service Deployment**: Web frontend + API backend
|
||||
- **API7 Gateway Configuration**: Automatic ADC (API7 Declarative CLI) sync to existing gateway
|
||||
- ⚠️ **Does NOT install API7 Gateway** - gateway must already exist
|
||||
- Only configures routes, services, and upstreams on existing gateway
|
||||
- **TLS/SSL Management**: cert-manager integration or custom certificates
|
||||
- **Service Discovery**: Kubernetes-native service discovery
|
||||
- **Rate Limiting**: Standard and AI token-based rate limiting
|
||||
- **Autoscaling**: Horizontal Pod Autoscaler (HPA) support
|
||||
- **High Availability**: Pod Disruption Budgets and multi-replica deployment
|
||||
- **Security**: Pod Security Contexts, RBAC, and Network Policies
|
||||
|
||||
## 🎯 Features
|
||||
|
||||
### Deployments
|
||||
|
||||
- **Web Service** (apache-service): FastAPI frontend with embedded documentation
|
||||
- **API Service** (nginx-service): FastAPI backend with REST API and LLM endpoints
|
||||
|
||||
### API7 Gateway Configuration
|
||||
|
||||
- **Automatic ADC Sync**: Configures routes, services, and upstreams
|
||||
- **Advanced Rate Limiting**:
|
||||
- Standard: 100 req/60s per IP for `/api/*`
|
||||
- AI: 100 tokens/60s for `/api/llm/*`
|
||||
- **Route Prioritization**: LLM (20) > API (10) > Web (1)
|
||||
- **TLS/SSL**: Automatic HTTPS redirect and certificate management
|
||||
- **Plugins**: CORS, Prometheus metrics, request logging, authentication
|
||||
|
||||
### Kubernetes Resources
|
||||
|
||||
- Deployments (Web and API)
|
||||
- Services (ClusterIP)
|
||||
- Ingress (NGINX)
|
||||
- ConfigMaps (Application config and ADC config)
|
||||
- Secrets (TLS certificates, API7 credentials)
|
||||
- ServiceAccount and RBAC
|
||||
- HorizontalPodAutoscaler
|
||||
- PodDisruptionBudget
|
||||
- Certificate (cert-manager)
|
||||
- Job (ADC sync)
|
||||
|
||||
## 📦 Prerequisites
|
||||
|
||||
### Required
|
||||
|
||||
- **Kubernetes**: v1.19 or higher
|
||||
- **Helm**: 3.8.0 or higher
|
||||
- **API7 Enterprise Edition Gateway**: **MUST be already installed and running**
|
||||
- This chart does NOT install API7 Gateway
|
||||
- This chart only configures routes and services on an existing gateway
|
||||
- You need the DP Manager admin URL and admin key
|
||||
- Example: `http://api7ee3-0-xxx-dp-manager.api7ee.svc.cluster.local:7900`
|
||||
|
||||
### Optional
|
||||
|
||||
- **Ingress Controller**: NGINX Ingress Controller (for direct Kubernetes ingress)
|
||||
- **cert-manager**: v1.0+ (for automatic TLS certificate management)
|
||||
|
||||
### Before Installation
|
||||
|
||||
1. **Verify API7 Gateway is Running**:
|
||||
|
||||
```bash
|
||||
kubectl get pods -n api7ee | grep gateway
|
||||
kubectl get pods -n api7ee | grep dp-manager
|
||||
```
|
||||
|
||||
2. **Get API7 Admin Credentials**:
|
||||
|
||||
```bash
|
||||
# Get DP Manager URL
|
||||
kubectl get svc -n api7ee | grep dp-manager
|
||||
|
||||
# Get admin key from API7 Dashboard or documentation
|
||||
# Default is usually generated during API7 installation
|
||||
```
|
||||
|
||||
3. **Test Gateway Connectivity**:
|
||||
```bash
|
||||
# Test from within cluster
|
||||
kubectl run test-curl --rm -i --restart=Never --image=curlimages/curl:latest \
|
||||
-- curl -s http://api7ee3-0-xxx-dp-manager.api7ee.svc.cluster.local:7900/version
|
||||
```
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### Add Helm Repository
|
||||
|
||||
```bash
|
||||
# Add the Gitea Helm repository
|
||||
helm repo add api7ee https://git.commandware.com/api/packages/demos/helm
|
||||
|
||||
# Update repositories
|
||||
helm repo update
|
||||
|
||||
# Search for available versions
|
||||
helm search repo api7ee/api7ee-demo-k8s
|
||||
```
|
||||
|
||||
### Basic Installation
|
||||
|
||||
```bash
|
||||
# Install with default values
|
||||
helm install api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--create-namespace
|
||||
|
||||
# Check installation
|
||||
helm list -n api7ee
|
||||
kubectl get pods -n api7ee
|
||||
```
|
||||
|
||||
### Installation with Custom Values
|
||||
|
||||
```bash
|
||||
# Install with custom values file
|
||||
helm install api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--create-namespace \
|
||||
-f custom-values.yaml
|
||||
|
||||
# Install with command-line overrides
|
||||
helm install api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--set web.replicaCount=3 \
|
||||
--set api.replicaCount=5 \
|
||||
--set api7.gateway.adminKey="your-admin-key" \
|
||||
--set api7.hosts[0]="your-domain.com"
|
||||
```
|
||||
|
||||
### Install from Local Chart
|
||||
|
||||
```bash
|
||||
# Install from local directory
|
||||
helm install api7ee-demo ./helm/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--create-namespace
|
||||
|
||||
# With development values
|
||||
helm install api7ee-dev ./helm/api7ee-demo-k8s \
|
||||
-f ./helm/api7ee-demo-k8s/values-dev.yaml \
|
||||
--namespace api7ee-dev
|
||||
```
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Global Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ------------------------- | ---------------------- | ------- |
|
||||
| `global.imageRegistry` | Global Docker registry | `""` |
|
||||
| `global.imagePullSecrets` | Image pull secrets | `[]` |
|
||||
|
||||
### Web Service Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ------------------------------------------------ | --------------------- | ---------------------- |
|
||||
| `web.enabled` | Enable Web component | `true` |
|
||||
| `web.replicaCount` | Number of replicas | `2` |
|
||||
| `web.image.registry` | Image registry | `gitea.server_url` |
|
||||
| `web.image.repository` | Image repository | `gitea.repository/web` |
|
||||
| `web.image.tag` | Image tag | `main` |
|
||||
| `web.image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||
| `web.service.type` | Service type | `ClusterIP` |
|
||||
| `web.service.port` | Service port | `8000` |
|
||||
| `web.resources.limits.cpu` | CPU limit | `500m` |
|
||||
| `web.resources.limits.memory` | Memory limit | `512Mi` |
|
||||
| `web.resources.requests.cpu` | CPU request | `250m` |
|
||||
| `web.resources.requests.memory` | Memory request | `256Mi` |
|
||||
| `web.autoscaling.enabled` | Enable HPA | `false` |
|
||||
| `web.autoscaling.minReplicas` | Min replicas | `2` |
|
||||
| `web.autoscaling.maxReplicas` | Max replicas | `10` |
|
||||
| `web.autoscaling.targetCPUUtilizationPercentage` | CPU target | `80` |
|
||||
| `web.env` | Environment variables | `[]` |
|
||||
|
||||
### API Service Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ------------------------------------------------ | --------------------- | ---------------------------------- |
|
||||
| `api.enabled` | Enable API component | `true` |
|
||||
| `api.replicaCount` | Number of replicas | `3` |
|
||||
| `api.image.registry` | Image registry | `gitea.server_url` |
|
||||
| `api.image.repository` | Image repository | `gitea.repository/api` |
|
||||
| `api.image.tag` | Image tag | `main` |
|
||||
| `api.image.pullPolicy` | Image pull policy | `IfNotPresent` |
|
||||
| `api.service.type` | Service type | `ClusterIP` |
|
||||
| `api.service.port` | Service port | `8080` |
|
||||
| `api.resources.limits.cpu` | CPU limit | `1000m` |
|
||||
| `api.resources.limits.memory` | Memory limit | `1Gi` |
|
||||
| `api.resources.requests.cpu` | CPU request | `500m` |
|
||||
| `api.resources.requests.memory` | Memory request | `512Mi` |
|
||||
| `api.autoscaling.enabled` | Enable HPA | `true` |
|
||||
| `api.autoscaling.minReplicas` | Min replicas | `3` |
|
||||
| `api.autoscaling.maxReplicas` | Max replicas | `20` |
|
||||
| `api.autoscaling.targetCPUUtilizationPercentage` | CPU target | `70` |
|
||||
| `api.env` | Environment variables | `[{name: LOG_LEVEL, value: info}]` |
|
||||
|
||||
### Ingress Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --------------------------- | ------------------- | ---------------------------- |
|
||||
| `ingress.enabled` | Enable ingress | `true` |
|
||||
| `ingress.className` | Ingress class | `nginx` |
|
||||
| `ingress.annotations` | Ingress annotations | See values.yaml |
|
||||
| `ingress.hosts[0].host` | Hostname | `commandware.it` |
|
||||
| `ingress.tls[0].secretName` | TLS secret | `api7ee-tls` |
|
||||
| `ingress.tls[0].hosts` | TLS hosts | `[commandware.it]` |
|
||||
|
||||
### API7 Gateway Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ----------------------------- | ---------------------- | ------------------------------------ |
|
||||
| `api7.enabled` | Enable API7 ADC config | `true` |
|
||||
| `api7.adc.image` | ADC Docker image | `ghcr.io/api7/adc:latest` |
|
||||
| `api7.adc.verbose` | Verbose logging | `true` |
|
||||
| `api7.adc.tlsSkipVerify` | Skip TLS verify | `false` |
|
||||
| `api7.gateway.adminUrl` | API7 Admin API URL | `http://api7ee3-0-xxx-dp-manager...` |
|
||||
| `api7.gateway.adminKey` | API7 Admin API key | `edd1c9f034335f136f87ad84b625c8f1` |
|
||||
| `api7.gateway.group` | Gateway group | `default` |
|
||||
| `api7.gateway.gatewayService` | Gateway service name | `gateway-0-xxx-gateway` |
|
||||
| `api7.backend` | Backend type | `api7ee` |
|
||||
| `api7.autoPublish` | Auto-publish routes | `true` |
|
||||
| `api7.hosts` | Routing hosts | `[commandware.it]` |
|
||||
|
||||
### TLS Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --------------------------------- | ------------------- | ---------------------- |
|
||||
| `api7.tls.enabled` | Enable TLS | `true` |
|
||||
| `api7.tls.certManager.enabled` | Use cert-manager | `true` |
|
||||
| `api7.tls.certManager.issuer` | ClusterIssuer name | `cloudflare-acme-prod` |
|
||||
| `api7.tls.certManager.issuerKind` | Issuer kind | `ClusterIssuer` |
|
||||
| `api7.tls.secretName` | Existing TLS secret | `""` |
|
||||
| `api7.tls.certificate` | Direct certificate | `""` |
|
||||
| `api7.tls.key` | Direct key | `""` |
|
||||
|
||||
### Rate Limiting Configuration
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| ---------------------------------------- | ----------------------------- | -------------- |
|
||||
| `api7.plugins.rateLimit.enabled` | Enable standard rate limiting | `true` |
|
||||
| `api7.plugins.rateLimit.count` | Request limit | `100` |
|
||||
| `api7.plugins.rateLimit.timeWindow` | Time window (seconds) | `60` |
|
||||
| `api7.plugins.rateLimit.keyType` | Key type | `var` |
|
||||
| `api7.plugins.rateLimit.key` | Key variable | `remote_addr` |
|
||||
| `api7.plugins.aiRateLimit.enabled` | Enable AI rate limiting | `true` |
|
||||
| `api7.plugins.aiRateLimit.limit` | Token limit | `100` |
|
||||
| `api7.plugins.aiRateLimit.timeWindow` | Time window (seconds) | `60` |
|
||||
| `api7.plugins.aiRateLimit.limitStrategy` | Limit strategy | `total_tokens` |
|
||||
|
||||
### Additional Plugins
|
||||
|
||||
| Parameter | Description | Default |
|
||||
| --------------------------------- | ------------------------- | ------- |
|
||||
| `api7.plugins.cors.enabled` | Enable CORS | `true` |
|
||||
| `api7.plugins.cors.allowOrigins` | Allowed origins | `["*"]` |
|
||||
| `api7.plugins.auth.enabled` | Enable authentication | `false` |
|
||||
| `api7.plugins.prometheus.enabled` | Enable Prometheus metrics | `true` |
|
||||
| `api7.plugins.logging.enabled` | Enable request logging | `false` |
|
||||
|
||||
## 📝 Configuration Examples
|
||||
|
||||
### Production Deployment
|
||||
|
||||
```yaml
|
||||
# production-values.yaml
|
||||
global:
|
||||
imageRegistry: "git.commandware.com"
|
||||
imagePullSecrets:
|
||||
- name: registry-secret
|
||||
|
||||
web:
|
||||
replicaCount: 3
|
||||
image:
|
||||
tag: "v1.0.0"
|
||||
pullPolicy: Always
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 15
|
||||
|
||||
api:
|
||||
replicaCount: 5
|
||||
image:
|
||||
tag: "v1.0.0"
|
||||
pullPolicy: Always
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 5
|
||||
maxReplicas: 30
|
||||
|
||||
api7:
|
||||
gateway:
|
||||
adminKey: "${API7_ADMIN_KEY}" # Use secret
|
||||
hosts:
|
||||
- api7-demo.yourdomain.com
|
||||
plugins:
|
||||
rateLimit:
|
||||
count: 1000
|
||||
auth:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
```bash
|
||||
helm install api7ee-prod api7ee/api7ee-demo-k8s \
|
||||
-f production-values.yaml \
|
||||
--namespace api7ee-prod \
|
||||
--create-namespace
|
||||
```
|
||||
|
||||
### Custom Domain and TLS
|
||||
|
||||
```yaml
|
||||
# custom-domain-values.yaml
|
||||
api7:
|
||||
hosts:
|
||||
- api.example.com
|
||||
- demo.example.com
|
||||
tls:
|
||||
certManager:
|
||||
enabled: true
|
||||
issuer: letsencrypt-prod
|
||||
|
||||
ingress:
|
||||
hosts:
|
||||
- host: api.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
service: web
|
||||
tls:
|
||||
- secretName: example-tls
|
||||
hosts:
|
||||
- api.example.com
|
||||
```
|
||||
|
||||
### High Resource Environment
|
||||
|
||||
```yaml
|
||||
# high-resources-values.yaml
|
||||
web:
|
||||
replicaCount: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
maxReplicas: 20
|
||||
|
||||
api:
|
||||
replicaCount: 10
|
||||
resources:
|
||||
limits:
|
||||
cpu: 4000m
|
||||
memory: 4Gi
|
||||
requests:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
autoscaling:
|
||||
enabled: true
|
||||
maxReplicas: 50
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 3
|
||||
```
|
||||
|
||||
### Custom API7 Gateway
|
||||
|
||||
```yaml
|
||||
# custom-gateway-values.yaml
|
||||
api7:
|
||||
gateway:
|
||||
adminUrl: http://my-api7-gateway:9180
|
||||
adminKey: "my-custom-key"
|
||||
group: production
|
||||
gatewayService: my-gateway-service
|
||||
backend: apisix # or api7ee
|
||||
autoPublish: false # Manual publish
|
||||
```
|
||||
|
||||
### LLM Configuration
|
||||
|
||||
```yaml
|
||||
# llm-values.yaml
|
||||
api:
|
||||
env:
|
||||
- name: OPENAI_API_BASE
|
||||
value: "http://openwebui.ai:8080/api"
|
||||
- name: OPENAI_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: llm-secrets
|
||||
key: api-key
|
||||
- name: DEFAULT_LLM_MODEL
|
||||
value: "videogame-expert"
|
||||
|
||||
api7:
|
||||
plugins:
|
||||
aiRateLimit:
|
||||
enabled: true
|
||||
limit: 200 # Higher limit
|
||||
timeWindow: 60
|
||||
```
|
||||
|
||||
### Development Environment
|
||||
|
||||
Use the included `values-dev.yaml`:
|
||||
|
||||
```bash
|
||||
helm install api7ee-dev ./helm/api7ee-demo-k8s \
|
||||
-f ./helm/api7ee-demo-k8s/values-dev.yaml \
|
||||
--namespace api7ee-dev
|
||||
```
|
||||
|
||||
## 🔄 Upgrade and Rollback
|
||||
|
||||
### Upgrade Release
|
||||
|
||||
```bash
|
||||
# Upgrade with new values
|
||||
helm upgrade api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
-f new-values.yaml
|
||||
|
||||
# Upgrade with inline values
|
||||
helm upgrade api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--reuse-values \
|
||||
--set api.replicaCount=5
|
||||
|
||||
# Force upgrade
|
||||
helm upgrade api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--force
|
||||
```
|
||||
|
||||
### Rollback
|
||||
|
||||
```bash
|
||||
# View release history
|
||||
helm history api7ee-demo -n api7ee
|
||||
|
||||
# Rollback to previous release
|
||||
helm rollback api7ee-demo -n api7ee
|
||||
|
||||
# Rollback to specific revision
|
||||
helm rollback api7ee-demo 3 -n api7ee
|
||||
```
|
||||
|
||||
### Verify Upgrade
|
||||
|
||||
```bash
|
||||
# Check release status
|
||||
helm status api7ee-demo -n api7ee
|
||||
|
||||
# View current values
|
||||
helm get values api7ee-demo -n api7ee
|
||||
|
||||
# Check all resources
|
||||
kubectl get all -n api7ee -l app.kubernetes.io/instance=api7ee-demo
|
||||
```
|
||||
|
||||
## 🗑️ Uninstallation
|
||||
|
||||
```bash
|
||||
# Uninstall release
|
||||
helm uninstall api7ee-demo --namespace api7ee
|
||||
|
||||
# Keep release history (for rollback)
|
||||
helm uninstall api7ee-demo --namespace api7ee --keep-history
|
||||
|
||||
# Delete namespace
|
||||
kubectl delete namespace api7ee
|
||||
```
|
||||
|
||||
## 🔍 Verification and Testing
|
||||
|
||||
### Check Deployment Status
|
||||
|
||||
```bash
|
||||
# Helm release status
|
||||
helm list -n api7ee
|
||||
helm status api7ee-demo -n api7ee
|
||||
|
||||
# Kubernetes resources
|
||||
kubectl get all -n api7ee
|
||||
kubectl get pods -n api7ee -w
|
||||
|
||||
# Check specific resources
|
||||
kubectl get deployments -n api7ee
|
||||
kubectl get services -n api7ee
|
||||
kubectl get ingress -n api7ee
|
||||
kubectl get hpa -n api7ee
|
||||
```
|
||||
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# Web service logs
|
||||
kubectl logs -n api7ee -l app=apache-service --tail=50
|
||||
|
||||
# API service logs
|
||||
kubectl logs -n api7ee -l app=nginx-service --tail=50
|
||||
|
||||
# ADC sync job logs
|
||||
kubectl logs -n api7ee -l app.kubernetes.io/component=adc-sync
|
||||
|
||||
# All logs
|
||||
kubectl logs -n api7ee -l app.kubernetes.io/instance=api7ee-demo --all-containers
|
||||
```
|
||||
|
||||
### Test Endpoints
|
||||
|
||||
```bash
|
||||
# Get ingress URL
|
||||
INGRESS_HOST=$(kubectl get ingress -n api7ee api7ee-demo-ingress -o jsonpath='{.spec.rules[0].host}')
|
||||
|
||||
# Test web service
|
||||
curl https://$INGRESS_HOST/
|
||||
|
||||
# Test API
|
||||
curl https://$INGRESS_HOST/api/items
|
||||
|
||||
# Test health checks
|
||||
curl https://$INGRESS_HOST/health
|
||||
curl https://$INGRESS_HOST/api/health
|
||||
|
||||
# Test LLM endpoint
|
||||
curl -X POST https://$INGRESS_HOST/api/llm/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"prompt": "What is Zelda?",
|
||||
"max_tokens": 100
|
||||
}'
|
||||
```
|
||||
|
||||
## 🔧 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### 1. ADC Sync Job Fails
|
||||
|
||||
**Check job logs**:
|
||||
|
||||
```bash
|
||||
kubectl logs -n api7ee -l app.kubernetes.io/component=adc-sync
|
||||
|
||||
# Common errors:
|
||||
# - "connection refused" → Check adminUrl
|
||||
# - "unauthorized" → Verify adminKey
|
||||
# - "route not found" → Check backend type (api7ee vs apisix)
|
||||
```
|
||||
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
# Update API7 credentials
|
||||
helm upgrade api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--reuse-values \
|
||||
--set api7.gateway.adminKey="correct-admin-key"
|
||||
```
|
||||
|
||||
#### 2. Pods Not Starting
|
||||
|
||||
**Check pod status**:
|
||||
|
||||
```bash
|
||||
kubectl describe pod -n api7ee <pod-name>
|
||||
|
||||
# Common issues:
|
||||
# - ImagePullBackOff → Check image registry credentials
|
||||
# - CrashLoopBackOff → Check application logs
|
||||
# - Pending → Check resources and node capacity
|
||||
```
|
||||
|
||||
**Solution for ImagePullBackOff**:
|
||||
|
||||
```bash
|
||||
# Create registry secret
|
||||
kubectl create secret docker-registry gitea-registry \
|
||||
--docker-server=git.commandware.com \
|
||||
--docker-username=<USERNAME> \
|
||||
--docker-password=<TOKEN> \
|
||||
-n api7ee
|
||||
|
||||
# Update values
|
||||
helm upgrade api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--reuse-values \
|
||||
--set global.imagePullSecrets[0].name=gitea-registry
|
||||
```
|
||||
|
||||
#### 3. Routes Return 404
|
||||
|
||||
**Cause**: Routes not published to gateway group
|
||||
|
||||
**Check API7 Dashboard**:
|
||||
|
||||
```
|
||||
Services → Select service → Routes → Check publication status
|
||||
```
|
||||
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
# Enable auto-publish
|
||||
helm upgrade api7ee-demo api7ee/api7ee-demo-k8s \
|
||||
--namespace api7ee \
|
||||
--reuse-values \
|
||||
--set api7.autoPublish=true
|
||||
|
||||
# Or publish manually via Dashboard:
|
||||
# Click "Publish" → Select "default" gateway group
|
||||
```
|
||||
|
||||
#### 4. TLS Certificate Issues
|
||||
|
||||
**Check certificate status**:
|
||||
|
||||
```bash
|
||||
kubectl get certificate -n api7ee
|
||||
kubectl describe certificate -n api7ee api7ee-tls
|
||||
|
||||
# Check cert-manager logs
|
||||
kubectl logs -n cert-manager -l app=cert-manager --tail=50
|
||||
```
|
||||
|
||||
**Verify cert-manager configuration**:
|
||||
|
||||
```bash
|
||||
# Check ClusterIssuer
|
||||
kubectl get clusterissuer cloudflare-acme-prod
|
||||
|
||||
# Check challenge
|
||||
kubectl get challenge -n api7ee
|
||||
```
|
||||
|
||||
#### 5. Service Discovery Not Working
|
||||
|
||||
**Check services have named ports**:
|
||||
|
||||
```bash
|
||||
kubectl get svc -n api7ee apache-service -o yaml | grep -A5 ports
|
||||
|
||||
# Ports must have 'name' field:
|
||||
# ports:
|
||||
# - port: 80
|
||||
# name: http # ← Required
|
||||
```
|
||||
|
||||
**Check endpoints**:
|
||||
|
||||
```bash
|
||||
kubectl get endpoints -n api7ee
|
||||
```
|
||||
|
||||
#### 6. HPA Not Scaling
|
||||
|
||||
**Check HPA status**:
|
||||
|
||||
```bash
|
||||
kubectl get hpa -n api7ee
|
||||
kubectl describe hpa -n api7ee <hpa-name>
|
||||
```
|
||||
|
||||
**Verify metrics-server**:
|
||||
|
||||
```bash
|
||||
kubectl top nodes
|
||||
kubectl top pods -n api7ee
|
||||
|
||||
# If metrics not available, install metrics-server:
|
||||
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Prometheus Metrics
|
||||
|
||||
If Prometheus is enabled:
|
||||
|
||||
```yaml
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 30s
|
||||
```
|
||||
|
||||
**View metrics**:
|
||||
|
||||
```bash
|
||||
# Check ServiceMonitor
|
||||
kubectl get servicemonitor -n api7ee
|
||||
|
||||
# Query Prometheus
|
||||
curl http://<prometheus-server>/api/v1/query?query=up{job="api7ee"}
|
||||
```
|
||||
|
||||
### API7 Dashboard
|
||||
|
||||
Access API7 Dashboard to view:
|
||||
|
||||
- Route traffic and statistics
|
||||
- Rate limiting metrics
|
||||
- Service health and upstreams
|
||||
- Plugin performance
|
||||
|
||||
## 🔐 Security
|
||||
|
||||
### Security Features
|
||||
|
||||
- **Pod Security Context**: Runs as non-root user (UID 1000)
|
||||
- **Security Context**: Drops all capabilities, prevents privilege escalation
|
||||
- **Read-only Root Filesystem**: Enabled for both services
|
||||
- **Network Policies**: Optional network policy support
|
||||
- **RBAC**: ServiceAccount with minimal permissions
|
||||
- **Secrets Management**: TLS certificates and API keys stored securely
|
||||
|
||||
### Enable Network Policies
|
||||
|
||||
```yaml
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
name: api7ee
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
```
|
||||
|
||||
### Secrets Management
|
||||
|
||||
**Create secrets before installation**:
|
||||
|
||||
```bash
|
||||
# API7 admin key
|
||||
kubectl create secret generic api7-admin-key \
|
||||
--from-literal=adminKey='your-admin-key' \
|
||||
-n api7ee
|
||||
|
||||
# LLM API key
|
||||
kubectl create secret generic llm-secrets \
|
||||
--from-literal=api-key='your-llm-api-key' \
|
||||
-n api7ee
|
||||
|
||||
# Image pull secret
|
||||
kubectl create secret docker-registry gitea-registry \
|
||||
--docker-server=git.commandware.com \
|
||||
--docker-username=<user> \
|
||||
--docker-password=<token> \
|
||||
-n api7ee
|
||||
```
|
||||
|
||||
**Reference in values**:
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
adminKey: "${API7_ADMIN_KEY}"
|
||||
|
||||
api:
|
||||
env:
|
||||
- name: OPENAI_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: llm-secrets
|
||||
key: api-key
|
||||
```
|
||||
|
||||
## 📚 Chart Structure
|
||||
|
||||
```
|
||||
helm/api7ee-demo-k8s/
|
||||
├── Chart.yaml # Chart metadata
|
||||
├── values.yaml # Default values
|
||||
├── values-dev.yaml # Development overrides
|
||||
├── values-production.yaml # Production overrides
|
||||
├── templates/
|
||||
│ ├── NOTES.txt # Post-install notes
|
||||
│ ├── _helpers.tpl # Template helpers
|
||||
│ ├── deployment-web.yaml # Web deployment
|
||||
│ ├── deployment-api.yaml # API deployment
|
||||
│ ├── service-web.yaml # Web service
|
||||
│ ├── service-api.yaml # API service
|
||||
│ ├── ingress.yaml # Ingress resource
|
||||
│ ├── configmap.yaml # Application config
|
||||
│ ├── configmap-adc.yaml # API7 ADC config
|
||||
│ ├── job-adc-sync.yaml # ADC sync job
|
||||
│ ├── secret.yaml # Application secrets
|
||||
│ ├── secret-api7.yaml # API7 secrets
|
||||
│ ├── certificate.yaml # cert-manager certificate
|
||||
│ ├── serviceaccount.yaml # ServiceAccount
|
||||
│ ├── rbac-adc.yaml # RBAC for ADC
|
||||
│ ├── hpa-web.yaml # Web HPA
|
||||
│ ├── hpa-api.yaml # API HPA
|
||||
│ └── poddisruptionbudget.yaml # PDB
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## 📦 Chart Information
|
||||
|
||||
- **Name**: api7ee-demo-k8s
|
||||
- **Version**: 0.1.0
|
||||
- **App Version**: 1.0.0
|
||||
- **Type**: application
|
||||
- **Keywords**: api7, api-gateway, web, api
|
||||
- **Home**: https://demo.commandware.it
|
||||
- **Sources**: https://git.commandware.com/demos/api7-demo
|
||||
|
||||
## 📄 Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **Helm**: https://helm.sh/docs/
|
||||
- **API7 Enterprise**: https://docs.api7.ai/
|
||||
- **Kubernetes**: https://kubernetes.io/docs/
|
||||
|
||||
### Related
|
||||
|
||||
- [Main README](../../README.md)
|
||||
- [Web Application README](../../web/README.md)
|
||||
- [API Application README](../../api/README.md)
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
- **Issues**: https://git.commandware.com/demos/api7-demo/issues
|
||||
- **Email**: support@commandware.com
|
||||
- **Repository**: https://git.commandware.com/demos/api7-demo
|
||||
|
||||
---
|
||||
|
||||
**Chart Version**: 0.1.0 | **Maintainer**: CommandWare | **License**: Demo Project
|
||||
58
helm/api7ee-demo-k8s/templates/NOTES.txt
Normal file
58
helm/api7ee-demo-k8s/templates/NOTES.txt
Normal file
@@ -0,0 +1,58 @@
|
||||
1. Get the application URL by running these commands:
|
||||
{{- if .Values.ingress.enabled }}
|
||||
{{- range $host := .Values.ingress.hosts }}
|
||||
{{- range .paths }}
|
||||
http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- else if contains "NodePort" .Values.web.service.type }}
|
||||
export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "api7ee.fullname" . }}-web)
|
||||
export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
|
||||
echo http://$NODE_IP:$NODE_PORT
|
||||
{{- else if contains "LoadBalancer" .Values.web.service.type }}
|
||||
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
|
||||
You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "api7ee.fullname" . }}-web'
|
||||
export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "api7ee.fullname" . }}-web --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
|
||||
echo http://$SERVICE_IP:{{ .Values.web.service.port }}
|
||||
{{- else if contains "ClusterIP" .Values.web.service.type }}
|
||||
echo "Visit http://127.0.0.1:8080 to use your application"
|
||||
kubectl --namespace {{ .Release.Namespace }} port-forward service/{{ include "api7ee.fullname" . }}-web 8080:{{ .Values.web.service.port }}
|
||||
{{- end }}
|
||||
|
||||
2. Check the deployment status:
|
||||
kubectl get deployments -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api7ee.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"
|
||||
|
||||
3. View the pods:
|
||||
kubectl get pods -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api7ee.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"
|
||||
|
||||
4. Check the logs:
|
||||
# For Web component:
|
||||
kubectl logs -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api7ee.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=web"
|
||||
|
||||
# For API component:
|
||||
kubectl logs -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "api7ee.name" . }},app.kubernetes.io/instance={{ .Release.Name }},app.kubernetes.io/component=api"
|
||||
|
||||
5. Scale the deployments:
|
||||
# Scale Web component:
|
||||
kubectl scale deployment {{ include "api7ee.fullname" . }}-web -n {{ .Release.Namespace }} --replicas=3
|
||||
|
||||
# Scale API component:
|
||||
kubectl scale deployment {{ include "api7ee.fullname" . }}-api -n {{ .Release.Namespace }} --replicas=5
|
||||
|
||||
{{- if .Values.web.autoscaling.enabled }}
|
||||
|
||||
6. Web Horizontal Pod Autoscaler is enabled:
|
||||
Min replicas: {{ .Values.web.autoscaling.minReplicas }}
|
||||
Max replicas: {{ .Values.web.autoscaling.maxReplicas }}
|
||||
Target CPU: {{ .Values.web.autoscaling.targetCPUUtilizationPercentage }}%
|
||||
Target Memory: {{ .Values.web.autoscaling.targetMemoryUtilizationPercentage }}%
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.api.autoscaling.enabled }}
|
||||
|
||||
7. API Horizontal Pod Autoscaler is enabled:
|
||||
Min replicas: {{ .Values.api.autoscaling.minReplicas }}
|
||||
Max replicas: {{ .Values.api.autoscaling.maxReplicas }}
|
||||
Target CPU: {{ .Values.api.autoscaling.targetCPUUtilizationPercentage }}%
|
||||
Target Memory: {{ .Values.api.autoscaling.targetMemoryUtilizationPercentage }}%
|
||||
{{- end }}
|
||||
62
helm/api7ee-demo-k8s/templates/_helpers.tpl
Normal file
62
helm/api7ee-demo-k8s/templates/_helpers.tpl
Normal file
@@ -0,0 +1,62 @@
|
||||
{{/*
|
||||
Expand the name of the chart.
|
||||
*/}}
|
||||
{{- define "api7ee.name" -}}
|
||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create a default fully qualified app name.
|
||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||
If release name contains chart name it will be used as a full name.
|
||||
*/}}
|
||||
{{- define "api7ee.fullname" -}}
|
||||
{{- if .Values.fullnameOverride }}
|
||||
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
||||
{{- if contains $name .Release.Name }}
|
||||
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
|
||||
{{- else }}
|
||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create chart name and version as used by the chart label.
|
||||
*/}}
|
||||
{{- define "api7ee.chart" -}}
|
||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Common labels
|
||||
*/}}
|
||||
{{- define "api7ee.labels" -}}
|
||||
helm.sh/chart: {{ include "api7ee.chart" . }}
|
||||
{{ include "api7ee.selectorLabels" . }}
|
||||
{{- if .Chart.AppVersion }}
|
||||
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
|
||||
{{- end }}
|
||||
app.kubernetes.io/managed-by: {{ .Release.Service }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Selector labels
|
||||
*/}}
|
||||
{{- define "api7ee.selectorLabels" -}}
|
||||
app.kubernetes.io/name: {{ include "api7ee.name" . }}
|
||||
app.kubernetes.io/instance: {{ .Release.Name }}
|
||||
{{- end }}
|
||||
|
||||
{{/*
|
||||
Create the name of the service account to use
|
||||
*/}}
|
||||
{{- define "api7ee.serviceAccountName" -}}
|
||||
{{- if .Values.serviceAccount.create }}
|
||||
{{- default (include "api7ee.fullname" .) .Values.serviceAccount.name }}
|
||||
{{- else }}
|
||||
{{- default "default" .Values.serviceAccount.name }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
30
helm/api7ee-demo-k8s/templates/certificate.yaml
Normal file
30
helm/api7ee-demo-k8s/templates/certificate.yaml
Normal file
@@ -0,0 +1,30 @@
|
||||
{{- if and .Values.api7.enabled .Values.api7.tls.enabled .Values.api7.tls.certManager.enabled }}
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-tls
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
spec:
|
||||
secretName: {{ .Values.api7.tls.secretName | default (printf "%s-tls" (include "api7ee.fullname" .)) }}
|
||||
issuerRef:
|
||||
name: {{ .Values.api7.tls.certManager.issuer }}
|
||||
kind: {{ .Values.api7.tls.certManager.issuerKind | default "ClusterIssuer" }}
|
||||
commonName: {{ first .Values.api7.hosts }}
|
||||
dnsNames:
|
||||
{{- range .Values.api7.hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
privateKey:
|
||||
algorithm: {{ .Values.api7.tls.privateKey.algorithm | default "RSA" }}
|
||||
encoding: PKCS1
|
||||
size: {{ .Values.api7.tls.privateKey.size | default 2048 }}
|
||||
rotationPolicy: {{ .Values.api7.tls.privateKey.rotationPolicy | default "Always" }}
|
||||
usages:
|
||||
- digital signature
|
||||
- key encipherment
|
||||
- server auth
|
||||
- client auth
|
||||
duration: {{ .Values.api7.tls.duration | default "2160h" }}
|
||||
renewBefore: {{ .Values.api7.tls.renewBefore | default "720h" }}
|
||||
{{- end }}
|
||||
172
helm/api7ee-demo-k8s/templates/configmap-adc.yaml
Normal file
172
helm/api7ee-demo-k8s/templates/configmap-adc.yaml
Normal file
@@ -0,0 +1,172 @@
|
||||
{{- if .Values.api7.enabled }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-adc-config
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: adc
|
||||
data:
|
||||
adc-config.yaml: |
|
||||
services:
|
||||
# Web Frontend Service
|
||||
- name: {{ include "api7ee.fullname" . }}-web-service
|
||||
hosts:
|
||||
- {{ (first .Values.api7.hosts) | quote }}
|
||||
upstream:
|
||||
name: {{ include "api7ee.fullname" . }}-web-upstream
|
||||
scheme: http
|
||||
type: roundrobin
|
||||
{{- if .Values.api7.serviceDiscovery.enabled }}
|
||||
# Use Kubernetes Service Discovery
|
||||
discovery_type: kubernetes
|
||||
{{- if .Values.api7.serviceDiscovery.namespace }}
|
||||
service_name: {{ .Values.api7.serviceDiscovery.namespace }}/{{ include "api7ee.fullname" . }}-web
|
||||
{{- else }}
|
||||
service_name: {{ .Release.Namespace }}/{{ include "api7ee.fullname" . }}-web
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
# Static nodes configuration
|
||||
nodes:
|
||||
- host: {{ include "api7ee.fullname" . }}-web.{{ .Release.Namespace }}.svc.cluster.local
|
||||
port: {{ .Values.web.service.port }}
|
||||
weight: 100
|
||||
{{- end }}
|
||||
routes:
|
||||
# Route for web frontend (all paths except /api)
|
||||
- name: {{ include "api7ee.fullname" . }}-web-route
|
||||
uris:
|
||||
- /*
|
||||
vars:
|
||||
- - uri
|
||||
- "~~"
|
||||
- "^(?!/api)"
|
||||
priority: 1
|
||||
plugins:
|
||||
{{- if .Values.api7.tls.enabled }}
|
||||
redirect:
|
||||
http_to_https: true
|
||||
{{- end }}
|
||||
{{- if .Values.api7.plugins.cors.enabled }}
|
||||
cors:
|
||||
allow_origins: {{ .Values.api7.plugins.cors.allowOrigins | join "," | quote }}
|
||||
allow_methods: {{ .Values.api7.plugins.cors.allowMethods | join "," | quote }}
|
||||
allow_headers: {{ .Values.api7.plugins.cors.allowHeaders | join "," | quote }}
|
||||
expose_headers: {{ .Values.api7.plugins.cors.exposeHeaders | join "," | quote }}
|
||||
max_age: {{ .Values.api7.plugins.cors.maxAge }}
|
||||
allow_credential: {{ .Values.api7.plugins.cors.allowCredentials }}
|
||||
{{- end }}
|
||||
|
||||
# API Backend Service
|
||||
- name: {{ include "api7ee.fullname" . }}-api-service
|
||||
hosts:
|
||||
- {{ (first .Values.api7.hosts) | quote }}
|
||||
upstream:
|
||||
name: {{ include "api7ee.fullname" . }}-api-upstream
|
||||
scheme: http
|
||||
type: roundrobin
|
||||
{{- if .Values.api7.serviceDiscovery.enabled }}
|
||||
# Use Kubernetes Service Discovery
|
||||
discovery_type: kubernetes
|
||||
{{- if .Values.api7.serviceDiscovery.namespace }}
|
||||
service_name: {{ .Values.api7.serviceDiscovery.namespace }}/{{ include "api7ee.fullname" . }}-api
|
||||
{{- else }}
|
||||
service_name: {{ .Release.Namespace }}/{{ include "api7ee.fullname" . }}-api
|
||||
{{- end }}
|
||||
{{- else }}
|
||||
# Static nodes configuration
|
||||
nodes:
|
||||
- host: {{ include "api7ee.fullname" . }}-api.{{ .Release.Namespace }}.svc.cluster.local
|
||||
port: {{ .Values.api.service.port }}
|
||||
weight: 100
|
||||
{{- end }}
|
||||
routes:
|
||||
# High priority route for LLM endpoints with AI rate limiting
|
||||
- name: {{ include "api7ee.fullname" . }}-api-llm-route
|
||||
uris:
|
||||
- /api/llm
|
||||
- /api/llm/*
|
||||
priority: 20
|
||||
plugins:
|
||||
{{- if .Values.api7.tls.enabled }}
|
||||
redirect:
|
||||
http_to_https: true
|
||||
{{- end }}
|
||||
# Remove /api prefix before forwarding to backend
|
||||
proxy-rewrite:
|
||||
regex_uri:
|
||||
- "^/api/(.*)"
|
||||
- "/$1"
|
||||
{{- if .Values.api7.plugins.cors.enabled }}
|
||||
cors:
|
||||
allow_origins: {{ .Values.api7.plugins.cors.allowOrigins | join "," | quote }}
|
||||
allow_methods: {{ .Values.api7.plugins.cors.allowMethods | join "," | quote }}
|
||||
allow_headers: {{ .Values.api7.plugins.cors.allowHeaders | join "," | quote }}
|
||||
expose_headers: {{ .Values.api7.plugins.cors.exposeHeaders | join "," | quote }}
|
||||
max_age: {{ .Values.api7.plugins.cors.maxAge }}
|
||||
allow_credential: {{ .Values.api7.plugins.cors.allowCredentials }}
|
||||
{{- end }}
|
||||
{{- if .Values.api7.plugins.aiRateLimit.enabled }}
|
||||
ai-rate-limiting:
|
||||
limit: {{ .Values.api7.plugins.aiRateLimit.limit }}
|
||||
time_window: {{ .Values.api7.plugins.aiRateLimit.timeWindow }}
|
||||
rejected_code: {{ .Values.api7.plugins.aiRateLimit.rejectedCode }}
|
||||
limit_strategy: {{ .Values.api7.plugins.aiRateLimit.limitStrategy | quote }}
|
||||
{{- end }}
|
||||
|
||||
# Standard API route with request rate limiting
|
||||
- name: {{ include "api7ee.fullname" . }}-api-route
|
||||
uris:
|
||||
- /api
|
||||
- /api/*
|
||||
priority: 10
|
||||
plugins:
|
||||
{{- if .Values.api7.tls.enabled }}
|
||||
redirect:
|
||||
http_to_https: true
|
||||
{{- end }}
|
||||
# Remove /api prefix before forwarding to backend
|
||||
proxy-rewrite:
|
||||
regex_uri:
|
||||
- "^/api/(.*)"
|
||||
- "/$1"
|
||||
{{- if .Values.api7.plugins.cors.enabled }}
|
||||
cors:
|
||||
allow_origins: {{ .Values.api7.plugins.cors.allowOrigins | join "," | quote }}
|
||||
allow_methods: {{ .Values.api7.plugins.cors.allowMethods | join "," | quote }}
|
||||
allow_headers: {{ .Values.api7.plugins.cors.allowHeaders | join "," | quote }}
|
||||
expose_headers: {{ .Values.api7.plugins.cors.exposeHeaders | join "," | quote }}
|
||||
max_age: {{ .Values.api7.plugins.cors.maxAge }}
|
||||
allow_credential: {{ .Values.api7.plugins.cors.allowCredentials }}
|
||||
{{- end }}
|
||||
{{- if .Values.api7.plugins.rateLimit.enabled }}
|
||||
limit-count:
|
||||
count: {{ .Values.api7.plugins.rateLimit.count }}
|
||||
time_window: {{ .Values.api7.plugins.rateLimit.timeWindow }}
|
||||
rejected_code: {{ .Values.api7.plugins.rateLimit.rejectedCode }}
|
||||
key_type: {{ .Values.api7.plugins.rateLimit.keyType | quote }}
|
||||
key: {{ .Values.api7.plugins.rateLimit.key | quote }}
|
||||
{{- end }}
|
||||
|
||||
{{- if .Values.api7.plugins.auth.enabled }}
|
||||
# API Consumers for authentication
|
||||
consumers:
|
||||
{{- range .Values.api7.consumers }}
|
||||
- username: {{ .username }}
|
||||
plugins:
|
||||
key-auth:
|
||||
key: {{ .apiKey }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- if .Values.api7.plugins.logging.enabled }}
|
||||
|
||||
# Global Rules
|
||||
global_rules:
|
||||
request-logging:
|
||||
plugins:
|
||||
http-logger:
|
||||
uri: {{ .Values.api7.plugins.logging.endpoint }}
|
||||
batch_max_size: {{ .Values.api7.plugins.logging.batchMaxSize | default 1000 }}
|
||||
inactive_timeout: {{ .Values.api7.plugins.logging.inactiveTimeout | default 5 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
10
helm/api7ee-demo-k8s/templates/configmap.yaml
Normal file
10
helm/api7ee-demo-k8s/templates/configmap.yaml
Normal file
@@ -0,0 +1,10 @@
|
||||
{{- if .Values.configMap.data }}
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
data:
|
||||
{{- toYaml .Values.configMap.data | nindent 2 }}
|
||||
{{- end }}
|
||||
81
helm/api7ee-demo-k8s/templates/deployment-api.yaml
Normal file
81
helm/api7ee-demo-k8s/templates/deployment-api.yaml
Normal file
@@ -0,0 +1,81 @@
|
||||
{{- if .Values.api.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-api
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
{{- if not .Values.api.autoscaling.enabled }}
|
||||
replicas: {{ .Values.api.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: api
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
labels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
{{- with .Values.global.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "api7ee.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: api
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.global.imageRegistry | default .Values.api.image.registry }}/{{ .Values.api.image.repository }}:{{ .Values.api.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.api.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.api.service.targetPort }}
|
||||
protocol: TCP
|
||||
{{- if and .Values.api.healthProbes.enabled .Values.api.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
{{- omit .Values.api.livenessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.api.healthProbes.enabled .Values.api.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
{{- omit .Values.api.readinessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.api.resources | nindent 12 }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: "{{ .Values.api.service.targetPort }}"
|
||||
{{- with .Values.api.env }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.configMap.data }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "api7ee.fullname" . }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
{{- with .Values.api.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.api.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.api.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
81
helm/api7ee-demo-k8s/templates/deployment-web.yaml
Normal file
81
helm/api7ee-demo-k8s/templates/deployment-web.yaml
Normal file
@@ -0,0 +1,81 @@
|
||||
{{- if .Values.web.enabled }}
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-web
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: web
|
||||
spec:
|
||||
{{- if not .Values.web.autoscaling.enabled }}
|
||||
replicas: {{ .Values.web.replicaCount }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: web
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
|
||||
labels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: web
|
||||
spec:
|
||||
{{- with .Values.global.imagePullSecrets }}
|
||||
imagePullSecrets:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
serviceAccountName: {{ include "api7ee.serviceAccountName" . }}
|
||||
securityContext:
|
||||
{{- toYaml .Values.podSecurityContext | nindent 8 }}
|
||||
containers:
|
||||
- name: web
|
||||
securityContext:
|
||||
{{- toYaml .Values.securityContext | nindent 12 }}
|
||||
image: "{{ .Values.global.imageRegistry | default .Values.web.image.registry }}/{{ .Values.web.image.repository }}:{{ .Values.web.image.tag | default .Chart.AppVersion }}"
|
||||
imagePullPolicy: {{ .Values.web.image.pullPolicy }}
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: {{ .Values.web.service.targetPort }}
|
||||
protocol: TCP
|
||||
{{- if and .Values.web.healthProbes.enabled .Values.web.livenessProbe.enabled }}
|
||||
livenessProbe:
|
||||
{{- omit .Values.web.livenessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if and .Values.web.healthProbes.enabled .Values.web.readinessProbe.enabled }}
|
||||
readinessProbe:
|
||||
{{- omit .Values.web.readinessProbe "enabled" | toYaml | nindent 12 }}
|
||||
{{- end }}
|
||||
resources:
|
||||
{{- toYaml .Values.web.resources | nindent 12 }}
|
||||
env:
|
||||
- name: PORT
|
||||
value: "{{ .Values.web.service.targetPort }}"
|
||||
{{- with .Values.web.env }}
|
||||
{{- toYaml . | nindent 12 }}
|
||||
{{- end }}
|
||||
{{- if .Values.configMap.data }}
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: {{ include "api7ee.fullname" . }}
|
||||
{{- end }}
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
{{- with .Values.web.nodeSelector }}
|
||||
nodeSelector:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.web.affinity }}
|
||||
affinity:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- with .Values.web.tolerations }}
|
||||
tolerations:
|
||||
{{- toYaml . | nindent 8 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
33
helm/api7ee-demo-k8s/templates/hpa-api.yaml
Normal file
33
helm/api7ee-demo-k8s/templates/hpa-api.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
{{- if and .Values.api.enabled .Values.api.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-api
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "api7ee.fullname" . }}-api
|
||||
minReplicas: {{ .Values.api.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.api.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.api.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.api.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.api.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.api.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
33
helm/api7ee-demo-k8s/templates/hpa-web.yaml
Normal file
33
helm/api7ee-demo-k8s/templates/hpa-web.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
{{- if and .Values.web.enabled .Values.web.autoscaling.enabled }}
|
||||
apiVersion: autoscaling/v2
|
||||
kind: HorizontalPodAutoscaler
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-web
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: web
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
name: {{ include "api7ee.fullname" . }}-web
|
||||
minReplicas: {{ .Values.web.autoscaling.minReplicas }}
|
||||
maxReplicas: {{ .Values.web.autoscaling.maxReplicas }}
|
||||
metrics:
|
||||
{{- if .Values.web.autoscaling.targetCPUUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: cpu
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.web.autoscaling.targetCPUUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- if .Values.web.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
- type: Resource
|
||||
resource:
|
||||
name: memory
|
||||
target:
|
||||
type: Utilization
|
||||
averageUtilization: {{ .Values.web.autoscaling.targetMemoryUtilizationPercentage }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
59
helm/api7ee-demo-k8s/templates/ingress.yaml
Normal file
59
helm/api7ee-demo-k8s/templates/ingress.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
{{- if .Values.ingress.enabled -}}
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
annotations:
|
||||
{{- if .Values.ingress.dnsScope }}
|
||||
dns-scope: {{ .Values.ingress.dnsScope | quote }}
|
||||
{{- end }}
|
||||
{{- with .Values.ingress.annotations }}
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
{{- if .Values.ingress.className }}
|
||||
ingressClassName: {{ .Values.ingress.className }}
|
||||
{{- end }}
|
||||
{{- if .Values.ingress.tls }}
|
||||
tls:
|
||||
{{- range .Values.ingress.tls }}
|
||||
- hosts:
|
||||
{{- range .hosts }}
|
||||
- {{ . | quote }}
|
||||
{{- end }}
|
||||
secretName: {{ .secretName }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
rules:
|
||||
{{- range .Values.ingress.hosts }}
|
||||
- host: {{ .host | quote }}
|
||||
http:
|
||||
paths:
|
||||
{{- range .paths }}
|
||||
- path: {{ .path }}
|
||||
pathType: {{ .pathType }}
|
||||
backend:
|
||||
{{- if .gateway }}
|
||||
{{- /* Use API7 Gateway as backend */}}
|
||||
service:
|
||||
name: {{ .gateway.serviceName }}
|
||||
port:
|
||||
number: {{ .gateway.port | default 80 }}
|
||||
{{- else if .service }}
|
||||
{{- /* Legacy: Use application service as backend */}}
|
||||
service:
|
||||
{{- if eq .service "web" }}
|
||||
name: {{ include "api7ee.fullname" $ }}-web
|
||||
port:
|
||||
number: {{ $.Values.web.service.port }}
|
||||
{{- else if eq .service "api" }}
|
||||
name: {{ include "api7ee.fullname" $ }}-api
|
||||
port:
|
||||
number: {{ $.Values.api.service.port }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
74
helm/api7ee-demo-k8s/templates/job-adc-sync.yaml
Normal file
74
helm/api7ee-demo-k8s/templates/job-adc-sync.yaml
Normal file
@@ -0,0 +1,74 @@
|
||||
{{- if .Values.api7.enabled }}
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-adc-sync
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: adc-sync
|
||||
annotations:
|
||||
"helm.sh/hook": post-install,post-upgrade
|
||||
"helm.sh/hook-weight": "10"
|
||||
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
|
||||
spec:
|
||||
backoffLimit: 3
|
||||
activeDeadlineSeconds: 300
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 8 }}
|
||||
app.kubernetes.io/component: adc-sync
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
serviceAccountName: {{ include "api7ee.serviceAccountName" . }}
|
||||
containers:
|
||||
- name: adc-sync
|
||||
image: ghcr.io/api7/adc:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- sync
|
||||
- -f
|
||||
- /config/adc-config.yaml
|
||||
- --backend
|
||||
- {{ .Values.api7.backend | default "api7ee" }}
|
||||
- --server
|
||||
- $(API7_ADMIN_URL)
|
||||
- --token
|
||||
- $(API7_ADMIN_KEY)
|
||||
- --gateway-group
|
||||
- $(API7_GATEWAY_GROUP)
|
||||
{{- if .Values.api7.adc.tlsSkipVerify }}
|
||||
- --tls-skip-verify
|
||||
{{- end }}
|
||||
{{- if .Values.api7.adc.verbose }}
|
||||
- --verbose
|
||||
{{- end }}
|
||||
env:
|
||||
- name: API7_ADMIN_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.api7.gateway.existingSecret | default (printf "%s-api7-admin" (include "api7ee.fullname" .)) }}
|
||||
key: {{ .Values.api7.gateway.existingSecretKeys.adminUrl | default "admin-url" }}
|
||||
- name: API7_ADMIN_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.api7.gateway.existingSecret | default (printf "%s-api7-admin" (include "api7ee.fullname" .)) }}
|
||||
key: {{ .Values.api7.gateway.existingSecretKeys.adminKey | default "admin-key" }}
|
||||
- name: API7_GATEWAY_GROUP
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: {{ .Values.api7.gateway.existingSecret | default (printf "%s-api7-admin" (include "api7ee.fullname" .)) }}
|
||||
key: {{ .Values.api7.gateway.existingSecretKeys.group | default "gateway-group" }}
|
||||
volumeMounts:
|
||||
- name: adc-config
|
||||
mountPath: /config
|
||||
readOnly: true
|
||||
{{- if .Values.api7.adc.resources }}
|
||||
resources:
|
||||
{{- toYaml .Values.api7.adc.resources | nindent 12 }}
|
||||
{{- end }}
|
||||
volumes:
|
||||
- name: adc-config
|
||||
configMap:
|
||||
name: {{ include "api7ee.fullname" . }}-adc-config
|
||||
{{- end }}
|
||||
40
helm/api7ee-demo-k8s/templates/poddisruptionbudget.yaml
Normal file
40
helm/api7ee-demo-k8s/templates/poddisruptionbudget.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
{{- if .Values.podDisruptionBudget.enabled }}
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-web
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: web
|
||||
spec:
|
||||
{{- if .Values.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .Values.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: web
|
||||
---
|
||||
apiVersion: policy/v1
|
||||
kind: PodDisruptionBudget
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-api
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: api
|
||||
spec:
|
||||
{{- if .Values.podDisruptionBudget.minAvailable }}
|
||||
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
|
||||
{{- end }}
|
||||
{{- if .Values.podDisruptionBudget.maxUnavailable }}
|
||||
maxUnavailable: {{ .Values.podDisruptionBudget.maxUnavailable }}
|
||||
{{- end }}
|
||||
selector:
|
||||
matchLabels:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 6 }}
|
||||
app.kubernetes.io/component: api
|
||||
{{- end }}
|
||||
36
helm/api7ee-demo-k8s/templates/rbac-adc.yaml
Normal file
36
helm/api7ee-demo-k8s/templates/rbac-adc.yaml
Normal file
@@ -0,0 +1,36 @@
|
||||
{{- if and .Values.api7.enabled .Values.serviceAccount.create }}
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-adc
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
rules:
|
||||
# Allow reading secrets (for certificates)
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list"]
|
||||
# Allow reading services and endpoints for service discovery
|
||||
- apiGroups: [""]
|
||||
resources: ["services", "endpoints"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# Allow reading pods for health checks
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-adc
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: {{ include "api7ee.fullname" . }}-adc
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: {{ include "api7ee.serviceAccountName" . }}
|
||||
namespace: {{ .Release.Namespace }}
|
||||
{{- end }}
|
||||
14
helm/api7ee-demo-k8s/templates/secret-api7.yaml
Normal file
14
helm/api7ee-demo-k8s/templates/secret-api7.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
{{- if and .Values.api7.enabled (not .Values.api7.gateway.existingSecret) }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-api7-admin
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: api7
|
||||
type: Opaque
|
||||
stringData:
|
||||
admin-key: {{ .Values.api7.gateway.adminKey | quote }}
|
||||
admin-url: {{ .Values.api7.gateway.adminUrl | quote }}
|
||||
gateway-group: {{ .Values.api7.gateway.group | default "default" | quote }}
|
||||
{{- end }}
|
||||
13
helm/api7ee-demo-k8s/templates/secret.yaml
Normal file
13
helm/api7ee-demo-k8s/templates/secret.yaml
Normal file
@@ -0,0 +1,13 @@
|
||||
{{- if .Values.secrets.create }}
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
type: Opaque
|
||||
data:
|
||||
{{- range $key, $val := .Values.secrets.data }}
|
||||
{{ $key }}: {{ $val | b64enc | quote }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
23
helm/api7ee-demo-k8s/templates/service-api.yaml
Normal file
23
helm/api7ee-demo-k8s/templates/service-api.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
{{- if .Values.api.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-api
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: api
|
||||
{{- with .Values.api.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.api.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.api.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: api
|
||||
{{- end }}
|
||||
23
helm/api7ee-demo-k8s/templates/service-web.yaml
Normal file
23
helm/api7ee-demo-k8s/templates/service-web.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
{{- if .Values.web.enabled }}
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: {{ include "api7ee.fullname" . }}-web
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: web
|
||||
{{- with .Values.web.service.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
spec:
|
||||
type: {{ .Values.web.service.type }}
|
||||
ports:
|
||||
- port: {{ .Values.web.service.port }}
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
{{- include "api7ee.selectorLabels" . | nindent 4 }}
|
||||
app.kubernetes.io/component: web
|
||||
{{- end }}
|
||||
12
helm/api7ee-demo-k8s/templates/serviceaccount.yaml
Normal file
12
helm/api7ee-demo-k8s/templates/serviceaccount.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
{{- if .Values.serviceAccount.create -}}
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: {{ include "api7ee.serviceAccountName" . }}
|
||||
labels:
|
||||
{{- include "api7ee.labels" . | nindent 4 }}
|
||||
{{- with .Values.serviceAccount.annotations }}
|
||||
annotations:
|
||||
{{- toYaml . | nindent 4 }}
|
||||
{{- end }}
|
||||
{{- end }}
|
||||
80
helm/api7ee-demo-k8s/values-dev.yaml
Normal file
80
helm/api7ee-demo-k8s/values-dev.yaml
Normal file
@@ -0,0 +1,80 @@
|
||||
# Development environment values for api7ee
|
||||
# This file contains development-specific configuration overrides
|
||||
|
||||
web:
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
tag: "latest"
|
||||
pullPolicy: Always
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
|
||||
api:
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
tag: "latest"
|
||||
pullPolicy: Always
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "debug"
|
||||
- name: ENVIRONMENT
|
||||
value: "development"
|
||||
- name: DEBUG
|
||||
value: "true"
|
||||
|
||||
ingress:
|
||||
enabled: false # Use port-forward in dev
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
|
||||
# Disable security features for easier debugging
|
||||
podSecurityContext:
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
fsGroup: 0
|
||||
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: true
|
||||
readOnlyRootFilesystem: false
|
||||
runAsNonRoot: false
|
||||
runAsUser: 0
|
||||
|
||||
# API7 configuration for development
|
||||
api7:
|
||||
enabled: false # Disable API7 ADC in dev to simplify local testing
|
||||
gateway:
|
||||
adminUrl: http://api7ee3-0-1759339083-dp-manager.api7ee.svc.cluster.local:7900
|
||||
adminKey: "dev-admin-key" # Use different key for dev
|
||||
hosts:
|
||||
- api7-demo-dev.commandware.it
|
||||
tls:
|
||||
enabled: false # No TLS in dev
|
||||
plugins:
|
||||
rateLimit:
|
||||
enabled: false # No rate limiting in dev
|
||||
auth:
|
||||
enabled: false # No auth in dev for easier testing
|
||||
106
helm/api7ee-demo-k8s/values-production.yaml
Normal file
106
helm/api7ee-demo-k8s/values-production.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
# Production environment values for api7ee
|
||||
# This file contains production-specific configuration overrides
|
||||
|
||||
global:
|
||||
imageRegistry: "git.commandware.com"
|
||||
imagePullSecrets:
|
||||
- name: registry-secret
|
||||
|
||||
web:
|
||||
replicaCount: 3
|
||||
|
||||
image:
|
||||
tag: "v1.0.0" # Use specific version in production
|
||||
pullPolicy: Always
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 15
|
||||
|
||||
api:
|
||||
replicaCount: 5
|
||||
|
||||
image:
|
||||
tag: "v1.0.0" # Use specific version in production
|
||||
pullPolicy: Always
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 5
|
||||
maxReplicas: 30
|
||||
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "warn"
|
||||
- name: ENVIRONMENT
|
||||
value: "production"
|
||||
|
||||
ingress:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rate-limit: "100"
|
||||
nginx.ingress.kubernetes.io/ssl-protocols: "TLSv1.2 TLSv1.3"
|
||||
nginx.ingress.kubernetes.io/ssl-ciphers: "HIGH:!aNULL:!MD5"
|
||||
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 2
|
||||
|
||||
metrics:
|
||||
enabled: true
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 15s
|
||||
|
||||
networkPolicy:
|
||||
enabled: true
|
||||
|
||||
# API7 configuration for production
|
||||
api7:
|
||||
enabled: true
|
||||
gateway:
|
||||
adminUrl: http://api7ee3-0-1759339083-dp-manager.api7ee.svc.cluster.local:7900
|
||||
adminKey: "${API7_ADMIN_KEY}" # Should be provided via secret in production
|
||||
gatewayService: gateway-0-1759393614-gateway
|
||||
hosts:
|
||||
- commandware.it
|
||||
- api7-demo.commandware.com # Additional production domain
|
||||
tls:
|
||||
enabled: true
|
||||
certManager:
|
||||
enabled: true
|
||||
issuer: cloudflare-acme-prod
|
||||
plugins:
|
||||
rateLimit:
|
||||
enabled: true
|
||||
count: 1000 # Higher limits for production
|
||||
timeWindow: 60
|
||||
apiCount: 10000 # Much higher for API endpoints
|
||||
cors:
|
||||
enabled: true
|
||||
allowOrigins:
|
||||
["https://commandware.it", "https://api7-demo.commandware.com"]
|
||||
allowCredentials: true
|
||||
auth:
|
||||
enabled: true # Enable auth in production
|
||||
prometheus:
|
||||
enabled: true
|
||||
logging:
|
||||
enabled: true
|
||||
endpoint: http://logging-service.monitoring:8080/logs
|
||||
445
helm/api7ee-demo-k8s/values.yaml
Normal file
445
helm/api7ee-demo-k8s/values.yaml
Normal file
@@ -0,0 +1,445 @@
|
||||
# Default values for api7ee.
|
||||
# This is a YAML-formatted file.
|
||||
# Declare variables to be passed into your templates.
|
||||
|
||||
global:
|
||||
# Global image registry to use for all images
|
||||
imageRegistry: ""
|
||||
# Image pull secrets for all images
|
||||
imagePullSecrets: []
|
||||
|
||||
# Configuration for the Web component
|
||||
web:
|
||||
enabled: false # Disabled when using API7 Gateway routing
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
registry: gitea.server_url # Will be replaced with actual Gitea URL
|
||||
repository: gitea.repository/web # Will be replaced with actual repository path
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "main" # Override with specific version
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
annotations: {}
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
minReplicas: 2
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 80
|
||||
targetMemoryUtilizationPercentage: 80
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
# Additional environment variables
|
||||
env: []
|
||||
|
||||
# Liveness and readiness probes
|
||||
healthProbes:
|
||||
enabled: true # Set to false to disable both probes
|
||||
livenessProbe:
|
||||
enabled: true # Set to false to disable liveness probe
|
||||
httpGet:
|
||||
path: /docs
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
enabled: true # Set to false to disable readiness probe
|
||||
httpGet:
|
||||
path: /docs
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
|
||||
# Configuration for the API component
|
||||
api:
|
||||
enabled: false # Disabled when using API7 Gateway routing
|
||||
replicaCount: 3
|
||||
|
||||
image:
|
||||
registry: gitea.server_url # Will be replaced with actual Gitea URL
|
||||
repository: gitea.repository/api # Will be replaced with actual repository path
|
||||
pullPolicy: IfNotPresent
|
||||
tag: "main" # Override with specific version
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
annotations: {}
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 20
|
||||
targetCPUUtilizationPercentage: 70
|
||||
targetMemoryUtilizationPercentage: 75
|
||||
|
||||
nodeSelector: {}
|
||||
tolerations: []
|
||||
affinity: {}
|
||||
|
||||
# Additional environment variables
|
||||
env:
|
||||
- name: LOG_LEVEL
|
||||
value: "info"
|
||||
|
||||
# Liveness and readiness probes
|
||||
healthProbes:
|
||||
enabled: true # Set to false to disable both probes
|
||||
livenessProbe:
|
||||
enabled: true # Set to false to disable liveness probe
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
enabled: true # Set to false to disable readiness probe
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
|
||||
# Ingress Configuration
|
||||
# Routes external traffic to the API7 Gateway, which then applies routing rules,
|
||||
# plugins (rate limiting, CORS, authentication), and forwards to backend services
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx" # Ingress controller class (nginx, traefik, etc.)
|
||||
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "cloudflare-acme-prod"
|
||||
# Add custom annotations as needed:
|
||||
# nginx.ingress.kubernetes.io/proxy-body-size: "10m"
|
||||
# nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
|
||||
|
||||
# Gateway routing configuration
|
||||
# All traffic is routed through API7 Gateway for advanced features:
|
||||
# - Dynamic routing based on ADC configuration
|
||||
# - Rate limiting (standard and AI-based)
|
||||
# - CORS policies
|
||||
# - Authentication/Authorization
|
||||
# - Request/Response transformation
|
||||
hosts:
|
||||
- host: commandware.it
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
# Route to API7 Gateway (recommended for production)
|
||||
gateway:
|
||||
serviceName: gateway-0-1759393614-gateway # API7 Gateway service name
|
||||
port: 80 # Gateway HTTP port (443 for HTTPS)
|
||||
namespace: api7ee # Gateway service namespace
|
||||
|
||||
# Direct service routing (legacy, not recommended)
|
||||
# Only use this if you need to bypass API7 Gateway
|
||||
# - path: /
|
||||
# pathType: Prefix
|
||||
# service: web # Routes directly to web service
|
||||
# - path: /api
|
||||
# pathType: Prefix
|
||||
# service: api # Routes directly to API service
|
||||
|
||||
# TLS/SSL Configuration
|
||||
tls:
|
||||
- secretName: api7ee-tls # Certificate secret name (created by cert-manager)
|
||||
hosts:
|
||||
- commandware.it
|
||||
|
||||
# ServiceAccount configuration
|
||||
serviceAccount:
|
||||
create: true
|
||||
annotations: {}
|
||||
name: ""
|
||||
|
||||
# Pod Security Context
|
||||
podSecurityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
fsGroup: 1000
|
||||
|
||||
# Security Context for containers
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
readOnlyRootFilesystem: true
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1000
|
||||
|
||||
# Network Policies
|
||||
networkPolicy:
|
||||
enabled: false
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
ingress: []
|
||||
egress: []
|
||||
|
||||
# Pod Disruption Budget
|
||||
podDisruptionBudget:
|
||||
enabled: true
|
||||
minAvailable: 1
|
||||
# maxUnavailable: 1
|
||||
|
||||
# Monitoring and metrics
|
||||
metrics:
|
||||
enabled: false
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
interval: 30s
|
||||
path: /metrics
|
||||
labels: {}
|
||||
|
||||
# ConfigMap for shared configuration
|
||||
configMap:
|
||||
data: {}
|
||||
|
||||
# Secrets for sensitive data
|
||||
secrets:
|
||||
create: false
|
||||
data: {}
|
||||
|
||||
# ============================================================================
|
||||
# API7 Gateway Configuration
|
||||
# ============================================================================
|
||||
# API7 Enterprise provides advanced API Gateway features including:
|
||||
# - Dynamic routing with service discovery
|
||||
# - Rate limiting (standard and AI-based for LLM endpoints)
|
||||
# - CORS policies
|
||||
# - Authentication/Authorization
|
||||
# - Request/Response transformation
|
||||
# - Observability and metrics
|
||||
api7:
|
||||
enabled: true # Enable API7 ADC (API Declarative Configuration) sync
|
||||
|
||||
# ADC Container Settings
|
||||
# ADC syncs declarative configuration from ConfigMap to API7 Gateway
|
||||
adc:
|
||||
image: ghcr.io/api7/adc:latest # ADC container image
|
||||
imagePullPolicy: IfNotPresent
|
||||
verbose: true # Enable verbose logging for debugging
|
||||
tlsSkipVerify: true # Skip TLS verification (required for self-signed dashboard certificates)
|
||||
# Resources for ADC sync job
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
|
||||
# API7 Gateway Connection Settings
|
||||
gateway:
|
||||
# ========================================================================
|
||||
# Secret Configuration
|
||||
# ========================================================================
|
||||
# Option 1: Use existing Secret (recommended for production)
|
||||
# Provide the name of an existing Kubernetes Secret containing credentials.
|
||||
# This is useful when using External Secrets Operator, Sealed Secrets,
|
||||
# or manually managed secrets for better security.
|
||||
#
|
||||
# The secret must contain these keys (or customize with existingSecretKeys):
|
||||
# - admin-url: Dashboard admin API URL
|
||||
# - admin-key: Dashboard admin API key
|
||||
# - gateway-group: Gateway group name
|
||||
#
|
||||
# Example: Create secret manually:
|
||||
# kubectl create secret generic api7-credentials \
|
||||
# --from-literal=admin-url=https://api7ee3-0-1759339083-dashboard:7443 \
|
||||
# --from-literal=admin-key=YOUR_ADMIN_KEY \
|
||||
# --from-literal=gateway-group=default \
|
||||
# -n api7ee
|
||||
existingSecret: "" # Name of existing secret (leave empty to create new secret)
|
||||
|
||||
# Custom key names for existing secret (optional)
|
||||
# Only needed if your existing secret uses different key names
|
||||
existingSecretKeys:
|
||||
adminUrl: admin-url # Key name for admin URL
|
||||
adminKey: admin-key # Key name for admin key
|
||||
group: gateway-group # Key name for gateway group
|
||||
|
||||
# ========================================================================
|
||||
# Option 2: Inline Configuration (for development only)
|
||||
# ========================================================================
|
||||
# When existingSecret is empty, a Secret will be created with these values.
|
||||
# NOT RECOMMENDED for production - use existingSecret instead!
|
||||
|
||||
# Dashboard Admin API URL (HTTPS required for API7 Enterprise)
|
||||
# The dashboard service exposes the admin API on port 7443
|
||||
adminUrl: https://api7ee3-0-1759339083-dashboard:7443
|
||||
|
||||
# Admin API key (CHANGE THIS IN PRODUCTION!)
|
||||
# Obtain from: kubectl get secret -n api7ee api7ee3-0-1759339083 -o jsonpath='{.data.admin_key}' | base64 -d
|
||||
adminKey: ""
|
||||
|
||||
# Gateway group name (logical grouping of gateway instances)
|
||||
group: default
|
||||
|
||||
# ========================================================================
|
||||
# Gateway Service Configuration
|
||||
# ========================================================================
|
||||
# Gateway service name (for traffic routing)
|
||||
# This is the Kubernetes service that routes traffic to APISIX data plane
|
||||
gatewayService: gateway-0-1759393614-gateway
|
||||
gatewayNamespace: api7ee # Gateway service namespace
|
||||
|
||||
# Backend Type
|
||||
# - api7ee: API7 Enterprise (includes all enterprise features)
|
||||
# - apisix: Open source APISIX (limited features)
|
||||
backend: api7ee
|
||||
|
||||
# Auto-publish Routes
|
||||
# When true, routes are automatically published after ADC sync
|
||||
# When false, routes must be manually published via dashboard
|
||||
autoPublish: true
|
||||
|
||||
# Domain Hosts
|
||||
# List of domains that API7 Gateway will handle
|
||||
# Must match Ingress hosts for proper routing
|
||||
hosts:
|
||||
- commandware.it
|
||||
|
||||
# TLS/SSL Configuration
|
||||
tls:
|
||||
enabled: true # Enable HTTPS for API7 Gateway
|
||||
|
||||
# Option 1: Use cert-manager (Recommended)
|
||||
# Automatically provisions and renews certificates
|
||||
certManager:
|
||||
enabled: true
|
||||
issuer: cloudflare-acme-prod # ClusterIssuer/Issuer name
|
||||
issuerKind: ClusterIssuer # ClusterIssuer or Issuer
|
||||
|
||||
# Private key settings
|
||||
privateKey:
|
||||
rotationPolicy: Always # Always or Never (cert-manager >= v1.18.0)
|
||||
algorithm: RSA # RSA or ECDSA
|
||||
size: 2048 # Key size in bits
|
||||
|
||||
# Certificate lifetime
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 720h # Renew 30 days before expiry
|
||||
|
||||
# Option 2: Use existing TLS secret
|
||||
secretName: "" # Leave empty to auto-generate name
|
||||
|
||||
# Option 3: Provide certificates directly (NOT recommended for production)
|
||||
certificate: ""
|
||||
key: ""
|
||||
|
||||
# ============================================================================
|
||||
# Service Discovery Configuration
|
||||
# ============================================================================
|
||||
# When enabled, API7 Gateway dynamically discovers backend Pods through
|
||||
# Kubernetes API instead of using static upstream node configuration.
|
||||
#
|
||||
# Benefits:
|
||||
# - Automatic scaling: New Pods are automatically added to upstream pool
|
||||
# - Health checks: Only healthy/ready Pods receive traffic
|
||||
# - Zero downtime: Seamless updates during deployments and rollouts
|
||||
# - No manual configuration: Eliminates need to specify Pod IPs/hostnames
|
||||
#
|
||||
# Requirements:
|
||||
# - RBAC permissions for services, endpoints (already configured in rbac-adc.yaml)
|
||||
# - Service must exist in Kubernetes
|
||||
serviceDiscovery:
|
||||
enabled: false # Disabled: requires Service Registry configuration in API7 Dashboard
|
||||
namespace: "" # Leave empty to use release namespace
|
||||
|
||||
# ============================================================================
|
||||
# API7 Plugins Configuration
|
||||
# ============================================================================
|
||||
# Plugins provide advanced features like rate limiting, CORS, auth, etc.
|
||||
# Each plugin can be enabled/disabled and configured independently
|
||||
|
||||
plugins:
|
||||
# Standard Rate Limiting
|
||||
# Applied to /api routes (except /api/llm)
|
||||
# Limits requests per IP address
|
||||
rateLimit:
|
||||
enabled: true
|
||||
count: 100 # Max requests per time window
|
||||
timeWindow: 60 # Time window in seconds
|
||||
rejectedCode: 429 # HTTP status code for rejected requests
|
||||
keyType: "var" # Key type: "var", "var_combination", "constant"
|
||||
key: "remote_addr" # Variable name for key (client IP)
|
||||
|
||||
# AI Rate Limiting
|
||||
# Applied to /api/llm routes
|
||||
# Specialized rate limiting for LLM/AI endpoints based on token usage
|
||||
aiRateLimit:
|
||||
enabled: true
|
||||
limit: 100 # Max tokens per time window
|
||||
timeWindow: 60 # Time window in seconds
|
||||
rejectedCode: 429 # HTTP status code
|
||||
limitStrategy: "total_tokens" # Strategy: "total_tokens", "input_tokens", "output_tokens"
|
||||
|
||||
# CORS (Cross-Origin Resource Sharing)
|
||||
# Enables browser-based applications to access the API
|
||||
cors:
|
||||
enabled: true
|
||||
allowOrigins: ["*"] # Allowed origins (use specific domains in production)
|
||||
allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH"]
|
||||
allowHeaders: ["*"] # Allowed headers
|
||||
exposeHeaders: ["*"] # Headers exposed to browser
|
||||
maxAge: 3600 # Preflight cache duration (seconds)
|
||||
allowCredentials: false # Allow credentials (cookies, auth headers)
|
||||
|
||||
# Authentication
|
||||
# Key-based authentication for API access
|
||||
auth:
|
||||
enabled: false # Enable to require API keys
|
||||
header: X-API-Key # Header name for API key
|
||||
|
||||
# Prometheus Metrics
|
||||
# Exposes metrics for monitoring and observability
|
||||
prometheus:
|
||||
enabled: true
|
||||
# Metrics endpoint: http://<gateway>:9091/apisix/prometheus/metrics
|
||||
|
||||
# Request Logging
|
||||
# Sends request logs to external logging service
|
||||
logging:
|
||||
enabled: false # Enable to send logs to external service
|
||||
endpoint: http://logging-service:8080/logs # Logging service URL
|
||||
batchMaxSize: 1000 # Max batch size before sending
|
||||
inactiveTimeout: 5 # Max wait time (seconds) before sending batch
|
||||
|
||||
# ============================================================================
|
||||
# API Consumers
|
||||
# ============================================================================
|
||||
# Consumers represent API clients with authentication credentials
|
||||
# Used with auth plugin (when auth.enabled: true)
|
||||
consumers:
|
||||
- username: demo-user
|
||||
apiKey: demo-key-12345 # Change in production!
|
||||
- username: admin
|
||||
apiKey: admin-key-67890 # Change in production!
|
||||
12
web/.env.example
Normal file
12
web/.env.example
Normal file
@@ -0,0 +1,12 @@
|
||||
# API Backend Configuration
|
||||
# Set this to the base URL where the API service is running
|
||||
|
||||
# Local development
|
||||
API_BASE_URL=http://localhost:8001
|
||||
|
||||
# Production
|
||||
# API_BASE_URL=https://commandware.it/api
|
||||
|
||||
# Other examples
|
||||
# API_BASE_URL=http://api:8001
|
||||
# API_BASE_URL=http://192.168.1.100:8001
|
||||
@@ -10,10 +10,13 @@ RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application and documentation
|
||||
COPY main.py .
|
||||
COPY templates/ ./templates/
|
||||
COPY static/ ./static/
|
||||
COPY docs/ ./docs/
|
||||
COPY mkdocs.yml .
|
||||
|
||||
# Build documentation during image build
|
||||
RUN mkdocs build -f docs/mkdocs.yml -d site
|
||||
RUN mkdocs build -d site
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
755
web/README.md
755
web/README.md
@@ -1,53 +1,96 @@
|
||||
# Web Application with Documentation
|
||||
# Web Application - FastAPI Frontend with Documentation
|
||||
|
||||
FastAPI web application serving a demo dashboard and comprehensive MkDocs documentation.
|
||||
A modern web frontend built with FastAPI featuring an interactive dashboard, embedded MkDocs documentation, and API proxy functionality for seamless integration with the backend API service.
|
||||
|
||||
## Features
|
||||
## 📋 Overview
|
||||
|
||||
- **Web Dashboard**: Simple HTML interface with metrics display
|
||||
- **Documentation Site**: Complete API7 Enterprise setup guide at `/docs`
|
||||
- **Health Checks**: Monitoring endpoint at `/health`
|
||||
This web application serves as the frontend for the API7 Enterprise Edition demo platform, providing:
|
||||
|
||||
## Documentation
|
||||
- **Interactive Dashboard**: Modern UI with real-time statistics
|
||||
- **Embedded Documentation**: Complete MkDocs documentation site at `/docs`
|
||||
- **API Proxy**: Seamless proxying of requests to the backend API
|
||||
- **LLM Chat Interface**: Interactive chat with AI videogame expert
|
||||
- **Health Monitoring**: Health check endpoints
|
||||
- **Static Assets**: Custom CSS and JavaScript for enhanced UX
|
||||
|
||||
The application includes comprehensive documentation built with MkDocs Material theme, covering:
|
||||
## ✨ Features
|
||||
|
||||
- **Architecture Overview**: Complete infrastructure and component details
|
||||
- **Getting Started**: Quick setup and deployment guide
|
||||
- **API7 Configuration**: Route and service configuration with examples
|
||||
### Web Pages
|
||||
|
||||
- **Home Page** (`/`): Dashboard with feature overview and statistics
|
||||
- **Items Page** (`/items`): Browse and manage items
|
||||
- **Users Page** (`/users`): View and manage users
|
||||
- **LLM Chat** (`/llm`): Interactive AI chat interface
|
||||
- **Documentation** (`/docs`): Full project documentation (MkDocs)
|
||||
|
||||
### API Proxy Endpoints
|
||||
|
||||
The web application proxies the following API endpoints:
|
||||
|
||||
- `GET /api/items` → Backend `/items`
|
||||
- `GET /api/items/{item_id}` → Backend `/items/{item_id}`
|
||||
- `GET /api/users` → Backend `/users`
|
||||
- `GET /api/users/{user_id}` → Backend `/users/{user_id}`
|
||||
- `POST /api/llm/chat` → Backend `/llm/chat`
|
||||
- `GET /api/llm/models` → Backend `/llm/models`
|
||||
- `GET /api/llm/health` → Backend `/llm/health`
|
||||
- `GET /api/config` → Returns frontend configuration
|
||||
|
||||
### Documentation Site
|
||||
|
||||
The embedded MkDocs documentation includes:
|
||||
|
||||
- **Getting Started**: Quick setup guide
|
||||
- **Architecture**: System architecture and component details
|
||||
- **Kubernetes Resources**: Complete resource reference
|
||||
- **CI/CD Pipeline**: Gitea Actions workflow documentation
|
||||
- **API7 Configuration**: Gateway setup and configuration
|
||||
- **CI/CD Pipeline**: Automation and deployment workflows
|
||||
- **Troubleshooting**: Common issues and solutions
|
||||
|
||||
## Running Locally
|
||||
## 🚀 Quick Start
|
||||
|
||||
### Prerequisites
|
||||
|
||||
```bash
|
||||
python >= 3.8
|
||||
pip
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
#### Install Dependencies
|
||||
|
||||
```bash
|
||||
cd web
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### Start Application
|
||||
#### Run the Application
|
||||
|
||||
```bash
|
||||
# Basic run (connects to localhost:8001 by default)
|
||||
python main.py
|
||||
|
||||
# With custom API backend URL
|
||||
export API_BASE_URL="http://localhost:8001"
|
||||
python main.py
|
||||
|
||||
# Or use uvicorn directly
|
||||
uvicorn main:app --reload --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
**Access**:
|
||||
#### Access the Application
|
||||
|
||||
- Main Page: http://localhost:8000
|
||||
- Documentation: http://localhost:8000/docs/
|
||||
- Health Check: http://localhost:8000/health
|
||||
- **Home**: http://localhost:8000
|
||||
- **Items**: http://localhost:8000/items
|
||||
- **Users**: http://localhost:8000/users
|
||||
- **LLM Chat**: http://localhost:8000/llm
|
||||
- **Documentation**: http://localhost:8000/docs
|
||||
- **Health Check**: http://localhost:8000/health
|
||||
|
||||
### Build Documentation Manually
|
||||
### Docker
|
||||
|
||||
```bash
|
||||
mkdocs build -f docs/mkdocs.yml -d site
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
### Build Image
|
||||
#### Build Image
|
||||
|
||||
```bash
|
||||
docker build -t web-app .
|
||||
@@ -56,23 +99,379 @@ docker build -t web-app .
|
||||
The Dockerfile:
|
||||
|
||||
1. Installs Python dependencies
|
||||
2. Copies application and documentation source
|
||||
3. Builds static documentation site with MkDocs
|
||||
4. Serves both the app and docs
|
||||
2. Copies application code, templates, static assets, and docs
|
||||
3. Builds static documentation with MkDocs
|
||||
4. Serves both the app and documentation
|
||||
|
||||
### Run Container
|
||||
#### Run Container
|
||||
|
||||
```bash
|
||||
# Basic run
|
||||
docker run -p 8000:8000 web-app
|
||||
|
||||
# With custom API backend URL
|
||||
docker run -p 8000:8000 \
|
||||
-e API_BASE_URL="http://api-backend:8001" \
|
||||
web-app
|
||||
|
||||
# Run with host network (for local testing)
|
||||
docker run --network host \
|
||||
-e API_BASE_URL="http://localhost:8001" \
|
||||
web-app
|
||||
```
|
||||
|
||||
## Kubernetes Deployment
|
||||
## 🔧 Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Description | Default | Required |
|
||||
| -------------- | --------------- | ----------------------- | -------- |
|
||||
| `API_BASE_URL` | Backend API URL | `http://localhost:8001` | No |
|
||||
|
||||
### Example Configuration
|
||||
|
||||
**Development**:
|
||||
|
||||
```bash
|
||||
export API_BASE_URL="http://localhost:8001"
|
||||
python main.py
|
||||
```
|
||||
|
||||
**Docker**:
|
||||
|
||||
```bash
|
||||
docker run -p 8000:8000 \
|
||||
-e API_BASE_URL="http://api-service:8001" \
|
||||
web-app
|
||||
```
|
||||
|
||||
**Kubernetes**:
|
||||
|
||||
```yaml
|
||||
env:
|
||||
- name: API_BASE_URL
|
||||
value: "http://nginx-service.api7ee.svc.cluster.local:8080"
|
||||
```
|
||||
|
||||
## 📄 Pages and Routes
|
||||
|
||||
### HTML Pages
|
||||
|
||||
#### `GET /`
|
||||
|
||||
Home dashboard with feature cards and statistics.
|
||||
|
||||
**Features**:
|
||||
|
||||
- Feature cards (Items, Users, LLM, API Docs)
|
||||
- Real-time statistics (item count, user count)
|
||||
- Feature highlights
|
||||
- Links to all sections
|
||||
|
||||
#### `GET /items`
|
||||
|
||||
Items management page.
|
||||
|
||||
**Features**:
|
||||
|
||||
- Display all items from API
|
||||
- Real-time data fetching
|
||||
- Interactive UI
|
||||
|
||||
#### `GET /users`
|
||||
|
||||
Users management page.
|
||||
|
||||
**Features**:
|
||||
|
||||
- Display all users from API
|
||||
- User information cards
|
||||
- Real-time data updates
|
||||
|
||||
#### `GET /llm`
|
||||
|
||||
LLM chat interface.
|
||||
|
||||
**Features**:
|
||||
|
||||
- Interactive chat with AI
|
||||
- Markdown rendering for responses
|
||||
- Token usage display
|
||||
- Rate limit indicator (100 tokens/60s)
|
||||
- Model selection (videogame-expert)
|
||||
- Real-time status updates
|
||||
|
||||
### API Proxy Routes
|
||||
|
||||
#### `GET /api/items`
|
||||
|
||||
Proxy to backend API items endpoint.
|
||||
|
||||
**Backend**: `GET {API_BASE_URL}/items`
|
||||
|
||||
#### `GET /api/items/{item_id}`
|
||||
|
||||
Proxy to backend API specific item endpoint.
|
||||
|
||||
**Backend**: `GET {API_BASE_URL}/items/{item_id}`
|
||||
|
||||
#### `GET /api/users`
|
||||
|
||||
Proxy to backend API users endpoint.
|
||||
|
||||
**Backend**: `GET {API_BASE_URL}/users`
|
||||
|
||||
#### `GET /api/users/{user_id}`
|
||||
|
||||
Proxy to backend API specific user endpoint.
|
||||
|
||||
**Backend**: `GET {API_BASE_URL}/users/{user_id}`
|
||||
|
||||
#### `POST /api/llm/chat`
|
||||
|
||||
Proxy to backend LLM chat endpoint.
|
||||
|
||||
**Backend**: `POST {API_BASE_URL}/llm/chat`
|
||||
|
||||
**Request Body**:
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "What is Zelda?",
|
||||
"max_tokens": 150,
|
||||
"temperature": 0.7,
|
||||
"model": "videogame-expert"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /api/llm/models`
|
||||
|
||||
Proxy to backend LLM models endpoint.
|
||||
|
||||
**Backend**: `GET {API_BASE_URL}/llm/models`
|
||||
|
||||
#### `GET /api/llm/health`
|
||||
|
||||
Proxy to backend LLM health check.
|
||||
|
||||
**Backend**: `GET {API_BASE_URL}/llm/health`
|
||||
|
||||
### Health Check
|
||||
|
||||
#### `GET /health`
|
||||
|
||||
Web application health check.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "web",
|
||||
"version": "1.0.0",
|
||||
"api_backend": "http://localhost:8001",
|
||||
"api_status": "healthy"
|
||||
}
|
||||
```
|
||||
|
||||
**API Status Values**:
|
||||
|
||||
- `healthy`: Backend is responding
|
||||
- `unhealthy`: Backend returned non-200 status
|
||||
- `unreachable`: Cannot connect to backend
|
||||
|
||||
### Configuration Endpoint
|
||||
|
||||
#### `GET /api/config`
|
||||
|
||||
Get current frontend configuration.
|
||||
|
||||
**Response**:
|
||||
|
||||
```json
|
||||
{
|
||||
"api_base_url": "http://localhost:8001"
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
### MkDocs Configuration
|
||||
|
||||
The application includes a comprehensive documentation site built with MkDocs Material theme.
|
||||
|
||||
**Configuration file**: `mkdocs.yml`
|
||||
|
||||
**Theme Features**:
|
||||
|
||||
- Material Design theme
|
||||
- Light/Dark mode toggle
|
||||
- Navigation tabs and sections
|
||||
- Search functionality
|
||||
- Syntax highlighting
|
||||
- Code copy buttons
|
||||
|
||||
### Documentation Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── index.md # Documentation home
|
||||
├── getting-started.md # Quick start guide
|
||||
├── architecture.md # System architecture
|
||||
├── kubernetes-resources.md # K8s resources reference
|
||||
├── api7-configuration.md # API7 Gateway setup
|
||||
├── cicd-pipeline.md # CI/CD documentation
|
||||
└── troubleshooting.md # Troubleshooting guide
|
||||
```
|
||||
|
||||
### Building Documentation
|
||||
|
||||
#### Serve Locally (Development)
|
||||
|
||||
```bash
|
||||
cd web
|
||||
mkdocs serve
|
||||
|
||||
# Access at http://localhost:8001
|
||||
```
|
||||
|
||||
#### Build Static Site
|
||||
|
||||
```bash
|
||||
mkdocs build -d site
|
||||
|
||||
# Output directory: web/site/
|
||||
```
|
||||
|
||||
The Docker build automatically builds the documentation during image creation.
|
||||
|
||||
### Updating Documentation
|
||||
|
||||
1. Edit markdown files in `docs/` directory
|
||||
2. Test locally with `mkdocs serve`
|
||||
3. Commit changes
|
||||
4. Rebuild Docker image or re-deploy
|
||||
|
||||
## 🎨 Templates and Static Assets
|
||||
|
||||
### Templates (Jinja2)
|
||||
|
||||
Located in `templates/`:
|
||||
|
||||
- **base.html**: Base template with common HTML structure
|
||||
- **index.html**: Home dashboard page
|
||||
- **items.html**: Items management page
|
||||
- **users.html**: Users management page
|
||||
- **llm.html**: LLM chat interface
|
||||
|
||||
### Static Assets
|
||||
|
||||
Located in `static/`:
|
||||
|
||||
- **css/**: Custom stylesheets
|
||||
- **js/**: JavaScript files
|
||||
|
||||
### Template Variables
|
||||
|
||||
Templates have access to:
|
||||
|
||||
- `request`: Starlette Request object
|
||||
- Context variables passed from route handlers
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
### Manual Testing
|
||||
|
||||
```bash
|
||||
# Test home page
|
||||
curl http://localhost:8000/
|
||||
|
||||
# Test health check
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Test API proxy
|
||||
curl http://localhost:8000/api/items
|
||||
curl http://localhost:8000/api/users
|
||||
|
||||
# Test LLM proxy
|
||||
curl -X POST http://localhost:8000/api/llm/chat \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"prompt": "What is Mario?",
|
||||
"max_tokens": 100
|
||||
}'
|
||||
|
||||
# Test configuration
|
||||
curl http://localhost:8000/api/config
|
||||
```
|
||||
|
||||
### Browser Testing
|
||||
|
||||
1. Navigate to http://localhost:8000
|
||||
2. Check all navigation links work
|
||||
3. Verify items and users load
|
||||
4. Test LLM chat interface
|
||||
5. Browse documentation at /docs
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
### Dockerfile Breakdown
|
||||
|
||||
```dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application files
|
||||
COPY main.py .
|
||||
COPY docs/ ./docs/
|
||||
COPY static/ ./static/
|
||||
COPY templates/ ./templates/
|
||||
COPY mkdocs.yml .
|
||||
|
||||
# Build documentation
|
||||
RUN mkdocs build -d site
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
```
|
||||
|
||||
### Multi-stage Build Benefits
|
||||
|
||||
- Dependencies installed during build
|
||||
- Documentation pre-built as static files
|
||||
- Smaller runtime container
|
||||
- Faster startup time
|
||||
|
||||
### Build and Push
|
||||
|
||||
```bash
|
||||
# Build
|
||||
docker build -t git.commandware.com/demos/api7-demo/web:latest .
|
||||
|
||||
# Tag
|
||||
docker tag git.commandware.com/demos/api7-demo/web:latest \
|
||||
git.commandware.com/demos/api7-demo/web:v1.0.0
|
||||
|
||||
# Push
|
||||
docker push git.commandware.com/demos/api7-demo/web:latest
|
||||
docker push git.commandware.com/demos/api7-demo/web:v1.0.0
|
||||
```
|
||||
|
||||
## ☸️ Kubernetes Deployment
|
||||
|
||||
### Basic Deployment
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web
|
||||
name: web-service
|
||||
namespace: api7ee
|
||||
spec:
|
||||
replicas: 2
|
||||
@@ -90,14 +489,28 @@ spec:
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
env:
|
||||
- name: API_BASE_URL
|
||||
value: "http://nginx-service.api7ee.svc.cluster.local:8080"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 256Mi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
@@ -114,55 +527,259 @@ spec:
|
||||
app: web
|
||||
```
|
||||
|
||||
## Documentation Structure
|
||||
### With Helm
|
||||
|
||||
```
|
||||
docs/
|
||||
├── mkdocs.yml # MkDocs configuration
|
||||
├── index.md # Documentation home
|
||||
├── getting-started.md # Quick start guide
|
||||
├── architecture.md # Infrastructure architecture
|
||||
├── kubernetes-resources.md # K8s resources reference
|
||||
├── api7-configuration.md # API7 Gateway config guide
|
||||
├── cicd-pipeline.md # CI/CD documentation
|
||||
└── troubleshooting.md # Troubleshooting guide
|
||||
See the [Helm chart README](../helm/api7ee-demo-k8s/README.md) for full deployment options.
|
||||
|
||||
```bash
|
||||
helm install api7ee-demo ./helm/api7ee-demo-k8s \
|
||||
--set web.image.tag=v1.0.0 \
|
||||
--set web.replicaCount=3 \
|
||||
--namespace api7ee
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
## 📦 Dependencies
|
||||
|
||||
| Endpoint | Description |
|
||||
| --------- | --------------------------- |
|
||||
| `/` | Main web dashboard |
|
||||
| `/docs/` | Documentation site (MkDocs) |
|
||||
| `/health` | Health check endpoint |
|
||||
### Python Requirements
|
||||
|
||||
## CI/CD
|
||||
```
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
jinja2==3.1.3
|
||||
python-multipart==0.0.6
|
||||
httpx==0.26.0
|
||||
mkdocs==1.5.3
|
||||
mkdocs-material==9.5.3
|
||||
pymdown-extensions==10.7
|
||||
```
|
||||
|
||||
Images are automatically built and pushed to Gitea registry on every push:
|
||||
### Install
|
||||
|
||||
**Registry**: `git.commandware.com/demos/api7-demo/web:<branch>`
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
See [CI/CD Pipeline documentation](docs/cicd-pipeline.md) for details.
|
||||
|
||||
## Development
|
||||
|
||||
### Update Documentation
|
||||
|
||||
1. Edit markdown files in `docs/` directory
|
||||
2. Test locally:
|
||||
```bash
|
||||
mkdocs serve -f docs/mkdocs.yml
|
||||
```
|
||||
3. View at http://localhost:8001
|
||||
4. Rebuild with `mkdocs build` or rebuild Docker image
|
||||
|
||||
### Dependencies
|
||||
### Dependency Breakdown
|
||||
|
||||
- **fastapi**: Web framework
|
||||
- **uvicorn**: ASGI server
|
||||
- **uvicorn**: ASGI server with WebSocket support
|
||||
- **jinja2**: Template engine for HTML pages
|
||||
- **python-multipart**: Form data parsing
|
||||
- **httpx**: Async HTTP client for API proxying
|
||||
- **mkdocs**: Documentation site generator
|
||||
- **mkdocs-material**: Material theme for MkDocs
|
||||
- **pymdown-extensions**: Additional markdown extensions
|
||||
|
||||
## 🚀 Production Deployment
|
||||
|
||||
### Recommended Configuration
|
||||
|
||||
```yaml
|
||||
web:
|
||||
replicaCount: 3
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 15
|
||||
targetCPUUtilizationPercentage: 80
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
env:
|
||||
- name: API_BASE_URL
|
||||
value: "http://nginx-service.api7ee.svc.cluster.local:8080"
|
||||
```
|
||||
|
||||
### API7 Gateway Integration
|
||||
|
||||
When deployed behind API7 Gateway:
|
||||
|
||||
**Routing**:
|
||||
|
||||
- Priority 1: `/*` routes to web service (matches all except `/api`)
|
||||
- Hosts: `commandware.it`
|
||||
|
||||
**Plugins**:
|
||||
|
||||
- `redirect`: HTTP → HTTPS enforcement
|
||||
- No rate limiting on web pages (only on API endpoints)
|
||||
|
||||
**Service Discovery**:
|
||||
|
||||
```yaml
|
||||
upstream:
|
||||
name: apache-upstream
|
||||
nodes:
|
||||
- host: apache-service.api7ee.svc.cluster.local
|
||||
port: 80
|
||||
weight: 100
|
||||
```
|
||||
|
||||
## 🔒 Security
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Non-root User**: Container runs as non-root (UID 1000)
|
||||
2. **Read-only Filesystem**: Root filesystem is read-only
|
||||
3. **CORS**: Configured via API7 Gateway
|
||||
4. **Input Validation**: FastAPI automatic validation
|
||||
5. **Proxy Security**: Validates API responses before forwarding
|
||||
|
||||
### Security Headers
|
||||
|
||||
Consider adding these headers via API7 Gateway:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
response-rewrite:
|
||||
headers:
|
||||
X-Frame-Options: DENY
|
||||
X-Content-Type-Options: nosniff
|
||||
X-XSS-Protection: "1; mode=block"
|
||||
```
|
||||
|
||||
## 📊 Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
The `/health` endpoint provides:
|
||||
|
||||
- Web service status
|
||||
- API backend connectivity
|
||||
- API backend health status
|
||||
|
||||
**Kubernetes Probes**:
|
||||
|
||||
```yaml
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
Monitor application logs:
|
||||
|
||||
```bash
|
||||
# Docker
|
||||
docker logs -f web-app
|
||||
|
||||
# Kubernetes
|
||||
kubectl logs -n api7ee -l app=web --tail=100 -f
|
||||
|
||||
# Specific pod
|
||||
kubectl logs -n api7ee web-service-xxx-xxx -f
|
||||
```
|
||||
|
||||
## 🐛 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Issue**: Cannot connect to API backend
|
||||
|
||||
```bash
|
||||
# Check API_BASE_URL
|
||||
curl http://localhost:8000/api/config
|
||||
|
||||
# Check backend health
|
||||
curl http://localhost:8000/health
|
||||
|
||||
# Response shows api_status: "unreachable"
|
||||
# Solution: Verify API_BASE_URL is correct and backend is running
|
||||
```
|
||||
|
||||
**Issue**: Documentation not showing
|
||||
|
||||
```bash
|
||||
# Rebuild documentation
|
||||
mkdocs build -d site
|
||||
|
||||
# Check if site directory exists
|
||||
ls -la site/
|
||||
|
||||
# Rebuild Docker image
|
||||
docker build -t web-app .
|
||||
```
|
||||
|
||||
**Issue**: Static assets not loading
|
||||
|
||||
```bash
|
||||
# Check static directory exists
|
||||
ls -la static/
|
||||
|
||||
# Verify static mount in Docker
|
||||
docker run -it web-app ls -la /app/static
|
||||
|
||||
# Check browser console for 404 errors
|
||||
```
|
||||
|
||||
**Issue**: Templates not rendering
|
||||
|
||||
```bash
|
||||
# Check templates directory
|
||||
ls -la templates/
|
||||
|
||||
# Verify template files exist
|
||||
ls templates/*.html
|
||||
|
||||
# Check application logs for Jinja2 errors
|
||||
docker logs web-app
|
||||
```
|
||||
|
||||
### Debug Mode
|
||||
|
||||
Run with debug logging:
|
||||
|
||||
```python
|
||||
# In main.py, add:
|
||||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
# Or with uvicorn:
|
||||
uvicorn main:app --log-level debug
|
||||
```
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
### Documentation
|
||||
|
||||
- **FastAPI**: https://fastapi.tiangolo.com/
|
||||
- **MkDocs**: https://www.mkdocs.org/
|
||||
- **MkDocs Material**: https://squidfunk.github.io/mkdocs-material/
|
||||
- **Jinja2**: https://jinja.palletsprojects.com/
|
||||
- **HTTPX**: https://www.python-httpx.org/
|
||||
|
||||
### Related
|
||||
|
||||
- [Main README](../README.md)
|
||||
- [API Application README](../api/README.md)
|
||||
- [Helm Chart README](../helm/api7ee-demo-k8s/README.md)
|
||||
|
||||
## 🎯 Future Enhancements
|
||||
|
||||
Potential improvements:
|
||||
|
||||
- [ ] User authentication
|
||||
- [ ] WebSocket support for real-time updates
|
||||
- [ ] Progressive Web App (PWA) features
|
||||
- [ ] Advanced caching strategies
|
||||
- [ ] Internationalization (i18n)
|
||||
- [ ] Enhanced error pages
|
||||
|
||||
---
|
||||
|
||||
_FastAPI web application with integrated MkDocs documentation for API7 Enterprise demo._
|
||||
**Version**: 1.0.0 | **Port**: 8000 | **Framework**: FastAPI 0.104.1 | **Theme**: Material for MkDocs 9.5.3
|
||||
|
||||
274
web/docs/argocd-values-guide.md
Normal file
274
web/docs/argocd-values-guide.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# ArgoCD Values Update Guide
|
||||
|
||||
Questo documento descrive le modifiche da applicare al `values.yaml` in ArgoCD.
|
||||
|
||||
## 📋 Modifiche Principali
|
||||
|
||||
### 1. Service Discovery - DISABILITATO
|
||||
|
||||
**Prima:**
|
||||
```yaml
|
||||
api7:
|
||||
serviceDiscovery:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```yaml
|
||||
api7:
|
||||
serviceDiscovery:
|
||||
enabled: false # Disabled until Service Registry is configured in API7 Dashboard
|
||||
namespace: ""
|
||||
```
|
||||
|
||||
**Motivo:** Service discovery richiede la configurazione di un Service Registry nel Dashboard API7 Enterprise. Finché non viene configurato, ADC sync fallisce con errore `service registry not found`.
|
||||
|
||||
---
|
||||
|
||||
### 2. Existing Secret - CONFIGURATO
|
||||
|
||||
**Prima:**
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
existingSecret: ""
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
# Use existing secret for production (recommended)
|
||||
# The secret api7-credentials already exists with the correct admin key
|
||||
existingSecret: "api7-credentials"
|
||||
existingSecretKeys:
|
||||
adminUrl: admin-url
|
||||
adminKey: admin-key
|
||||
group: gateway-group
|
||||
```
|
||||
|
||||
**Motivo:** Il Secret `api7-credentials` esiste già nel namespace `api7ee` con la chiave admin corretta. Usarlo evita di creare un nuovo Secret vuoto.
|
||||
|
||||
---
|
||||
|
||||
### 3. Gateway Namespace - AGGIUNTO
|
||||
|
||||
**Prima:**
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
gatewayService: gateway-0-1759393614-gateway
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
gatewayService: gateway-0-1759393614-gateway
|
||||
gatewayNamespace: api7ee
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. ADC Resources - CONFIGURATE
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
adc:
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 256Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Ingress Configuration - AGGIORNATA
|
||||
|
||||
**Prima:**
|
||||
```yaml
|
||||
ingress:
|
||||
annotations:
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
hosts:
|
||||
- host: commandware.it
|
||||
paths:
|
||||
- path: /
|
||||
gateway:
|
||||
serviceName: gateway-0-1759393614-gateway
|
||||
port: 80
|
||||
- path: /api
|
||||
gateway:
|
||||
serviceName: gateway-0-1759393614-gateway
|
||||
port: 80
|
||||
tls:
|
||||
- secretName: api7ee-demo-tls
|
||||
```
|
||||
|
||||
**Dopo:**
|
||||
```yaml
|
||||
ingress:
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-acme-prod
|
||||
hosts:
|
||||
- host: commandware.it
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
gateway:
|
||||
serviceName: gateway-0-1759393614-gateway
|
||||
port: 80
|
||||
namespace: api7ee
|
||||
tls:
|
||||
- secretName: api7ee-tls
|
||||
hosts:
|
||||
- commandware.it
|
||||
```
|
||||
|
||||
**Modifiche:**
|
||||
- Rimossa annotation `nginx.ingress.kubernetes.io/rewrite-target: /`
|
||||
- Aggiunta annotation `cert-manager.io/cluster-issuer`
|
||||
- Un solo path `/` invece di due path separati (`/` e `/api`)
|
||||
- Aggiunto `namespace: api7ee` al gateway
|
||||
- Cambiato `secretName` da `api7ee-demo-tls` a `api7ee-tls`
|
||||
|
||||
---
|
||||
|
||||
### 6. TLS Configuration - COMPLETATA
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
tls:
|
||||
enabled: true
|
||||
certManager:
|
||||
enabled: true
|
||||
issuer: cloudflare-acme-prod
|
||||
issuerKind: ClusterIssuer
|
||||
privateKey:
|
||||
rotationPolicy: Always
|
||||
algorithm: RSA
|
||||
size: 2048
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 720h # 30 days before expiry
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Come Applicare le Modifiche
|
||||
|
||||
### Opzione 1: Interfaccia Web ArgoCD
|
||||
|
||||
1. Accedi ad ArgoCD Dashboard
|
||||
2. Seleziona l'applicazione `api7ee-demo`
|
||||
3. Clicca su "APP DETAILS" → "PARAMETERS"
|
||||
4. Modifica i valori come sopra indicato
|
||||
5. Clicca "SAVE"
|
||||
6. Clicca "SYNC" per applicare le modifiche
|
||||
|
||||
### Opzione 2: Via CLI
|
||||
|
||||
```bash
|
||||
# Edit application
|
||||
kubectl edit application -n argocd api7ee-demo
|
||||
|
||||
# O usa ArgoCD CLI
|
||||
argocd app set api7ee-demo --values /path/to/updated-values.yaml
|
||||
argocd app sync api7ee-demo
|
||||
```
|
||||
|
||||
### Opzione 3: Usa il values.yaml aggiornato
|
||||
|
||||
Il file `values.yaml` nella root del progetto è già stato aggiornato con tutte le modifiche.
|
||||
Puoi copiarlo direttamente nella configurazione ArgoCD.
|
||||
|
||||
---
|
||||
|
||||
## ✅ Verifica Post-Sync
|
||||
|
||||
Dopo la sincronizzazione, verifica che tutto funzioni:
|
||||
|
||||
### 1. Check ConfigMap
|
||||
```bash
|
||||
kubectl get configmap -n api7ee api7ee-demo-api7ee-demo-k8s-adc-config \
|
||||
-o jsonpath='{.data.adc-config\.yaml}' | grep "discovery_type"
|
||||
```
|
||||
**Risultato atteso:** Nessun output (service discovery disabilitato)
|
||||
|
||||
### 2. Check Secret
|
||||
```bash
|
||||
kubectl get secret -n api7ee api7ee-demo-api7ee-demo-k8s-api7-admin \
|
||||
-o jsonpath='{.data.admin-key}' | base64 -d | wc -c
|
||||
```
|
||||
**Risultato atteso:** `88` (chiave presente)
|
||||
|
||||
### 3. Check ADC Sync Job
|
||||
```bash
|
||||
kubectl logs -n api7ee job/api7ee-demo-api7ee-demo-k8s-adc-sync --tail=20
|
||||
```
|
||||
**Risultato atteso:**
|
||||
```
|
||||
[ADC] › ✔ success Sync configuration
|
||||
[ADC] › ★ star All is well, see you next time!
|
||||
```
|
||||
|
||||
### 4. Test Routing
|
||||
```bash
|
||||
# Test web
|
||||
curl -I https://commandware.it/
|
||||
|
||||
# Test API
|
||||
curl -I https://commandware.it/api/health
|
||||
```
|
||||
**Risultato atteso:** HTTP 200 o 405 (metodi funzionanti)
|
||||
|
||||
---
|
||||
|
||||
## 📝 Note Importanti
|
||||
|
||||
### Service Discovery
|
||||
Per riabilitare service discovery in futuro:
|
||||
|
||||
1. **Configura Service Registry nel Dashboard API7:**
|
||||
- Accedi al Dashboard: `https://api7ee3-0-1759339083-dashboard:7443`
|
||||
- Vai a: Gateway Groups → Service Registries
|
||||
- Aggiungi: Kubernetes Service Registry
|
||||
- Configura: API Server URL e Token
|
||||
|
||||
2. **Abilita nel values.yaml:**
|
||||
```yaml
|
||||
api7:
|
||||
serviceDiscovery:
|
||||
enabled: true
|
||||
```
|
||||
|
||||
3. **Sync ArgoCD**
|
||||
|
||||
### Secret Management
|
||||
Il Secret `api7-credentials` contiene:
|
||||
- `admin-url`: URL del Dashboard API7
|
||||
- `admin-key`: Chiave di autenticazione (88 caratteri)
|
||||
- `gateway-group`: Nome del gateway group (`default`)
|
||||
|
||||
Per ruotare le credenziali:
|
||||
```bash
|
||||
kubectl patch secret -n api7ee api7-credentials \
|
||||
-p '{"data":{"admin-key":"<new-base64-encoded-key>"}}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Riferimenti
|
||||
|
||||
- [Service Discovery Guide](web/docs/service-discovery.md)
|
||||
- [Secret Management Guide](web/docs/secret-management.md)
|
||||
- [Ingress & Routing Guide](web/docs/ingress-routing.md)
|
||||
- [API7 Enterprise Docs](https://docs.api7.ai/enterprise/)
|
||||
|
||||
---
|
||||
|
||||
**Data aggiornamento:** 2025-10-09
|
||||
**Versione Chart:** api7ee-demo-k8s-0.1.0
|
||||
120
web/docs/examples/externalsecret-api7.yaml
Normal file
120
web/docs/examples/externalsecret-api7.yaml
Normal file
@@ -0,0 +1,120 @@
|
||||
# Example ExternalSecret for API7 Gateway credentials
|
||||
# This file demonstrates how to use External Secrets Operator with API7 Gateway
|
||||
#
|
||||
# Documentation: https://external-secrets.io/
|
||||
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: api7-gateway-credentials
|
||||
namespace: api7ee
|
||||
labels:
|
||||
app.kubernetes.io/name: api7ee-demo
|
||||
app.kubernetes.io/component: api7
|
||||
spec:
|
||||
# Refresh interval for fetching secrets from external provider
|
||||
refreshInterval: 1h
|
||||
|
||||
# Reference to SecretStore
|
||||
secretStoreRef:
|
||||
name: vault-backend # Name of your SecretStore
|
||||
kind: SecretStore # or ClusterSecretStore
|
||||
|
||||
# Target Secret configuration
|
||||
target:
|
||||
name: api7-credentials
|
||||
creationPolicy: Owner
|
||||
template:
|
||||
type: Opaque
|
||||
data:
|
||||
# Map external secret keys to Kubernetes secret keys
|
||||
admin-url: "{{ .adminUrl }}"
|
||||
admin-key: "{{ .adminKey }}"
|
||||
gateway-group: "{{ .group }}"
|
||||
|
||||
# Data to fetch from external provider
|
||||
data:
|
||||
- secretKey: adminUrl
|
||||
remoteRef:
|
||||
key: api7/gateway # Path in external secret store
|
||||
property: admin_url # Property name
|
||||
|
||||
- secretKey: adminKey
|
||||
remoteRef:
|
||||
key: api7/gateway
|
||||
property: admin_key
|
||||
|
||||
- secretKey: group
|
||||
remoteRef:
|
||||
key: api7/gateway
|
||||
property: gateway_group
|
||||
|
||||
---
|
||||
# Example SecretStore for AWS Secrets Manager
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: aws-secretsmanager
|
||||
namespace: api7ee
|
||||
spec:
|
||||
provider:
|
||||
aws:
|
||||
service: SecretsManager
|
||||
region: us-east-1
|
||||
auth:
|
||||
jwt:
|
||||
serviceAccountRef:
|
||||
name: external-secrets-sa
|
||||
|
||||
---
|
||||
# Example SecretStore for HashiCorp Vault
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: vault-backend
|
||||
namespace: api7ee
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: "https://vault.example.com"
|
||||
path: "secret"
|
||||
version: "v2"
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: "kubernetes"
|
||||
role: "api7-role"
|
||||
serviceAccountRef:
|
||||
name: api7ee-demo-api7ee-demo-k8s
|
||||
|
||||
---
|
||||
# Example SecretStore for Azure Key Vault
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: azure-keyvault
|
||||
namespace: api7ee
|
||||
spec:
|
||||
provider:
|
||||
azurekv:
|
||||
vaultUrl: "https://my-vault.vault.azure.net"
|
||||
authType: WorkloadIdentity
|
||||
serviceAccountRef:
|
||||
name: api7ee-demo-api7ee-demo-k8s
|
||||
|
||||
---
|
||||
# Example SecretStore for GCP Secret Manager
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: gcp-secretmanager
|
||||
namespace: api7ee
|
||||
spec:
|
||||
provider:
|
||||
gcpsm:
|
||||
projectID: "my-project"
|
||||
auth:
|
||||
workloadIdentity:
|
||||
clusterLocation: us-central1
|
||||
clusterName: my-cluster
|
||||
serviceAccountRef:
|
||||
name: api7ee-demo-api7ee-demo-k8s
|
||||
616
web/docs/ingress-routing.md
Normal file
616
web/docs/ingress-routing.md
Normal file
@@ -0,0 +1,616 @@
|
||||
# Ingress and Gateway Routing
|
||||
|
||||
Learn how external traffic is routed through Kubernetes Ingress to the API7 Gateway, and then to backend services.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[External User] -->|HTTPS| B[Ingress Controller]
|
||||
B -->|HTTP/HTTPS| C[API7 Gateway]
|
||||
C -->|Apply Plugins| C
|
||||
C -->|Route Traffic| D[Backend Service 1]
|
||||
C -->|Route Traffic| E[Backend Service 2]
|
||||
C -->|Route Traffic| F[Backend Service 3]
|
||||
|
||||
G[cert-manager] -->|TLS Cert| B
|
||||
H[ADC ConfigMap] -->|Routing Rules| C
|
||||
```
|
||||
|
||||
**Traffic Flow:**
|
||||
1. **External request** arrives at Ingress Controller (NGINX)
|
||||
2. **Ingress Controller** terminates TLS and forwards to API7 Gateway
|
||||
3. **API7 Gateway** applies policies (rate limiting, CORS, auth)
|
||||
4. **Gateway** routes to backend services based on ADC rules
|
||||
5. **Backend services** process request and return response
|
||||
|
||||
## Ingress Configuration
|
||||
|
||||
### Current Setup
|
||||
|
||||
The Helm chart configures Ingress to route **all traffic** through API7 Gateway:
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
|
||||
hosts:
|
||||
- host: commandware.it
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
gateway:
|
||||
serviceName: gateway-0-1759393614-gateway
|
||||
port: 80
|
||||
```
|
||||
|
||||
Generated Ingress resource:
|
||||
|
||||
```yaml
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: api7ee-demo
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-acme-prod
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
tls:
|
||||
- hosts:
|
||||
- commandware.it
|
||||
secretName: api7ee-tls
|
||||
rules:
|
||||
- host: commandware.it
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: gateway-0-1759393614-gateway
|
||||
port:
|
||||
number: 80
|
||||
```
|
||||
|
||||
### Why Route Through Gateway?
|
||||
|
||||
**With API7 Gateway (Recommended):**
|
||||
```
|
||||
User → Ingress → API7 Gateway → Backend
|
||||
↓
|
||||
TLS, Basic Routing
|
||||
↓
|
||||
Advanced Features:
|
||||
- Dynamic routing
|
||||
- Rate limiting
|
||||
- CORS policies
|
||||
- Authentication
|
||||
- Request transformation
|
||||
- Metrics & logging
|
||||
```
|
||||
|
||||
**Direct to Service (Legacy):**
|
||||
```
|
||||
User → Ingress → Backend
|
||||
↓
|
||||
TLS, Basic Routing
|
||||
❌ No advanced features
|
||||
❌ No rate limiting
|
||||
❌ No CORS
|
||||
❌ No centralized auth
|
||||
```
|
||||
|
||||
## Gateway Routing Rules
|
||||
|
||||
### ADC Configuration
|
||||
|
||||
API7 Gateway uses **ADC (API Declarative Configuration)** to define routing rules:
|
||||
|
||||
```yaml
|
||||
# Managed by Helm chart in ConfigMap
|
||||
services:
|
||||
- name: web-service
|
||||
hosts:
|
||||
- commandware.it
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: my-web-app
|
||||
namespace_id: default
|
||||
routes:
|
||||
- name: web-route
|
||||
uris:
|
||||
- /*
|
||||
priority: 1
|
||||
plugins:
|
||||
cors: {...}
|
||||
redirect:
|
||||
http_to_https: true
|
||||
|
||||
- name: api-service
|
||||
hosts:
|
||||
- commandware.it
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: my-api
|
||||
namespace_id: default
|
||||
routes:
|
||||
- name: api-route
|
||||
uris:
|
||||
- /api
|
||||
- /api/*
|
||||
priority: 10
|
||||
plugins:
|
||||
limit-count: {...}
|
||||
cors: {...}
|
||||
```
|
||||
|
||||
### Route Priority
|
||||
|
||||
Routes are evaluated by **priority** (higher number = higher priority):
|
||||
|
||||
```yaml
|
||||
routes:
|
||||
# Evaluated FIRST (highest priority)
|
||||
- name: api-llm-route
|
||||
uris:
|
||||
- /api/llm/*
|
||||
priority: 20
|
||||
plugins:
|
||||
ai-rate-limiting: {...}
|
||||
|
||||
# Evaluated SECOND
|
||||
- name: api-route
|
||||
uris:
|
||||
- /api/*
|
||||
priority: 10
|
||||
plugins:
|
||||
limit-count: {...}
|
||||
|
||||
# Evaluated LAST (lowest priority)
|
||||
- name: web-route
|
||||
uris:
|
||||
- /*
|
||||
priority: 1
|
||||
```
|
||||
|
||||
**Example Request Matching:**
|
||||
- `GET /api/llm/chat` → Matches `api-llm-route` (priority 20)
|
||||
- `GET /api/users` → Matches `api-route` (priority 10)
|
||||
- `GET /about` → Matches `web-route` (priority 1)
|
||||
|
||||
### URI Matching Patterns
|
||||
|
||||
**Prefix matching:**
|
||||
```yaml
|
||||
uris:
|
||||
- /api/* # Matches /api/users, /api/v1/products, etc.
|
||||
- /admin/* # Matches /admin/dashboard, /admin/users, etc.
|
||||
```
|
||||
|
||||
**Exact matching:**
|
||||
```yaml
|
||||
uris:
|
||||
- /health # Matches only /health
|
||||
- /ready # Matches only /ready
|
||||
```
|
||||
|
||||
**Regex matching:**
|
||||
```yaml
|
||||
vars:
|
||||
- - uri
|
||||
- "~~"
|
||||
- "^(?!/api)" # Match all URIs NOT starting with /api
|
||||
```
|
||||
|
||||
## TLS/SSL Configuration
|
||||
|
||||
### Certificate Management
|
||||
|
||||
The setup uses **cert-manager** with Cloudflare ACME:
|
||||
|
||||
```yaml
|
||||
# values.yaml
|
||||
ingress:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: cloudflare-acme-prod
|
||||
tls:
|
||||
- secretName: api7ee-tls
|
||||
hosts:
|
||||
- commandware.it
|
||||
```
|
||||
|
||||
Generated Certificate:
|
||||
|
||||
```yaml
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: Certificate
|
||||
metadata:
|
||||
name: api7ee-demo-tls
|
||||
spec:
|
||||
secretName: api7ee-tls
|
||||
issuerRef:
|
||||
name: cloudflare-acme-prod
|
||||
kind: ClusterIssuer
|
||||
dnsNames:
|
||||
- commandware.it
|
||||
duration: 2160h # 90 days
|
||||
renewBefore: 720h # Renew 30 days before expiry
|
||||
```
|
||||
|
||||
### HTTP to HTTPS Redirect
|
||||
|
||||
API7 Gateway handles HTTP→HTTPS redirect:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
```
|
||||
|
||||
**Request flow:**
|
||||
1. User visits `http://commandware.it/api`
|
||||
2. Gateway returns `301 Moved Permanently`
|
||||
3. Browser redirects to `https://commandware.it/api`
|
||||
|
||||
## Advanced Routing Scenarios
|
||||
|
||||
### Multiple Domains
|
||||
|
||||
Route different domains to different backends:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: main-site
|
||||
hosts:
|
||||
- commandware.it
|
||||
upstream:
|
||||
service_name: main-web
|
||||
routes:
|
||||
- uris: ["/*"]
|
||||
|
||||
- name: api-site
|
||||
hosts:
|
||||
- api.commandware.it
|
||||
upstream:
|
||||
service_name: api-backend
|
||||
routes:
|
||||
- uris: ["/*"]
|
||||
```
|
||||
|
||||
### Path-Based Routing
|
||||
|
||||
Route based on URL paths:
|
||||
|
||||
```yaml
|
||||
routes:
|
||||
# Route 1: Frontend SPA
|
||||
- name: frontend
|
||||
uris:
|
||||
- /
|
||||
- /app/*
|
||||
- /assets/*
|
||||
upstream:
|
||||
service_name: frontend-app
|
||||
|
||||
# Route 2: API endpoints
|
||||
- name: backend-api
|
||||
uris:
|
||||
- /api/*
|
||||
upstream:
|
||||
service_name: backend-api
|
||||
|
||||
# Route 3: Admin panel
|
||||
- name: admin
|
||||
uris:
|
||||
- /admin/*
|
||||
upstream:
|
||||
service_name: admin-panel
|
||||
plugins:
|
||||
key-auth: {...} # Require authentication
|
||||
```
|
||||
|
||||
### Header-Based Routing
|
||||
|
||||
Route based on HTTP headers:
|
||||
|
||||
```yaml
|
||||
routes:
|
||||
# Route for API v2 clients
|
||||
- name: api-v2
|
||||
uris:
|
||||
- /api/*
|
||||
vars:
|
||||
- - http_x_api_version
|
||||
- "=="
|
||||
- "v2"
|
||||
upstream:
|
||||
service_name: api-v2
|
||||
|
||||
# Route for API v1 clients (default)
|
||||
- name: api-v1
|
||||
uris:
|
||||
- /api/*
|
||||
upstream:
|
||||
service_name: api-v1
|
||||
```
|
||||
|
||||
### Method-Based Routing
|
||||
|
||||
Route based on HTTP method:
|
||||
|
||||
```yaml
|
||||
routes:
|
||||
# Read operations → Read-only replica
|
||||
- name: read-ops
|
||||
uris:
|
||||
- /api/*
|
||||
methods:
|
||||
- GET
|
||||
- HEAD
|
||||
upstream:
|
||||
service_name: api-readonly
|
||||
|
||||
# Write operations → Primary backend
|
||||
- name: write-ops
|
||||
uris:
|
||||
- /api/*
|
||||
methods:
|
||||
- POST
|
||||
- PUT
|
||||
- DELETE
|
||||
- PATCH
|
||||
upstream:
|
||||
service_name: api-primary
|
||||
```
|
||||
|
||||
## Configuration Examples
|
||||
|
||||
### Example 1: Microservices API
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# User service
|
||||
- name: users-api
|
||||
hosts:
|
||||
- api.commandware.it
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: users-service
|
||||
routes:
|
||||
- uris: ["/users", "/users/*"]
|
||||
plugins:
|
||||
limit-count:
|
||||
count: 100
|
||||
time_window: 60
|
||||
|
||||
# Orders service
|
||||
- name: orders-api
|
||||
hosts:
|
||||
- api.commandware.it
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: orders-service
|
||||
routes:
|
||||
- uris: ["/orders", "/orders/*"]
|
||||
plugins:
|
||||
key-auth: {}
|
||||
|
||||
# Products service
|
||||
- name: products-api
|
||||
hosts:
|
||||
- api.commandware.it
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: products-service
|
||||
routes:
|
||||
- uris: ["/products", "/products/*"]
|
||||
```
|
||||
|
||||
### Example 2: API with WebSocket
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: websocket-service
|
||||
hosts:
|
||||
- ws.commandware.it
|
||||
upstream:
|
||||
service_name: websocket-backend
|
||||
routes:
|
||||
# WebSocket endpoint
|
||||
- name: ws-route
|
||||
uris:
|
||||
- /ws
|
||||
enable_websocket: true
|
||||
|
||||
# HTTP fallback
|
||||
- name: http-route
|
||||
uris:
|
||||
- /*
|
||||
```
|
||||
|
||||
### Example 3: Canary Deployment
|
||||
|
||||
```yaml
|
||||
services:
|
||||
# 90% traffic to stable version
|
||||
- name: app-stable
|
||||
hosts:
|
||||
- app.commandware.it
|
||||
upstream:
|
||||
service_name: app-v1
|
||||
nodes:
|
||||
- weight: 90
|
||||
routes:
|
||||
- uris: ["/*"]
|
||||
|
||||
# 10% traffic to canary version
|
||||
- name: app-canary
|
||||
hosts:
|
||||
- app.commandware.it
|
||||
upstream:
|
||||
service_name: app-v2
|
||||
nodes:
|
||||
- weight: 10
|
||||
routes:
|
||||
- uris: ["/*"]
|
||||
```
|
||||
|
||||
## Monitoring and Debugging
|
||||
|
||||
### Check Ingress Status
|
||||
|
||||
```bash
|
||||
# Get Ingress resource
|
||||
kubectl get ingress -n api7ee
|
||||
|
||||
# Describe Ingress
|
||||
kubectl describe ingress api7ee-demo -n api7ee
|
||||
|
||||
# Check Ingress Controller logs
|
||||
kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx
|
||||
```
|
||||
|
||||
### Check Gateway Routing
|
||||
|
||||
```bash
|
||||
# View ADC configuration
|
||||
kubectl get configmap api7ee-demo-api7ee-demo-k8s-adc-config -n api7ee -o yaml
|
||||
|
||||
# Check Gateway logs
|
||||
kubectl logs -n api7ee -l app.kubernetes.io/name=gateway
|
||||
|
||||
# Test routing
|
||||
curl -v https://commandware.it/api/health
|
||||
```
|
||||
|
||||
### Verify TLS Certificate
|
||||
|
||||
```bash
|
||||
# Check certificate
|
||||
kubectl get certificate -n api7ee
|
||||
|
||||
# Describe certificate
|
||||
kubectl describe certificate api7ee-demo-tls -n api7ee
|
||||
|
||||
# View certificate secret
|
||||
kubectl get secret api7ee-tls -n api7ee -o yaml
|
||||
|
||||
# Test TLS connection
|
||||
openssl s_client -connect commandware.it:443 -servername commandware.it
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 404 Not Found
|
||||
|
||||
**Cause:** No route matches the request
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
# Check ADC routes
|
||||
kubectl get configmap -n api7ee api7ee-demo-adc-config -o yaml | grep -A 10 "routes:"
|
||||
|
||||
# Check Gateway logs
|
||||
kubectl logs -n api7ee -l app.kubernetes.io/name=gateway | grep 404
|
||||
```
|
||||
|
||||
### 502 Bad Gateway
|
||||
|
||||
**Cause:** Backend service is unavailable
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
# Check backend service
|
||||
kubectl get svc my-backend -n api7ee
|
||||
|
||||
# Check backend Pods
|
||||
kubectl get pods -l app=my-backend -n api7ee
|
||||
|
||||
# Check service discovery
|
||||
kubectl get endpoints my-backend -n api7ee
|
||||
```
|
||||
|
||||
### TLS Certificate Issues
|
||||
|
||||
**Cause:** cert-manager failed to issue certificate
|
||||
|
||||
**Debug:**
|
||||
```bash
|
||||
# Check certificate status
|
||||
kubectl describe certificate api7ee-demo-tls -n api7ee
|
||||
|
||||
# Check cert-manager logs
|
||||
kubectl logs -n cert-manager -l app=cert-manager
|
||||
|
||||
# Check certificate request
|
||||
kubectl get certificaterequest -n api7ee
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Use Gateway for Production
|
||||
|
||||
✅ **Do:** Route through API7 Gateway for advanced features
|
||||
|
||||
❌ **Don't:** Route directly to backend services
|
||||
|
||||
### 2. Use Descriptive Route Names
|
||||
|
||||
```yaml
|
||||
# Good
|
||||
routes:
|
||||
- name: api-users-list
|
||||
- name: api-products-create
|
||||
- name: web-frontend-spa
|
||||
|
||||
# Bad
|
||||
routes:
|
||||
- name: route1
|
||||
- name: route2
|
||||
```
|
||||
|
||||
### 3. Set Appropriate Priorities
|
||||
|
||||
Higher priority for more specific routes:
|
||||
|
||||
```yaml
|
||||
# Specific route (high priority)
|
||||
- uris: ["/api/v2/users/*"]
|
||||
priority: 20
|
||||
|
||||
# General route (low priority)
|
||||
- uris: ["/api/*"]
|
||||
priority: 10
|
||||
```
|
||||
|
||||
### 4. Enable TLS Everywhere
|
||||
|
||||
Always use HTTPS in production:
|
||||
|
||||
```yaml
|
||||
plugins:
|
||||
redirect:
|
||||
http_to_https: true
|
||||
```
|
||||
|
||||
### 5. Monitor Gateway Metrics
|
||||
|
||||
Set up monitoring for:
|
||||
- Request latency
|
||||
- Error rates (4xx, 5xx)
|
||||
- Upstream health
|
||||
- TLS certificate expiry
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Configure Service Discovery](service-discovery.md)
|
||||
- [Manage Secrets Securely](secret-management.md)
|
||||
- [Set Up Rate Limiting](api7-configuration.md#rate-limiting)
|
||||
|
||||
## References
|
||||
|
||||
- [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/)
|
||||
- [cert-manager Documentation](https://cert-manager.io/docs/)
|
||||
- [API7 Gateway Routing](https://docs.api7.ai/enterprise/key-concepts/routes/)
|
||||
233
web/docs/secret-management.md
Normal file
233
web/docs/secret-management.md
Normal file
@@ -0,0 +1,233 @@
|
||||
# Secret Management for API7 Gateway
|
||||
|
||||
This document explains how to manage API7 Gateway credentials securely.
|
||||
|
||||
## Overview
|
||||
|
||||
API7 Gateway requires the following credentials:
|
||||
- **Admin URL**: Dashboard admin API endpoint
|
||||
- **Admin Key**: Authentication token for admin API
|
||||
- **Gateway Group**: Logical grouping of gateway instances
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Option 1: Inline Configuration (Development Only)
|
||||
|
||||
**⚠️ NOT RECOMMENDED for production**
|
||||
|
||||
Credentials are stored directly in `values.yaml`:
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
existingSecret: "" # Leave empty
|
||||
adminUrl: https://api7ee3-0-1759339083-dashboard:7443
|
||||
adminKey: "your-admin-key"
|
||||
group: default
|
||||
```
|
||||
|
||||
The chart will create a Kubernetes Secret automatically.
|
||||
|
||||
### Option 2: Existing Secret (Recommended)
|
||||
|
||||
**✅ RECOMMENDED for production**
|
||||
|
||||
Create a Secret manually or using a secret management tool, then reference it:
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
existingSecret: "api7-credentials" # Name of your secret
|
||||
existingSecretKeys:
|
||||
adminUrl: admin-url
|
||||
adminKey: admin-key
|
||||
group: gateway-group
|
||||
```
|
||||
|
||||
#### Create Secret Manually
|
||||
|
||||
```bash
|
||||
kubectl create secret generic api7-credentials \
|
||||
--from-literal=admin-url=https://api7ee3-0-1759339083-dashboard:7443 \
|
||||
--from-literal=admin-key=YOUR_ADMIN_KEY \
|
||||
--from-literal=gateway-group=default \
|
||||
-n api7ee
|
||||
```
|
||||
|
||||
#### Get Admin Key from API7 Enterprise
|
||||
|
||||
```bash
|
||||
# Get admin key from API7 Enterprise installation
|
||||
kubectl get secret -n api7ee api7ee3-0-1759339083 \
|
||||
-o jsonpath='{.data.admin_key}' | base64 -d
|
||||
```
|
||||
|
||||
### Option 3: External Secrets Operator
|
||||
|
||||
**✅ RECOMMENDED for production with centralized secret management**
|
||||
|
||||
Use External Secrets Operator to sync secrets from external providers like:
|
||||
- AWS Secrets Manager
|
||||
- HashiCorp Vault
|
||||
- Azure Key Vault
|
||||
- GCP Secret Manager
|
||||
- And more...
|
||||
|
||||
#### Step 1: Install External Secrets Operator
|
||||
|
||||
```bash
|
||||
helm repo add external-secrets https://charts.external-secrets.io
|
||||
helm install external-secrets external-secrets/external-secrets -n external-secrets-system --create-namespace
|
||||
```
|
||||
|
||||
#### Step 2: Configure SecretStore
|
||||
|
||||
Example for HashiCorp Vault:
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: SecretStore
|
||||
metadata:
|
||||
name: vault-backend
|
||||
namespace: api7ee
|
||||
spec:
|
||||
provider:
|
||||
vault:
|
||||
server: "https://vault.example.com"
|
||||
path: "secret"
|
||||
version: "v2"
|
||||
auth:
|
||||
kubernetes:
|
||||
mountPath: "kubernetes"
|
||||
role: "api7-role"
|
||||
serviceAccountRef:
|
||||
name: api7ee-demo-api7ee-demo-k8s
|
||||
```
|
||||
|
||||
#### Step 3: Create ExternalSecret
|
||||
|
||||
See `templates/externalsecret-api7.yaml.example` for a complete example.
|
||||
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: api7-credentials-external
|
||||
namespace: api7ee
|
||||
spec:
|
||||
refreshInterval: 1h
|
||||
secretStoreRef:
|
||||
name: vault-backend
|
||||
kind: SecretStore
|
||||
target:
|
||||
name: api7-credentials
|
||||
creationPolicy: Owner
|
||||
data:
|
||||
- secretKey: admin-url
|
||||
remoteRef:
|
||||
key: api7/gateway
|
||||
property: admin_url
|
||||
- secretKey: admin-key
|
||||
remoteRef:
|
||||
key: api7/gateway
|
||||
property: admin_key
|
||||
- secretKey: gateway-group
|
||||
remoteRef:
|
||||
key: api7/gateway
|
||||
property: gateway_group
|
||||
```
|
||||
|
||||
#### Step 4: Configure Helm Values
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
existingSecret: "api7-credentials" # Created by ExternalSecret
|
||||
```
|
||||
|
||||
## Secret Keys
|
||||
|
||||
The Secret must contain these keys (default names):
|
||||
|
||||
| Key | Description | Example |
|
||||
|-----|-------------|---------|
|
||||
| `admin-url` | Dashboard admin API URL | `https://api7ee3-0-1759339083-dashboard:7443` |
|
||||
| `admin-key` | Dashboard admin API key | `edd1c9f034335f136f87ad84b625c8f1` |
|
||||
| `gateway-group` | Gateway group name | `default` |
|
||||
|
||||
### Custom Key Names
|
||||
|
||||
If your Secret uses different key names, configure them:
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
gateway:
|
||||
existingSecret: "my-custom-secret"
|
||||
existingSecretKeys:
|
||||
adminUrl: dashboard_url # Your custom key name
|
||||
adminKey: api_token # Your custom key name
|
||||
group: group_name # Your custom key name
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
1. **Never commit secrets to Git**
|
||||
- Use `.gitignore` for values files containing secrets
|
||||
- Use secret management tools for production
|
||||
|
||||
2. **Use RBAC to restrict secret access**
|
||||
- Limit which ServiceAccounts can read secrets
|
||||
- Use namespace isolation
|
||||
|
||||
3. **Rotate credentials regularly**
|
||||
- Update admin keys periodically
|
||||
- Use External Secrets Operator for automatic rotation
|
||||
|
||||
4. **Enable audit logging**
|
||||
- Monitor secret access in Kubernetes audit logs
|
||||
- Alert on unauthorized access attempts
|
||||
|
||||
5. **Use encryption at rest**
|
||||
- Enable Kubernetes secret encryption
|
||||
- Use external KMS for additional security
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Secret not found
|
||||
|
||||
```bash
|
||||
# Check if secret exists
|
||||
kubectl get secret -n api7ee
|
||||
|
||||
# Describe the secret
|
||||
kubectl describe secret api7-credentials -n api7ee
|
||||
```
|
||||
|
||||
### Invalid credentials
|
||||
|
||||
```bash
|
||||
# View secret data (base64 encoded)
|
||||
kubectl get secret api7-credentials -n api7ee -o yaml
|
||||
|
||||
# Decode and verify values
|
||||
kubectl get secret api7-credentials -n api7ee \
|
||||
-o jsonpath='{.data.admin-key}' | base64 -d
|
||||
```
|
||||
|
||||
### ADC sync job fails
|
||||
|
||||
```bash
|
||||
# Check job logs
|
||||
kubectl logs -n api7ee job/api7ee-demo-api7ee-demo-k8s-adc-sync
|
||||
|
||||
# Common issues:
|
||||
# - Wrong admin URL (check DNS resolution)
|
||||
# - Invalid admin key (verify key is correct)
|
||||
# - TLS certificate issues (use tlsSkipVerify: true for self-signed)
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [External Secrets Operator Documentation](https://external-secrets.io/)
|
||||
- [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/)
|
||||
- [API7 Enterprise Documentation](https://docs.api7.ai/enterprise/)
|
||||
379
web/docs/service-discovery.md
Normal file
379
web/docs/service-discovery.md
Normal file
@@ -0,0 +1,379 @@
|
||||
# Service Discovery
|
||||
|
||||
Learn how API7 Gateway dynamically discovers backend services using Kubernetes service discovery.
|
||||
|
||||
## Overview
|
||||
|
||||
Service Discovery enables API7 Gateway to automatically discover and track backend Pod endpoints instead of using static upstream node configuration. This provides a dynamic, scalable, and resilient architecture.
|
||||
|
||||
## Why Use Service Discovery?
|
||||
|
||||
### Traditional Static Configuration
|
||||
|
||||
```yaml
|
||||
upstream:
|
||||
nodes:
|
||||
- host: 10.0.1.23
|
||||
port: 8080
|
||||
weight: 100
|
||||
- host: 10.0.1.24
|
||||
port: 8080
|
||||
weight: 100
|
||||
```
|
||||
|
||||
**Problems:**
|
||||
- ❌ Manual updates required when Pods scale
|
||||
- ❌ Stale Pod IPs after deployments
|
||||
- ❌ No automatic health checks
|
||||
- ❌ Downtime during updates
|
||||
|
||||
### With Kubernetes Service Discovery
|
||||
|
||||
```yaml
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: my-service
|
||||
namespace_id: default
|
||||
```
|
||||
|
||||
**Benefits:**
|
||||
- ✅ Automatic Pod discovery
|
||||
- ✅ Real-time updates during scaling
|
||||
- ✅ Health checks built-in
|
||||
- ✅ Zero downtime deployments
|
||||
|
||||
## How It Works
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[API7 Gateway] -->|Query Endpoints| B[Kubernetes API]
|
||||
B -->|Return Pod IPs| A
|
||||
A -->|Route Traffic| C[Pod 1]
|
||||
A -->|Route Traffic| D[Pod 2]
|
||||
A -->|Route Traffic| E[Pod 3]
|
||||
|
||||
F[HPA/Deploy] -->|Scale/Update| B
|
||||
B -->|Notify| A
|
||||
```
|
||||
|
||||
1. **API7 Gateway** watches Kubernetes Endpoints API
|
||||
2. **Kubernetes API** returns list of healthy Pod IPs
|
||||
3. **Gateway** automatically adds/removes Pods from upstream
|
||||
4. **Traffic** is load-balanced across discovered Pods
|
||||
|
||||
## Configuration
|
||||
|
||||
### Enable in Helm Chart
|
||||
|
||||
Edit `values.yaml`:
|
||||
|
||||
```yaml
|
||||
api7:
|
||||
serviceDiscovery:
|
||||
enabled: true # Enable Kubernetes service discovery
|
||||
namespace: "" # Leave empty to use release namespace
|
||||
```
|
||||
|
||||
### ADC Configuration
|
||||
|
||||
The Helm chart automatically generates ADC configuration with service discovery:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: my-service
|
||||
upstream:
|
||||
name: my-upstream
|
||||
type: roundrobin
|
||||
discovery_type: kubernetes
|
||||
service_name: my-service # Kubernetes Service name
|
||||
namespace_id: default # Kubernetes namespace
|
||||
```
|
||||
|
||||
### RBAC Requirements
|
||||
|
||||
Service Discovery requires permissions to watch Kubernetes resources. These are automatically configured in `rbac-adc.yaml`:
|
||||
|
||||
```yaml
|
||||
rules:
|
||||
# Allow reading services and endpoints for service discovery
|
||||
- apiGroups: [""]
|
||||
resources: ["services", "endpoints"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
```
|
||||
|
||||
## Use Cases
|
||||
|
||||
### 1. Auto-Scaling Applications
|
||||
|
||||
When your application scales:
|
||||
|
||||
```bash
|
||||
# Scale deployment
|
||||
kubectl scale deployment my-app --replicas=10
|
||||
|
||||
# API7 Gateway automatically discovers new Pods
|
||||
# No configuration changes needed!
|
||||
```
|
||||
|
||||
### 2. Rolling Updates
|
||||
|
||||
During deployments:
|
||||
|
||||
```bash
|
||||
# Start rolling update
|
||||
kubectl rollout restart deployment my-app
|
||||
|
||||
# Gateway automatically:
|
||||
# 1. Discovers new Pods
|
||||
# 2. Adds them to upstream
|
||||
# 3. Removes old Pods
|
||||
# 4. Zero downtime!
|
||||
```
|
||||
|
||||
### 3. Multi-Service Architecture
|
||||
|
||||
Discover multiple backend services:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
- name: web-frontend
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: web-frontend
|
||||
namespace_id: production
|
||||
|
||||
- name: api-backend
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: api-backend
|
||||
namespace_id: production
|
||||
|
||||
- name: admin-api
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: admin-api
|
||||
namespace_id: admin
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Cross-Namespace Discovery
|
||||
|
||||
Discover services in different namespaces:
|
||||
|
||||
```yaml
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: shared-service
|
||||
namespace_id: shared-services # Different namespace
|
||||
```
|
||||
|
||||
!!! warning "RBAC Permissions"
|
||||
Ensure the ServiceAccount has permissions to watch Endpoints in the target namespace.
|
||||
|
||||
### Load Balancing Algorithms
|
||||
|
||||
Combine service discovery with load balancing:
|
||||
|
||||
```yaml
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: my-service
|
||||
namespace_id: default
|
||||
type: roundrobin # or chash, least_conn, ewma
|
||||
```
|
||||
|
||||
**Available algorithms:**
|
||||
- `roundrobin`: Distribute requests evenly (default)
|
||||
- `chash`: Consistent hashing (sticky sessions)
|
||||
- `least_conn`: Send to Pod with fewest connections
|
||||
- `ewma`: Exponentially weighted moving average
|
||||
|
||||
### Health Checks
|
||||
|
||||
API7 Gateway respects Kubernetes readiness probes:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: my-app
|
||||
spec:
|
||||
containers:
|
||||
- name: app
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8080
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 3
|
||||
```
|
||||
|
||||
Only Pods with passing readiness checks receive traffic.
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Check Discovered Endpoints
|
||||
|
||||
View upstream nodes in API7 Dashboard:
|
||||
|
||||
```bash
|
||||
# Port-forward to dashboard
|
||||
kubectl port-forward -n api7ee svc/api7ee3-0-1759339083-dashboard 7443:7443
|
||||
|
||||
# Open browser
|
||||
open https://localhost:7443
|
||||
```
|
||||
|
||||
Navigate to: **Gateway Groups → Upstreams → View Nodes**
|
||||
|
||||
### Debug Service Discovery
|
||||
|
||||
Check ADC sync logs:
|
||||
|
||||
```bash
|
||||
# View ADC sync job logs
|
||||
kubectl logs -n api7ee job/api7ee-demo-api7ee-demo-k8s-adc-sync
|
||||
|
||||
# Look for service discovery messages
|
||||
# [INFO] Discovered 3 endpoints for service my-service
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Endpoints Discovered
|
||||
|
||||
**Symptoms:** Gateway shows 0 upstream nodes
|
||||
|
||||
**Causes:**
|
||||
1. Service doesn't exist
|
||||
2. No Pods match Service selector
|
||||
3. All Pods are not ready
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check Service exists
|
||||
kubectl get service my-service -n default
|
||||
|
||||
# Check Endpoints
|
||||
kubectl get endpoints my-service -n default
|
||||
|
||||
# Check Pod readiness
|
||||
kubectl get pods -l app=my-service -n default
|
||||
```
|
||||
|
||||
### RBAC Permission Denied
|
||||
|
||||
**Error:** `forbidden: User "system:serviceaccount:..." cannot list endpoints`
|
||||
|
||||
**Solution:**
|
||||
Verify RBAC permissions:
|
||||
|
||||
```bash
|
||||
# Check Role binding
|
||||
kubectl get rolebinding -n api7ee | grep adc
|
||||
|
||||
# Describe permissions
|
||||
kubectl describe role api7ee-demo-api7ee-demo-k8s-adc -n api7ee
|
||||
```
|
||||
|
||||
### Stale Endpoints
|
||||
|
||||
**Symptoms:** Traffic sent to terminated Pods
|
||||
|
||||
**Causes:** Kubernetes API not updating fast enough
|
||||
|
||||
**Solution:**
|
||||
Check Kubernetes API health:
|
||||
|
||||
```bash
|
||||
# Verify API server is responsive
|
||||
kubectl get --raw /healthz
|
||||
|
||||
# Check endpoint update timestamps
|
||||
kubectl get endpoints my-service -o yaml | grep -i time
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Use Service Discovery for All Dynamic Workloads
|
||||
|
||||
✅ **Do:**
|
||||
```yaml
|
||||
upstream:
|
||||
discovery_type: kubernetes
|
||||
service_name: my-dynamic-app
|
||||
```
|
||||
|
||||
❌ **Don't:**
|
||||
```yaml
|
||||
upstream:
|
||||
nodes:
|
||||
- host: pod-ip # Brittle, breaks on restart
|
||||
```
|
||||
|
||||
### 2. Configure Readiness Probes
|
||||
|
||||
Always configure readiness probes on Pods:
|
||||
|
||||
```yaml
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /ready
|
||||
port: 8080
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
```
|
||||
|
||||
### 3. Use Namespaces for Isolation
|
||||
|
||||
Separate environments by namespace:
|
||||
|
||||
```yaml
|
||||
# Production
|
||||
namespace_id: production
|
||||
|
||||
# Staging
|
||||
namespace_id: staging
|
||||
|
||||
# Development
|
||||
namespace_id: development
|
||||
```
|
||||
|
||||
### 4. Monitor Discovery Performance
|
||||
|
||||
Set up alerts for:
|
||||
- Number of discovered endpoints
|
||||
- Service discovery latency
|
||||
- RBAC permission errors
|
||||
|
||||
### 5. Test Failover Scenarios
|
||||
|
||||
Regularly test:
|
||||
- Pod termination
|
||||
- Rolling updates
|
||||
- Scale up/down events
|
||||
|
||||
## Comparison: Static vs Service Discovery
|
||||
|
||||
| Feature | Static Nodes | Service Discovery |
|
||||
|---------|--------------|-------------------|
|
||||
| Setup | Manual | Automatic |
|
||||
| Scaling | Manual updates | Automatic |
|
||||
| Health checks | Manual | Built-in |
|
||||
| Deployments | Downtime risk | Zero downtime |
|
||||
| Maintenance | High | Low |
|
||||
| Flexibility | Low | High |
|
||||
| Production-ready | ❌ | ✅ |
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Configure Secret Management](secret-management.md)
|
||||
- [Set up Ingress Routing](kubernetes-resources.md#ingress)
|
||||
- [Monitor Gateway Metrics](troubleshooting.md#monitoring)
|
||||
|
||||
## References
|
||||
|
||||
- [API7 Service Discovery Documentation](https://docs.api7.ai/enterprise/3.2.14.6/best-practice/service-discovery/)
|
||||
- [Kubernetes Endpoints API](https://kubernetes.io/docs/concepts/services-networking/service/#endpoints)
|
||||
- [Kubernetes Service Discovery](https://kubernetes.io/docs/concepts/services-networking/service/)
|
||||
457
web/docs/swagger-documentation.md
Normal file
457
web/docs/swagger-documentation.md
Normal file
@@ -0,0 +1,457 @@
|
||||
---
|
||||
title: Swagger & OpenAPI Documentation
|
||||
description: Complete guide to API documentation with Swagger UI, ReDoc, and OpenAPI
|
||||
---
|
||||
|
||||
# Swagger & OpenAPI Documentation
|
||||
|
||||
Complete documentation for the API7 Enterprise Demo API with interactive Swagger UI.
|
||||
|
||||
## Overview
|
||||
|
||||
The API provides comprehensive documentation through multiple formats:
|
||||
|
||||
- **Swagger UI** - Interactive API documentation and testing
|
||||
- **ReDoc** - Clean, responsive API documentation
|
||||
- **OpenAPI JSON** - Machine-readable API specification
|
||||
|
||||
## Access Documentation
|
||||
|
||||
### Via Web Interface
|
||||
|
||||
1. Navigate to [https://commandware.it/](https://commandware.it/)
|
||||
2. Click **📚 API Docs** in the navigation menu
|
||||
3. Choose your preferred format:
|
||||
- **Swagger UI** - For interactive testing
|
||||
- **ReDoc** - For reading documentation
|
||||
- **OpenAPI JSON** - For programmatic access
|
||||
- **API Root** - For API metadata
|
||||
|
||||
### Direct Links
|
||||
|
||||
| Format | URL | Description |
|
||||
|--------|-----|-------------|
|
||||
| **Swagger UI** | [/api/docs](https://commandware.it/api/docs) | Interactive documentation |
|
||||
| **ReDoc** | [/api/redoc](https://commandware.it/api/redoc) | Clean documentation style |
|
||||
| **OpenAPI JSON** | [/api/openapi.json](https://commandware.it/api/openapi.json) | Raw OpenAPI schema |
|
||||
| **API Root** | [/api/](https://commandware.it/api/) | API information |
|
||||
|
||||
---
|
||||
|
||||
## API Overview
|
||||
|
||||
### Architecture
|
||||
|
||||
```
|
||||
Client → Ingress (NGINX) → API7 Gateway → Backend API
|
||||
↓
|
||||
• Rate Limiting
|
||||
• CORS
|
||||
• Proxy Rewrite (/api → /)
|
||||
• Service Discovery
|
||||
```
|
||||
|
||||
### Features
|
||||
|
||||
- ✅ **CRUD Operations** - Items and Users management
|
||||
- ✅ **LLM Integration** - AI-powered chat with OpenAI-compatible API
|
||||
- ✅ **Health Checks** - Kubernetes-ready liveness and readiness probes
|
||||
- ✅ **Rate Limiting** - Standard (100 req/min) and AI-based (100 tokens/min)
|
||||
- ✅ **CORS** - Cross-origin resource sharing enabled
|
||||
- ✅ **Proxy Rewrite** - Automatic /api prefix removal by API7 Gateway
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
| Endpoint Type | Limit | Window | Strategy |
|
||||
|---------------|-------|--------|----------|
|
||||
| Standard API (`/items`, `/users`) | 100 requests | 60 seconds | Per IP address |
|
||||
| LLM API (`/llm/*`) | 100 tokens | 60 seconds | AI-based (total tokens) |
|
||||
|
||||
---
|
||||
|
||||
## Endpoint Groups
|
||||
|
||||
### 🏠 Root
|
||||
|
||||
#### `GET /`
|
||||
|
||||
API information with navigation links.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"message": "Welcome to API7 Enterprise Demo API",
|
||||
"version": "1.0.0",
|
||||
"documentation": {
|
||||
"swagger": "/docs",
|
||||
"redoc": "/redoc",
|
||||
"openapi": "/openapi.json"
|
||||
},
|
||||
"endpoints": {
|
||||
"items": "/items",
|
||||
"users": "/users",
|
||||
"llm": "/llm"
|
||||
},
|
||||
"timestamp": "2025-10-09T16:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 💚 Health
|
||||
|
||||
#### `GET /health`
|
||||
|
||||
Kubernetes liveness probe endpoint.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "api",
|
||||
"timestamp": "2025-10-09T16:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /ready`
|
||||
|
||||
Kubernetes readiness probe endpoint.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "ready",
|
||||
"service": "api",
|
||||
"timestamp": "2025-10-09T16:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📦 Items (CRUD)
|
||||
|
||||
Full CRUD operations for items inventory management.
|
||||
|
||||
#### `GET /items`
|
||||
|
||||
List all items in inventory.
|
||||
|
||||
**Rate Limit:** 100 requests per 60 seconds per IP
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
[
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Laptop",
|
||||
"description": "High-performance laptop",
|
||||
"price": 999.99,
|
||||
"in_stock": true
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### `GET /items/{id}`
|
||||
|
||||
Get a specific item by ID.
|
||||
|
||||
**Parameters:**
|
||||
- `id` (path, integer, required) - Item ID
|
||||
|
||||
**Responses:**
|
||||
- `200 OK` - Item details
|
||||
- `404 Not Found` - Item not found
|
||||
|
||||
#### `POST /items`
|
||||
|
||||
Create a new item.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"name": "Laptop",
|
||||
"description": "High-performance laptop",
|
||||
"price": 999.99,
|
||||
"in_stock": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created`
|
||||
|
||||
#### `PUT /items/{id}`
|
||||
|
||||
Update an existing item.
|
||||
|
||||
**Parameters:**
|
||||
- `id` (path, integer, required) - Item ID
|
||||
|
||||
**Response:** `200 OK`
|
||||
|
||||
#### `DELETE /items/{id}`
|
||||
|
||||
Delete an item.
|
||||
|
||||
**Parameters:**
|
||||
- `id` (path, integer, required) - Item ID
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"message": "Item deleted successfully",
|
||||
"id": 1
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 👥 Users
|
||||
|
||||
User management operations.
|
||||
|
||||
#### `GET /users`
|
||||
|
||||
List all users.
|
||||
|
||||
**Rate Limit:** 100 requests per 60 seconds per IP
|
||||
|
||||
#### `GET /users/{id}`
|
||||
|
||||
Get a specific user by ID.
|
||||
|
||||
#### `POST /users`
|
||||
|
||||
Create a new user.
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"username": "john_doe",
|
||||
"email": "john@example.com",
|
||||
"active": true
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `201 Created`
|
||||
|
||||
---
|
||||
|
||||
### 🤖 LLM (AI Integration)
|
||||
|
||||
AI-powered chat endpoints with OpenAI-compatible API.
|
||||
|
||||
#### `POST /llm/chat`
|
||||
|
||||
Chat completion with AI rate limiting.
|
||||
|
||||
**Rate Limit:** 100 tokens per 60 seconds (AI-based)
|
||||
|
||||
**Request Body:**
|
||||
```json
|
||||
{
|
||||
"prompt": "What is API7 Enterprise?",
|
||||
"max_tokens": 150,
|
||||
"temperature": 0.7,
|
||||
"model": "videogame-expert"
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"response": "API7 Enterprise is...",
|
||||
"tokens_used": 45,
|
||||
"model": "videogame-expert",
|
||||
"timestamp": "2025-10-09T16:00:00"
|
||||
}
|
||||
```
|
||||
|
||||
**Errors:**
|
||||
- `429 Too Many Requests` - Rate limit exceeded (100 tokens per 60 seconds)
|
||||
- `500 Internal Server Error` - LLM service error
|
||||
|
||||
#### `GET /llm/models`
|
||||
|
||||
List available LLM models.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"models": [
|
||||
{
|
||||
"id": "videogame-expert",
|
||||
"name": "Videogame Expert",
|
||||
"max_tokens": 4096,
|
||||
"provider": "Open WebUI",
|
||||
"description": "Specialized model for videogame-related questions"
|
||||
}
|
||||
],
|
||||
"default_model": "videogame-expert",
|
||||
"provider": "Open WebUI"
|
||||
}
|
||||
```
|
||||
|
||||
#### `GET /llm/health`
|
||||
|
||||
LLM service health check.
|
||||
|
||||
**Response:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "llm-api",
|
||||
"provider": "Open WebUI",
|
||||
"endpoint": "http://localhost/api",
|
||||
"default_model": "videogame-expert",
|
||||
"rate_limit": {
|
||||
"enabled": true,
|
||||
"limit": 100,
|
||||
"window": "60 seconds",
|
||||
"strategy": "total_tokens",
|
||||
"managed_by": "API7 Gateway (ai-rate-limiting plugin)"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Using Swagger UI
|
||||
|
||||
### Interactive Testing
|
||||
|
||||
1. Navigate to [/api/docs](https://commandware.it/api/docs)
|
||||
2. Browse available endpoints organized by groups
|
||||
3. Click **"Try it out"** on any endpoint
|
||||
4. Fill in parameters and request body
|
||||
5. Click **"Execute"** to test the API
|
||||
6. View response with status code, headers, and body
|
||||
|
||||
### Example: Testing Items API
|
||||
|
||||
1. Go to **Items** section
|
||||
2. Click on `GET /items`
|
||||
3. Click **"Try it out"**
|
||||
4. Click **"Execute"**
|
||||
5. View the response with all items
|
||||
|
||||
### Example: Creating an Item
|
||||
|
||||
1. Go to **Items** section
|
||||
2. Click on `POST /items`
|
||||
3. Click **"Try it out"**
|
||||
4. Modify the request body:
|
||||
```json
|
||||
{
|
||||
"name": "New Laptop",
|
||||
"description": "Gaming laptop",
|
||||
"price": 1499.99,
|
||||
"in_stock": true
|
||||
}
|
||||
```
|
||||
5. Click **"Execute"**
|
||||
6. View `201 Created` response with the new item
|
||||
|
||||
---
|
||||
|
||||
## Using ReDoc
|
||||
|
||||
### Clean Documentation
|
||||
|
||||
1. Navigate to [/api/redoc](https://commandware.it/api/redoc)
|
||||
2. Browse the clean, three-column layout:
|
||||
- **Left**: Navigation menu
|
||||
- **Center**: Endpoint details
|
||||
- **Right**: Code examples
|
||||
3. Search for specific endpoints
|
||||
4. Download OpenAPI spec
|
||||
|
||||
### Features
|
||||
|
||||
- ✅ Responsive design
|
||||
- ✅ Search functionality
|
||||
- ✅ Code examples in multiple languages
|
||||
- ✅ Print-friendly
|
||||
- ✅ Mobile-friendly
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI Specification
|
||||
|
||||
### Download Schema
|
||||
|
||||
Access the raw OpenAPI 3.0 specification:
|
||||
|
||||
```bash
|
||||
curl https://commandware.it/api/openapi.json > openapi.json
|
||||
```
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Code Generation** - Generate client SDKs
|
||||
- **API Testing** - Import into Postman/Insomnia
|
||||
- **Validation** - Validate requests/responses
|
||||
- **Documentation** - Generate custom documentation
|
||||
|
||||
### Tools Integration
|
||||
|
||||
**Postman:**
|
||||
```bash
|
||||
# Import OpenAPI spec
|
||||
File → Import → https://commandware.it/api/openapi.json
|
||||
```
|
||||
|
||||
**Insomnia:**
|
||||
```bash
|
||||
# Import OpenAPI spec
|
||||
Dashboard → Import/Export → https://commandware.it/api/openapi.json
|
||||
```
|
||||
|
||||
**OpenAPI Generator:**
|
||||
```bash
|
||||
# Generate Python client
|
||||
openapi-generator-cli generate \
|
||||
-i https://commandware.it/api/openapi.json \
|
||||
-g python \
|
||||
-o ./api-client
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Changes
|
||||
|
||||
### Enhanced Documentation
|
||||
|
||||
All endpoints now include:
|
||||
|
||||
- ✅ Detailed summaries and descriptions
|
||||
- ✅ Parameter explanations with examples
|
||||
- ✅ Response descriptions with status codes
|
||||
- ✅ Error responses (404, 429, 500)
|
||||
- ✅ Rate limiting information
|
||||
- ✅ Request/response examples
|
||||
|
||||
### Model Enhancements
|
||||
|
||||
All Pydantic models include:
|
||||
|
||||
- ✅ Field descriptions
|
||||
- ✅ Validation rules (min_length, gt, ge, le)
|
||||
- ✅ Schema examples
|
||||
- ✅ Default values
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
- [Getting Started](../getting-started.md) - Set up the development environment
|
||||
- [API7 Configuration](../api7-configuration.md) - Configure API7 Gateway
|
||||
- [Ingress & Routing](../ingress-routing.md) - Configure traffic routing
|
||||
- [Troubleshooting](../troubleshooting.md) - Common issues and solutions
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
||||
- [OpenAPI Specification](https://swagger.io/specification/)
|
||||
- [Swagger UI](https://swagger.io/tools/swagger-ui/)
|
||||
- [ReDoc](https://redocly.com/redoc/)
|
||||
236
web/main.py
236
web/main.py
@@ -1,137 +1,133 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi import FastAPI, Request, HTTPException
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
import uvicorn
|
||||
import os
|
||||
import subprocess
|
||||
import httpx
|
||||
|
||||
app = FastAPI(title="Web Demo Application")
|
||||
|
||||
# Build MkDocs documentation on startup
|
||||
def build_docs():
|
||||
docs_dir = os.path.join(os.path.dirname(__file__), "docs")
|
||||
site_dir = os.path.join(os.path.dirname(__file__), "site")
|
||||
# Get the directory where this script is located
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
if os.path.exists(docs_dir):
|
||||
# API Configuration - can be set via environment variable
|
||||
API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8001")
|
||||
|
||||
# Mount static files
|
||||
static_dir = os.path.join(BASE_DIR, "static")
|
||||
if os.path.exists(static_dir):
|
||||
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
||||
|
||||
# Setup templates
|
||||
templates_dir = os.path.join(BASE_DIR, "templates")
|
||||
templates = Jinja2Templates(directory=templates_dir)
|
||||
|
||||
# HTTP client for API calls
|
||||
async def api_request(method: str, endpoint: str, **kwargs):
|
||||
"""Make a request to the API backend"""
|
||||
url = f"{API_BASE_URL}{endpoint}"
|
||||
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||
try:
|
||||
subprocess.run(
|
||||
["mkdocs", "build", "-f", os.path.join(docs_dir, "mkdocs.yml"), "-d", site_dir],
|
||||
check=True,
|
||||
capture_output=True
|
||||
)
|
||||
print(f"✓ Documentation built successfully at {site_dir}")
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"✗ Failed to build documentation: {e.stderr.decode()}")
|
||||
return False
|
||||
except FileNotFoundError:
|
||||
print("✗ MkDocs not installed. Install with: pip install mkdocs mkdocs-material")
|
||||
return False
|
||||
return False
|
||||
|
||||
# Build docs on startup
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
build_docs()
|
||||
|
||||
# Mount static documentation site at /docs
|
||||
site_dir = os.path.join(os.path.dirname(__file__), "site")
|
||||
if os.path.exists(site_dir):
|
||||
app.mount("/docs", StaticFiles(directory=site_dir, html=True), name="docs")
|
||||
|
||||
# Simple HTML template inline
|
||||
HTML_TEMPLATE = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Web Demo</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.container {
|
||||
background-color: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
h1 {
|
||||
color: #333;
|
||||
border-bottom: 2px solid #4CAF50;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.info-box {
|
||||
background-color: #e8f5e9;
|
||||
padding: 15px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #4CAF50;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.metric {
|
||||
display: inline-block;
|
||||
margin: 10px 20px 10px 0;
|
||||
padding: 10px 20px;
|
||||
background-color: #2196F3;
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.doc-link {
|
||||
display: inline-block;
|
||||
margin: 20px 10px 0 0;
|
||||
padding: 12px 24px;
|
||||
background-color: #673AB7;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
.doc-link:hover {
|
||||
background-color: #512DA8;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Welcome to Web Demo Application</h1>
|
||||
<div class="info-box">
|
||||
<h2>Application Information</h2>
|
||||
<p><strong>Service:</strong> Web Frontend</p>
|
||||
<p><strong>Status:</strong> ✓ Running</p>
|
||||
<p><strong>Version:</strong> 1.0.0</p>
|
||||
</div>
|
||||
<h2>Metrics Dashboard</h2>
|
||||
<div>
|
||||
<span class="metric">Requests: 1,234</span>
|
||||
<span class="metric">Uptime: 99.9%</span>
|
||||
<span class="metric">Users: 567</span>
|
||||
</div>
|
||||
<div class="info-box">
|
||||
<h3>About</h3>
|
||||
<p>This is a demo FastAPI web application serving HTML content.
|
||||
It demonstrates a simple web interface with metrics and information display.</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/docs/" class="doc-link">📚 View Documentation</a>
|
||||
<a href="/health" class="doc-link">🏥 Health Check</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
response = await client.request(method, url, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise HTTPException(status_code=e.response.status_code, detail=str(e))
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"API request failed: {str(e)}")
|
||||
|
||||
# ===== ROUTES - HTML Pages =====
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def root():
|
||||
"""Serve the main webpage"""
|
||||
return HTML_TEMPLATE
|
||||
async def home(request: Request):
|
||||
"""Serve the home page"""
|
||||
return templates.TemplateResponse("index.html", {"request": request})
|
||||
|
||||
@app.get("/items", response_class=HTMLResponse)
|
||||
async def items_page(request: Request):
|
||||
"""Serve the items page"""
|
||||
return templates.TemplateResponse("items.html", {"request": request})
|
||||
|
||||
@app.get("/users", response_class=HTMLResponse)
|
||||
async def users_page(request: Request):
|
||||
"""Serve the users page"""
|
||||
return templates.TemplateResponse("users.html", {"request": request})
|
||||
|
||||
@app.get("/llm", response_class=HTMLResponse)
|
||||
async def llm_page(request: Request):
|
||||
"""Serve the LLM chat page"""
|
||||
return templates.TemplateResponse("llm.html", {"request": request})
|
||||
|
||||
# ===== API PROXY ENDPOINTS =====
|
||||
@app.get("/api/items")
|
||||
async def proxy_get_items():
|
||||
"""Proxy GET /items to API backend"""
|
||||
return await api_request("GET", "/items")
|
||||
|
||||
@app.get("/api/items/{item_id}")
|
||||
async def proxy_get_item(item_id: int):
|
||||
"""Proxy GET /items/{id} to API backend"""
|
||||
return await api_request("GET", f"/items/{item_id}")
|
||||
|
||||
@app.get("/api/users")
|
||||
async def proxy_get_users():
|
||||
"""Proxy GET /users to API backend"""
|
||||
return await api_request("GET", "/users")
|
||||
|
||||
@app.get("/api/users/{user_id}")
|
||||
async def proxy_get_user(user_id: int):
|
||||
"""Proxy GET /users/{id} to API backend"""
|
||||
return await api_request("GET", f"/users/{user_id}")
|
||||
|
||||
@app.post("/api/llm/chat")
|
||||
async def proxy_llm_chat(request: Request):
|
||||
"""Proxy POST /llm/chat to API backend"""
|
||||
body = await request.json()
|
||||
return await api_request("POST", "/llm/chat", json=body)
|
||||
|
||||
@app.get("/api/llm/models")
|
||||
async def proxy_llm_models():
|
||||
"""Proxy GET /llm/models to API backend"""
|
||||
return await api_request("GET", "/llm/models")
|
||||
|
||||
@app.get("/api/llm/health")
|
||||
async def proxy_llm_health():
|
||||
"""Proxy GET /llm/health to API backend"""
|
||||
return await api_request("GET", "/llm/health")
|
||||
|
||||
# ===== WEB HEALTH CHECK =====
|
||||
@app.get("/health")
|
||||
async def health():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy", "service": "web"}
|
||||
# Try to connect to API backend
|
||||
api_status = "unknown"
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||
response = await client.get(f"{API_BASE_URL}/health")
|
||||
if response.status_code == 200:
|
||||
api_status = "healthy"
|
||||
else:
|
||||
api_status = "unhealthy"
|
||||
except:
|
||||
api_status = "unreachable"
|
||||
|
||||
return {
|
||||
"status": "healthy",
|
||||
"service": "web",
|
||||
"version": "1.0.0",
|
||||
"api_backend": API_BASE_URL,
|
||||
"api_status": api_status
|
||||
}
|
||||
|
||||
# ===== CONFIG ENDPOINT =====
|
||||
@app.get("/api/config")
|
||||
async def get_config():
|
||||
"""Get current API configuration"""
|
||||
return {
|
||||
"api_base_url": API_BASE_URL
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"Starting Web service")
|
||||
print(f"API Backend: {API_BASE_URL}")
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
site_name: API7 Enterprise Demo Documentation
|
||||
site_description: Complete documentation for API7 Enterprise Gateway demo deployment
|
||||
site_author: CommandWare
|
||||
site_url: https://demo.commandware.it/docs
|
||||
site_url: https://commandware.it/docs
|
||||
docs_dir: docs
|
||||
|
||||
theme:
|
||||
name: material
|
||||
@@ -23,9 +24,11 @@ theme:
|
||||
- navigation.sections
|
||||
- navigation.expand
|
||||
- navigation.top
|
||||
- navigation.instant
|
||||
- search.suggest
|
||||
- search.highlight
|
||||
- content.code.copy
|
||||
- content.code.annotate
|
||||
icon:
|
||||
repo: fontawesome/brands/git-alt
|
||||
|
||||
@@ -40,15 +43,27 @@ nav:
|
||||
- Kubernetes Resources: kubernetes-resources.md
|
||||
- Configuration:
|
||||
- API7 Gateway: api7-configuration.md
|
||||
- Ingress & Routing: ingress-routing.md
|
||||
- Service Discovery: service-discovery.md
|
||||
- Secret Management: secret-management.md
|
||||
- ArgoCD Values Guide: argocd-values-guide.md
|
||||
- CI/CD Pipeline: cicd-pipeline.md
|
||||
- API Documentation:
|
||||
- Swagger & OpenAPI: swagger-documentation.md
|
||||
- Troubleshooting: troubleshooting.md
|
||||
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- pymdownx.details
|
||||
- pymdownx.superfences
|
||||
- pymdownx.superfences:
|
||||
custom_fences:
|
||||
- name: mermaid
|
||||
class: mermaid
|
||||
format: !!python/name:pymdownx.superfences.fence_code_format
|
||||
- pymdownx.highlight:
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
- pymdownx.tabbed:
|
||||
@@ -58,15 +73,24 @@ markdown_extensions:
|
||||
- md_in_html
|
||||
- toc:
|
||||
permalink: true
|
||||
- def_list
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:material.extensions.emoji.twemoji
|
||||
emoji_generator: !!python/name:material.extensions.emoji.to_svg
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- tags
|
||||
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/git-alt
|
||||
link: https://git.commandware.com/demos/api7-demo
|
||||
name: Git Repository
|
||||
version:
|
||||
provider: mike
|
||||
generator: false
|
||||
|
||||
copyright: Copyright © 2025 CommandWare
|
||||
copyright: Copyright © 2025 CommandWare | Powered by API7 Enterprise
|
||||
@@ -1,4 +1,8 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
fastapi==0.109.0
|
||||
uvicorn==0.27.0
|
||||
jinja2==3.1.3
|
||||
python-multipart==0.0.6
|
||||
httpx==0.26.0
|
||||
mkdocs==1.5.3
|
||||
mkdocs-material==9.5.3
|
||||
pymdown-extensions==10.7
|
||||
|
||||
503
web/static/css/style.css
Normal file
503
web/static/css/style.css
Normal file
@@ -0,0 +1,503 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue",
|
||||
Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 1rem 0;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.nav-brand h2 {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
|
||||
.nav-menu li {
|
||||
display: inline-block;
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
.nav-menu a {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.nav-menu a:hover {
|
||||
color: #3498db;
|
||||
}
|
||||
|
||||
/* Main content */
|
||||
main {
|
||||
min-height: calc(100vh - 200px);
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
/* Hero section */
|
||||
.hero {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Cards grid */
|
||||
.cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
transition:
|
||||
transform 0.3s,
|
||||
box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin-bottom: 1rem;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 10px 20px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #667eea;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #5568d3;
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: 5px 15px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
/* Info section */
|
||||
.info-section {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 40px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.features-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.features-list li {
|
||||
padding: 10px 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Stats */
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.stat-box h3 {
|
||||
font-size: 2.5rem;
|
||||
color: #667eea;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Items grid */
|
||||
.items-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.item-card {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.item-card.out-of-stock {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.item-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.item-description {
|
||||
color: #666;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #27ae60;
|
||||
}
|
||||
|
||||
/* Table */
|
||||
.table-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
padding: 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Badges */
|
||||
.badge {
|
||||
padding: 5px 10px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.badge-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.badge-danger {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
/* Chat */
|
||||
.chat-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.chat-messages {
|
||||
height: 400px;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-message,
|
||||
.assistant-message,
|
||||
.system-message {
|
||||
padding: 10px 15px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 10px;
|
||||
max-width: 80%;
|
||||
}
|
||||
|
||||
.user-message {
|
||||
background-color: #667eea;
|
||||
color: white;
|
||||
margin-left: auto;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.assistant-message {
|
||||
background-color: white;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.system-message {
|
||||
background-color: #e3f2fd;
|
||||
color: #1976d2;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.chat-input-container textarea {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.chat-info {
|
||||
text-align: center;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* Page header */
|
||||
.page-header {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.page-header h1 {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 20px 0;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 5% auto;
|
||||
padding: 0;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
|
||||
max-width: 600px;
|
||||
animation: modalFadeIn 0.3s;
|
||||
}
|
||||
|
||||
@keyframes modalFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 20px 30px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h2 {
|
||||
margin: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.close:hover,
|
||||
.close:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 30px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
width: 100%;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.form-control:focus {
|
||||
outline: none;
|
||||
border-color: #667eea;
|
||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
||||
}
|
||||
|
||||
.form-hint {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
color: #666;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-hint code {
|
||||
background-color: #f5f5f5;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: "Courier New", monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.config-info {
|
||||
background-color: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
border-left: 4px solid #667eea;
|
||||
}
|
||||
|
||||
.config-info strong {
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background-color: #5a6268;
|
||||
}
|
||||
|
||||
/* Utility */
|
||||
.loading {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #f44336;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
}
|
||||
123
web/static/js/app.js
Normal file
123
web/static/js/app.js
Normal file
@@ -0,0 +1,123 @@
|
||||
// Global API configuration
|
||||
const DEFAULT_API_BASE = "/api";
|
||||
const API_BASE_KEY = "api_base_url";
|
||||
|
||||
// Get API base URL from localStorage or use default
|
||||
function getApiBaseUrl() {
|
||||
return localStorage.getItem(API_BASE_KEY) || DEFAULT_API_BASE;
|
||||
}
|
||||
|
||||
// Set API base URL
|
||||
function setApiBaseUrl(url) {
|
||||
localStorage.setItem(API_BASE_KEY, url);
|
||||
}
|
||||
|
||||
// Export for global access
|
||||
window.API_BASE = getApiBaseUrl();
|
||||
|
||||
// API Configuration Modal Functions
|
||||
function openApiConfig(event) {
|
||||
if (event) event.preventDefault();
|
||||
|
||||
const modal = document.getElementById("api-config-modal");
|
||||
const input = document.getElementById("api-base-url");
|
||||
const currentUrl = document.getElementById("current-api-url");
|
||||
|
||||
input.value = getApiBaseUrl();
|
||||
currentUrl.textContent = getApiBaseUrl();
|
||||
modal.style.display = "block";
|
||||
}
|
||||
|
||||
function closeApiConfig() {
|
||||
const modal = document.getElementById("api-config-modal");
|
||||
modal.style.display = "none";
|
||||
}
|
||||
|
||||
function saveApiConfig() {
|
||||
const input = document.getElementById("api-base-url");
|
||||
const url = input.value.trim();
|
||||
|
||||
if (!url) {
|
||||
alert("Please enter a valid API base URL");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove trailing slash if present
|
||||
const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url;
|
||||
|
||||
setApiBaseUrl(cleanUrl);
|
||||
window.API_BASE = cleanUrl;
|
||||
|
||||
showNotification("API configuration saved. Reloading page...", "success");
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function resetApiConfig() {
|
||||
if (confirm("Reset API configuration to default (/api)?")) {
|
||||
setApiBaseUrl(DEFAULT_API_BASE);
|
||||
window.API_BASE = DEFAULT_API_BASE;
|
||||
|
||||
showNotification("API configuration reset. Reloading page...", "success");
|
||||
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
// Close modal when clicking outside
|
||||
window.onclick = function (event) {
|
||||
const modal = document.getElementById("api-config-modal");
|
||||
if (event.target === modal) {
|
||||
closeApiConfig();
|
||||
}
|
||||
};
|
||||
|
||||
// Utility functions
|
||||
function showNotification(message, type = "info") {
|
||||
console.log(`[${type.toUpperCase()}] ${message}`);
|
||||
// Could be extended with toast notifications
|
||||
}
|
||||
|
||||
function formatDate(dateString) {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString() + " " + date.toLocaleTimeString();
|
||||
}
|
||||
|
||||
function formatPrice(price) {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "USD",
|
||||
}).format(price);
|
||||
}
|
||||
|
||||
// API call wrapper
|
||||
async function apiCall(endpoint, options = {}) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options.headers,
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
showNotification(error.message, "error");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Export for use in templates
|
||||
window.apiCall = apiCall;
|
||||
window.showNotification = showNotification;
|
||||
window.formatDate = formatDate;
|
||||
window.formatPrice = formatPrice;
|
||||
147
web/templates/base.html
Normal file
147
web/templates/base.html
Normal file
@@ -0,0 +1,147 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{% block title %}API Demo{% endblock %}</title>
|
||||
<link rel="stylesheet" href="/static/css/style.css" />
|
||||
<style>
|
||||
/* Dropdown Menu Styles */
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #2c3e50;
|
||||
min-width: 200px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.3);
|
||||
z-index: 1000;
|
||||
border-radius: 4px;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.dropdown-content a {
|
||||
color: white;
|
||||
padding: 12px 16px;
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-content a:hover {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdown-content {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropbtn {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar">
|
||||
<div class="container">
|
||||
<div class="nav-brand">
|
||||
<h2>🚀 API7EE Demo</h2>
|
||||
</div>
|
||||
<ul class="nav-menu">
|
||||
<li><a href="/" title="Home page">🏠 Home</a></li>
|
||||
<li><a href="/items" title="Manage items inventory">📦 Items</a></li>
|
||||
<li><a href="/users" title="Manage users">👥 Users</a></li>
|
||||
<li><a href="/llm" title="AI-powered chat">🤖 LLM Chat</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropbtn" title="API Documentation">📚 API Docs ▾</a>
|
||||
<div class="dropdown-content">
|
||||
<a href="/api/" target="_blank" title="API root endpoint">API Root</a>
|
||||
<a href="/api/docs" target="_blank" title="Swagger UI documentation">Swagger UI</a>
|
||||
<a href="/api/redoc" target="_blank" title="ReDoc documentation">ReDoc</a>
|
||||
<a href="/api/openapi.json" target="_blank" title="OpenAPI JSON schema">OpenAPI JSON</a>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" onclick="openApiConfig(event)" title="Configure API settings">⚙️ Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- API Configuration Modal -->
|
||||
<div id="api-config-modal" class="modal">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2>⚙️ API Configuration</h2>
|
||||
<span class="close" onclick="closeApiConfig()"
|
||||
>×</span
|
||||
>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Configure the base URL for API requests:</p>
|
||||
<div class="form-group">
|
||||
<label for="api-base-url">API Base URL:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="api-base-url"
|
||||
placeholder="https://commandware.it/api"
|
||||
class="form-control"
|
||||
/>
|
||||
<small class="form-hint">
|
||||
Examples:
|
||||
<code>/api</code> (relative),
|
||||
<code>https://commandware.it/api</code> (absolute),
|
||||
<code>http://localhost:8001</code> (local)
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Current Configuration:</label>
|
||||
<div class="config-info">
|
||||
<strong>Base URL:</strong>
|
||||
<span id="current-api-url">-</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
onclick="saveApiConfig()"
|
||||
>
|
||||
Save Configuration
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
onclick="resetApiConfig()"
|
||||
>
|
||||
Reset to Default
|
||||
</button>
|
||||
<button class="btn" onclick="closeApiConfig()">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<main class="container">{% block content %}{% endblock %}</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<p>© 2025 API7EE Demo | Powered by FastAPI & API7 Enterprise Gateway</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="/static/js/app.js"></script>
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
86
web/templates/index.html
Normal file
86
web/templates/index.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Home - API Demo{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="hero">
|
||||
<h1>Welcome to API7EE Demo Platform</h1>
|
||||
<p class="subtitle">Explore our API services with real-time data and AI-powered features</p>
|
||||
</div>
|
||||
|
||||
<div class="cards-grid">
|
||||
<div class="card">
|
||||
<div class="card-icon">📦</div>
|
||||
<h3>Items Management</h3>
|
||||
<p>Browse and manage products in our catalog</p>
|
||||
<a href="/items" class="btn btn-primary">View Items</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-icon">👥</div>
|
||||
<h3>Users</h3>
|
||||
<p>Manage user accounts and profiles</p>
|
||||
<a href="/users" class="btn btn-primary">View Users</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-icon">🤖</div>
|
||||
<h3>AI Chat (LLM)</h3>
|
||||
<p>Chat with our videogame expert AI assistant</p>
|
||||
<a href="/llm" class="btn btn-primary">Start Chat</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-icon">📚</div>
|
||||
<h3>API Documentation</h3>
|
||||
<p>Explore our OpenAPI/Swagger documentation</p>
|
||||
<a href="/api/docs" target="_blank" class="btn btn-primary">Open Docs</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-section">
|
||||
<h2>Features</h2>
|
||||
<ul class="features-list">
|
||||
<li>✅ RESTful API with FastAPI</li>
|
||||
<li>✅ AI Rate Limiting (100 tokens/60s for LLM)</li>
|
||||
<li>✅ Standard Rate Limiting (100 req/60s per IP)</li>
|
||||
<li>✅ OpenAI-compatible LLM endpoint</li>
|
||||
<li>✅ Real-time data management</li>
|
||||
<li>✅ Swagger/OpenAPI documentation</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat-box">
|
||||
<h3 id="items-count">-</h3>
|
||||
<p>Total Items</p>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<h3 id="users-count">-</h3>
|
||||
<p>Active Users</p>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<h3>AI Ready</h3>
|
||||
<p>LLM Service</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
// Fetch stats from API
|
||||
fetch('/api/items')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
document.getElementById('items-count').textContent = data.length;
|
||||
})
|
||||
.catch(err => console.error('Error fetching items:', err));
|
||||
|
||||
fetch('/api/users')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
document.getElementById('users-count').textContent = data.length;
|
||||
})
|
||||
.catch(err => console.error('Error fetching users:', err));
|
||||
</script>
|
||||
{% endblock %}
|
||||
55
web/templates/items.html
Normal file
55
web/templates/items.html
Normal file
@@ -0,0 +1,55 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Items - API Demo{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>📦 Items Catalog</h1>
|
||||
<p>Browse all available products</p>
|
||||
</div>
|
||||
|
||||
<div id="items-container" class="items-grid">
|
||||
<div class="loading">Loading items...</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const API_BASE = '/api';
|
||||
|
||||
async function loadItems() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/items`);
|
||||
const items = await response.json();
|
||||
|
||||
const container = document.getElementById('items-container');
|
||||
container.innerHTML = items.map(item => `
|
||||
<div class="item-card ${!item.in_stock ? 'out-of-stock' : ''}">
|
||||
<div class="item-header">
|
||||
<h3>${item.name}</h3>
|
||||
<span class="badge ${item.in_stock ? 'badge-success' : 'badge-danger'}">
|
||||
${item.in_stock ? 'In Stock' : 'Out of Stock'}
|
||||
</span>
|
||||
</div>
|
||||
<p class="item-description">${item.description || 'No description'}</p>
|
||||
<div class="item-footer">
|
||||
<span class="price">$${item.price.toFixed(2)}</span>
|
||||
<button class="btn btn-sm" onclick="viewItem(${item.id})">View Details</button>
|
||||
</div>
|
||||
</div>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
console.error('Error loading items:', error);
|
||||
document.getElementById('items-container').innerHTML =
|
||||
'<div class="error">Failed to load items. Please try again.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function viewItem(id) {
|
||||
alert(`View item details for ID: ${id}\n\nAPI Endpoint: /api/items/${id}`);
|
||||
}
|
||||
|
||||
// Load items on page load
|
||||
loadItems();
|
||||
</script>
|
||||
{% endblock %}
|
||||
135
web/templates/llm.html
Normal file
135
web/templates/llm.html
Normal file
@@ -0,0 +1,135 @@
|
||||
{% extends "base.html" %} {% block title %}LLM Chat - API Demo{% endblock %} {%
|
||||
block content %}
|
||||
<div class="page-header">
|
||||
<h1>🤖 AI Chat - Videogame Expert</h1>
|
||||
<p>Chat with our AI assistant (Rate limited: 100 tokens/60s)</p>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-messages" id="chat-messages">
|
||||
<div class="system-message">
|
||||
Welcome! Ask me anything about videogames. I'm powered by the
|
||||
videogame-expert model.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-input-container">
|
||||
<textarea
|
||||
id="chat-input"
|
||||
placeholder="Type your message here..."
|
||||
rows="3"
|
||||
></textarea>
|
||||
<button id="send-btn" class="btn btn-primary" onclick="sendMessage()">
|
||||
Send Message
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="chat-info">
|
||||
<small>
|
||||
Model: <strong>videogame-expert</strong> | Status:
|
||||
<span id="status">Ready</span> | Rate Limit:
|
||||
<strong>100 tokens/60s</strong>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %}
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked@11.1.1/marked.min.js"></script>
|
||||
<script>
|
||||
const API_BASE = "/api";
|
||||
let isProcessing = false;
|
||||
|
||||
function addMessage(content, isUser = false) {
|
||||
const messagesDiv = document.getElementById("chat-messages");
|
||||
const messageDiv = document.createElement("div");
|
||||
messageDiv.className = isUser ? "user-message" : "assistant-message";
|
||||
|
||||
if (isUser) {
|
||||
messageDiv.textContent = content;
|
||||
} else {
|
||||
// Parse markdown for assistant messages
|
||||
messageDiv.innerHTML = marked.parse(content);
|
||||
}
|
||||
|
||||
messagesDiv.appendChild(messageDiv);
|
||||
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
||||
}
|
||||
|
||||
function setStatus(text, isError = false) {
|
||||
const statusSpan = document.getElementById("status");
|
||||
statusSpan.textContent = text;
|
||||
statusSpan.style.color = isError ? "#f44336" : "#4CAF50";
|
||||
}
|
||||
|
||||
async function sendMessage() {
|
||||
if (isProcessing) return;
|
||||
|
||||
const input = document.getElementById("chat-input");
|
||||
const prompt = input.value.trim();
|
||||
|
||||
if (!prompt) {
|
||||
alert("Please enter a message");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add user message
|
||||
addMessage(prompt, true);
|
||||
input.value = "";
|
||||
|
||||
// Set processing state
|
||||
isProcessing = true;
|
||||
document.getElementById("send-btn").disabled = true;
|
||||
setStatus("Processing...");
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/llm/chat`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
max_tokens: 150,
|
||||
temperature: 0.7,
|
||||
model: "videogame-expert",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || "API request failed");
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// Add assistant response
|
||||
addMessage(data.response, false);
|
||||
|
||||
// Show tokens used
|
||||
const tokensInfo = `Tokens used: ${data.tokens_used}`;
|
||||
const infoDiv = document.createElement("div");
|
||||
infoDiv.className = "system-message";
|
||||
infoDiv.textContent = tokensInfo;
|
||||
document.getElementById("chat-messages").appendChild(infoDiv);
|
||||
|
||||
setStatus("Ready");
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
addMessage(`Error: ${error.message}`, false);
|
||||
setStatus("Error", true);
|
||||
} finally {
|
||||
isProcessing = false;
|
||||
document.getElementById("send-btn").disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow Enter to send (Shift+Enter for newline)
|
||||
document
|
||||
.getElementById("chat-input")
|
||||
.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
69
web/templates/users.html
Normal file
69
web/templates/users.html
Normal file
@@ -0,0 +1,69 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Users - API Demo{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page-header">
|
||||
<h1>👥 Users</h1>
|
||||
<p>Manage user accounts</p>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="users-table-body">
|
||||
<tr>
|
||||
<td colspan="5" class="loading">Loading users...</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
const API_BASE = '/api';
|
||||
|
||||
async function loadUsers() {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}/users`);
|
||||
const users = await response.json();
|
||||
|
||||
const tbody = document.getElementById('users-table-body');
|
||||
tbody.innerHTML = users.map(user => `
|
||||
<tr>
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>
|
||||
<span class="badge ${user.active ? 'badge-success' : 'badge-danger'}">
|
||||
${user.active ? 'Active' : 'Inactive'}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button class="btn btn-sm" onclick="viewUser(${user.id})">View</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
} catch (error) {
|
||||
console.error('Error loading users:', error);
|
||||
document.getElementById('users-table-body').innerHTML =
|
||||
'<tr><td colspan="5" class="error">Failed to load users</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function viewUser(id) {
|
||||
alert(`View user details for ID: ${id}\n\nAPI Endpoint: /api/users/${id}`);
|
||||
}
|
||||
|
||||
loadUsers();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user