From 776d953d8d3cf01fa5e12c12de1308e86b04650a Mon Sep 17 00:00:00 2001 From: Marcos Salamanca Date: Tue, 9 Jun 2026 13:51:23 -0500 Subject: [PATCH 1/4] address AKSService Bug --- .../src/Services/AksService.cs | 434 ++++++++++++++++-- 1 file changed, 404 insertions(+), 30 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs index 7207106f80..fa3ce04458 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Text.Json; using Azure.Mcp.Core.Services.Azure; using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Core.Services.Azure.Tenant; @@ -17,7 +18,7 @@ public sealed class AksService( ISubscriptionService subscriptionService, ITenantService tenantService, ICacheService cacheService, - ILogger logger) : BaseAzureService(tenantService), IAksService + ILogger logger) : BaseAzureResourceService(subscriptionService, tenantService), IAksService { private readonly ISubscriptionService _subscriptionService = subscriptionService ?? throw new ArgumentNullException(nameof(subscriptionService)); private readonly ICacheService _cacheService = cacheService ?? throw new ArgumentNullException(nameof(cacheService)); @@ -56,36 +57,16 @@ public async Task> GetClusters( return cachedClusters; } - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - - var clusters = new List(); - if (string.IsNullOrEmpty(resourceGroup)) - { + var result = await ExecuteResourceQueryAsync( + "Microsoft.ContainerService/managedClusters", + resourceGroup, + subscription, + retryPolicy, + ConvertToClusterFromJson, + tenant: tenant, + cancellationToken: cancellationToken); - await foreach (var cluster in subscriptionResource.GetContainerServiceManagedClustersAsync(cancellationToken)) - { - if (cluster?.Data != null) - { - clusters.Add(ConvertToClusterModel(cluster)); - } - } - } - else - { - var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken); - if (resourceGroupResource?.Value == null) - { - return clusters; - } - - await foreach (var cluster in resourceGroupResource.Value.GetContainerServiceManagedClusters().GetAllAsync(cancellationToken)) - { - if (cluster?.Data != null) - { - clusters.Add(ConvertToClusterModel(cluster)); - } - } - } + var clusters = result.Results; // Cache the results await _cacheService.SetAsync(CacheGroup, cacheKey, clusters, s_cacheDuration, cancellationToken); @@ -242,6 +223,399 @@ public async Task> GetNodePools( } } + private static Cluster ConvertToClusterFromJson(JsonElement item) + { + var cluster = new Cluster + { + Id = item.TryGetProperty("id", out var idEl) ? idEl.GetString() : null, + Name = item.TryGetProperty("name", out var nameEl) ? nameEl.GetString() : null, + SubscriptionId = item.TryGetProperty("subscriptionId", out var subEl) ? subEl.GetString() : null, + ResourceGroupName = item.TryGetProperty("resourceGroup", out var rgEl) ? rgEl.GetString() : null, + Location = item.TryGetProperty("location", out var locEl) ? locEl.GetString() : null, + Tags = ReadStringDictionaryFromProperty(item, "tags"), + }; + + if (item.TryGetProperty("sku", out var skuEl)) + { + cluster.SkuName = skuEl.TryGetProperty("name", out var skuNameEl) ? skuNameEl.GetString() : null; + cluster.SkuTier = skuEl.TryGetProperty("tier", out var skuTierEl) ? skuTierEl.GetString() : null; + } + + if (item.TryGetProperty("identity", out var identEl)) + { + cluster.IdentityType = identEl.TryGetProperty("type", out var itEl) ? itEl.GetString() : null; + cluster.Identity = new() + { + Type = cluster.IdentityType, + PrincipalId = identEl.TryGetProperty("principalId", out var pidEl) ? pidEl.GetString() : null, + TenantId = identEl.TryGetProperty("tenantId", out var tidEl) ? tidEl.GetString() : null, + }; + } + + if (!item.TryGetProperty("properties", out var props)) + return cluster; + + cluster.KubernetesVersion = props.TryGetProperty("kubernetesVersion", out var kvEl) ? kvEl.GetString() : null; + cluster.ProvisioningState = props.TryGetProperty("provisioningState", out var psEl) ? psEl.GetString() : null; + cluster.DnsPrefix = props.TryGetProperty("dnsPrefix", out var dpEl) ? dpEl.GetString() : null; + cluster.Fqdn = props.TryGetProperty("fqdn", out var fqdnEl) ? fqdnEl.GetString() : null; + cluster.NodeResourceGroup = props.TryGetProperty("nodeResourceGroup", out var nrgEl) ? nrgEl.GetString() : null; + cluster.SupportPlan = props.TryGetProperty("supportPlan", out var spEl) ? spEl.GetString() : null; + cluster.ResourceUid = props.TryGetProperty("resourceUID", out var ruidEl) ? ruidEl.GetString() : null; + cluster.EnableRbac = TryGetNullableBool(props, "enableRBAC"); + cluster.DisableLocalAccounts = TryGetNullableBool(props, "disableLocalAccounts"); + + if (props.TryGetProperty("maxAgentPools", out var mapEl) && mapEl.TryGetInt32(out var mapVal)) + cluster.MaxAgentPools = mapVal; + + if (props.TryGetProperty("powerState", out var psStateEl)) + cluster.PowerState = psStateEl.TryGetProperty("code", out var psCodeEl) ? psCodeEl.GetString() : null; + + if (props.TryGetProperty("networkProfile", out var npEl)) + { + cluster.NetworkPlugin = npEl.TryGetProperty("networkPlugin", out var npluginEl) ? npluginEl.GetString() : null; + cluster.NetworkPolicy = npEl.TryGetProperty("networkPolicy", out var npolicyEl) ? npolicyEl.GetString() : null; + cluster.ServiceCidr = npEl.TryGetProperty("serviceCidr", out var scCidrEl) ? scCidrEl.GetString() : null; + cluster.DnsServiceIP = npEl.TryGetProperty("dnsServiceIP", out var dnsEl) ? dnsEl.GetString() : null; + cluster.NetworkProfile = ReadNetworkProfile(npEl); + } + + if (props.TryGetProperty("oidcIssuerProfile", out var oidcEl)) + { + cluster.OidcIssuerProfile = new() + { + Enabled = TryGetNullableBool(oidcEl, "enabled"), + IssuerUrl = oidcEl.TryGetProperty("issuerURL", out var iuEl) ? iuEl.GetString() : null, + }; + } + + if (props.TryGetProperty("autoUpgradeProfile", out var aupEl)) + { + cluster.AutoUpgradeProfile = new() + { + UpgradeChannel = aupEl.TryGetProperty("upgradeChannel", out var ucEl) ? ucEl.GetString() : null, + NodeOSUpgradeChannel = aupEl.TryGetProperty("nodeOSUpgradeChannel", out var noscEl) ? noscEl.GetString() : null, + }; + } + + if (props.TryGetProperty("securityProfile", out var secEl)) + cluster.SecurityProfile = ReadSecurityProfile(secEl); + + if (props.TryGetProperty("storageProfile", out var stEl)) + cluster.StorageProfile = ReadStorageProfile(stEl); + + if (props.TryGetProperty("workloadAutoScalerProfile", out var waspEl)) + { + cluster.WorkloadAutoScalerProfile = new() + { + Keda = waspEl.TryGetProperty("keda", out var kedaEl) + ? new() { Enabled = TryGetNullableBool(kedaEl, "enabled") } + : null, + VerticalPodAutoscaler = waspEl.TryGetProperty("verticalPodAutoscaler", out var vpaEl) + ? new() { Enabled = TryGetNullableBool(vpaEl, "enabled") } + : null, + }; + } + + if (props.TryGetProperty("addonProfiles", out var addonEl) && addonEl.ValueKind == JsonValueKind.Object) + { + var addonProfiles = new Dictionary>(); + foreach (var addon in addonEl.EnumerateObject()) + { + var map = new Dictionary(); + if (addon.Value.TryGetProperty("config", out var configEl) && configEl.ValueKind == JsonValueKind.Object) + { + foreach (var cfg in configEl.EnumerateObject()) + map[$"config.{cfg.Name}"] = cfg.Value.GetString() ?? string.Empty; + } + if (addon.Value.TryGetProperty("identity", out var addonIdentEl)) + { + if (addonIdentEl.TryGetProperty("clientId", out var cliEl)) + map["identity.clientId"] = cliEl.GetString() ?? string.Empty; + if (addonIdentEl.TryGetProperty("objectId", out var objEl)) + map["identity.objectId"] = objEl.GetString() ?? string.Empty; + } + addonProfiles[addon.Name] = map; + } + cluster.AddonProfiles = addonProfiles; + } + + if (props.TryGetProperty("identityProfile", out var ipEl) && ipEl.ValueKind == JsonValueKind.Object) + { + var identityProfile = new Dictionary(); + foreach (var entry in ipEl.EnumerateObject()) + { + identityProfile[entry.Name] = new() + { + ResourceId = entry.Value.TryGetProperty("resourceId", out var rIdEl) ? rIdEl.GetString() : null, + ClientId = entry.Value.TryGetProperty("clientId", out var cIdEl) ? cIdEl.GetString() : null, + ObjectId = entry.Value.TryGetProperty("objectId", out var oIdEl) ? oIdEl.GetString() : null, + }; + } + cluster.IdentityProfile = identityProfile; + } + + if (props.TryGetProperty("agentPoolProfiles", out var appEl) && appEl.ValueKind == JsonValueKind.Array) + { + var pools = new List(); + foreach (var poolEl in appEl.EnumerateArray()) + pools.Add(ReadNodePoolFromJson(poolEl)); + cluster.AgentPoolProfiles = pools; + + var first = pools.Count > 0 ? pools[0] : null; + cluster.NodeCount = first?.Count; + cluster.NodeVmSize = first?.VmSize; + } + + return cluster; + } + + private static bool? TryGetNullableBool(JsonElement element, string propertyName) + { + if (!element.TryGetProperty(propertyName, out var el)) + return null; + return el.ValueKind switch + { + JsonValueKind.True => true, + JsonValueKind.False => false, + _ => null, + }; + } + + private static IDictionary? ReadStringDictionaryFromProperty(JsonElement element, string propertyName) + { + if (!element.TryGetProperty(propertyName, out var dictEl) || dictEl.ValueKind != JsonValueKind.Object) + return null; + var result = new Dictionary(); + foreach (var prop in dictEl.EnumerateObject()) + result[prop.Name] = prop.Value.GetString() ?? string.Empty; + return result.Count > 0 ? result : null; + } + + private static ClusterNetworkProfile ReadNetworkProfile(JsonElement npEl) + { + ClusterNetworkLoadBalancerProfile? lbProfile = null; + if (npEl.TryGetProperty("loadBalancerProfile", out var lbEl)) + { + int? managedOutboundIPCount = null; + if (lbEl.TryGetProperty("managedOutboundIPs", out var moEl) + && moEl.TryGetProperty("count", out var moCountEl) + && moCountEl.TryGetInt32(out var moCountVal)) + { + managedOutboundIPCount = moCountVal; + } + + List? effectiveOutboundIPs = null; + if (lbEl.TryGetProperty("effectiveOutboundIPs", out var eoEl) && eoEl.ValueKind == JsonValueKind.Array) + { + effectiveOutboundIPs = []; + foreach (var eoItem in eoEl.EnumerateArray()) + effectiveOutboundIPs.Add(new() { Id = eoItem.TryGetProperty("id", out var eoIdEl) ? eoIdEl.GetString() : null }); + } + + lbProfile = new() + { + ManagedOutboundIPCount = managedOutboundIPCount, + EffectiveOutboundIPs = effectiveOutboundIPs, + BackendPoolType = lbEl.TryGetProperty("backendPoolType", out var bptEl) ? bptEl.GetString() : null, + }; + } + + List? podCidrs = null; + if (npEl.TryGetProperty("podCidrs", out var podCidrsEl) && podCidrsEl.ValueKind == JsonValueKind.Array) + podCidrs = podCidrsEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); + + List? serviceCidrs = null; + if (npEl.TryGetProperty("serviceCidrs", out var svcCidrsEl) && svcCidrsEl.ValueKind == JsonValueKind.Array) + serviceCidrs = svcCidrsEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); + + List? ipFamilies = null; + if (npEl.TryGetProperty("ipFamilies", out var ipFamsEl) && ipFamsEl.ValueKind == JsonValueKind.Array) + ipFamilies = ipFamsEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); + + return new() + { + NetworkPlugin = npEl.TryGetProperty("networkPlugin", out var npluginEl) ? npluginEl.GetString() : null, + NetworkPluginMode = npEl.TryGetProperty("networkPluginMode", out var npmodeEl) ? npmodeEl.GetString() : null, + NetworkPolicy = npEl.TryGetProperty("networkPolicy", out var npolicyEl) ? npolicyEl.GetString() : null, + NetworkDataplane = npEl.TryGetProperty("networkDataplane", out var ndpEl) ? ndpEl.GetString() : null, + LoadBalancerSku = npEl.TryGetProperty("loadBalancerSku", out var lbSkuEl) ? lbSkuEl.GetString() : null, + LoadBalancerProfile = lbProfile, + PodCidr = npEl.TryGetProperty("podCidr", out var pcCidrEl) ? pcCidrEl.GetString() : null, + ServiceCidr = npEl.TryGetProperty("serviceCidr", out var scEl) ? scEl.GetString() : null, + DnsServiceIP = npEl.TryGetProperty("dnsServiceIP", out var dnsEl2) ? dnsEl2.GetString() : null, + OutboundType = npEl.TryGetProperty("outboundType", out var otEl) ? otEl.GetString() : null, + PodCidrs = podCidrs, + ServiceCidrs = serviceCidrs, + IpFamilies = ipFamilies, + }; + } + + private static ClusterSecurityProfile ReadSecurityProfile(JsonElement secEl) + { + AzureKeyVaultKms? kms = null; + if (secEl.TryGetProperty("azureKeyVaultKms", out var kmsEl)) + { + kms = new() + { + Enabled = TryGetNullableBool(kmsEl, "enabled"), + KeyId = kmsEl.TryGetProperty("keyId", out var kidEl) ? kidEl.GetString() : null, + }; + } + + DefenderProfile? defender = null; + if (secEl.TryGetProperty("defender", out var defEl)) + { + DefenderSecurityMonitoring? monitoring = null; + if (defEl.TryGetProperty("securityMonitoring", out var smEl)) + monitoring = new() { Enabled = TryGetNullableBool(smEl, "enabled") }; + + defender = new() + { + LogAnalyticsWorkspaceResourceId = defEl.TryGetProperty("logAnalyticsWorkspaceResourceId", out var lawEl) ? lawEl.GetString() : null, + SecurityMonitoring = monitoring, + }; + } + + ImageCleanerProfile? imageCleaner = null; + if (secEl.TryGetProperty("imageCleaner", out var icEl)) + { + int? intervalHours = null; + if (icEl.TryGetProperty("intervalHours", out var ihEl) && ihEl.TryGetInt32(out var ihVal)) + intervalHours = ihVal; + imageCleaner = new() + { + Enabled = TryGetNullableBool(icEl, "enabled"), + IntervalHours = intervalHours, + }; + } + + WorkloadIdentityProfile? workloadIdentity = null; + if (secEl.TryGetProperty("workloadIdentity", out var wiEl)) + workloadIdentity = new() { Enabled = TryGetNullableBool(wiEl, "enabled") }; + + return new() + { + AzureKeyVaultKms = kms, + Defender = defender, + ImageCleaner = imageCleaner, + WorkloadIdentity = workloadIdentity, + }; + } + + private static ClusterStorageProfile ReadStorageProfile(JsonElement stEl) + { + return new() + { + BlobCSIDriver = stEl.TryGetProperty("blobCSIDriver", out var blobEl) ? new() { Enabled = TryGetNullableBool(blobEl, "enabled") } : null, + DiskCSIDriver = stEl.TryGetProperty("diskCSIDriver", out var diskEl) ? new() { Enabled = TryGetNullableBool(diskEl, "enabled") } : null, + FileCSIDriver = stEl.TryGetProperty("fileCSIDriver", out var fileEl) ? new() { Enabled = TryGetNullableBool(fileEl, "enabled") } : null, + SnapshotController = stEl.TryGetProperty("snapshotController", out var scEl) ? new() { Enabled = TryGetNullableBool(scEl, "enabled") } : null, + }; + } + + private static NodePool ReadNodePoolFromJson(JsonElement poolEl) + { + NodePoolPowerState? powerState = null; + if (poolEl.TryGetProperty("powerState", out var psEl)) + powerState = new() { Code = psEl.TryGetProperty("code", out var psCodeEl) ? psCodeEl.GetString() : null }; + + NodePoolUpgradeSettings? upgradeSettings = null; + if (poolEl.TryGetProperty("upgradeSettings", out var usEl)) + { + upgradeSettings = new() + { + MaxSurge = usEl.TryGetProperty("maxSurge", out var msEl) ? msEl.GetString() : null, + MaxUnavailable = usEl.TryGetProperty("maxUnavailable", out var muEl) ? muEl.GetString() : null, + }; + } + + NodePoolNetworkProfile? networkProfile = null; + if (poolEl.TryGetProperty("networkProfile", out var npEl)) + { + List? allowedHostPorts = null; + if (npEl.TryGetProperty("allowedHostPorts", out var ahpEl) && ahpEl.ValueKind == JsonValueKind.Array) + { + allowedHostPorts = []; + foreach (var portEl in ahpEl.EnumerateArray()) + { + int? startPort = null, endPort = null; + if (portEl.TryGetProperty("portStart", out var spEl) && spEl.TryGetInt32(out var spVal)) startPort = spVal; + if (portEl.TryGetProperty("portEnd", out var epEl) && epEl.TryGetInt32(out var epVal)) endPort = epVal; + allowedHostPorts.Add(new() { StartPort = startPort, EndPort = endPort }); + } + } + + List? applicationSecurityGroups = null; + if (npEl.TryGetProperty("applicationSecurityGroups", out var asgEl) && asgEl.ValueKind == JsonValueKind.Array) + applicationSecurityGroups = asgEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); + + List? nodePublicIPTags = null; + if (npEl.TryGetProperty("nodePublicIPTags", out var ipTagsEl) && ipTagsEl.ValueKind == JsonValueKind.Array) + { + nodePublicIPTags = []; + foreach (var tagEl in ipTagsEl.EnumerateArray()) + { + nodePublicIPTags.Add(new() + { + IpTagType = tagEl.TryGetProperty("ipTagType", out var ittEl) ? ittEl.GetString() : null, + Tag = tagEl.TryGetProperty("tag", out var tagValEl) ? tagValEl.GetString() : null, + }); + } + } + + networkProfile = new() + { + AllowedHostPorts = allowedHostPorts, + ApplicationSecurityGroups = applicationSecurityGroups, + NodePublicIPTags = nodePublicIPTags, + }; + } + + IList? nodeTaints = null; + if (poolEl.TryGetProperty("nodeTaints", out var ntEl) && ntEl.ValueKind == JsonValueKind.Array) + nodeTaints = ntEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); + + return new() + { + Name = poolEl.TryGetProperty("name", out var poolNameEl) ? poolNameEl.GetString() : null, + Count = poolEl.TryGetProperty("count", out var countEl) && countEl.TryGetInt32(out var countVal) ? countVal : null, + VmSize = poolEl.TryGetProperty("vmSize", out var vmSizeEl) ? vmSizeEl.GetString() : null, + OsDiskSizeGB = poolEl.TryGetProperty("osDiskSizeGB", out var odsEl) && odsEl.TryGetInt32(out var odsVal) ? odsVal : null, + OsDiskType = poolEl.TryGetProperty("osDiskType", out var odtEl) ? odtEl.GetString() : null, + KubeletDiskType = poolEl.TryGetProperty("kubeletDiskType", out var kdtEl) ? kdtEl.GetString() : null, + MaxPods = poolEl.TryGetProperty("maxPods", out var mpEl) && mpEl.TryGetInt32(out var mpVal) ? mpVal : null, + Type = poolEl.TryGetProperty("type", out var typeEl) ? typeEl.GetString() : null, + MaxCount = poolEl.TryGetProperty("maxCount", out var maxCEl) && maxCEl.TryGetInt32(out var maxCVal) ? maxCVal : null, + MinCount = poolEl.TryGetProperty("minCount", out var minCEl) && minCEl.TryGetInt32(out var minCVal) ? minCVal : null, + EnableAutoScaling = TryGetNullableBool(poolEl, "enableAutoScaling"), + ScaleDownMode = poolEl.TryGetProperty("scaleDownMode", out var sdmEl) ? sdmEl.GetString() : null, + ProvisioningState = poolEl.TryGetProperty("provisioningState", out var ppEl) ? ppEl.GetString() : null, + PowerState = powerState, + Mode = poolEl.TryGetProperty("mode", out var poolModeEl) ? poolModeEl.GetString() : null, + OrchestratorVersion = poolEl.TryGetProperty("orchestratorVersion", out var ovEl) ? ovEl.GetString() : null, + CurrentOrchestratorVersion = poolEl.TryGetProperty("currentOrchestratorVersion", out var covEl) ? covEl.GetString() : null, + EnableNodePublicIP = TryGetNullableBool(poolEl, "enableNodePublicIP"), + ScaleSetPriority = poolEl.TryGetProperty("scaleSetPriority", out var sspEl) ? sspEl.GetString() : null, + ScaleSetEvictionPolicy = poolEl.TryGetProperty("scaleSetEvictionPolicy", out var ssepEl) ? ssepEl.GetString() : null, + NodeLabels = ReadStringDictionaryFromProperty(poolEl, "nodeLabels"), + NodeTaints = nodeTaints, + OsType = poolEl.TryGetProperty("osType", out var osTypeEl) ? osTypeEl.GetString() : null, + OsSKU = poolEl.TryGetProperty("osSKU", out var osSkuEl) ? osSkuEl.GetString() : null, + NodeImageVersion = poolEl.TryGetProperty("nodeImageVersion", out var nivEl) ? nivEl.GetString() : null, + Tags = ReadStringDictionaryFromProperty(poolEl, "tags"), + SpotMaxPrice = poolEl.TryGetProperty("spotMaxPrice", out var smpEl) && smpEl.TryGetDouble(out var smpVal) ? smpVal : null, + WorkloadRuntime = poolEl.TryGetProperty("workloadRuntime", out var wrEl) ? wrEl.GetString() : null, + EnableEncryptionAtHost = TryGetNullableBool(poolEl, "enableEncryptionAtHost"), + EnableUltraSSD = TryGetNullableBool(poolEl, "enableUltraSSD"), + EnableFIPS = TryGetNullableBool(poolEl, "enableFIPS"), + UpgradeSettings = upgradeSettings, + NetworkProfile = networkProfile, + PodSubnetId = poolEl.TryGetProperty("podSubnetID", out var psidEl) ? psidEl.GetString() : null, + VnetSubnetId = poolEl.TryGetProperty("vnetSubnetID", out var vsidEl) ? vsidEl.GetString() : null, + }; + } + private static Cluster ConvertToClusterModel(ContainerServiceManagedClusterResource clusterResource) { var data = clusterResource.Data; From 09c45aa911966072a00dab9c8e5aa793d84a9a87 Mon Sep 17 00:00:00 2001 From: Marcos Salamanca Date: Tue, 9 Jun 2026 15:55:24 -0500 Subject: [PATCH 2/4] condense refactor --- .../src/Services/AksService.cs | 194 +----------------- 1 file changed, 11 insertions(+), 183 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs index fa3ce04458..c4df2b967f 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs @@ -89,24 +89,22 @@ public async Task> GetClusters( return cachedCluster; } - var subscriptionResource = await _subscriptionService.GetSubscription(subscription, tenant, retryPolicy, cancellationToken); - var resourceGroupResource = await subscriptionResource.GetResourceGroupAsync(resourceGroup, cancellationToken); - - if (resourceGroupResource?.Value == null) - { - return []; - } - - var clusterResource = await resourceGroupResource.Value - .GetContainerServiceManagedClusters() - .GetAsync(clusterName, cancellationToken); + var cluster = await ExecuteSingleResourceQueryAsync( + "Microsoft.ContainerService/managedClusters", + resourceGroup: resourceGroup, + subscription: subscription, + retryPolicy: retryPolicy, + converter: ConvertToClusterFromJson, + additionalFilter: $"name =~ '{EscapeKqlString(clusterName!)}'" , + tenant: tenant, + cancellationToken: cancellationToken); - if (clusterResource?.Value?.Data == null) + if (cluster == null) { return []; } - var clusters = new List() { ConvertToClusterModel(clusterResource.Value) }; + var clusters = new List() { cluster }; // Cache the result await _cacheService.SetAsync(CacheGroup, cacheKey, clusters, s_cacheDuration, cancellationToken); @@ -616,176 +614,6 @@ private static NodePool ReadNodePoolFromJson(JsonElement poolEl) }; } - private static Cluster ConvertToClusterModel(ContainerServiceManagedClusterResource clusterResource) - { - var data = clusterResource.Data; - var agentPool = data.AgentPoolProfiles?.FirstOrDefault(); - - var cluster = new Cluster - { - Id = clusterResource.Id.ToString(), - Name = data.Name, - SubscriptionId = clusterResource.Id.SubscriptionId, - ResourceGroupName = clusterResource.Id.ResourceGroupName, - Location = data.Location.ToString(), - KubernetesVersion = data.KubernetesVersion, - ProvisioningState = data.ProvisioningState?.ToString(), - PowerState = data.PowerStateCode?.ToString(), - DnsPrefix = data.DnsPrefix, - Fqdn = data.Fqdn, - NodeCount = agentPool?.Count, - NodeVmSize = agentPool?.VmSize, - IdentityType = data.Identity?.ManagedServiceIdentityType.ToString(), - Identity = new() - { - Type = data.Identity?.ManagedServiceIdentityType.ToString(), - PrincipalId = data.Identity?.PrincipalId?.ToString(), - TenantId = data.Identity?.TenantId?.ToString() - }, - EnableRbac = data.EnableRbac, - NetworkPlugin = data.NetworkProfile?.NetworkPlugin?.ToString(), - NetworkPolicy = data.NetworkProfile?.NetworkPolicy?.ToString(), - ServiceCidr = data.NetworkProfile?.ServiceCidr, - DnsServiceIP = data.NetworkProfile?.DnsServiceIP?.ToString(), - SkuTier = data.Sku?.Tier?.ToString(), - SkuName = data.Sku?.Name?.ToString(), - NodeResourceGroup = data.NodeResourceGroup, - MaxAgentPools = data.MaxAgentPools, - SupportPlan = data.SupportPlan?.ToString(), - NetworkProfile = new() - { - NetworkPlugin = data.NetworkProfile?.NetworkPlugin?.ToString(), - NetworkPluginMode = data.NetworkProfile?.NetworkPluginMode?.ToString(), - NetworkPolicy = data.NetworkProfile?.NetworkPolicy?.ToString(), - NetworkDataplane = data.NetworkProfile?.NetworkDataplane?.ToString(), - LoadBalancerSku = data.NetworkProfile?.LoadBalancerSku?.ToString(), - LoadBalancerProfile = data.NetworkProfile?.LoadBalancerProfile is null ? null : new() - { - ManagedOutboundIPCount = data.NetworkProfile?.LoadBalancerProfile?.ManagedOutboundIPs?.Count, - EffectiveOutboundIPs = data.NetworkProfile?.LoadBalancerProfile?.EffectiveOutboundIPs?.Select(e => new EffectiveOutboundIPReference() { Id = e.Id?.ToString() }).ToList(), - BackendPoolType = data.NetworkProfile?.LoadBalancerProfile?.BackendPoolType?.ToString() - }, - PodCidr = data.NetworkProfile?.PodCidr, - ServiceCidr = data.NetworkProfile?.ServiceCidr, - DnsServiceIP = data.NetworkProfile?.DnsServiceIP?.ToString(), - OutboundType = data.NetworkProfile?.OutboundType?.ToString(), - PodCidrs = data.NetworkProfile?.PodCidrs?.ToList(), - ServiceCidrs = data.NetworkProfile?.ServiceCidrs?.ToList(), - IpFamilies = data.NetworkProfile?.IPFamilies?.Select(f => f.ToString()).ToList() - }, - WindowsProfile = data.WindowsProfile is null ? null : new() { AdminUsername = data.WindowsProfile.AdminUsername }, - ServicePrincipalProfile = data.ServicePrincipalProfile is null ? null : new() { ClientId = data.ServicePrincipalProfile.ClientId }, - AutoUpgradeProfile = data.AutoUpgradeProfile is null ? null : new() - { - UpgradeChannel = data.AutoUpgradeProfile.UpgradeChannel?.ToString(), - NodeOSUpgradeChannel = data.AutoUpgradeProfile.NodeOSUpgradeChannel?.ToString() - }, - // OIDC Issuer Profile - OidcIssuerProfile = data.OidcIssuerProfile is null ? null : new() - { - Enabled = data.OidcIssuerProfile.IsEnabled, - IssuerUrl = data.OidcIssuerProfile.IssuerUriInfo - }, - AddonProfiles = data.AddonProfiles?.ToDictionary( - kvp => kvp.Key, - static kvp => - { - IDictionary map = new Dictionary(); - if (kvp.Value != null) - { - if (kvp.Value.Config != null) - { - foreach (var c in kvp.Value.Config) - { - map[$"config.{c.Key}"] = c.Value; - } - } - if (kvp.Value.Identity != null) - { - if (kvp.Value.Identity.ClientId != null) - map.Add("identity.clientId", kvp.Value.Identity.ClientId.ToString()!); - if (kvp.Value.Identity.ObjectId != null) - map.Add("identity.objectId", kvp.Value.Identity.ObjectId.ToString()!); - } - } - return map; - }), - IdentityProfile = data.IdentityProfile?.ToDictionary( - kvp => kvp.Key, - kvp => new ManagedIdentityReference - { - ResourceId = kvp.Value?.ResourceId?.ToString(), - ClientId = kvp.Value?.ClientId?.ToString(), - ObjectId = kvp.Value?.ObjectId?.ToString() - }), - DisableLocalAccounts = data.DisableLocalAccounts, - // Security Profile - SecurityProfile = data.SecurityProfile is null ? null : new() - { - AzureKeyVaultKms = data.SecurityProfile.AzureKeyVaultKms is null ? null : new() - { - Enabled = data.SecurityProfile.AzureKeyVaultKms.IsEnabled, - KeyId = data.SecurityProfile.AzureKeyVaultKms.KeyId?.ToString() - }, - Defender = data.SecurityProfile.Defender is null ? null : new() - { - LogAnalyticsWorkspaceResourceId = data.SecurityProfile.Defender.LogAnalyticsWorkspaceResourceId?.ToString(), - SecurityMonitoring = new() { Enabled = data.SecurityProfile.Defender.IsSecurityMonitoringEnabled } - }, - ImageCleaner = data.SecurityProfile.ImageCleaner is null ? null : new() - { - Enabled = data.SecurityProfile.ImageCleaner.IsEnabled, - IntervalHours = data.SecurityProfile.ImageCleaner.IntervalHours - }, - WorkloadIdentity = data.SecurityProfile.IsWorkloadIdentityEnabled is null - ? null - : new() { Enabled = data.SecurityProfile.IsWorkloadIdentityEnabled } - }, - // Storage Profile - StorageProfile = data.StorageProfile is null ? null : new() - { - BlobCSIDriver = data.StorageProfile.IsBlobCsiDriverEnabled is null ? null : new() { Enabled = data.StorageProfile.IsBlobCsiDriverEnabled }, - DiskCSIDriver = data.StorageProfile.IsDiskCsiDriverEnabled is null ? null : new() { Enabled = data.StorageProfile.IsDiskCsiDriverEnabled }, - FileCSIDriver = data.StorageProfile.IsFileCsiDriverEnabled is null ? null : new() { Enabled = data.StorageProfile.IsFileCsiDriverEnabled }, - SnapshotController = data.StorageProfile.IsSnapshotControllerEnabled is null ? null : new() { Enabled = data.StorageProfile.IsSnapshotControllerEnabled } - }, - // Metrics profile (no 1.2.5 SDK match for our CostAnalysis model) - MetricsProfile = null, - // Node provisioning and bootstrap profiles are not exposed in SDK 1.2.5 - NodeProvisioningProfile = null, - BootstrapProfile = null, - // Workload Auto-scaler profile - WorkloadAutoScalerProfile = data.WorkloadAutoScalerProfile is null ? null : new() - { - Keda = data.WorkloadAutoScalerProfile.IsKedaEnabled is null ? null : new() { Enabled = data.WorkloadAutoScalerProfile.IsKedaEnabled }, - VerticalPodAutoscaler = data.WorkloadAutoScalerProfile.IsVpaEnabled is null ? null : new() { Enabled = data.WorkloadAutoScalerProfile.IsVpaEnabled } - }, - // AI toolchain operator profile not exposed in SDK 1.2.5 - AiToolchainOperatorProfile = null, - // Unique resource UID - ResourceUid = data.ResourceId, - Tags = data.Tags?.ToDictionary(kvp => kvp.Key, kvp => kvp.Value) - }; - - // Map agent pool profiles from the cluster resource data when available - if (data.AgentPoolProfiles is not null) - { - try - { - cluster.AgentPoolProfiles = data.AgentPoolProfiles.Select(ConvertToNodePoolModel).ToList(); - } - catch - { - // If SDK shape differs, fall back to minimal projection - cluster.AgentPoolProfiles = data.AgentPoolProfiles - .Select(p => new NodePool { Name = p.Name, Count = p.Count, VmSize = p.VmSize?.ToString(), Mode = p.Mode?.ToString() }) - .ToList(); - } - } - - return cluster; - } - private static NodePool ConvertToNodePoolModel(ContainerServiceAgentPoolResource agentPoolResource) { var data = agentPoolResource.Data; From 1b26b2485ba2bd99ebaad7924eb2adebe6147658 Mon Sep 17 00:00:00 2001 From: Marcos Salamanca Date: Tue, 9 Jun 2026 17:24:20 -0500 Subject: [PATCH 3/4] simplified cluster conversion --- .../src/Commands/AksJsonContext.cs | 10 + .../src/Models/NodePool.cs | 3 + .../AksClusterResourceGraphResponse.cs | 120 +++++ .../src/Services/AksService.cs | 439 ++++-------------- 4 files changed, 214 insertions(+), 358 deletions(-) create mode 100644 tools/Azure.Mcp.Tools.Aks/src/Services/AksClusterResourceGraphResponse.cs diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs index 4af52fc34a..8500be36b4 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/AksJsonContext.cs @@ -4,6 +4,7 @@ using System.Text.Json.Serialization; using Azure.Mcp.Tools.Aks.Commands.Cluster; using Azure.Mcp.Tools.Aks.Commands.Nodepool; +using Azure.Mcp.Tools.Aks.Services; namespace Azure.Mcp.Tools.Aks.Commands; @@ -46,5 +47,14 @@ namespace Azure.Mcp.Tools.Aks.Commands; [JsonSerializable(typeof(Models.WorkloadAutoScalerProfile))] [JsonSerializable(typeof(Models.WorkloadAutoScalerKeda))] [JsonSerializable(typeof(Models.WorkloadAutoScalerVerticalPodAutoscaler))] +[JsonSerializable(typeof(AksClusterResourceGraphResponse))] +[JsonSerializable(typeof(AksClusterSkuJson))] +[JsonSerializable(typeof(AksClusterPropertiesJson))] +[JsonSerializable(typeof(AksPowerStateJson))] +[JsonSerializable(typeof(AksClusterNetworkProfileJson))] +[JsonSerializable(typeof(AksNetworkLoadBalancerProfileJson))] +[JsonSerializable(typeof(AksManagedOutboundIPsJson))] +[JsonSerializable(typeof(AksAddonProfileJson))] +[JsonSerializable(typeof(AksAddonIdentityJson))] [JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)] internal sealed partial class AksJsonContext : JsonSerializerContext; diff --git a/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs b/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs index 05063053bc..d8ec04bed8 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Models/NodePool.cs @@ -155,7 +155,10 @@ public sealed class NodePoolNetworkProfile public sealed class PortRange { + [System.Text.Json.Serialization.JsonPropertyName("portStart")] public int? StartPort { get; set; } + + [System.Text.Json.Serialization.JsonPropertyName("portEnd")] public int? EndPort { get; set; } } diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/AksClusterResourceGraphResponse.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/AksClusterResourceGraphResponse.cs new file mode 100644 index 0000000000..e94457d440 --- /dev/null +++ b/tools/Azure.Mcp.Tools.Aks/src/Services/AksClusterResourceGraphResponse.cs @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Azure.Mcp.Tools.Aks.Models; + +namespace Azure.Mcp.Tools.Aks.Services; + +/// +/// Intermediate type that mirrors the Azure Resource Graph JSON structure for a managed cluster +/// resource. Used only for deserialization; the public model is then +/// populated from this type via AksService.MapToCluster. +/// +internal sealed class AksClusterResourceGraphResponse +{ + public string? Id { get; set; } + public string? Name { get; set; } + public string? SubscriptionId { get; set; } + + [JsonPropertyName("resourceGroup")] + public string? ResourceGroup { get; set; } + + public string? Location { get; set; } + public Dictionary? Tags { get; set; } + public AksClusterSkuJson? Sku { get; set; } + public ResourceIdentity? Identity { get; set; } + public AksClusterPropertiesJson? Properties { get; set; } +} + +internal sealed class AksClusterSkuJson +{ + public string? Name { get; set; } + public string? Tier { get; set; } +} + +internal sealed class AksClusterPropertiesJson +{ + public string? KubernetesVersion { get; set; } + public string? ProvisioningState { get; set; } + public string? DnsPrefix { get; set; } + public string? Fqdn { get; set; } + public string? NodeResourceGroup { get; set; } + public string? SupportPlan { get; set; } + + [JsonPropertyName("resourceUID")] + public string? ResourceUid { get; set; } + + [JsonPropertyName("enableRBAC")] + public bool? EnableRbac { get; set; } + + public bool? DisableLocalAccounts { get; set; } + public int? MaxAgentPools { get; set; } + public AksPowerStateJson? PowerState { get; set; } + public AksClusterNetworkProfileJson? NetworkProfile { get; set; } + public OidcIssuerProfile? OidcIssuerProfile { get; set; } + public AutoUpgradeProfile? AutoUpgradeProfile { get; set; } + public ClusterSecurityProfile? SecurityProfile { get; set; } + public ClusterStorageProfile? StorageProfile { get; set; } + public WorkloadAutoScalerProfile? WorkloadAutoScalerProfile { get; set; } + public Dictionary? AddonProfiles { get; set; } + public Dictionary? IdentityProfile { get; set; } + public List? AgentPoolProfiles { get; set; } +} + +internal sealed class AksPowerStateJson +{ + public string? Code { get; set; } +} + +/// +/// Intermediate network profile type. The public model +/// has a flat ManagedOutboundIPCount, while the Resource Graph JSON nests this value +/// under loadBalancerProfile.managedOutboundIPs.count, requiring a separate type. +/// +internal sealed class AksClusterNetworkProfileJson +{ + public string? NetworkPlugin { get; set; } + public string? NetworkPluginMode { get; set; } + public string? NetworkPolicy { get; set; } + public string? NetworkDataplane { get; set; } + public string? LoadBalancerSku { get; set; } + public AksNetworkLoadBalancerProfileJson? LoadBalancerProfile { get; set; } + public string? PodCidr { get; set; } + public string? ServiceCidr { get; set; } + public string? DnsServiceIP { get; set; } + public string? OutboundType { get; set; } + public List? PodCidrs { get; set; } + public List? ServiceCidrs { get; set; } + public List? IpFamilies { get; set; } +} + +internal sealed class AksNetworkLoadBalancerProfileJson +{ + public AksManagedOutboundIPsJson? ManagedOutboundIPs { get; set; } + public List? EffectiveOutboundIPs { get; set; } + public string? BackendPoolType { get; set; } +} + +internal sealed class AksManagedOutboundIPsJson +{ + public int? Count { get; set; } +} + +/// +/// Intermediate add-on profile type used to deserialize each entry in +/// properties.addonProfiles before flattening to the config.* / +/// identity.* key convention used by . +/// +internal sealed class AksAddonProfileJson +{ + public bool? Enabled { get; set; } + public Dictionary? Config { get; set; } + public AksAddonIdentityJson? Identity { get; set; } +} + +internal sealed class AksAddonIdentityJson +{ + public string? ClientId { get; set; } + public string? ObjectId { get; set; } +} diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs index c4df2b967f..5aeb055ee6 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs @@ -5,6 +5,7 @@ using Azure.Mcp.Core.Services.Azure; using Azure.Mcp.Core.Services.Azure.Subscription; using Azure.Mcp.Core.Services.Azure.Tenant; +using Azure.Mcp.Tools.Aks.Commands; using Azure.Mcp.Tools.Aks.Models; using Azure.ResourceManager.ContainerService; using Azure.ResourceManager.ContainerService.Models; @@ -223,397 +224,119 @@ public async Task> GetNodePools( private static Cluster ConvertToClusterFromJson(JsonElement item) { - var cluster = new Cluster - { - Id = item.TryGetProperty("id", out var idEl) ? idEl.GetString() : null, - Name = item.TryGetProperty("name", out var nameEl) ? nameEl.GetString() : null, - SubscriptionId = item.TryGetProperty("subscriptionId", out var subEl) ? subEl.GetString() : null, - ResourceGroupName = item.TryGetProperty("resourceGroup", out var rgEl) ? rgEl.GetString() : null, - Location = item.TryGetProperty("location", out var locEl) ? locEl.GetString() : null, - Tags = ReadStringDictionaryFromProperty(item, "tags"), - }; - - if (item.TryGetProperty("sku", out var skuEl)) - { - cluster.SkuName = skuEl.TryGetProperty("name", out var skuNameEl) ? skuNameEl.GetString() : null; - cluster.SkuTier = skuEl.TryGetProperty("tier", out var skuTierEl) ? skuTierEl.GetString() : null; - } - - if (item.TryGetProperty("identity", out var identEl)) - { - cluster.IdentityType = identEl.TryGetProperty("type", out var itEl) ? itEl.GetString() : null; - cluster.Identity = new() - { - Type = cluster.IdentityType, - PrincipalId = identEl.TryGetProperty("principalId", out var pidEl) ? pidEl.GetString() : null, - TenantId = identEl.TryGetProperty("tenantId", out var tidEl) ? tidEl.GetString() : null, - }; - } - - if (!item.TryGetProperty("properties", out var props)) - return cluster; - - cluster.KubernetesVersion = props.TryGetProperty("kubernetesVersion", out var kvEl) ? kvEl.GetString() : null; - cluster.ProvisioningState = props.TryGetProperty("provisioningState", out var psEl) ? psEl.GetString() : null; - cluster.DnsPrefix = props.TryGetProperty("dnsPrefix", out var dpEl) ? dpEl.GetString() : null; - cluster.Fqdn = props.TryGetProperty("fqdn", out var fqdnEl) ? fqdnEl.GetString() : null; - cluster.NodeResourceGroup = props.TryGetProperty("nodeResourceGroup", out var nrgEl) ? nrgEl.GetString() : null; - cluster.SupportPlan = props.TryGetProperty("supportPlan", out var spEl) ? spEl.GetString() : null; - cluster.ResourceUid = props.TryGetProperty("resourceUID", out var ruidEl) ? ruidEl.GetString() : null; - cluster.EnableRbac = TryGetNullableBool(props, "enableRBAC"); - cluster.DisableLocalAccounts = TryGetNullableBool(props, "disableLocalAccounts"); - - if (props.TryGetProperty("maxAgentPools", out var mapEl) && mapEl.TryGetInt32(out var mapVal)) - cluster.MaxAgentPools = mapVal; - - if (props.TryGetProperty("powerState", out var psStateEl)) - cluster.PowerState = psStateEl.TryGetProperty("code", out var psCodeEl) ? psCodeEl.GetString() : null; - - if (props.TryGetProperty("networkProfile", out var npEl)) - { - cluster.NetworkPlugin = npEl.TryGetProperty("networkPlugin", out var npluginEl) ? npluginEl.GetString() : null; - cluster.NetworkPolicy = npEl.TryGetProperty("networkPolicy", out var npolicyEl) ? npolicyEl.GetString() : null; - cluster.ServiceCidr = npEl.TryGetProperty("serviceCidr", out var scCidrEl) ? scCidrEl.GetString() : null; - cluster.DnsServiceIP = npEl.TryGetProperty("dnsServiceIP", out var dnsEl) ? dnsEl.GetString() : null; - cluster.NetworkProfile = ReadNetworkProfile(npEl); - } - - if (props.TryGetProperty("oidcIssuerProfile", out var oidcEl)) - { - cluster.OidcIssuerProfile = new() - { - Enabled = TryGetNullableBool(oidcEl, "enabled"), - IssuerUrl = oidcEl.TryGetProperty("issuerURL", out var iuEl) ? iuEl.GetString() : null, - }; - } - - if (props.TryGetProperty("autoUpgradeProfile", out var aupEl)) - { - cluster.AutoUpgradeProfile = new() - { - UpgradeChannel = aupEl.TryGetProperty("upgradeChannel", out var ucEl) ? ucEl.GetString() : null, - NodeOSUpgradeChannel = aupEl.TryGetProperty("nodeOSUpgradeChannel", out var noscEl) ? noscEl.GetString() : null, - }; - } - - if (props.TryGetProperty("securityProfile", out var secEl)) - cluster.SecurityProfile = ReadSecurityProfile(secEl); - - if (props.TryGetProperty("storageProfile", out var stEl)) - cluster.StorageProfile = ReadStorageProfile(stEl); - - if (props.TryGetProperty("workloadAutoScalerProfile", out var waspEl)) - { - cluster.WorkloadAutoScalerProfile = new() - { - Keda = waspEl.TryGetProperty("keda", out var kedaEl) - ? new() { Enabled = TryGetNullableBool(kedaEl, "enabled") } - : null, - VerticalPodAutoscaler = waspEl.TryGetProperty("verticalPodAutoscaler", out var vpaEl) - ? new() { Enabled = TryGetNullableBool(vpaEl, "enabled") } - : null, - }; - } - - if (props.TryGetProperty("addonProfiles", out var addonEl) && addonEl.ValueKind == JsonValueKind.Object) - { - var addonProfiles = new Dictionary>(); - foreach (var addon in addonEl.EnumerateObject()) - { - var map = new Dictionary(); - if (addon.Value.TryGetProperty("config", out var configEl) && configEl.ValueKind == JsonValueKind.Object) - { - foreach (var cfg in configEl.EnumerateObject()) - map[$"config.{cfg.Name}"] = cfg.Value.GetString() ?? string.Empty; - } - if (addon.Value.TryGetProperty("identity", out var addonIdentEl)) - { - if (addonIdentEl.TryGetProperty("clientId", out var cliEl)) - map["identity.clientId"] = cliEl.GetString() ?? string.Empty; - if (addonIdentEl.TryGetProperty("objectId", out var objEl)) - map["identity.objectId"] = objEl.GetString() ?? string.Empty; - } - addonProfiles[addon.Name] = map; - } - cluster.AddonProfiles = addonProfiles; - } - - if (props.TryGetProperty("identityProfile", out var ipEl) && ipEl.ValueKind == JsonValueKind.Object) - { - var identityProfile = new Dictionary(); - foreach (var entry in ipEl.EnumerateObject()) - { - identityProfile[entry.Name] = new() - { - ResourceId = entry.Value.TryGetProperty("resourceId", out var rIdEl) ? rIdEl.GetString() : null, - ClientId = entry.Value.TryGetProperty("clientId", out var cIdEl) ? cIdEl.GetString() : null, - ObjectId = entry.Value.TryGetProperty("objectId", out var oIdEl) ? oIdEl.GetString() : null, - }; - } - cluster.IdentityProfile = identityProfile; - } - - if (props.TryGetProperty("agentPoolProfiles", out var appEl) && appEl.ValueKind == JsonValueKind.Array) - { - var pools = new List(); - foreach (var poolEl in appEl.EnumerateArray()) - pools.Add(ReadNodePoolFromJson(poolEl)); - cluster.AgentPoolProfiles = pools; - - var first = pools.Count > 0 ? pools[0] : null; - cluster.NodeCount = first?.Count; - cluster.NodeVmSize = first?.VmSize; - } - - return cluster; + var response = JsonSerializer.Deserialize(item, AksJsonContext.Default.AksClusterResourceGraphResponse); + return response is null ? new Cluster() : MapToCluster(response); } - private static bool? TryGetNullableBool(JsonElement element, string propertyName) + private static Cluster MapToCluster(AksClusterResourceGraphResponse response) { - if (!element.TryGetProperty(propertyName, out var el)) - return null; - return el.ValueKind switch + var props = response.Properties; + var np = props?.NetworkProfile; + + var cluster = new Cluster { - JsonValueKind.True => true, - JsonValueKind.False => false, - _ => null, + Id = response.Id, + Name = response.Name, + SubscriptionId = response.SubscriptionId, + ResourceGroupName = response.ResourceGroup, + Location = response.Location, + Tags = response.Tags, + SkuName = response.Sku?.Name, + SkuTier = response.Sku?.Tier, + IdentityType = response.Identity?.Type, + Identity = response.Identity, + KubernetesVersion = props?.KubernetesVersion, + ProvisioningState = props?.ProvisioningState, + DnsPrefix = props?.DnsPrefix, + Fqdn = props?.Fqdn, + NodeResourceGroup = props?.NodeResourceGroup, + SupportPlan = props?.SupportPlan, + ResourceUid = props?.ResourceUid, + EnableRbac = props?.EnableRbac, + DisableLocalAccounts = props?.DisableLocalAccounts, + MaxAgentPools = props?.MaxAgentPools, + PowerState = props?.PowerState?.Code, + NetworkPlugin = np?.NetworkPlugin, + NetworkPolicy = np?.NetworkPolicy, + ServiceCidr = np?.ServiceCidr, + DnsServiceIP = np?.DnsServiceIP, + NetworkProfile = MapToNetworkProfile(np), + OidcIssuerProfile = props?.OidcIssuerProfile, + AutoUpgradeProfile = props?.AutoUpgradeProfile, + SecurityProfile = props?.SecurityProfile, + StorageProfile = props?.StorageProfile, + WorkloadAutoScalerProfile = props?.WorkloadAutoScalerProfile, + AddonProfiles = MapAddonProfiles(props?.AddonProfiles), + IdentityProfile = props?.IdentityProfile, + AgentPoolProfiles = props?.AgentPoolProfiles, + NodeCount = props?.AgentPoolProfiles?.Count > 0 ? props.AgentPoolProfiles[0].Count : null, + NodeVmSize = props?.AgentPoolProfiles?.Count > 0 ? props.AgentPoolProfiles[0].VmSize : null, }; + + return cluster; } - private static IDictionary? ReadStringDictionaryFromProperty(JsonElement element, string propertyName) + private static ClusterNetworkProfile? MapToNetworkProfile(AksClusterNetworkProfileJson? np) { - if (!element.TryGetProperty(propertyName, out var dictEl) || dictEl.ValueKind != JsonValueKind.Object) + if (np is null) return null; - var result = new Dictionary(); - foreach (var prop in dictEl.EnumerateObject()) - result[prop.Name] = prop.Value.GetString() ?? string.Empty; - return result.Count > 0 ? result : null; - } - private static ClusterNetworkProfile ReadNetworkProfile(JsonElement npEl) - { ClusterNetworkLoadBalancerProfile? lbProfile = null; - if (npEl.TryGetProperty("loadBalancerProfile", out var lbEl)) + if (np.LoadBalancerProfile is { } lb) { - int? managedOutboundIPCount = null; - if (lbEl.TryGetProperty("managedOutboundIPs", out var moEl) - && moEl.TryGetProperty("count", out var moCountEl) - && moCountEl.TryGetInt32(out var moCountVal)) - { - managedOutboundIPCount = moCountVal; - } - - List? effectiveOutboundIPs = null; - if (lbEl.TryGetProperty("effectiveOutboundIPs", out var eoEl) && eoEl.ValueKind == JsonValueKind.Array) - { - effectiveOutboundIPs = []; - foreach (var eoItem in eoEl.EnumerateArray()) - effectiveOutboundIPs.Add(new() { Id = eoItem.TryGetProperty("id", out var eoIdEl) ? eoIdEl.GetString() : null }); - } - lbProfile = new() { - ManagedOutboundIPCount = managedOutboundIPCount, - EffectiveOutboundIPs = effectiveOutboundIPs, - BackendPoolType = lbEl.TryGetProperty("backendPoolType", out var bptEl) ? bptEl.GetString() : null, + ManagedOutboundIPCount = lb.ManagedOutboundIPs?.Count, + EffectiveOutboundIPs = lb.EffectiveOutboundIPs, + BackendPoolType = lb.BackendPoolType, }; } - List? podCidrs = null; - if (npEl.TryGetProperty("podCidrs", out var podCidrsEl) && podCidrsEl.ValueKind == JsonValueKind.Array) - podCidrs = podCidrsEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); - - List? serviceCidrs = null; - if (npEl.TryGetProperty("serviceCidrs", out var svcCidrsEl) && svcCidrsEl.ValueKind == JsonValueKind.Array) - serviceCidrs = svcCidrsEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); - - List? ipFamilies = null; - if (npEl.TryGetProperty("ipFamilies", out var ipFamsEl) && ipFamsEl.ValueKind == JsonValueKind.Array) - ipFamilies = ipFamsEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); - return new() { - NetworkPlugin = npEl.TryGetProperty("networkPlugin", out var npluginEl) ? npluginEl.GetString() : null, - NetworkPluginMode = npEl.TryGetProperty("networkPluginMode", out var npmodeEl) ? npmodeEl.GetString() : null, - NetworkPolicy = npEl.TryGetProperty("networkPolicy", out var npolicyEl) ? npolicyEl.GetString() : null, - NetworkDataplane = npEl.TryGetProperty("networkDataplane", out var ndpEl) ? ndpEl.GetString() : null, - LoadBalancerSku = npEl.TryGetProperty("loadBalancerSku", out var lbSkuEl) ? lbSkuEl.GetString() : null, + NetworkPlugin = np.NetworkPlugin, + NetworkPluginMode = np.NetworkPluginMode, + NetworkPolicy = np.NetworkPolicy, + NetworkDataplane = np.NetworkDataplane, + LoadBalancerSku = np.LoadBalancerSku, LoadBalancerProfile = lbProfile, - PodCidr = npEl.TryGetProperty("podCidr", out var pcCidrEl) ? pcCidrEl.GetString() : null, - ServiceCidr = npEl.TryGetProperty("serviceCidr", out var scEl) ? scEl.GetString() : null, - DnsServiceIP = npEl.TryGetProperty("dnsServiceIP", out var dnsEl2) ? dnsEl2.GetString() : null, - OutboundType = npEl.TryGetProperty("outboundType", out var otEl) ? otEl.GetString() : null, - PodCidrs = podCidrs, - ServiceCidrs = serviceCidrs, - IpFamilies = ipFamilies, + PodCidr = np.PodCidr, + ServiceCidr = np.ServiceCidr, + DnsServiceIP = np.DnsServiceIP, + OutboundType = np.OutboundType, + PodCidrs = np.PodCidrs, + ServiceCidrs = np.ServiceCidrs, + IpFamilies = np.IpFamilies, }; } - private static ClusterSecurityProfile ReadSecurityProfile(JsonElement secEl) + private static IDictionary>? MapAddonProfiles( + Dictionary? addonProfiles) { - AzureKeyVaultKms? kms = null; - if (secEl.TryGetProperty("azureKeyVaultKms", out var kmsEl)) - { - kms = new() - { - Enabled = TryGetNullableBool(kmsEl, "enabled"), - KeyId = kmsEl.TryGetProperty("keyId", out var kidEl) ? kidEl.GetString() : null, - }; - } - - DefenderProfile? defender = null; - if (secEl.TryGetProperty("defender", out var defEl)) - { - DefenderSecurityMonitoring? monitoring = null; - if (defEl.TryGetProperty("securityMonitoring", out var smEl)) - monitoring = new() { Enabled = TryGetNullableBool(smEl, "enabled") }; - - defender = new() - { - LogAnalyticsWorkspaceResourceId = defEl.TryGetProperty("logAnalyticsWorkspaceResourceId", out var lawEl) ? lawEl.GetString() : null, - SecurityMonitoring = monitoring, - }; - } - - ImageCleanerProfile? imageCleaner = null; - if (secEl.TryGetProperty("imageCleaner", out var icEl)) - { - int? intervalHours = null; - if (icEl.TryGetProperty("intervalHours", out var ihEl) && ihEl.TryGetInt32(out var ihVal)) - intervalHours = ihVal; - imageCleaner = new() - { - Enabled = TryGetNullableBool(icEl, "enabled"), - IntervalHours = intervalHours, - }; - } - - WorkloadIdentityProfile? workloadIdentity = null; - if (secEl.TryGetProperty("workloadIdentity", out var wiEl)) - workloadIdentity = new() { Enabled = TryGetNullableBool(wiEl, "enabled") }; - - return new() - { - AzureKeyVaultKms = kms, - Defender = defender, - ImageCleaner = imageCleaner, - WorkloadIdentity = workloadIdentity, - }; - } - - private static ClusterStorageProfile ReadStorageProfile(JsonElement stEl) - { - return new() - { - BlobCSIDriver = stEl.TryGetProperty("blobCSIDriver", out var blobEl) ? new() { Enabled = TryGetNullableBool(blobEl, "enabled") } : null, - DiskCSIDriver = stEl.TryGetProperty("diskCSIDriver", out var diskEl) ? new() { Enabled = TryGetNullableBool(diskEl, "enabled") } : null, - FileCSIDriver = stEl.TryGetProperty("fileCSIDriver", out var fileEl) ? new() { Enabled = TryGetNullableBool(fileEl, "enabled") } : null, - SnapshotController = stEl.TryGetProperty("snapshotController", out var scEl) ? new() { Enabled = TryGetNullableBool(scEl, "enabled") } : null, - }; - } - - private static NodePool ReadNodePoolFromJson(JsonElement poolEl) - { - NodePoolPowerState? powerState = null; - if (poolEl.TryGetProperty("powerState", out var psEl)) - powerState = new() { Code = psEl.TryGetProperty("code", out var psCodeEl) ? psCodeEl.GetString() : null }; - - NodePoolUpgradeSettings? upgradeSettings = null; - if (poolEl.TryGetProperty("upgradeSettings", out var usEl)) - { - upgradeSettings = new() - { - MaxSurge = usEl.TryGetProperty("maxSurge", out var msEl) ? msEl.GetString() : null, - MaxUnavailable = usEl.TryGetProperty("maxUnavailable", out var muEl) ? muEl.GetString() : null, - }; - } + if (addonProfiles is null) + return null; - NodePoolNetworkProfile? networkProfile = null; - if (poolEl.TryGetProperty("networkProfile", out var npEl)) + var result = new Dictionary>(); + foreach (var (name, addon) in addonProfiles) { - List? allowedHostPorts = null; - if (npEl.TryGetProperty("allowedHostPorts", out var ahpEl) && ahpEl.ValueKind == JsonValueKind.Array) + var map = new Dictionary(); + if (addon.Config is not null) { - allowedHostPorts = []; - foreach (var portEl in ahpEl.EnumerateArray()) - { - int? startPort = null, endPort = null; - if (portEl.TryGetProperty("portStart", out var spEl) && spEl.TryGetInt32(out var spVal)) startPort = spVal; - if (portEl.TryGetProperty("portEnd", out var epEl) && epEl.TryGetInt32(out var epVal)) endPort = epVal; - allowedHostPorts.Add(new() { StartPort = startPort, EndPort = endPort }); - } + foreach (var (key, value) in addon.Config) + map[$"config.{key}"] = value; } - - List? applicationSecurityGroups = null; - if (npEl.TryGetProperty("applicationSecurityGroups", out var asgEl) && asgEl.ValueKind == JsonValueKind.Array) - applicationSecurityGroups = asgEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); - - List? nodePublicIPTags = null; - if (npEl.TryGetProperty("nodePublicIPTags", out var ipTagsEl) && ipTagsEl.ValueKind == JsonValueKind.Array) + if (addon.Identity is not null) { - nodePublicIPTags = []; - foreach (var tagEl in ipTagsEl.EnumerateArray()) - { - nodePublicIPTags.Add(new() - { - IpTagType = tagEl.TryGetProperty("ipTagType", out var ittEl) ? ittEl.GetString() : null, - Tag = tagEl.TryGetProperty("tag", out var tagValEl) ? tagValEl.GetString() : null, - }); - } + if (addon.Identity.ClientId is not null) + map["identity.clientId"] = addon.Identity.ClientId; + if (addon.Identity.ObjectId is not null) + map["identity.objectId"] = addon.Identity.ObjectId; } - - networkProfile = new() - { - AllowedHostPorts = allowedHostPorts, - ApplicationSecurityGroups = applicationSecurityGroups, - NodePublicIPTags = nodePublicIPTags, - }; + result[name] = map; } - IList? nodeTaints = null; - if (poolEl.TryGetProperty("nodeTaints", out var ntEl) && ntEl.ValueKind == JsonValueKind.Array) - nodeTaints = ntEl.EnumerateArray().Select(e => e.GetString() ?? string.Empty).ToList(); - - return new() - { - Name = poolEl.TryGetProperty("name", out var poolNameEl) ? poolNameEl.GetString() : null, - Count = poolEl.TryGetProperty("count", out var countEl) && countEl.TryGetInt32(out var countVal) ? countVal : null, - VmSize = poolEl.TryGetProperty("vmSize", out var vmSizeEl) ? vmSizeEl.GetString() : null, - OsDiskSizeGB = poolEl.TryGetProperty("osDiskSizeGB", out var odsEl) && odsEl.TryGetInt32(out var odsVal) ? odsVal : null, - OsDiskType = poolEl.TryGetProperty("osDiskType", out var odtEl) ? odtEl.GetString() : null, - KubeletDiskType = poolEl.TryGetProperty("kubeletDiskType", out var kdtEl) ? kdtEl.GetString() : null, - MaxPods = poolEl.TryGetProperty("maxPods", out var mpEl) && mpEl.TryGetInt32(out var mpVal) ? mpVal : null, - Type = poolEl.TryGetProperty("type", out var typeEl) ? typeEl.GetString() : null, - MaxCount = poolEl.TryGetProperty("maxCount", out var maxCEl) && maxCEl.TryGetInt32(out var maxCVal) ? maxCVal : null, - MinCount = poolEl.TryGetProperty("minCount", out var minCEl) && minCEl.TryGetInt32(out var minCVal) ? minCVal : null, - EnableAutoScaling = TryGetNullableBool(poolEl, "enableAutoScaling"), - ScaleDownMode = poolEl.TryGetProperty("scaleDownMode", out var sdmEl) ? sdmEl.GetString() : null, - ProvisioningState = poolEl.TryGetProperty("provisioningState", out var ppEl) ? ppEl.GetString() : null, - PowerState = powerState, - Mode = poolEl.TryGetProperty("mode", out var poolModeEl) ? poolModeEl.GetString() : null, - OrchestratorVersion = poolEl.TryGetProperty("orchestratorVersion", out var ovEl) ? ovEl.GetString() : null, - CurrentOrchestratorVersion = poolEl.TryGetProperty("currentOrchestratorVersion", out var covEl) ? covEl.GetString() : null, - EnableNodePublicIP = TryGetNullableBool(poolEl, "enableNodePublicIP"), - ScaleSetPriority = poolEl.TryGetProperty("scaleSetPriority", out var sspEl) ? sspEl.GetString() : null, - ScaleSetEvictionPolicy = poolEl.TryGetProperty("scaleSetEvictionPolicy", out var ssepEl) ? ssepEl.GetString() : null, - NodeLabels = ReadStringDictionaryFromProperty(poolEl, "nodeLabels"), - NodeTaints = nodeTaints, - OsType = poolEl.TryGetProperty("osType", out var osTypeEl) ? osTypeEl.GetString() : null, - OsSKU = poolEl.TryGetProperty("osSKU", out var osSkuEl) ? osSkuEl.GetString() : null, - NodeImageVersion = poolEl.TryGetProperty("nodeImageVersion", out var nivEl) ? nivEl.GetString() : null, - Tags = ReadStringDictionaryFromProperty(poolEl, "tags"), - SpotMaxPrice = poolEl.TryGetProperty("spotMaxPrice", out var smpEl) && smpEl.TryGetDouble(out var smpVal) ? smpVal : null, - WorkloadRuntime = poolEl.TryGetProperty("workloadRuntime", out var wrEl) ? wrEl.GetString() : null, - EnableEncryptionAtHost = TryGetNullableBool(poolEl, "enableEncryptionAtHost"), - EnableUltraSSD = TryGetNullableBool(poolEl, "enableUltraSSD"), - EnableFIPS = TryGetNullableBool(poolEl, "enableFIPS"), - UpgradeSettings = upgradeSettings, - NetworkProfile = networkProfile, - PodSubnetId = poolEl.TryGetProperty("podSubnetID", out var psidEl) ? psidEl.GetString() : null, - VnetSubnetId = poolEl.TryGetProperty("vnetSubnetID", out var vsidEl) ? vsidEl.GetString() : null, - }; + return result.Count > 0 ? result : null; } - private static NodePool ConvertToNodePoolModel(ContainerServiceAgentPoolResource agentPoolResource) { var data = agentPoolResource.Data; From e42fa5b43c5fe22ef9153f50532928a6db3c7618 Mon Sep 17 00:00:00 2001 From: Marcos Salamanca Date: Thu, 18 Jun 2026 16:19:01 -0500 Subject: [PATCH 4/4] fix test issues, removed rg requirement --- .../src/Commands/Cluster/ClusterGetCommand.cs | 9 ------ .../src/Services/AksService.cs | 12 +++++--- .../AksCommandTests.cs | 28 +++++++++++++++---- .../Cluster/ClusterGetCommandTests.cs | 6 ++-- .../Nodepool/NodepoolGetCommandTests.cs | 2 +- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs b/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs index 666b4a3ce3..a2dd81fb94 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Commands/Cluster/ClusterGetCommand.cs @@ -33,15 +33,6 @@ protected override void RegisterOptions(Command command) base.RegisterOptions(command); command.Options.Add(OptionDefinitions.Common.ResourceGroup); command.Options.Add(AksOptionDefinitions.Cluster); - command.Validators.Add(commandResults => - { - var clusterName = commandResults.GetValueOrDefault(AksOptionDefinitions.Cluster); - var resourceGroup = commandResults.GetValueOrDefault(OptionDefinitions.Common.ResourceGroup); - if (!string.IsNullOrEmpty(clusterName) && string.IsNullOrEmpty(resourceGroup)) - { - commandResults.AddError("When specifying a cluster name, the --resource-group option is required."); - } - }); } protected override ClusterGetOptions BindOptions(ParseResult parseResult) diff --git a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs index 5aeb055ee6..dae5805c8c 100644 --- a/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs +++ b/tools/Azure.Mcp.Tools.Aks/src/Services/AksService.cs @@ -76,12 +76,16 @@ public async Task> GetClusters( } else { - ValidateRequiredParameters((nameof(resourceGroup), resourceGroup), (nameof(clusterName), clusterName)); + ValidateRequiredParameters((nameof(clusterName), clusterName)); // Create cache key - var cacheKey = string.IsNullOrEmpty(tenant) - ? CacheKeyBuilder.Build("cluster", subscription, resourceGroup!, clusterName) - : CacheKeyBuilder.Build("cluster", subscription, resourceGroup!, clusterName, tenant); + var cacheKey = (string.IsNullOrEmpty(resourceGroup), string.IsNullOrEmpty(tenant)) switch + { + (true, true) => CacheKeyBuilder.Build("cluster", subscription, clusterName), + (false, true) => CacheKeyBuilder.Build("cluster", subscription, resourceGroup, clusterName), + (true, false) => CacheKeyBuilder.Build("cluster", subscription, clusterName, tenant), + (false, false) => CacheKeyBuilder.Build("cluster", subscription, resourceGroup, clusterName, tenant) + }; // Try to get from cache first var cachedCluster = await _cacheService.GetAsync>(CacheGroup, cacheKey, s_cacheDuration, cancellationToken); diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/AksCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/AksCommandTests.cs index 63b6a71f79..efc58fc6a0 100644 --- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/AksCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/AksCommandTests.cs @@ -178,21 +178,36 @@ public async Task Should_get_specific_aks_cluster() [Fact] public async Task Should_handle_nonexistent_cluster_gracefully() { + // First, get a list of clusters to find a resource group to test against + var listResult = await CallToolAsync( + "aks_cluster_get", + new() + { + { "subscription", Settings.SubscriptionId } + }); + + var clusters = listResult.AssertProperty("clusters"); + Assert.True(clusters.GetArrayLength() > 0, "Expected at least one AKS cluster for testing get command"); + + // Get the first cluster's resource group + var firstCluster = clusters.EnumerateArray().First(); + var resourceGroupName = RegisterOrRetrieveVariable("firstResourceGroupName", firstCluster.GetProperty("resourceGroupName").GetString()!); + + // Attempt to get a non-existent cluster from that resource group var result = await CallToolAsync( "aks_cluster_get", new() { { "subscription", Settings.SubscriptionId }, - { "resource-group", "nonexistent-rg" }, + { "resource-group", resourceGroupName }, { "cluster", "nonexistent-cluster" } }); - // Should return runtime error response with error details + // Should return list with zero clusters Assert.True(result.HasValue); - var errorDetails = result.Value; - errorDetails.AssertProperty("message"); - var typeProperty = errorDetails.AssertProperty("type"); - Assert.Equal("RequestFailedException", typeProperty.GetString()); + var results = result.Value; + var resultsClusters = results.AssertProperty("clusters"); + Assert.True(resultsClusters.GetArrayLength() == 0, "Expected no clusters for nonexistent cluster request"); } [Fact] @@ -353,6 +368,7 @@ public async Task Should_handle_nonexistent_nodepool_gracefully() var errorDetails = result.Value; errorDetails.AssertProperty("message"); var typeProperty = errorDetails.AssertProperty("type"); + Assert.Equal("RequestFailedException", typeProperty.GetString()); } diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Cluster/ClusterGetCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Cluster/ClusterGetCommandTests.cs index e99b43ceb5..ce5ddbe031 100644 --- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Cluster/ClusterGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Cluster/ClusterGetCommandTests.cs @@ -26,9 +26,9 @@ public void Constructor_InitializesCommandCorrectly() [Theory] [InlineData("--subscription sub1 --resource-group rg1 --cluster cluster1", true)] - [InlineData("--subscription sub1 --cluster cluster1", false)] // Missing resource-group - [InlineData("--resource-group rg1 --cluster cluster1", false)] // Missing subscription - [InlineData("", false)] // Missing all required options + [InlineData("--subscription sub1 --cluster cluster1", true)] // Resource group is optional with ARG queries + [InlineData("--resource-group rg1 --cluster cluster1", true)] // Subscription is no longer required + [InlineData("", true)] // Valid scenario now that resource querying is being utilized public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) { // Arrange diff --git a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Nodepool/NodepoolGetCommandTests.cs b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Nodepool/NodepoolGetCommandTests.cs index b54108c949..c89e69d767 100644 --- a/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Nodepool/NodepoolGetCommandTests.cs +++ b/tools/Azure.Mcp.Tools.Aks/tests/Azure.Mcp.Tools.Aks.Tests/Nodepool/NodepoolGetCommandTests.cs @@ -29,7 +29,7 @@ public void Constructor_InitializesCommandCorrectly() [InlineData("--subscription sub123 --resource-group rg1 --cluster c1 --nodepool np1 --tenant t1", true)] [InlineData("--subscription sub123 --resource-group rg1 --nodepool np1", false)] // missing cluster [InlineData("--subscription sub123 --cluster c1 --nodepool np1", false)] // missing rg - [InlineData("--resource-group rg1 --cluster c1 --nodepool np1", false)] // missing subscription + [InlineData("--resource-group rg1 --cluster c1 --nodepool np1", true)] // ARG querying doesn't require subscription [InlineData("", false)] public async Task ExecuteAsync_ValidatesInputCorrectly(string args, bool shouldSucceed) {