- Добавлен RazorConsole.Core для интерактивного TUI-дашборда - ToolRegistryService: живое включение/отключение модулей и отдельных методов - InMemoryLogSink: кольцевой буфер логов с фильтрацией по модулю - TUI: 3 таба (Overview, Logs, Settings) - IToolModule: generic-интерфейс для легкого добавления новых MCP-модулей - Guard-проверка TryCheckEnabled() во всех существующих инструментах
148 lines
5.1 KiB
C#
148 lines
5.1 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|