Skip to content

Commit

Permalink
Merge pull request #51 from sassoftware/develop
Browse files Browse the repository at this point in the history
Merge develop to main branch
  • Loading branch information
kevinlinglesas authored Nov 17, 2020
2 parents b09c7cf + b096e45 commit f2d4a60
Show file tree
Hide file tree
Showing 29 changed files with 1,090 additions and 363 deletions.
179 changes: 53 additions & 126 deletions deployment_report/deployment_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,19 @@
# SPDX-License-Identifier: Apache-2.0 ###
# ###
####################################################################
import getopt
import os
import sys

from typing import List, Optional, Text, Tuple
from argparse import ArgumentParser
from typing import List, Text

from deployment_report.model.viya_deployment_report import ViyaDeploymentReport

from viya_ark_library.command import Command
from viya_ark_library.lrp_indicator import LRPIndicator
from viya_ark_library.k8s.sas_k8s_errors import KubectlRequestForbiddenError, NamespaceNotFoundError
from viya_ark_library.k8s.sas_kubectl import Kubectl

# command line options
_DATA_FILE_OPT_SHORT_ = "d"
_DATA_FILE_OPT_LONG_ = "data-file-only"
_DATA_FILE_OPT_DESC_TMPL_ = " -{}, --{:<30} (Optional) Generate only the report data JSON file."

_KUBECTL_GLOBAL_OPT_SHORT_ = "k"
_KUBECTL_GLOBAL_OPT_LONG_ = "kubectl-global-opts"
_KUBECTL_GLOBAL_OPT_DESC_TMPL_ = " -{}, --{:<30} (Optional) Any kubectl global options to use with all executions " \
"(excluding namespace, which should be set using -n, --namespace=)."

_POD_SNIP_OPT_SHORT_ = "l"
_POD_SNIP_OPT_LONG_ = "include-pod-log-snips"
_POD_SNIP_OPT_DESC_TMPL_ = " -{}, --{:<30} (Optional) Include a 10-line log snippet for each pod container " \
"(increases command runtime and file size)."

_NAMESPACE_OPT_SHORT_ = "n"
_NAMESPACE_OPT_LONG_ = "namespace"
_NAMESPACE_OPT_DESC_TMPL_ = " -{}, --{:<30} (Optional) The namespace to target containing SAS software, if not " \
"defined by KUBECONFIG."

_OUTPUT_DIR_OPT_SHORT_ = "o"
_OUTPUT_DIR_OPT_LONG_ = "output-dir"
_OUTPUT_DIR_OPT_DESC_TMPL_ = " -{}, --{:<30} (Optional) Existing directory where report files will be written."

_RESOURCE_DEF_OPT_SHORT_ = "r"
_RESOURCE_DEF_OPT_LONG_ = "include-resource-definitions"
_RESOURCE_DEF_OPT_DESC_TMPL_ = " -{}, --{:<30} (Optional) Include the full JSON resource definition for each " \
"object in the report (increases file size)."

_HELP_OPT_SHORT_ = "h"
_HELP_OPT_LONG_ = "help"
_HELP_OPT_DESC_TMPL_ = " -{}, --{:<30} Print usage."

# command line return codes #
_SUCCESS_RC_ = 0
_BAD_OPT_RC_ = 1
Expand Down Expand Up @@ -103,72 +71,47 @@ def main(argv: List):
:param argv: The parameters passed to the script at execution.
"""
data_file_only: bool = False
kubectl_global_opts: Text = ""
include_pod_log_snips: bool = False
namespace: Optional[Text] = None
output_dir: Text = "./"
include_resource_definitions: bool = False

# define the short options for this script
short_opts: Text = (f"{_DATA_FILE_OPT_SHORT_}"
f"{_KUBECTL_GLOBAL_OPT_SHORT_}:"
f"{_POD_SNIP_OPT_SHORT_}"
f"{_NAMESPACE_OPT_SHORT_}:"
f"{_OUTPUT_DIR_OPT_SHORT_}:"
f"{_RESOURCE_DEF_OPT_SHORT_}"
f"{_HELP_OPT_SHORT_}")

# define the long options for this script
long_opts: List[Text] = [_DATA_FILE_OPT_LONG_,
f"{_KUBECTL_GLOBAL_OPT_LONG_}=",
_POD_SNIP_OPT_LONG_,
f"{_NAMESPACE_OPT_LONG_}=",
f"{_OUTPUT_DIR_OPT_LONG_}=",
_RESOURCE_DEF_OPT_LONG_,
_HELP_OPT_LONG_]

# get command line options
opts: Tuple = tuple()
try:
opts, args = getopt.getopt(argv, short_opts, long_opts)
except getopt.GetoptError as opt_error:
print()
print(f"ERROR: {opt_error}", file=sys.stderr)
usage(_BAD_OPT_RC_)

# process opts
for opt, arg in opts:
if opt in (f"-{_DATA_FILE_OPT_SHORT_}", f"--{_DATA_FILE_OPT_LONG_}"):
data_file_only = True

elif opt in (f"-{_KUBECTL_GLOBAL_OPT_SHORT_}", f"--{_KUBECTL_GLOBAL_OPT_LONG_}"):
kubectl_global_opts = arg

elif opt in (f"-{_POD_SNIP_OPT_SHORT_}", f"--{_POD_SNIP_OPT_LONG_}"):
include_pod_log_snips = True

elif opt in (f"-{_NAMESPACE_OPT_SHORT_}", f"--{_NAMESPACE_OPT_LONG_}"):
namespace = arg

elif opt in (f"-{_OUTPUT_DIR_OPT_SHORT_}", f"--{_OUTPUT_DIR_OPT_LONG_}"):
output_dir = arg

elif opt in (f"-{_RESOURCE_DEF_OPT_SHORT_}", f"--{_RESOURCE_DEF_OPT_LONG_}"):
include_resource_definitions = True

elif opt in (f"-{_HELP_OPT_SHORT_}", f"--{_HELP_OPT_LONG_}"):
usage(_SUCCESS_RC_)

else:
print()
print(f"ERROR: option {opt} not recognized", file=sys.stderr)
usage(_BAD_OPT_RC_)
# configure ArgumentParser
arg_parser: ArgumentParser = ArgumentParser(prog=f"viya-ark.py {ViyaDeploymentReportCommand.command_name()}",
description=ViyaDeploymentReportCommand.command_desc())

# add optional arguments
# data-file-only
arg_parser.add_argument(
"-d", "--data-file-only", action="store_true", dest="data_file_only",
help="Generate only the JSON-formatted data.")
# kubectl-global-opts
arg_parser.add_argument(
"-k", "--kubectl-global-opts", type=Text, default="", dest="kubectl_global_opts",
help="Any kubectl global options to use with all executions (excluding namespace, which should be set using "
"-n, --namespace).")
# include-pod-log-snips
arg_parser.add_argument(
"-l", "--include-pod-log-snips", action="store_true", dest="include_pod_log_snips",
help="Include the most recent log lines (up to 10) for each container in the report. This option increases "
"command runtime and file size.")
# namespace
arg_parser.add_argument(
"-n", "--namespace", type=Text, default=None, dest="namespace",
help="Namespace to target containing SAS software, if not defined by KUBECONFIG.")
# output-dir
arg_parser.add_argument(
"-o", "--output-dir", type=Text, default=ViyaDeploymentReport.OUTPUT_DIRECTORY_DEFAULT, dest="output_dir",
help="Directory where log files will be written. Defaults to "
f"\"{ViyaDeploymentReport.OUTPUT_DIRECTORY_DEFAULT}\".")
# include-resource-definitions
arg_parser.add_argument(
"-r", "--include-resource-definitions", action="store_true", dest="include_resource_definitions",
help="Include the full JSON-formatted definition for each resource in the report. This option increases file "
"size.")

# parse the args passed to this command
args = arg_parser.parse_args(argv)

# initialize the kubectl object
# this will also verify the connection to the cluster and if the namespace is valid, if provided
try:
kubectl: Kubectl = Kubectl(namespace=namespace, global_opts=kubectl_global_opts)
kubectl: Kubectl = Kubectl(namespace=args.namespace, global_opts=args.kubectl_global_opts)
except ConnectionError as e:
print()
print(f"ERROR: {e}", file=sys.stderr)
Expand All @@ -185,8 +128,10 @@ def main(argv: List):

# gather the details for the report
try:
sas_deployment_report.gather_details(kubectl=kubectl,
include_pod_log_snips=include_pod_log_snips)
print()
with LRPIndicator(enter_message="Generating deployment report"):
sas_deployment_report.gather_details(kubectl=kubectl,
include_pod_log_snips=args.include_pod_log_snips)
except KubectlRequestForbiddenError as e:
print()
print(f"ERROR: {e}", file=sys.stderr)
Expand All @@ -198,35 +143,17 @@ def main(argv: List):
print()
sys.exit(_RUNTIME_ERROR_RC_)

sas_deployment_report.write_report(output_directory=output_dir,
data_file_only=data_file_only,
include_resource_definitions=include_resource_definitions)

sys.exit(_SUCCESS_RC_)

data_file, html_file = sas_deployment_report.write_report(
output_directory=args.output_dir,
data_file_only=args.data_file_only,
include_resource_definitions=args.include_resource_definitions)

###################
# usage() #
###################
def usage(exit_code: int):
"""
Prints the usage information for the deployment-report command and exits the program with the given exit_code.
:param exit_code: The exit code to return when exiting the program.
"""
print()
print(f"Usage: {ViyaDeploymentReportCommand.command_name()} [<options>]")
print(f"\nCreated: {data_file}")
if not args.data_file_only:
print(f"Created: {html_file}")
print()
print("Options:")
print(_DATA_FILE_OPT_DESC_TMPL_.format(_DATA_FILE_OPT_SHORT_, _DATA_FILE_OPT_LONG_))
print(_KUBECTL_GLOBAL_OPT_DESC_TMPL_.format(_KUBECTL_GLOBAL_OPT_SHORT_, _KUBECTL_GLOBAL_OPT_LONG_ + "=\"<opts>\""))
print(_POD_SNIP_OPT_DESC_TMPL_.format(_POD_SNIP_OPT_SHORT_, _POD_SNIP_OPT_LONG_))
print(_NAMESPACE_OPT_DESC_TMPL_.format(_NAMESPACE_OPT_SHORT_, _NAMESPACE_OPT_LONG_ + "=\"<namespace>\""))
print(_OUTPUT_DIR_OPT_DESC_TMPL_.format(_OUTPUT_DIR_OPT_SHORT_, _OUTPUT_DIR_OPT_LONG_ + "=\"<dir>\""))
print(_RESOURCE_DEF_OPT_DESC_TMPL_.format(_RESOURCE_DEF_OPT_SHORT_, _RESOURCE_DEF_OPT_LONG_))
print(_HELP_OPT_DESC_TMPL_.format(_HELP_OPT_SHORT_, _HELP_OPT_LONG_))
print()
sys.exit(exit_code)

sys.exit(_SUCCESS_RC_)


####################
Expand Down
25 changes: 13 additions & 12 deletions deployment_report/model/viya_deployment_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,21 @@ class ViyaDeploymentReport(object):
The gathered data can be written to disk as an HTML report and a JSON file containing the gathered data.
"""

# default values for arguments shared between the model and command #
DATA_FILE_ONLY_DEFAULT: bool = False
INCLUDE_POD_LOG_SNIPS_DEFAULT: bool = False
INCLUDE_RESOURCE_DEFINITIONS_DEFAULT: bool = False
OUTPUT_DIRECTORY_DEFAULT: Text = "./"

def __init__(self) -> None:
"""
Constructor for ViyaDeploymentReport object.
"""
self._report_data = None

def gather_details(self, kubectl: KubectlInterface, include_pod_log_snips: bool = False) -> None:
def gather_details(self, kubectl: KubectlInterface,
include_pod_log_snips: bool = INCLUDE_POD_LOG_SNIPS_DEFAULT) -> None:
"""
This method executes the gathering of Kubernetes resources related to SAS components.
Before this method is executed class fields will have a None value. This method will
Expand Down Expand Up @@ -496,9 +504,10 @@ def get_sas_component_resources(self, component_name: Text, resource_kind: Text)
except KeyError:
return None

def write_report(self, output_directory: Text = "", data_file_only: bool = False,
include_resource_definitions: bool = False, file_timestamp: Optional[Text] = None) \
-> Tuple[Optional[AnyStr], Optional[AnyStr]]:
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,
file_timestamp: Optional[Text] = None) -> Tuple[Optional[AnyStr], Optional[AnyStr]]:
"""
Writes the report data to a file as a JSON string.
Expand All @@ -522,14 +531,10 @@ def write_report(self, output_directory: Text = "", data_file_only: bool = False
# convert the data to a JSON string #
data_json = json.dumps(self._report_data, cls=KubernetesObjectJSONEncoder, indent=4, sort_keys=True)

# blank line for output readability #
print()

# write the report data #
data_file_path: Text = output_directory + _REPORT_DATA_FILE_NAME_TMPL_.format(file_timestamp)
with open(data_file_path, "w+") as data_file:
data_file.write(data_json)
print("Created: {}".format(os.path.abspath(data_file_path)))

# write the html file, if requested #
html_file_path: Optional[Text] = None
Expand All @@ -541,9 +546,5 @@ def write_report(self, output_directory: Text = "", data_file_only: bool = False
trim_blocks=True, lstrip_blocks=True,
report_data=json.loads(data_json),
include_definitions=include_resource_definitions)
print("Created: {}".format(html_file_path))

# blank line for output readability #
print()

return os.path.abspath(data_file_path), html_file_path
12 changes: 0 additions & 12 deletions deployment_report/test/data/expected_command_output.txt

This file was deleted.

27 changes: 27 additions & 0 deletions deployment_report/test/data/expected_usage_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
usage: viya-ark.py deployment-report [-h] [-d] [-k KUBECTL_GLOBAL_OPTS] [-l]
[-n NAMESPACE] [-o OUTPUT_DIR] [-r]

Generate a deployment report of SAS components for a target Kubernetes
environment.

optional arguments:
-h, --help show this help message and exit
-d, --data-file-only Generate only the JSON-formatted data.
-k KUBECTL_GLOBAL_OPTS, --kubectl-global-opts KUBECTL_GLOBAL_OPTS
Any kubectl global options to use with all executions
(excluding namespace, which should be set using -n,
--namespace).
-l, --include-pod-log-snips
Include the most recent log lines (up to 10) for each
container in the report. This option increases command
runtime and file size.
-n NAMESPACE, --namespace NAMESPACE
Namespace to target containing SAS software, if not
defined by KUBECONFIG.
-o OUTPUT_DIR, --output-dir OUTPUT_DIR
Directory where log files will be written. Defaults to
"./".
-r, --include-resource-definitions
Include the full JSON-formatted definition for each
resource in the report. This option increases file
size.
21 changes: 7 additions & 14 deletions deployment_report/test/test_deployment_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import os
import pytest

from deployment_report.deployment_report import ViyaDeploymentReportCommand, usage
from deployment_report.deployment_report import ViyaDeploymentReportCommand, main

####################################################################
# There is not unit test defined for:
Expand Down Expand Up @@ -48,27 +48,20 @@ def test_viya_deployment_report_command_command_desc():


def test_usage(capfd):
# test that a SystemExit is raised
with pytest.raises(SystemExit) as sys_exit:
usage(0)
_argv: list = ["-h"]

# make sure the exit value is correct
assert sys_exit.value.code == 0
# run main
with pytest.raises(SystemExit):
main(_argv)

# define expected output
current_dir = os.path.dirname(os.path.abspath(__file__))
test_data_file = os.path.join(current_dir, f"data{os.sep}expected_command_output.txt")
test_data_file = os.path.join(current_dir, f"data{os.sep}expected_usage_output.txt")

with open(test_data_file) as f:
expected = f.read()

# get the captured output
# get output
out, err = capfd.readouterr()

# assert that the captured output matches the expected
assert out == expected

# make sure that a non-zero exit code is correct
with pytest.raises(SystemExit) as sys_exit:
usage(5)
assert sys_exit.value.code == 5
Loading

0 comments on commit f2d4a60

Please sign in to comment.