Files
Shahovalov MIkhail 879becadfe feat: внедрение RazorConsole TUI с runtime-управлением MCP-инструментами
- Добавлен RazorConsole.Core для интерактивного TUI-дашборда
- ToolRegistryService: живое включение/отключение модулей и отдельных методов
- InMemoryLogSink: кольцевой буфер логов с фильтрацией по модулю
- TUI: 3 таба (Overview, Logs, Settings)
- IToolModule: generic-интерфейс для легкого добавления новых MCP-модулей
- Guard-проверка TryCheckEnabled() во всех существующих инструментах
2026-04-13 17:31:28 +03:00

148 lines
5.1 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.ComponentModel;
using k8s;
using k8s.Autorest;
using LazyBear.MCP.Services.ToolRegistry;
using ModelContextProtocol.Server;
namespace LazyBear.MCP.Services.Kubernetes;
[McpServerToolType]
public sealed class K8sPodsTools(
K8sClientProvider clientProvider,
IConfiguration configuration,
ToolRegistryService registry,
ILogger<K8sPodsTools>? logger = null)
: KubernetesToolsBase(clientProvider, configuration, registry, logger)
{
private const int MaxTailLines = 10;
private const int MinTailLines = 10;
[McpServerTool, Description("Список подов в namespace")]
public async Task<string> ListPods(
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("ListPods", out var enabledError)) return enabledError;
ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace));
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var pods = await client.CoreV1.ListNamespacedPodAsync(ns, cancellationToken: cancellationToken);
if (pods.Items.Count == 0)
{
return $"В namespace '{ns}' поды не найдены.";
}
var lines = pods.Items.Select(pod =>
{
var podName = pod.Metadata?.Name ?? "unknown";
var phase = pod.Status?.Phase ?? "Unknown";
var node = pod.Spec?.NodeName ?? "-";
var restarts = pod.Status?.ContainerStatuses?.Sum(s => s.RestartCount) ?? 0;
return $"{podName}: phase={phase}, restarts={restarts}, node={node}";
});
return $"Поды namespace '{ns}':\n{string.Join('\n', lines)}";
}
catch (Exception ex)
{
return FormatError("list_pods", ns, ex);
}
}
[McpServerTool, Description("Статус pod по имени")]
public async Task<string> GetPodStatus(
[Description("Имя pod")] string name,
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("GetPodStatus", out var enabledError)) return enabledError;
ValidateResourceName(name, nameof(name));
ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace));
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var pod = await client.CoreV1.ReadNamespacedPodAsync(name, ns, cancellationToken: cancellationToken);
var phase = pod.Status?.Phase ?? "Unknown";
var hostIp = pod.Status?.HostIP ?? "-";
var podIp = pod.Status?.PodIP ?? "-";
var conditions = pod.Status?.Conditions?.Select(c => $"{c.Type}={c.Status}") ?? [];
return $"Pod '{name}' в namespace '{ns}': phase={phase}, hostIp={hostIp}, podIp={podIp}, conditions=[{string.Join(", ", conditions)}]";
}
catch (Exception ex)
{
return FormatError("get_pod_status", ns, ex, name);
}
}
[McpServerTool, Description("Логи pod")]
public async Task<string> GetPodLogs(
[Description("Имя pod")] string name,
[Description("Namespace Kubernetes")] string? @namespace = null,
[Description("Имя контейнера")]
string? container = null,
[Description("Количество строк с конца")]
int? tailLines = 100,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("GetPodLogs", out var enabledError)) return enabledError;
ValidateResourceName(name, nameof(name));
ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace));
if (tailLines < MinTailLines)
{
tailLines = MinTailLines;
}
if (tailLines > MaxTailLines)
{
tailLines = MaxTailLines;
}
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
await using var logStream = await client.CoreV1.ReadNamespacedPodLogAsync(
name,
ns,
container: container,
tailLines: (int?)tailLines,
cancellationToken: cancellationToken);
using var reader = new StreamReader(logStream);
var logs = await reader.ReadToEndAsync(cancellationToken);
if (string.IsNullOrWhiteSpace(logs))
{
return $"Логи pod '{name}' в namespace '{ns}' пустые.";
}
return logs;
}
catch (Exception ex)
{
return FormatError("get_pod_logs", ns, ex, name);
}
}
}