diff --git a/src/connectedk8s/HISTORY.rst b/src/connectedk8s/HISTORY.rst index fbec032acd8..7f9790f9e45 100644 --- a/src/connectedk8s/HISTORY.rst +++ b/src/connectedk8s/HISTORY.rst @@ -2,6 +2,13 @@ Release History =============== +1.3.10 +++++++ + +* Added CLI heuristics change +* Added AKS IOT infra support +* Bug Fix in precheckutils + 1.3.9 ++++++ diff --git a/src/connectedk8s/azext_connectedk8s/_constants.py b/src/connectedk8s/azext_connectedk8s/_constants.py index 03f327f262d..067a5bdb2ed 100644 --- a/src/connectedk8s/azext_connectedk8s/_constants.py +++ b/src/connectedk8s/azext_connectedk8s/_constants.py @@ -6,8 +6,8 @@ # pylint: disable=line-too-long -Distribution_Enum_Values = ["auto", "generic", "openshift", "rancher_rke", "kind", "k3s", "minikube", "gke", "eks", "aks", "aks_management", "aks_workload", "capz", "aks_engine", "tkg", "canonical", "karbon"] -Infrastructure_Enum_Values = ["auto", "generic", "azure", "aws", "gcp", "azure_stack_hci", "azure_stack_hub", "azure_stack_edge", "vsphere", "windows_server"] +Distribution_Enum_Values = ["generic", "openshift", "rancher_rke", "kind", "k3s", "minikube", "gke", "eks", "aks", "aks_management", "aks_workload", "capz", "aks_engine", "tkg", "canonical", "karbon", "aks_edge_k3s", "aks_edge_k8s"] +Infrastructure_Enum_Values = ["generic", "azure", "aws", "gcp", "azure_stack_hci", "azure_stack_hub", "azure_stack_edge", "vsphere", "windows_server", "Windows 11 Enterprise", "Windows 11 Enterprise N", "Windows 11 IoT Enterprise", "Windows 11 Pro", "Windows 10 Enterprise", "Windows 10 Enterprise N", "Windows 10 Enterprise LTSC 2021", "Windows 10 Enterprise N LTSC 2021", "Windows 10 IoT Enterprise", "Windows 10 IoT Enterprise LTSC 2021", "Windows 10 Pro", "Windows 10 Enterprise LTSC 2019", "Windows 10 Enterprise N LTSC 2019", "Windows 10 IoT Enterprise LTSC 2019", "Windows Server 2022", "Windows Server 2022 Datacenter", "Windows Server 2022 Standard", "Windows Server 2019", "Windows Server 2019 Datacenter", "Windows Server 2019 Standard"] AHB_Enum_Values = ["True", "False", "NotApplicable"] Feature_Values = ["cluster-connect", "azure-rbac", "custom-locations"] CRD_FOR_FORCE_DELETE = ["arccertificates.clusterconfig.azure.com", "azureclusteridentityrequests.clusterconfig.azure.com", "azureextensionidentities.clusterconfig.azure.com", "connectedclusters.arc.azure.com", "customlocationsettings.clusterconfig.azure.com", "extensionconfigs.clusterconfig.azure.com", "gitconfigs.clusterconfig.azure.com"] diff --git a/src/connectedk8s/azext_connectedk8s/_precheckutils.py b/src/connectedk8s/azext_connectedk8s/_precheckutils.py index a44a03fe836..4a18716d0a1 100644 --- a/src/connectedk8s/azext_connectedk8s/_precheckutils.py +++ b/src/connectedk8s/azext_connectedk8s/_precheckutils.py @@ -26,6 +26,7 @@ from azext_connectedk8s._client_factory import _resource_client_factory, _resource_providers_client import azext_connectedk8s._constants as consts import azext_connectedk8s._utils as azext_utils +import azext_connectedk8s.custom as custom from kubernetes import client as kube_client from azure.cli.core import get_default_cli from azure.cli.core.azclierror import CLIInternalError, ClientRequestError, ArgumentUsageError, ManualInterrupt, AzureResponseError, AzureInternalError, ValidationError @@ -96,8 +97,8 @@ def executing_cluster_diagnostic_checks_job(corev1_api_instance, batchv1_api_ins job_name = "cluster-diagnostic-checks-job" # Setting the log output as Empty cluster_diagnostic_checks_container_log = "" - - cmd_helm_delete = [helm_client_location, "uninstall", "cluster-diagnostic-checks", "-n", "azure-arc-release"] + release_namespace = azext_utils.get_release_namespace(kube_config, kube_context, helm_client_location, "cluster-diagnostic-checks") + cmd_helm_delete = [helm_client_location, "delete", "cluster-diagnostic-checks", "-n", "azure-arc-release"] if kube_config: cmd_helm_delete.extend(["--kubeconfig", kube_config]) if kube_context: @@ -107,28 +108,30 @@ def executing_cluster_diagnostic_checks_job(corev1_api_instance, batchv1_api_ins try: # Executing the cluster diagnostic checks job yaml config.load_kube_config(kube_config, kube_context) - # Attempting deletion of cluster diagnostic checks resources to handle the scenario if any stale resources are present - response_kubectl_delete_helm = Popen(cmd_helm_delete, stdout=PIPE, stderr=PIPE) - output_kubectl_delete_helm, error_kubectl_delete_helm = response_kubectl_delete_helm.communicate() - # If any error occured while execution of delete command - if (response_kubectl_delete_helm != 0): - # Converting the string of multiple errors to list - error_msg_list = error_kubectl_delete_helm.decode("ascii").split("\n") - error_msg_list.pop(-1) - valid_exception_list = [] - # Checking if any exception occured or not - exception_occured_counter = 0 - for ind_errors in error_msg_list: - if('not found' in ind_errors or 'deleted' in ind_errors): - pass - else: - valid_exception_list.append(ind_errors) - exception_occured_counter = 1 - # If any exception occured we will print the exception and return - if exception_occured_counter == 1: - logger.warning("Cleanup of previous diagnostic checks helm release failed and hence couldn't install the new helm release. Please cleanup older release using \"helm delete cluster-diagnostic-checks -n azuer-arc-release\" and try onboarding again") - telemetry.set_exception(exception=error_kubectl_delete_helm.decode("ascii"), fault_type=consts.Cluster_Diagnostic_Checks_Release_Cleanup_Failed, summary="Error while executing cluster diagnostic checks Job") - return + # checking existence of the release and if present we delete the stale release + if release_namespace is not None: + # Attempting deletion of cluster diagnostic checks resources to handle the scenario if any stale resources are present + response_kubectl_delete_helm = Popen(cmd_helm_delete, stdout=PIPE, stderr=PIPE) + output_kubectl_delete_helm, error_kubectl_delete_helm = response_kubectl_delete_helm.communicate() + # If any error occured while execution of delete command + if (response_kubectl_delete_helm.returncode != 0): + # Converting the string of multiple errors to list + error_msg_list = error_kubectl_delete_helm.decode("ascii").split("\n") + error_msg_list.pop(-1) + valid_exception_list = [] + # Checking if any exception occured or not + exception_occured_counter = 0 + for ind_errors in error_msg_list: + if('not found' in ind_errors or 'deleted' in ind_errors): + pass + else: + valid_exception_list.append(ind_errors) + exception_occured_counter = 1 + # If any exception occured we will print the exception and return + if exception_occured_counter == 1: + logger.warning("Cleanup of previous diagnostic checks helm release failed and hence couldn't install the new helm release. Please cleanup older release using \"helm delete cluster-diagnostic-checks -n azure-arc-release\" and try onboarding again") + telemetry.set_exception(exception=error_kubectl_delete_helm.decode("ascii"), fault_type=consts.Cluster_Diagnostic_Checks_Release_Cleanup_Failed, summary="Error while executing cluster diagnostic checks Job") + return chart_path = azext_utils.get_chart_path(consts.Cluster_Diagnostic_Checks_Job_Registry_Path, kube_config, kube_context, helm_client_location, consts.Pre_Onboarding_Helm_Charts_Folder_Name, consts.Pre_Onboarding_Helm_Charts_Release_Name) diff --git a/src/connectedk8s/azext_connectedk8s/_utils.py b/src/connectedk8s/azext_connectedk8s/_utils.py index 6efe15b118c..43d11d3666d 100644 --- a/src/connectedk8s/azext_connectedk8s/_utils.py +++ b/src/connectedk8s/azext_connectedk8s/_utils.py @@ -536,6 +536,31 @@ def helm_install_release(chart_path, subscription_id, kubernetes_distro, kuberne raise CLIInternalError("Unable to install helm release: " + error_helm_install.decode("ascii")) +def get_release_namespace(kube_config, kube_context, helm_client_location, release_name='azure-arc'): + cmd_helm_release = [helm_client_location, "list", "-a", "--all-namespaces", "--output", "json"] + if kube_config: + cmd_helm_release.extend(["--kubeconfig", kube_config]) + if kube_context: + cmd_helm_release.extend(["--kube-context", kube_context]) + response_helm_release = Popen(cmd_helm_release, stdout=PIPE, stderr=PIPE) + output_helm_release, error_helm_release = response_helm_release.communicate() + if response_helm_release.returncode != 0: + if 'forbidden' in error_helm_release.decode("ascii"): + telemetry.set_user_fault() + telemetry.set_exception(exception=error_helm_release.decode("ascii"), fault_type=consts.List_HelmRelease_Fault_Type, + summary='Unable to list helm release') + raise CLIInternalError("Helm list release failed: " + error_helm_release.decode("ascii")) + output_helm_release = output_helm_release.decode("ascii") + try: + output_helm_release = json.loads(output_helm_release) + except json.decoder.JSONDecodeError: + return None + for release in output_helm_release: + if release['name'] == release_name: + return release['namespace'] + return None + + def flatten(dd, separator='.', prefix=''): try: if isinstance(dd, dict): diff --git a/src/connectedk8s/azext_connectedk8s/custom.py b/src/connectedk8s/azext_connectedk8s/custom.py index 69fffe4387a..a6c5b91d72e 100644 --- a/src/connectedk8s/azext_connectedk8s/custom.py +++ b/src/connectedk8s/azext_connectedk8s/custom.py @@ -66,7 +66,7 @@ def create_connectedk8s(cmd, client, resource_group_name, cluster_name, correlation_id=None, https_proxy="", http_proxy="", no_proxy="", proxy_cert="", location=None, - kube_config=None, kube_context=None, no_wait=False, tags=None, distribution='auto', infrastructure='auto', + kube_config=None, kube_context=None, no_wait=False, tags=None, distribution='generic', infrastructure='generic', disable_auto_upgrade=False, cl_oid=None, onboarding_timeout="600", enable_private_link=None, private_link_scope_resource_id=None, distribution_version=None, azure_hybrid_benefit=None, yes=False, container_log_path=None): logger.warning("This operation might take a while...\n") @@ -201,14 +201,8 @@ def create_connectedk8s(cmd, client, resource_group_name, cluster_name, correlat raise ValidationError("Your credentials doesn't have permission to create clusterrolebindings on this kubernetes cluster. Please check your permissions.") # Get kubernetes cluster info - if distribution == 'auto': - kubernetes_distro = get_kubernetes_distro(node_api_response) # (cluster heuristics) - else: - kubernetes_distro = distribution - if infrastructure == 'auto': - kubernetes_infra = get_kubernetes_infra(node_api_response) # (cluster heuristics) - else: - kubernetes_infra = infrastructure + kubernetes_distro = distribution + kubernetes_infra = infrastructure kubernetes_properties = { 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, @@ -246,7 +240,7 @@ def create_connectedk8s(cmd, client, resource_group_name, cluster_name, correlat logger.warning("Error occured while checking the private link scope resource location: %s\n", ex) # Check Release Existance - release_namespace = get_release_namespace(kube_config, kube_context, helm_client_location) + release_namespace = utils.get_release_namespace(kube_config, kube_context, helm_client_location) if release_namespace: # Loading config map @@ -543,64 +537,6 @@ def get_private_key(key_pair): return PEM.encode(privKey_DER, "RSA PRIVATE KEY") -def get_kubernetes_distro(api_response): # Heuristic - if api_response is None: - return "generic" - try: - for node in api_response.items: - labels = node.metadata.labels - provider_id = str(node.spec.provider_id) - annotations = node.metadata.annotations - if labels.get("node.openshift.io/os_id"): - return "openshift" - if labels.get("kubernetes.azure.com/node-image-version"): - return "aks" - if labels.get("cloud.google.com/gke-nodepool") or labels.get("cloud.google.com/gke-os-distribution"): - return "gke" - if labels.get("eks.amazonaws.com/nodegroup"): - return "eks" - if labels.get("minikube.k8s.io/version"): - return "minikube" - if provider_id.startswith("kind://"): - return "kind" - if provider_id.startswith("k3s://"): - return "k3s" - if annotations.get("rke.cattle.io/external-ip") or annotations.get("rke.cattle.io/internal-ip"): - return "rancher_rke" - return "generic" - except Exception as e: # pylint: disable=broad-except - logger.debug("Error occured while trying to fetch kubernetes distribution: " + str(e)) - utils.kubernetes_exception_handler(e, consts.Get_Kubernetes_Distro_Fault_Type, 'Unable to fetch kubernetes distribution', - raise_error=False) - return "generic" - - -def get_kubernetes_infra(api_response): # Heuristic - if api_response is None: - return "generic" - try: - for node in api_response.items: - provider_id = str(node.spec.provider_id) - infra = provider_id.split(':')[0] - if infra == "k3s" or infra == "kind": - return "generic" - if infra == "azure": - return "azure" - if infra == "gce": - return "gcp" - if infra == "aws": - return "aws" - k8s_infra = utils.validate_infrastructure_type(infra) - if k8s_infra is not None: - return k8s_infra - return "generic" - except Exception as e: # pylint: disable=broad-except - logger.debug("Error occured while trying to fetch kubernetes infrastructure: " + str(e)) - utils.kubernetes_exception_handler(e, consts.Get_Kubernetes_Infra_Fault_Type, 'Unable to fetch kubernetes infrastructure', - raise_error=False) - return "generic" - - def check_linux_amd64_node(api_response): try: for item in api_response.items: @@ -767,7 +703,7 @@ def delete_connectedk8s(cmd, client, resource_group_name, cluster_name, helm_client_location = install_helm_client() # Check Release Existance - release_namespace = get_release_namespace(kube_config, kube_context, helm_client_location) + release_namespace = utils.get_release_namespace(kube_config, kube_context, helm_client_location) # Check forced delete flag if(force_delete): @@ -864,31 +800,6 @@ def delete_connectedk8s(cmd, client, resource_group_name, cluster_name, utils.delete_arc_agents(release_namespace, kube_config, kube_context, helm_client_location) -def get_release_namespace(kube_config, kube_context, helm_client_location): - cmd_helm_release = [helm_client_location, "list", "-a", "--all-namespaces", "--output", "json"] - if kube_config: - cmd_helm_release.extend(["--kubeconfig", kube_config]) - if kube_context: - cmd_helm_release.extend(["--kube-context", kube_context]) - response_helm_release = Popen(cmd_helm_release, stdout=PIPE, stderr=PIPE) - output_helm_release, error_helm_release = response_helm_release.communicate() - if response_helm_release.returncode != 0: - if 'forbidden' in error_helm_release.decode("ascii"): - telemetry.set_user_fault() - telemetry.set_exception(exception=error_helm_release.decode("ascii"), fault_type=consts.List_HelmRelease_Fault_Type, - summary='Unable to list helm release') - raise CLIInternalError("Helm list release failed: " + error_helm_release.decode("ascii")) - output_helm_release = output_helm_release.decode("ascii") - try: - output_helm_release = json.loads(output_helm_release) - except json.decoder.JSONDecodeError: - return None - for release in output_helm_release: - if release['name'] == 'azure-arc': - return release['namespace'] - return None - - def create_cc_resource(client, resource_group_name, cluster_name, cc, no_wait): try: return sdk_no_wait(no_wait, client.begin_create, resource_group_name=resource_group_name, @@ -1005,26 +916,17 @@ def update_connected_cluster(cmd, client, resource_group_name, cluster_name, htt # Fetch Connected Cluster for agent version connected_cluster = get_connectedk8s(cmd, client, resource_group_name, cluster_name) - api_instance = kube_client.CoreV1Api() - node_api_response = None + + kubernetes_properties = {'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version} if hasattr(connected_cluster, 'distribution') and (connected_cluster.distribution is not None): kubernetes_distro = connected_cluster.distribution - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_distro = get_kubernetes_distro(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesDistro'] = kubernetes_distro if hasattr(connected_cluster, 'infrastructure') and (connected_cluster.infrastructure is not None): kubernetes_infra = connected_cluster.infrastructure - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_infra = get_kubernetes_infra(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesInfra'] = kubernetes_infra - kubernetes_properties = { - 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, - 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro, - 'Context.Default.AzureCLI.KubernetesInfra': kubernetes_infra - } telemetry.add_extension_event('connectedk8s', kubernetes_properties) # Adding helm repo @@ -1143,13 +1045,12 @@ def upgrade_agents(cmd, client, resource_group_name, cluster_name, kube_config=N utils.try_list_node_fix() api_instance = kube_client.CoreV1Api() - node_api_response = None # Install helm client helm_client_location = install_helm_client() # Check Release Existance - release_namespace = get_release_namespace(kube_config, kube_context, helm_client_location) + release_namespace = utils.get_release_namespace(kube_config, kube_context, helm_client_location) if release_namespace: # Loading config map api_instance = kube_client.CoreV1Api() @@ -1191,23 +1092,16 @@ def upgrade_agents(cmd, client, resource_group_name, cluster_name, kube_config=N # Fetch Connected Cluster for agent version connected_cluster = get_connectedk8s(cmd, client, resource_group_name, cluster_name) + kubernetes_properties = {'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version} + if hasattr(connected_cluster, 'distribution') and (connected_cluster.distribution is not None): kubernetes_distro = connected_cluster.distribution - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_distro = get_kubernetes_distro(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesDistro'] = kubernetes_distro if hasattr(connected_cluster, 'infrastructure') and (connected_cluster.infrastructure is not None): kubernetes_infra = connected_cluster.infrastructure - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_infra = get_kubernetes_infra(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesInfra'] = kubernetes_infra - kubernetes_properties = { - 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, - 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro, - 'Context.Default.AzureCLI.KubernetesInfra': kubernetes_infra - } telemetry.add_extension_event('connectedk8s', kubernetes_properties) # Adding helm repo @@ -1305,7 +1199,7 @@ def upgrade_agents(cmd, client, resource_group_name, cluster_name, kube_config=N def validate_release_namespace(client, cluster_name, resource_group_name, kube_config, kube_context, helm_client_location): # Check Release Existance - release_namespace = get_release_namespace(kube_config, kube_context, helm_client_location) + release_namespace = utils.get_release_namespace(kube_config, kube_context, helm_client_location) if release_namespace: # Loading config map api_instance = kube_client.CoreV1Api() @@ -1423,8 +1317,6 @@ def enable_features(cmd, client, resource_group_name, cluster_name, features, ku kubernetes_version = check_kube_connection() utils.try_list_node_fix() - api_instance = kube_client.CoreV1Api() - node_api_response = None # Install helm client helm_client_location = install_helm_client() @@ -1434,23 +1326,16 @@ def enable_features(cmd, client, resource_group_name, cluster_name, features, ku # Fetch Connected Cluster for agent version connected_cluster = get_connectedk8s(cmd, client, resource_group_name, cluster_name) + kubernetes_properties = {'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version} + if hasattr(connected_cluster, 'distribution') and (connected_cluster.distribution is not None): kubernetes_distro = connected_cluster.distribution - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_distro = get_kubernetes_distro(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesDistro'] = kubernetes_distro if hasattr(connected_cluster, 'infrastructure') and (connected_cluster.infrastructure is not None): kubernetes_infra = connected_cluster.infrastructure - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_infra = get_kubernetes_infra(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesInfra'] = kubernetes_infra - kubernetes_properties = { - 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, - 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro, - 'Context.Default.AzureCLI.KubernetesInfra': kubernetes_infra - } telemetry.add_extension_event('connectedk8s', kubernetes_properties) # Adding helm repo @@ -1542,8 +1427,6 @@ def disable_features(cmd, client, resource_group_name, cluster_name, features, k kubernetes_version = check_kube_connection() utils.try_list_node_fix() - api_instance = kube_client.CoreV1Api() - node_api_response = None # Install helm client helm_client_location = install_helm_client() @@ -1553,23 +1436,16 @@ def disable_features(cmd, client, resource_group_name, cluster_name, features, k # Fetch Connected Cluster for agent version connected_cluster = get_connectedk8s(cmd, client, resource_group_name, cluster_name) + kubernetes_properties = {'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version} + if hasattr(connected_cluster, 'distribution') and (connected_cluster.distribution is not None): kubernetes_distro = connected_cluster.distribution - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_distro = get_kubernetes_distro(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesDistro'] = kubernetes_distro if hasattr(connected_cluster, 'infrastructure') and (connected_cluster.infrastructure is not None): kubernetes_infra = connected_cluster.infrastructure - else: - node_api_response = utils.validate_node_api_response(api_instance, node_api_response) - kubernetes_infra = get_kubernetes_infra(node_api_response) + kubernetes_properties['Context.Default.AzureCLI.KubernetesInfra'] = kubernetes_infra - kubernetes_properties = { - 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, - 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro, - 'Context.Default.AzureCLI.KubernetesInfra': kubernetes_infra - } telemetry.add_extension_event('connectedk8s', kubernetes_properties) if disable_cluster_connect: diff --git a/src/connectedk8s/setup.py b/src/connectedk8s/setup.py index ef5b845dcfe..3c18d028061 100644 --- a/src/connectedk8s/setup.py +++ b/src/connectedk8s/setup.py @@ -17,7 +17,7 @@ # TODO: Confirm this is the right version number you want and it matches your # HISTORY.rst entry. -VERSION = '1.3.9' +VERSION = '1.3.10' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers