mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-13 13:03:01 +00:00
add rest-api-{operation}
xref targets, so we can link to our own REST API
This commit is contained in:
@@ -773,7 +773,7 @@ paths:
|
||||
|
||||
/groups/{name}/users:
|
||||
post:
|
||||
operationId: get-group-users
|
||||
operationId: post-group-users
|
||||
summary: Add users to a group
|
||||
parameters:
|
||||
- $ref: "#/components/parameters/groupName"
|
||||
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
||||
from urllib.request import urlretrieve
|
||||
|
||||
from docutils import nodes
|
||||
from ruamel.yaml import YAML
|
||||
from sphinx.directives.other import SphinxDirective
|
||||
from sphinx.util import logging
|
||||
|
||||
@@ -46,6 +47,10 @@ source_suffix = [".md"]
|
||||
# default_role let's use use `foo` instead of ``foo`` in rST
|
||||
default_role = "literal"
|
||||
|
||||
docs = Path(__file__).parent.parent.absolute()
|
||||
docs_source = docs / "source"
|
||||
rest_api_yaml = docs_source / "_static" / "rest-api.yml"
|
||||
|
||||
|
||||
# -- MyST configuration ------------------------------------------------------
|
||||
# ref: https://myst-parser.readthedocs.io/en/latest/configuration.html
|
||||
@@ -123,6 +128,43 @@ class HelpAllDirective(SphinxDirective):
|
||||
return [par]
|
||||
|
||||
|
||||
class RestAPILinksDirective(SphinxDirective):
|
||||
"""Directive to populate link targets for the REST API
|
||||
|
||||
The resulting nodes resolve xref targets,
|
||||
but are not actually rendered in the final result
|
||||
which is handled by a custom template.
|
||||
"""
|
||||
|
||||
hast_content = False
|
||||
required_arguments = 0
|
||||
optional_arguments = 0
|
||||
final_argument_whitespace = False
|
||||
option_spec = {}
|
||||
|
||||
def run(self):
|
||||
targets = []
|
||||
yaml = YAML(typ="safe")
|
||||
with rest_api_yaml.open() as f:
|
||||
api = yaml.load(f)
|
||||
for path, path_spec in api["paths"].items():
|
||||
for method, operation in path_spec.items():
|
||||
operation_id = operation.get("operationId")
|
||||
if not operation_id:
|
||||
logger.warning(f"No operation id for {method} {path}")
|
||||
continue
|
||||
# 'id' is the id on the page (must match redoc anchor)
|
||||
# 'name' is the name of the ref for use in our documents
|
||||
target = nodes.target(
|
||||
ids=[f"operation/{operation_id}"],
|
||||
names=[f"rest-api-{operation_id}"],
|
||||
)
|
||||
targets.append(target)
|
||||
self.state.document.note_explicit_target(target, target)
|
||||
|
||||
return targets
|
||||
|
||||
|
||||
templates_path = ["_templates"]
|
||||
|
||||
|
||||
@@ -147,6 +189,7 @@ def setup(app):
|
||||
app.add_css_file("custom.css")
|
||||
app.add_directive("jupyterhub-generate-config", ConfigDirective)
|
||||
app.add_directive("jupyterhub-help-all", HelpAllDirective)
|
||||
app.add_directive("jupyterhub-rest-api-links", RestAPILinksDirective)
|
||||
|
||||
|
||||
# -- Read The Docs -----------------------------------------------------------
|
||||
@@ -155,8 +198,7 @@ def setup(app):
|
||||
# pre-requisite steps for "make html" from here if needed.
|
||||
#
|
||||
if os.environ.get("READTHEDOCS"):
|
||||
docs = os.path.dirname(os.path.dirname(__file__))
|
||||
subprocess.check_call(["make", "metrics", "scopes"], cwd=docs)
|
||||
subprocess.check_call(["make", "metrics", "scopes"], cwd=str(docs))
|
||||
|
||||
|
||||
# -- Spell checking ----------------------------------------------------------
|
||||
@@ -211,7 +253,6 @@ linkcheck_ignore = [
|
||||
"https://github.com/jupyterhub/jupyterhub/pull/", # too many PRs in changelog
|
||||
"https://github.com/jupyterhub/jupyterhub/compare/", # too many comparisons in changelog
|
||||
r"https?://(localhost|127.0.0.1).*", # ignore localhost references in auto-links
|
||||
r".*/rest-api.html#.*", # ignore javascript-resolved internal rest-api links
|
||||
r"https://linux.die.net/.*", # linux.die.net seems to block requests from CI with 403 sometimes
|
||||
]
|
||||
linkcheck_anchors_ignore = [
|
||||
|
@@ -82,7 +82,7 @@ Additionally, there is usually _very_ little load on the database itself.
|
||||
By far the most taxing activity on the database is the 'list all users' endpoint, primarily used by the [idle-culling service](https://github.com/jupyterhub/jupyterhub-idle-culler).
|
||||
Database-based optimizations have been added to make even these operations feasible for large numbers of users:
|
||||
|
||||
1. State filtering on [GET /hub/api/users?state=active](../reference/rest-api.html#/default/get_users){.external},
|
||||
1. State filtering on [GET /hub/api/users?state=active](rest-api-get-users),
|
||||
which limits the number of results in the query to only the relevant subset (added in JupyterHub 1.3), rather than all users.
|
||||
2. [Pagination](api-pagination) of all list endpoints, allowing the request of a large number of resources to be more fairly balanced with other Hub activities across multiple requests (added in 2.0).
|
||||
|
||||
|
@@ -202,7 +202,7 @@ Authorization header.
|
||||
### Use requests
|
||||
|
||||
Using the popular Python [requests](https://docs.python-requests.org)
|
||||
library, an API GET request is made, and the request sends an API token for
|
||||
library, an API GET request is made to [/users](rest-api-get-users), and the request sends an API token for
|
||||
authorization. The response contains information about the users, here's example code to make an API request for the users of a JupyterHub deployment
|
||||
|
||||
```python
|
||||
@@ -220,7 +220,7 @@ r.raise_for_status()
|
||||
users = r.json()
|
||||
```
|
||||
|
||||
This example provides a slightly more complicated request, yet the
|
||||
This example provides a slightly more complicated request (to [/groups/formgrade-data301/users](rest-api-post-group-users)), yet the
|
||||
process is very similar:
|
||||
|
||||
```python
|
||||
@@ -254,7 +254,7 @@ provided by notebook servers managed by JupyterHub if it has the necessary `acce
|
||||
|
||||
Pagination is available through the `offset` and `limit` query parameters on
|
||||
list endpoints, which can be used to return ideally sized windows of results.
|
||||
Here's example code demonstrating pagination on the `GET /users`
|
||||
Here's example code demonstrating pagination on the [`GET /users`](rest-api-get-users)
|
||||
endpoint to fetch the first 20 records.
|
||||
|
||||
```python
|
||||
@@ -353,12 +353,18 @@ hub:
|
||||
|
||||
With that setting in place, a new named-server is activated like this:
|
||||
|
||||
```{parsed-literal}
|
||||
[POST /api/users/:username/servers/:servername](rest-api-post-user-server-name)
|
||||
```
|
||||
|
||||
e.g.
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverA>"
|
||||
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverB>"
|
||||
```
|
||||
|
||||
The same servers can be stopped by substituting `DELETE` for `POST` above.
|
||||
The same servers can be [stopped](rest-api-delete-user-server-name) by substituting `DELETE` for `POST` above.
|
||||
|
||||
### Some caveats for using named-servers
|
||||
|
||||
|
@@ -12,3 +12,14 @@ redoc_options:
|
||||
|
||||
NOTE: The contents of this markdown file are not used,
|
||||
this page is entirely generated from `_templates/redoc.html` and `_static/rest-api.yml`
|
||||
|
||||
REST API methods can be linked by their operationId in rest-api.yml,
|
||||
prefixed with `rest-api-`, e.g.
|
||||
|
||||
```markdown
|
||||
you cat [GET /api/users](rest-api-get-users)
|
||||
```
|
||||
|
||||
```{jupyterhub-rest-api-links}
|
||||
|
||||
```
|
||||
|
@@ -75,10 +75,14 @@ You can only modify access to one server at a time.
|
||||
To grant access to a particular user, in addition to `shares`, the granter must have at least `read:user:name` permission for the target user (or `read:group:name` if it's a group).
|
||||
|
||||
Send a POST request to `/api/shares/:username/:servername` to grant permissions.
|
||||
|
||||
```{parsed-literal}
|
||||
[POST /api/shares/:username/:servername](rest-api-post-shares-server)
|
||||
```
|
||||
|
||||
The JSON body should specify what permissions to grant and whom to grant them to:
|
||||
|
||||
```
|
||||
POST /api/shares/:username/:servername
|
||||
```python
|
||||
{
|
||||
"scopes": [],
|
||||
"user": "username", # or:
|
||||
@@ -100,8 +104,8 @@ You can only modify access to one server at a time.
|
||||
|
||||
Send a PATCH request to `/api/shares/:username/:servername` to revoke permissions.
|
||||
|
||||
```
|
||||
PATCH /api/shares/:username/:servername
|
||||
```{parsed-literal}
|
||||
[PATCH /api/shares/:username/:servername](rest-api-patch-shares-server)
|
||||
```
|
||||
|
||||
The JSON body should specify the scopes to revoke
|
||||
@@ -121,16 +125,16 @@ If `scopes` is empty or unspecified, _all_ scopes are revoked from the target us
|
||||
|
||||
A DELETE request will revoke all shared access permissions for the given server.
|
||||
|
||||
```
|
||||
DELETE /api/shares/:username/:servername
|
||||
```{parsed-literal}
|
||||
[DELETE /api/shares/:username/:servername](rest-api-delete-shares-server)
|
||||
```
|
||||
|
||||
### View shares for a server
|
||||
|
||||
To view shares for a given server, you need the permission `read:shares` with the appropriate _server_ filter.
|
||||
|
||||
```
|
||||
GET /api/shares/:username/:servername
|
||||
```{parsed-literal}
|
||||
[GET /api/shares/:username/:servername](rest-api-get-shares-server)
|
||||
```
|
||||
|
||||
This is a paginated endpoint, so responses has `items` as a list of Share models, and `_pagination` for information about retrieving all shares if there are many:
|
||||
@@ -158,34 +162,34 @@ This is a paginated endpoint, so responses has `items` as a list of Share models
|
||||
}
|
||||
```
|
||||
|
||||
see the [rest-api](rest-api) for full details of the response models.
|
||||
see the [rest-api](rest-api-get-shares-server) for full details of the response models.
|
||||
|
||||
### View servers shared with user or group
|
||||
|
||||
To review servers shared with a given user or group, you need the permission `read:users:shares` or `read:groups:shares` with the appropriate _user_ or _group_ filter.
|
||||
|
||||
```{parsed-literal}
|
||||
[GET /api/users/:username/shared](rest-api-get-user-shared)
|
||||
```
|
||||
|
||||
GET /api/users/:username/shared
|
||||
|
||||
# or
|
||||
|
||||
GET /api/groups/:groupname/shared
|
||||
or
|
||||
|
||||
```{parsed-literal}
|
||||
[GET /api/groups/:groupname/shared](rest-api-get-group-shared)
|
||||
```
|
||||
|
||||
These are paginated endpoints.
|
||||
|
||||
### Access permission for a single user on a single server
|
||||
|
||||
```{parsed-literal}
|
||||
[GET /api/users/:username/shared/:ownername/:servername](rest-api-get-user-shared-server)
|
||||
```
|
||||
|
||||
GET /api/users/:username/shared/:ownername/:servername
|
||||
|
||||
# or
|
||||
|
||||
GET /api/groups/:groupname/shared/:ownername/:servername
|
||||
or
|
||||
|
||||
```{parsed-literal}
|
||||
[GET /api/groups/:groupname/shared/:ownername/:servername](rest-api-get-group-shared-server)
|
||||
```
|
||||
|
||||
will return the _single_ Share info for the given user or group for the server specified by `ownername/servername`,
|
||||
@@ -197,14 +201,14 @@ To revoke sharing permissions from the perspective of the user or group being sh
|
||||
you need the permissions `users:shares` or `groups:shares` with the appropriate _user_ or _group_ filter.
|
||||
This allows users to 'leave' shared servers, without needing permission to manage the server's sharing permissions.
|
||||
|
||||
```
|
||||
[DELETE /api/users/:username/shared/:ownername/:servername](rest-api-delete-user-shared-server)
|
||||
```
|
||||
|
||||
DELETE /api/users/:username/shared/:ownername/:servername
|
||||
|
||||
# or
|
||||
|
||||
DELETE /api/groups/:groupname/shared/:ownername/:servername
|
||||
or
|
||||
|
||||
```
|
||||
[DELETE /api/groups/:groupname/shared/:ownername/:servername](rest-api-delete-group-shared-server)
|
||||
```
|
||||
|
||||
will revoke all permissions granted to the user or group for the specified server.
|
||||
@@ -237,7 +241,7 @@ A Share returned in the REST API has the following structure:
|
||||
|
||||
where exactly one of `user` and `group` is not null and the other is null.
|
||||
|
||||
See the [rest-api](rest-api) for full details of the response models.
|
||||
See the [rest-api](rest-api-get-shares-server) for full details of the response models.
|
||||
|
||||
## Share via invitation code
|
||||
|
||||
@@ -259,8 +263,8 @@ Share codes are much like shares, except:
|
||||
|
||||
To create a share code:
|
||||
|
||||
```
|
||||
POST /api/share-code/:username/:servername
|
||||
```{parsed-literal}
|
||||
[POST /api/share-code/:username/:servername](rest-api-post-share-code)
|
||||
```
|
||||
|
||||
where the body should include the scopes to be granted and expiration.
|
||||
@@ -288,7 +292,7 @@ The response contains the code itself:
|
||||
}
|
||||
```
|
||||
|
||||
See the [rest-api](rest-api) for full details of the response models.
|
||||
See the [rest-api](rest-api-post-share-code) for full details of the response models.
|
||||
|
||||
### Accepting sharing invitations
|
||||
|
||||
@@ -308,8 +312,8 @@ you will need to contact the owner of the server to start it.
|
||||
|
||||
You can see existing invitations for
|
||||
|
||||
```
|
||||
GET /hub/api/share-codes/:username/:servername
|
||||
```{parsed-literal}
|
||||
[GET /hub/api/share-codes/:username/:servername](rest-api-get-share-codes-server)
|
||||
```
|
||||
|
||||
which produces a paginated list of share codes (_excluding_ the codes themselves, which are not stored by jupyterhub):
|
||||
@@ -373,14 +377,14 @@ and the `id` that can be used for revocation:
|
||||
}
|
||||
```
|
||||
|
||||
see the [rest-api](rest-api) for full details of the response models.
|
||||
see the [rest-api](rest-api-get-share-codes-server) for full details of the response models.
|
||||
|
||||
### Revoking invitations
|
||||
|
||||
If you've finished inviting users to a server, you can revoke all invitations with:
|
||||
|
||||
```
|
||||
DELETE /hub/api/share-codes/:username/:servername
|
||||
```{parsed-literal}
|
||||
[DELETE /hub/api/share-codes/:username/:servername](rest-api-delete-share-code)
|
||||
```
|
||||
|
||||
or revoke a single invitation code:
|
||||
|
Reference in New Issue
Block a user