commit a4edf158626dead17fb95a1aaa441c0f622a68d8 Author: Scaffolder Date: Wed Mar 18 18:18:32 2026 +0000 initial commit Change-Id: I15e0d095273278f8531caa7399565fe92857a7ad diff --git a/.gitea/workflows/build-push.yml b/.gitea/workflows/build-push.yml new file mode 100644 index 0000000..a69fd32 --- /dev/null +++ b/.gitea/workflows/build-push.yml @@ -0,0 +1,81 @@ +name: Build and Push to ACR + +on: + push: + branches: [ "main" ] + workflow_dispatch: {} + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + +jobs: + build: + name: Build and Push + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Azure CLI + run: | + command -v az &>/dev/null || curl -sL https://aka.ms/InstallAzureCLIDeb | bash + + - name: Install Docker CLI + run: | + command -v docker &>/dev/null || (apt-get update -qq && apt-get install -y docker.io) + docker --version + + - name: Azure login (OIDC) + run: | + az login \ + --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --tenant "$AZURE_TENANT_ID" \ + --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + echo "✓ Azure login successful" + + - name: Get ACR details + id: acr + run: | + ACR_NAME=$(az acr list --query "[0].name" -o tsv) + ACR_NAME="${ACR_NAME:-bstagecjotdevacr}" + echo "ACR_NAME=$ACR_NAME" >> $GITHUB_ENV + echo "ACR_LOGIN_SERVER=${ACR_NAME}.azurecr.io" >> $GITHUB_ENV + echo "✓ Using ACR: ${ACR_NAME}.azurecr.io" + + - name: ACR Login + run: | + ACR_TOKEN=$(az acr login --name "$ACR_NAME" --expose-token --output tsv --query accessToken) + docker login "$ACR_LOGIN_SERVER" \ + --username 00000000-0000-0000-0000-000000000000 \ + --password "$ACR_TOKEN" + echo "✓ ACR login successful" + + - name: Build and Push Docker image + run: | + IMAGE_TAG="${{ gitea.sha }}" + IMAGE_FULL="${ACR_LOGIN_SERVER}/test-alex-1:${IMAGE_TAG}" + IMAGE_LATEST="${ACR_LOGIN_SERVER}/test-alex-1:latest" + docker build -t "$IMAGE_FULL" -t "$IMAGE_LATEST" . + docker push "$IMAGE_FULL" + docker push "$IMAGE_LATEST" + echo "IMAGE_FULL=$IMAGE_FULL" >> $GITHUB_ENV + echo "✓ Pushed: $IMAGE_FULL" + + - name: Build Summary + run: | + echo "### ✅ Build Successful" >> $GITHUB_STEP_SUMMARY + echo "| | |" >> $GITHUB_STEP_SUMMARY + echo "|---|---|" >> $GITHUB_STEP_SUMMARY + echo "| **Service** | test-alex-1 |" >> $GITHUB_STEP_SUMMARY + echo "| **Runtime** | Python FastAPI |" >> $GITHUB_STEP_SUMMARY + echo "| **Commit** | ${{ gitea.sha }} |" >> $GITHUB_STEP_SUMMARY + echo "| **Image** | $IMAGE_FULL |" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/deploy-humanitec.yml b/.gitea/workflows/deploy-humanitec.yml new file mode 100644 index 0000000..fb84338 --- /dev/null +++ b/.gitea/workflows/deploy-humanitec.yml @@ -0,0 +1,188 @@ +name: Deploy to Humanitec + +on: + workflow_run: + workflows: ["Build and Push to ACR"] + types: + - completed + branches: [ "main" ] + workflow_dispatch: + inputs: + environment: + description: 'Target environment' + required: true + default: 'dev' + type: choice + options: + - dev + - staging + - prod + +env: + APP_ID: test-alex-1 + PROJECT_ID: cjot-platform + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + DEFAULT_ENV_ID: dev + +jobs: + deploy: + name: Deploy to Humanitec + runs-on: ubuntu-latest + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + permissions: + contents: read + id-token: write + outputs: + sha: ${{ steps.get-sha.outputs.sha }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Get short SHA + id: get-sha + run: echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + + - name: Install CLI tools + run: | + # jq (needed for github API call) + command -v jq &>/dev/null || (apt-get update -qq && apt-get install -y -qq jq) + # hctl (Humanitec Platform Orchestrator v2 CLI) + HCTL_VERSION=$(curl -s https://api.github.com/repos/humanitec/hctl/releases/latest | jq -r '.tag_name') + curl -fLO "https://github.com/humanitec/hctl/releases/download/${HCTL_VERSION}/hctl_${HCTL_VERSION#v}_linux_amd64.tar.gz" + tar -xzf "hctl_${HCTL_VERSION#v}_linux_amd64.tar.gz" + install -m 755 hctl /usr/local/bin/hctl + hctl --version + echo "✓ Tools installed" + + - name: Install Azure CLI + run: command -v az &>/dev/null || curl -sL https://aka.ms/InstallAzureCLIDeb | bash + + - name: Azure login (OIDC) + run: | + az login \ + --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --tenant "$AZURE_TENANT_ID" \ + --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + echo "✓ Azure login successful" + + - name: Get image reference + id: image + run: | + ACR_LOGIN_SERVER=$(az acr list --query "[0].loginServer" -o tsv) + SHA=${{ steps.get-sha.outputs.sha }} + echo "IMAGE_FULL=${ACR_LOGIN_SERVER}/test-alex-1:${SHA}" >> $GITHUB_ENV + echo "✓ Image: ${ACR_LOGIN_SERVER}/test-alex-1:${SHA}" + + - name: Set environment + id: set-env + run: | + if [ "${{ gitea.event_name }}" = "workflow_dispatch" ]; then + ENV_ID="${{ gitea.event.inputs.environment }}" + else + ENV_ID="$DEFAULT_ENV_ID" + fi + ENV_ID="${ENV_ID:-dev}" + echo "ENV_ID=$ENV_ID" >> $GITHUB_OUTPUT + echo "ENV_ID=$ENV_ID" >> $GITHUB_ENV + echo "✓ Target environment: $ENV_ID" + + - name: Get Humanitec credentials from Key Vault + run: | + HUMANITEC_TOKEN=$(az keyvault secret show \ + --vault-name "bstage-cjot-dev-core-kv" \ + --name "humanitec-api-token-v2" \ + --query "value" -o tsv) + [ -z "$HUMANITEC_TOKEN" ] && echo "❌ Failed to retrieve HUMANITEC_TOKEN" && exit 1 + echo "HUMANITEC_AUTH_TOKEN=$HUMANITEC_TOKEN" >> $GITHUB_ENV + + HUMANITEC_ORG=$(az keyvault secret show \ + --vault-name "bstage-cjot-dev-core-kv" \ + --name "humanitec-org-id" \ + --query "value" -o tsv) + [ -z "$HUMANITEC_ORG" ] && echo "❌ Failed to retrieve HUMANITEC_ORG" && exit 1 + echo "HUMANITEC_ORG=$HUMANITEC_ORG" >> $GITHUB_ENV + echo "✓ Credentials retrieved" + + - name: Deploy via Humanitec Score + run: | + # Pre-flight: wait for any prior deployment to finish before calling hctl. + # hctl refuses to start a new deployment while one is still executing. + echo "Pre-flight: checking for in-progress deployments..." + PREFLIGHT_WAITED=0 + while [ $PREFLIGHT_WAITED -lt 300 ]; do + PREFLIGHT_STATUS=$(curl -sf \ + -H "Authorization: Bearer $HUMANITEC_AUTH_TOKEN" \ + "https://api.humanitec.dev/orgs/$HUMANITEC_ORG/last-deployments?env_id=$ENV_ID&project_id=$PROJECT_ID&state_change_only=true" \ + | jq -r '.items[0].status // "none"' 2>/dev/null || echo "none") + if [ "$PREFLIGHT_STATUS" != "in progress" ] && [ "$PREFLIGHT_STATUS" != "pending" ] && [ "$PREFLIGHT_STATUS" != "executing" ]; then + echo "Pre-flight passed (status=$PREFLIGHT_STATUS). Proceeding." + break + fi + echo " Prior deployment still running ($PREFLIGHT_WAITED s elapsed, status=$PREFLIGHT_STATUS)..." + sleep 15 + PREFLIGHT_WAITED=$((PREFLIGHT_WAITED + 15)) + done + # First deploy — provisions all resources. On a brand-new Humanitec project the + # dns-k8s-ingress Terraform module runs before the K8s Service exists, so the + # ingress backend port falls back to 3000. A second deploy (below) corrects it + # once the Service is up, which is essential for apps not running on port 3000. + HCTL_EXIT=0 + timeout 300 hctl score deploy "$PROJECT_ID" "$ENV_ID" score.yaml \ + --no-prompt \ + --default-image "$IMAGE_FULL" || HCTL_EXIT=$? + if [ "$HCTL_EXIT" -eq 0 ]; then + echo "✓ First deployment complete to $ENV_ID" + elif [ "$HCTL_EXIT" -eq 124 ]; then + echo "✓ First deployment submitted (polling timed out — waiting for K8s to settle)" + else + echo "✗ hctl failed with exit code $HCTL_EXIT" + exit $HCTL_EXIT + fi + # Poll Humanitec API until the first deployment is no longer in-progress before + # re-deploying. A flat sleep is unreliable — Terraform DNS modules can take 4-6 min. + echo "Waiting for first deployment to finish (polling Humanitec API)..." + MAX_WAIT=360 + WAITED=0 + while [ $WAITED -lt $MAX_WAIT ]; do + DEPLOY_STATUS=$(curl -sf \ + -H "Authorization: Bearer $HUMANITEC_AUTH_TOKEN" \ + "https://api.humanitec.dev/orgs/$HUMANITEC_ORG/last-deployments?env_id=$ENV_ID&project_id=$PROJECT_ID&state_change_only=true" \ + | jq -r '.items[0].status // "unknown"' 2>/dev/null || echo "unknown") + if [ "$DEPLOY_STATUS" != "in progress" ] && [ "$DEPLOY_STATUS" != "pending" ] && [ "$DEPLOY_STATUS" != "executing" ]; then + echo "First deployment finished with status: $DEPLOY_STATUS" + break + fi + echo " Still running ($WAITED s elapsed, status=$DEPLOY_STATUS)..." + sleep 15 + WAITED=$((WAITED + 15)) + done + if [ $WAITED -ge $MAX_WAIT ]; then + echo "Warning: first deployment still running after $MAX_WAIT s — proceeding anyway" + fi + # Second deploy — dns module now reads the real K8s Service port, fixing the + # ingress backend. This is a no-op for apps already routed correctly (e.g. port 3000). + HCTL_EXIT2=0 + timeout 120 hctl score deploy "$PROJECT_ID" "$ENV_ID" score.yaml \ + --no-prompt \ + --default-image "$IMAGE_FULL" || HCTL_EXIT2=$? + if [ "$HCTL_EXIT2" -eq 0 ]; then + echo "✓ Deployment finalised to $ENV_ID" + elif [ "$HCTL_EXIT2" -eq 124 ]; then + echo "✓ Second deployment submitted (polling timed out)" + else + echo "✗ Second hctl deploy failed with exit code $HCTL_EXIT2" + exit $HCTL_EXIT2 + fi + + - name: Deployment Summary + run: | + DEPLOY_URL="https://console.humanitec.dev/orgs/${HUMANITEC_ORG}/projects/$APP_ID" + echo "### 🚀 Deployment Complete" >> $GITHUB_STEP_SUMMARY + echo "| | |" >> $GITHUB_STEP_SUMMARY + echo "|---|---|" >> $GITHUB_STEP_SUMMARY + echo "| **Service** | $APP_ID |" >> $GITHUB_STEP_SUMMARY + echo "| **Environment** | $ENV_ID |" >> $GITHUB_STEP_SUMMARY + echo "| **Image** | $IMAGE_FULL |" >> $GITHUB_STEP_SUMMARY + echo "| **Humanitec** | [$DEPLOY_URL]($DEPLOY_URL) |" >> $GITHUB_STEP_SUMMARY diff --git a/.gitea/workflows/techdocs.yml b/.gitea/workflows/techdocs.yml new file mode 100644 index 0000000..7226797 --- /dev/null +++ b/.gitea/workflows/techdocs.yml @@ -0,0 +1,90 @@ +name: Build and Publish TechDocs + +on: + workflow_run: + workflows: ["Build and Push to ACR"] + branches: [main] + types: [completed] + workflow_dispatch: {} + +env: + AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token + AZURE_ACCOUNT_NAME: "bstagecjotdevsttechdocs" + ENTITY_NAMESPACE: default + ENTITY_KIND: component + ENTITY_NAME: test-alex-1 + +jobs: + build-and-publish: + if: >- + ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Bootstrap pip + run: | + python3 --version + if python3 -m pip --version 2>/dev/null; then + echo "pip already available" + elif python3 -m ensurepip --version 2>/dev/null; then + python3 -m ensurepip --upgrade + else + apt-get update -qq + apt-get install -y python3-pip + fi + python3 -m pip install --upgrade pip + python3 -m pip --version + + - name: Install dependencies + run: | + python3 -m pip install --upgrade pip + python3 -m pip install \ + mkdocs-techdocs-core==1.* \ + mkdocs-git-revision-date-localized-plugin \ + mkdocs-awesome-pages-plugin + + npm install -g @techdocs/cli + + - name: Validate MkDocs config + run: mkdocs build --strict --site-dir /tmp/mkdocs-validate + + - name: Build TechDocs site + run: | + techdocs-cli generate \ + --source-dir . \ + --output-dir ./site \ + --no-docker \ + --verbose + + - name: Install Azure CLI + run: | + if command -v az &>/dev/null; then + echo "Azure CLI already installed: $(az version --query '"azure-cli"' -o tsv)" + else + curl -sL https://aka.ms/InstallAzureCLIDeb | bash + fi + + - name: Azure login (OIDC) + run: | + az login \ + --service-principal \ + --username "$AZURE_CLIENT_ID" \ + --tenant "$AZURE_TENANT_ID" \ + --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" + + echo "Azure login successful" + + - name: Publish TechDocs site + run: | + techdocs-cli publish \ + --publisher-type azureBlobStorage \ + --storage-name "techdocs" \ + --azureAccountName "$AZURE_ACCOUNT_NAME" \ + --entity "$ENTITY_NAMESPACE/$ENTITY_KIND/$ENTITY_NAME" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0621d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +__pycache__/ +*.pyc +*.pyo +.venv/ +venv/ +.env +*.egg-info/ +dist/ +.DS_Store diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d9a5a7a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +# ---- Build stage (install deps) ---- +FROM python:3.12-slim AS build +WORKDIR /app + +RUN pip install --upgrade pip +COPY requirements.txt . +RUN pip install --no-cache-dir --prefix=/install -r requirements.txt + +# ---- Runtime stage ---- +FROM python:3.12-slim +WORKDIR /app + +# Non-root user +RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser +USER appuser + +COPY --from=build /install /usr/local +COPY app/ ./app/ + +EXPOSE 8000 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ + CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e167bc7 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ +# test-alex-1 + +test-alex-1 + +**Runtime**: Python 3.12 · FastAPI +**Owner**: group:default/platform-engineering +**Platform**: Humanitec · Project `cjot-platform` + +## Quick Start + +```bash +python -m venv .venv && source .venv/bin/activate +pip install -r requirements.txt +uvicorn app.main:app --reload +``` + +Service listens on **http://localhost:8000**. +Swagger UI: **http://localhost:8000/docs** + +## Endpoints +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/items` | List all items | +| POST | `/api/items` | Create an item | +| GET | `/api/items/{id}` | Get by ID | +| PUT | `/api/items/{id}` | Update | +| DELETE | `/api/items/{id}` | Delete | +| GET | `/health` | Health check | +| GET | `/metrics` | Prometheus metrics | +| GET | `/docs` | Swagger UI | + +## Container +```bash +docker build -t test-alex-1:dev . +docker run -p 8000:8000 test-alex-1:dev +``` diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..9fab277 --- /dev/null +++ b/app/__init__.py @@ -0,0 +1 @@ +from app.main import app # noqa: F401 — re-export for uvicorn entrypoint diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..bbe953c --- /dev/null +++ b/app/main.py @@ -0,0 +1,90 @@ +from __future__ import annotations + +from threading import Lock +from typing import Optional + +from fastapi import FastAPI, HTTPException +from prometheus_fastapi_instrumentator import Instrumentator +from pydantic import BaseModel + +app = FastAPI( + title="test-alex-1", + description="test-alex-1", + version="0.1.0", +) + +# Expose /metrics for Prometheus scraping +Instrumentator().instrument(app).expose(app) + + +# --------------------------------------------------------------------------- +# Domain model +# --------------------------------------------------------------------------- +class ItemRequest(BaseModel): + name: str + description: str = "" + + +class Item(BaseModel): + id: int + name: str + description: str + + +# --------------------------------------------------------------------------- +# In-memory store (thread-safe) +# --------------------------------------------------------------------------- +_store: dict[int, Item] = {} +_counter = 0 +_lock = Lock() + + +def _next_id() -> int: + global _counter + with _lock: + _counter += 1 + return _counter + + +# --------------------------------------------------------------------------- +# Routes +# --------------------------------------------------------------------------- +@app.get("/health", tags=["observability"]) +def health() -> dict: + return {"status": "UP"} + + +@app.get("/api/items", response_model=list[Item], tags=["items"]) +def list_items(): + return list(_store.values()) + + +@app.post("/api/items", response_model=Item, status_code=201, tags=["items"]) +def create_item(body: ItemRequest): + item = Item(id=_next_id(), name=body.name, description=body.description) + _store[item.id] = item + return item + + +@app.get("/api/items/{item_id}", response_model=Item, tags=["items"]) +def get_item(item_id: int): + item = _store.get(item_id) + if item is None: + raise HTTPException(status_code=404, detail="Item not found") + return item + + +@app.put("/api/items/{item_id}", response_model=Item, tags=["items"]) +def update_item(item_id: int, body: ItemRequest): + item = _store.get(item_id) + if item is None: + raise HTTPException(status_code=404, detail="Item not found") + updated = Item(id=item_id, name=body.name, description=body.description) + _store[item_id] = updated + return updated + + +@app.delete("/api/items/{item_id}", tags=["items"]) +def delete_item(item_id: int): + _store.pop(item_id, None) + return {"deleted": item_id} diff --git a/catalog-info.yaml b/catalog-info.yaml new file mode 100644 index 0000000..2629f00 --- /dev/null +++ b/catalog-info.yaml @@ -0,0 +1,125 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: test-alex-1 + description: test-alex-1 + annotations: + humanitec.dev/orgId: skillful-wild-chicken-2617 + humanitec.dev/projectId: cjot-platform + backstage.io/techdocs-ref: dir:. + backstage.io/kubernetes-namespace: dev + backstage.io/kubernetes-namespaces: "dev" + backstage.io/kubernetes-label-selector: "app=test-alex-1" + gitea.kyndemo.live/repo-slug: "demo-platform/test-alex-1" + prometheus.io/scrape: "true" + prometheus.io/port: "8000" + prometheus.io/path: "/metrics" + grafana/grafana-instance: default + grafana/alert-label-selector: "app=test-alex-1" + grafana/dashboard-selector: "uid == 'app-test-alex-1'" + tags: + - microservice + - golden-path + - python-fastapi + - stateless + links: + - url: https://console.humanitec.dev/orgs/skillful-wild-chicken-2617/projects/cjot-platform + title: Humanitec Console + icon: dashboard + - url: https://gitea.kyndemo.live/demo-platform/test-alex-1 + title: Source Repository + icon: github +spec: + type: service + owner: group:default/platform-engineering + system: system:default/platform-engineering + lifecycle: experimental + providesApis: + - test-alex-1-api +--- +apiVersion: backstage.io/v1alpha1 +kind: API +metadata: + name: test-alex-1-api + description: REST API for test-alex-1 + annotations: + backstage.io/techdocs-ref: dir:. +spec: + type: openapi + lifecycle: experimental + owner: group:default/platform-engineering + system: system:default/platform-engineering + definition: | + openapi: "3.0.0" + info: + title: test-alex-1 + version: "0.1.0" + description: "test-alex-1" + servers: + - url: https://test-alex-1.kyndemo.live + paths: + /api/items: + get: + summary: List all items + responses: + "200": + description: OK + post: + summary: Create item + responses: + "201": + description: Created + /api/items/{id}: + get: + summary: Get item by ID + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + "200": + description: OK + "404": + description: Not found + put: + summary: Update item + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + "200": + description: OK + delete: + summary: Delete item + parameters: + - name: id + in: path + required: true + schema: + type: integer + responses: + "200": + description: Deleted + /health: + get: + summary: Health check + responses: + "200": + description: "{ status: UP }" + /metrics: + get: + summary: Prometheus metrics + responses: + "200": + description: Prometheus text format + /docs: + get: + summary: Swagger UI + responses: + "200": + description: HTML diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..7014a70 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,22 @@ +# API Reference + +Base URL: `https://test-alex-1.kyndemo.live` + +Interactive docs: `/docs` (Swagger UI) · `/redoc` (ReDoc) + +## Items + +| Method | Path | Description | +|--------|------|-------------| +| GET | `/api/items` | List items | +| POST | `/api/items` | Create item `{"name":"..","description":".."}` | +| GET | `/api/items/{id}` | Get by ID | +| PUT | `/api/items/{id}` | Update | +| DELETE | `/api/items/{id}` | Delete | + +## Observability + +| Path | Description | +|------|-------------| +| `/health` | `{"status":"UP"}` | +| `/metrics` | Prometheus text format | diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..4218d70 --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,32 @@ +# Architecture + +`test-alex-1` is a Python 3.12 **FastAPI** application served by **Uvicorn**. + +## Components + +| Component | Technology | +|-----------|-----------| +| Web framework | FastAPI 0.111 | +| ASGI server | Uvicorn | +| Metrics | prometheus-fastapi-instrumentator → `/metrics` | +| Validation | Pydantic v2 models | +| Health | Custom `/health` endpoint | +| API docs | Built-in Swagger UI at `/docs` | + +## Deployment flow + +``` +Push to main + │ + ▼ +build-push.yml + docker build (python:3.12-slim) + docker push → ACR (OIDC) + │ + ▼ +deploy-humanitec.yml + humctl score deploy → Humanitec + │ + ▼ +AKS pod :8000 +``` diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..5b66183 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,9 @@ +# Home + +test-alex-1 — test-alex-1 + +- **Runtime**: Python 3.12 · FastAPI · Uvicorn +- **Owner**: group:default/platform-engineering +- **Deployment profile**: `stateless` + +See [Architecture](architecture.md) and [API Reference](api.md). diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..af613f7 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,11 @@ +site_name: test-alex-1 +site_description: test-alex-1 +docs_dir: docs + +nav: + - Home: index.md + - Architecture: architecture.md + - API Reference: api.md + +plugins: + - techdocs-core diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e760df2 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +fastapi==0.111.0 +uvicorn[standard]==0.29.0 +prometheus-fastapi-instrumentator==7.0.0 diff --git a/score.yaml b/score.yaml new file mode 100644 index 0000000..dd6cbd6 --- /dev/null +++ b/score.yaml @@ -0,0 +1,43 @@ +apiVersion: score.dev/v1b1 +metadata: + name: test-alex-1 + +containers: + app: + image: . + livenessProbe: + httpGet: + path: /health + port: 8000 + readinessProbe: + httpGet: + path: /health + port: 8000 + resources: + limits: + memory: "256Mi" + cpu: "300m" + requests: + memory: "128Mi" + cpu: "50m" + +service: + ports: + http: + port: 80 + targetPort: 8000 + +resources: + env: + type: environment + + dns: + type: dns + + # ----- Uncomment to add a PostgreSQL database (deployment_profile: db-only or db+cache) ----- + # db: + # type: postgres + + # ----- Uncomment to add Redis cache (deployment_profile: db+cache) ----- + # cache: + # type: redis