def get_users_samba_password(username: str) -> str: """ Returns samba password of a user with a given username :param username: name of a user :return: password of a user, In case of any problems during gathering of a password it raises KubectlIntError If password doesnt exist - it raises ValueError. """ error_message = Texts.GATHERING_PASSWORD_ERROR_MSG password = None try: api = get_k8s_api() secret = api.read_namespaced_secret("password", username) password = str(base64.b64decode(secret.data["password"]), encoding="utf-8") except ApiException as exe: if exe.status == HTTPStatus.NOT_FOUND: password = None else: logger.exception(error_message) raise KubernetesError(error_message) from exe except Exception as exe: logger.exception(error_message) raise KubernetesError(error_message) from exe if password is None: raise ValueError(Texts.LACK_OF_PASSWORD_ERROR_MSG) return str.strip(password)
def delete_namespace(namespace: str, propagate: bool = False): """ Removes a namespace with the given name :param namespace: namespace to be deleted :param propagate: If True - all objects in a namespace will be deleted In case of any problems (i.e. lack of privileges) it throws an exception """ try: api = get_k8s_api() propagation_policy = "Orphan" if propagate: propagation_policy = "Foreground" body = V1DeleteOptions(propagation_policy=propagation_policy) response = api.delete_namespace(namespace, body) if response.status != "{'phase': 'Terminating'}": error_description = Texts.NAMESPACE_DELETE_ERROR_MSG.format( namespace=namespace) logger.exception(error_description) raise KubernetesError(error_description) except Exception: error_description = Texts.NAMESPACE_DELETE_ERROR_MSG.format( namespace=namespace) logger.exception(error_description) raise KubernetesError(error_description)
def check_users_presence(username: str) -> UserState: """ Checks whether a user with a given name exists. It searches also for a namespace with a name equal to the given username :param username: username :return: returns a current state of user - as an item for UserState enum In case of problems during gathering user's data - it raises an exception. """ namespace = find_namespace(username) if namespace != NamespaceStatus.NOT_EXISTS: logger.debug("Namespace {} already exists.".format(username)) return UserState(namespace.value) try: user_data = User.get(username) if user_data and user_data.name == username: return UserState.ACTIVE else: return UserState.NOT_EXISTS except Exception as exe: error_message = Texts.USER_PRESENCE_CHECK_ERROR_MSG logger.error(error_message) raise KubernetesError(error_message) from exe
def get_pod_events(namespace: str, name: str = None) -> List[client.V1Event]: try: api = get_k8s_api() events = [] try: if name: event_list: client.V1EventList = api.list_namespaced_event( namespace=namespace, field_selector=f"involvedObject.name={name}") else: event_list: client.V1EventList = api.list_namespaced_event( namespace=namespace) events = event_list.items except ApiException as ex: if ex.status != HTTPStatus.NOT_FOUND: logger.exception('Exception when getting pod events') raise return events except Exception as exe: error_message = Texts.GATHERING_EVENTS_ERROR_MSG logger.exception(error_message) raise KubernetesError(error_message) from exe
def get_users_token(namespace: str) -> str: """ Gets a default token of a user from a given namespace :param namespace: namespace of a user :return: encoded token of a user - if it doesn't exist or errors occurred during gathering the token - function returns an empty string """ ret_token = "" try: api = get_k8s_api() tokens_list = api.list_namespaced_secret(namespace) if tokens_list: for token in tokens_list.items: if "default-token" in token.metadata.name: ret_token = str(base64.b64decode(token.data.get("token")), encoding="utf-8") break else: raise ValueError(Texts.LACK_OF_DEFAULT_TOKEN_ERROR_MSG) else: raise ValueError(Texts.EMPTY_LIST_OF_TOKENS_ERROR_MSG) except Exception as exe: error_message = Texts.GATHERING_USERS_TOKEN_ERROR_MSG logger.exception(error_message) raise KubernetesError(error_message) from exe return ret_token
def get_certificate(namespace: str) -> str: """ Gets a certificate of a user from a given namespace :param namespace: namespace of a user :return: certificate - if it doesn't exist or errors occurred during gathering the certificate - function returns an empty string """ ret_cert = "" try: api = get_k8s_api() secrets_list = api.list_namespaced_secret(namespace) if secrets_list: for secret in secrets_list.items: if "default-token" in secret.metadata.name: ret_cert = str(base64.b64decode(secret.data.get("ca.crt")), encoding="utf-8") break else: raise ValueError(Texts.LACK_OF_DEFAULT_TOKEN_ERROR_MSG) else: raise ValueError(Texts.EMPTY_LIST_OF_TOKENS_ERROR_MSG) except Exception as exe: error_message = Texts.GATHERING_USER_CERTIFICATE_ERROR_MSG logger.exception(error_message) raise KubernetesError(error_message) from exe return ret_cert
def get_top_for_pod(name: str, namespace: str) -> Tuple[str, str]: """ Returns cpu and memory usage for a pod with a given name located in a given namespace :param name: name of a pod :param namespace: namespace where the pod resided. Optional - if not given, function searches the pod in current namespace :return: tuple containing two values - cpu and memory usage expressed in k8s format """ top_command = ["kubectl", "top", "pod", name] if namespace: top_command.extend(["-n", namespace]) output, err_code, log_output = system.execute_system_command(top_command) if err_code: raise KubectlConnectionError( Texts.K8S_CLUSTER_NO_CONNECTION_ERROR_MSG.format( output=log_output)) if output: lines = output.split("\n") if lines and len(lines) > 1: second_line = lines[1] if second_line: split_second_line = second_line.split() if split_second_line and len(split_second_line) > 2: return (split_second_line[1], split_second_line[2]) logger.error(Texts.TOP_COMMAND_ERROR_LOG.format(output=log_output)) raise KubernetesError(Texts.TOP_COMMAND_ERROR)
def test_version_with_kubernetes_exception(mocker): config_map_mock = mocker.patch('util.config.NAUTAConfigMap.__init__') config_map_mock.side_effect = KubernetesError("") runner = CliRunner() result = runner.invoke(version.version, []) assert_version_table_rows(result.output, on_cmd_fail=True) assert Texts.KUBECTL_INT_ERROR_MSG + " " + VERBOSE_RERUN_MSG in result.output
def get_config_map_data(name: str, namespace: str, request_timeout: int = None) -> Dict[str, str]: """ Returns a dictionary taken from data section of a config_map with a given name located in the given namespace. :param name: name of a config map :param namespace: name of a namespace :param request_timeout: optional timeout for k8s request. Defaults inside k8s_api to 120 sec. :return: dictonary created based on data section of a config map. In case of any problems it raises an Exception """ try: api = get_k8s_api() ret_dict = api.read_namespaced_config_map(name, namespace, _request_timeout=request_timeout).data except Exception: error_description = Texts.CONFIG_MAP_ACCESS_ERROR_MSG.format(name=name) logger.exception(error_description) raise KubernetesError(error_description) return ret_dict
def patch_config_map_data(key: str, value: str, name: str, namespace: str): """ Function patches configmap with a given name and located in a given namespace. :param key: key identifying valueto be patched :param value: value :param name: name of a configmap :param namespace: namespace where configmap is located :return: raises an exception in case of any errors """ api = get_k8s_api() try: api.patch_namespaced_config_map(name=name, namespace=namespace, body={"data": {key: value}}) except ApiException as exe: error_message = Texts.PATCHING_CM_ERROR_MSG logger.exception(error_message) raise KubernetesError(error_message) from exe
def find_namespace(namespace: str) -> NamespaceStatus: """ Checks whether a namespace with a given name exists :param namespace: name of a namespace to be found :return: value from the NamespaceStatus enum """ api = get_k8s_api() try: namespace_def = api.read_namespace(namespace) if namespace_def and namespace_def.metadata and namespace_def.metadata.name == namespace: return NamespaceStatus(namespace_def.status.phase) except ApiException as e: if e.status == 404: return NamespaceStatus.NOT_EXISTS else: error_message = Texts.OTHER_FIND_NAMESPACE_ERROR logger.exception(error_message) raise KubernetesError(error_message) return NamespaceStatus.NOT_EXISTS
def start_port_forwarding( k8s_app_name: NAUTAAppNames, port: int = None, app_name: str = None, number_of_retries: int = 0, namespace: str = None) -> Tuple[subprocess.Popen, int, int]: """ Creates a proxy responsible for forwarding requests to and from a kubernetes' local docker proxy. In case of any errors during creating the process - throws a RuntimeError exception with a short description of a cause of a problem. When proxy created by this function is no longer needed - it should be closed by calling kill() function on a process returned by this function. :param k8s_app_name: name of kubernetes application for tunnel creation value taken from NAUTAAppNames enum :param port: if given - the system will try to use it as a local port. Random port will be used if that port is not available :return: instance of a process with proxy, tunneled port and container port """ logger.debug("Start port forwarding") try: service_node_port = None service_container_port = None app_services = get_app_services(nauta_app_name=k8s_app_name, namespace=namespace, app_name=app_name) if app_services: service_node_port = app_services[0].spec.ports[0].node_port if service_node_port: logger.debug('Service node port pod has been found: {}'.format( service_node_port)) service_container_port = app_services[0].spec.ports[0].port if service_container_port: logger.debug( 'Service container port has been found: {}'.format( service_container_port)) service_name = app_services[0].metadata.name namespace = app_services[0].metadata.namespace if not service_node_port and not service_container_port: logger.error(f'Cannot find open ports for {k8s_app_name} app') raise KubernetesError(Texts.PROXY_CREATION_MISSING_PORT_ERROR_MSG) if port and check_port_availability(port): tunnel_port = port else: tunnel_port = find_random_available_port() port_forward_command = [ 'kubectl', 'port-forward', f'--namespace={namespace}', f'service/{service_name}', f'{tunnel_port}:{service_container_port}', '-v=4' ] logger.debug(port_forward_command) process = None if number_of_retries: for i in range(number_of_retries - 1): try: process = system.execute_subprocess_command( port_forward_command) except Exception: logger.exception( "Error during setting up proxy - retrying.") else: break time.sleep(5) if not process: process = system.execute_subprocess_command(port_forward_command) except KubernetesError as exe: raise RuntimeError(exe) except LocalPortOccupiedError as exe: raise exe except Exception: raise RuntimeError(Texts.PROXY_CREATION_OTHER_ERROR_MSG) logger.info("Port forwarding - proxy set up") return process, tunnel_port, service_container_port