def check_pod_status(pod_dict): v1 = kube_client.CoreV1Api() w = watch.Watch() for event in w.stream(v1.list_namespaced_pod, namespace='azure-arc', timeout_seconds=360): pod_status = event['raw_object'].get('status') pod_name = event['object'].metadata.name if pod_status.get('containerStatuses'): for container in pod_status.get('containerStatuses'): if container.get('state').get('running') is None: pod_dict[pod_name] = 0 break else: pod_dict[pod_name] = 1 if container.get('state').get('terminated') is not None: logger.warning( "%s%s%s", "The pod {} was terminated. ".format( container.get('name')), "Please ensure it is in running state once the operation completes. ", "Run 'kubectl get pods -n azure-arc' to check the pod status." ) if all(ele == 1 for ele in list(pod_dict.values())): return telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.ExitStatus': 'Timedout'}) logger.warning( "%s%s", 'The pods were unable to start before timeout. ', 'Please run "kubectl get pods -n azure-arc" to ensure if the pods are in running state.' )
def conclude(): if not _session.aliases_hit: return _session.end_time = datetime.datetime.now() for properties in _session.generate_payload(): telemetry_core.add_extension_event(EXTENSION_NAME, properties)
def conclude(): if not _session.aliases_hit and not _session.exceptions: return _session.end_time = datetime.datetime.now() for properties in _session.generate_payload(): telemetry_core.add_extension_event(EXTENSION_NAME, properties)
def process_query(cli_term): print(random.choice(WAIT_MESSAGE)) response = call_aladdin_service(cli_term) if response.status_code != 200: err_msg = '[?] Unexpected Error: [HTTP {0}]: Content: {1}'.format( response.status_code, response.content) logger.error(err_msg) else: if (platform.system() == 'Windows' and should_enable_styling()): colorama.init(convert=True) answer_list = json.loads(response.content) if (not answer_list or answer_list[0]['source'] == 'bing'): print("\nSorry I am not able to help with [" + cli_term + "]." "\nTry typing the beginning of a command e.g. " + style_message('az vm') + ".") else: print("\nHere are the most common ways to use [" + cli_term + "]: \n") num_results_to_show = min(3, len(answer_list)) for i in range(num_results_to_show): current_title = answer_list[i]['title'].strip() current_snippet = answer_list[i]['snippet'].strip() if current_title.startswith("az "): current_title, current_snippet = current_snippet, current_title current_title = current_title.split('\r\n')[0] elif '```azurecli\r\n' in current_snippet: start_index = current_snippet.index( '```azurecli\r\n') + len('```azurecli\r\n') current_snippet = current_snippet[start_index:] current_snippet = current_snippet.replace('```', '').replace( current_title, '').strip() current_snippet = re.sub(r'\[.*\]', '', current_snippet).strip() print(style_message(current_title)) print(current_snippet) print("") feedback = prompt( "[Enter to close. Press + or - to give feedback]:") if feedback in ['+', '-']: print('Wow, you are a true hero!') print("""\ O_ """ + style_message("""<T>""") + """`-. """ + style_message("""|""") + """`-‘ """ + style_message("""I""") + """ """) print( 'My human overlords review each of these reports; I\'m told these reports makes me smarter.' ) print( 'Send us more feedback by email: [email protected]') properties = {} set_custom_properties(properties, 'Feedback', feedback) telemetry_core.add_extension_event(EXTENSION_NAME, properties)
def send_cloud_telemetry(cmd): telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.AzureCloud': cmd.cli_ctx.cloud.name}) cloud_name = cmd.cli_ctx.cloud.name.upper() # Setting cloud name to format that is understood by golang SDK. if cloud_name == consts.PublicCloud_OriginalName: cloud_name = consts.Azure_PublicCloudName elif cloud_name == consts.USGovCloud_OriginalName: cloud_name = consts.Azure_USGovCloudName return cloud_name
def start_rdp_connection(ssh_info, delete_keys, delete_cert): try: ssh_process = None log_list = [] print_ssh_logs = False ssh_success = False resource_port = 3389 local_port = _get_open_port() while not is_local_port_open(local_port): local_port = _get_open_port() if ssh_info.ssh_args is None: ssh_info.ssh_args = [ '-L', f"{local_port}:localhost:{resource_port}", "-N" ] else: ssh_info.ssh_args = [ '-L', f"{local_port}:localhost:{resource_port}", "-N" ] + ssh_info.ssh_args ssh_process, print_ssh_logs = start_ssh_tunnel(ssh_info) ssh_connection_t0 = time.time() ssh_success, log_list = wait_for_ssh_connection( ssh_process, print_ssh_logs) ssh_utils.do_cleanup(delete_keys, delete_cert, ssh_info.cert_file, ssh_info.private_key_file, ssh_info.public_key_file) if ssh_success and ssh_process.poll() is None: call_rdp(local_port) finally: if ssh_success: ssh_connection_data = { 'Context.Default.AzureCLI.SSHConnectionDurationInMinutes': (time.time() - ssh_connection_t0) / 60 } ssh_connection_data[ 'Context.Default.AzureCLI.SSHConnectionStatus'] = "Success" telemetry.add_extension_event('ssh', ssh_connection_data) terminate_ssh(ssh_process, log_list, print_ssh_logs) ssh_utils.do_cleanup(delete_keys, delete_cert, ssh_info.cert_file, ssh_info.private_key_file, ssh_info.public_key_file) if delete_keys: # This is only true if keys were generated, so they must be in a temp folder. temp_dir = os.path.dirname(ssh_info.cert_file) file_utils.delete_folder( temp_dir, f"Couldn't delete temporary folder {temp_dir}", True)
def get_client_side_proxy(arc_proxy_folder): request_uri, install_location, older_version_location = _get_proxy_filename_and_url( arc_proxy_folder) install_dir = os.path.dirname(install_location) # Only download new proxy if it doesn't exist already if not os.path.isfile(install_location): t0 = time.time() # download the executable try: with urllib.request.urlopen(request_uri) as response: response_content = response.read() response.close() except Exception as e: raise azclierror.ClientRequestError( f"Failed to download client proxy executable from {request_uri}. " "Error: " + str(e)) from e time_elapsed = time.time() - t0 proxy_data = { 'Context.Default.AzureCLI.SSHProxyDownloadTime': time_elapsed, 'Context.Default.AzureCLI.SSHProxyVersion': consts.CLIENT_PROXY_VERSION } telemetry.add_extension_event('ssh', proxy_data) # if directory doesn't exist, create it if not os.path.isdir(install_dir): file_utils.create_directory( install_dir, f"Failed to create client proxy directory '{install_dir}'. ") # if directory exists, delete any older versions of the proxy else: older_version_files = glob(older_version_location) for f in older_version_files: file_utils.delete_file( f, f"failed to delete older version file {f}", warning=True) # write executable in the install location file_utils.write_to_file(install_location, 'wb', response_content, "Failed to create client proxy file. ") os.chmod(install_location, os.stat(install_location).st_mode | stat.S_IXUSR) colorama.init() print(Fore.GREEN + f"SSH Client Proxy saved to {install_location}" + Style.RESET_ALL) return install_location
def process_query(cli_term): print(random.choice(WAIT_MESSAGE)) response = call_aladdin_service(cli_term) if response.status_code != 200: logger.error('[?] Unexpected Error: [HTTP {0}]: Content: {1}'.format(response.status_code, response.content)) else: if (platform.system() == 'Windows' and should_enable_styling()): colorama.init(convert=True) answer_list = json.loads(response.content) if (not answer_list or answer_list[0]['source'] == 'bing'): print("\nSorry I am not able to help with [" + cli_term + "]." "\nTry typing the beginning of a command e.g. " + style_message('az vm') + ".") else: print("\nHere are the most common ways to use [" + cli_term + "]: \n") num_results_to_show = min(3, len(answer_list)) for i in range(num_results_to_show): current_title = answer_list[i]['title'].strip() current_snippet = answer_list[i]['snippet'].strip() if current_title.startswith("az "): current_title, current_snippet = current_snippet, current_title current_title = current_title.split('\r\n')[0] elif '```azurecli\r\n' in current_snippet: start_index = current_snippet.index('```azurecli\r\n') + len('```azurecli\r\n') current_snippet = current_snippet[start_index:] current_snippet = current_snippet.replace('```', '').replace(current_title, '').strip() current_snippet = re.sub(r'\[.*\]', '', current_snippet).strip() print(style_message(current_title)) print(current_snippet) print("") feedback = prompt("[Enter to close. Press + or - to give feedback]:") if feedback in ['+', '-']: print('Wow, you are a true hero!') print("""\ O_ """ + style_message("""<T>""") + """`-. """ + style_message("""|""") + """`-‘ """ + style_message("""I""") + """ """) print('My human overlords review each of these reports; I\'m told these reports makes me smarter.') print('Send us more feedback by email: [email protected]') properties = {} set_custom_properties(properties, 'Feedback', feedback) telemetry_core.add_extension_event(EXTENSION_NAME, properties)
def get_relay_information(cmd, resource_group, vm_name, certificate_validity_in_seconds): from azext_ssh._client_factory import cf_endpoint client = cf_endpoint(cmd.cli_ctx) if not certificate_validity_in_seconds or \ certificate_validity_in_seconds > consts.RELAY_INFO_MAXIMUM_DURATION_IN_SECONDS: certificate_validity_in_seconds = consts.RELAY_INFO_MAXIMUM_DURATION_IN_SECONDS try: t0 = time.time() result = client.list_credentials( resource_group_name=resource_group, machine_name=vm_name, endpoint_name="default", expiresin=certificate_validity_in_seconds) time_elapsed = time.time() - t0 telemetry.add_extension_event( 'ssh', {'Context.Default.AzureCLI.SSHListCredentialsTime': time_elapsed}) except ResourceNotFoundError: logger.debug( "Default Endpoint couldn't be found. Trying to create Default Endpoint." ) _create_default_endpoint(cmd, resource_group, vm_name, client) try: t0 = time.time() result = client.list_credentials( resource_group_name=resource_group, machine_name=vm_name, endpoint_name="default", expiresin=certificate_validity_in_seconds) time_elapsed = time.time() - t0 telemetry.add_extension_event('ssh', { 'Context.Default.AzureCLI.SSHListCredentialsTime': time_elapsed }) except Exception as e: raise azclierror.ClientRequestError( f"Request for Azure Relay Information Failed:\n{str(e)}") except Exception as e: raise azclierror.ClientRequestError( f"Request for Azure Relay Information Failed:\n{str(e)}") return result
def _get_and_write_certificate(cmd, public_key_file, cert_file, ssh_client_folder): cloudtoscope = { "azurecloud": "https://pas.windows.net/CheckMyAccess/Linux/.default", "azurechinacloud": "https://pas.chinacloudapi.cn/CheckMyAccess/Linux/.default", "azureusgovernment": "https://pasff.usgovcloudapi.net/CheckMyAccess/Linux/.default" } scope = cloudtoscope.get(cmd.cli_ctx.cloud.name.lower(), None) if not scope: raise azclierror.InvalidArgumentValueError( f"Unsupported cloud {cmd.cli_ctx.cloud.name.lower()}", "Supported clouds include azurecloud,azurechinacloud,azureusgovernment") scopes = [scope] data = _prepare_jwk_data(public_key_file) from azure.cli.core._profile import Profile profile = Profile(cli_ctx=cmd.cli_ctx) t0 = time.time() # We currently are using the presence of get_msal_token to detect if we are running on an older azure cli client # TODO: Remove when adal has been deprecated for a while if hasattr(profile, "get_msal_token"): # we used to use the username from the token but now we throw it away _, certificate = profile.get_msal_token(scopes, data) else: credential, _, _ = profile.get_login_credentials(subscription_id=profile.get_subscription()["id"]) certificatedata = credential.get_token(*scopes, data=data) certificate = certificatedata.token time_elapsed = time.time() - t0 telemetry.add_extension_event('ssh', {'Context.Default.AzureCLI.SSHGetCertificateTime': time_elapsed}) if not cert_file: cert_file = public_key_file + "-aadcert.pub" logger.debug("Generating certificate %s", cert_file) _write_cert_file(certificate, cert_file) # instead we use the validprincipals from the cert due to mismatched upn and email in guest scenarios username = ssh_utils.get_ssh_cert_principals(cert_file, ssh_client_folder)[0] return cert_file, username.lower()
def update_agents(cmd, client, resource_group_name, cluster_name, https_proxy="", http_proxy="", no_proxy="", kube_config=None, kube_context=None, no_wait=False): logger.warning( "Ensure that you have the latest helm version installed before proceeding." ) logger.warning("This operation might take a while...\n") # Send cloud information to telemetry send_cloud_telemetry(cmd) # Setting kubeconfig kube_config = set_kube_config(kube_config) # Escaping comma, forward slash present in https proxy urls, needed for helm params. https_proxy = escape_proxy_settings(https_proxy) # Escaping comma, forward slash present in http proxy urls, needed for helm params. http_proxy = escape_proxy_settings(http_proxy) # Escaping comma, forward slash present in no proxy urls, needed for helm params. no_proxy = escape_proxy_settings(no_proxy) # Checking whether optional extra values file has been provided. values_file_provided = False values_file = os.getenv('HELMVALUESPATH') if (values_file is not None) and (os.path.isfile(values_file)): values_file_provided = True logger.warning( "Values files detected. Reading additional helm parameters from same." ) # trimming required for windows os if (values_file.startswith("'") or values_file.startswith('"')): values_file = values_file[1:] if (values_file.endswith("'") or values_file.endswith('"')): values_file = values_file[:-1] # Validate the helm environment file for Dogfood. dp_endpoint_dogfood = None release_train_dogfood = None if cmd.cli_ctx.cloud.endpoints.resource_manager == consts.Dogfood_RMEndpoint: dp_endpoint_dogfood, release_train_dogfood = validate_env_file_dogfood( values_file, values_file_provided) # Loading the kubeconfig file in kubernetes client configuration try: config.load_kube_config(config_file=kube_config, context=kube_context) except Exception as e: telemetry.set_user_fault() telemetry.set_exception(exception=e, fault_type=consts.Load_Kubeconfig_Fault_Type, summary='Problem loading the kubeconfig file') raise CLIError("Problem loading the kubeconfig file." + str(e)) configuration = kube_client.Configuration() # Checking the connection to kubernetes cluster. # This check was added to avoid large timeouts when connecting to AAD Enabled AKS clusters # if the user had not logged in. check_kube_connection(configuration) # Get kubernetes cluster info for telemetry kubernetes_version = get_server_version(configuration) kubernetes_distro = get_kubernetes_distro(configuration) kubernetes_properties = { 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro } telemetry.add_extension_event('connectedk8s', kubernetes_properties) # Checking helm installation check_helm_install(kube_config, kube_context) # Check helm version helm_version = check_helm_version(kube_config, kube_context) telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.HelmVersion': helm_version}) # Check whether Connected Cluster is present if not connected_cluster_exists(client, resource_group_name, cluster_name): telemetry.set_user_fault() telemetry.set_exception( exception='The connected cluster resource does not exist', fault_type=consts.Resource_Does_Not_Exist_Fault_Type, summary='Connected cluster resource does not exist') raise CLIError( "The connected cluster resource {} does not exist ".format( cluster_name) + "in the resource group {} ".format(resource_group_name) + "Please onboard the connected cluster using: az connectedk8s connect -n <connected-cluster-name> -g <resource-group-name>" ) # Fetch Connected Cluster for agent version connected_cluster = get_connectedk8s(cmd, client, resource_group_name, cluster_name) # Adding helm repo if os.getenv('HELMREPONAME') and os.getenv('HELMREPOURL'): utils.add_helm_repo(kube_config, kube_context) # Retrieving Helm chart OCI Artifact location registry_path = os.getenv('HELMREGISTRY') if os.getenv( 'HELMREGISTRY') else utils.get_helm_registry( cmd, connected_cluster.location, dp_endpoint_dogfood, release_train_dogfood) reg_path_array = registry_path.split(':') agent_version = reg_path_array[1] # Set agent version in registry path if connected_cluster.agent_version is not None: agent_version = connected_cluster.agent_version registry_path = reg_path_array[ 0] + ":" + connected_cluster.agent_version telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.AgentVersion': agent_version}) # Get Helm chart path chart_path = utils.get_chart_path(registry_path, kube_config, kube_context) cmd_helm_upgrade = [ "helm", "upgrade", "azure-arc", chart_path, "--reuse-values", "--set", "global.httpsProxy={}".format(https_proxy), "--set", "global.httpProxy={}".format(http_proxy), "--set", "global.noProxy={}".format(no_proxy), "--wait", "--output", "json" ] if values_file_provided: cmd_helm_upgrade.extend(["-f", values_file]) if kube_config: cmd_helm_upgrade.extend(["--kubeconfig", kube_config]) if kube_context: cmd_helm_upgrade.extend(["--kube-context", kube_context]) response_helm_upgrade = Popen(cmd_helm_upgrade, stdout=PIPE, stderr=PIPE) _, error_helm_upgrade = response_helm_upgrade.communicate() if response_helm_upgrade.returncode != 0: if ('forbidden' in error_helm_upgrade.decode("ascii") or 'timed out waiting for the condition' in error_helm_upgrade.decode("ascii")): telemetry.set_user_fault() telemetry.set_exception( exception=error_helm_upgrade.decode("ascii"), fault_type=consts.Install_HelmRelease_Fault_Type, summary='Unable to install helm release') raise CLIError( str.format(consts.Update_Agent_Failure, error_helm_upgrade.decode("ascii"))) return str.format(consts.Update_Agent_Success, connected_cluster.name)
def create_connectedk8s(cmd, client, resource_group_name, cluster_name, https_proxy="", http_proxy="", no_proxy="", location=None, kube_config=None, kube_context=None, no_wait=False, tags=None): logger.warning( "Ensure that you have the latest helm version installed before proceeding." ) logger.warning("This operation might take a while...\n") # Setting subscription id subscription_id = get_subscription_id(cmd.cli_ctx) # Send cloud information to telemetry send_cloud_telemetry(cmd) # Fetching Tenant Id graph_client = _graph_client_factory(cmd.cli_ctx) onboarding_tenant_id = graph_client.config.tenant_id # Setting kubeconfig kube_config = set_kube_config(kube_config) # Escaping comma, forward slash present in https proxy urls, needed for helm params. https_proxy = escape_proxy_settings(https_proxy) # Escaping comma, forward slash present in http proxy urls, needed for helm params. http_proxy = escape_proxy_settings(http_proxy) # Escaping comma, forward slash present in no proxy urls, needed for helm params. no_proxy = escape_proxy_settings(no_proxy) # Checking whether optional extra values file has been provided. values_file_provided = False values_file = os.getenv('HELMVALUESPATH') if (values_file is not None) and (os.path.isfile(values_file)): values_file_provided = True logger.warning( "Values files detected. Reading additional helm parameters from same." ) # trimming required for windows os if (values_file.startswith("'") or values_file.startswith('"')): values_file = values_file[1:] if (values_file.endswith("'") or values_file.endswith('"')): values_file = values_file[:-1] # Validate the helm environment file for Dogfood. dp_endpoint_dogfood = None release_train_dogfood = None if cmd.cli_ctx.cloud.endpoints.resource_manager == consts.Dogfood_RMEndpoint: dp_endpoint_dogfood, release_train_dogfood = validate_env_file_dogfood( values_file, values_file_provided) # Loading the kubeconfig file in kubernetes client configuration try: config.load_kube_config(config_file=kube_config, context=kube_context) except Exception as e: telemetry.set_user_fault() telemetry.set_exception(exception=e, fault_type=consts.Load_Kubeconfig_Fault_Type, summary='Problem loading the kubeconfig file') raise CLIError("Problem loading the kubeconfig file." + str(e)) configuration = kube_client.Configuration() # Checking the connection to kubernetes cluster. # This check was added to avoid large timeouts when connecting to AAD Enabled AKS clusters # if the user had not logged in. check_kube_connection(configuration) # Get kubernetes cluster info for telemetry kubernetes_version = get_server_version(configuration) kubernetes_distro = get_kubernetes_distro(configuration) kubernetes_properties = { 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro } telemetry.add_extension_event('connectedk8s', kubernetes_properties) # Checking helm installation check_helm_install(kube_config, kube_context) # Check helm version helm_version = check_helm_version(kube_config, kube_context) telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.HelmVersion': helm_version}) # Validate location utils.validate_location(cmd, location) resourceClient = _resource_client_factory(cmd.cli_ctx, subscription_id=subscription_id) # Check Release Existance release_namespace = get_release_namespace(kube_config, kube_context) if release_namespace: # Loading config map api_instance = kube_client.CoreV1Api( kube_client.ApiClient(configuration)) try: configmap = api_instance.read_namespaced_config_map( 'azure-clusterconfig', 'azure-arc') except Exception as e: # pylint: disable=broad-except utils.kubernetes_exception_handler( e, consts.Read_ConfigMap_Fault_Type, 'Unable to read ConfigMap', error_message= "Unable to read ConfigMap 'azure-clusterconfig' in 'azure-arc' namespace: ", message_for_not_found= "The helm release 'azure-arc' is present but the azure-arc namespace/configmap is missing. Please run 'helm delete azure-arc --no-hooks' to cleanup the release before onboarding the cluster again." ) configmap_rg_name = configmap.data["AZURE_RESOURCE_GROUP"] configmap_cluster_name = configmap.data["AZURE_RESOURCE_NAME"] if connected_cluster_exists(client, configmap_rg_name, configmap_cluster_name): if (configmap_rg_name.lower() == resource_group_name.lower() and configmap_cluster_name.lower() == cluster_name.lower()): # Re-put connected cluster try: public_key = client.get( configmap_rg_name, configmap_cluster_name).agent_public_key_certificate except Exception as e: # pylint: disable=broad-except utils.arm_exception_handler( e, consts.Get_ConnectedCluster_Fault_Type, 'Failed to check if connected cluster resource already exists.' ) cc = generate_request_payload(configuration, location, public_key, tags) create_cc_resource(client, resource_group_name, cluster_name, cc, no_wait) else: telemetry.set_user_fault() telemetry.set_exception( exception='The kubernetes cluster is already onboarded', fault_type=consts.Cluster_Already_Onboarded_Fault_Type, summary='Kubernetes cluster already onboarded') raise CLIError( "The kubernetes cluster you are trying to onboard " + "is already onboarded to the resource group" + " '{}' with resource name '{}'.".format( configmap_rg_name, configmap_cluster_name)) else: # Cleanup agents and continue with put delete_arc_agents(release_namespace, kube_config, kube_context, configuration) else: if connected_cluster_exists(client, resource_group_name, cluster_name): telemetry.set_user_fault() telemetry.set_exception( exception='The connected cluster resource already exists', fault_type=consts.Resource_Already_Exists_Fault_Type, summary='Connected cluster resource already exists') raise CLIError( "The connected cluster resource {} already exists ".format( cluster_name) + "in the resource group {} ".format(resource_group_name) + "and corresponds to a different Kubernetes cluster. To onboard this Kubernetes cluster" + "to Azure, specify different resource name or resource group name." ) # Resource group Creation if resource_group_exists(cmd.cli_ctx, resource_group_name, subscription_id) is False: resource_group_params = {'location': location} try: resourceClient.resource_groups.create_or_update( resource_group_name, resource_group_params) except Exception as e: # pylint: disable=broad-except utils.arm_exception_handler(e, consts.Create_ResourceGroup_Fault_Type, 'Failed to create the resource group') # Adding helm repo if os.getenv('HELMREPONAME') and os.getenv('HELMREPOURL'): utils.add_helm_repo(kube_config, kube_context) # Retrieving Helm chart OCI Artifact location registry_path = os.getenv('HELMREGISTRY') if os.getenv( 'HELMREGISTRY') else utils.get_helm_registry( cmd, location, dp_endpoint_dogfood, release_train_dogfood) # Get azure-arc agent version for telemetry azure_arc_agent_version = registry_path.split(':')[1] telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.AgentVersion': azure_arc_agent_version}) # Get helm chart path chart_path = utils.get_chart_path(registry_path, kube_config, kube_context) # Generate public-private key pair try: key_pair = RSA.generate(4096) except Exception as e: telemetry.set_exception( exception=e, fault_type=consts.KeyPair_Generate_Fault_Type, summary='Failed to generate public-private key pair') raise CLIError("Failed to generate public-private key pair. " + str(e)) try: public_key = get_public_key(key_pair) except Exception as e: telemetry.set_exception(exception=e, fault_type=consts.PublicKey_Export_Fault_Type, summary='Failed to export public key') raise CLIError("Failed to export public key." + str(e)) try: private_key_pem = get_private_key(key_pair) except Exception as e: telemetry.set_exception(exception=e, fault_type=consts.PrivateKey_Export_Fault_Type, summary='Failed to export private key') raise CLIError("Failed to export private key." + str(e)) # Generate request payload cc = generate_request_payload(configuration, location, public_key, tags) # Create connected cluster resource put_cc_response = create_cc_resource(client, resource_group_name, cluster_name, cc, no_wait) # Install azure-arc agents helm_install_release(chart_path, subscription_id, kubernetes_distro, resource_group_name, cluster_name, location, onboarding_tenant_id, http_proxy, https_proxy, no_proxy, private_key_pem, kube_config, kube_context, no_wait, values_file_provided, values_file) return put_cc_response
def send_cloud_telemetry(cmd): cloud_name = cmd.cli_ctx.cloud.name telemetry.add_extension_event( 'connectedk8s', {'Context.Default.AzureCLI.AzureCloud': cloud_name})
def create_connectedk8s(cmd, client, resource_group_name, cluster_name, location=None, kube_config=None, kube_context=None, no_wait=False, tags=None): logger.warning("Ensure that you have the latest helm version installed before proceeding.") logger.warning("This operation might take a while...\n") # Setting subscription id subscription_id = get_subscription_id(cmd.cli_ctx) # Setting user profile profile = Profile(cli_ctx=cmd.cli_ctx) # Fetching Tenant Id graph_client = _graph_client_factory(cmd.cli_ctx) onboarding_tenant_id = graph_client.config.tenant_id # Setting kubeconfig kube_config = set_kube_config(kube_config) # Removing quotes from kubeconfig path. This is necessary for windows OS. trim_kube_config(kube_config) # Loading the kubeconfig file in kubernetes client configuration try: config.load_kube_config(config_file=kube_config, context=kube_context) except Exception as e: telemetry.set_user_fault() telemetry.set_exception(exception=e, fault_type=Load_Kubeconfig_Fault_Type, summary='Problem loading the kubeconfig file') raise CLIError("Problem loading the kubeconfig file." + str(e)) configuration = kube_client.Configuration() # Checking the connection to kubernetes cluster. # This check was added to avoid large timeouts when connecting to AAD Enabled AKS clusters # if the user had not logged in. check_kube_connection(configuration) # Get kubernetes cluster info for telemetry kubernetes_version = get_server_version(configuration) kubernetes_distro = get_kubernetes_distro(configuration) kubernetes_properties = { 'Context.Default.AzureCLI.KubernetesVersion': kubernetes_version, 'Context.Default.AzureCLI.KubernetesDistro': kubernetes_distro } telemetry.add_extension_event('connectedk8s', kubernetes_properties) # Checking helm installation check_helm_install(kube_config, kube_context) # Check helm version helm_version = check_helm_version(kube_config, kube_context) telemetry.add_extension_event('connectedk8s', {'Context.Default.AzureCLI.HelmVersion': helm_version}) # Validate location rp_locations = [] resourceClient = _resource_client_factory(cmd.cli_ctx, subscription_id=subscription_id) providerDetails = resourceClient.providers.get('Microsoft.Kubernetes') for resourceTypes in providerDetails.resource_types: if resourceTypes.resource_type == 'connectedClusters': rp_locations = [location.replace(" ", "").lower() for location in resourceTypes.locations] if location.lower() not in rp_locations: telemetry.set_user_fault() telemetry.set_exception(exception='Location not supported', fault_type=Invalid_Location_Fault_Type, summary='Provided location is not supported for creating connected clusters') raise CLIError("Connected cluster resource creation is supported only in the following locations: " + ', '.join(map(str, rp_locations)) + ". Use the --location flag to specify one of these locations.") break # Check Release Existance release_namespace = get_release_namespace(kube_config, kube_context) if release_namespace is not None: # Loading config map api_instance = kube_client.CoreV1Api(kube_client.ApiClient(configuration)) try: configmap = api_instance.read_namespaced_config_map('azure-clusterconfig', 'azure-arc') except Exception as e: # pylint: disable=broad-except telemetry.set_exception(exception=e, fault_type=Read_ConfigMap_Fault_Type, summary='Unable to read ConfigMap') raise CLIError("Unable to read ConfigMap 'azure-clusterconfig' in 'azure-arc' namespace: %s\n" % e) configmap_rg_name = configmap.data["AZURE_RESOURCE_GROUP"] configmap_cluster_name = configmap.data["AZURE_RESOURCE_NAME"] if connected_cluster_exists(client, configmap_rg_name, configmap_cluster_name): if (configmap_rg_name.lower() == resource_group_name.lower() and configmap_cluster_name.lower() == cluster_name.lower()): # Re-put connected cluster public_key = client.get(configmap_rg_name, configmap_cluster_name).agent_public_key_certificate cc = generate_request_payload(configuration, location, public_key, tags) try: return sdk_no_wait(no_wait, client.create, resource_group_name=resource_group_name, cluster_name=cluster_name, connected_cluster=cc) except CloudError as ex: telemetry.set_exception(exception=ex, fault_type=Create_ConnectedCluster_Fault_Type, summary='Unable to create connected cluster resource') raise CLIError(ex) else: telemetry.set_user_fault() telemetry.set_exception(exception='The kubernetes cluster is already onboarded', fault_type=Cluster_Already_Onboarded_Fault_Type, summary='Kubernetes cluster already onboarded') raise CLIError("The kubernetes cluster you are trying to onboard " + "is already onboarded to the resource group" + " '{}' with resource name '{}'.".format(configmap_rg_name, configmap_cluster_name)) else: # Cleanup agents and continue with put delete_arc_agents(release_namespace, kube_config, kube_context, configuration) else: if connected_cluster_exists(client, resource_group_name, cluster_name): telemetry.set_user_fault() telemetry.set_exception(exception='The connected cluster resource already exists', fault_type=Resource_Already_Exists_Fault_Type, summary='Connected cluster resource already exists') raise CLIError("The connected cluster resource {} already exists ".format(cluster_name) + "in the resource group {} ".format(resource_group_name) + "and corresponds to a different Kubernetes cluster. To onboard this Kubernetes cluster" + "to Azure, specify different resource name or resource group name.") # Resource group Creation if resource_group_exists(cmd.cli_ctx, resource_group_name, subscription_id) is False: resource_group_params = {'location': location} try: resourceClient.resource_groups.create_or_update(resource_group_name, resource_group_params) except Exception as e: telemetry.set_exception(exception=e, fault_type=Create_ResourceGroup_Fault_Type, summary='Failed to create the resource group') raise CLIError("Failed to create the resource group {} :".format(resource_group_name) + str(e)) # Adding helm repo if os.getenv('HELMREPONAME') and os.getenv('HELMREPOURL'): repo_name = os.getenv('HELMREPONAME') repo_url = os.getenv('HELMREPOURL') cmd_helm_repo = ["helm", "repo", "add", repo_name, repo_url, "--kubeconfig", kube_config] if kube_context: cmd_helm_repo.extend(["--kube-context", kube_context]) response_helm_repo = Popen(cmd_helm_repo, stdout=PIPE, stderr=PIPE) _, error_helm_repo = response_helm_repo.communicate() if response_helm_repo.returncode != 0: telemetry.set_exception(exception=error_helm_repo.decode("ascii"), fault_type=Add_HelmRepo_Fault_Type, summary='Failed to add helm repository') raise CLIError("Unable to add repository {} to helm: ".format(repo_url) + error_helm_repo.decode("ascii")) # Retrieving Helm chart OCI Artifact location registry_path = os.getenv('HELMREGISTRY') if os.getenv('HELMREGISTRY') else get_helm_registry(profile, location) # Get azure-arc agent version for telemetry azure_arc_agent_version = registry_path.split(':')[1] telemetry.add_extension_event('connectedk8s', {'Context.Default.AzureCLI.AgentVersion': azure_arc_agent_version}) # Pulling helm chart from registry os.environ['HELM_EXPERIMENTAL_OCI'] = '1' pull_helm_chart(registry_path, kube_config, kube_context) # Exporting helm chart chart_export_path = os.path.join(os.path.expanduser('~'), '.azure', 'AzureArcCharts') export_helm_chart(registry_path, chart_export_path, kube_config, kube_context) # Generate public-private key pair try: key_pair = RSA.generate(4096) except Exception as e: telemetry.set_exception(exception=e, fault_type=KeyPair_Generate_Fault_Type, summary='Failed to generate public-private key pair') raise CLIError("Failed to generate public-private key pair. " + str(e)) try: public_key = get_public_key(key_pair) except Exception as e: telemetry.set_exception(exception=e, fault_type=PublicKey_Export_Fault_Type, summary='Failed to export public key') raise CLIError("Failed to export public key." + str(e)) try: private_key_pem = get_private_key(key_pair) except Exception as e: telemetry.set_exception(exception=e, fault_type=PrivateKey_Export_Fault_Type, summary='Failed to export private key') raise CLIError("Failed to export private key." + str(e)) # Helm Install helm_chart_path = os.path.join(chart_export_path, 'azure-arc-k8sagents') chart_path = os.getenv('HELMCHART') if os.getenv('HELMCHART') else helm_chart_path cmd_helm_install = ["helm", "upgrade", "--install", "azure-arc", chart_path, "--set", "global.subscriptionId={}".format(subscription_id), "--set", "global.kubernetesDistro={}".format(kubernetes_distro), "--set", "global.resourceGroupName={}".format(resource_group_name), "--set", "global.resourceName={}".format(cluster_name), "--set", "global.location={}".format(location), "--set", "global.tenantId={}".format(onboarding_tenant_id), "--set", "global.onboardingPrivateKey={}".format(private_key_pem), "--set", "systemDefaultValues.spnOnboarding=false", "--kubeconfig", kube_config, "--output", "json"] if kube_context: cmd_helm_install.extend(["--kube-context", kube_context]) response_helm_install = Popen(cmd_helm_install, stdout=PIPE, stderr=PIPE) _, error_helm_install = response_helm_install.communicate() if response_helm_install.returncode != 0: telemetry.set_exception(exception=error_helm_install.decode("ascii"), fault_type=Install_HelmRelease_Fault_Type, summary='Unable to install helm release') raise CLIError("Unable to install helm release: " + error_helm_install.decode("ascii")) # Create connected cluster resource cc = generate_request_payload(configuration, location, public_key, tags) try: put_cc_response = sdk_no_wait(no_wait, client.create, resource_group_name=resource_group_name, cluster_name=cluster_name, connected_cluster=cc) if no_wait: return put_cc_response except CloudError as ex: telemetry.set_exception(exception=ex, fault_type=Create_ConnectedCluster_Fault_Type, summary='Unable to create connected cluster resource') raise CLIError(ex) # Getting total number of pods scheduled to run in azure-arc namespace api_instance = kube_client.CoreV1Api(kube_client.ApiClient(configuration)) pod_dict = get_pod_dict(api_instance) # Checking azure-arc pod statuses try: check_pod_status(pod_dict) except Exception as e: # pylint: disable=broad-except telemetry.set_exception(exception=e, fault_type=Check_PodStatus_Fault_Type, summary='Failed to check arc agent pods statuses') logger.warning("Failed to check arc agent pods statuses: %s", e) return put_cc_response
def start_ssh_connection(op_info, delete_keys, delete_cert): try: # Initialize these so that if something fails in the try block before these # are initialized, then the finally block won't fail. cleanup_process = None log_file = None connection_status = None ssh_arg_list = [] if op_info.ssh_args: ssh_arg_list = op_info.ssh_args env = os.environ.copy() if op_info.is_arc(): env['SSHPROXY_RELAY_INFO'] = connectivity_utils.format_relay_info_string( op_info.relay_info) # Get ssh client before starting the clean up process in case there is an error in getting client. command = [ get_ssh_client_path('ssh', op_info.ssh_client_folder), op_info.get_host() ] if not op_info.cert_file and not op_info.private_key_file: # In this case, even if delete_credentials is true, there is nothing to clean-up. op_info.delete_credentials = False log_file, ssh_arg_list, cleanup_process = _start_cleanup( op_info.cert_file, op_info.private_key_file, op_info.public_key_file, op_info.delete_credentials, delete_keys, delete_cert, ssh_arg_list) command = command + op_info.build_args() + ssh_arg_list connection_duration = time.time() logger.debug("Running ssh command %s", ' '.join(command)) # pylint: disable=subprocess-run-check try: if set(['-v', '-vv', '-vvv']).isdisjoint(ssh_arg_list) or log_file: connection_status = subprocess.run( command, shell=platform.system() == 'Windows', env=env, stderr=subprocess.PIPE, encoding='utf-8') else: # Logs are sent to stderr. In that case, we shouldn't capture stderr. connection_status = subprocess.run( command, shell=platform.system() == 'Windows', env=env) except OSError as e: colorama.init() raise azclierror.BadRequestError( f"Failed to run ssh command with error: {str(e)}.", const.RECOMMENDATION_SSH_CLIENT_NOT_FOUND) connection_duration = (time.time() - connection_duration) / 60 ssh_connection_data = { 'Context.Default.AzureCLI.SSHConnectionDurationInMinutes': connection_duration } if connection_status and connection_status.returncode == 0: ssh_connection_data[ 'Context.Default.AzureCLI.SSHConnectionStatus'] = "Success" telemetry.add_extension_event('ssh', ssh_connection_data) finally: # Even if something fails between the creation of the credentials and the end of the ssh connection, we # want to make sure that all credentials are cleaned up, and that the clean up process is terminated. _terminate_cleanup(delete_keys, delete_cert, op_info.delete_credentials, cleanup_process, op_info.cert_file, op_info.private_key_file, op_info.public_key_file, log_file, connection_status)
def _decide_resource_type(cmd, op_info): # If the user provides an IP address the target will be treated as an Azure VM even if it is an # Arc Server. Which just means that the Connectivity Proxy won't be used to establish connection. is_arc_server = False is_azure_vm = False if op_info.ip: is_azure_vm = True vm = None elif op_info.resource_type: if op_info.resource_type.lower() == "microsoft.hybridcompute": arc, arc_error, is_arc_server = _check_if_arc_server(cmd, op_info.resource_group_name, op_info.vm_name) if not is_arc_server: colorama.init() if isinstance(arc_error, ResourceNotFoundError): raise azclierror.ResourceNotFoundError(f"The resource {op_info.vm_name} in the resource group " f"{op_info.resource_group_name} was not found.", const.RECOMMENDATION_RESOURCE_NOT_FOUND) raise azclierror.BadRequestError("Unable to determine that the target machine is an Arc Server. " f"Error:\n{str(arc_error)}", const.RECOMMENDATION_RESOURCE_NOT_FOUND) elif op_info.resource_type.lower() == "microsoft.compute": vm, vm_error, is_azure_vm = _check_if_azure_vm(cmd, op_info.resource_group_name, op_info.vm_name) if not is_azure_vm: colorama.init() if isinstance(vm_error, ResourceNotFoundError): raise azclierror.ResourceNotFoundError(f"The resource {op_info.vm_name} in the resource group " f"{op_info.resource_group_name} was not found.", const.RECOMMENDATION_RESOURCE_NOT_FOUND) raise azclierror.BadRequestError("Unable to determine that the target machine is an Azure VM. " f"Error:\n{str(vm_error)}", const.RECOMMENDATION_RESOURCE_NOT_FOUND) else: vm, vm_error, is_azure_vm = _check_if_azure_vm(cmd, op_info.resource_group_name, op_info.vm_name) arc, arc_error, is_arc_server = _check_if_arc_server(cmd, op_info.resource_group_name, op_info.vm_name) if is_azure_vm and is_arc_server: colorama.init() raise azclierror.BadRequestError(f"{op_info.resource_group_name} has Azure VM and Arc Server with the " f"same name: {op_info.vm_name}.", colorama.Fore.YELLOW + "Please provide a --resource-type." + colorama.Style.RESET_ALL) if not is_azure_vm and not is_arc_server: colorama.init() if isinstance(arc_error, ResourceNotFoundError) and isinstance(vm_error, ResourceNotFoundError): raise azclierror.ResourceNotFoundError(f"The resource {op_info.vm_name} in the resource group " f"{op_info.resource_group_name} was not found. ", const.RECOMMENDATION_RESOURCE_NOT_FOUND) raise azclierror.BadRequestError("Unable to determine the target machine type as Azure VM or " f"Arc Server. Errors:\n{str(arc_error)}\n{str(vm_error)}", const.RECOMMENDATION_RESOURCE_NOT_FOUND) # Note: We are not able to determine the os of the target if the user only provides an IP address. os_type = None if is_azure_vm and vm and vm.storage_profile and vm.storage_profile.os_disk and vm.storage_profile.os_disk.os_type: os_type = vm.storage_profile.os_disk.os_type if is_arc_server and arc and arc.properties and arc.properties and arc.properties.os_name: os_type = arc.properties.os_name if os_type: telemetry.add_extension_event('ssh', {'Context.Default.AzureCLI.TargetOSType': os_type}) # Note 2: This is a temporary check while AAD login is not enabled for Windows. if os_type and os_type.lower() == 'windows' and not op_info.local_user: colorama.init() raise azclierror.RequiredArgumentMissingError("SSH Login using AAD credentials is not currently supported " "for Windows.", colorama.Fore.YELLOW + "Please provide --local-user." + colorama.Style.RESET_ALL) target_resource_type = "Microsoft.Compute" if is_arc_server: target_resource_type = "Microsoft.HybridCompute" telemetry.add_extension_event('ssh', {'Context.Default.AzureCLI.TargetResourceType': target_resource_type}) return target_resource_type