dify/api/controllers/console/app/ops_trace.py
Xiyuan Chen 86134ea99e
feat(rbac): agent RBAC/ACL scenes and resource-locator check bundles (#41768)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-09-07 04:39:43 +00:00

279 lines
11 KiB
Python

from typing import Any
from uuid import UUID
from flask_restx import Resource
from pydantic import BaseModel, Field
from controllers.common.rbac import PlainApp, RBACCheck
from controllers.common.schema import query_params_from_model, register_response_schema_models, register_schema_models
from controllers.console import console_ns
from controllers.console.app.error import (
AppNotFoundError,
InvalidTracingConfigError,
TracingConfigAlreadyExistsError,
TracingConfigNotFoundError,
TracingConfigProcessingError,
TracingConfigVerificationFailedError,
UnsupportedTracingProviderError,
)
from controllers.console.flask_admission import console_account_admission
from controllers.console.wraps import (
RBACPermission,
model_validate,
)
from extensions.ext_application_services import application_services
from fields.base import ResponseModel
from libs.helper import dump_response
from machinery.context import RequestContext
from models.account import TenantAccountRole
from services.app_tracing_config_service import (
AppTracingConfigAlreadyExistsError,
AppTracingConfigAppNotFoundError,
AppTracingConfigInvalidConfigurationError,
AppTracingConfigInvalidProviderError,
AppTracingConfigNotFoundError,
AppTracingConfigProcessingError,
AppTracingConfigRecord,
AppTracingConfigVerificationFailedError,
)
_APP_TRACING_CONFIG_EDIT_ROLES = frozenset(
{
TenantAccountRole.OWNER,
TenantAccountRole.ADMIN,
TenantAccountRole.EDITOR,
}
)
class TraceProviderQuery(BaseModel):
tracing_provider: str = Field(..., description="Tracing provider name")
class TraceConfigPayload(BaseModel):
tracing_provider: str = Field(..., description="Tracing provider name")
tracing_config: dict[str, Any] = Field(
...,
description="Tracing configuration data",
)
class TraceAppConfigResponse(ResponseModel):
result: str | None = None
error: str | None = None
has_not_configured: bool | None = None
id: str | None = None
app_id: str | None = None
tracing_provider: str | None = None
tracing_config: dict[str, Any] | None = Field(default=None)
is_active: bool | None = None
created_at: str | None = None
updated_at: str | None = None
@classmethod
def from_record(cls, record: AppTracingConfigRecord) -> "TraceAppConfigResponse":
return cls(
id=record.id,
app_id=record.app_id,
tracing_provider=record.tracing_provider,
tracing_config=record.tracing_config,
is_active=record.is_active,
created_at=str(record.created_at),
updated_at=str(record.updated_at),
)
register_schema_models(console_ns, TraceProviderQuery, TraceConfigPayload)
register_response_schema_models(console_ns, TraceAppConfigResponse)
@console_ns.route("/apps/<uuid:app_id>/trace-config")
class TraceAppConfigApi(Resource):
"""
Manage trace app configurations
"""
@console_ns.doc("get_trace_app_config")
@console_ns.doc(description="Get tracing configuration for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(TraceProviderQuery))
@console_ns.response(
200,
"Tracing configuration retrieved successfully",
console_ns.models[TraceAppConfigResponse.__name__],
)
@console_ns.response(400, "Invalid request parameters or unsupported tracing provider")
@console_ns.response(404, "Application not found")
@console_ns.response(500, "Tracing configuration processing failed")
@console_account_admission(
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
)
@model_validate(TraceProviderQuery)
def get(
self,
req_data: TraceProviderQuery,
request_context: RequestContext,
app_id: UUID,
):
try:
trace_config = application_services().app_tracing_configs.get(
context=request_context,
app_id=str(app_id),
tracing_provider=req_data.tracing_provider,
)
except AppTracingConfigAppNotFoundError as error:
raise AppNotFoundError() from error
except AppTracingConfigInvalidProviderError as error:
raise UnsupportedTracingProviderError() from error
except AppTracingConfigProcessingError as error:
raise TracingConfigProcessingError() from error
except ValueError as error:
raise TracingConfigProcessingError() from error
if trace_config is None:
return dump_response(TraceAppConfigResponse, {"has_not_configured": True}, exclude_none=True)
return dump_response(
TraceAppConfigResponse,
TraceAppConfigResponse.from_record(trace_config),
exclude_none=True,
)
@console_ns.doc("create_trace_app_config")
@console_ns.doc(description="Create a new tracing configuration for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.expect(console_ns.models[TraceConfigPayload.__name__])
@console_ns.response(
201,
"Tracing configuration created successfully",
console_ns.models[TraceAppConfigResponse.__name__],
)
@console_ns.response(400, "Invalid request parameters or tracing configuration")
@console_ns.response(403, "Insufficient permissions")
@console_ns.response(404, "Application not found")
@console_ns.response(409, "Tracing configuration already exists")
@console_ns.response(500, "Tracing configuration processing failed")
@console_account_admission(
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
)
@model_validate(TraceConfigPayload)
def post(
self,
req_data: TraceConfigPayload,
request_context: RequestContext,
app_id: UUID,
):
"""Create a new trace app configuration"""
try:
application_services().app_tracing_configs.create(
context=request_context,
app_id=str(app_id),
tracing_provider=req_data.tracing_provider,
tracing_config=req_data.tracing_config,
)
except AppTracingConfigAppNotFoundError as error:
raise AppNotFoundError() from error
except AppTracingConfigAlreadyExistsError as error:
raise TracingConfigAlreadyExistsError() from error
except AppTracingConfigInvalidProviderError as error:
raise UnsupportedTracingProviderError() from error
except AppTracingConfigInvalidConfigurationError as error:
raise InvalidTracingConfigError() from error
except AppTracingConfigVerificationFailedError as error:
raise TracingConfigVerificationFailedError() from error
except AppTracingConfigProcessingError as error:
raise TracingConfigProcessingError() from error
except ValueError as error:
raise TracingConfigProcessingError() from error
return dump_response(TraceAppConfigResponse, {"result": "success"}, exclude_none=True), 201
@console_ns.doc("update_trace_app_config")
@console_ns.doc(description="Update an existing tracing configuration for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.expect(console_ns.models[TraceConfigPayload.__name__])
@console_ns.response(
200,
"Tracing configuration updated successfully",
console_ns.models[TraceAppConfigResponse.__name__],
)
@console_ns.response(400, "Invalid request parameters or tracing configuration")
@console_ns.response(403, "Insufficient permissions")
@console_ns.response(404, "Application or tracing configuration not found")
@console_ns.response(500, "Tracing configuration processing failed")
@console_account_admission(
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
)
@model_validate(TraceConfigPayload)
def patch(
self,
req_data: TraceConfigPayload,
request_context: RequestContext,
app_id: UUID,
):
"""Update an existing trace app configuration"""
try:
application_services().app_tracing_configs.update(
context=request_context,
app_id=str(app_id),
tracing_provider=req_data.tracing_provider,
tracing_config=req_data.tracing_config,
)
except AppTracingConfigAppNotFoundError as error:
raise AppNotFoundError() from error
except AppTracingConfigNotFoundError as error:
raise TracingConfigNotFoundError() from error
except AppTracingConfigInvalidProviderError as error:
raise UnsupportedTracingProviderError() from error
except AppTracingConfigInvalidConfigurationError as error:
raise InvalidTracingConfigError() from error
except AppTracingConfigVerificationFailedError as error:
raise TracingConfigVerificationFailedError() from error
except AppTracingConfigProcessingError as error:
raise TracingConfigProcessingError() from error
except ValueError as error:
raise TracingConfigProcessingError() from error
return dump_response(TraceAppConfigResponse, {"result": "success"}, exclude_none=True)
@console_ns.doc("delete_trace_app_config")
@console_ns.doc(description="Delete an existing tracing configuration for an application")
@console_ns.doc(params={"app_id": "Application ID"})
@console_ns.doc(params=query_params_from_model(TraceProviderQuery))
@console_ns.response(204, "Tracing configuration deleted successfully")
@console_ns.response(400, "Invalid request parameters or unsupported tracing provider")
@console_ns.response(403, "Insufficient permissions")
@console_ns.response(404, "Application or tracing configuration not found")
@console_ns.response(500, "Tracing configuration processing failed")
@console_account_admission(
allowed_roles=_APP_TRACING_CONFIG_EDIT_ROLES,
rbac_checks=[RBACCheck(RBACPermission.APP_TRACING_CONFIG, PlainApp())],
)
@model_validate(TraceProviderQuery)
def delete(
self,
req_data: TraceProviderQuery,
request_context: RequestContext,
app_id: UUID,
):
"""Delete an existing trace app configuration"""
try:
application_services().app_tracing_configs.delete(
context=request_context,
app_id=str(app_id),
tracing_provider=req_data.tracing_provider,
)
except AppTracingConfigAppNotFoundError as error:
raise AppNotFoundError() from error
except AppTracingConfigNotFoundError as error:
raise TracingConfigNotFoundError() from error
except AppTracingConfigInvalidProviderError as error:
raise UnsupportedTracingProviderError() from error
except AppTracingConfigProcessingError as error:
raise TracingConfigProcessingError() from error
except ValueError as error:
raise TracingConfigProcessingError() from error
return "", 204