From e70f1cc8b998e32bbeabe4da539562dddfc49a2d Mon Sep 17 00:00:00 2001 From: Sanghun Lee Date: Thu, 2 Jan 2025 18:12:33 +0900 Subject: [PATCH] feat: Update GQL user mutation to allow set uid/gid --- changes/3352.feature.md | 1 + docs/manager/graphql-reference/schema.graphql | 60 +++++++++++++++++++ .../backend/manager/models/gql_models/user.py | 13 ++++ src/ai/backend/manager/models/user.py | 45 ++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 changes/3352.feature.md diff --git a/changes/3352.feature.md b/changes/3352.feature.md new file mode 100644 index 00000000000..49aed8c5e04 --- /dev/null +++ b/changes/3352.feature.md @@ -0,0 +1 @@ +Enable per-user UID/GID set for containers diff --git a/docs/manager/graphql-reference/schema.graphql b/docs/manager/graphql-reference/schema.graphql index c8aac732d1c..70ed754534a 100644 --- a/docs/manager/graphql-reference/schema.graphql +++ b/docs/manager/graphql-reference/schema.graphql @@ -726,6 +726,21 @@ type UserNode implements Node { totp_activated: Boolean totp_activated_at: DateTime sudo_session_enabled: Boolean + + """ + Added in 25.1.0. The user ID (UID) assigned to processes running inside the container. + """ + container_uid: Int + + """ + Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container. + """ + container_main_gid: Int + + """ + Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container. + """ + container_supplementary_gids: [Int] } """Added in 24.03.0""" @@ -829,6 +844,21 @@ type User implements Item { Added in 24.03.0. Used as the default authentication credential for password-based logins and sets the user's total resource usage limit. User's main_access_key cannot be deleted, and only super-admin can replace main_access_key. """ main_access_key: String + + """ + Added in 25.1.0. The user ID (UID) assigned to processes running inside the container. + """ + container_uid: Int + + """ + Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container. + """ + container_main_gid: Int + + """ + Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container. + """ + container_supplementary_gids: [Int] groups: [UserGroup] } @@ -2059,6 +2089,21 @@ input UserInput { totp_activated: Boolean = false resource_policy: String = "default" sudo_session_enabled: Boolean = false + + """ + Added in 25.1.0. The user ID (UID) assigned to processes running inside the container. + """ + container_uid: Int + + """ + Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container. + """ + container_main_gid: Int + + """ + Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container. + """ + container_supplementary_gids: [Int] } type ModifyUser { @@ -2083,6 +2128,21 @@ input ModifyUserInput { resource_policy: String sudo_session_enabled: Boolean main_access_key: String + + """ + Added in 25.1.0. The user ID (UID) assigned to processes running inside the container. + """ + container_uid: Int + + """ + Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container. + """ + container_main_gid: Int + + """ + Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container. + """ + container_supplementary_gids: [Int] } """ diff --git a/src/ai/backend/manager/models/gql_models/user.py b/src/ai/backend/manager/models/gql_models/user.py index 4af73ae6f09..4a57e61d6ae 100644 --- a/src/ai/backend/manager/models/gql_models/user.py +++ b/src/ai/backend/manager/models/gql_models/user.py @@ -52,6 +52,16 @@ class Meta: totp_activated = graphene.Boolean() totp_activated_at = GQLDateTime() sudo_session_enabled = graphene.Boolean() + container_uid = graphene.Int( + description="Added in 25.1.0. The user ID (UID) assigned to processes running inside the container." + ) + container_main_gid = graphene.Int( + description="Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container." + ) + container_gids = graphene.List( + lambda: graphene.Int, + description="Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container.", + ) @classmethod def from_row(cls, ctx: GraphQueryContext, row: UserRow) -> Self: @@ -74,6 +84,9 @@ def from_row(cls, ctx: GraphQueryContext, row: UserRow) -> Self: totp_activated=row.totp_activated, totp_activated_at=row.totp_activated_at, sudo_session_enabled=row.sudo_session_enabled, + container_uid=row.container_uid, + container_main_gid=row.container_main_gid, + container_gids=row.container_gids, ) @classmethod diff --git a/src/ai/backend/manager/models/user.py b/src/ai/backend/manager/models/user.py index 9c2c5060d4b..05a4b7e1dd9 100644 --- a/src/ai/backend/manager/models/user.py +++ b/src/ai/backend/manager/models/user.py @@ -256,6 +256,16 @@ class Meta: " be deleted, and only super-admin can replace main_access_key." ) ) + container_uid = graphene.Int( + description="Added in 25.1.0. The user ID (UID) assigned to processes running inside the container." + ) + container_main_gid = graphene.Int( + description="Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container." + ) + container_gids = graphene.List( + lambda: graphene.Int, + description="Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container.", + ) groups = graphene.List(lambda: UserGroup) @@ -295,6 +305,9 @@ def from_row( totp_activated_at=row["totp_activated_at"], sudo_session_enabled=row["sudo_session_enabled"], main_access_key=row["main_access_key"], + container_uid=row["container_uid"], + container_main_gid=row["container_main_gid"], + container_gids=row["container_gids"], ) @classmethod @@ -557,6 +570,19 @@ class UserInput(graphene.InputObjectType): totp_activated = graphene.Boolean(required=False, default_value=False) resource_policy = graphene.String(required=False, default_value="default") sudo_session_enabled = graphene.Boolean(required=False, default_value=False) + container_uid = graphene.Int( + required=False, + description="Added in 25.1.0. The user ID (UID) assigned to processes running inside the container.", + ) + container_main_gid = graphene.Int( + required=False, + description="Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container.", + ) + container_gids = graphene.List( + lambda: graphene.Int, + required=False, + description="Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container.", + ) # When creating, you MUST set all fields. # When modifying, set the field to "None" to skip setting the value. @@ -577,6 +603,19 @@ class ModifyUserInput(graphene.InputObjectType): resource_policy = graphene.String(required=False) sudo_session_enabled = graphene.Boolean(required=False, default=False) main_access_key = graphene.String(required=False) + container_uid = graphene.Int( + required=False, + description="Added in 25.1.0. The user ID (UID) assigned to processes running inside the container.", + ) + container_main_gid = graphene.Int( + required=False, + description="Added in 25.1.0. The primary group ID (GID) assigned to processes running inside the container.", + ) + container_gids = graphene.List( + lambda: graphene.Int, + required=False, + description="Added in 25.1.0. Supplementary group IDs assigned to processes running inside the container.", + ) class PurgeUserInput(graphene.InputObjectType): @@ -625,6 +664,9 @@ async def mutate( "totp_activated": props.totp_activated, "resource_policy": props.resource_policy, "sudo_session_enabled": props.sudo_session_enabled, + "container_uid": props.container_uid, + "container_main_gid": props.container_main_gid, + "container_gids": props.container_gids, } user_insert_query = sa.insert(users).values(user_data) @@ -737,6 +779,9 @@ async def mutate( set_if_set(props, data, "sudo_session_enabled") set_if_set(props, data, "main_access_key") set_if_set(props, data, "is_active") + set_if_set(props, data, "container_uid") + set_if_set(props, data, "container_main_gid") + set_if_set(props, data, "container_gids") if data.get("password") is None: data.pop("password", None) if not data and not props.group_ids: