From 87b13e108f73f8be9630f61f9606da5a355208e6 Mon Sep 17 00:00:00 2001 From: wenhug <50309350+wenhug@users.noreply.github.com> Date: Thu, 21 May 2026 23:16:31 +0000 Subject: [PATCH] aks: skip subnet validation for existing outbound type --- .../acs/managed_cluster_decorator.py | 23 ++++++--- .../latest/test_managed_cluster_decorator.py | 51 ++++++++++++++++++- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py b/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py index 6efebb3201c..b2b8e1b124a 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py +++ b/src/azure-cli/azure/cli/command_modules/acs/managed_cluster_decorator.py @@ -2375,9 +2375,10 @@ def _raise_missing_vnet_subnet_for_outbound_type(outbound_type: str, sku_name: s if sku_name == CONST_MANAGED_CLUSTER_SKU_NAME_AUTOMATIC: raise RequiredArgumentMissingError( - "--vnet-subnet-id must be specified for {outbound_type}. For an Automatic cluster " - "using Managed System Pool BYO VNet, specify --system-node-subnet-id, --node-subnet-id " - "and --apiserver-subnet-id instead. The subnet must be pre-configured with {requirement}".format( + "For an Automatic cluster using Managed System Pool BYO VNet, --system-node-subnet-id, " + "--node-subnet-id and --apiserver-subnet-id must be specified for {outbound_type}. " + "For other BYO VNet clusters, specify --vnet-subnet-id. The subnet must be " + "pre-configured with {requirement}".format( outbound_type=outbound_type, requirement=subnet_requirement, ) @@ -2436,17 +2437,27 @@ def _get_outbound_type( skuName = self.get_sku_name() isVnetSubnetIdEmpty = self.get_vnet_subnet_id() in ["", None] # For BYO VNet Managed System Pool (Automatic SKU with system-node/node subnet trio), - # the user's subnet IDs replace --vnet-subnet-id; don't force ManagedNATGateway in that case. + # those subnet IDs replace --vnet-subnet-id; don't force ManagedNATGateway in that case. byo_subnets_set = bool( self.raw_param.get("system_node_subnet_id") or self.raw_param.get("node_subnet_id") ) + hosted_system_profile = getattr(self.mc, "hosted_system_profile", None) if self.mc else None + existing_byo_subnets_set = bool( + self.decorator_mode == DecoratorMode.UPDATE and + hosted_system_profile and + ( + getattr(hosted_system_profile, "system_node_subnet_id", None) or + getattr(hosted_system_profile, "node_subnet_id", None) + ) + ) + byo_subnets_configured = byo_subnets_set or existing_byo_subnets_set use_automatic_managed_nat_gateway = ( skuName is not None and skuName == CONST_MANAGED_CLUSTER_SKU_NAME_AUTOMATIC and isVnetSubnetIdEmpty and not read_from_mc and - not byo_subnets_set + not byo_subnets_configured ) if use_automatic_managed_nat_gateway and outbound_type == CONST_OUTBOUND_TYPE_LOAD_BALANCER: # outbound_type of Automatic SKU should be ManagedNATGateway if no subnet id provided. @@ -2479,7 +2490,7 @@ def _get_outbound_type( CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY, ]: - if self.get_vnet_subnet_id() in ["", None] and not byo_subnets_set: + if not read_from_mc and self.get_vnet_subnet_id() in ["", None] and not byo_subnets_configured: self._raise_missing_vnet_subnet_for_outbound_type(outbound_type, skuName) if outbound_type == CONST_OUTBOUND_TYPE_MANAGED_NAT_GATEWAY: if self.get_vnet_subnet_id() not in ["", None] or byo_subnets_set: diff --git a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py index be04d278208..0ad0ca1be32 100644 --- a/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py +++ b/src/azure-cli/azure/cli/command_modules/acs/tests/latest/test_managed_cluster_decorator.py @@ -2273,6 +2273,53 @@ def test_get_outbound_type(self): ctx_14.attach_mc(mc_14) self.assertEqual(ctx_14.get_outbound_type(), CONST_OUTBOUND_TYPE_LOAD_BALANCER) + network_profile_14_0 = self.models.ContainerServiceNetworkProfile( + outbound_type=CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY + ) + mc_14_0 = self.models.ManagedCluster( + location="test_location", + network_profile=network_profile_14_0, + sku=self.models.ManagedClusterSKU(name="Base"), + ) + ctx_14_0 = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict({"sku": "base"}), + self.models, + DecoratorMode.UPDATE, + ) + ctx_14_0.agentpool_context = mock.MagicMock() + ctx_14_0.agentpool_context.get_vnet_subnet_id.return_value = None + ctx_14_0.attach_mc(mc_14_0) + self.assertEqual(ctx_14_0.get_outbound_type(), CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY) + + hosted_system_profile_existing_byo = self.models.ManagedClusterHostedSystemProfile() + hosted_system_profile_existing_byo.system_node_subnet_id = ( + "/subscriptions/s/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/v/subnets/sys" + ) + hosted_system_profile_existing_byo.node_subnet_id = ( + "/subscriptions/s/resourceGroups/rg/providers/Microsoft.Network/virtualNetworks/v/subnets/node" + ) + mc_existing_byo = self.models.ManagedCluster( + location="test_location", + network_profile=network_profile_14, + sku=self.models.ManagedClusterSKU(name="Automatic"), + hosted_system_profile=hosted_system_profile_existing_byo, + ) + for outbound_type in [ + CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING, + CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY, + ]: + ctx_existing_byo = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict({"outbound_type": outbound_type}), + self.models, + DecoratorMode.UPDATE, + ) + ctx_existing_byo.agentpool_context = mock.MagicMock() + ctx_existing_byo.agentpool_context.get_vnet_subnet_id.return_value = None + ctx_existing_byo.attach_mc(mc_existing_byo) + self.assertEqual(ctx_existing_byo.get_outbound_type(), outbound_type) + ctx_14_1 = AKSManagedClusterContext( self.cmd, AKSManagedClusterParamDict({ @@ -2286,7 +2333,7 @@ def test_get_outbound_type(self): ctx_14_1.agentpool_context.get_vnet_subnet_id.return_value = None with self.assertRaisesRegex( RequiredArgumentMissingError, - "--system-node-subnet-id, --node-subnet-id and --apiserver-subnet-id", + "Automatic cluster using Managed System Pool BYO VNet", ): ctx_14_1.get_outbound_type() @@ -2303,7 +2350,7 @@ def test_get_outbound_type(self): ctx_14_2.agentpool_context.get_vnet_subnet_id.return_value = None with self.assertRaisesRegex( RequiredArgumentMissingError, - "--system-node-subnet-id, --node-subnet-id and --apiserver-subnet-id", + "Automatic cluster using Managed System Pool BYO VNet", ): ctx_14_2.get_outbound_type()