Commit 328773db authored by Dimitrios Gogos's avatar Dimitrios Gogos
Browse files

Merge branch 'feat/add-dockerfile' into 'feat/UI-enhancments'

feat: add dockerfile and helm chart

See merge request !9
parents 465e5c9c e9dad7a2
Loading
Loading
Loading
Loading
Loading

.dockerignore

0 → 100644
+10 −0
Changes for .dockerignore: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
*
!pyproject.toml
!uv.lock
!README.md
!src/portal_service/
!portal-gui/
**/__pycache__
portal-gui/node_modules
portal-gui/dist
portal-gui/.env*.local

Dockerfile

0 → 100644
+25 −0
Changes for Dockerfile: 25 added lines, 0 removed lines.
Original line number Diff line number Diff line
FROM node:22-slim AS gui
WORKDIR /gui
COPY portal-gui/package.json portal-gui/package-lock.json ./
RUN npm ci --no-audit --no-fund
COPY portal-gui/ ./
RUN npm run build

FROM python:3.12-slim AS backend
RUN pip install --no-cache-dir uv
WORKDIR /app
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
COPY pyproject.toml uv.lock ./
RUN uv sync --locked --no-install-project
COPY README.md ./
COPY src/ src/
RUN uv sync --locked --no-editable

FROM python:3.12-slim
WORKDIR /app
COPY --from=backend /app/.venv .venv
COPY --from=gui /gui/dist gui
ENV PATH=/app/.venv/bin:$PATH GUI_DIR=/app/gui
USER 65534
EXPOSE 8000
CMD ["uvicorn", "portal_service.main:create_app", "--factory", "--host", "0.0.0.0", "--port", "8000"]
+4 −5
Changes for deployment/Chart.yaml: 4 added lines, 5 removed lines.
Original line number Diff line number Diff line
apiVersion: v2
name: portal
description: Helm chart for the Portal frontend (Next.js)
description: Helm chart for the OOP Portal (developer portal UI and its backend)
type: application
version: 0.1.0
appVersion: "1.0.0"
version: 1.0.0
appVersion: "2.0.0"
keywords:
  - frontend
  - oop
  - etsi
  - portal
home: https://labs.etsi.org/rep/oop/code/dev-portal
home: https://labs.etsi.org/rep/oop/code/portal
maintainers:
  - name: OOP Team
+41 −70
Changes for deployment/README.md: 41 added lines, 70 removed lines.
Original line number Diff line number Diff line
# Portal Helm Chart

Deploy the Portal frontend (Next.js) to Kubernetes.
Deploys the OOP developer portal: one container in which the backend serves the web UI and
its API (`/api/*`) on the same address, so the login cookies stay first-party.

## Prerequisites
The portal needs, from the umbrella chart:

- Helm 3
- Kubernetes cluster
- Portal image (build from `portal-gui/` or use a registry)
- **PostgreSQL**: database and role `portal`, an entry in `postgresql.databases`. An init
  container waits until the role can log in.
- **Keycloak** (unless `global.auth.enabled=false`): the `oop-portal` client logs users in, and
  `oop-portal-provisioner` creates developers' API clients. Their secrets come from the
  `keycloak-credentials` Secret.
- **OEG** and the **AI2 agent**, which the backend forwards `/api/oeg/*` and `/api/ai2/chat` to.

## Build the image
## Addresses

From the repo root:
Browsers reach the portal and Keycloak on public URLs built from `global.publicHost`, `global.scheme`
and `global.nodePorts`, as the keycloak chart builds them. The backend's own calls to Keycloak use
`global.keycloak.host`/`port` instead, since the public address may not be reachable from pods.

```bash
docker build -t portal-gui:latest -f portal/portal-gui/Dockerfile portal/portal-gui
```

For **Kind**, load the image into the cluster:

```bash
kind load docker-image portal-gui:latest
```

## Install

```bash
cd portal/deployment
helm install portal .
```
If you set the keycloak chart's `hostname` or `realms.oop.portal.baseUrl`, set `keycloak.publicUrl`
or `publicUrl` here to match.

Override the image tag to use your built image:
## Configuration

```bash
helm install portal . --set image.tag=latest
```
| Parameter | Description | Default |
|-----------|-------------|---------|
| `image.repository` | Image repository | `labs.etsi.org:5050/oop/code/portal` |
| `image.tag` | Image tag, also reported as the app version | `2.0.0` |
| `publicUrl` | Address browsers reach the portal on | `<scheme>://<public host>:<NodePort>` |
| `oeg.url` | Where `/api/oeg/*` is forwarded to | `http://oeg:80` |
| `oeg.publicUrl` | OEG address shown with developers' API clients | `""` (not shown) |
| `ai2.chatUrl` | AI2 agent chat endpoint; empty turns the chat off | `http://ai2-ai-agent:9013/groq-mcp` |
| `keycloak.publicUrl` | Keycloak's public URL (token issuer) | `<scheme>://<public host>:<global.nodePorts.keycloak>` |
| `keycloak.existingSecret` | Secret with the client secrets | `keycloak-credentials` |
| `keycloak.provisioner.enabled` | API clients feature | `true` |
| `keycloak.provisioner.oegClientId` | The keycloak chart's `realms.oop.oeg.internalId` | `oeg-client` |
| `database.name` / `database.user` | Database and role | `portal` |
| `database.passwordKey` | Key of the role's password in `global.postgresql.secretName` | `portal-password` |
| `service.type` | Service type | `ClusterIP` (`NodePort` in the umbrella chart) |
| `service.nodePort` | NodePort | `global.nodePorts.portal` |

Set the OEG service URL (used by the frontend as `OEG_SERVICE_URL`):
The API products shown in the portal come from `global.apiProducts`, the same list Keycloak's
roles are built from.

```bash
helm install portal . \
  --set image.tag=latest \
  --set config.oegServiceUrl=http://oeg-service:8080
```
API clients' registration tokens are encrypted with a key generated into the Secret
`portal-credentials` on first install. It is kept on `helm uninstall`, like the database it
belongs with.

If the default NodePort is already in use, pick another (30000–32767):
## Troubleshooting

```bash
helm install portal . --set service.nodePort=30081
kubectl get pods -n oop -l app.kubernetes.io/name=portal
kubectl logs -f deployment/portal -n oop
kubectl logs deployment/portal -n oop -c wait-for-database
```

## Upgrade

```bash
helm upgrade portal . --set image.tag=latest
```

## Get the URL

After install, Helm prints the URL. You can also run:

```bash
kubectl get svc -l app.kubernetes.io/name=portal -o jsonpath='http://{.items[0].spec.clusterIP}:{.items[0].spec.ports[0].port}'
```

For **NodePort**, use the node IP and `nodePort` (see `kubectl get svc -l app.kubernetes.io/name=portal`).

## Uninstall

```bash
helm uninstall portal
```

## Main values

| Value | Default | Description |
|-------|---------|-------------|
| `image.repository` | `portal-gui` | Image name |
| `image.tag` | `1.0.0` | Image tag |
| `config.oegServiceUrl` | `""` | OEG service base URL (env `OEG_SERVICE_URL`) |
| `service.type` | `NodePort` | Service type |
| `service.nodePort` | `30082` | NodePort (when type is NodePort) |
| `replicaCount` | `1` | Number of replicas |

See `values.yaml` for all options.

deployment/templates/NOTES.txt

deleted100644 → 0
+0 −16
Changes for deployment/templates/NOTES.txt: 0 added lines, 16 removed lines.
Original line number Diff line number Diff line
Portal frontend has been deployed.

1. Get the application URL by running these commands:
{{- if contains "NodePort" .Values.service.type }}
  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "portal.fullname" . }})
  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.service.type }}
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
  kubectl get --namespace {{ .Release.Namespace }} svc {{ include "portal.fullname" . }} -w
{{- else if contains "ClusterIP" .Values.service.type }}
  kubectl port-forward --namespace {{ .Release.Namespace }} svc/{{ include "portal.fullname" . }} {{ .Values.service.port }}:{{ .Values.service.port }}
  echo "Visit http://127.0.0.1:{{ .Values.service.port }}"
{{- end }}

OEG_SERVICE_URL is set to: {{ .Values.config.oegServiceUrl }}
Loading