Skip to content

Commit ed279fb

Browse files
committed
feat: add command and args to environments
1 parent 795f01a commit ed279fb

10 files changed

Lines changed: 145 additions & 30 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""Add command and args to environment
2+
3+
Revision ID: 1ef98b967767
4+
Revises: 584598f3b769
5+
Create Date: 2024-08-25 21:05:02.158021
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
from alembic import op
11+
from sqlalchemy.dialects import postgresql
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "1ef98b967767"
15+
down_revision = "584598f3b769"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade() -> None:
21+
# ### commands auto generated by Alembic - please adjust! ###
22+
op.add_column(
23+
"environments",
24+
sa.Column("args", sa.JSON().with_variant(postgresql.JSONB(astext_type=sa.Text()), "postgresql"), nullable=True),
25+
schema="sessions",
26+
)
27+
op.add_column(
28+
"environments",
29+
sa.Column(
30+
"command", sa.JSON().with_variant(postgresql.JSONB(astext_type=sa.Text()), "postgresql"), nullable=True
31+
),
32+
schema="sessions",
33+
)
34+
# ### end Alembic commands ###
35+
36+
37+
def downgrade() -> None:
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.drop_column("environments", "command", schema="sessions")
40+
op.drop_column("environments", "args", schema="sessions")
41+
# ### end Alembic commands ###

components/renku_data_services/migrations/versions/584598f3b769_expand_and_separate_environments_from_.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""expand and separate environments from session launchers
22
33
Revision ID: 584598f3b769
4-
Revises: 17eea03f938e
4+
Revises: 9058bf0a1a12
55
Create Date: 2024-08-12 14:25:24.292285
66
77
"""
@@ -12,7 +12,7 @@
1212

1313
# revision identifiers, used by Alembic.
1414
revision = "584598f3b769"
15-
down_revision = "17eea03f938e"
15+
down_revision = "9058bf0a1a12"
1616
branch_labels = None
1717
depends_on = None
1818

components/renku_data_services/repositories/apispec_base.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ class Config:
1717

1818
@field_validator("connection_id", mode="before", check_fields=False)
1919
@classmethod
20-
def serialize_connection_id(cls, connection_id: str | ULID) -> str:
20+
def serialize_connection_id(cls, connection_id: str | ULID | None) -> str | None:
2121
"""Custom serializer that can handle ULIDs."""
22+
if connection_id is None:
23+
return None
2224
return str(connection_id)
2325

2426

components/renku_data_services/session/api.spec.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,10 @@ components:
274274
$ref: "#/components/schemas/EnvironmentMountDirectory"
275275
port:
276276
$ref: "#/components/schemas/EnvironmentPort"
277+
command:
278+
$ref: "#/components/schemas/EnvironmentCommand"
279+
args:
280+
$ref: "#/components/schemas/EnvironmentArgs"
277281
required:
278282
- id
279283
- name
@@ -359,6 +363,10 @@ components:
359363
- $ref: "#/components/schemas/EnvironmentPort"
360364
- default: 8080
361365
default: 8080
366+
command:
367+
$ref: "#/components/schemas/EnvironmentCommand"
368+
args:
369+
$ref: "#/components/schemas/EnvironmentArgs"
362370
required:
363371
- name
364372
- container_image
@@ -392,6 +400,10 @@ components:
392400
$ref: "#/components/schemas/EnvironmentMountDirectory"
393401
port:
394402
$ref: "#/components/schemas/EnvironmentPort"
403+
command:
404+
$ref: "#/components/schemas/EnvironmentCommand"
405+
args:
406+
$ref: "#/components/schemas/EnvironmentArgs"
395407
SessionLaunchersList:
396408
description: A list of Renku session launchers
397409
type: array
@@ -571,6 +583,18 @@ components:
571583
type: string
572584
description: The location where the persistent storage for the session will be mounted, usually it should be identical to or a parent of the working directory
573585
minLength: 1
586+
EnvironmentCommand:
587+
type: array
588+
items:
589+
type: string
590+
description: The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes
591+
minLength: 1
592+
EnvironmentArgs:
593+
type: array
594+
items:
595+
type: string
596+
description: The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes
597+
minLength: 1
574598
ErrorResponse:
575599
type: object
576600
properties:

components/renku_data_services/session/apispec.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: api.spec.yaml
3-
# timestamp: 2024-08-12T14:34:54+00:00
3+
# timestamp: 2024-08-25T21:01:41+00:00
44

55
from __future__ import annotations
66

@@ -35,7 +35,7 @@ class Environment(BaseAPISpec):
3535
description="ULID identifier",
3636
max_length=26,
3737
min_length=26,
38-
pattern="^[A-Z0-9]{26}$",
38+
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
3939
)
4040
name: str = Field(
4141
...,
@@ -84,6 +84,16 @@ class Environment(BaseAPISpec):
8484
gt=0,
8585
lt=65400,
8686
)
87+
command: Optional[List[str]] = Field(
88+
None,
89+
description="The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes",
90+
min_length=1,
91+
)
92+
args: Optional[List[str]] = Field(
93+
None,
94+
description="The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes",
95+
min_length=1,
96+
)
8797

8898

8999
class EnvironmentGetInLauncher(Environment):
@@ -135,6 +145,16 @@ class EnvironmentPost(BaseAPISpec):
135145
gt=0,
136146
lt=65400,
137147
)
148+
command: Optional[List[str]] = Field(
149+
None,
150+
description="The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes",
151+
min_length=1,
152+
)
153+
args: Optional[List[str]] = Field(
154+
None,
155+
description="The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes",
156+
min_length=1,
157+
)
138158

139159

140160
class EnvironmentPatch(BaseAPISpec):
@@ -183,6 +203,16 @@ class EnvironmentPatch(BaseAPISpec):
183203
gt=0,
184204
lt=65400,
185205
)
206+
command: Optional[List[str]] = Field(
207+
None,
208+
description="The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes",
209+
min_length=1,
210+
)
211+
args: Optional[List[str]] = Field(
212+
None,
213+
description="The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes",
214+
min_length=1,
215+
)
186216

187217

188218
class SessionLauncher(BaseAPISpec):
@@ -191,14 +221,14 @@ class SessionLauncher(BaseAPISpec):
191221
description="ULID identifier",
192222
max_length=26,
193223
min_length=26,
194-
pattern="^[A-Z0-9]{26}$",
224+
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
195225
)
196226
project_id: str = Field(
197227
...,
198228
description="ULID identifier",
199229
max_length=26,
200230
min_length=26,
201-
pattern="^[A-Z0-9]{26}$",
231+
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
202232
)
203233
name: str = Field(
204234
...,
@@ -273,7 +303,7 @@ class SessionLauncherPost(BaseAPISpec):
273303
description="ULID identifier",
274304
max_length=26,
275305
min_length=26,
276-
pattern="^[A-Z0-9]{26}$",
306+
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
277307
)
278308
description: Optional[str] = Field(
279309
None, description="A description for the resource", max_length=500

components/renku_data_services/session/apispec_base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class Config:
1414

1515
from_attributes = True
1616

17-
@field_validator("id", mode="before", check_fields=False)
17+
@field_validator("id", "project_id", mode="before", check_fields=False)
1818
@classmethod
1919
def serialize_id(cls, id: str | ULID) -> str:
2020
"""Custom serializer that can handle ULIDs."""

components/renku_data_services/session/blueprints.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ async def _post(_: Request, user: base_models.APIUser, body: apispec.Environment
5959
uid=body.uid,
6060
gid=body.gid,
6161
environment_kind=models.EnvironmentKind.GLOBAL,
62+
command=body.command,
63+
args=body.args,
6264
)
6365
environment = await self.session_repo.insert_environment(user=user, new_environment=unsaved_environment)
6466
return json(apispec.Environment.model_validate(environment).model_dump(exclude_none=True, mode="json"), 201)
@@ -145,9 +147,11 @@ async def _post(_: Request, user: base_models.APIUser, body: apispec.SessionLaun
145147
uid=body.environment.uid,
146148
gid=body.environment.gid,
147149
environment_kind=models.EnvironmentKind(body.environment.environment_kind.value),
150+
args=body.environment.args,
151+
command=body.environment.command,
148152
)
149153
new_launcher = models.UnsavedSessionLauncher(
150-
project_id=body.project_id,
154+
project_id=ULID.from_str(body.project_id),
151155
name=body.name,
152156
description=body.description,
153157
environment=environment,

components/renku_data_services/session/db.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ async def __insert_environment(
7979
uid=new_environment.uid,
8080
gid=new_environment.gid,
8181
environment_kind=new_environment.environment_kind,
82+
command=new_environment.command,
83+
args=new_environment.args,
8284
)
8385

8486
session.add(environment)
@@ -128,6 +130,8 @@ async def __update_environment(
128130
"mount_directory",
129131
"uid",
130132
"gid",
133+
"args",
134+
"command",
131135
]:
132136
setattr(environment, key, value)
133137

@@ -138,7 +142,7 @@ async def update_environment(
138142
) -> models.Environment:
139143
"""Update a global session environment entry."""
140144
if not user.is_admin:
141-
raise errors.Unauthorized(message="You do not have the required permissions for this operation.")
145+
raise errors.UnauthorizedError(message="You do not have the required permissions for this operation.")
142146

143147
async with self.session_maker() as session, session.begin():
144148
return await self.__update_environment(
@@ -241,15 +245,15 @@ async def insert_launcher(
241245
message=f"Project with id '{project_id}' does not exist or you do not have access to it."
242246
)
243247

244-
environment_id: str
248+
environment_id: ULID
245249
environment: models.Environment
246250
environment_orm: schemas.EnvironmentORM | None
247251
if isinstance(new_launcher.environment, models.UnsavedEnvironment):
248252
environment_orm = await self.__insert_environment(user, session, new_launcher.environment)
249253
environment = environment_orm.dump()
250254
environment_id = environment.id
251255
else:
252-
environment_id = new_launcher.environment
256+
environment_id = ULID.from_str(new_launcher.environment)
253257
res_env = await session.scalars(
254258
select(schemas.EnvironmentORM)
255259
.where(schemas.EnvironmentORM.id == environment_id)
@@ -314,7 +318,7 @@ async def update_launcher(
314318
authorized = await self.project_authz.has_permission(
315319
user,
316320
ResourceType.project,
317-
str(launcher.project_id),
321+
launcher.project_id,
318322
Scope.WRITE,
319323
)
320324
if not authorized:
@@ -351,7 +355,7 @@ async def update_launcher(
351355
if len(env_payload.keys()) == 1 and "id" in env_payload and isinstance(env_payload["id"], str):
352356
# The environment ID is being changed or set
353357
old_environment = launcher.environment
354-
new_environment_id = env_payload["id"]
358+
new_environment_id = ULID.from_str(env_payload["id"])
355359
res_env = await session.scalars(
356360
select(schemas.EnvironmentORM).where(schemas.EnvironmentORM.id == new_environment_id)
357361
)
@@ -397,6 +401,8 @@ async def update_launcher(
397401
uid=env_payload_valid.uid,
398402
gid=env_payload_valid.gid,
399403
environment_kind=models.EnvironmentKind(env_payload_valid.environment_kind.value),
404+
args=env_payload_valid.args,
405+
command=env_payload_valid.command,
400406
)
401407
new_env = await self.__insert_environment(user, session, new_unsaved_env)
402408
launcher.environment = new_env
@@ -414,6 +420,8 @@ async def update_launcher(
414420
"mount_directory",
415421
"uid",
416422
"gid",
423+
"args",
424+
"command",
417425
]:
418426
setattr(launcher.environment, key, val)
419427

components/renku_data_services/session/models.py

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

33
from dataclasses import dataclass
44
from datetime import datetime
5-
6-
from pydantic import BaseModel, model_validator
7-
from ulid import ULID
85
from enum import StrEnum
96
from pathlib import PurePosixPath
107

8+
from ulid import ULID
9+
1110
from renku_data_services import errors
1211

1312

@@ -32,6 +31,8 @@ class BaseEnvironment:
3231
uid: int
3332
gid: int
3433
environment_kind: EnvironmentKind
34+
args: list[str] | None = None
35+
command: list[str] | None = None
3536

3637

3738
@dataclass(kw_only=True, frozen=True, eq=True)
@@ -64,7 +65,7 @@ def __post_init__(self) -> None:
6465
class Environment(BaseEnvironment):
6566
"""Session environment model that has been saved in the DB."""
6667

67-
id: str
68+
id: ULID
6869
creation_date: datetime
6970
created_by: str
7071

@@ -74,7 +75,7 @@ class BaseSessionLauncher:
7475
"""Session launcher model."""
7576

7677
id: ULID | None
77-
project_id: str
78+
project_id: ULID
7879
name: str
7980
description: str | None
8081
environment: str | UnsavedEnvironment | Environment
@@ -85,6 +86,7 @@ class BaseSessionLauncher:
8586
class UnsavedSessionLauncher(BaseSessionLauncher):
8687
"""Session launcher model that has not been persisted in the DB."""
8788

89+
id: ULID | None = None
8890
environment: str | UnsavedEnvironment
8991
"""When a string is passed for the environment it should be the ID of an existing environment."""
9092

@@ -93,7 +95,7 @@ class UnsavedSessionLauncher(BaseSessionLauncher):
9395
class SessionLauncher(BaseSessionLauncher):
9496
"""Session launcher model that has been already saved in the DB."""
9597

96-
id: str
98+
id: ULID
9799
creation_date: datetime
98100
created_by: str
99101
environment: Environment

0 commit comments

Comments
 (0)