From 2cae847f9b2f7b85f1b147ddb46d3fe24a0068db Mon Sep 17 00:00:00 2001 From: Josh Woods <47215585+sasjowood@users.noreply.github.com> Date: Mon, 10 May 2021 10:38:03 -0400 Subject: [PATCH] (Fix for Issue #85) Deployment Report: Traceback using Kubernetes Server & Client (kubectl) version v1.19+ (#94) * (Issue #85) Deployment Report: Traceback using Kubernetes Server & Client (kubectl) version v1.19+ * (Issue #85) Deployment Report: Traceback using Kubernetes Server & Client (kubectl) version v1.19+ --- .../model/viya_deployment_report.py | 39 ++++++++++--------- download_pod_logs/model.py | 29 +++++++++----- viya_ark_library/k8s/sas_kubectl.py | 33 +++++++++++----- viya_ark_library/k8s/sas_kubectl_interface.py | 4 +- .../k8s/test_impl/sas_kubectl_test.py | 2 +- 5 files changed, 67 insertions(+), 40 deletions(-) diff --git a/deployment_report/model/viya_deployment_report.py b/deployment_report/model/viya_deployment_report.py index 38aeaba..a36b46e 100644 --- a/deployment_report/model/viya_deployment_report.py +++ b/deployment_report/model/viya_deployment_report.py @@ -521,25 +521,6 @@ def get_sas_component_resources(self, component_name: Text, resource_kind: Text) except KeyError: return None - def get_cadence_version(self, resource: KubernetesResource) -> Optional[Text]: - """ - Returns the combined key values from the 'data' dictionary. - - :param key: The key of the value to return. - :return: The value mapped to the given key, or None if the given key doesn't exist. - """ - cadence_info: Optional[Text] = None - try: - if 'sas-deployment-metadata' in resource.get_name(): - cadence_data = resource.get_data() - cadence_info = cadence_data['SAS_CADENCE_NAME'].capitalize() + ' ' + \ - cadence_data['SAS_CADENCE_VERSION'] + ' (' + \ - cadence_data['SAS_CADENCE_RELEASE'] + ')' - - return cadence_info - except KeyError: - return None - def write_report(self, output_directory: Text = OUTPUT_DIRECTORY_DEFAULT, data_file_only: bool = DATA_FILE_ONLY_DEFAULT, include_resource_definitions: bool = INCLUDE_RESOURCE_DEFINITIONS_DEFAULT, @@ -584,3 +565,23 @@ def write_report(self, output_directory: Text = OUTPUT_DIRECTORY_DEFAULT, include_definitions=include_resource_definitions) return os.path.abspath(data_file_path), html_file_path + + @staticmethod + def get_cadence_version(resource: KubernetesResource) -> Optional[Text]: + """ + Returns the cadence version of the targeted SAS deployment. + + :param resource: The key of the value to return. + :return: A string representing the cadence version of the targeted SAS deployment. + """ + cadence_info: Optional[Text] = None + try: + if 'sas-deployment-metadata' in resource.get_name(): + cadence_data: Optional[Dict] = resource.get_data() + cadence_info = \ + (f"{cadence_data['SAS_CADENCE_NAME'].capitalize()} {cadence_data['SAS_CADENCE_VERSION']} " + f"({cadence_data['SAS_CADENCE_RELEASE']})") + + return cadence_info + except KeyError: + return None diff --git a/download_pod_logs/model.py b/download_pod_logs/model.py index ad0c742..a51bba9 100644 --- a/download_pod_logs/model.py +++ b/download_pod_logs/model.py @@ -216,28 +216,37 @@ def _write_log(kubectl: KubectlInterface, pod: KubernetesResource, tail: int, ou output_file.writelines(("#" * 50) + "\n\n") # call kubectl to get the log for this container - log: List[AnyStr] = list() try: log = kubectl.logs(pod_name=pod.get_name(), container_name=container_status.get_name(), - prefix=False, tail=tail, ignore_errors=False) - except CalledProcessError as e: + prefix=False, tail=tail) + except CalledProcessError as container_err: # container log has error, we'll retrieve log from initContainers # add log from previous container call - log += [e.output.decode("utf-8")] - initcontainers: Optional[List[Dict]] - initcontainers = pod.get_spec_value(KubernetesResource.Keys.INIT_CONTAINERS) + # add anything returned in the raised error + if container_err.stdout: + log += str(container_err.stdout.decode()).splitlines() + + if container_err.stderr: + log += str(container_err.stderr.decode()).splitlines() + + initcontainers: Optional[List[Dict]] = pod.get_spec_value(KubernetesResource.Keys.INIT_CONTAINERS) if initcontainers: for initcontainer in initcontainers: container_name: Optional[Text] = initcontainer.get("name") log += ["\n" + "#" * 50] + [f"# Log from initContainer: {container_name}"] + ["#" * 50] try: - log += kubectl.logs(pod_name=pod.get_name(), - container_name=container_name, ignore_errors=True, prefix=False, - tail=tail) + log += kubectl.logs(pod_name=pod.get_name(), container_name=container_name, + prefix=False, tail=tail) + except CalledProcessError as initcontainer_err: + # add anything returned in the raised error + if initcontainer_err.stdout: + log += str(initcontainer_err.stdout.decode()).splitlines() + + if initcontainer_err.stderr: + log += str(initcontainer_err.stderr.decode()).splitlines() - except CalledProcessError: err_msg = (f"ERROR: A log could not be retrieved for the container " f"[{container_status.get_name()}] " f"in pod [{pod.get_name()}] in namespace [{kubectl.get_namespace()}]") diff --git a/viya_ark_library/k8s/sas_kubectl.py b/viya_ark_library/k8s/sas_kubectl.py index 82089fc..1a8300f 100644 --- a/viya_ark_library/k8s/sas_kubectl.py +++ b/viya_ark_library/k8s/sas_kubectl.py @@ -11,7 +11,7 @@ #################################################################### import json -from subprocess import CalledProcessError, check_output, STDOUT +from subprocess import CalledProcessError, Popen, PIPE from typing import AnyStr, Dict, List, Text, Union, Optional from viya_ark_library.k8s.sas_k8s_errors import NamespaceNotFoundError @@ -119,16 +119,31 @@ def __init__(self, executable: Text = "kubectl", namespace: Text = None, global_ def get_namespace(self) -> Text: return self.namespace - def do(self, command: Text, ignore_errors: bool = False) -> AnyStr: - # try to execute the command # - try: - return check_output(f"{self.exec} {command}", shell=True, stderr=STDOUT) - except CalledProcessError as e: - # raise the error if errors are not being ignored, otherwise print the error to stdout # + def do(self, command: Text, ignore_errors: bool = False, success_rcs: Optional[List[int]] = None) \ + -> AnyStr: + # set default return code list if one was not provided + if success_rcs is None: + success_rcs = [0] + + # define the process to run + proc: Popen = Popen(f"{self.exec} {command}", shell=True, stdout=PIPE, stderr=PIPE) + + # execute the procedure + stdout, stderr = proc.communicate() + + # get the return code + rc: int = proc.returncode + + # check if an acceptable code was returned + if rc not in success_rcs: if not ignore_errors: - raise e + raise CalledProcessError(returncode=rc, cmd=f"{self.exec} {command}", output=stdout, stderr=stderr) + else: + print(f"WARNING: Error encountered executing: {self.exec} {command} " + f"(rc: {rc} | stdout: {stdout} | stderr: {stderr})") - return e.output + # return the stdout + return stdout def api_resources(self, ignore_errors: bool = False) -> KubernetesApiResources: # get the api-resources and convert the response into a list # diff --git a/viya_ark_library/k8s/sas_kubectl_interface.py b/viya_ark_library/k8s/sas_kubectl_interface.py index f41b07e..d2e3970 100644 --- a/viya_ark_library/k8s/sas_kubectl_interface.py +++ b/viya_ark_library/k8s/sas_kubectl_interface.py @@ -30,13 +30,15 @@ def get_namespace(self) -> Text: pass @abstractmethod - def do(self, command: Text, ignore_errors: bool = False) -> AnyStr: + def do(self, command: Text, ignore_errors: bool = False, success_rcs: Optional[List[int]] = None) -> AnyStr: """ Generic method for executing a kubectl command. :param command: The kubectl command to execute. :param ignore_errors: True if errors encountered during execution should be ignored (a message will be printed to stdout), otherwise False. + :param success_rcs: A list of return codes representing a successful execution of the command. If a None value + is provided, the default list "[0]" will be used. :raises CalledProcessError: If the command returns a non-zero return code. :return: The stdout of the command that was executed. """ diff --git a/viya_ark_library/k8s/test_impl/sas_kubectl_test.py b/viya_ark_library/k8s/test_impl/sas_kubectl_test.py index c8ace26..c0f1a0e 100644 --- a/viya_ark_library/k8s/test_impl/sas_kubectl_test.py +++ b/viya_ark_library/k8s/test_impl/sas_kubectl_test.py @@ -312,7 +312,7 @@ def __init__(self, def get_namespace(self) -> Text: return self.namespace - def do(self, command: Text, ignore_errors: bool = False) -> AnyStr: + def do(self, command: Text, ignore_errors: bool = False, success_rcs: Optional[List[int]] = None) -> AnyStr: return "Not functional in testing implementation" def api_resources(self, ignore_errors: bool = False) -> KubernetesApiResources: