167 lines
6.2 KiB
C#
167 lines
6.2 KiB
C#
using System.ComponentModel;
|
||
using System.Text.Json;
|
||
using k8s;
|
||
using k8s.Autorest;
|
||
using k8s.Models;
|
||
using ModelContextProtocol.Server;
|
||
|
||
namespace LazyBear.MCP.Services.Kubernetes;
|
||
|
||
[McpServerToolType]
|
||
public sealed class K8sDeploymentTools(K8sClientProvider clientProvider, IConfiguration configuration, ILogger<K8sDeploymentTools>? logger = null) : KubernetesToolsBase(clientProvider, configuration, 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)
|
||
{
|
||
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)
|
||
{
|
||
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)
|
||
{
|
||
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)
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
}
|