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:
|
/groups/{name}/users:
|
||||||
post:
|
post:
|
||||||
operationId: get-group-users
|
operationId: post-group-users
|
||||||
summary: Add users to a group
|
summary: Add users to a group
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: "#/components/parameters/groupName"
|
- $ref: "#/components/parameters/groupName"
|
||||||
|
@@ -11,6 +11,7 @@ from pathlib import Path
|
|||||||
from urllib.request import urlretrieve
|
from urllib.request import urlretrieve
|
||||||
|
|
||||||
from docutils import nodes
|
from docutils import nodes
|
||||||
|
from ruamel.yaml import YAML
|
||||||
from sphinx.directives.other import SphinxDirective
|
from sphinx.directives.other import SphinxDirective
|
||||||
from sphinx.util import logging
|
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 let's use use `foo` instead of ``foo`` in rST
|
||||||
default_role = "literal"
|
default_role = "literal"
|
||||||
|
|
||||||
|
docs = Path(__file__).parent.parent.absolute()
|
||||||
|
docs_source = docs / "source"
|
||||||
|
rest_api_yaml = docs_source / "_static" / "rest-api.yml"
|
||||||
|
|
||||||
|
|
||||||
# -- MyST configuration ------------------------------------------------------
|
# -- MyST configuration ------------------------------------------------------
|
||||||
# ref: https://myst-parser.readthedocs.io/en/latest/configuration.html
|
# ref: https://myst-parser.readthedocs.io/en/latest/configuration.html
|
||||||
@@ -123,6 +128,43 @@ class HelpAllDirective(SphinxDirective):
|
|||||||
return [par]
|
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"]
|
templates_path = ["_templates"]
|
||||||
|
|
||||||
|
|
||||||
@@ -147,6 +189,7 @@ def setup(app):
|
|||||||
app.add_css_file("custom.css")
|
app.add_css_file("custom.css")
|
||||||
app.add_directive("jupyterhub-generate-config", ConfigDirective)
|
app.add_directive("jupyterhub-generate-config", ConfigDirective)
|
||||||
app.add_directive("jupyterhub-help-all", HelpAllDirective)
|
app.add_directive("jupyterhub-help-all", HelpAllDirective)
|
||||||
|
app.add_directive("jupyterhub-rest-api-links", RestAPILinksDirective)
|
||||||
|
|
||||||
|
|
||||||
# -- Read The Docs -----------------------------------------------------------
|
# -- Read The Docs -----------------------------------------------------------
|
||||||
@@ -155,8 +198,7 @@ def setup(app):
|
|||||||
# pre-requisite steps for "make html" from here if needed.
|
# pre-requisite steps for "make html" from here if needed.
|
||||||
#
|
#
|
||||||
if os.environ.get("READTHEDOCS"):
|
if os.environ.get("READTHEDOCS"):
|
||||||
docs = os.path.dirname(os.path.dirname(__file__))
|
subprocess.check_call(["make", "metrics", "scopes"], cwd=str(docs))
|
||||||
subprocess.check_call(["make", "metrics", "scopes"], cwd=docs)
|
|
||||||
|
|
||||||
|
|
||||||
# -- Spell checking ----------------------------------------------------------
|
# -- Spell checking ----------------------------------------------------------
|
||||||
@@ -211,7 +253,6 @@ linkcheck_ignore = [
|
|||||||
"https://github.com/jupyterhub/jupyterhub/pull/", # too many PRs in changelog
|
"https://github.com/jupyterhub/jupyterhub/pull/", # too many PRs in changelog
|
||||||
"https://github.com/jupyterhub/jupyterhub/compare/", # too many comparisons 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"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
|
r"https://linux.die.net/.*", # linux.die.net seems to block requests from CI with 403 sometimes
|
||||||
]
|
]
|
||||||
linkcheck_anchors_ignore = [
|
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).
|
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:
|
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.
|
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).
|
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
|
### Use requests
|
||||||
|
|
||||||
Using the popular Python [requests](https://docs.python-requests.org)
|
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
|
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
|
```python
|
||||||
@@ -220,7 +220,7 @@ r.raise_for_status()
|
|||||||
users = r.json()
|
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:
|
process is very similar:
|
||||||
|
|
||||||
```python
|
```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
|
Pagination is available through the `offset` and `limit` query parameters on
|
||||||
list endpoints, which can be used to return ideally sized windows of results.
|
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.
|
endpoint to fetch the first 20 records.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
@@ -353,12 +353,18 @@ hub:
|
|||||||
|
|
||||||
With that setting in place, a new named-server is activated like this:
|
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
|
```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/<serverA>"
|
||||||
curl -X POST -H "Authorization: token <token>" "http://127.0.0.1:8081/hub/api/users/<user>/servers/<serverB>"
|
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
|
### Some caveats for using named-servers
|
||||||
|
|
||||||
|
@@ -12,3 +12,14 @@ redoc_options:
|
|||||||
|
|
||||||
NOTE: The contents of this markdown file are not used,
|
NOTE: The contents of this markdown file are not used,
|
||||||
this page is entirely generated from `_templates/redoc.html` and `_static/rest-api.yml`
|
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).
|
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.
|
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:
|
The JSON body should specify what permissions to grant and whom to grant them to:
|
||||||
|
|
||||||
```
|
```python
|
||||||
POST /api/shares/:username/:servername
|
|
||||||
{
|
{
|
||||||
"scopes": [],
|
"scopes": [],
|
||||||
"user": "username", # or:
|
"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.
|
Send a PATCH request to `/api/shares/:username/:servername` to revoke permissions.
|
||||||
|
|
||||||
```
|
```{parsed-literal}
|
||||||
PATCH /api/shares/:username/:servername
|
[PATCH /api/shares/:username/:servername](rest-api-patch-shares-server)
|
||||||
```
|
```
|
||||||
|
|
||||||
The JSON body should specify the scopes to revoke
|
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.
|
A DELETE request will revoke all shared access permissions for the given server.
|
||||||
|
|
||||||
```
|
```{parsed-literal}
|
||||||
DELETE /api/shares/:username/:servername
|
[DELETE /api/shares/:username/:servername](rest-api-delete-shares-server)
|
||||||
```
|
```
|
||||||
|
|
||||||
### View shares for a server
|
### View shares for a server
|
||||||
|
|
||||||
To view shares for a given server, you need the permission `read:shares` with the appropriate _server_ filter.
|
To view shares for a given server, you need the permission `read:shares` with the appropriate _server_ filter.
|
||||||
|
|
||||||
```
|
```{parsed-literal}
|
||||||
GET /api/shares/:username/:servername
|
[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:
|
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
|
### 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.
|
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
|
||||||
|
|
||||||
# or
|
|
||||||
|
|
||||||
GET /api/groups/:groupname/shared
|
|
||||||
|
|
||||||
|
```{parsed-literal}
|
||||||
|
[GET /api/groups/:groupname/shared](rest-api-get-group-shared)
|
||||||
```
|
```
|
||||||
|
|
||||||
These are paginated endpoints.
|
These are paginated endpoints.
|
||||||
|
|
||||||
### Access permission for a single user on a single server
|
### 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
|
||||||
|
|
||||||
# or
|
|
||||||
|
|
||||||
GET /api/groups/:groupname/shared/:ownername/:servername
|
|
||||||
|
|
||||||
|
```{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`,
|
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.
|
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.
|
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
|
||||||
|
|
||||||
# or
|
|
||||||
|
|
||||||
DELETE /api/groups/:groupname/shared/:ownername/:servername
|
|
||||||
|
|
||||||
|
```
|
||||||
|
[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.
|
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.
|
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
|
## Share via invitation code
|
||||||
|
|
||||||
@@ -259,8 +263,8 @@ Share codes are much like shares, except:
|
|||||||
|
|
||||||
To create a share code:
|
To create a share code:
|
||||||
|
|
||||||
```
|
```{parsed-literal}
|
||||||
POST /api/share-code/:username/:servername
|
[POST /api/share-code/:username/:servername](rest-api-post-share-code)
|
||||||
```
|
```
|
||||||
|
|
||||||
where the body should include the scopes to be granted and expiration.
|
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
|
### 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
|
You can see existing invitations for
|
||||||
|
|
||||||
```
|
```{parsed-literal}
|
||||||
GET /hub/api/share-codes/:username/:servername
|
[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):
|
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
|
### Revoking invitations
|
||||||
|
|
||||||
If you've finished inviting users to a server, you can revoke all invitations with:
|
If you've finished inviting users to a server, you can revoke all invitations with:
|
||||||
|
|
||||||
```
|
```{parsed-literal}
|
||||||
DELETE /hub/api/share-codes/:username/:servername
|
[DELETE /hub/api/share-codes/:username/:servername](rest-api-delete-share-code)
|
||||||
```
|
```
|
||||||
|
|
||||||
or revoke a single invitation code:
|
or revoke a single invitation code:
|
||||||
|
Reference in New Issue
Block a user