Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/12928.feature.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add the AppConfig fragment write REST v2 API: create / get / update / purge plus bulk update and bulk delete of raw config fragments, open to any authenticated user and gated by RBAC per scope (BEP-1052).
300 changes: 300 additions & 0 deletions docs/manager/rest-reference/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,127 @@
"title": "AppConfigScopeType",
"type": "string"
},
"CreateAppConfigFragmentInput": {
"description": "Input for creating a new app config fragment at a given scope.",
"properties": {
"config_name": {
"description": "Registered config name.",
"maxLength": 128,
"minLength": 1,
"title": "Config Name",
"type": "string"
},
"scope_type": {
"$ref": "#/components/schemas/AppConfigScopeType",
"description": "Scope the fragment is written at (public | domain | user)."
},
"scope_id": {
"anyOf": [
{
"format": "uuid",
"type": "string"
},
{
"type": "null"
}
],
"default": null,
"description": "Scope identifier: the domain id (domain scope) or the user id (user scope). Null for public scope, which has no owner.",
"title": "Scope Id"
},
"config": {
"additionalProperties": true,
"description": "The fragment's JSON config document.",
"title": "Config",
"type": "object"
}
},
"required": [
"config_name",
"scope_type",
"config"
],
"title": "CreateAppConfigFragmentInput",
"type": "object"
},
"AppConfigFragmentUpdateItem": {
"description": "One item of a bulk update, carrying its own target id.\n\nBulk requests address many fragments in a single call, so the id belongs in the body\nhere β€” unlike the single-fragment :class:`UpdateAppConfigFragmentInput`.",
"properties": {
"id": {
"description": "App config fragment id to update.",
"format": "uuid",
"title": "Id",
"type": "string"
},
"config": {
"additionalProperties": true,
"description": "The replacement JSON config document.",
"title": "Config",
"type": "object"
}
},
"required": [
"id",
"config"
],
"title": "AppConfigFragmentUpdateItem",
"type": "object"
},
"BulkUpdateAppConfigFragmentInput": {
"description": "Input for updating many fragments' config documents (per-item partial success).",
"properties": {
"items": {
"description": "Fragments to update, each identified by its id.",
"items": {
"$ref": "#/components/schemas/AppConfigFragmentUpdateItem"
},
"minItems": 1,
"title": "Items",
"type": "array"
}
},
"required": [
"items"
],
"title": "BulkUpdateAppConfigFragmentInput",
"type": "object"
},
"BulkPurgeAppConfigFragmentInput": {
"description": "Input for purging many fragments (per-item partial success).",
"properties": {
"ids": {
"description": "Fragment ids to purge.",
"items": {
"format": "uuid",
"type": "string"
},
"minItems": 1,
"title": "Ids",
"type": "array"
}
},
"required": [
"ids"
],
"title": "BulkPurgeAppConfigFragmentInput",
"type": "object"
},
"UpdateAppConfigFragmentInput": {
"description": "Input for updating one app config fragment's config document.\n\nThe target fragment is identified by the request path, not by this body.",
"properties": {
"config": {
"additionalProperties": true,
"description": "The replacement JSON config document.",
"title": "Config",
"type": "object"
}
},
"required": [
"config"
],
"title": "UpdateAppConfigFragmentInput",
"type": "object"
},
"CreateAppConfigAllowListInput": {
"description": "Input for registering a new app config allow-list entry.",
"properties": {
Expand Down Expand Up @@ -39583,6 +39704,185 @@
"description": "Retrieve aggregate resource capacity/usage across all agents (superadmin only).\n\n**Preconditions:**\n* Superadmin privilege required.\n"
}
},
"/v2/app-config-fragments/": {
"post": {
"operationId": "v2/app-config-fragments.create",
"tags": [
"v2/app-config-fragments"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateAppConfigFragmentInput"
}
}
}
},
"parameters": [],
"description": "Create a fragment at the caller's authorized scope (auth required, RBAC-gated).\n\n**Preconditions:**\n* User privilege required.\n"
}
},
"/v2/app-config-fragments/bulk-update": {
"post": {
"operationId": "v2/app-config-fragments.bulk_update",
"tags": [
"v2/app-config-fragments"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkUpdateAppConfigFragmentInput"
}
}
}
},
"parameters": [],
"description": "Update many fragments' configs by id, with per-item partial success (auth, RBAC).\n\n**Preconditions:**\n* User privilege required.\n"
}
},
"/v2/app-config-fragments/bulk-delete": {
"post": {
"operationId": "v2/app-config-fragments.bulk_purge",
"tags": [
"v2/app-config-fragments"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BulkPurgeAppConfigFragmentInput"
}
}
}
},
"parameters": [],
"description": "Purge many fragments by id, with per-item partial success (auth, RBAC).\n\n**Preconditions:**\n* User privilege required.\n"
}
},
"/v2/app-config-fragments/{fragment_id}": {
"get": {
"operationId": "v2/app-config-fragments.get",
"tags": [
"v2/app-config-fragments"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"parameters": [
{
"name": "fragment_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"description": "Get a fragment by id (auth required, RBAC-gated).\n\n**Preconditions:**\n* User privilege required.\n"
},
"patch": {
"operationId": "v2/app-config-fragments.update",
"tags": [
"v2/app-config-fragments"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/UpdateAppConfigFragmentInput"
}
}
}
},
"parameters": [
{
"name": "fragment_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"description": "Update a fragment's config document by id (auth required, RBAC-gated).\n\n**Preconditions:**\n* User privilege required.\n"
},
"delete": {
"operationId": "v2/app-config-fragments.purge",
"tags": [
"v2/app-config-fragments"
],
"responses": {
"200": {
"description": "Successful response"
}
},
"security": [
{
"TokenAuth": []
}
],
"parameters": [
{
"name": "fragment_id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"description": "Purge a fragment by id (auth required, RBAC-gated).\n\n**Preconditions:**\n* User privilege required.\n"
}
},
"/v2/app-config-allow-list/": {
"post": {
"operationId": "v2/app-config-allow-list.admin_create",
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""Request DTOs for app_config_fragment v2."""

from __future__ import annotations

from typing import Any, Self
from uuid import UUID

from pydantic import Field, model_validator

from ai.backend.common.api_handlers import BaseRequestModel
from ai.backend.common.data.app_config.types import AppConfigScopeType

__all__ = (
"AppConfigFragmentUpdateItem",
"BulkPurgeAppConfigFragmentInput",
"BulkUpdateAppConfigFragmentInput",
"CreateAppConfigFragmentInput",
"UpdateAppConfigFragmentInput",
)


class CreateAppConfigFragmentInput(BaseRequestModel):
"""Input for creating a new app config fragment at a given scope."""

config_name: str = Field(
min_length=1,
max_length=128,
description="Registered config name.",
)
scope_type: AppConfigScopeType = Field(
description="Scope the fragment is written at (public | domain | user)."
)
scope_id: UUID | None = Field(
default=None,
description="Scope identifier: the domain id (domain scope) or the user id (user scope). "
"Null for public scope, which has no owner.",
)
config: dict[str, Any] = Field(description="The fragment's JSON config document.")

@model_validator(mode="after")
def _check_scope_id(self) -> Self:
if self.scope_type is AppConfigScopeType.PUBLIC:
if self.scope_id is not None:
raise ValueError("scope_id must be null for public scope.")
elif self.scope_id is None:
raise ValueError("scope_id is required for domain and user scopes.")
return self


class UpdateAppConfigFragmentInput(BaseRequestModel):
"""Input for updating one app config fragment's config document.

The target fragment is identified by the request path, not by this body.
"""

config: dict[str, Any] = Field(description="The replacement JSON config document.")


class AppConfigFragmentUpdateItem(BaseRequestModel):
"""One item of a bulk update, carrying its own target id.

Bulk requests address many fragments in a single call, so the id belongs in the body
here β€” unlike the single-fragment :class:`UpdateAppConfigFragmentInput`.
"""

id: UUID = Field(description="App config fragment id to update.")
config: dict[str, Any] = Field(description="The replacement JSON config document.")


class BulkUpdateAppConfigFragmentInput(BaseRequestModel):
"""Input for updating many fragments' config documents (per-item partial success)."""

items: list[AppConfigFragmentUpdateItem] = Field(
min_length=1, description="Fragments to update, each identified by its id."
)


class BulkPurgeAppConfigFragmentInput(BaseRequestModel):
"""Input for purging many fragments (per-item partial success)."""

ids: list[UUID] = Field(min_length=1, description="Fragment ids to purge.")
Loading
Loading