notebooks/workspaces/backend
Andy Stoneberg 843e5c3c48
feat(ws): add workspace pause actions backend API (#340)
related: #298

- Added PauseActionWorkspaceHandler to handle pausing or unpausing a given workspace
- Introduced single new route for starting and pausing workspaces in the API.
    - `api/v1/workspaces/{namespace}/{name}/actions/pause`
			- pausing or unpausing operation is specified in the request payload
- Created a new WorkspaceActionPauseEnvelope type for successful responses.
- Leveraging JSONPatch / client.RawPatch to ensure Workspace in "valid state" before attempting action
    - for `start`: `spec.paused` must be `true`, and `status.state` must be `Paused`
    - for `pause`: `spec.paused` must be `false`
        - note: I would love to have a `status.state` check here of `status.state != Paused`, but that type of comparison is not supported in [JSONPatch](https://datatracker.ietf.org/doc/html/rfc6902#section-4.6)
- Added tests for the new API, including success and error cases.
- Updated README/OpenAPI documentation to include the new endpoints.

---

As an interesting "edge case" worth calling out, the following payload is currently honored by the API:
```
{
  "data": {}
}
```

Given the `WorkspaceActionPause` struct is simply `{"paused": true|false}`, the "empty" Envelope presented above deserializes the JSON using the zero value of `bool` (which is `false`).

Our validation today is always performed against the **deserialized** object, and as such impossible to distinguish the following cases:
```
{
  "data": {}
}
```

vs

```
{
  "data": {
    "paused": false
  }
}
```

The effort and (relative) complexity to prevent this and return a `422` in this scenario was not deemed "worth it" for the time being.  As a result, a test case has been added for this specific scenario to at minimum document this "strange" behavior.
- Clients, however, should **NOT** rely on this behavior and always provide a fully defined `WorkspaceActionPause` JSON object to ensure future compatibility.

Signed-off-by: Andy Stoneberg <astonebe@redhat.com>
2025-07-24 20:05:02 +00:00
..
api feat(ws): add workspace pause actions backend API (#340) 2025-07-24 20:05:02 +00:00
cmd feat(ws): add swagger api docs to backend (#206) 2025-03-03 02:28:36 +00:00
internal feat(ws): add workspace pause actions backend API (#340) 2025-07-24 20:05:02 +00:00
openapi feat(ws): add workspace pause actions backend API (#340) 2025-07-24 20:05:02 +00:00
.dockerignore feat(ws): add kubernetes client to backend (#15) 2024-06-08 19:25:05 +00:00
.gitignore chore: update go linting + update dependencies for controller (#137) 2024-12-02 16:56:58 +00:00
.golangci.yml chore: update go linting + update dependencies for controller (#137) 2024-12-02 16:56:58 +00:00
Dockerfile fix(ws): backend dockerfile (#386) 2025-06-26 21:47:17 +00:00
Makefile fix(ws): backend dockerfile (#386) 2025-06-26 21:47:17 +00:00
OWNERS chore: add OWNERS files with reviewers and labels (#450) 2025-06-26 21:42:17 +00:00
README.md feat(ws): add workspace pause actions backend API (#340) 2025-07-24 20:05:02 +00:00
go.mod feat(ws): add swagger api docs to backend (#206) 2025-03-03 02:28:36 +00:00
go.sum feat(ws): add swagger api docs to backend (#206) 2025-03-03 02:28:36 +00:00

README.md

Kubeflow Workspaces Backend

The Kubeflow Workspaces Backend is the backend for frontend (BFF) used by the Kubeflow Workspaces Frontend as part of Kubeflow Notebooks 2.0.

⚠️ Warning ⚠️

The Kubeflow Workspaces Backend is a work in progress and is NOT currently ready for use. We greatly appreciate any contributions.

Building and Deploying

TBD

Development

Run the following command to build the BFF:

make build

After building it, you can run our app with:

make run

If you want to use a different port:

make run PORT=8000

Endpoints

URL Pattern Handler Action
GET /api/v1/healthcheck healthcheck_handler Show application information
GET /api/v1/namespaces namespaces_handler Get all Namespaces
GET /api/v1/swagger/ swagger_handler Swagger API documentation
GET /api/v1/workspaces workspaces_handler Get all Workspaces
GET /api/v1/workspaces/{namespace} workspaces_handler Get all Workspaces from a namespace
POST /api/v1/workspaces/{namespace} workspaces_handler Create a Workspace in a given namespace
GET /api/v1/workspaces/{namespace}/{name} workspaces_handler Get a Workspace entity
PATCH /api/v1/workspaces/{namespace}/{name} TBD Patch a Workspace entity
PUT /api/v1/workspaces/{namespace}/{name} TBD Update a Workspace entity
DELETE /api/v1/workspaces/{namespace}/{name} workspaces_handler Delete a Workspace entity
POST /api/v1/workspaces/{namespace}/{name}/actions/pause workspace_actions_handler Set paused state of a workspace
GET /api/v1/workspacekinds workspacekinds_handler Get all WorkspaceKind
POST /api/v1/workspacekinds TBD Create a WorkspaceKind
GET /api/v1/workspacekinds/{name} workspacekinds_handler Get a WorkspaceKind entity
PATCH /api/v1/workspacekinds/{name} TBD Patch a WorkspaceKind entity
PUT /api/v1/workspacekinds/{name} TBD Update a WorkspaceKind entity
DELETE /api/v1/workspacekinds/{name} TBD Delete a WorkspaceKind entity

Sample local calls

Healthcheck:

# GET /api/v1/healthcheck
curl -i localhost:4000/api/v1/healthcheck

List all Namespaces:

# GET /api/v1/namespaces
curl -i localhost:4000/api/v1/namespaces

List all Workspaces:

# GET /api/v1/workspaces/
curl -i localhost:4000/api/v1/workspaces

List all Workspaces in a Namespace:

# GET /api/v1/workspaces/{namespace}
curl -i localhost:4000/api/v1/workspaces/default

Create a Workspace:

# POST /api/v1/workspaces/{namespace}
curl -X POST http://localhost:4000/api/v1/workspaces/default \
    -H "Content-Type: application/json" \
    -d '{
    "data": {
        "name": "dora",
        "kind": "jupyterlab",
        "paused": false,
        "deferUpdates": false,
        "podTemplate": {
            "podMetadata": {
                "labels": {
                    "app": "dora"
                },
                "annotations": {
                    "app": "dora"
                }
            },
            "volumes": {
                "home": "workspace-home-bella",
                "data": [
                    {
                        "pvcName": "workspace-data-bella",
                        "mountPath": "/data/my-data",
                        "readOnly": false
                    }
                ]
            },
            "options": {
                "imageConfig": "jupyterlab_scipy_190",
                "podConfig": "tiny_cpu"
            }
        }
    }
}'

Get a Workspace:

# GET /api/v1/workspaces/{namespace}/{name}
curl -i localhost:4000/api/v1/workspaces/default/dora

Pause a Workspace:

# POST /api/v1/workspaces/{namespace}/{name}/actions/pause
curl -X POST localhost:4000/api/v1/workspaces/default/dora/actions/pause \
    -H "Content-Type: application/json" \
    -d '{
    "data": {
        "paused": true
    }
}'

Start a Workspace:

# POST /api/v1/workspaces/{namespace}/{name}/actions/pause
curl -X POST localhost:4000/api/v1/workspaces/default/dora/actions/pause \
    -H "Content-Type: application/json" \
    -d '{
    "data": {
        "paused": false
    }
}'

Delete a Workspace:

# DELETE /api/v1/workspaces/{namespace}/{name}
curl -X DELETE localhost:4000/api/v1/workspaces/default/dora

List all WorkspaceKinds:

# GET /api/v1/workspacekinds
curl -i localhost:4000/api/v1/workspacekinds

Get a WorkspaceKind:

# GET /api/v1/workspacekinds/{name}
curl -i localhost:4000/api/v1/workspacekinds/jupyterlab