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); } }