using System.Text.RegularExpressions; using k8s; using LazyBear.MCP.Services.ToolRegistry; using Microsoft.Extensions.Logging; using ModelContextProtocol.Server; namespace LazyBear.MCP.Services.Kubernetes; [McpServerToolType] public abstract class KubernetesToolsBase( K8sClientProvider clientProvider, IConfiguration configuration, ToolRegistryService registry, ILogger? logger = null) { protected readonly IKubernetes? _client = clientProvider.Client; protected readonly string? _clientInitializationError = clientProvider.InitializationError; protected readonly string _defaultNamespace = configuration["Kubernetes:DefaultNamespace"] ?? "default"; protected readonly ILogger? _logger = logger; protected readonly ToolRegistryService _registry = registry; protected const string K8sModuleName = "Kubernetes"; protected bool TryCheckEnabled(string toolName, out string error) { if (!_registry.IsToolEnabled(K8sModuleName, toolName)) { error = $"Инструмент '{toolName}' модуля Kubernetes отключён в TUI."; return false; } error = string.Empty; return true; } protected static readonly Regex NamespaceRegex = new(@"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", RegexOptions.Compiled, TimeSpan.FromSeconds(1)); protected static readonly Regex ResourceNameRegex = new(@"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", RegexOptions.Compiled, TimeSpan.FromSeconds(1)); protected string ResolveNamespace(string? @namespace) { return string.IsNullOrWhiteSpace(@namespace) ? _defaultNamespace : @namespace; } protected void ValidateNamespace(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException($"Namespace не может быть пустым", parameterName); } if (value.Length > 63) { throw new ArgumentException($"Namespace не может быть длиннее 63 символов", parameterName); } if (!NamespaceRegex.IsMatch(value)) { throw new ArgumentException($"Невалидный формат namespace: '{value}'. Только lowercase alphanumetic, -, не может начинаться/заканчиваться на -", parameterName); } } protected void ValidateResourceName(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException($"Resource name не может быть пустым", parameterName); } if (value.Length > 253) { throw new ArgumentException($"Resource name не может быть длиннее 253 символов", parameterName); } if (!ResourceNameRegex.IsMatch(value)) { throw new ArgumentException($"Невалидный формат: '{value}'. Только lowercase alphanumetic, -, не может начинаться/заканчиваться на -", parameterName); } } protected bool TryGetClient(out IKubernetes client, out string error) { if (_client is null) { client = null!; var details = string.IsNullOrWhiteSpace(_clientInitializationError) ? string.Empty : $" Детали: {_clientInitializationError}"; error = "Kubernetes клиент не инициализирован. Проверьте Kubernetes:KubeconfigPath или in-cluster окружение (KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT)." + details; return false; } client = _client; error = string.Empty; return true; } protected string FormatError(string toolName, string @namespace, Exception exception, string? resourceName = null) { _logger?.LogError(exception, "Ошибка Kubernetes в tool '{ToolName}' (namespace='{Namespace}'{ResourcePart}): {ExceptionMessage}", toolName, @namespace, string.IsNullOrWhiteSpace(resourceName) ? string.Empty : $", resource='{resourceName}'", exception.Message); var resourcePart = string.IsNullOrWhiteSpace(resourceName) ? string.Empty : $", resource='{resourceName}'"; if (exception is global::k8s.Autorest.HttpOperationException httpException) { var statusCode = httpException.Response?.StatusCode; var reason = httpException.Response?.ReasonPhrase; var body = string.IsNullOrWhiteSpace(httpException.Response?.Content) ? "-" : httpException.Response!.Content; return $"Ошибка Kubernetes в tool '{toolName}' (namespace='{@namespace}'{resourcePart}): status={(int?)statusCode ?? 0} {reason}, details={body}"; } return $"Ошибка Kubernetes в tool '{toolName}' (namespace='{@namespace}'{resourcePart}): {exception.GetType().Name}: {exception.Message}"; } protected string FormatException(string toolName, Exception exception, string? resource = null) { _logger?.LogError(exception, "Ошибка exception в tool '{ToolName}'{ResourcePart}: {ExceptionMessage}", toolName, string.IsNullOrWhiteSpace(resource) ? string.Empty : $", resource='{resource}'", exception.Message); var resourcePart = string.IsNullOrWhiteSpace(resource) ? string.Empty : $", resource='{resource}'"; return $"Ошибка в tool '{toolName}'{resourcePart}: {exception.GetType().Name}: {exception.Message}"; } }