From a27f32f458fef332983b1e1479bb940db77c92a6 Mon Sep 17 00:00:00 2001 From: Gyubong Lee Date: Fri, 2 Aug 2024 11:58:33 +0000 Subject: [PATCH] feat: revert to `hostname` based client workflow --- src/ai/backend/manager/api/schema.graphql | 128 ++------- .../manager/models/container_registry.py | 265 +++++------------- src/ai/backend/manager/models/gql.py | 60 +--- .../models/test_container_registries.py | 164 ++--------- 4 files changed, 118 insertions(+), 499 deletions(-) diff --git a/src/ai/backend/manager/api/schema.graphql b/src/ai/backend/manager/api/schema.graphql index 8bb5d574148..62050db210c 100644 --- a/src/ai/backend/manager/api/schema.graphql +++ b/src/ai/backend/manager/api/schema.graphql @@ -113,18 +113,8 @@ type Queries { endpoint_token(token: String!): EndpointToken endpoint_token_list(limit: Int!, offset: Int!, filter: String, order: String, endpoint_id: UUID): EndpointTokenList quota_scope(storage_host_name: String!, quota_scope_id: String!): QuotaScope - - """Added in 24.09.0.""" - container_registry(id: UUID!): ContainerRegistry - - """Added in 24.09.0.""" - container_registries(registry_name: String!): [ContainerRegistry] - - """Added in 24.09.0.""" - container_registry_node(id: String!): ContainerRegistry - - """Added in 24.09.0.""" - container_registry_nodes(filter: String, order: String, offset: Int, before: String, after: String, first: Int, last: Int): ContainerRegistryConnection + container_registry(hostname: String!): ContainerRegistry + container_registries: [ContainerRegistry] """Added in 24.03.0.""" model_card(id: String!): ModelCard @@ -1028,57 +1018,20 @@ type QuotaDetails { type ContainerRegistry implements Node { """The ID of the object""" id: ID! + hostname: String config: ContainerRegistryConfig } type ContainerRegistryConfig { - """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.""" + type: String! + project: [String] username: String - - """Added in 24.09.0.""" password: String - - """Added in 24.09.0.""" ssl_verify: Boolean -} - -"""Added in 24.09.0.""" -scalar ContainerRegistryTypeField - -"""Added in 24.09.0.""" -type ContainerRegistryConnection { - """Pagination data for this connection.""" - pageInfo: PageInfo! - - """Contains the nodes in this connection.""" - edges: [ContainerRegistryEdge]! - """Total count of the GQL nodes of the query.""" - count: Int -} - -"""A Relay edge containing a `ContainerRegistry` and its cursor.""" -type ContainerRegistryEdge { - """The item at the end of the edge""" - node: ContainerRegistry - - """A cursor for use in pagination""" - cursor: String! + """Added in 24.09.0.""" + is_global: Boolean } type ModelCard implements Node { @@ -1233,12 +1186,9 @@ type Mutations { disassociate_all_scaling_groups_with_group(user_group: UUID!): DisassociateAllScalingGroupsWithGroup set_quota_scope(props: QuotaScopeInput!, quota_scope_id: String!, storage_host_name: String!): SetQuotaScope unset_quota_scope(quota_scope_id: String!, storage_host_name: String!): UnsetQuotaScope - create_container_registry( - """Added in 24.09.0.""" - props: CreateContainerRegistryInput! - ): CreateContainerRegistry - modify_container_registry(props: ModifyContainerRegistryInput!): ModifyContainerRegistry - delete_container_registry(props: DeleteContainerRegistryInput!): DeleteContainerRegistry + create_container_registry(hostname: String!, props: CreateContainerRegistryInput!): CreateContainerRegistry + modify_container_registry(hostname: String!, props: ModifyContainerRegistryInput!): ModifyContainerRegistry + delete_container_registry(hostname: String!): DeleteContainerRegistry modify_endpoint(endpoint_id: UUID!, props: ModifyEndpointInput!): ModifyEndpoint } @@ -1843,36 +1793,19 @@ type UnsetQuotaScope { } type CreateContainerRegistry { - id: UUID! container_registry: ContainerRegistry } input CreateContainerRegistryInput { - """Added in 24.09.0.""" url: String! - - """ - Registry type. One of ('docker', 'harbor', 'harbor2', 'local'). 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.""" + type: String! + project: [String] username: String - - """Added in 24.09.0.""" password: String + ssl_verify: Boolean """Added in 24.09.0.""" - ssl_verify: Boolean + is_global: Boolean } type ModifyContainerRegistry { @@ -1880,46 +1813,21 @@ type ModifyContainerRegistry { } input ModifyContainerRegistryInput { - """Object id. Can be either global id or object id. Added in 24.09.0.""" - id: String! - - """Added in 24.09.0.""" url: String - - """ - Registry type. One of ('docker', 'harbor', 'harbor2', 'local'). 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.""" + type: String + project: [String] username: String - - """Added in 24.09.0.""" password: String + ssl_verify: Boolean """Added in 24.09.0.""" - ssl_verify: Boolean + is_global: Boolean } type DeleteContainerRegistry { container_registry: ContainerRegistry } -"""Added in 24.09.0.""" -input DeleteContainerRegistryInput { - """Object id. Can be either global id or object id. Added in 24.09.0.""" - id: String! -} - type ModifyEndpoint { ok: Boolean msg: String diff --git a/src/ai/backend/manager/models/container_registry.py b/src/ai/backend/manager/models/container_registry.py index b069d185b7d..fe314e6c483 100644 --- a/src/ai/backend/manager/models/container_registry.py +++ b/src/ai/backend/manager/models/container_registry.py @@ -6,9 +6,7 @@ from typing import TYPE_CHECKING, Any, Sequence import graphene -import graphql import sqlalchemy as sa -from graphene.types import Scalar from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm.exc import NoResultFound @@ -17,17 +15,12 @@ from ..defs import PASSWORD_PLACEHOLDER from .base import ( Base, - FilterExprArg, IDColumn, - OrderExprArg, StrEnumType, - generate_sql_info_for_gql_connection, privileged_mutation, set_if_set, ) -from .gql_relay import AsyncNode, Connection, ConnectionResolverResult -from .minilang.ordering import OrderSpecItem, QueryOrderParser -from .minilang.queryfilter import FieldSpecItem, QueryFilterParser +from .gql_relay import AsyncNode from .user import UserRole if TYPE_CHECKING: @@ -51,25 +44,6 @@ class ContainerRegistryType(enum.StrEnum): LOCAL = "local" -class ContainerRegistryTypeField(Scalar): - """Added in 24.09.0.""" - - allowed_values = tuple(t.value for t in ContainerRegistryType) - - @staticmethod - def serialize(val: ContainerRegistryType) -> str: - return val.value - - @staticmethod - def parse_literal(node, _variables=None): - if isinstance(node, graphql.language.ast.StringValueNode): - return ContainerRegistryType(node.value) - - @staticmethod - def parse_value(value: str) -> ContainerRegistryType: - return ContainerRegistryType(value) - - class ContainerRegistryRow(Base): __tablename__ = "container_registries" id = IDColumn() @@ -106,162 +80,68 @@ async def get( raise NoResultFound return row + # TODO: Remove the following function assuming the registry_name is unique. @classmethod - async def list_by_registry_name( + async def get_by_hostname( cls, session: AsyncSession, - registry_name: str, - ) -> Sequence[ContainerRegistryRow]: + hostname: str, + ) -> ContainerRegistryRow: query = sa.select(ContainerRegistryRow).where( - ContainerRegistryRow.registry_name == registry_name + ContainerRegistryRow.registry_name == hostname ) result = await session.execute(query) - rows = result.scalars().all() - if not rows: + row = result.scalar() + if row is None: raise NoResultFound - return rows + return row class CreateContainerRegistryInput(graphene.InputObjectType): - url = graphene.String(required=True, description="Added in 24.09.0.") - type = ContainerRegistryTypeField( - required=True, - description=f"Registry type. One of {ContainerRegistryTypeField.allowed_values}. Added in 24.09.0.", - ) - registry_name = graphene.String(required=True, description="Added in 24.09.0.") + url = graphene.String(required=True) + type = graphene.String(required=True) + project = graphene.List(graphene.String) + username = graphene.String() + password = graphene.String() + ssl_verify = graphene.Boolean() 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.") class ModifyContainerRegistryInput(graphene.InputObjectType): - id = graphene.String( - 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.") + url = graphene.String() + type = graphene.String() + project = graphene.List(graphene.String) + username = graphene.String() + password = graphene.String() + ssl_verify = graphene.Boolean() 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.") - - -class DeleteContainerRegistryInput(graphene.InputObjectType): - """Added in 24.09.0.""" - - id = graphene.String( - required=True, - description="Object id. Can be either global id or object id. Added in 24.09.0.", - ) class ContainerRegistryConfig(graphene.ObjectType): - 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.") + url = graphene.String(required=True) + type = graphene.String(required=True) + project = graphene.List(graphene.String) + username = graphene.String() + password = graphene.String() + ssl_verify = graphene.Boolean() 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.") class ContainerRegistry(graphene.ObjectType): - class Meta: - interfaces = (AsyncNode,) - + hostname = graphene.String() config = graphene.Field(ContainerRegistryConfig) - _queryfilter_fieldspec: dict[str, FieldSpecItem] = { - "id": ("id", None), - "registry_name": ("registry_name", None), - } - - _queryorder_colmap: dict[str, OrderSpecItem] = { - "id": ("id", None), - "registry_name": ("registry_name", None), - } - - @classmethod - async def get_node(cls, info: graphene.ResolveInfo, id: str) -> ContainerRegistry: - graph_ctx: GraphQueryContext = info.context - - _, reg_id = AsyncNode.resolve_global_id(info, id) - select_stmt = sa.select(ContainerRegistryRow).where(ContainerRegistryRow.id == reg_id) - async with graph_ctx.db.begin_readonly_session() as db_session: - reg_row = await db_session.scalar(select_stmt) - if reg_row is None: - raise ValueError(f"Container registry not found (id: {reg_id})") - return cls.from_row(graph_ctx, reg_row) - - @classmethod - async def get_connection( - cls, - info: graphene.ResolveInfo, - filter_expr: str | None = None, - order_expr: str | None = None, - offset: int | None = None, - after: str | None = None, - first: int | None = None, - before: str | None = None, - last: int | None = None, - ) -> ConnectionResolverResult: - graph_ctx: GraphQueryContext = info.context - _filter_arg = ( - FilterExprArg(filter_expr, QueryFilterParser(cls._queryfilter_fieldspec)) - if filter_expr is not None - else None - ) - _order_expr = ( - OrderExprArg(order_expr, QueryOrderParser(cls._queryorder_colmap)) - if order_expr is not None - else None - ) - ( - query, - _, - conditions, - cursor, - pagination_order, - page_size, - ) = generate_sql_info_for_gql_connection( - info, - ContainerRegistryRow, - ContainerRegistryRow.id, - _filter_arg, - _order_expr, - offset, - after=after, - first=first, - before=before, - last=last, - ) - cnt_query = sa.select(sa.func.count()).select_from(ContainerRegistryRow) - for cond in conditions: - cnt_query = cnt_query.where(cond) - async with graph_ctx.db.begin_readonly_session() as db_session: - reg_rows = (await db_session.scalars(query)).all() - result = [cls.from_row(graph_ctx, row) for row in reg_rows] - - total_cnt = await db_session.scalar(cnt_query) - return ConnectionResolverResult(result, cursor, pagination_order, page_size, total_cnt) + class Meta: + interfaces = (AsyncNode,) @classmethod def from_row(cls, ctx: GraphQueryContext, row: ContainerRegistryRow) -> ContainerRegistry: return cls( - id=row.id, + hostname=row.registry_name, config=ContainerRegistryConfig( url=row.url, - type=row.type, - registry_name=row.registry_name, - project=row.project, + type=str(row.type), + project=[row.project], username=row.username, password=PASSWORD_PLACEHOLDER if row.password is not None else None, ssl_verify=row.ssl_verify, @@ -269,14 +149,15 @@ def from_row(cls, ctx: GraphQueryContext, row: ContainerRegistryRow) -> Containe ), ) + # TODO: Remove the following function assuming the registry_name is unique. @classmethod - async def load(cls, ctx: GraphQueryContext, id: str | uuid.UUID) -> ContainerRegistry: + async def load_by_hostname(cls, ctx: GraphQueryContext, hostname: str) -> ContainerRegistry: async with ctx.db.begin_readonly_session() as session: return cls.from_row( ctx, - await ContainerRegistryRow.get( + await ContainerRegistryRow.get_by_hostname( session, - id, + hostname, ), ) @@ -289,31 +170,14 @@ async def load_all( rows = await session.execute(sa.select(ContainerRegistryRow)) return [cls.from_row(ctx, row) for row in rows] - @classmethod - async def list_by_registry_name( - cls, - ctx: GraphQueryContext, - registry_name: str, - ) -> Sequence[ContainerRegistry]: - async with ctx.db.begin_readonly_session() as session: - rows = await ContainerRegistryRow.list_by_registry_name(session, registry_name) - return [cls.from_row(ctx, row) for row in rows] - - -class ContainerRegistryConnection(Connection): - """Added in 24.09.0.""" - - class Meta: - node = ContainerRegistry - class CreateContainerRegistry(graphene.Mutation): allowed_roles = (UserRole.SUPERADMIN,) - id = graphene.UUID(required=True) container_registry = graphene.Field(ContainerRegistry) class Arguments: - props = CreateContainerRegistryInput(required=True, description="Added in 24.09.0.") + hostname = graphene.String(required=True) + props = CreateContainerRegistryInput(required=True) @classmethod @privileged_mutation( @@ -321,17 +185,19 @@ class Arguments: lambda id, **kwargs: (None, id), ) async def mutate( - cls, root, info: graphene.ResolveInfo, props: CreateContainerRegistryInput + cls, root, info: graphene.ResolveInfo, hostname: str, props: CreateContainerRegistryInput ) -> CreateContainerRegistry: ctx: GraphQueryContext = info.context input_config: dict[str, Any] = { - "registry_name": props.registry_name, + "registry_name": hostname, "url": props.url, - "type": props.type, + "type": ContainerRegistryType(props.type), } - set_if_set(props, input_config, "project") + if props.project: + input_config["project"] = props.project[0] + set_if_set(props, input_config, "username") set_if_set(props, input_config, "password") set_if_set(props, input_config, "ssl_verify") @@ -344,7 +210,6 @@ async def mutate( await db_session.refresh(reg_row) return cls( - id=reg_row.id, container_registry=ContainerRegistry.from_row(ctx, reg_row), ) @@ -354,6 +219,7 @@ class ModifyContainerRegistry(graphene.Mutation): container_registry = graphene.Field(ContainerRegistry) class Arguments: + hostname = graphene.String(required=True) props = ModifyContainerRegistryInput(required=True) @classmethod @@ -365,29 +231,35 @@ async def mutate( cls, root, info: graphene.ResolveInfo, + hostname: str, props: ModifyContainerRegistryInput, ) -> ModifyContainerRegistry: ctx: GraphQueryContext = info.context - input_config: dict[str, Any] = {} + input_config: dict[str, Any] = { + "registry_name": hostname, + } + + if props.project: + input_config["project"] = props.project[0] + + if props.type: + input_config["type"] = ContainerRegistryType(props.type) set_if_set(props, input_config, "url") - set_if_set(props, input_config, "type") - set_if_set(props, input_config, "registry_name") set_if_set(props, input_config, "is_global") set_if_set(props, input_config, "username") set_if_set(props, input_config, "password") - set_if_set(props, input_config, "project") set_if_set(props, input_config, "ssl_verify") - _, _id = AsyncNode.resolve_global_id(info, props.id) - reg_id = uuid.UUID(_id) if _id else uuid.UUID(props.id) - async with ctx.db.begin_session() as session: - stmt = sa.select(ContainerRegistryRow).where(ContainerRegistryRow.id == reg_id) + stmt = sa.select(ContainerRegistryRow).where( + ContainerRegistryRow.registry_name == hostname + ) reg_row = await session.scalar(stmt) if reg_row is None: - raise ValueError(f"ContainerRegistry not found (id: {reg_id})") + raise ValueError(f"ContainerRegistry not found (hostname: {hostname})") + for field, val in input_config.items(): setattr(reg_row, field, val) @@ -399,7 +271,7 @@ class DeleteContainerRegistry(graphene.Mutation): container_registry = graphene.Field(ContainerRegistry) class Arguments: - props = DeleteContainerRegistryInput(required=True) + hostname = graphene.String(required=True) @classmethod @privileged_mutation( @@ -410,16 +282,13 @@ async def mutate( cls, root, info: graphene.ResolveInfo, - props: DeleteContainerRegistryInput, + hostname: str, ) -> DeleteContainerRegistry: ctx: GraphQueryContext = info.context - - _, _id = AsyncNode.resolve_global_id(info, props.id) - reg_id = uuid.UUID(_id) if _id else uuid.UUID(props.id) - container_registry = await ContainerRegistry.load(ctx, reg_id) + container_registry = await ContainerRegistry.load_by_hostname(ctx, hostname) async with ctx.db.begin_session() as session: - await session.execute( - sa.delete(ContainerRegistryRow).where(ContainerRegistryRow.id == reg_id) + stmt = sa.delete(ContainerRegistryRow).where( + ContainerRegistryRow.registry_name == hostname ) - + await session.execute(stmt) return cls(container_registry=container_registry) diff --git a/src/ai/backend/manager/models/gql.py b/src/ai/backend/manager/models/gql.py index 22c73f1aef9..84935c2a22c 100644 --- a/src/ai/backend/manager/models/gql.py +++ b/src/ai/backend/manager/models/gql.py @@ -18,7 +18,6 @@ from .container_registry import ( ContainerRegistry, - ContainerRegistryConnection, CreateContainerRegistry, DeleteContainerRegistry, ModifyContainerRegistry, @@ -707,23 +706,9 @@ class Queries(graphene.ObjectType): quota_scope_id=graphene.String(required=True), ) - container_registry = graphene.Field( - ContainerRegistry, id=graphene.UUID(required=True), description="Added in 24.09.0." - ) + container_registry = graphene.Field(ContainerRegistry, hostname=graphene.String(required=True)) - container_registries = graphene.List( - ContainerRegistry, - registry_name=graphene.String(required=True), - description="Added in 24.09.0.", - ) - - container_registry_node = graphene.Field( - ContainerRegistry, id=graphene.String(required=True), description="Added in 24.09.0." - ) - - container_registry_nodes = PaginatedConnectionField( - ContainerRegistryConnection, description="Added in 24.09.0." - ) + container_registries = graphene.List(ContainerRegistry) model_card = graphene.Field( ModelCard, id=graphene.String(required=True), description="Added in 24.03.0." @@ -2208,54 +2193,19 @@ async def resolve_quota_scope( async def resolve_container_registry( root: Any, info: graphene.ResolveInfo, - id: graphene.UUID, + hostname: str, ) -> ContainerRegistry: ctx: GraphQueryContext = info.context - return await ContainerRegistry.load(ctx, id) + return await ContainerRegistry.load_by_hostname(ctx, hostname) @staticmethod @privileged_query(UserRole.SUPERADMIN) async def resolve_container_registries( root: Any, info: graphene.ResolveInfo, - registry_name: graphene.String, ) -> Sequence[ContainerRegistry]: ctx: GraphQueryContext = info.context - return await ContainerRegistry.list_by_registry_name(ctx, registry_name) - - @staticmethod - @privileged_query(UserRole.SUPERADMIN) - async def resolve_container_registry_node( - root: Any, - info: graphene.ResolveInfo, - id: str, - ) -> ContainerRegistry: - return await ContainerRegistry.get_node(info, id) - - @staticmethod - @privileged_query(UserRole.SUPERADMIN) - async def resolve_container_registry_nodes( - root: Any, - info: graphene.ResolveInfo, - *, - filter: str | None = None, - order: str | None = None, - offset: int | None = None, - after: str | None = None, - first: int | None = None, - before: str | None = None, - last: int | None = None, - ) -> ConnectionResolverResult: - return await ContainerRegistry.get_connection( - info, - filter, - order, - offset, - after, - first, - before, - last, - ) + return await ContainerRegistry.load_all(ctx) async def resolve_model_card( root: Any, diff --git a/tests/manager/models/test_container_registries.py b/tests/manager/models/test_container_registries.py index b6791d156a8..e4c167035df 100644 --- a/tests/manager/models/test_container_registries.py +++ b/tests/manager/models/test_container_registries.py @@ -7,9 +7,8 @@ from ai.backend.manager.models.utils import ExtendedAsyncSAEngine CONTAINER_REGISTRY_FIELDS = """ - id + hostname config { - registry_name url type project @@ -54,8 +53,8 @@ async def test_create_container_registry(client: Client, database_engine: Extend context = get_graphquery_context(database_engine) query = """ - mutation CreateContainerRegistry($props: CreateContainerRegistryInput!) { - create_container_registry(props: $props) { + mutation CreateContainerRegistry($hostname: String!, $props: CreateContainerRegistryInput!) { + create_container_registry(hostname: $hostname, props: $props) { container_registry { $CONTAINER_REGISTRY_FIELDS } @@ -64,11 +63,11 @@ async def test_create_container_registry(client: Client, database_engine: Extend """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) variables = { + "hostname": "cr.example.com", "props": { - "registry_name": "cr.example.com", "url": "http://cr.example.com", "type": "docker", - "project": "default", + "project": ["default"], "username": "username", "password": "password", "ssl_verify": False, @@ -80,55 +79,25 @@ async def test_create_container_registry(client: Client, database_engine: Extend container_registry = response["data"]["create_container_registry"]["container_registry"] - id = container_registry.pop("id", None) - assert id is not None - assert container_registry["config"] == { - "registry_name": "cr.example.com", "url": "http://cr.example.com", "type": "docker", - "project": "default", + "project": ["default"], "username": "username", "password": PASSWORD_PLACEHOLDER, "ssl_verify": False, "is_global": False, } - variables["props"]["project"] = "default2" - await client.execute_async(query, variables=variables, context_value=context) - -@pytest.mark.dependency(depends=["test_get_container_registry"]) +@pytest.mark.dependency(depends=["test_create_container_registry"]) @pytest.mark.asyncio async def test_modify_container_registry(client: Client, database_engine: ExtendedAsyncSAEngine): context = get_graphquery_context(database_engine) query = """ - query ContainerRegistries($registry_name: String!) { - container_registries (registry_name: $registry_name) { - $CONTAINER_REGISTRY_FIELDS - } - } - """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) - - variables: dict[str, dict | str] = { - "registry_name": "cr.example.com", - } - - response = await client.execute_async(query, variables=variables, context_value=context) - - target_container_registries = list( - filter( - lambda item: item["config"]["project"] == "default", - response["data"]["container_registries"], - ) - ) - assert len(target_container_registries) == 1 - target_container_registry = target_container_registries[0] - - query = """ - mutation ModifyContainerRegistry($id: String!, $props: ModifyContainerRegistryInput!) { - modify_container_registry(id: $id, props: $props) { + mutation ModifyContainerRegistry($hostname: String!, $props: ModifyContainerRegistryInput!) { + modify_container_registry(hostname: $hostname, props: $props) { container_registry { $CONTAINER_REGISTRY_FIELDS } @@ -137,9 +106,8 @@ async def test_modify_container_registry(client: Client, database_engine: Extend """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) variables = { - "id": target_container_registry["id"], + "hostname": "cr.example.com", "props": { - "registry_name": "cr.example.com", "username": "username2", }, } @@ -147,31 +115,28 @@ async def test_modify_container_registry(client: Client, database_engine: Extend response = await client.execute_async(query, variables=variables, context_value=context) container_registry = response["data"]["modify_container_registry"]["container_registry"] - assert container_registry["config"]["registry_name"] == "cr.example.com" assert container_registry["config"]["url"] == "http://cr.example.com" assert container_registry["config"]["type"] == "docker" - assert container_registry["config"]["project"] == "default" + assert container_registry["config"]["project"] == ["default"] assert container_registry["config"]["username"] == "username2" assert container_registry["config"]["ssl_verify"] is False assert container_registry["config"]["is_global"] is False variables = { - "id": target_container_registry["id"], + "hostname": "cr.example.com", "props": { - "registry_name": "cr.example.com", "url": "http://cr2.example.com", "type": "harbor2", - "project": "example", + "project": ["example"], }, } response = await client.execute_async(query, variables=variables, context_value=context) container_registry = response["data"]["modify_container_registry"]["container_registry"] - assert container_registry["config"]["registry_name"] == "cr.example.com" assert container_registry["config"]["url"] == "http://cr2.example.com" assert container_registry["config"]["type"] == "harbor2" - assert container_registry["config"]["project"] == "example" + assert container_registry["config"]["project"] == ["example"] assert container_registry["config"]["username"] == "username2" assert container_registry["config"]["ssl_verify"] is False assert container_registry["config"]["is_global"] is False @@ -185,31 +150,8 @@ async def test_modify_container_registry_allows_empty_string( context = get_graphquery_context(database_engine) query = """ - query ContainerRegistries($registry_name: String!) { - container_registries (registry_name: $registry_name) { - $CONTAINER_REGISTRY_FIELDS - } - } - """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) - - variables: dict[str, dict | str] = { - "registry_name": "cr.example.com", - } - - response = await client.execute_async(query, variables=variables, context_value=context) - - target_container_registries = list( - filter( - lambda item: item["config"]["project"] == "example", - response["data"]["container_registries"], - ) - ) - assert len(target_container_registries) == 1 - target_container_registry = target_container_registries[0] - - query = """ - mutation ModifyContainerRegistry($id: String!, $props: ModifyContainerRegistryInput!) { - modify_container_registry(id: $id, props: $props) { + mutation ModifyContainerRegistry($hostname: String!, $props: ModifyContainerRegistryInput!) { + modify_container_registry(hostname: $hostname, props: $props) { container_registry { $CONTAINER_REGISTRY_FIELDS } @@ -219,9 +161,8 @@ async def test_modify_container_registry_allows_empty_string( # Given an empty string to password variables = { - "id": target_container_registry["id"], + "hostname": "cr.example.com", "props": { - "registry_name": "cr.example.com", "password": "", }, } @@ -229,10 +170,9 @@ async def test_modify_container_registry_allows_empty_string( # Then password is set to empty string response = await client.execute_async(query, variables=variables, context_value=context) container_registry = response["data"]["modify_container_registry"]["container_registry"] - assert container_registry["config"]["registry_name"] == "cr.example.com" assert container_registry["config"]["url"] == "http://cr2.example.com" assert container_registry["config"]["type"] == "harbor2" - assert container_registry["config"]["project"] == "example" + assert container_registry["config"]["project"] == ["example"] assert container_registry["config"]["username"] == "username2" assert container_registry["config"]["password"] == PASSWORD_PLACEHOLDER assert container_registry["config"]["ssl_verify"] is False @@ -247,31 +187,8 @@ async def test_modify_container_registry_allows_null_for_unset( context = get_graphquery_context(database_engine) query = """ - query ContainerRegistries($registry_name: String!) { - container_registries (registry_name: $registry_name) { - $CONTAINER_REGISTRY_FIELDS - } - } - """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) - - variables: dict[str, dict | str] = { - "registry_name": "cr.example.com", - } - - response = await client.execute_async(query, variables=variables, context_value=context) - - target_container_registries = list( - filter( - lambda item: item["config"]["project"] == "example", - response["data"]["container_registries"], - ) - ) - assert len(target_container_registries) == 1 - target_container_registry = target_container_registries[0] - - query = """ - mutation ModifyContainerRegistry($id: String!, $props: ModifyContainerRegistryInput!) { - modify_container_registry(id: $id, props: $props) { + mutation ModifyContainerRegistry($hostname: String!, $props: ModifyContainerRegistryInput!) { + modify_container_registry(hostname: $hostname, props: $props) { container_registry { $CONTAINER_REGISTRY_FIELDS } @@ -281,9 +198,8 @@ async def test_modify_container_registry_allows_null_for_unset( # Given a null to password variables = { - "id": target_container_registry["id"], + "hostname": "cr.example.com", "props": { - "registry_name": "cr.example.com", "password": None, }, } @@ -291,10 +207,9 @@ async def test_modify_container_registry_allows_null_for_unset( # Then password is unset response = await client.execute_async(query, variables=variables, context_value=context) container_registry = response["data"]["modify_container_registry"]["container_registry"] - assert container_registry["config"]["registry_name"] == "cr.example.com" assert container_registry["config"]["url"] == "http://cr2.example.com" assert container_registry["config"]["type"] == "harbor2" - assert container_registry["config"]["project"] == "example" + assert container_registry["config"]["project"] == ["example"] assert container_registry["config"]["username"] == "username2" assert container_registry["config"]["password"] is None assert container_registry["config"]["ssl_verify"] is False @@ -307,31 +222,8 @@ async def test_delete_container_registry(client: Client, database_engine: Extend context = get_graphquery_context(database_engine) query = """ - query ContainerRegistries($registry_name: String!) { - container_registries (registry_name: $registry_name) { - $CONTAINER_REGISTRY_FIELDS - } - } - """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) - - variables = { - "registry_name": "cr.example.com", - } - - response = await client.execute_async(query, variables=variables, context_value=context) - - target_container_registries = list( - filter( - lambda item: item["config"]["project"] == "example", - response["data"]["container_registries"], - ) - ) - assert len(target_container_registries) == 1 - target_container_registry = target_container_registries[0] - - query = """ - mutation DeleteContainerRegistry($id: String!) { - delete_container_registry(id: $id) { + mutation DeleteContainerRegistry($hostname: String!) { + delete_container_registry(hostname: $hostname) { container_registry { $CONTAINER_REGISTRY_FIELDS } @@ -340,16 +232,16 @@ async def test_delete_container_registry(client: Client, database_engine: Extend """.replace("$CONTAINER_REGISTRY_FIELDS", CONTAINER_REGISTRY_FIELDS) variables = { - "id": target_container_registry["id"], + "hostname": "cr.example.com", } response = await client.execute_async(query, variables=variables, context_value=context) container_registry = response["data"]["delete_container_registry"]["container_registry"] - assert container_registry["config"]["registry_name"] == "cr.example.com" + assert container_registry["hostname"] == "cr.example.com" query = """ - query ContainerRegistries($registry_name: String!) { - container_registries (registry_name: $registry_name) { + query ContainerRegistries() { + container_registries () { $CONTAINER_REGISTRY_FIELDS } }