From adc707a933c2302a8c76c052574faf00fb99c632 Mon Sep 17 00:00:00 2001 From: jopemachine Date: Fri, 10 Jan 2025 05:23:39 +0000 Subject: [PATCH] refactor: Revamp ContainerRegistryNode API --- .../backend/client/func/container_registry.py | 60 ++----- .../backend/manager/api/container_registry.py | 129 ++++++++------ src/ai/backend/manager/api/schema.graphql | 150 ++++++++-------- .../manager/models/container_registry.py | 166 +++++++++++------- src/ai/backend/manager/models/gql.py | 11 -- .../models/gql_models/container_registry.py | 72 -------- 6 files changed, 262 insertions(+), 326 deletions(-) delete mode 100644 src/ai/backend/manager/models/gql_models/container_registry.py diff --git a/src/ai/backend/client/func/container_registry.py b/src/ai/backend/client/func/container_registry.py index 570ed80237c..ee5582478b6 100644 --- a/src/ai/backend/client/func/container_registry.py +++ b/src/ai/backend/client/func/container_registry.py @@ -1,8 +1,7 @@ from __future__ import annotations -import textwrap +from ai.backend.client.request import Request -from ..session import api_session from .base import BaseFunction, api_function __all__ = ("ContainerRegistry",) @@ -10,58 +9,25 @@ class ContainerRegistry(BaseFunction): """ - Provides a shortcut of :func:`Admin.query() - ` that fetches, modifies various container registry - information. - - .. note:: - - All methods in this function class require your API access key to - have the *admin* privilege. + Provides functions to manage container registries. """ @api_function @classmethod - async def associate_group(cls, registry_id: str, group_id: str) -> dict: + # TODO: Implement params type + async def patch_container_registry(cls, registry_id: str, params) -> None: """ - Associate container_registry with group. + Updates the container registry information, and return the container registry. :param registry_id: ID of the container registry. - :param group_id: ID of the group. - """ - query = textwrap.dedent( - """\ - mutation($registry_id: String!, $group_id: String!) { - associate_container_registry_with_group( - registry_id: $registry_id, group_id: $group_id) { - ok msg - } - } - """ - ) - variables = {"registry_id": registry_id, "group_id": group_id} - data = await api_session.get().Admin._query(query, variables) - return data["associate_container_registry_with_group"] - - @api_function - @classmethod - async def disassociate_group(cls, registry_id: str, group_id: str) -> dict: + :param params: Parameters to update the container registry. """ - Disassociate container_registry with group. - :param registry_id: ID of the container registry. - :param group_id: ID of the group. - """ - query = textwrap.dedent( - """\ - mutation($registry_id: String!, $group_id: String!) { - disassociate_container_registry_with_group( - registry_id: $registry_id, group_id: $group_id) { - ok msg - } - } - """ + request = Request( + "PATCH", + f"/container-registries/{registry_id}", ) - variables = {"registry_id": registry_id, "group_id": group_id} - data = await api_session.get().Admin._query(query, variables) - return data["disassociate_container_registry_with_group"] + request.set_json(params) + + async with request.fetch() as resp: + await resp.read() diff --git a/src/ai/backend/manager/api/container_registry.py b/src/ai/backend/manager/api/container_registry.py index ab0c282b1c6..e59374c5e9d 100644 --- a/src/ai/backend/manager/api/container_registry.py +++ b/src/ai/backend/manager/api/container_registry.py @@ -1,20 +1,23 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Iterable, Tuple +import uuid +from typing import TYPE_CHECKING, Iterable, Optional, Tuple import aiohttp_cors import sqlalchemy as sa from aiohttp import web -from pydantic import AliasChoices, BaseModel, Field +from pydantic import BaseModel from sqlalchemy.exc import IntegrityError from ai.backend.logging import BraceStyleAdapter from ai.backend.manager.models.association_container_registries_groups import ( AssociationContainerRegistriesGroupsRow, ) +from ai.backend.manager.models.container_registry import ContainerRegistryRow +from ai.backend.manager.models.utils import ExtendedAsyncSAEngine -from .exceptions import ContainerRegistryNotFound, GenericBadRequest +from .exceptions import GenericBadRequest, InternalServerError if TYPE_CHECKING: from .context import RootContext @@ -27,75 +30,86 @@ log = BraceStyleAdapter(logging.getLogger(__spec__.name)) -class AssociationRequestModel(BaseModel): - registry_id: str = Field( - validation_alias=AliasChoices("registry_id", "registry"), - description="Container registry row's ID", - ) - group_id: str = Field( - validation_alias=AliasChoices("group_id", "group"), - description="Group row's ID", - ) +class AllowedGroups(BaseModel): + add: list[str] = [] + remove: list[str] = [] -@server_status_required(READ_ALLOWED) -@superadmin_required -@pydantic_params_api_handler(AssociationRequestModel) -async def associate_with_group( - request: web.Request, params: AssociationRequestModel -) -> web.Response: - log.info("ASSOCIATE_WITH_GROUP (cr:{}, gr:{})", params.registry_id, params.group_id) - root_ctx: RootContext = request.app["_root.context"] - registry_id = params.registry_id - group_id = params.group_id +class PatchContainerRegistryRequestModel(BaseModel): + url: Optional[str] = None + type: Optional[str] = None + registry_name: Optional[str] = None + is_global: Optional[bool] = None + project: Optional[str] = None + username: Optional[str] = None + password: Optional[str] = None + ssl_verify: Optional[bool] = None + extra: Optional[str] = None + allowed_groups: Optional[AllowedGroups] = None - async with root_ctx.db.begin_session() as db_sess: - insert_query = sa.insert(AssociationContainerRegistriesGroupsRow).values({ - "registry_id": registry_id, - "group_id": group_id, - }) - try: - await db_sess.execute(insert_query) - except IntegrityError: - raise GenericBadRequest("Association already exists.") +# TODO: Add this. ContainerRegistryRow is not compatible with BaseModel +# class PatchContainerRegistryResponseModel(BaseModel): +# container_registry: ContainerRegistryRow - return web.Response(status=204) +async def handle_allowed_groups_update( + db: ExtendedAsyncSAEngine, registry_id: uuid.UUID, allowed_group_updates: AllowedGroups +): + async with db.begin_session() as db_sess: + if allowed_group_updates.add: + insert_values = [ + {"registry_id": registry_id, "group_id": group_id} + for group_id in allowed_group_updates.add + ] + + insert_query = sa.insert(AssociationContainerRegistriesGroupsRow).values(insert_values) + await db_sess.execute(insert_query) -class DisassociationRequestModel(BaseModel): - registry_id: str = Field( - validation_alias=AliasChoices("registry_id", "registry"), - description="Container registry row's ID", - ) - group_id: str = Field( - validation_alias=AliasChoices("group_id", "group"), - description="Group row's ID", - ) + if allowed_group_updates.remove: + delete_query = ( + sa.delete(AssociationContainerRegistriesGroupsRow) + .where(AssociationContainerRegistriesGroupsRow.registry_id == registry_id) + .where( + AssociationContainerRegistriesGroupsRow.group_id.in_( + allowed_group_updates.remove + ) + ) + ) + await db_sess.execute(delete_query) @server_status_required(READ_ALLOWED) @superadmin_required -@pydantic_params_api_handler(DisassociationRequestModel) -async def disassociate_with_group( - request: web.Request, params: DisassociationRequestModel +@pydantic_params_api_handler(PatchContainerRegistryRequestModel) +async def patch_container_registry( + request: web.Request, params: PatchContainerRegistryRequestModel ) -> web.Response: - log.info("DISASSOCIATE_WITH_GROUP (cr:{}, gr:{})", params.registry_id, params.group_id) + registry_id = uuid.UUID(request.match_info["registry_id"]) + log.info("PATCH_CONTAINER_REGISTRY (cr:{})", registry_id) root_ctx: RootContext = request.app["_root.context"] - registry_id = params.registry_id - group_id = params.group_id - - async with root_ctx.db.begin_session() as db_sess: - delete_query = ( - sa.delete(AssociationContainerRegistriesGroupsRow) - .where(AssociationContainerRegistriesGroupsRow.registry_id == registry_id) - .where(AssociationContainerRegistriesGroupsRow.group_id == group_id) + input_config = params.model_dump(exclude={"allowed_groups"}, exclude_none=True) + + async with root_ctx.db.begin_session() as db_session: + update_stmt = ( + sa.update(ContainerRegistryRow) + .where(ContainerRegistryRow.id == registry_id) + .values(input_config) ) + await db_session.execute(update_stmt) + + # select_stmt = sa.select(ContainerRegistryRow).where(ContainerRegistryRow.id == registry_id) + # updated_container_registry = await db_session.execute(select_stmt) - result = await db_sess.execute(delete_query) - if result.rowcount == 0: - raise ContainerRegistryNotFound() + try: + if params.allowed_groups: + await handle_allowed_groups_update(root_ctx.db, registry_id, params.allowed_groups) + except IntegrityError as e: + raise GenericBadRequest(f"Failed to update allowed groups! Details: {str(e)}") + except Exception as e: + raise InternalServerError(f"Failed to update allowed groups! Details: {str(e)}") + # return PatchContainerRegistryResponseModel(container_registry=updated_container_registry) return web.Response(status=204) @@ -106,6 +120,5 @@ def create_app( app["api_versions"] = (1, 2, 3, 4, 5) app["prefix"] = "container-registries" cors = aiohttp_cors.setup(app, defaults=default_cors_options) - cors.add(app.router.add_route("POST", "/associate-with-group", associate_with_group)) - cors.add(app.router.add_route("POST", "/disassociate-with-group", disassociate_with_group)) + cors.add(app.router.add_route("PATCH", "/{registry_id}", patch_container_registry)) return app, [] diff --git a/src/ai/backend/manager/api/schema.graphql b/src/ai/backend/manager/api/schema.graphql index 0d80cf3da75..bd300c9229c 100644 --- a/src/ai/backend/manager/api/schema.graphql +++ b/src/ai/backend/manager/api/schema.graphql @@ -1833,69 +1833,17 @@ type Mutations { """Added in 24.09.0.""" create_container_registry_node( - """Added in 24.09.3.""" - extra: JSONString - - """Added in 24.09.0.""" - is_global: Boolean - - """Added in 24.09.0.""" - password: String - - """Added in 24.09.0.""" - project: String - - """Added in 24.09.0.""" - registry_name: String! - - """Added in 24.09.0.""" - ssl_verify: Boolean - - """ - Added in 24.09.0. Registry type. One of ('docker', 'harbor', 'harbor2', 'github', 'gitlab', 'ecr', 'ecr-public', 'local'). - """ - type: ContainerRegistryTypeField! - - """Added in 24.09.0.""" - url: String! - - """Added in 24.09.0.""" - username: String + """Added in 25.1.0.""" + props: CreateContainerRegistryNodeInput! ): CreateContainerRegistryNode """Added in 24.09.0.""" modify_container_registry_node( - """Added in 24.09.3.""" - extra: JSONString - """Object id. Can be either global id or object id. Added in 24.09.0.""" id: String! - """Added in 24.09.0.""" - is_global: Boolean - - """Added in 24.09.0.""" - password: String - - """Added in 24.09.0.""" - project: String - - """Added in 24.09.0.""" - registry_name: String - - """Added in 24.09.0.""" - ssl_verify: Boolean - - """ - Registry type. One of ('docker', 'harbor', 'harbor2', 'github', 'gitlab', 'ecr', 'ecr-public', 'local'). Added in 24.09.0. - """ - type: ContainerRegistryTypeField - - """Added in 24.09.0.""" - url: String - - """Added in 24.09.0.""" - username: String + """Added in 25.1.0.""" + props: ModifyContainerRegistryNodeInput! ): ModifyContainerRegistryNode """Added in 24.09.0.""" @@ -1903,12 +1851,6 @@ type Mutations { """Object id. Can be either global id or object id. Added in 24.09.0.""" id: String! ): DeleteContainerRegistryNode - - """Added in 25.1.0.""" - associate_container_registry_with_group(group_id: String!, registry_id: String!): AssociateContainerRegistryWithGroup - - """Added in 25.1.0.""" - disassociate_container_registry_with_group(group_id: String!, registry_id: String!): DisassociateContainerRegistryWithGroup create_container_registry(hostname: String!, props: CreateContainerRegistryInput!): CreateContainerRegistry modify_container_registry(hostname: String!, props: ModifyContainerRegistryInput!): ModifyContainerRegistry delete_container_registry(hostname: String!): DeleteContainerRegistry @@ -2632,26 +2574,86 @@ type CreateContainerRegistryNode { container_registry: ContainerRegistryNode } -"""Added in 24.09.0.""" -type ModifyContainerRegistryNode { - container_registry: ContainerRegistryNode +input CreateContainerRegistryNodeInput { + """Added in 24.09.0.""" + url: String! + + """Added in 24.09.0.""" + type: ContainerRegistryTypeField! + + """Added in 24.09.0.""" + registry_name: String! + + """Added in 24.09.0.""" + is_global: Boolean + + """Added in 24.09.0.""" + project: String + + """Added in 24.09.0.""" + username: String + + """Added in 24.09.0.""" + password: String + + """Added in 24.09.0.""" + ssl_verify: Boolean + + """Added in 24.09.3.""" + extra: JSONString + + """Added in 25.1.0.""" + allowed_groups: AllowedGroups +} + +input AllowedGroups { + """List of group_ids to add associations. Added in 25.1.0.""" + add: [String] = [] + + """List of group_ids to remove associations. Added in 25.1.0.""" + remove: [String] = [] } """Added in 24.09.0.""" -type DeleteContainerRegistryNode { +type ModifyContainerRegistryNode { container_registry: ContainerRegistryNode } -"""Added in 25.1.0.""" -type AssociateContainerRegistryWithGroup { - ok: Boolean - msg: String +input ModifyContainerRegistryNodeInput { + """Added in 24.09.0.""" + url: String + + """Added in 24.09.0.""" + type: ContainerRegistryTypeField + + """Added in 24.09.0.""" + registry_name: String + + """Added in 24.09.0.""" + is_global: Boolean + + """Added in 24.09.0.""" + project: String + + """Added in 24.09.0.""" + username: String + + """Added in 24.09.0.""" + password: String + + """Added in 24.09.0.""" + ssl_verify: Boolean + + """Added in 24.09.3.""" + extra: JSONString + + """Added in 25.1.0.""" + allowed_groups: AllowedGroups } -"""Added in 25.1.0.""" -type DisassociateContainerRegistryWithGroup { - ok: Boolean - msg: String +"""Added in 24.09.0.""" +type DeleteContainerRegistryNode { + container_registry: ContainerRegistryNode } type CreateContainerRegistry { diff --git a/src/ai/backend/manager/models/container_registry.py b/src/ai/backend/manager/models/container_registry.py index 40b810f870d..6f08a23c940 100644 --- a/src/ai/backend/manager/models/container_registry.py +++ b/src/ai/backend/manager/models/container_registry.py @@ -10,13 +10,17 @@ import graphql import sqlalchemy as sa import yarl -from graphql import Undefined, UndefinedType +from graphql import Undefined from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import load_only, relationship from sqlalchemy.orm.exc import NoResultFound from ai.backend.common.exception import UnknownImageRegistry from ai.backend.common.logging_utils import BraceStyleAdapter +from ai.backend.manager.models.association_container_registries_groups import ( + AssociationContainerRegistriesGroupsRow, +) +from ai.backend.manager.models.utils import ExtendedAsyncSAEngine from ..defs import PASSWORD_PLACEHOLDER from .base import ( @@ -270,6 +274,45 @@ async def load_all( return [cls.from_row(ctx, row) for row in rows] +class AllowedGroups(graphene.InputObjectType): + add = graphene.List( + graphene.String, + default_value=[], + description="List of group_ids to add associations. Added in 25.1.0.", + ) + remove = graphene.List( + graphene.String, + default_value=[], + description="List of group_ids to remove associations. Added in 25.1.0.", + ) + + +async def handle_allowed_groups_update( + db: ExtendedAsyncSAEngine, registry_id: uuid.UUID, allowed_group_updates: AllowedGroups +): + async with db.begin_session() as db_sess: + if allowed_group_updates.add: + insert_values = [ + {"registry_id": registry_id, "group_id": group_id} + for group_id in allowed_group_updates.add + ] + + insert_query = sa.insert(AssociationContainerRegistriesGroupsRow).values(insert_values) + await db_sess.execute(insert_query) + + if allowed_group_updates.remove: + delete_query = ( + sa.delete(AssociationContainerRegistriesGroupsRow) + .where(AssociationContainerRegistriesGroupsRow.registry_id == registry_id) + .where( + AssociationContainerRegistriesGroupsRow.group_id.in_( + allowed_group_updates.remove + ) + ) + ) + await db_sess.execute(delete_query) + + class ContainerRegistryNode(graphene.ObjectType): class Meta: interfaces = (AsyncNode,) @@ -382,6 +425,19 @@ class Meta: description = "Added in 24.09.0." +class CreateContainerRegistryNodeInput(graphene.InputObjectType): + url = graphene.String(required=True, description="Added in 24.09.0.") + type = ContainerRegistryTypeField(required=True, description="Added in 24.09.0.") + registry_name = graphene.String(required=True, description="Added in 24.09.0.") + is_global = graphene.Boolean(description="Added in 24.09.0.") + project = graphene.String(description="Added in 24.09.0.") + username = graphene.String(description="Added in 24.09.0.") + password = graphene.String(description="Added in 24.09.0.") + ssl_verify = graphene.Boolean(description="Added in 24.09.0.") + extra = graphene.JSONString(description="Added in 24.09.3.") + allowed_groups = AllowedGroups(description="Added in 25.1.0.") + + class CreateContainerRegistryNode(graphene.Mutation): class Meta: description = "Added in 24.09.0." @@ -390,52 +446,33 @@ class Meta: container_registry = graphene.Field(ContainerRegistryNode) class Arguments: - url = graphene.String(required=True, description="Added in 24.09.0.") - type = ContainerRegistryTypeField( - required=True, - description=f"Added in 24.09.0. Registry type. One of {ContainerRegistryTypeField.allowed_values}.", - ) - registry_name = graphene.String(required=True, description="Added in 24.09.0.") - is_global = graphene.Boolean(description="Added in 24.09.0.") - project = graphene.String(description="Added in 24.09.0.") - username = graphene.String(description="Added in 24.09.0.") - password = graphene.String(description="Added in 24.09.0.") - ssl_verify = graphene.Boolean(description="Added in 24.09.0.") - extra = graphene.JSONString(description="Added in 24.09.3.") + props = CreateContainerRegistryNodeInput(required=True, description="Added in 25.1.0.") @classmethod async def mutate( cls, root, info: graphene.ResolveInfo, - url: str, - type: ContainerRegistryType, - registry_name: str, - is_global: bool | UndefinedType = Undefined, - project: str | UndefinedType = Undefined, - username: str | UndefinedType = Undefined, - password: str | UndefinedType = Undefined, - ssl_verify: bool | UndefinedType = Undefined, - extra: dict | UndefinedType = Undefined, + props: CreateContainerRegistryNodeInput, ) -> CreateContainerRegistryNode: ctx: GraphQueryContext = info.context input_config: dict[str, Any] = { - "registry_name": registry_name, - "url": url, - "type": type, + "registry_name": props.registry_name, + "url": props.url, + "type": props.type, } def _set_if_set(name: str, val: Any) -> None: if val is not Undefined: input_config[name] = val - _set_if_set("project", project) - _set_if_set("username", username) - _set_if_set("password", password) - _set_if_set("ssl_verify", ssl_verify) - _set_if_set("is_global", is_global) - _set_if_set("extra", extra) + _set_if_set("project", props.project) + _set_if_set("username", props.username) + _set_if_set("password", props.password) + _set_if_set("ssl_verify", props.ssl_verify) + _set_if_set("is_global", props.is_global) + _set_if_set("extra", props.extra) async with ctx.db.begin_session() as db_session: reg_row = ContainerRegistryRow(**input_config) @@ -443,9 +480,25 @@ def _set_if_set(name: str, val: Any) -> None: await db_session.flush() await db_session.refresh(reg_row) - return cls( - container_registry=ContainerRegistryNode.from_row(ctx, reg_row), - ) + if props.allowed_groups: + await handle_allowed_groups_update(ctx.db, reg_row.id, props.allowed_groups) + + return cls( + container_registry=ContainerRegistryNode.from_row(ctx, reg_row), + ) + + +class ModifyContainerRegistryNodeInput(graphene.InputObjectType): + url = graphene.String(description="Added in 24.09.0.") + type = ContainerRegistryTypeField(description="Added in 24.09.0.") + registry_name = graphene.String(description="Added in 24.09.0.") + is_global = graphene.Boolean(description="Added in 24.09.0.") + project = graphene.String(description="Added in 24.09.0.") + username = graphene.String(description="Added in 24.09.0.") + password = graphene.String(description="Added in 24.09.0.") + ssl_verify = graphene.Boolean(description="Added in 24.09.0.") + extra = graphene.JSONString(description="Added in 24.09.3.") + allowed_groups = AllowedGroups(description="Added in 25.1.0.") class ModifyContainerRegistryNode(graphene.Mutation): @@ -460,17 +513,7 @@ class Arguments: required=True, description="Object id. Can be either global id or object id. Added in 24.09.0.", ) - url = graphene.String(description="Added in 24.09.0.") - type = ContainerRegistryTypeField( - description=f"Registry type. One of {ContainerRegistryTypeField.allowed_values}. Added in 24.09.0." - ) - registry_name = graphene.String(description="Added in 24.09.0.") - is_global = graphene.Boolean(description="Added in 24.09.0.") - project = graphene.String(description="Added in 24.09.0.") - username = graphene.String(description="Added in 24.09.0.") - password = graphene.String(description="Added in 24.09.0.") - ssl_verify = graphene.Boolean(description="Added in 24.09.0.") - extra = graphene.JSONString(description="Added in 24.09.3.") + props = ModifyContainerRegistryNodeInput(required=True, description="Added in 25.1.0.") @classmethod async def mutate( @@ -478,15 +521,7 @@ async def mutate( root, info: graphene.ResolveInfo, id: str, - url: str | UndefinedType = Undefined, - type: ContainerRegistryType | UndefinedType = Undefined, - registry_name: str | UndefinedType = Undefined, - is_global: bool | UndefinedType = Undefined, - project: str | UndefinedType = Undefined, - username: str | UndefinedType = Undefined, - password: str | UndefinedType = Undefined, - ssl_verify: bool | UndefinedType = Undefined, - extra: dict | UndefinedType = Undefined, + props: ModifyContainerRegistryNodeInput, ) -> ModifyContainerRegistryNode: ctx: GraphQueryContext = info.context @@ -496,15 +531,15 @@ def _set_if_set(name: str, val: Any) -> None: if val is not Undefined: input_config[name] = val - _set_if_set("url", url) - _set_if_set("type", type) - _set_if_set("registry_name", registry_name) - _set_if_set("username", username) - _set_if_set("password", password) - _set_if_set("project", project) - _set_if_set("ssl_verify", ssl_verify) - _set_if_set("is_global", is_global) - _set_if_set("extra", extra) + _set_if_set("url", props.url) + _set_if_set("type", props.type) + _set_if_set("registry_name", props.registry_name) + _set_if_set("username", props.username) + _set_if_set("password", props.password) + _set_if_set("project", props.project) + _set_if_set("ssl_verify", props.ssl_verify) + _set_if_set("is_global", props.is_global) + _set_if_set("extra", props.extra) _, _id = AsyncNode.resolve_global_id(info, id) reg_id = uuid.UUID(_id) if _id else uuid.UUID(id) @@ -517,7 +552,10 @@ def _set_if_set(name: str, val: Any) -> None: for field, val in input_config.items(): setattr(reg_row, field, val) - return cls(container_registry=ContainerRegistryNode.from_row(ctx, reg_row)) + if props.allowed_groups: + await handle_allowed_groups_update(ctx.db, reg_row.id, props.allowed_groups) + + return cls(container_registry=ContainerRegistryNode.from_row(ctx, reg_row)) class DeleteContainerRegistryNode(graphene.Mutation): diff --git a/src/ai/backend/manager/models/gql.py b/src/ai/backend/manager/models/gql.py index 1c5640bbe57..db40a160dc4 100644 --- a/src/ai/backend/manager/models/gql.py +++ b/src/ai/backend/manager/models/gql.py @@ -74,10 +74,6 @@ AgentSummaryList, ModifyAgent, ) -from .gql_models.container_registry import ( - AssociateContainerRegistryWithGroup, - DisassociateContainerRegistryWithGroup, -) from .gql_models.domain import ( CreateDomainNode, DomainConnection, @@ -346,13 +342,6 @@ class Mutations(graphene.ObjectType): description="Added in 24.09.0." ) - associate_container_registry_with_group = AssociateContainerRegistryWithGroup.Field( - description="Added in 25.1.0." - ) - disassociate_container_registry_with_group = DisassociateContainerRegistryWithGroup.Field( - description="Added in 25.1.0." - ) - # Legacy mutations create_container_registry = CreateContainerRegistry.Field() modify_container_registry = ModifyContainerRegistry.Field() diff --git a/src/ai/backend/manager/models/gql_models/container_registry.py b/src/ai/backend/manager/models/gql_models/container_registry.py deleted file mode 100644 index 97b9f377977..00000000000 --- a/src/ai/backend/manager/models/gql_models/container_registry.py +++ /dev/null @@ -1,72 +0,0 @@ -from __future__ import annotations - -import logging -from typing import Self - -import graphene -import sqlalchemy as sa - -from ai.backend.logging import BraceStyleAdapter - -from ..association_container_registries_groups import ( - AssociationContainerRegistriesGroupsRow, -) -from ..base import simple_db_mutate -from ..user import UserRole - -log = BraceStyleAdapter(logging.getLogger(__spec__.name)) # type: ignore - - -class AssociateContainerRegistryWithGroup(graphene.Mutation): - """Added in 25.1.0.""" - - allowed_roles = (UserRole.SUPERADMIN,) - - class Arguments: - registry_id = graphene.String(required=True) - group_id = graphene.String(required=True) - - ok = graphene.Boolean() - msg = graphene.String() - - @classmethod - async def mutate( - cls, - root, - info: graphene.ResolveInfo, - registry_id: str, - group_id: str, - ) -> Self: - insert_query = sa.insert(AssociationContainerRegistriesGroupsRow).values({ - "registry_id": registry_id, - "group_id": group_id, - }) - return await simple_db_mutate(cls, info.context, insert_query) - - -class DisassociateContainerRegistryWithGroup(graphene.Mutation): - """Added in 25.1.0.""" - - allowed_roles = (UserRole.SUPERADMIN,) - - class Arguments: - registry_id = graphene.String(required=True) - group_id = graphene.String(required=True) - - ok = graphene.Boolean() - msg = graphene.String() - - @classmethod - async def mutate( - cls, - root, - info: graphene.ResolveInfo, - registry_id: str, - group_id: str, - ) -> Self: - delete_query = ( - sa.delete(AssociationContainerRegistriesGroupsRow) - .where(AssociationContainerRegistriesGroupsRow.registry_id == registry_id) - .where(AssociationContainerRegistriesGroupsRow.group_id == group_id) - ) - return await simple_db_mutate(cls, info.context, delete_query)