Skip to content

Commit 058200a

Browse files
authored
chore(service): improve api docs (#2103)
1 parent 84b6d0f commit 058200a

File tree

8 files changed

+172
-46
lines changed

8 files changed

+172
-46
lines changed

docs/gensidebar.py

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def write_external(desc, link):
8585

8686
toctree(None, max_depth=3, hidden=True, include_hidden=False)
8787
write("Renku Client", "introduction", "renku-python")
88+
write("Renku Service", "service", "renku-python")
8889

8990
toctree(None, max_depth=1, hidden=True)
9091
write("Get in touch", "get_in_touch", "renku")

docs/service.rst

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
..
2+
Copyright 2017-2021 - Swiss Data Science Center (SDSC)
3+
A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
18+
Renku Core Service
19+
==================
20+
21+
The Renku Core service exposes a functionality similar to the Renku CLI via a
22+
JSON-RPC API.
23+
24+
25+
API Specification
26+
-----------------
27+
28+
To explore the API documentation and test the current API against a running
29+
instance of Renku you can use the `Swagger UI on renkulab.io
30+
<https://renkulab.io/swagger>`_.

renku/cli/service.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ def all_logs(ctx, follow, output_all, errors):
403403
@service.command(name="apispec")
404404
def apispec():
405405
"""Return the api spec."""
406-
from renku.service.entrypoint import app, get_apispec
406+
from renku.service.entrypoint import app
407+
from renku.service.views.apispec import get_apispec
407408

408409
with app.test_request_context():
409410
click.echo(get_apispec(app).to_yaml())

renku/service/config.py

+6
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,16 @@
2222

2323
import pkg_resources
2424

25+
# TODO: #2100 the git access error should have its own error code
2526
GIT_ACCESS_DENIED_ERROR_CODE = -32000
2627
GIT_UNKNOWN_ERROR_CODE = -32001
2728

2829
RENKU_EXCEPTION_ERROR_CODE = -32100
2930
REDIS_EXCEPTION_ERROR_CODE = -32200
3031

32+
# TODO: #2100 according to the JSON-RPC spec this code is reserved for "method not
33+
# found" - the invalid headers code should either be a custom code or lumped
34+
# under invalid params code
3135
INVALID_HEADERS_ERROR_CODE = -32601
3236
INVALID_PARAMS_ERROR_CODE = -32602
3337
INTERNAL_FAILURE_ERROR_CODE = -32603
@@ -66,5 +70,7 @@
6670
SERVICE_API_BASE_PATH = os.getenv("CORE_SERVICE_API_BASE_PATH", "/")
6771
# path to the swagger spec
6872
API_SPEC_URL = SERVICE_PREFIX.lstrip("/") + "/spec.json"
73+
# URL for fetching the OIDC configuration
74+
OIDC_URL = os.getenv("OIDC_URL", "/auth/realms/Renku/.well-known/openid-configuration")
6975

7076
LOGGER_CONFIG_FILE = Path(pkg_resources.resource_filename("renku", "service/logging.yaml"))

renku/service/entrypoint.py

+4-43
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@
2222
import uuid
2323

2424
import sentry_sdk
25-
from apispec import APISpec
26-
from apispec.ext.marshmallow import MarshmallowPlugin
27-
from apispec_webframeworks.flask import FlaskPlugin
2825
from flask import Flask, jsonify, request, url_for
2926
from jwt import InvalidTokenError
3027
from sentry_sdk import capture_exception
@@ -33,19 +30,12 @@
3330
from sentry_sdk.integrations.rq import RqIntegration
3431

3532
from renku.service.cache import cache
36-
from renku.service.config import (
37-
API_VERSION,
38-
CACHE_DIR,
39-
HTTP_SERVER_ERROR,
40-
OPENAPI_VERSION,
41-
SERVICE_API_BASE_PATH,
42-
SERVICE_NAME,
43-
SERVICE_PREFIX,
44-
)
33+
from renku.service.config import CACHE_DIR, HTTP_SERVER_ERROR, SERVICE_PREFIX
4534
from renku.service.logger import service_log
4635
from renku.service.serializers.headers import JWT_TOKEN_SECRET
4736
from renku.service.utils.json_encoder import SvcJSONEncoder
4837
from renku.service.views import error_response
38+
from renku.service.views.apispec import apispec_blueprint
4939
from renku.service.views.cache import cache_blueprint
5040
from renku.service.views.config import config_blueprint
5141
from renku.service.views.datasets import dataset_blueprint
@@ -84,7 +74,7 @@ def root():
8474
"""Root shows basic service information."""
8575
import renku
8676

87-
return jsonify({"service_version": renku.__version__, "spec_url": url_for("openapi")})
77+
return jsonify({"service_version": renku.__version__, "spec_url": url_for("apispec.openapi")})
8878

8979
@app.route("/health")
9080
def health():
@@ -93,11 +83,6 @@ def health():
9383

9484
return "renku repository service version {}\n".format(renku.__version__)
9585

96-
@app.route(SERVICE_PREFIX.rstrip("/") + "/spec.json")
97-
def openapi():
98-
"""Return the OpenAPI spec for this service."""
99-
return jsonify(get_apispec(app).to_dict())
100-
10186
return app
10287

10388

@@ -110,6 +95,7 @@ def build_routes(app):
11095
app.register_blueprint(jobs_blueprint)
11196
app.register_blueprint(templates_blueprint)
11297
app.register_blueprint(version_blueprint)
98+
app.register_blueprint(apispec_blueprint)
11399

114100

115101
app = create_app()
@@ -179,28 +165,3 @@ def exceptions(e):
179165

180166
app.logger.handlers.extend(service_log.handlers)
181167
app.run()
182-
183-
184-
def get_apispec(app):
185-
"""Return the apispec."""
186-
spec = APISpec(
187-
title=SERVICE_NAME,
188-
openapi_version=OPENAPI_VERSION,
189-
version=API_VERSION,
190-
plugins=[FlaskPlugin(), MarshmallowPlugin()],
191-
servers=[{"url": SERVICE_API_BASE_PATH}],
192-
components={
193-
"securitySchemes": {
194-
"oidc": {
195-
"type": "openIdConnect",
196-
"openIdConnectUrl": "/auth/realms/Renku/.well-known/openid-configuration",
197-
},
198-
"JWT": {"type": "apiKey", "name": "Renku-User", "in": "header"},
199-
"gitlab-token": {"type": "apiKey", "name": "Authorization", "in": "header"},
200-
}
201-
},
202-
security=[{"oidc": []}, {"JWT": [], "gitlab-token": []}],
203-
)
204-
for rule in app.url_map.iter_rules():
205-
spec.path(view=app.view_functions[rule.endpoint])
206-
return spec

renku/service/views/apispec.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright 2020 - Swiss Data Science Center (SDSC)
4+
# A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
5+
# Eidgenössische Technische Hochschule Zürich (ETHZ).
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
"""Renku service apispec views."""
19+
from apispec import APISpec
20+
from apispec.ext.marshmallow import MarshmallowPlugin
21+
from apispec_webframeworks.flask import FlaskPlugin
22+
from flask import Blueprint, current_app, jsonify
23+
24+
from renku.service.config import (
25+
API_VERSION,
26+
OIDC_URL,
27+
OPENAPI_VERSION,
28+
SERVICE_API_BASE_PATH,
29+
SERVICE_NAME,
30+
SERVICE_PREFIX,
31+
)
32+
33+
apispec_blueprint = Blueprint("apispec", __name__, url_prefix=SERVICE_PREFIX)
34+
35+
# security schemes
36+
oidc_scheme = {"type": "openIdConnect", "openIdConnectUrl": OIDC_URL}
37+
jwt_scheme = {"type": "apiKey", "name": "Renku-User", "in": "header"}
38+
gitlab_token_scheme = {"type": "apiKey", "name": "Authorization", "in": "header"}
39+
40+
TOP_LEVEL_DESCRIPTION = """
41+
This is the API specification of the renku core service. The API follows the
42+
[JSON-RPC 2.0](https://www.jsonrpc.org/specification) specifications and mirrors
43+
the functionality of the renku CLI.
44+
45+
The basic API is low-level and requires that the client handles project
46+
(repository) state in the service cache by invoking the `cache.project_clone`
47+
method. This returns a `project_id` that is required for many of the other API
48+
calls. Note that the `project_id` identifies a combination of `git_url` and
49+
`ref` - i.e. each combination of `git_url` and `ref` receives a different
50+
`project_id`.
51+
52+
## Higher-level interface
53+
54+
Some API methods allow the client to defer repository management to the service.
55+
In these cases, the API documentation will include `project_id` _and_
56+
`git_url`+`ref` in the spec. Note that for such methods, _either_ `project_id`
57+
_or_ `git_url` (and optionally `ref`) should be passed in the request body.
58+
59+
## Responses
60+
61+
Following the JSON-RPC 2.0 Specification, the methods all return with HTTP code
62+
200 and include a [response
63+
object](https://www.jsonrpc.org/specification#response_object) may contain
64+
either a `result` or an `error` object. If the call succeeds, the returned
65+
`result` follows the schema documented in the individual methods. In the case of
66+
an error, the [`error`
67+
object](https://www.jsonrpc.org/specification#error_object), contains a code and
68+
a message describing the nature of the error. In addition to the [standard JSON-RPC
69+
response codes](https://www.jsonrpc.org/specification#error_object), we define application-specific
70+
codes:
71+
72+
```
73+
GIT_ACCESS_DENIED_ERROR_CODE = -32000
74+
GIT_UNKNOWN_ERROR_CODE = -32001
75+
76+
RENKU_EXCEPTION_ERROR_CODE = -32100
77+
REDIS_EXCEPTION_ERROR_CODE = -32200
78+
79+
INVALID_HEADERS_ERROR_CODE = -32601
80+
INVALID_PARAMS_ERROR_CODE = -32602
81+
INTERNAL_FAILURE_ERROR_CODE = -32603
82+
83+
HTTP_SERVER_ERROR = -32000
84+
```
85+
86+
"""
87+
88+
spec = APISpec(
89+
title=SERVICE_NAME,
90+
openapi_version=OPENAPI_VERSION,
91+
version=API_VERSION,
92+
plugins=[FlaskPlugin(), MarshmallowPlugin()],
93+
servers=[{"url": SERVICE_API_BASE_PATH}],
94+
security=[{"oidc": []}, {"JWT": [], "gitlab-token": []}],
95+
info={"description": TOP_LEVEL_DESCRIPTION},
96+
)
97+
98+
spec.components.security_scheme("oidc", oidc_scheme)
99+
spec.components.security_scheme("jwt", jwt_scheme)
100+
spec.components.security_scheme("gitlab-token", gitlab_token_scheme)
101+
102+
103+
@apispec_blueprint.route("/spec.json")
104+
def openapi():
105+
"""Return the OpenAPI spec for this service."""
106+
return jsonify(get_apispec(current_app).to_dict())
107+
108+
109+
def get_apispec(app):
110+
"""Return the apispec."""
111+
for rule in current_app.url_map.iter_rules():
112+
spec.path(view=app.view_functions[rule.endpoint])
113+
return spec

renku/service/views/cache.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,18 @@ def upload_file_view(user_data, cache):
7575
---
7676
post:
7777
description: Upload a file or archive of files.
78+
parameters:
79+
- in: query
80+
schema: FileUploadRequest
7881
requestBody:
7982
content:
80-
application/json:
81-
schema: FileUploadRequest
83+
multipart/form-data:
84+
schema:
85+
type: object
86+
properties:
87+
file:
88+
type: string
89+
format: binary
8290
responses:
8391
200:
8492
description: List of uploaded files.
@@ -192,6 +200,9 @@ def migration_check_project_view(user_data, cache):
192200
---
193201
get:
194202
description: Retrieve migration information for a project.
203+
parameters:
204+
- in: query
205+
schema: ProjectMigrationCheckRequest
195206
responses:
196207
200:
197208
description: Information about required migrations for the project.

renku/service/views/datasets.py

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ def list_datasets_view(user_data, cache):
5252
---
5353
get:
5454
description: List all datasets in a project.
55+
parameters:
56+
- in: query
57+
schema: DatasetListRequest
5558
responses:
5659
200:
5760
description: Listing of all datasets in a project.

0 commit comments

Comments
 (0)