Files
LazyBearWorks/LazyBear.MCP/Services/Kubernetes/K8sDeploymentTools.cs
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

181 lines
6.7 KiB
C#
Raw 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 System.Text.Json;
using k8s;
using k8s.Autorest;
using k8s.Models;
using LazyBear.MCP.Services.ToolRegistry;
using ModelContextProtocol.Server;
namespace LazyBear.MCP.Services.Kubernetes;
[McpServerToolType]
public sealed class K8sDeploymentTools(
K8sClientProvider clientProvider,
IConfiguration configuration,
ToolRegistryService registry,
ILogger<K8sDeploymentTools>? logger = null)
: KubernetesToolsBase(clientProvider, configuration, registry, logger)
{
private const int MinReplicas = 0;
private const int MaxReplicas = 100;
[McpServerTool, Description("Список deployment в namespace")]
public async Task<string> ListDeployments(
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("ListDeployments", 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 deployments = await client.AppsV1.ListNamespacedDeploymentAsync(ns, cancellationToken: cancellationToken);
if (deployments.Items.Count == 0)
{
return $"В namespace '{ns}' deployment не найдены.";
}
var lines = deployments.Items.Select(dep =>
{
var name = dep.Metadata?.Name ?? "unknown";
var desired = dep.Spec?.Replicas ?? 0;
var ready = dep.Status?.ReadyReplicas ?? 0;
var available = dep.Status?.AvailableReplicas ?? 0;
return $"{name}: desired={desired}, ready={ready}, available={available}";
});
return $"Deployment namespace '{ns}':\n{string.Join('\n', lines)}";
}
catch (Exception ex)
{
return FormatError("list_deployments", ns, ex);
}
}
[McpServerTool, Description("Масштабировать deployment")]
public async Task<string> ScaleDeployment(
[Description("Имя deployment")] string name,
[Description("Новое количество реплик")] int replicas,
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("ScaleDeployment", out var enabledError)) return enabledError;
ValidateResourceName(name, nameof(name));
ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace));
if (replicas < MinReplicas || replicas > MaxReplicas)
{
return $"Invalid replicas value: {replicas}. Must be between {MinReplicas} and {MaxReplicas}.";
}
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var deployment = await client.AppsV1.ReadNamespacedDeploymentAsync(name, ns, cancellationToken: cancellationToken);
deployment.Spec ??= new V1DeploymentSpec();
deployment.Spec.Replicas = replicas;
await client.AppsV1.ReplaceNamespacedDeploymentAsync(deployment, name, ns, cancellationToken: cancellationToken);
return $"Deployment '{name}' в namespace '{ns}' масштабирован до {replicas} реплик.";
}
catch (Exception ex)
{
return FormatError("scale_deployment", ns, ex, name);
}
}
[McpServerTool, Description("Статус rollout deployment")]
public async Task<string> GetRolloutStatus(
[Description("Имя deployment")] string name,
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("GetRolloutStatus", 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 deployment = await client.AppsV1.ReadNamespacedDeploymentStatusAsync(name, ns, cancellationToken: cancellationToken);
var desired = deployment.Spec?.Replicas ?? 0;
var updated = deployment.Status?.UpdatedReplicas ?? 0;
var ready = deployment.Status?.ReadyReplicas ?? 0;
var available = deployment.Status?.AvailableReplicas ?? 0;
return $"Rollout '{name}' в namespace '{ns}': desired={desired}, updated={updated}, ready={ready}, available={available}";
}
catch (Exception ex)
{
return FormatError("get_rollout_status", ns, ex, name);
}
}
[McpServerTool, Description("Перезапустить deployment (rolling restart)")]
public async Task<string> RestartDeployment(
[Description("Имя deployment")] string name,
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
if (!TryCheckEnabled("RestartDeployment", 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 patchJson = JsonSerializer.Serialize(new
{
spec = new
{
template = new
{
metadata = new
{
annotations = new Dictionary<string, string>
{
["kubectl.kubernetes.io/restartedAt"] = DateTimeOffset.UtcNow.ToString("O")
}
}
}
}
});
var patch = new V1Patch(patchJson, V1Patch.PatchType.StrategicMergePatch);
await client.AppsV1.PatchNamespacedDeploymentAsync(patch, name, ns, cancellationToken: cancellationToken);
return $"Deployment '{name}' в namespace '{ns}' отправлен на rolling restart.";
}
catch (Exception ex)
{
return FormatError("restart_deployment", ns, ex, name);
}
}
}