diff --git a/src/connectedk8s/HISTORY.rst b/src/connectedk8s/HISTORY.rst index 6e4cabf841d..764fe7e8450 100644 --- a/src/connectedk8s/HISTORY.rst +++ b/src/connectedk8s/HISTORY.rst @@ -3,6 +3,13 @@ Release History =============== + +1.1.7 +++++++ +* Add non-existing namespace deploy check +* Improve some error and warning experiences + + 1.1.6 ++++++ * Moved to track2 SDK diff --git a/src/connectedk8s/azext_connectedk8s/_constants.py b/src/connectedk8s/azext_connectedk8s/_constants.py index 56ae91c6de8..338bf88e159 100644 --- a/src/connectedk8s/azext_connectedk8s/_constants.py +++ b/src/connectedk8s/azext_connectedk8s/_constants.py @@ -10,6 +10,9 @@ Infrastructure_Enum_Values = ["auto", "generic", "azure", "aws", "gcp", "azure_stack_hci", "azure_stack_hub", "azure_stack_edge", "vsphere", "windows_server"] Feature_Values = ["cluster-connect", "azure-rbac", "custom-locations"] Custom_Locations_Provider_Namespace = 'Microsoft.ExtendedLocation' +Connected_Cluster_Provider_Namespace = 'Microsoft.Kubernetes' +Kubernetes_Configuration_Provider_Namespace = 'Microsoft.KubernetesConfiguration' +Arc_Namespace = 'azure-arc' Azure_PublicCloudName = 'AZUREPUBLICCLOUD' Azure_USGovCloudName = 'AZUREUSGOVERNMENTCLOUD' @@ -101,6 +104,9 @@ Error_enabling_Features = 'Error while updating agents for enabling features. Please run \"kubectl get pods -n azure-arc\" to check the pods in case of timeout error. Error: {}' Error_disabling_Features = 'Error while updating agents for disabling features. Please run \"kubectl get pods -n azure-arc\" to check the pods in case of timeout error. Error: {}' Proxy_Kubeconfig_During_Deletion_Fault_Type = 'Encountered proxy kubeconfig during deletion.' +Cannot_Create_ClusterRoleBindings_Fault_Type = 'Cannot create cluster role bindings on this Kubernets cluster' +CC_Provider_Namespace_Not_Registered_Fault_Type = "Connected Cluster Provider MS.K8 namespace not registered" +Default_Namespace_Does_Not_Exist_Fault_Type = "The default namespace defined in the kubeconfig doesn't exist on the kubernetes cluster." CLIENT_PROXY_VERSION = '1.1.0' API_SERVER_PORT = 47011 CLIENT_PROXY_PORT = 47010 diff --git a/src/connectedk8s/azext_connectedk8s/_utils.py b/src/connectedk8s/azext_connectedk8s/_utils.py index 05988a57ae2..a02c9f923ec 100644 --- a/src/connectedk8s/azext_connectedk8s/_utils.py +++ b/src/connectedk8s/azext_connectedk8s/_utils.py @@ -20,10 +20,11 @@ from azure.cli.core.util import send_raw_request from azure.cli.core import telemetry from azure.core.exceptions import ResourceNotFoundError -from msrest.exceptions import AuthenticationError, HttpOperationError, TokenExpiredError, ValidationError +from msrest.exceptions import AuthenticationError, HttpOperationError, TokenExpiredError +from msrest.exceptions import ValidationError as MSRestValidationError from msrestazure.azure_exceptions import CloudError from kubernetes.client.rest import ApiException -from azext_connectedk8s._client_factory import _resource_client_factory +from azext_connectedk8s._client_factory import _resource_client_factory, _resource_providers_client import azext_connectedk8s._constants as consts from kubernetes import client as kube_client from azure.cli.core.azclierror import CLIInternalError, ClientRequestError, ArgumentUsageError, ManualInterrupt, AzureResponseError, AzureInternalError, ValidationError @@ -183,7 +184,7 @@ def arm_exception_handler(ex, fault_type, summary, return_if_not_found=False): raise AzureInternalError("Http operation error occured while making ARM request: " + str(ex) + "\nSummary: {}".format(summary)) raise AzureResponseError("Http operation error occured while making ARM request: " + str(ex) + "\nSummary: {}".format(summary)) - if isinstance(ex, ValidationError): + if isinstance(ex, MSRestValidationError): telemetry.set_exception(exception=ex, fault_type=fault_type, summary=summary) raise AzureResponseError("Validation error occured while making ARM request: " + str(ex) + "\nSummary: {}".format(summary)) @@ -395,3 +396,38 @@ def names(self, names): V1ContainerImage.names = V1ContainerImage.names.setter(names) except Exception as ex: logger.debug("Error while trying to monkey patch the fix for list_node(): {}".format(str(ex))) + + +def check_provider_registrations(cli_ctx): + try: + rp_client = _resource_providers_client(cli_ctx) + cc_registration_state = rp_client.get(consts.Connected_Cluster_Provider_Namespace).registration_state + if cc_registration_state != "Registered": + telemetry.set_exception(exception="{} provider is not registered".format(consts.Connected_Cluster_Provider_Namespace), fault_type=consts.CC_Provider_Namespace_Not_Registered_Fault_Type, + summary="{} provider is not registered".format(consts.Connected_Cluster_Provider_Namespace)) + raise ValidationError("{} provider is not registered. Please register it using 'az provider register -n 'Microsoft.Kubernetes' before running the connect command.".format(consts.Connected_Cluster_Provider_Namespace)) + kc_registration_state = rp_client.get(consts.Kubernetes_Configuration_Provider_Namespace).registration_state + if kc_registration_state != "Registered": + telemetry.set_user_fault() + logger.warning("{} provider is not registered".format(consts.Kubernetes_Configuration_Provider_Namespace)) + except ValidationError as e: + raise e + except Exception as ex: + logger.warning("Couldn't check the required provider's registration status. Error: {}".format(str(ex))) + + +def can_create_clusterrolebindings(configuration): + try: + api_instance = kube_client.AuthorizationV1Api(kube_client.ApiClient(configuration)) + access_review = kube_client.V1SelfSubjectAccessReview(spec={ + "resourceAttributes": { + "verb": "create", + "resource": "clusterrolebindings", + "group": "rbac.authorization.k8s.io" + } + }) + response = api_instance.create_self_subject_access_review(access_review) + return response.status.allowed + except Exception as ex: + logger.warning("Couldn't check for the permission to create clusterrolebindings on this k8s cluster. Error: {}".format(str(ex))) + return "Unknown" diff --git a/src/connectedk8s/azext_connectedk8s/custom.py b/src/connectedk8s/azext_connectedk8s/custom.py index d76ea1853bb..4068ed3428c 100644 --- a/src/connectedk8s/azext_connectedk8s/custom.py +++ b/src/connectedk8s/azext_connectedk8s/custom.py @@ -72,6 +72,9 @@ def create_connectedk8s(cmd, client, resource_group_name, cluster_name, https_pr graph_client = _graph_client_factory(cmd.cli_ctx) onboarding_tenant_id = graph_client.config.tenant_id + # Checking provider registration status + utils.check_provider_registrations(cmd.cli_ctx) + # Setting kubeconfig kube_config = set_kube_config(kube_config) @@ -119,6 +122,12 @@ def create_connectedk8s(cmd, client, resource_group_name, cluster_name, https_pr summary="Couldn't find any node on the kubernetes cluster with the architecture type 'amd64' and OS 'linux'") logger.warning("Please ensure that this Kubernetes cluster have any nodes with OS 'linux' and architecture 'amd64', for scheduling the Arc-Agents onto and connecting to Azure. Learn more at {}".format("https://aka.ms/ArcK8sSupportedOSArchitecture")) + crb_permission = utils.can_create_clusterrolebindings(configuration) + if not crb_permission: + telemetry.set_exception(exception="Your credentials doesn't have permission to create clusterrolebindings on this kubernetes cluster.", fault_type=consts.Cannot_Create_ClusterRoleBindings_Fault_Type, + summary="Your credentials doesn't have permission to create clusterrolebindings on this kubernetes cluster.") + raise ValidationError("Your credentials doesn't have permission to create clusterrolebindings on this kubernetes cluster. Please check your permissions.") + # Get kubernetes cluster info kubernetes_version = get_server_version(configuration) @@ -200,6 +209,36 @@ def create_connectedk8s(cmd, client, resource_group_name, cluster_name, https_pr "and corresponds to a different Kubernetes cluster.", recommendation="To onboard this Kubernetes cluster " + "to Azure, specify different resource name or resource group name.") + try: + k8s_contexts = config.list_kube_config_contexts() # returns tuple of (all_contexts, current_context) + if kube_context: # if custom kube-context is specified + if k8s_contexts[1].get('name') == kube_context: + current_k8s_context = k8s_contexts[1] + else: + for context in k8s_contexts[0]: + if context.get('name') == kube_context: + current_k8s_context = context + break + else: + current_k8s_context = k8s_contexts[1] + + current_k8s_namespace = current_k8s_context.get('context').get('namespace', "default") # Take "default" namespace, if not specified in the kube-config + namespace_exists = False + k8s_v1 = kube_client.CoreV1Api() + k8s_ns = k8s_v1.list_namespace() + for ns in k8s_ns.items: + if ns.metadata.name == current_k8s_namespace: + namespace_exists = True + break + if namespace_exists is False: + telemetry.set_exception(exception="Namespace doesn't exist", fault_type=consts.Default_Namespace_Does_Not_Exist_Fault_Type, + summary="The default namespace defined in the kubeconfig doesn't exist on the kubernetes cluster.") + raise ValidationError("The default namespace '{}' defined in the kubeconfig doesn't exist on the kubernetes cluster.".format(current_k8s_namespace)) + except ValidationError as e: + raise e + except Exception as e: + logger.warning("Failed to validate if the active namespace exists on the kubernetes cluster. Exception: {}".format(str(e))) + # Resource group Creation if resource_group_exists(cmd.cli_ctx, resource_group_name, subscription_id) is False: from azure.cli.core.profiles import ResourceType diff --git a/src/connectedk8s/setup.py b/src/connectedk8s/setup.py index 0e5f3d59160..d7598a7f7e4 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.1.6' +VERSION = '1.1.7' # The full list of classifiers is available at # https://pypi.python.org/pypi?%3Aaction=list_classifiers