Files
LazyBearWorks/LazyBear.MCP/Services/Kubernetes/K8sConfigTools.cs

206 lines
7.2 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 k8s;
using k8s.Autorest;
using ModelContextProtocol.Server;
namespace LazyBear.MCP.Services.Kubernetes;
[McpServerToolType]
public sealed class K8sConfigTools(K8sClientProvider clientProvider, IConfiguration configuration)
{
private readonly IKubernetes? _client = clientProvider.Client;
private readonly string? _clientInitializationError = clientProvider.InitializationError;
private readonly string _defaultNamespace = configuration["Kubernetes:DefaultNamespace"] ?? "default";
[McpServerTool, Description("Список ConfigMap в namespace")]
public async Task<string> ListConfigMaps(
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var configMaps = await client.CoreV1.ListNamespacedConfigMapAsync(ns, cancellationToken: cancellationToken);
if (configMaps.Items.Count == 0)
{
return $"В namespace '{ns}' configMap не найдены.";
}
var lines = configMaps.Items.Select(cm =>
{
var name = cm.Metadata?.Name ?? "unknown";
var keyCount = cm.Data?.Count ?? 0;
return $"{name}: keys={keyCount}";
});
return $"ConfigMap namespace '{ns}':\n{string.Join('\n', lines)}";
}
catch (Exception ex)
{
return FormatError("list_config_maps", ns, ex);
}
}
[McpServerTool, Description("Получить данные ConfigMap")]
public async Task<string> GetConfigMapData(
[Description("Имя ConfigMap")] string name,
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var configMap = await client.CoreV1.ReadNamespacedConfigMapAsync(name, ns, cancellationToken: cancellationToken);
if (configMap.Data is null || configMap.Data.Count == 0)
{
return $"ConfigMap '{name}' в namespace '{ns}' не содержит данных.";
}
var lines = configMap.Data.Select(item => $"{item.Key}={item.Value}");
return $"Данные ConfigMap '{name}' namespace '{ns}':\n{string.Join('\n', lines)}";
}
catch (Exception ex)
{
return FormatError("get_config_map_data", ns, ex, name);
}
}
[McpServerTool, Description("Список Secret в namespace (без значений)")]
public async Task<string> ListSecrets(
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var secrets = await client.CoreV1.ListNamespacedSecretAsync(ns, cancellationToken: cancellationToken);
if (secrets.Items.Count == 0)
{
return $"В namespace '{ns}' secret не найдены.";
}
var lines = secrets.Items.Select(secret =>
{
var name = secret.Metadata?.Name ?? "unknown";
var type = secret.Type ?? "Opaque";
return $"{name}: type={type}";
});
return $"Secret namespace '{ns}':\n{string.Join('\n', lines)}";
}
catch (Exception ex)
{
return FormatError("list_secrets", ns, ex);
}
}
[McpServerTool, Description("Ключи Secret без значений")]
public async Task<string> GetSecretKeys(
[Description("Имя Secret")] string name,
[Description("Namespace Kubernetes")] string? @namespace = null,
CancellationToken cancellationToken = default)
{
var ns = ResolveNamespace(@namespace);
if (!TryGetClient(out var client, out var clientError))
{
return clientError;
}
try
{
var secret = await client.CoreV1.ReadNamespacedSecretAsync(name, ns, cancellationToken: cancellationToken);
var keys = new HashSet<string>(StringComparer.Ordinal);
if (secret.Data is not null)
{
foreach (var key in secret.Data.Keys)
{
keys.Add(key);
}
}
if (secret.StringData is not null)
{
foreach (var key in secret.StringData.Keys)
{
keys.Add(key);
}
}
if (keys.Count == 0)
{
return $"Secret '{name}' в namespace '{ns}' не содержит ключей.";
}
return $"Ключи Secret '{name}' namespace '{ns}': {string.Join(", ", keys.OrderBy(k => k))}";
}
catch (Exception ex)
{
return FormatError("get_secret_keys", ns, ex, name);
}
}
private string ResolveNamespace(string? @namespace)
{
return string.IsNullOrWhiteSpace(@namespace) ? _defaultNamespace : @namespace;
}
private static string BuildClientInitializationError()
{
return "Kubernetes клиент не инициализирован. Проверьте Kubernetes:KubeconfigPath или in-cluster окружение (KUBERNETES_SERVICE_HOST/KUBERNETES_SERVICE_PORT).";
}
private bool TryGetClient(out IKubernetes client, out string error)
{
if (_client is null)
{
client = null!;
var details = string.IsNullOrWhiteSpace(_clientInitializationError)
? string.Empty
: $" Детали: {_clientInitializationError}";
error = BuildClientInitializationError() + details;
return false;
}
client = _client;
error = string.Empty;
return true;
}
private static string FormatError(string toolName, string @namespace, Exception exception, string? resourceName = null)
{
var resourcePart = string.IsNullOrWhiteSpace(resourceName) ? string.Empty : $", resource='{resourceName}'";
if (exception is 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}";
}
}