Files
KubeDiagrams/webapp/backend/utils/validators.py
Philippe Merle a9f76de7d8 Cosmetic updates
2026-02-18 11:13:45 +01:00

183 lines
5.2 KiB
Python

"""Validation of user inputs."""
import re
from typing import Optional, Tuple
from constants import MANIFEST_RE, KIND_RE
class ValidationError(Exception):
"""Exception error validation."""
pass
class InputValidator:
"""Validator for user inputs."""
SUPPORTED_FORMATS = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'pdf', 'dot', 'dot_json']
# Valid Pattern for url
HELM_URL_PATTERN = re.compile(
r'^(https?://|oci://|file://|[a-zA-Z0-9\-_]+/[a-zA-Z0-9\-_]+)'
)
@classmethod
def validate_manifest(cls, content: str) -> Tuple[bool, Optional[str]]:
"""
Validate a Kubernetes manifest content.
Args:
content: Manifest content
Returns:
Tuple[bool, Optional[str]]: (is_valid, error_message)
"""
if not content or not content.strip():
return False, "Manifest content cannot be empty."
# Verify minimum length
if len(content.strip()) < 10:
return False, "Manifest content is too short."
# Verify it looks like a manifest K8s
if not cls.looks_like_manifest(content):
return False, "Content does not appear to be a valid Kubernetes manifest (missing apiVersion or kind)."
return True, None
@classmethod
def validate_helmfile(cls, content: str) -> Tuple[bool, Optional[str]]:
"""
Validate a Helmfile content.
Args:
content: Content of a Helmfile
Returns:
Tuple[bool, Optional[str]]: (is_valid, error_message)
"""
if not content or not content.strip():
return False, "Helmfile content cannot be empty."
if len(content.strip()) < 10:
return False, "Helmfile content is too short."
# Verify it looks like a Helmfile
if not cls.looks_like_helmfile(content):
return False, "Content does not appear to be a valid Helmfile (missing typical Helmfile keys)."
return True, None
@classmethod
def validate_helm_chart_url(cls, url: str) -> Tuple[bool, Optional[str]]:
"""
Validate a Helm chart URL.
Args:
url: URL of the Helm chart
Returns:
Tuple[bool, Optional[str]]: (est_valide, message_erreur)
"""
if not url or not url.strip():
return False, "Chart URL cannot be empty."
url = url.strip()
# Verify the helm url pattern
if not cls.HELM_URL_PATTERN.match(url):
return False, "Invalid Helm chart URL format. Must start with http://, https://, oci://, file://, or be a chart reference."
return True, None
@classmethod
def validate_output_format(cls, format_str: str) -> Tuple[bool, Optional[str]]:
"""
Validate output format.
Args:
format_str: output format
Returns:
Tuple[bool, Optional[str]]: (is_valid, error_message)
"""
if not format_str:
return False, "Output format cannot be empty."
format_str = format_str.lower().strip()
if format_str not in cls.SUPPORTED_FORMATS:
return False, f"Unsupported output format '{format_str}'. Supported formats: {', '.join(cls.SUPPORTED_FORMATS)}"
return True, None
@classmethod
def validate_extra_args(cls, args: str) -> Tuple[bool, Optional[str]]:
"""
Validate extra args.
Args:
args: extra_args
Returns:
Tuple[bool, Optional[str]]: (is_valid, error_message)
"""
if not args or not args.strip():
return True, None # Les args vides sont valides
dangerous_chars = [';', '&', '|', '`', '$', '(', ')']
for char in dangerous_chars:
if char in args:
return False, f"Extra args contain dangerous character '{char}'"
return True, None
@staticmethod
def looks_like_manifest(text: str) -> bool:
"""
Heuristic to detect a Kubernetes manifest.
Args:
text: content that will be checked
Returns:
bool: True if it looks like a manifest
"""
if not text:
return False
t = text.strip()
return bool(MANIFEST_RE.search(t) and KIND_RE.search(t))
@staticmethod
def looks_like_helmfile(text: str) -> bool:
"""
Heuristic to detect a HelmFile.
Args:
text: Content that will be checked.
Returns:
bool: True if it looks like a Helmfile
"""
if not text:
return False
t = text.lower()
helmfile_keys = ["\nreleases:", "\nrepositories:", "\nhelmdefaults:", "\nenvironments:", "\ntemplates:"]
return any(key in t for key in helmfile_keys)
# TODO: Not used yet
@classmethod
def sanitize_filename(cls, filename: str) -> str:
"""
Clean a filename to make it safe.
Args:
filename: Filename to clean
Returns:
str: Filename cleaned
"""
safe_filename = re.sub(r'[^a-zA-Z0-9._-]', '_', filename)
# Length limit
if len(safe_filename) > 255:
safe_filename = safe_filename[:255]
return safe_filename