Skip to content

Commit

Permalink
feat: add command and args to environments
Browse files Browse the repository at this point in the history
  • Loading branch information
olevski committed Aug 28, 2024
1 parent 795f01a commit ed279fb
Show file tree
Hide file tree
Showing 10 changed files with 145 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Add command and args to environment
Revision ID: 1ef98b967767
Revises: 584598f3b769
Create Date: 2024-08-25 21:05:02.158021
"""

import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "1ef98b967767"
down_revision = "584598f3b769"
branch_labels = None
depends_on = None


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"environments",
sa.Column("args", sa.JSON().with_variant(postgresql.JSONB(astext_type=sa.Text()), "postgresql"), nullable=True),
schema="sessions",
)
op.add_column(
"environments",
sa.Column(
"command", sa.JSON().with_variant(postgresql.JSONB(astext_type=sa.Text()), "postgresql"), nullable=True
),
schema="sessions",
)
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("environments", "command", schema="sessions")
op.drop_column("environments", "args", schema="sessions")
# ### end Alembic commands ###
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""expand and separate environments from session launchers
Revision ID: 584598f3b769
Revises: 17eea03f938e
Revises: 9058bf0a1a12
Create Date: 2024-08-12 14:25:24.292285
"""
Expand All @@ -12,7 +12,7 @@

# revision identifiers, used by Alembic.
revision = "584598f3b769"
down_revision = "17eea03f938e"
down_revision = "9058bf0a1a12"
branch_labels = None
depends_on = None

Expand Down
4 changes: 3 additions & 1 deletion components/renku_data_services/repositories/apispec_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ class Config:

@field_validator("connection_id", mode="before", check_fields=False)
@classmethod
def serialize_connection_id(cls, connection_id: str | ULID) -> str:
def serialize_connection_id(cls, connection_id: str | ULID | None) -> str | None:
"""Custom serializer that can handle ULIDs."""
if connection_id is None:
return None
return str(connection_id)


Expand Down
24 changes: 24 additions & 0 deletions components/renku_data_services/session/api.spec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@ components:
$ref: "#/components/schemas/EnvironmentMountDirectory"
port:
$ref: "#/components/schemas/EnvironmentPort"
command:
$ref: "#/components/schemas/EnvironmentCommand"
args:
$ref: "#/components/schemas/EnvironmentArgs"
required:
- id
- name
Expand Down Expand Up @@ -359,6 +363,10 @@ components:
- $ref: "#/components/schemas/EnvironmentPort"
- default: 8080
default: 8080
command:
$ref: "#/components/schemas/EnvironmentCommand"
args:
$ref: "#/components/schemas/EnvironmentArgs"
required:
- name
- container_image
Expand Down Expand Up @@ -392,6 +400,10 @@ components:
$ref: "#/components/schemas/EnvironmentMountDirectory"
port:
$ref: "#/components/schemas/EnvironmentPort"
command:
$ref: "#/components/schemas/EnvironmentCommand"
args:
$ref: "#/components/schemas/EnvironmentArgs"
SessionLaunchersList:
description: A list of Renku session launchers
type: array
Expand Down Expand Up @@ -571,6 +583,18 @@ components:
type: string
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
minLength: 1
EnvironmentCommand:
type: array
items:
type: string
description: The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes
minLength: 1
EnvironmentArgs:
type: array
items:
type: string
description: The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes
minLength: 1
ErrorResponse:
type: object
properties:
Expand Down
40 changes: 35 additions & 5 deletions components/renku_data_services/session/apispec.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# generated by datamodel-codegen:
# filename: api.spec.yaml
# timestamp: 2024-08-12T14:34:54+00:00
# timestamp: 2024-08-25T21:01:41+00:00

from __future__ import annotations

Expand Down Expand Up @@ -35,7 +35,7 @@ class Environment(BaseAPISpec):
description="ULID identifier",
max_length=26,
min_length=26,
pattern="^[A-Z0-9]{26}$",
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
)
name: str = Field(
...,
Expand Down Expand Up @@ -84,6 +84,16 @@ class Environment(BaseAPISpec):
gt=0,
lt=65400,
)
command: Optional[List[str]] = Field(
None,
description="The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes",
min_length=1,
)
args: Optional[List[str]] = Field(
None,
description="The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes",
min_length=1,
)


class EnvironmentGetInLauncher(Environment):
Expand Down Expand Up @@ -135,6 +145,16 @@ class EnvironmentPost(BaseAPISpec):
gt=0,
lt=65400,
)
command: Optional[List[str]] = Field(
None,
description="The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes",
min_length=1,
)
args: Optional[List[str]] = Field(
None,
description="The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes",
min_length=1,
)


class EnvironmentPatch(BaseAPISpec):
Expand Down Expand Up @@ -183,6 +203,16 @@ class EnvironmentPatch(BaseAPISpec):
gt=0,
lt=65400,
)
command: Optional[List[str]] = Field(
None,
description="The command that will be run i.e. will overwrite the image Dockerfile ENTRYPOINT, equivalent to command in Kubernetes",
min_length=1,
)
args: Optional[List[str]] = Field(
None,
description="The arguments that will follow the command, i.e. will overwrite the image Dockerfile CMD, equivalent to args in Kubernetes",
min_length=1,
)


class SessionLauncher(BaseAPISpec):
Expand All @@ -191,14 +221,14 @@ class SessionLauncher(BaseAPISpec):
description="ULID identifier",
max_length=26,
min_length=26,
pattern="^[A-Z0-9]{26}$",
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
)
project_id: str = Field(
...,
description="ULID identifier",
max_length=26,
min_length=26,
pattern="^[A-Z0-9]{26}$",
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
)
name: str = Field(
...,
Expand Down Expand Up @@ -273,7 +303,7 @@ class SessionLauncherPost(BaseAPISpec):
description="ULID identifier",
max_length=26,
min_length=26,
pattern="^[A-Z0-9]{26}$",
pattern="^[0-7][0-9A-HJKMNP-TV-Z]{25}$",
)
description: Optional[str] = Field(
None, description="A description for the resource", max_length=500
Expand Down
2 changes: 1 addition & 1 deletion components/renku_data_services/session/apispec_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class Config:

from_attributes = True

@field_validator("id", mode="before", check_fields=False)
@field_validator("id", "project_id", mode="before", check_fields=False)
@classmethod
def serialize_id(cls, id: str | ULID) -> str:
"""Custom serializer that can handle ULIDs."""
Expand Down
6 changes: 5 additions & 1 deletion components/renku_data_services/session/blueprints.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ async def _post(_: Request, user: base_models.APIUser, body: apispec.Environment
uid=body.uid,
gid=body.gid,
environment_kind=models.EnvironmentKind.GLOBAL,
command=body.command,
args=body.args,
)
environment = await self.session_repo.insert_environment(user=user, new_environment=unsaved_environment)
return json(apispec.Environment.model_validate(environment).model_dump(exclude_none=True, mode="json"), 201)
Expand Down Expand Up @@ -145,9 +147,11 @@ async def _post(_: Request, user: base_models.APIUser, body: apispec.SessionLaun
uid=body.environment.uid,
gid=body.environment.gid,
environment_kind=models.EnvironmentKind(body.environment.environment_kind.value),
args=body.environment.args,
command=body.environment.command,
)
new_launcher = models.UnsavedSessionLauncher(
project_id=body.project_id,
project_id=ULID.from_str(body.project_id),
name=body.name,
description=body.description,
environment=environment,
Expand Down
18 changes: 13 additions & 5 deletions components/renku_data_services/session/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ async def __insert_environment(
uid=new_environment.uid,
gid=new_environment.gid,
environment_kind=new_environment.environment_kind,
command=new_environment.command,
args=new_environment.args,
)

session.add(environment)
Expand Down Expand Up @@ -128,6 +130,8 @@ async def __update_environment(
"mount_directory",
"uid",
"gid",
"args",
"command",
]:
setattr(environment, key, value)

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

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

environment_id: str
environment_id: ULID
environment: models.Environment
environment_orm: schemas.EnvironmentORM | None
if isinstance(new_launcher.environment, models.UnsavedEnvironment):
environment_orm = await self.__insert_environment(user, session, new_launcher.environment)
environment = environment_orm.dump()
environment_id = environment.id
else:
environment_id = new_launcher.environment
environment_id = ULID.from_str(new_launcher.environment)
res_env = await session.scalars(
select(schemas.EnvironmentORM)
.where(schemas.EnvironmentORM.id == environment_id)
Expand Down Expand Up @@ -314,7 +318,7 @@ async def update_launcher(
authorized = await self.project_authz.has_permission(
user,
ResourceType.project,
str(launcher.project_id),
launcher.project_id,
Scope.WRITE,
)
if not authorized:
Expand Down Expand Up @@ -351,7 +355,7 @@ async def update_launcher(
if len(env_payload.keys()) == 1 and "id" in env_payload and isinstance(env_payload["id"], str):
# The environment ID is being changed or set
old_environment = launcher.environment
new_environment_id = env_payload["id"]
new_environment_id = ULID.from_str(env_payload["id"])
res_env = await session.scalars(
select(schemas.EnvironmentORM).where(schemas.EnvironmentORM.id == new_environment_id)
)
Expand Down Expand Up @@ -397,6 +401,8 @@ async def update_launcher(
uid=env_payload_valid.uid,
gid=env_payload_valid.gid,
environment_kind=models.EnvironmentKind(env_payload_valid.environment_kind.value),
args=env_payload_valid.args,
command=env_payload_valid.command,
)
new_env = await self.__insert_environment(user, session, new_unsaved_env)
launcher.environment = new_env
Expand All @@ -414,6 +420,8 @@ async def update_launcher(
"mount_directory",
"uid",
"gid",
"args",
"command",
]:
setattr(launcher.environment, key, val)

Expand Down
14 changes: 8 additions & 6 deletions components/renku_data_services/session/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@

from dataclasses import dataclass
from datetime import datetime

from pydantic import BaseModel, model_validator
from ulid import ULID
from enum import StrEnum
from pathlib import PurePosixPath

from ulid import ULID

from renku_data_services import errors


Expand All @@ -32,6 +31,8 @@ class BaseEnvironment:
uid: int
gid: int
environment_kind: EnvironmentKind
args: list[str] | None = None
command: list[str] | None = None


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

id: str
id: ULID
creation_date: datetime
created_by: str

Expand All @@ -74,7 +75,7 @@ class BaseSessionLauncher:
"""Session launcher model."""

id: ULID | None
project_id: str
project_id: ULID
name: str
description: str | None
environment: str | UnsavedEnvironment | Environment
Expand All @@ -85,6 +86,7 @@ class BaseSessionLauncher:
class UnsavedSessionLauncher(BaseSessionLauncher):
"""Session launcher model that has not been persisted in the DB."""

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

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

id: str
id: ULID
creation_date: datetime
created_by: str
environment: Environment
Loading

0 comments on commit ed279fb

Please sign in to comment.