165 lines
5.7 KiB
C#
165 lines
5.7 KiB
C#
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, ILogger<K8sConfigTools>? logger = null) : KubernetesToolsBase(clientProvider, configuration, logger)
|
||
{
|
||
private const int MaxSecretKeyLimit = 100;
|
||
|
||
[McpServerTool, Description("Список ConfigMap в namespace")]
|
||
public async Task<string> ListConfigMaps(
|
||
[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 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)
|
||
{
|
||
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 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)
|
||
{
|
||
ValidateNamespace(@namespace ?? _defaultNamespace, nameof(@namespace));
|
||
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)
|
||
{
|
||
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 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);
|
||
}
|
||
}
|
||
}
|