fix: sanitize template variables before YAML validation
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import re
|
||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -7,6 +8,11 @@ from pydantic import BaseModel, ConfigDict, field_validator, model_validator
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_template_vars(template: str) -> str:
|
||||||
|
"""Replace template variables like {{VAR}} with placeholders to avoid YAML parsing errors."""
|
||||||
|
return re.sub(r"\{\{[A-Za-z_][A-Za-z0-9_]*\}\}", "__PLACEHOLDER__", template)
|
||||||
|
|
||||||
from src.auth.dependencies import get_current_user_id, get_db_session
|
from src.auth.dependencies import get_current_user_id, get_db_session
|
||||||
from src.models.tool_type import ToolType
|
from src.models.tool_type import ToolType
|
||||||
from src.models.user import User
|
from src.models.user import User
|
||||||
@@ -65,8 +71,12 @@ class ToolTypeCreate(BaseModel):
|
|||||||
if v is None:
|
if v is None:
|
||||||
raise ValueError("compose_template is required when definition_type is 'compose'")
|
raise ValueError("compose_template is required when definition_type is 'compose'")
|
||||||
|
|
||||||
|
# Replace template variables with dummy values before YAML validation
|
||||||
|
# to avoid YAML parsing errors with {{VAR}} syntax
|
||||||
|
sanitized = _sanitize_template_vars(v)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(v)
|
parsed = yaml.safe_load(sanitized)
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
raise ValueError(f"Invalid YAML: {e}")
|
raise ValueError(f"Invalid YAML: {e}")
|
||||||
|
|
||||||
@@ -145,7 +155,8 @@ class ToolTypeCreate(BaseModel):
|
|||||||
# Validate that default_port is exposed in compose template (only if requires_port)
|
# Validate that default_port is exposed in compose template (only if requires_port)
|
||||||
if self.requires_port and self.definition_type == "compose" and self.compose_template:
|
if self.requires_port and self.definition_type == "compose" and self.compose_template:
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(self.compose_template)
|
sanitized = _sanitize_template_vars(self.compose_template)
|
||||||
|
parsed = yaml.safe_load(sanitized)
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -215,8 +226,11 @@ class ToolTypeUpdate(BaseModel):
|
|||||||
if definition_type and definition_type != "compose":
|
if definition_type and definition_type != "compose":
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
# Replace template variables with dummy values before YAML validation
|
||||||
|
sanitized = _sanitize_template_vars(v)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(v)
|
parsed = yaml.safe_load(sanitized)
|
||||||
except yaml.YAMLError as e:
|
except yaml.YAMLError as e:
|
||||||
raise ValueError(f"Invalid YAML: {e}")
|
raise ValueError(f"Invalid YAML: {e}")
|
||||||
|
|
||||||
@@ -427,7 +441,8 @@ async def update_tool_type(
|
|||||||
template = update_data.get("compose_template", tool_type.compose_template)
|
template = update_data.get("compose_template", tool_type.compose_template)
|
||||||
if template:
|
if template:
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(template)
|
sanitized = _sanitize_template_vars(template)
|
||||||
|
parsed = yaml.safe_load(sanitized)
|
||||||
except yaml.YAMLError:
|
except yaml.YAMLError:
|
||||||
parsed = None
|
parsed = None
|
||||||
|
|
||||||
@@ -518,7 +533,8 @@ async def validate_tool_type_template(
|
|||||||
errors.append("Compose template is required")
|
errors.append("Compose template is required")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(data.compose_template)
|
sanitized = _sanitize_template_vars(data.compose_template)
|
||||||
|
parsed = yaml.safe_load(sanitized)
|
||||||
if not isinstance(parsed, dict):
|
if not isinstance(parsed, dict):
|
||||||
errors.append("Compose template must be a YAML mapping")
|
errors.append("Compose template must be a YAML mapping")
|
||||||
elif "services" not in parsed:
|
elif "services" not in parsed:
|
||||||
@@ -575,7 +591,8 @@ async def validate_tool_type(
|
|||||||
errors.append("Compose template is empty")
|
errors.append("Compose template is empty")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
parsed = yaml.safe_load(tool_type.compose_template)
|
sanitized = _sanitize_template_vars(tool_type.compose_template)
|
||||||
|
parsed = yaml.safe_load(sanitized)
|
||||||
if not isinstance(parsed, dict):
|
if not isinstance(parsed, dict):
|
||||||
errors.append("Compose template must be a YAML mapping")
|
errors.append("Compose template must be a YAML mapping")
|
||||||
elif "services" not in parsed:
|
elif "services" not in parsed:
|
||||||
|
|||||||
Reference in New Issue
Block a user