using System.Text;
using EntKube.Clusters.Domain;
using k8s;
using k8s.Models;
using Microsoft.Extensions.Logging;
namespace EntKube.Clusters.Features.AdoptCluster.Components.Istio;
///
/// Checks whether Istio service mesh with Gateway API support is installed.
/// The Terraform bootstrap installs three layers:
/// 1. Gateway API CRDs (standard + experimental)
/// 2. istio-base (Istio CRDs + namespace)
/// 3. istiod (control plane with PILOT_ENABLE_GATEWAY_API=true)
///
/// This check verifies all three layers are present and healthy.
///
public class IstioCheck : IAdoptionCheck
{
private readonly ILogger logger;
public string ComponentName => "istio";
public string DisplayName => "Istio Service Mesh + Gateway API";
public IstioCheck(ILogger logger)
{
this.logger = logger;
}
public async Task CheckAsync(KubernetesCluster cluster, CancellationToken ct = default)
{
Kubernetes client = BuildClient(cluster);
List details = new();
List missing = new();
// Check 1: Gateway API CRDs installed (gateways.gateway.networking.k8s.io)
bool gatewayApiCrd = await CrdExists(client, "gateways.gateway.networking.k8s.io", ct);
if (gatewayApiCrd)
{
details.Add("Gateway API CRDs present");
}
else
{
missing.Add("Gateway API CRDs not installed (gateways.gateway.networking.k8s.io)");
}
// Check 2: istio-system namespace with istiod pods running
List istiodPods = await FindIstiodPods(client, ct);
if (istiodPods.Count > 0)
{
details.AddRange(istiodPods.Select(p => $"Pod running: {p}"));
}
else
{
missing.Add("No istiod pods found in istio-system namespace");
}
// Check 3: Istio CRDs (VirtualService or Gateway) — confirms istio-base installed
bool istioCrd = await CrdExists(client, "virtualservices.networking.istio.io", ct);
if (istioCrd)
{
details.Add("Istio CRDs present (networking.istio.io)");
}
else
{
missing.Add("Istio base CRDs not installed (networking.istio.io)");
}
// Determine status
if (istiodPods.Count == 0 && !istioCrd)
{
return new ComponentCheckResult(ComponentName, ComponentStatus.NotInstalled, details, missing);
}
// Discover configuration from running pods
string? version = await GetIstiodVersion(client, ct);
DiscoveredConfiguration config = new(
Version: version,
Namespace: "istio-system",
HelmReleaseName: "istiod",
Values: new Dictionary
{
["pilotReplicas"] = istiodPods.Count.ToString(),
["gatewayApiEnabled"] = gatewayApiCrd.ToString(),
["istioCrdsInstalled"] = istioCrd.ToString()
});
if (missing.Count > 0)
{
return new ComponentCheckResult(ComponentName, ComponentStatus.Degraded, details, missing, config);
}
return new ComponentCheckResult(ComponentName, ComponentStatus.Installed, details, missing, config);
}
private async Task GetIstiodVersion(Kubernetes client, CancellationToken ct)
{
try
{
V1PodList podList = await client.CoreV1.ListNamespacedPodAsync(
"istio-system", labelSelector: "app=istiod", cancellationToken: ct);
foreach (V1Pod pod in podList.Items)
{
if (pod.Spec?.Containers?.Count > 0)
{
string? image = pod.Spec.Containers[0].Image;
if (image?.Contains(':') == true)
{
return image.Split(':').Last().TrimStart('v');
}
}
}
}
catch { }
return null;
}
private async Task> FindIstiodPods(Kubernetes client, CancellationToken ct)
{
List pods = new();
try
{
V1PodList podList = await client.CoreV1.ListNamespacedPodAsync(
"istio-system",
labelSelector: "app=istiod",
cancellationToken: ct);
foreach (V1Pod pod in podList.Items)
{
if (pod.Status?.Phase == "Running")
{
pods.Add(pod.Metadata.Name);
}
}
}
catch (k8s.Autorest.HttpOperationException ex) when (ex.Response.StatusCode == System.Net.HttpStatusCode.NotFound)
{
logger.LogDebug("istio-system namespace not found");
}
catch (Exception ex)
{
logger.LogWarning(ex, "Error checking for istiod pods");
}
return pods;
}
private async Task CrdExists(Kubernetes client, string crdName, CancellationToken ct)
{
try
{
await client.ApiextensionsV1.ReadCustomResourceDefinitionAsync(crdName, cancellationToken: ct);
return true;
}
catch
{
return false;
}
}
private static Kubernetes BuildClient(KubernetesCluster cluster)
{
byte[] kubeConfigBytes = Encoding.UTF8.GetBytes(cluster.KubeConfig);
using MemoryStream stream = new(kubeConfigBytes);
KubernetesClientConfiguration config = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream, cluster.ContextName);
return new Kubernetes(config);
}
}