Skip to content

Commit d475878

Browse files
feat: support for search reprovisioning (#392)
1 parent f442831 commit d475878

25 files changed

Lines changed: 775 additions & 78 deletions

File tree

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
AMALTHEA_JS_VERSION ?= 0.11.0
44
AMALTHEA_SESSIONS_VERSION ?= 0.0.1-new-operator-chart
5-
codegen_params = --input-file-type openapi --output-model-type pydantic_v2.BaseModel --use-double-quotes --target-python-version 3.12 --collapse-root-models --field-constraints --strict-nullable --set-default-enum-member --openapi-scopes schemas paths parameters --set-default-enum-member --use-one-literal-as-default --use-default
5+
codegen_params = --input-file-type openapi --output-model-type pydantic_v2.BaseModel --use-double-quotes --target-python-version 3.12 --collapse-root-models --field-constraints --strict-nullable --openapi-scopes schemas paths parameters --set-default-enum-member --use-one-literal-as-default --use-default
66

77
define test_apispec_up_to_date
88
$(eval $@_NAME=$(1))
@@ -38,10 +38,12 @@ components/renku_data_services/notebooks/apispec.py: components/renku_data_servi
3838
poetry run datamodel-codegen --input components/renku_data_services/notebooks/api.spec.yaml --output components/renku_data_services/notebooks/apispec.py --base-class renku_data_services.notebooks.apispec_base.BaseAPISpec $(codegen_params)
3939
components/renku_data_services/platform/apispec.py: components/renku_data_services/platform/api.spec.yaml
4040
poetry run datamodel-codegen --input components/renku_data_services/platform/api.spec.yaml --output components/renku_data_services/platform/apispec.py --base-class renku_data_services.platform.apispec_base.BaseAPISpec $(codegen_params)
41+
components/renku_data_services/message_queue/apispec.py: components/renku_data_services/message_queue/api.spec.yaml
42+
poetry run datamodel-codegen --input components/renku_data_services/message_queue/api.spec.yaml --output components/renku_data_services/message_queue/apispec.py --base-class renku_data_services.message_queue.apispec_base.BaseAPISpec $(codegen_params)
4143

4244
##@ Apispec
4345

44-
schemas: components/renku_data_services/crc/apispec.py components/renku_data_services/storage/apispec.py components/renku_data_services/users/apispec.py components/renku_data_services/project/apispec.py components/renku_data_services/namespace/apispec.py components/renku_data_services/secrets/apispec.py components/renku_data_services/connected_services/apispec.py components/renku_data_services/repositories/apispec.py components/renku_data_services/notebooks/apispec.py components/renku_data_services/platform/apispec.py ## Generate pydantic classes from apispec yaml files
46+
schemas: components/renku_data_services/crc/apispec.py components/renku_data_services/storage/apispec.py components/renku_data_services/users/apispec.py components/renku_data_services/project/apispec.py components/renku_data_services/namespace/apispec.py components/renku_data_services/secrets/apispec.py components/renku_data_services/connected_services/apispec.py components/renku_data_services/repositories/apispec.py components/renku_data_services/notebooks/apispec.py components/renku_data_services/platform/apispec.py components/renku_data_services/message_queue/apispec.py ## Generate pydantic classes from apispec yaml files
4547
@echo "generated classes based on ApiSpec"
4648

4749
##@ Avro schemas
@@ -84,6 +86,8 @@ style_checks: ## Run linting and style checks
8486
@$(call test_apispec_up_to_date,"notebooks")
8587
@echo "checking platform apispec is up to date"
8688
@$(call test_apispec_up_to_date,"platform")
89+
@echo "checking message_queue apispec is up to date"
90+
@$(call test_apispec_up_to_date,"message_queue")
8791
poetry run mypy
8892
poetry run ruff format --check
8993
poetry run ruff check .

bases/renku_data_services/data_api/app.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
ResourcePoolUsersBP,
1616
UserResourcePoolsBP,
1717
)
18+
from renku_data_services.message_queue.blueprints import SearchBP
1819
from renku_data_services.namespace.blueprints import GroupsBP
1920
from renku_data_services.platform.blueprints import PlatformConfigBP
2021
from renku_data_services.project.blueprints import ProjectsBP
@@ -140,6 +141,18 @@ def register_all_handlers(app: Sanic, config: Config) -> Sanic:
140141
platform_repo=config.platform_repo,
141142
authenticator=config.authenticator,
142143
)
144+
search = SearchBP(
145+
name="search",
146+
url_prefix=url_prefix,
147+
authenticator=config.authenticator,
148+
session_maker=config.db.async_session_maker,
149+
reprovisioning_repo=config.reprovisioning_repo,
150+
event_repo=config.event_repo,
151+
user_repo=config.kc_user_repo,
152+
group_repo=config.group_repo,
153+
project_repo=config.project_repo,
154+
authz=config.authz,
155+
)
143156
app.blueprint(
144157
[
145158
resource_pools.blueprint(),
@@ -162,6 +175,7 @@ def register_all_handlers(app: Sanic, config: Config) -> Sanic:
162175
oauth2_connections.blueprint(),
163176
repositories.blueprint(),
164177
platform_config.blueprint(),
178+
search.blueprint(),
165179
]
166180
)
167181

components/renku_data_services/app_config/config.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
from renku_data_services.k8s.clients import DummyCoreClient, DummySchedulingClient, K8sCoreClient, K8sSchedulingClient
4949
from renku_data_services.k8s.quota import QuotaRepository
5050
from renku_data_services.message_queue.config import RedisConfig
51-
from renku_data_services.message_queue.db import EventRepository
51+
from renku_data_services.message_queue.db import EventRepository, ReprovisioningRepository
5252
from renku_data_services.message_queue.interface import IMessageQueue
5353
from renku_data_services.message_queue.redis_queue import RedisQueue
5454
from renku_data_services.namespace.db import GroupRepository
@@ -166,6 +166,7 @@ class Config:
166166
_project_repo: ProjectRepository | None = field(default=None, repr=False, init=False)
167167
_group_repo: GroupRepository | None = field(default=None, repr=False, init=False)
168168
_event_repo: EventRepository | None = field(default=None, repr=False, init=False)
169+
_reprovisioning_repo: ReprovisioningRepository | None = field(default=None, repr=False, init=False)
169170
_session_repo: SessionRepository | None = field(default=None, repr=False, init=False)
170171
_user_preferences_repo: UserPreferencesRepository | None = field(default=None, repr=False, init=False)
171172
_kc_user_repo: KcUserRepo | None = field(default=None, repr=False, init=False)
@@ -176,6 +177,7 @@ class Config:
176177
_platform_repo: PlatformRepository | None = field(default=None, repr=False, init=False)
177178

178179
def __post_init__(self) -> None:
180+
# NOTE: Read spec files required for Swagger
179181
spec_file = Path(renku_data_services.crc.__file__).resolve().parent / "api.spec.yaml"
180182
with open(spec_file) as f:
181183
crc_spec = safe_load(f)
@@ -212,6 +214,10 @@ def __post_init__(self) -> None:
212214
with open(spec_file) as f:
213215
platform = safe_load(f)
214216

217+
spec_file = Path(renku_data_services.message_queue.__file__).resolve().parent / "api.spec.yaml"
218+
with open(spec_file) as f:
219+
search = safe_load(f)
220+
215221
self.spec = merge_api_specs(
216222
crc_spec,
217223
storage_spec,
@@ -222,6 +228,7 @@ def __post_init__(self) -> None:
222228
connected_services,
223229
repositories,
224230
platform,
231+
search,
225232
)
226233

227234
if self.default_resource_pool_file is not None:
@@ -287,6 +294,13 @@ def event_repo(self) -> EventRepository:
287294
)
288295
return self._event_repo
289296

297+
@property
298+
def reprovisioning_repo(self) -> ReprovisioningRepository:
299+
"""The DB adapter for reprovisioning."""
300+
if not self._reprovisioning_repo:
301+
self._reprovisioning_repo = ReprovisioningRepository(session_maker=self.db.async_session_maker)
302+
return self._reprovisioning_repo
303+
290304
@property
291305
def project_repo(self) -> ProjectRepository:
292306
"""The DB adapter for Renku native projects."""

components/renku_data_services/authz/authz.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Projects authorization adapter."""
22

33
import asyncio
4-
from collections.abc import AsyncIterable, Awaitable, Callable
4+
from collections.abc import AsyncGenerator, AsyncIterable, Awaitable, Callable
55
from dataclasses import dataclass, field
66
from enum import StrEnum
77
from functools import wraps
@@ -385,6 +385,15 @@ async def users_with_permission(
385385
ids.append(response.subject.subject_object_id)
386386
return ids
387387

388+
async def get_all_members(
389+
self, resource_type: ResourceType, *, zed_token: ZedToken | None = None
390+
) -> AsyncGenerator[Member, None]:
391+
"""Get all users that are members of a specific resource."""
392+
members = self._get_members_helper(resource_type, resource_id=None, zed_token=zed_token)
393+
async for member in members:
394+
if member.user_id and member.user_id != "*":
395+
yield member
396+
388397
@_is_allowed(Scope.READ)
389398
async def members(
390399
self,
@@ -395,41 +404,63 @@ async def members(
395404
*,
396405
zed_token: ZedToken | None = None,
397406
) -> list[Member]:
407+
"""Get all users that are members of a specific resource type, if role is None then all roles are retrieved."""
408+
members = self._get_members_helper(resource_type, str(resource_id), role, zed_token=zed_token)
409+
return [m async for m in members]
410+
411+
async def _get_members_helper(
412+
self,
413+
resource_type: ResourceType,
414+
resource_id: str | None,
415+
role: Role | None = None,
416+
*,
417+
zed_token: ZedToken | None = None,
418+
) -> AsyncGenerator[Member, None]:
398419
"""Get all users that are members of a resource, if role is None then all roles are retrieved."""
399-
resource_id_str = str(resource_id)
400420
consistency = Consistency(at_least_as_fresh=zed_token) if zed_token else Consistency(fully_consistent=True)
401421
sub_filter = SubjectFilter(subject_type=ResourceType.user.value)
402-
rel_filter = RelationshipFilter(
403-
resource_type=resource_type,
404-
optional_resource_id=resource_id_str,
405-
optional_subject_filter=sub_filter,
406-
)
407-
if role:
408-
relation = _Relation.from_role(role)
422+
if resource_id is None:
423+
rel_filter = RelationshipFilter(resource_type=resource_type, optional_subject_filter=sub_filter)
424+
else:
409425
rel_filter = RelationshipFilter(
410426
resource_type=resource_type,
411-
optional_resource_id=resource_id_str,
412-
optional_relation=relation,
427+
optional_resource_id=resource_id,
413428
optional_subject_filter=sub_filter,
414429
)
430+
if role:
431+
relation = _Relation.from_role(role)
432+
if resource_id is None:
433+
rel_filter = RelationshipFilter(
434+
resource_type=resource_type,
435+
optional_relation=relation,
436+
optional_subject_filter=sub_filter,
437+
)
438+
else:
439+
rel_filter = RelationshipFilter(
440+
resource_type=resource_type,
441+
optional_resource_id=resource_id,
442+
optional_relation=relation,
443+
optional_subject_filter=sub_filter,
444+
)
415445
responses: AsyncIterable[ReadRelationshipsResponse] = self.client.ReadRelationships(
416446
ReadRelationshipsRequest(
417447
consistency=consistency,
418448
relationship_filter=rel_filter,
419449
)
420450
)
421-
members: list[Member] = []
451+
422452
async for response in responses:
423453
# Skip "public_viewer" relationships
424454
if response.relationship.relation == _Relation.public_viewer.value:
425455
continue
426456
member_role = _Relation(response.relationship.relation).to_role()
427-
members.append(
428-
Member(
429-
user_id=response.relationship.subject.object.object_id, role=member_role, resource_id=resource_id
430-
)
457+
member = Member(
458+
user_id=response.relationship.subject.object.object_id,
459+
role=member_role,
460+
resource_id=response.relationship.resource.object_id,
431461
)
432-
return members
462+
463+
yield member
433464

434465
@staticmethod
435466
def authz_change(

components/renku_data_services/authz/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class MembershipChange:
8888

8989

9090
class Visibility(Enum):
91-
"""The visisibilty of a resource."""
91+
"""The visibility of a resource."""
9292

9393
PUBLIC: str = "public"
9494
PRIVATE: str = "private"
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
openapi: 3.0.2
2+
info:
3+
title: Renku Data Services API
4+
description: |
5+
This service is the main backend for Renku. It provides information about users, projects,
6+
cloud storage, access to compute resources and many other things.
7+
version: v1
8+
servers:
9+
- url: /api/data
10+
- url: /ui-server/api/data
11+
paths:
12+
/message_queue/reprovision:
13+
post:
14+
summary: Start a new reprovisioning
15+
description: Only a single reprovisioning is active at any time
16+
responses:
17+
"201":
18+
description: The reprovisioning is/will be started
19+
content:
20+
application/json:
21+
schema:
22+
$ref: "#/components/schemas/Reprovisioning"
23+
"409":
24+
description: A reprovisioning is already started
25+
default:
26+
$ref: "#/components/responses/Error"
27+
tags:
28+
- message_queue
29+
get:
30+
summary: Return status of reprovisioning
31+
responses:
32+
"200":
33+
description: Status of reprovisioning if there's one in progress
34+
content:
35+
application/json:
36+
schema:
37+
$ref: "#/components/schemas/ReprovisioningStatus"
38+
"404":
39+
description: There's no active reprovisioning
40+
content:
41+
application/json:
42+
schema:
43+
$ref: "#/components/schemas/ErrorResponse"
44+
default:
45+
$ref: "#/components/responses/Error"
46+
tags:
47+
- message_queue
48+
delete:
49+
summary: Stop an active reprovisioning
50+
responses:
51+
"204":
52+
description: The reprovisioning was stopped or there was no one in progress
53+
default:
54+
$ref: "#/components/responses/Error"
55+
tags:
56+
- message_queue
57+
58+
components:
59+
schemas:
60+
Reprovisioning:
61+
description: A reprovisioning
62+
type: object
63+
properties:
64+
id:
65+
$ref: "#/components/schemas/Ulid"
66+
start_date:
67+
description: The date and time the reprovisioning was started (in UTC and ISO-8601 format)
68+
type: string
69+
format: date-time
70+
example: "2023-11-01T17:32:28Z"
71+
required:
72+
- id
73+
- start_date
74+
ReprovisioningStatus:
75+
description: Status of a reprovisioning
76+
allOf:
77+
- $ref: "#/components/schemas/Reprovisioning"
78+
Ulid:
79+
description: ULID identifier
80+
type: string
81+
minLength: 26
82+
maxLength: 26
83+
pattern: "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" # This is case-insensitive
84+
ErrorResponse:
85+
type: object
86+
properties:
87+
error:
88+
type: object
89+
properties:
90+
code:
91+
type: integer
92+
minimum: 0
93+
exclusiveMinimum: true
94+
example: 1404
95+
detail:
96+
type: string
97+
example: A more detailed optional message showing what the problem was
98+
message:
99+
type: string
100+
example: Something went wrong - please try again later
101+
required:
102+
- code
103+
- message
104+
required:
105+
- error
106+
responses:
107+
Error:
108+
description: The schema for all 4xx and 5xx responses
109+
content:
110+
application/json:
111+
schema:
112+
$ref: "#/components/schemas/ErrorResponse"
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# generated by datamodel-codegen:
2+
# filename: api.spec.yaml
3+
# timestamp: 2024-10-03T07:51:44+00:00
4+
5+
from __future__ import annotations
6+
7+
from datetime import datetime
8+
from typing import Optional
9+
10+
from pydantic import Field
11+
from renku_data_services.message_queue.apispec_base import BaseAPISpec
12+
13+
14+
class Error(BaseAPISpec):
15+
code: int = Field(..., example=1404, gt=0)
16+
detail: Optional[str] = Field(
17+
None, example="A more detailed optional message showing what the problem was"
18+
)
19+
message: str = Field(..., example="Something went wrong - please try again later")
20+
21+
22+
class ErrorResponse(BaseAPISpec):
23+
error: Error
24+
25+
26+
class Reprovisioning(BaseAPISpec):
27+
id: str = Field(
28+
...,
29+
description="ULID identifier",
30+
max_length=26,
31+
min_length=26,
32+
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
33+
)
34+
start_date: datetime = Field(
35+
...,
36+
description="The date and time the reprovisioning was started (in UTC and ISO-8601 format)",
37+
example="2023-11-01T17:32:28Z",
38+
)
39+
40+
41+
class ReprovisioningStatus(Reprovisioning):
42+
pass

0 commit comments

Comments
 (0)