diff --git a/.gitea/workflows/APP_NAME-build.yaml b/.gitea/workflows/APP_NAME-build.yaml new file mode 100644 index 0000000..731fefa --- /dev/null +++ b/.gitea/workflows/APP_NAME-build.yaml @@ -0,0 +1,83 @@ +name: __APP_NAME__ — build & deploy + +on: + push: + branches: [main] + paths: + - 'code/**' + - 'helm/**' + - '.gitea/workflows/__APP_NAME__-build.yaml' + +jobs: + # Tests + build + push a Harbor — patrón ADR-038 reusable workflow. + build: + uses: coralware/IDP/.gitea/workflows/_python-microservice.yaml@main + with: + service_name: __APP_NAME__ + service_dir: code + uses_metrics: false + coverage_threshold: 80 + trivy_exit_code: 0 + enforce_rollback: false + + # Deploy al cluster del tenant — Helm upgrade --install (sin hook Alembic). + helm-deploy: + needs: build + runs-on: [self-hosted, linux/amd64] + container: alpine/helm:3.14.4 + steps: + - uses: actions/checkout@v4 + + - name: Install kubectl + run: apk add --no-cache kubectl + + - name: Decodificar kubeconfig del tenant + env: + KUBECONFIG_B64: ${{ secrets.KUBECONFIG_TENANT_B64 }} + run: | + if [ -z "$KUBECONFIG_B64" ]; then + echo "ERROR: secret KUBECONFIG_TENANT_B64 no configurado en el proyecto Gitea." + echo "tenant-manager debe provisionarlo al crear el repo." + exit 1 + fi + mkdir -p /root/.kube + echo "$KUBECONFIG_B64" | base64 -d > /root/.kube/config + chmod 600 /root/.kube/config + + - name: Helm upgrade --install + env: + TENANT_ID: ${{ vars.TENANT_ID }} + TENANT_ID_NODASHES: ${{ vars.TENANT_ID_NODASHES }} + TENANT_TIER: ${{ vars.TENANT_TIER }} + IMAGE_TAG: ${{ needs.build.outputs.image_tag }} + run: | + helm upgrade --install __APP_NAME__ ./helm/__APP_NAME__ \ + --namespace tenant-${TENANT_ID_NODASHES}-apps \ + --create-namespace \ + --set image.tag=${IMAGE_TAG} \ + --set tenantId=${TENANT_ID} \ + --set tenantIdNodashes=${TENANT_ID_NODASHES} \ + --set tier=${TENANT_TIER} \ + --wait --timeout 5m + + - name: Verificar rollout + env: + TENANT_ID_NODASHES: ${{ vars.TENANT_ID_NODASHES }} + run: | + kubectl -n tenant-${TENANT_ID_NODASHES}-apps \ + rollout status deploy/__APP_NAME__ --timeout=3m + + - name: Smoke check post-deploy + env: + INGRESS_HOST: __APP_NAME__-${{ vars.TENANT_ID_NODASHES }}.apps.coralware.cloud + run: | + for i in $(seq 1 12); do + if wget -q -O - --timeout=10 "https://${INGRESS_HOST}/health" | grep -q '"status":"ok"'; then + echo "Smoke OK: /health responde 200" + exit 0 + fi + echo "Intento ${i}/12 — /health aún no responde, esperando 5s…" + sleep 5 + done + echo "ERROR: /health no respondió 200 dentro del timeout" + exit 1 diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml deleted file mode 100644 index ed5e22b..0000000 --- a/.gitea/workflows/ci.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: ci - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: [self-hosted] - steps: - - uses: actions/checkout@v3 - - name: Build - run: echo "Configurar pasos de build para tu proyecto" diff --git a/.gitignore b/.gitignore index cb048bc..8fa8c20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,14 @@ -# Python __pycache__/ *.py[cod] -.env -venv/ +*.egg-info/ +.pytest_cache/ +.ruff_cache/ +.coverage +htmlcov/ .venv/ - -# Node.js -node_modules/ -dist/ +venv/ .env - -# Go -*.exe -*.out -vendor/ - -# General +.idea/ +.vscode/ +*.swp .DS_Store -*.log diff --git a/README.md b/README.md index f3d085f..37701ae 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,100 @@ -# template-rest-api-python +# __APP_NAME__ — REST API Python (FastAPI) -Template Golden Path — REST API con Python (FastAPI) \ No newline at end of file +App generada desde el Golden Path **`rest-api/python`** de Coralware Cove IDP. + +Scaffold para API HTTP stateless en FastAPI. **Sin persistencia por +diseño** — si tu app necesita base de datos, migra al shape +`web-backend/python` (que incluye Alembic + MariaDB). + +> **Contrato del shape:** ver `coralware-shape.yaml`. Define los endpoints +> obligatorios y la versión del shape. No elimines ese archivo — la +> plataforma lo lee. + +--- + +## Estructura + +``` +. +├── coralware-shape.yaml ← contrato con la plataforma +├── catalog-info.yaml ← entidad Backstage +├── code/ +│ ├── app/main.py ← FastAPI app +│ ├── tests/ ← pytest + httpx TestClient +│ ├── pyproject.toml +│ ├── requirements.txt +│ └── Dockerfile +├── helm/__APP_NAME__/ ← Helm chart minimal (sin hooks) +└── .gitea/workflows/ ← CI: tests + build + deploy +``` + +--- + +## Endpoints obligatorios + +| Path | Auth | Propósito | +|------|------|-----------| +| `/health` | none | Liveness + readiness; devuelve `{status: "ok"}` | +| `/docs` | configurable | Swagger UI | +| `/openapi.json` | configurable | Schema OpenAPI | +| `/metrics` | restringido vía NetworkPolicy | Métricas Prometheus | + +--- + +## Variables de entorno + +| Variable | Default | Descripción | +|----------|---------|-------------| +| `LOG_LEVEL` | `INFO` | `DEBUG` para troubleshooting | +| `CORRELATION_ID_HEADER` | `X-Correlation-ID` | Header de trazabilidad | +| `OPENAPI_REQUIRE_AUTH` | `false` | Activar JWT en `/docs` | +| `KEYCLOAK_ISSUER` | — | URL del realm cuando auth activado | + +--- + +## Desarrollo local + +```bash +cd code +pip install -r requirements-dev.txt +uvicorn app.main:app --reload --port 8000 +``` + +Probar: + +```bash +curl http://localhost:8000/health +curl http://localhost:8000/docs +``` + +--- + +## Plan de salida + +Sin lock-in técnico: + +| Componente | Cómo lo exportas | +|-----------|------------------| +| Código | `git remote add github && git push github main` | +| Imagen | `docker pull` desde Harbor + push a tu registry | +| Manifests | Ya están en `helm/` — `helm template` genera YAML aplicable | + +--- + +## Cuándo migrar a `web-backend/python` + +Si tu app empieza a: +- Necesitar persistencia (base de datos relacional) +- Usar migraciones de schema +- Servir frontend estático en `/demo` +- Manejar sesiones o estado entre requests + +→ migra al shape `web-backend/python` (un repo nuevo, un nuevo template). + +--- + +## Referencias + +- `docs/adr-027-product-design-golden-paths.md` — Golden Paths Fase 5 +- `docs/adr-053-shape-web-backend-python.md` — anatomía de shapes (referencia) +- `docs/adr-038-ci-cd-standardization.md` — pipeline Python diff --git a/code/Dockerfile b/code/Dockerfile new file mode 100644 index 0000000..76901e4 --- /dev/null +++ b/code/Dockerfile @@ -0,0 +1,26 @@ +# Multi-stage para imagen final lean. +# Stage 1: deps — instala dependencias Python en una venv aislada. +# Stage 2: runtime — copia código y venv, corre como user no-root con FS read-only. + +FROM python:3.12-slim AS deps +WORKDIR /build +COPY requirements.txt . +RUN python -m venv /venv \ + && /venv/bin/pip install --no-cache-dir --upgrade pip \ + && /venv/bin/pip install --no-cache-dir -r requirements.txt + +FROM python:3.12-slim AS runtime +WORKDIR /app + +COPY --from=deps /venv /venv +ENV PATH="/venv/bin:$PATH" + +COPY app /app/app + +RUN useradd -u 1001 -r appuser \ + && chown -R appuser:appuser /app +USER appuser + +EXPOSE 8000 + +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--no-access-log"] diff --git a/code/app/__init__.py b/code/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/app/main.py b/code/app/main.py new file mode 100644 index 0000000..e1b9d54 --- /dev/null +++ b/code/app/main.py @@ -0,0 +1,94 @@ +""" +Aplicación FastAPI — entry point. + +Endpoints obligatorios del shape rest-api/python (ver coralware-shape.yaml): +- /health → liveness + readiness (siempre 200 si el proceso está vivo) +- /docs → Swagger UI (auth opcional) +- /metrics → Prometheus + +Endpoints de dominio: agregar en app/routers/ y registrar en _register_routers(). +""" + +import logging + +from fastapi import Depends, FastAPI, HTTPException, Request, status +from fastapi.responses import JSONResponse, Response +from prometheus_client import CONTENT_TYPE_LATEST, generate_latest + +from app.settings import settings + +logging.basicConfig( + level=getattr(logging, settings.LOG_LEVEL, logging.INFO), + format="%(asctime)s [%(levelname)s] [cid=%(correlation_id)s] %(message)s", + datefmt="%Y-%m-%dT%H:%M:%SZ", +) + + +class CorrelationIdFilter(logging.Filter): + def filter(self, record: logging.LogRecord) -> bool: + if not hasattr(record, "correlation_id"): + record.correlation_id = "-" + return True + + +logging.getLogger().addFilter(CorrelationIdFilter()) +log = logging.getLogger("__APP_NAME__") + + +def _require_auth_dep() -> None: + """ + Dependency placeholder para /docs cuando OPENAPI_REQUIRE_AUTH=true. + Implementación real: validar JWT contra KEYCLOAK_ISSUER (ADR-029 R-004). + """ + if not settings.OPENAPI_REQUIRE_AUTH: + return + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Auth requerido — implementar validación JWT contra Keycloak", + ) + + +def create_app() -> FastAPI: + app = FastAPI( + title="__APP_NAME__", + version="0.1.0", + dependencies=[Depends(_require_auth_dep)] if settings.OPENAPI_REQUIRE_AUTH else [], + ) + + @app.middleware("http") + async def correlation_id_middleware(request: Request, call_next): + cid = request.headers.get(settings.CORRELATION_ID_HEADER) or _generate_cid() + request.state.correlation_id = cid + response = await call_next(request) + response.headers[settings.CORRELATION_ID_HEADER] = cid + return response + + @app.get("/health", tags=["platform"]) + async def health() -> JSONResponse: + return JSONResponse(status_code=200, content={"status": "ok"}) + + @app.get("/metrics", tags=["platform"]) + async def metrics() -> Response: + return Response(content=generate_latest(), media_type=CONTENT_TYPE_LATEST) + + _register_routers(app) + return app + + +def _generate_cid() -> str: + """Genera correlation_id si el cliente no envió uno.""" + import time + import uuid + + return f"__APP_NAME__-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}" + + +def _register_routers(app: FastAPI) -> None: + """ + Registra los routers de dominio. + Agrega aquí los include_router(...) conforme tu app crezca. + """ + pass + + +app = create_app() diff --git a/code/app/settings.py b/code/app/settings.py new file mode 100644 index 0000000..e98d997 --- /dev/null +++ b/code/app/settings.py @@ -0,0 +1,19 @@ +""" +Configuración runtime via variables de entorno. +Toda la configuración pasa por aquí — no leas env vars dispersos en otros módulos. +""" + +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + model_config = SettingsConfigDict(env_file=None, case_sensitive=True) + + LOG_LEVEL: str = "INFO" + CORRELATION_ID_HEADER: str = "X-Correlation-ID" + + OPENAPI_REQUIRE_AUTH: bool = False + KEYCLOAK_ISSUER: str = "" + + +settings = Settings() diff --git a/code/pyproject.toml b/code/pyproject.toml new file mode 100644 index 0000000..126eda2 --- /dev/null +++ b/code/pyproject.toml @@ -0,0 +1,36 @@ +[project] +name = "__APP_NAME__" +version = "0.1.0" +description = "REST API Python (FastAPI) — Golden Path rest-api/python" +requires-python = ">=3.12" + +[tool.black] +line-length = 100 +target-version = ["py312"] + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "W", "I", "B", "UP", "S"] +ignore = ["S101"] + +[tool.ruff.lint.per-file-ignores] +"tests/*" = ["S105", "S106"] + +[tool.bandit] +exclude_dirs = ["tests", ".venv"] + +[tool.pytest.ini_options] +pythonpath = ["."] +testpaths = ["tests"] +addopts = "-v --strict-markers" + +[tool.coverage.run] +source = ["app"] +omit = ["app/__init__.py", "tests/*"] + +[tool.coverage.report] +fail_under = 80 +show_missing = true diff --git a/code/requirements-dev.txt b/code/requirements-dev.txt new file mode 100644 index 0000000..8942a09 --- /dev/null +++ b/code/requirements-dev.txt @@ -0,0 +1,4 @@ +-r requirements.txt +-r ../../code/requirements-dev-base.txt + +httpx==0.27.2 diff --git a/code/requirements.txt b/code/requirements.txt new file mode 100644 index 0000000..fb43720 --- /dev/null +++ b/code/requirements.txt @@ -0,0 +1,6 @@ +fastapi==0.115.0 +uvicorn[standard]==0.32.0 +pydantic==2.9.2 +pydantic-settings==2.6.0 +prometheus-client==0.21.0 +python-jose[cryptography]==3.3.0 diff --git a/code/tests/__init__.py b/code/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/code/tests/conftest.py b/code/tests/conftest.py new file mode 100644 index 0000000..89c541d --- /dev/null +++ b/code/tests/conftest.py @@ -0,0 +1,21 @@ +""" +Fixtures para los tests del shape rest-api/python. + +A diferencia de web-backend/python (que levanta MariaDB con testcontainers), +este shape no tiene persistencia — los tests corren sin Docker. +""" + +from collections.abc import Iterator + +import pytest +from fastapi.testclient import TestClient + + +@pytest.fixture +def client() -> Iterator[TestClient]: + """TestClient con la app cargada. Sin BD, sin contenedores externos.""" + from app.main import create_app + + app = create_app() + with TestClient(app) as c: + yield c diff --git a/code/tests/test_main.py b/code/tests/test_main.py new file mode 100644 index 0000000..dc92f16 --- /dev/null +++ b/code/tests/test_main.py @@ -0,0 +1,26 @@ +"""Cubre los endpoints obligatorios del shape: /health y /metrics.""" + +from fastapi.testclient import TestClient + + +def test_health_returns_200_ok(client: TestClient) -> None: + response = client.get("/health") + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + + +def test_health_propagates_correlation_id(client: TestClient) -> None: + cid = "test-cid-12345" + response = client.get("/health", headers={"X-Correlation-ID": cid}) + assert response.headers.get("X-Correlation-ID") == cid + + +def test_health_generates_correlation_id_when_absent(client: TestClient) -> None: + response = client.get("/health") + assert response.headers.get("X-Correlation-ID") + + +def test_metrics_endpoint_serves_prometheus_format(client: TestClient) -> None: + response = client.get("/metrics") + assert response.status_code == 200 + assert "text/plain" in response.headers.get("content-type", "") diff --git a/coralware-shape.yaml b/coralware-shape.yaml new file mode 100644 index 0000000..068ab82 --- /dev/null +++ b/coralware-shape.yaml @@ -0,0 +1,28 @@ +apiVersion: coralware.cloud/v1alpha1 +kind: Shape +metadata: + name: rest-api-python +spec: + goldenPath: rest-api + goldenPathLang: python + shapeVersion: "1.0.0" + minTier: free-trial + app: + name: __APP_NAME__ + framework: fastapi + pythonVersion: "3.12" + endpoints: + health: /health + docs: /docs + openapi: /openapi.json + metrics: /metrics + database: + required: false + migrations: + tool: none + observability: + metricsPort: 9090 + prometheusScrape: false + promotionPath: + nextShape: web-backend-python + reason: cuando-la-app-necesita-persistencia diff --git a/helm/APP_NAME/.helmignore b/helm/APP_NAME/.helmignore new file mode 100644 index 0000000..2d8090e --- /dev/null +++ b/helm/APP_NAME/.helmignore @@ -0,0 +1,9 @@ +.DS_Store +.git/ +.gitignore +.bzr/ +.hg/ +.svn/ +*.tmproj +.vscode/ +.idea/ diff --git a/helm/APP_NAME/Chart.yaml b/helm/APP_NAME/Chart.yaml new file mode 100644 index 0000000..de72d7f --- /dev/null +++ b/helm/APP_NAME/Chart.yaml @@ -0,0 +1,12 @@ +apiVersion: v2 +name: __APP_NAME__ +description: Helm chart del Golden Path rest-api/python +type: application +version: 0.1.0 +appVersion: "0.1.0" +keywords: + - fastapi + - rest-api + - coralware-cove +maintainers: + - name: __OWNER__ diff --git a/helm/APP_NAME/templates/_helpers.tpl b/helm/APP_NAME/templates/_helpers.tpl new file mode 100644 index 0000000..e4df2d7 --- /dev/null +++ b/helm/APP_NAME/templates/_helpers.tpl @@ -0,0 +1,24 @@ +{{/* +Labels comunes a todos los recursos del chart. +Sigue convenciones CLAUDE.md global + IDP (idp.coralware.cloud/*). +*/}} +{{- define "app.labels" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/part-of: {{ .Chart.Name }} +idp.coralware.cloud/tier: {{ .Values.tier | quote }} +idp.coralware.cloud/tenant-id: {{ .Values.tenantId | quote }} +idp.coralware.cloud/golden-path: {{ .Values.goldenPath }} +idp.coralware.cloud/golden-path-lang: {{ .Values.goldenPathLang }} +{{- end }} + +{{/* +Selector labels — subset estable usado en Deployment.spec.selector y Service.spec.selector. +NO debe incluir labels que cambien (version, tier). +*/}} +{{- define "app.selectorLabels" -}} +app.kubernetes.io/name: {{ .Chart.Name }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} diff --git a/helm/APP_NAME/templates/deployment.yaml b/helm/APP_NAME/templates/deployment.yaml new file mode 100644 index 0000000..5a2b722 --- /dev/null +++ b/helm/APP_NAME/templates/deployment.yaml @@ -0,0 +1,80 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Release.Name }} + labels: + {{- include "app.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.replicaCount }} + selector: + matchLabels: + {{- include "app.selectorLabels" . | nindent 6 }} + template: + metadata: + labels: + {{- include "app.labels" . | nindent 8 }} + spec: + serviceAccountName: {{ .Release.Name }} + imagePullSecrets: + - name: {{ .Values.image.pullSecret }} + securityContext: + runAsNonRoot: true + runAsUser: 1001 + seccompProfile: + type: RuntimeDefault + containers: + - name: app + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: 8000 + protocol: TCP + envFrom: + - configMapRef: + name: {{ .Release.Name }}-config + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.liveness.periodSeconds }} + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }} + periodSeconds: {{ .Values.probes.readiness.periodSeconds }} + failureThreshold: {{ .Values.probes.readiness.failureThreshold }} + resources: + {{- toYaml .Values.resources | nindent 12 }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ .Release.Name }} + labels: + {{- include "app.labels" . | nindent 4 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ .Release.Name }}-config + labels: + {{- include "app.labels" . | nindent 4 }} +data: + LOG_LEVEL: {{ .Values.config.logLevel | quote }} + CORRELATION_ID_HEADER: {{ .Values.config.correlationIdHeader | quote }} + OPENAPI_REQUIRE_AUTH: {{ .Values.config.openapiRequireAuth | quote }} + KEYCLOAK_ISSUER: {{ .Values.config.keycloakIssuer | quote }} diff --git a/helm/APP_NAME/templates/ingressroute.yaml b/helm/APP_NAME/templates/ingressroute.yaml new file mode 100644 index 0000000..e85a4dc --- /dev/null +++ b/helm/APP_NAME/templates/ingressroute.yaml @@ -0,0 +1,19 @@ +{{- if .Values.ingress.enabled }} +apiVersion: traefik.io/v1alpha1 +kind: IngressRoute +metadata: + name: {{ .Release.Name }} + labels: + {{- include "app.labels" . | nindent 4 }} +spec: + entryPoints: + - websecure + routes: + - match: Host(`{{ .Values.ingress.host }}`) + kind: Rule + services: + - name: {{ .Release.Name }} + port: {{ .Values.service.port }} + tls: + secretName: {{ .Values.ingress.tlsSecretName }} +{{- end }} diff --git a/helm/APP_NAME/templates/networkpolicy.yaml b/helm/APP_NAME/templates/networkpolicy.yaml new file mode 100644 index 0000000..36e265a --- /dev/null +++ b/helm/APP_NAME/templates/networkpolicy.yaml @@ -0,0 +1,57 @@ +{{- if .Values.networkPolicy.enabled }} +# NetworkPolicy mínima del shape rest-api: bajo deny-all-default del namespace, permite: +# - Ingress: Traefik (público) + Prometheus (scrape de /metrics). +# - Egress: DNS, Harbor pull HTTPS. +# Sin acceso a ns-addons (rest-api no tiene BD por diseño). +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: {{ .Release.Name }} + labels: + {{- include "app.labels" . | nindent 4 }} +spec: + podSelector: + matchLabels: + {{- include "app.selectorLabels" . | nindent 6 }} + policyTypes: [Ingress, Egress] + + ingress: + # Tráfico HTTP desde Traefik (IngressRoute) + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: traefik-system + ports: + - protocol: TCP + port: 8000 + # Scrape de métricas Prometheus + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: cattle-monitoring-system + podSelector: + matchLabels: + app.kubernetes.io/name: prometheus + ports: + - protocol: TCP + port: 8000 + + egress: + # DNS + - to: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + ports: + - protocol: UDP + port: 53 + - protocol: TCP + port: 53 + # Harbor pull (registro de imágenes) — HTTPS abierto + - to: + - ipBlock: + cidr: 0.0.0.0/0 + ports: + - protocol: TCP + port: 443 +{{- end }} diff --git a/helm/APP_NAME/templates/service.yaml b/helm/APP_NAME/templates/service.yaml new file mode 100644 index 0000000..e4d4ff6 --- /dev/null +++ b/helm/APP_NAME/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ .Release.Name }} + labels: + {{- include "app.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + selector: + {{- include "app.selectorLabels" . | nindent 4 }} + ports: + - name: http + port: {{ .Values.service.port }} + targetPort: http + protocol: TCP diff --git a/helm/APP_NAME/values.yaml b/helm/APP_NAME/values.yaml new file mode 100644 index 0000000..0526032 --- /dev/null +++ b/helm/APP_NAME/values.yaml @@ -0,0 +1,52 @@ +# Defaults sensatos para tier free-trial. El CI inyecta tier/tenantId via --set +# leyendo valores del TenantProfile correspondiente. + +replicaCount: 1 + +image: + repository: registry.coralsafety.com/__TENANT_ID_NODASHES__/__APP_NAME__ + tag: latest + pullPolicy: Always + pullSecret: harbor-tenant-pull + +# Identidad del tenant — populated por --set desde el CI +tenantId: "" +tenantIdNodashes: "" +tier: free-trial +goldenPath: rest-api +goldenPathLang: python + +config: + logLevel: INFO + correlationIdHeader: X-Correlation-ID + openapiRequireAuth: false + keycloakIssuer: "" + +resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 250m + memory: 256Mi + +probes: + liveness: + initialDelaySeconds: 15 + periodSeconds: 10 + readiness: + initialDelaySeconds: 3 + periodSeconds: 5 + failureThreshold: 3 + +service: + type: ClusterIP + port: 8000 + +ingress: + enabled: true + host: __APP_NAME__-__TENANT_ID_NODASHES__.apps.coralware.cloud + tlsSecretName: __APP_NAME__-tls + +networkPolicy: + enabled: true