Skip to content

Commit

Permalink
(Fix for Issue #85) Deployment Report: Traceback using Kubernetes Ser…
Browse files Browse the repository at this point in the history
…ver & 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+
  • Loading branch information
sasjowood authored May 10, 2021
1 parent fa5cb83 commit 2cae847
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 40 deletions.
39 changes: 20 additions & 19 deletions deployment_report/model/viya_deployment_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
29 changes: 19 additions & 10 deletions download_pod_logs/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()}]")
Expand Down
33 changes: 24 additions & 9 deletions viya_ark_library/k8s/sas_kubectl.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 #
Expand Down
4 changes: 3 additions & 1 deletion viya_ark_library/k8s/sas_kubectl_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
"""
Expand Down
2 changes: 1 addition & 1 deletion viya_ark_library/k8s/test_impl/sas_kubectl_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 2cae847

Please sign in to comment.