From 1a6f727d192b9a7a16f4ff4197d1678e5af4dc88 Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 2 Jul 2026 11:02:27 +1000 Subject: [PATCH 1/2] [AKS] `az aks update`: Fix `--outbound-type` validation for UDR and userAssignedNATGateway --- .../acs/managed_cluster_decorator.py | 13 +++++ .../latest/test_managed_cluster_decorator.py | 50 +++++++++++++++++++ 2 files changed, 63 insertions(+) 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 df48993aaac..d58c6c7e0e0 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 @@ -2491,6 +2491,19 @@ def _get_outbound_type( CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY, ]: if not read_from_mc and self.get_vnet_subnet_id() in ["", None] and not byo_subnets_configured: + if self.decorator_mode == DecoratorMode.UPDATE: + # --vnet-subnet-id is not registered for 'aks update'. For BYO VNet clusters the subnet + # is already known from the agentpool, so validation passes above. Reaching here in update + # mode means the cluster uses a managed VNet, which cannot be migrated to UDR/userAssignedNATGateway. + raise InvalidArgumentValueError( + "Updating outbound type to {outbound_type} is only supported for " + "clusters using a custom (BYO) virtual network. Managed VNet clusters " + "cannot be updated to {outbound_type}. Please refer to " + "https://learn.microsoft.com/en-us/azure/aks/egress-outboundtype" + "#updating-outboundtype-after-cluster-creation for supported migration paths.".format( + outbound_type=outbound_type + ) + ) 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 211269efdb9..29e354c2ad2 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 @@ -2391,6 +2391,56 @@ def test_get_outbound_type(self): with self.assertRaises(InvalidArgumentValueError): ctx_17.get_outbound_type() + # update to UDR on a BYO VNet cluster (subnet known from agentpool) should succeed + ctx_18 = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + {"outbound_type": CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING} + ), + self.models, + DecoratorMode.UPDATE, + ) + ctx_18.agentpool_context = mock.MagicMock() + ctx_18.agentpool_context.get_vnet_subnet_id.return_value = "test_vnet_subnet_id" + self.assertEqual( + ctx_18.get_outbound_type(), CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING + ) + + # update to UDR on a managed VNet cluster (no subnet) should fail with a clear error, + # not ask for --vnet-subnet-id (which is not registered for 'aks update') + ctx_19 = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + {"outbound_type": CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING} + ), + self.models, + DecoratorMode.UPDATE, + ) + ctx_19.agentpool_context = mock.MagicMock() + ctx_19.agentpool_context.get_vnet_subnet_id.return_value = None + with self.assertRaisesRegex( + InvalidArgumentValueError, + "only supported for clusters using a custom", + ): + ctx_19.get_outbound_type() + + # update to userAssignedNATGateway on a managed VNet cluster (no subnet) should fail with a clear error + ctx_20 = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + {"outbound_type": CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY} + ), + self.models, + DecoratorMode.UPDATE, + ) + ctx_20.agentpool_context = mock.MagicMock() + ctx_20.agentpool_context.get_vnet_subnet_id.return_value = None + with self.assertRaisesRegex( + InvalidArgumentValueError, + "only supported for clusters using a custom", + ): + ctx_20.get_outbound_type() + def test_get_network_plugin_mode(self): # default ctx_1 = AKSManagedClusterContext( From e128887b6f4c56a37a3b50a34f9992b9e7c9df82 Mon Sep 17 00:00:00 2001 From: Fuming Zhang Date: Thu, 2 Jul 2026 11:07:51 +1000 Subject: [PATCH 2/2] Add UPDATE-mode BYO VNet success test for userAssignedNATGateway --- .../latest/test_managed_cluster_decorator.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 29e354c2ad2..26e06749272 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 @@ -2406,6 +2406,21 @@ def test_get_outbound_type(self): ctx_18.get_outbound_type(), CONST_OUTBOUND_TYPE_USER_DEFINED_ROUTING ) + # update to userAssignedNATGateway on a BYO VNet cluster (subnet known from agentpool) should succeed + ctx_18_1 = AKSManagedClusterContext( + self.cmd, + AKSManagedClusterParamDict( + {"outbound_type": CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY} + ), + self.models, + DecoratorMode.UPDATE, + ) + ctx_18_1.agentpool_context = mock.MagicMock() + ctx_18_1.agentpool_context.get_vnet_subnet_id.return_value = "test_vnet_subnet_id" + self.assertEqual( + ctx_18_1.get_outbound_type(), CONST_OUTBOUND_TYPE_USER_ASSIGNED_NAT_GATEWAY + ) + # update to UDR on a managed VNet cluster (no subnet) should fail with a clear error, # not ask for --vnet-subnet-id (which is not registered for 'aks update') ctx_19 = AKSManagedClusterContext(