[PR #6] [CLOSED] feat: enable Terminals K8s Orchestrator policy management in Open WebUI #39

Closed
opened 2026-10-18 21:13:41 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/open-webui/terminals/pull/6
Author: @james-westbrook
Created: 3/25/2026
Status: ❌ Closed

Base: main ← Head: feat-operator-ui-integration


📝 Commits (1)

  • 605338e feat: enable Terminals K8s Orchestrator policy management in Open WebUI

📊 Changes

5 files changed (+168 additions, -0 deletions)

View changed files

📝 pyproject.toml (+3 -0)
📝 terminals/backends/base.py (+26 -0)
📝 terminals/backends/kubernetes_operator.py (+49 -0)
📝 terminals/main.py (+2 -0)
➕ terminals/routers/instances.py (+88 -0)

📄 Description

Description

Summary

Adds three new API endpoints to the Terminals orchestrator that enable Open WebUI admins to manage terminal instances and inspect server configuration through the UI. This is the Terminals-side half of a two-PR effort. The companion Open WebUI PR adds the admin proxy routes and frontend UI that consume these endpoints.

Motivation

Why this is needed

The Kubernetes Operator backend (kubernetes-operator) creates and manages Terminal CRDs, but until now there was no way for administrators to:

  1. See what terminal instances are running — which users have active terminals, what policy governs each one, and what state they're in.
  2. Tear down a specific instance — for example, to reclaim resources or force-restart a misbehaving terminal.
  3. Inspect the server's configuration — what backend is in use, what resource caps are enforced, etc.

These capabilities are prerequisites for the admin UI integration in Open WebUI, where admins need visibility into deployed terminals and the ability to act on them.

Why it lives in the Terminals service

Policy CRUD already exists in terminals/routers/policy.py. Instance listing and server info are the natural complement — they let the admin see "what's configured" (policies) alongside "what's running" (instances) and "what's allowed" (server info / resource caps). Keeping these in the orchestrator means Open WebUI simply proxies to them, staying decoupled from Kubernetes details.

Changes

New file: terminals/routers/instances.py (+88 lines)

Three new endpoints, all behind verify_api_key authentication:

Endpoint Method Purpose
/api/v1/instances GET List all active terminal instances with status, resource config, timestamps
/api/v1/instances/{instance_id} DELETE Force-teardown a specific instance by ID
/api/v1/info GET Return server metadata: backend type, version, resource caps (max CPU/memory/storage), allowed images, idle timeout

Response schemas:

  • InstanceResponse — user_id, policy_id, instance_id, instance_name, status, host, port, image, cpu_limit, memory_limit, storage, idle_timeout_minutes, last_activity, created_at, message
  • InfoResponse — backend, version, max_cpu, max_memory, max_storage, allowed_images, idle_timeout_minutes

Modified: terminals/backends/base.py (+26 lines)

Added a default list_instances() method to the Backend ABC that returns instance info from the in-memory _instances tracking dict. This provides a working implementation for all backends (Docker, Local, Static) without requiring backend-specific overrides.

Each returned dict includes: user_id, policy_id, instance_id, instance_name, status, host, port. Status is fetched live via self.status().

Modified: terminals/backends/kubernetes_operator.py (+49 lines)

Overrides list_instances() to query Kubernetes directly rather than relying on in-memory tracking. This is important because:

  • The orchestrator process may restart, losing in-memory state, but Terminal CRs persist.
  • Multiple orchestrator replicas won't share in-memory state.
  • Kubernetes is the source of truth for CRD-managed resources.

The implementation:

  1. Calls list_namespaced_custom_object() with label selector app.kubernetes.io/managed-by=terminals
  2. Maps CRD fields (spec, status, metadata, labels) to the InstanceResponse schema
  3. Extracts userId, image, cpuLimit, memoryLimit, storage, idleTimeoutMinutes from spec
  4. Extracts phase, serviceName, lastActivityAt, message from status
  5. Extracts openwebui.com/user-id and openwebui.com/policy from labels

Modified: terminals/main.py (+2 lines)

Registers the new instances_router alongside the existing policy_router, both before the catch-all proxy router.

Modified: pyproject.toml (+3 lines)

Added sqlalchemy[asyncio]>=2.0.0, aiosqlite>=0.20.0, alembic>=1.13.0 to dependencies. These are required by the existing DB layer (terminals/db/session.py) but were previously missing from the declared dependencies.

Design decisions

Why a default implementation on the base class?

The base Backend uses in-memory _instances tracking (self._instances dict populated by _track/_untrack calls). Adding list_instances() there means Docker, Local, and Static backends get instance listing for free. The Kubernetes Operator backend overrides it because CRDs are the authoritative source, and in-memory tracking is best-effort for that backend.

Why /api/v1/info rather than extending /health?

/health is unauthenticated (used by load balancers and readiness probes). Server info includes configuration details that should only be visible to authenticated administrators, so it lives behind verify_api_key on a separate endpoint.

Why does the teardown endpoint accept instance_id (UID), not instance name?

The backend teardown() method already operates on instance IDs (Kubernetes UIDs for the operator backend). Using the UID avoids ambiguity if names are reused and aligns with the existing teardown() contract.

Testing

Tested end-to-end on a local kind cluster with the Kubernetes Operator backend:

  1. Policy CRUD: Created policies with varying resource limits, verified they're stored and retrievable.
  2. Instance listing: Provisioned terminals for multiple users via different policies, verified /api/v1/instances returns correct user_id, policy_id, status, and resource info for each.
  3. Instance teardown: Called DELETE /api/v1/instances/{uid}, verified the Terminal CR was deleted and the instance disappeared from subsequent list calls.
  4. Server info: Verified /api/v1/info returns the correct backend type (kubernetes-operator), resource caps from environment variables, and version.
  5. Auth: Verified all three endpoints return 401/403 without a valid API key.
  6. Frontend: Tested that the Open WebUI frontend shows the expected information about terminals spun up through the Orchestrator, including each Terminal endpoint, Policies, and Active Terminals. Screenshots of the most up-to-date implementation are present in the Open WebUI PR.

Related PRs

The Open WebUI PR adds:

  • Backend proxy routes that forward admin requests to these endpoints
  • Frontend components: policy management UI, instance monitoring table, server info display
  • Tabbed admin interface under Settings → Integrations → Terminals

The associated PR can be found in Open WebUI here: https://github.com/open-webui/open-webui/pull/23011

Contributor License Agreement

  • [ x ] By submitting this pull request, I confirm that I have read and fully agree to the Contributor License Agreement (CLA), and I am providing my contributions under its terms.

Note

Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/open-webui/terminals/pull/6 **Author:** [@james-westbrook](https://github.com/james-westbrook) **Created:** 3/25/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `feat-operator-ui-integration` --- ### 📝 Commits (1) - [`605338e`](https://github.com/open-webui/terminals/commit/605338e4abbc4283ec0f4700dca47b44ca425869) feat: enable Terminals K8s Orchestrator policy management in Open WebUI ### 📊 Changes **5 files changed** (+168 additions, -0 deletions) <details> <summary>View changed files</summary> 📝 `pyproject.toml` (+3 -0) 📝 `terminals/backends/base.py` (+26 -0) 📝 `terminals/backends/kubernetes_operator.py` (+49 -0) 📝 `terminals/main.py` (+2 -0) ➕ `terminals/routers/instances.py` (+88 -0) </details> ### 📄 Description # Description ## Summary Adds three new API endpoints to the Terminals orchestrator that enable Open WebUI admins to manage terminal instances and inspect server configuration through the UI. This is the Terminals-side half of a two-PR effort. The companion [Open WebUI PR](https://github.com/open-webui/open-webui/pull/23011) adds the admin proxy routes and frontend UI that consume these endpoints. ## Motivation ### Why this is needed The Kubernetes Operator backend (`kubernetes-operator`) creates and manages Terminal CRDs, but until now there was no way for administrators to: 1. **See what terminal instances are running** — which users have active terminals, what policy governs each one, and what state they're in. 2. **Tear down a specific instance** — for example, to reclaim resources or force-restart a misbehaving terminal. 3. **Inspect the server's configuration** — what backend is in use, what resource caps are enforced, etc. These capabilities are prerequisites for the admin UI integration in Open WebUI, where admins need visibility into deployed terminals and the ability to act on them. ### Why it lives in the Terminals service Policy CRUD already exists in `terminals/routers/policy.py`. Instance listing and server info are the natural complement — they let the admin see "what's configured" (policies) alongside "what's running" (instances) and "what's allowed" (server info / resource caps). Keeping these in the orchestrator means Open WebUI simply proxies to them, staying decoupled from Kubernetes details. ## Changes ### New file: `terminals/routers/instances.py` (+88 lines) Three new endpoints, all behind `verify_api_key` authentication: | Endpoint | Method | Purpose | |---|---|---| | `/api/v1/instances` | GET | List all active terminal instances with status, resource config, timestamps | | `/api/v1/instances/{instance_id}` | DELETE | Force-teardown a specific instance by ID | | `/api/v1/info` | GET | Return server metadata: backend type, version, resource caps (max CPU/memory/storage), allowed images, idle timeout | **Response schemas:** - `InstanceResponse` — `user_id`, `policy_id`, `instance_id`, `instance_name`, `status`, `host`, `port`, `image`, `cpu_limit`, `memory_limit`, `storage`, `idle_timeout_minutes`, `last_activity`, `created_at`, `message` - `InfoResponse` — `backend`, `version`, `max_cpu`, `max_memory`, `max_storage`, `allowed_images`, `idle_timeout_minutes` ### Modified: `terminals/backends/base.py` (+26 lines) Added a default `list_instances()` method to the `Backend` ABC that returns instance info from the in-memory `_instances` tracking dict. This provides a working implementation for all backends (Docker, Local, Static) without requiring backend-specific overrides. Each returned dict includes: `user_id`, `policy_id`, `instance_id`, `instance_name`, `status`, `host`, `port`. Status is fetched live via `self.status()`. ### Modified: `terminals/backends/kubernetes_operator.py` (+49 lines) Overrides `list_instances()` to query Kubernetes directly rather than relying on in-memory tracking. This is important because: - The orchestrator process may restart, losing in-memory state, but Terminal CRs persist. - Multiple orchestrator replicas won't share in-memory state. - Kubernetes is the source of truth for CRD-managed resources. The implementation: 1. Calls `list_namespaced_custom_object()` with label selector `app.kubernetes.io/managed-by=terminals` 2. Maps CRD fields (spec, status, metadata, labels) to the `InstanceResponse` schema 3. Extracts `userId`, `image`, `cpuLimit`, `memoryLimit`, `storage`, `idleTimeoutMinutes` from spec 4. Extracts `phase`, `serviceName`, `lastActivityAt`, `message` from status 5. Extracts `openwebui.com/user-id` and `openwebui.com/policy` from labels ### Modified: `terminals/main.py` (+2 lines) Registers the new `instances_router` alongside the existing `policy_router`, both before the catch-all proxy router. ### Modified: `pyproject.toml` (+3 lines) Added `sqlalchemy[asyncio]>=2.0.0`, `aiosqlite>=0.20.0`, `alembic>=1.13.0` to dependencies. These are required by the existing DB layer (`terminals/db/session.py`) but were previously missing from the declared dependencies. ## Design decisions ### Why a default implementation on the base class? The base `Backend` uses in-memory `_instances` tracking (`self._instances` dict populated by `_track`/`_untrack` calls). Adding `list_instances()` there means Docker, Local, and Static backends get instance listing for free. The Kubernetes Operator backend overrides it because CRDs are the authoritative source, and in-memory tracking is best-effort for that backend. ### Why `/api/v1/info` rather than extending `/health`? `/health` is unauthenticated (used by load balancers and readiness probes). Server info includes configuration details that should only be visible to authenticated administrators, so it lives behind `verify_api_key` on a separate endpoint. ### Why does the teardown endpoint accept `instance_id` (UID), not instance name? The backend `teardown()` method already operates on instance IDs (Kubernetes UIDs for the operator backend). Using the UID avoids ambiguity if names are reused and aligns with the existing `teardown()` contract. ## Testing Tested end-to-end on a local `kind` cluster with the Kubernetes Operator backend: 1. **Policy CRUD**: Created policies with varying resource limits, verified they're stored and retrievable. 2. **Instance listing**: Provisioned terminals for multiple users via different policies, verified `/api/v1/instances` returns correct user_id, policy_id, status, and resource info for each. 3. **Instance teardown**: Called `DELETE /api/v1/instances/{uid}`, verified the Terminal CR was deleted and the instance disappeared from subsequent list calls. 4. **Server info**: Verified `/api/v1/info` returns the correct backend type (`kubernetes-operator`), resource caps from environment variables, and version. 5. **Auth**: Verified all three endpoints return 401/403 without a valid API key. 6. **Frontend**: Tested that the Open WebUI frontend shows the expected information about terminals spun up through the Orchestrator, including each Terminal endpoint, Policies, and Active Terminals. Screenshots of the most up-to-date implementation are present in the Open WebUI PR. # Related PRs The Open WebUI PR adds: - Backend proxy routes that forward admin requests to these endpoints - Frontend components: policy management UI, instance monitoring table, server info display - Tabbed admin interface under Settings → Integrations → Terminals The associated PR can be found in Open WebUI here: https://github.com/open-webui/open-webui/pull/23011 --- # Contributor License Agreement <!-- 🚨 DO NOT DELETE THE TEXT BELOW 🚨 Keep the "Contributor License Agreement" confirmation text intact. Deleting it will trigger the CLA-Bot to INVALIDATE your PR. Your PR will NOT be reviewed or merged until you check the box below confirming that you have read and agree to the terms of the CLA. --> - [ x ] By submitting this pull request, I confirm that I have read and fully agree to the [Contributor License Agreement (CLA)](./CLA.md), and I am providing my contributions under its terms. > [!NOTE] > Deleting the CLA section will lead to immediate closure of your PR and it will not be merged in. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-18 21:13:41 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
open-webui/terminals#39
No description provided.