diff --git a/src/aks-preview/azext_aks_preview/decorator.py b/src/aks-preview/azext_aks_preview/decorator.py index 21695b2a8d7..6fd41cdb82f 100644 --- a/src/aks-preview/azext_aks_preview/decorator.py +++ b/src/aks-preview/azext_aks_preview/decorator.py @@ -4,6 +4,7 @@ # -------------------------------------------------------------------------------------------- import os +import time from typing import Dict, TypeVar, Union from azure.cli.command_modules.acs._consts import DecoratorMode @@ -25,8 +26,12 @@ from azure.cli.core.profiles import ResourceType from azure.cli.core.util import get_file_json from knack.log import get_logger +from msrestazure.azure_exceptions import CloudError from azext_aks_preview._natgateway import create_nat_gateway_profile +from azext_aks_preview.addonconfiguration import ( + ensure_container_insights_for_monitoring, +) logger = get_logger(__name__) @@ -40,6 +45,7 @@ LinuxOSConfig = TypeVar("LinuxOSConfig") ManagedClusterHTTPProxyConfig = TypeVar("ManagedClusterHTTPProxyConfig") ContainerServiceNetworkProfile = TypeVar("ContainerServiceNetworkProfile") +ManagedClusterAddonProfile = TypeVar("ManagedClusterAddonProfile") # pylint: disable=too-many-instance-attributes,too-few-public-methods @@ -390,7 +396,7 @@ def _get_enable_managed_identity( ) -> bool: """Internal function to obtain the value of enable_pod_identity. - Inherited and extended to perform additional validation. + Note: Inherited and extended in aks-preview to perform additional validation. This function supports the option of enable_validation. When enabled, if enable_managed_identity is not specified but enable_pod_identity is, raise a RequiredArgumentMissingError. @@ -496,6 +502,152 @@ def get_enable_pod_identity_with_kubenet(self) -> bool: """ return self._get_enable_pod_identity_with_kubenet(enable_validation=True) + def get_addon_consts(self) -> Dict[str, str]: + """Helper function to obtain the constants used by addons. + + Note: Inherited and extended in aks-preview to replace and add a few values. + + Note: This is not a parameter of aks commands. + + :return: dict + """ + from azext_aks_preview._consts import ( + ADDONS, + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME, + CONST_GITOPS_ADDON_NAME, + CONST_SECRET_ROTATION_ENABLED, + CONST_MONITORING_USING_AAD_MSI_AUTH, + ) + + addon_consts = super().get_addon_consts() + addon_consts["ADDONS"] = ADDONS + addon_consts["CONST_GITOPS_ADDON_NAME"] = CONST_GITOPS_ADDON_NAME + addon_consts[ + "CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME" + ] = CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME + addon_consts[ + "CONST_SECRET_ROTATION_ENABLED" + ] = CONST_SECRET_ROTATION_ENABLED + addon_consts[ + "CONST_MONITORING_USING_AAD_MSI_AUTH" + ] = CONST_MONITORING_USING_AAD_MSI_AUTH + return addon_consts + + def get_appgw_subnet_prefix(self) -> Union[str, None]: + """Obtain the value of appgw_subnet_prefix. + + [Deprecated] Note: this parameter is depracated and replaced by appgw_subnet_cidr. + + :return: string or None + """ + # determine the value of constants + addon_consts = self.get_addon_consts() + CONST_INGRESS_APPGW_ADDON_NAME = addon_consts.get("CONST_INGRESS_APPGW_ADDON_NAME") + CONST_INGRESS_APPGW_SUBNET_CIDR = addon_consts.get("CONST_INGRESS_APPGW_SUBNET_CIDR") + + # read the original value passed by the command + appgw_subnet_prefix = self.raw_param.get("appgw_subnet_prefix") + # try to read the property value corresponding to the parameter from the `mc` object + if ( + self.mc and + self.mc.addon_profiles and + CONST_INGRESS_APPGW_ADDON_NAME in self.mc.addon_profiles and + self.mc.addon_profiles.get( + CONST_INGRESS_APPGW_ADDON_NAME + ).config.get(CONST_INGRESS_APPGW_SUBNET_CIDR) is not None + ): + appgw_subnet_prefix = self.mc.addon_profiles.get( + CONST_INGRESS_APPGW_ADDON_NAME + ).config.get(CONST_INGRESS_APPGW_SUBNET_CIDR) + + # this parameter does not need dynamic completion + # this parameter does not need validation + return appgw_subnet_prefix + + def get_enable_msi_auth_for_monitoring(self) -> Union[bool, None]: + """Obtain the value of enable_msi_auth_for_monitoring. + + Note: The arg type of this parameter supports three states (True, False or None), but the corresponding default + value in entry function is not None. + + :return: bool or None + """ + # determine the value of constants + addon_consts = self.get_addon_consts() + CONST_MONITORING_ADDON_NAME = addon_consts.get("CONST_MONITORING_ADDON_NAME") + CONST_MONITORING_USING_AAD_MSI_AUTH = addon_consts.get("CONST_MONITORING_USING_AAD_MSI_AUTH") + + # read the original value passed by the command + enable_msi_auth_for_monitoring = self.raw_param.get("enable_msi_auth_for_monitoring") + # try to read the property value corresponding to the parameter from the `mc` object + if ( + self.mc and + self.mc.addon_profiles and + CONST_MONITORING_ADDON_NAME in self.mc.addon_profiles and + self.mc.addon_profiles.get( + CONST_MONITORING_ADDON_NAME + ).config.get(CONST_MONITORING_USING_AAD_MSI_AUTH) is not None + ): + enable_msi_auth_for_monitoring = self.mc.addon_profiles.get( + CONST_MONITORING_ADDON_NAME + ).config.get(CONST_MONITORING_USING_AAD_MSI_AUTH) + + # this parameter does not need dynamic completion + # this parameter does not need validation + return enable_msi_auth_for_monitoring + + def get_no_wait(self) -> bool: + """Obtain the value of no_wait. + + Note: Inherited and extended in aks-preview to replace the set value when enable_msi_auth_for_monitoring is + specified. + + Note: no_wait will not be decorated into the `mc` object. + + :return: bool + """ + no_wait = super().get_no_wait() + + if self.get_intermediate("monitoring") and self.get_enable_msi_auth_for_monitoring(): + logger.warning("Enabling msi auth for monitoring addon requires waiting for cluster creation to complete") + if no_wait: + logger.warning("The set option '--no-wait' has been ignored") + no_wait = False + return no_wait + + def get_enable_secret_rotation(self) -> bool: + """Obtain the value of enable_secret_rotation. + + :return: bool + """ + # determine the value of constants + addon_consts = self.get_addon_consts() + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME = addon_consts.get( + "CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME" + ) + CONST_SECRET_ROTATION_ENABLED = addon_consts.get( + "CONST_SECRET_ROTATION_ENABLED" + ) + + # read the original value passed by the command + enable_secret_rotation = self.raw_param.get("enable_secret_rotation") + # try to read the property value corresponding to the parameter from the `mc` object + if ( + self.mc and + self.mc.addon_profiles and + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME in self.mc.addon_profiles and + self.mc.addon_profiles.get( + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME + ).config.get(CONST_SECRET_ROTATION_ENABLED) is not None + ): + enable_secret_rotation = self.mc.addon_profiles.get( + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME + ).config.get(CONST_SECRET_ROTATION_ENABLED) == "true" + + # this parameter does not need dynamic completion + # this parameter does not need validation + return enable_secret_rotation + class AKSPreviewCreateDecorator(AKSCreateDecorator): # pylint: disable=super-init-not-called @@ -528,8 +680,7 @@ def __init__( def set_up_agent_pool_profiles(self, mc: ManagedCluster) -> ManagedCluster: """Set up agent pool profiles for the ManagedCluster object. - Call the method of the same name in the parent class to set up agent_pool_profiles, and then set some additional - properties on this basis. + Note: Inherited and extended in aks-preview to set some additional properties. :return: the ManagedCluster object """ @@ -578,8 +729,7 @@ def set_up_node_resource_group(self, mc: ManagedCluster) -> ManagedCluster: def set_up_network_profile(self, mc: ManagedCluster) -> ManagedCluster: """Set up network profile for the ManagedCluster object. - Call the method of the same name in the parent class to set up network_profile, and then set the - nat_gateway_profile on this basis. + Note: Inherited and extended in aks-preview to set the nat_gateway_profile. :return: the ManagedCluster object """ @@ -635,6 +785,126 @@ def set_up_pod_identity_profile(self, mc: ManagedCluster) -> ManagedCluster: mc.pod_identity_profile = pod_identity_profile return mc + def build_monitoring_addon_profile(self) -> ManagedClusterAddonProfile: + """Build monitoring addon profile. + + Note: Overwritten in aks-preview. + + :return: a ManagedClusterAddonProfile object + """ + # determine the value of constants + addon_consts = self.context.get_addon_consts() + CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID = addon_consts.get( + "CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID" + ) + CONST_MONITORING_USING_AAD_MSI_AUTH = addon_consts.get( + "CONST_MONITORING_USING_AAD_MSI_AUTH" + ) + + monitoring_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID: self.context.get_workspace_resource_id(), + CONST_MONITORING_USING_AAD_MSI_AUTH: self.context.get_enable_msi_auth_for_monitoring(), + }, + ) + # post-process, create a deployment + ensure_container_insights_for_monitoring( + self.cmd, + monitoring_addon_profile, + self.context.get_subscription_id(), + self.context.get_resource_group_name(), + self.context.get_name(), + self.context.get_location(), + remove_monitoring=False, + aad_route=self.context.get_enable_msi_auth_for_monitoring(), + create_dcr=True, + create_dcra=False, + ) + # set intermediate + self.context.set_intermediate("monitoring", True, overwrite_exists=True) + return monitoring_addon_profile + + def build_ingress_appgw_addon_profile(self) -> ManagedClusterAddonProfile: + """Build ingress appgw addon profile. + + Note: Inherited and extended in aks-preview to support option appgw_subnet_prefix. + + :return: a ManagedClusterAddonProfile object + """ + # determine the value of constants + addon_consts = self.context.get_addon_consts() + CONST_INGRESS_APPGW_SUBNET_CIDR = addon_consts.get( + "CONST_INGRESS_APPGW_SUBNET_CIDR" + ) + + ingress_appgw_addon_profile = super().build_ingress_appgw_addon_profile() + appgw_subnet_prefix = self.context.get_appgw_subnet_prefix() + if ( + appgw_subnet_prefix is not None and + ingress_appgw_addon_profile.config.get( + CONST_INGRESS_APPGW_SUBNET_CIDR + ) + is None + ): + ingress_appgw_addon_profile.config[CONST_INGRESS_APPGW_SUBNET_CIDR] = appgw_subnet_prefix + return ingress_appgw_addon_profile + + def build_azure_keyvault_secrets_provider_addon_profile(self) -> ManagedClusterAddonProfile: + """Build azure keyvault secrets provider addon profile. + + :return: a ManagedClusterAddonProfile object + """ + # determine the value of constants + addon_consts = self.context.get_addon_consts() + CONST_SECRET_ROTATION_ENABLED = addon_consts.get( + "CONST_SECRET_ROTATION_ENABLED" + ) + + azure_keyvault_secrets_provider_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, config={CONST_SECRET_ROTATION_ENABLED: "false"} + ) + if self.context.get_enable_secret_rotation(): + azure_keyvault_secrets_provider_addon_profile.config[CONST_SECRET_ROTATION_ENABLED] = "true" + return azure_keyvault_secrets_provider_addon_profile + + def build_gitops_addon_profile(self) -> ManagedClusterAddonProfile: + """Build gitops addon profile. + + :return: a ManagedClusterAddonProfile object + """ + gitops_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, + ) + return gitops_addon_profile + + def set_up_addon_profiles(self, mc: ManagedCluster) -> ManagedCluster: + """Set up addon profiles for the ManagedCluster object. + + Note: Inherited and extended in aks-preview to set some extra addons. + + :return: the ManagedCluster object + """ + addon_consts = self.context.get_addon_consts() + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME = addon_consts.get( + "CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME" + ) + CONST_GITOPS_ADDON_NAME = addon_consts.get("CONST_GITOPS_ADDON_NAME") + + mc = super().set_up_addon_profiles(mc) + addon_profiles = mc.addon_profiles + addons = self.context.get_enable_addons() + if "azure-keyvault-secrets-provider" in addons: + addon_profiles[ + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME + ] = self.build_azure_keyvault_secrets_provider_addon_profile() + if "gitops" in addons: + addon_profiles[ + CONST_GITOPS_ADDON_NAME + ] = self.build_gitops_addon_profile() + mc.addon_profiles = addon_profiles + return mc + def construct_preview_mc_profile(self) -> ManagedCluster: """The overall controller used to construct the preview ManagedCluster profile. @@ -655,6 +925,48 @@ def construct_preview_mc_profile(self) -> ManagedCluster: mc = self.set_up_pod_identity_profile(mc) return mc + def create_mc(self, mc: ManagedCluster) -> ManagedCluster: + """Send request to create a real managed cluster. + + Note: Inherited and extended in aks-preview to create dcr association for monitoring addon if + enable_msi_auth_for_monitoring is specified after cluster is created. + + :return: the ManagedCluster object + """ + created_cluster = super().create_mc(mc) + + # determine the value of constants + addon_consts = self.context.get_addon_consts() + CONST_MONITORING_ADDON_NAME = addon_consts.get("CONST_MONITORING_ADDON_NAME") + + # Due to SPN replication latency, we do a few retries here + max_retry = 30 + retry_exception = Exception(None) + for _ in range(0, max_retry): + try: + if self.context.get_intermediate("monitoring") and self.context.get_enable_msi_auth_for_monitoring(): + # Create the DCR Association here + ensure_container_insights_for_monitoring( + self.cmd, + mc.addon_profiles[CONST_MONITORING_ADDON_NAME], + self.context.get_subscription_id(), + self.context.get_resource_group_name(), + self.context.get_name(), + self.context.get_location(), + remove_monitoring=False, + aad_route=self.context.get_enable_msi_auth_for_monitoring(), + create_dcr=False, + create_dcra=True, + ) + return created_cluster + except CloudError as ex: + retry_exception = ex + if 'not found in Active Directory tenant' in ex.message: + time.sleep(3) + else: + raise ex + raise retry_exception + class AKSPreviewUpdateDecorator(AKSUpdateDecorator): # pylint: disable=super-init-not-called diff --git a/src/aks-preview/azext_aks_preview/tests/latest/test_decorator.py b/src/aks-preview/azext_aks_preview/tests/latest/test_decorator.py index 1f24ba88a2c..a97d31361a2 100644 --- a/src/aks-preview/azext_aks_preview/tests/latest/test_decorator.py +++ b/src/aks-preview/azext_aks_preview/tests/latest/test_decorator.py @@ -5,6 +5,7 @@ import importlib import unittest +from unittest.mock import patch from azext_aks_preview.__init__ import register_aks_preview_resource_type from azext_aks_preview._client_factory import CUSTOM_MGMT_AKS_PREVIEW @@ -26,6 +27,29 @@ RequiredArgumentMissingError, UnknownError, ) +from azext_aks_preview._consts import ( + ADDONS, + CONST_ACC_SGX_QUOTE_HELPER_ENABLED, + CONST_AZURE_POLICY_ADDON_NAME, + CONST_CONFCOM_ADDON_NAME, + CONST_HTTP_APPLICATION_ROUTING_ADDON_NAME, + CONST_INGRESS_APPGW_ADDON_NAME, + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_ID, + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_NAME, + CONST_INGRESS_APPGW_SUBNET_CIDR, + CONST_INGRESS_APPGW_SUBNET_ID, + CONST_INGRESS_APPGW_WATCH_NAMESPACE, + CONST_KUBE_DASHBOARD_ADDON_NAME, + CONST_MONITORING_ADDON_NAME, + CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID, + CONST_OPEN_SERVICE_MESH_ADDON_NAME, + CONST_VIRTUAL_NODE_ADDON_NAME, + CONST_VIRTUAL_NODE_SUBNET_NAME, + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME, + CONST_GITOPS_ADDON_NAME, + CONST_SECRET_ROTATION_ENABLED, + CONST_MONITORING_USING_AAD_MSI_AUTH, +) class AKSPreviewModelsTestCase(unittest.TestCase): @@ -464,6 +488,127 @@ def test_get_enable_pod_identity_with_kubenet(self): ctx_2.get_enable_pod_identity_with_kubenet(), False ) + def test_get_addon_consts(self): + # default + ctx_1 = AKSPreviewContext( + self.cmd, + {}, + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + addon_consts = ctx_1.get_addon_consts() + ground_truth_addon_consts = { + "ADDONS": ADDONS, + "CONST_ACC_SGX_QUOTE_HELPER_ENABLED": CONST_ACC_SGX_QUOTE_HELPER_ENABLED, + "CONST_AZURE_POLICY_ADDON_NAME": CONST_AZURE_POLICY_ADDON_NAME, + "CONST_CONFCOM_ADDON_NAME": CONST_CONFCOM_ADDON_NAME, + "CONST_HTTP_APPLICATION_ROUTING_ADDON_NAME": CONST_HTTP_APPLICATION_ROUTING_ADDON_NAME, + "CONST_INGRESS_APPGW_ADDON_NAME": CONST_INGRESS_APPGW_ADDON_NAME, + "CONST_INGRESS_APPGW_APPLICATION_GATEWAY_ID": CONST_INGRESS_APPGW_APPLICATION_GATEWAY_ID, + "CONST_INGRESS_APPGW_APPLICATION_GATEWAY_NAME": CONST_INGRESS_APPGW_APPLICATION_GATEWAY_NAME, + "CONST_INGRESS_APPGW_SUBNET_CIDR": CONST_INGRESS_APPGW_SUBNET_CIDR, + "CONST_INGRESS_APPGW_SUBNET_ID": CONST_INGRESS_APPGW_SUBNET_ID, + "CONST_INGRESS_APPGW_WATCH_NAMESPACE": CONST_INGRESS_APPGW_WATCH_NAMESPACE, + "CONST_KUBE_DASHBOARD_ADDON_NAME": CONST_KUBE_DASHBOARD_ADDON_NAME, + "CONST_MONITORING_ADDON_NAME": CONST_MONITORING_ADDON_NAME, + "CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID": CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID, + "CONST_OPEN_SERVICE_MESH_ADDON_NAME": CONST_OPEN_SERVICE_MESH_ADDON_NAME, + "CONST_VIRTUAL_NODE_ADDON_NAME": CONST_VIRTUAL_NODE_ADDON_NAME, + "CONST_VIRTUAL_NODE_SUBNET_NAME": CONST_VIRTUAL_NODE_SUBNET_NAME, + "CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME": CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME, + "CONST_GITOPS_ADDON_NAME": CONST_GITOPS_ADDON_NAME, + "CONST_SECRET_ROTATION_ENABLED": CONST_SECRET_ROTATION_ENABLED, + "CONST_MONITORING_USING_AAD_MSI_AUTH": CONST_MONITORING_USING_AAD_MSI_AUTH, + } + self.assertEqual(addon_consts, ground_truth_addon_consts) + + def test_get_appgw_subnet_prefix(self): + # default + ctx_1 = AKSPreviewContext( + self.cmd, + { + "appgw_subnet_prefix": None, + }, + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_1.get_appgw_subnet_prefix(), None) + addon_profiles_1 = { + CONST_INGRESS_APPGW_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_INGRESS_APPGW_SUBNET_CIDR: "test_appgw_subnet_prefix" + }, + ) + } + mc = self.models.ManagedCluster( + location="test_location", addon_profiles=addon_profiles_1 + ) + ctx_1.attach_mc(mc) + self.assertEqual( + ctx_1.get_appgw_subnet_prefix(), "test_appgw_subnet_prefix" + ) + + def test_get_enable_msi_auth_for_monitoring(self): + # default + ctx_1 = AKSPreviewContext( + self.cmd, + { + "enable_msi_auth_for_monitoring": False, + }, + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_1.get_enable_msi_auth_for_monitoring(), False) + addon_profiles_1 = { + CONST_MONITORING_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + config={CONST_MONITORING_USING_AAD_MSI_AUTH: True}, + ) + } + mc = self.models.ManagedCluster( + location="test_location", addon_profiles=addon_profiles_1 + ) + ctx_1.attach_mc(mc) + self.assertEqual(ctx_1.get_enable_msi_auth_for_monitoring(), True) + + def test_get_no_wait(self): + # custom value + ctx_1 = AKSPreviewContext( + self.cmd, + { + "no_wait": True, + "enable_msi_auth_for_monitoring": True, + }, + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + ctx_1.set_intermediate("monitoring", True, overwrite_exists=True) + self.assertEqual(ctx_1.get_no_wait(), False) + + def test_get_enable_secret_rotation(self): + # default + ctx_1 = AKSPreviewContext( + self.cmd, + { + "enable_secret_rotation": False, + }, + self.models, + decorator_mode=DecoratorMode.CREATE, + ) + self.assertEqual(ctx_1.get_enable_secret_rotation(), False) + addon_profiles_1 = { + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + config={CONST_SECRET_ROTATION_ENABLED: "true"}, + ) + } + mc = self.models.ManagedCluster( + location="test_location", addon_profiles=addon_profiles_1 + ) + ctx_1.attach_mc(mc) + self.assertEqual(ctx_1.get_enable_secret_rotation(), True) + class AKSPreviewCreateDecoratorTestCase(unittest.TestCase): def setUp(self): @@ -873,21 +1018,358 @@ def test_set_up_pod_identity_profile(self): location="test_location", network_profile=network_profile_2 ) dec_mc_2 = dec_2.set_up_pod_identity_profile(mc_2) - ground_truth_network_profile_2 = ( - self.models.ContainerServiceNetworkProfile(network_plugin="kubenet") + network_profile_2 = self.models.ContainerServiceNetworkProfile( + network_plugin="kubenet" + ) + pod_identity_profile_2 = self.models.ManagedClusterPodIdentityProfile( + enabled=True, + allow_network_plugin_kubenet=True, + ) + ground_truth_mc_2 = self.models.ManagedCluster( + location="test_location", + network_profile=network_profile_2, + pod_identity_profile=pod_identity_profile_2, + ) + self.assertEqual(dec_mc_2, ground_truth_mc_2) + + def test_build_monitoring_addon_profile(self): + # default + dec_1 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + { + "resource_group_name": "test_rg_name", + "name": "test_name", + "location": "test_location", + "enable_addons": "monitoring", + "workspace_resource_id": "test_workspace_resource_id", + "enable_msi_auth_for_monitoring": False, + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + dec_1.context.set_intermediate( + "subscription_id", "test_subscription_id" + ) + + with patch( + "azext_aks_preview.decorator.ensure_container_insights_for_monitoring", + return_value=None, + ): + self.assertEqual(dec_1.context.get_intermediate("monitoring"), None) + monitoring_addon_profile = dec_1.build_monitoring_addon_profile() + ground_truth_monitoring_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID: "/test_workspace_resource_id", + CONST_MONITORING_USING_AAD_MSI_AUTH: False, + }, + ) + self.assertEqual( + monitoring_addon_profile, ground_truth_monitoring_addon_profile + ) + self.assertEqual(dec_1.context.get_intermediate("monitoring"), True) + + # custom value + dec_2 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + { + "resource_group_name": "test_rg_name", + "name": "test_name", + "location": "test_location", + "enable_addons": "monitoring", + "workspace_resource_id": "test_workspace_resource_id", + "enable_msi_auth_for_monitoring": True, + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + dec_2.context.set_intermediate( + "subscription_id", "test_subscription_id" + ) + + with patch( + "azext_aks_preview.decorator.ensure_container_insights_for_monitoring", + return_value=None, + ): + self.assertEqual(dec_2.context.get_intermediate("monitoring"), None) + monitoring_addon_profile = dec_2.build_monitoring_addon_profile() + ground_truth_monitoring_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID: "/test_workspace_resource_id", + CONST_MONITORING_USING_AAD_MSI_AUTH: True, + }, + ) + self.assertEqual( + monitoring_addon_profile, ground_truth_monitoring_addon_profile + ) + self.assertEqual(dec_2.context.get_intermediate("monitoring"), True) + + def test_build_ingress_appgw_addon_profile(self): + # default + dec_1 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + {}, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + self.assertEqual( + dec_1.context.get_intermediate("ingress_appgw_addon_enabled"), None + ) + ingress_appgw_addon_profile = dec_1.build_ingress_appgw_addon_profile() + ground_truth_ingress_appgw_addon_profile = ( + self.models.ManagedClusterAddonProfile( + enabled=True, + config={}, + ) + ) + self.assertEqual( + ingress_appgw_addon_profile, + ground_truth_ingress_appgw_addon_profile, + ) + self.assertEqual( + dec_1.context.get_intermediate("ingress_appgw_addon_enabled"), True ) - ground_truth_pod_identity_profile_2 = ( - self.models.ManagedClusterPodIdentityProfile( + + # custom value + dec_2 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + { + "appgw_name": "test_appgw_name", + "appgw_subnet_prefix": "test_appgw_subnet_prefix", + "appgw_id": "test_appgw_id", + "appgw_subnet_id": "test_appgw_subnet_id", + "appgw_watch_namespace": "test_appgw_watch_namespace", + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + self.assertEqual( + dec_2.context.get_intermediate("ingress_appgw_addon_enabled"), None + ) + ingress_appgw_addon_profile = dec_2.build_ingress_appgw_addon_profile() + ground_truth_ingress_appgw_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_NAME: "test_appgw_name", + CONST_INGRESS_APPGW_SUBNET_CIDR: "test_appgw_subnet_prefix", + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_ID: "test_appgw_id", + CONST_INGRESS_APPGW_SUBNET_ID: "test_appgw_subnet_id", + CONST_INGRESS_APPGW_WATCH_NAMESPACE: "test_appgw_watch_namespace", + }, + ) + self.assertEqual( + ingress_appgw_addon_profile, + ground_truth_ingress_appgw_addon_profile, + ) + self.assertEqual( + dec_2.context.get_intermediate("ingress_appgw_addon_enabled"), True + ) + + # custom value + dec_3 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + { + "appgw_name": "test_appgw_name", + "appgw_subnet_prefix": "test_appgw_subnet_prefix", + "appgw_subnet_cidr": "test_appgw_subnet_cidr", + "appgw_id": "test_appgw_id", + "appgw_subnet_id": "test_appgw_subnet_id", + "appgw_watch_namespace": "test_appgw_watch_namespace", + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + self.assertEqual( + dec_3.context.get_intermediate("ingress_appgw_addon_enabled"), None + ) + ingress_appgw_addon_profile = dec_3.build_ingress_appgw_addon_profile() + ground_truth_ingress_appgw_addon_profile = self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_NAME: "test_appgw_name", + CONST_INGRESS_APPGW_SUBNET_CIDR: "test_appgw_subnet_cidr", + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_ID: "test_appgw_id", + CONST_INGRESS_APPGW_SUBNET_ID: "test_appgw_subnet_id", + CONST_INGRESS_APPGW_WATCH_NAMESPACE: "test_appgw_watch_namespace", + }, + ) + self.assertEqual( + ingress_appgw_addon_profile, + ground_truth_ingress_appgw_addon_profile, + ) + self.assertEqual( + dec_3.context.get_intermediate("ingress_appgw_addon_enabled"), True + ) + + def test_build_azure_keyvault_secrets_provider_addon_profile(self): + # default + dec_1 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + {"enable_secret_rotation": False}, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + azure_keyvault_secrets_provider_addon_profile = dec_1.build_azure_keyvault_secrets_provider_addon_profile() + ground_truth_azure_keyvault_secrets_provider_addon_profile = ( + self.models.ManagedClusterAddonProfile( + enabled=True, + config={CONST_SECRET_ROTATION_ENABLED: "false"} + ) + ) + self.assertEqual( + azure_keyvault_secrets_provider_addon_profile, ground_truth_azure_keyvault_secrets_provider_addon_profile + ) + + # custom value + dec_2 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + {"enable_secret_rotation": True}, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + azure_keyvault_secrets_provider_addon_profile = dec_2.build_azure_keyvault_secrets_provider_addon_profile() + ground_truth_azure_keyvault_secrets_provider_addon_profile = ( + self.models.ManagedClusterAddonProfile( enabled=True, - allow_network_plugin_kubenet=True, + config={CONST_SECRET_ROTATION_ENABLED: "true"} ) ) + self.assertEqual( + azure_keyvault_secrets_provider_addon_profile, ground_truth_azure_keyvault_secrets_provider_addon_profile + ) + + def test_build_gitops_addon_profile(self): + # default + dec_1 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + {}, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + gitops_addon_profile = dec_1.build_gitops_addon_profile() + ground_truth_gitops_addon_profile = ( + self.models.ManagedClusterAddonProfile( + enabled=True, + ) + ) + self.assertEqual( + gitops_addon_profile, ground_truth_gitops_addon_profile + ) + + def test_set_up_addon_profiles(self): + # default value in `aks_create` + dec_1 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + { + "enable_addons": None, + "workspace_resource_id": None, + "aci_subnet_name": None, + "appgw_name": None, + "appgw_subnet_cidr": None, + "appgw_id": None, + "appgw_subnet_id": None, + "appgw_watch_namespace": None, + "enable_sgxquotehelper": False, + "appgw_subnet_prefix": None, + "enable_msi_auth_for_monitoring": False, + "enable_secret_rotation": False, + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + + mc_1 = self.models.ManagedCluster(location="test_location") + # fail on passing the wrong mc object + with self.assertRaises(CLIInternalError): + dec_1.set_up_addon_profiles(None) + dec_mc_1 = dec_1.set_up_addon_profiles(mc_1) + ground_truth_mc_1 = self.models.ManagedCluster( + location="test_location", addon_profiles={} + ) + self.assertEqual(dec_mc_1, ground_truth_mc_1) + self.assertEqual(dec_1.context.get_intermediate("monitoring"), None) + self.assertEqual( + dec_1.context.get_intermediate("enable_virtual_node"), None + ) + self.assertEqual( + dec_1.context.get_intermediate("ingress_appgw_addon_enabled"), None + ) + + # custom value + dec_2 = AKSPreviewCreateDecorator( + self.cmd, + self.client, + { + "name": "test_name", + "resource_group_name": "test_rg_name", + "location": "test_location", + "vnet_subnet_id": "test_vnet_subnet_id", + "enable_addons": "monitoring,ingress-appgw,gitops,azure-keyvault-secrets-provider", + "workspace_resource_id": "test_workspace_resource_id", + "enable_msi_auth_for_monitoring": True, + "appgw_name": "test_appgw_name", + "appgw_subnet_prefix": "test_appgw_subnet_prefix", + "appgw_id": "test_appgw_id", + "appgw_subnet_id": "test_appgw_subnet_id", + "appgw_watch_namespace": "test_appgw_watch_namespace", + "enable_secret_rotation": True, + }, + CUSTOM_MGMT_AKS_PREVIEW, + ) + dec_2.context.set_intermediate( + "subscription_id", "test_subscription_id" + ) + mc_2 = self.models.ManagedCluster(location="test_location") + with patch( + "azext_aks_preview.decorator.ensure_container_insights_for_monitoring", + return_value=None, + ): + dec_mc_2 = dec_2.set_up_addon_profiles(mc_2) + + addon_profiles_2 = { + CONST_MONITORING_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_MONITORING_LOG_ANALYTICS_WORKSPACE_RESOURCE_ID: "/test_workspace_resource_id", + CONST_MONITORING_USING_AAD_MSI_AUTH: True, + }, + ), + CONST_INGRESS_APPGW_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + config={ + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_NAME: "test_appgw_name", + CONST_INGRESS_APPGW_APPLICATION_GATEWAY_ID: "test_appgw_id", + CONST_INGRESS_APPGW_SUBNET_ID: "test_appgw_subnet_id", + CONST_INGRESS_APPGW_SUBNET_CIDR: "test_appgw_subnet_prefix", + CONST_INGRESS_APPGW_WATCH_NAMESPACE: "test_appgw_watch_namespace", + }, + ), + CONST_AZURE_KEYVAULT_SECRETS_PROVIDER_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + config={CONST_SECRET_ROTATION_ENABLED: "true"}, + ), + CONST_GITOPS_ADDON_NAME: self.models.ManagedClusterAddonProfile( + enabled=True, + ), + } ground_truth_mc_2 = self.models.ManagedCluster( - location="test_location", - network_profile=ground_truth_network_profile_2, - pod_identity_profile=ground_truth_pod_identity_profile_2, + location="test_location", addon_profiles=addon_profiles_2 ) self.assertEqual(dec_mc_2, ground_truth_mc_2) + self.assertEqual(dec_2.context.get_intermediate("monitoring"), True) + self.assertEqual( + dec_2.context.get_intermediate("enable_virtual_node"), None + ) + self.assertEqual( + dec_2.context.get_intermediate("ingress_appgw_addon_enabled"), True + ) def test_construct_preview_mc_profile(self): pass