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..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 @@ -2391,6 +2391,71 @@ 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 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( + 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(