diff --git a/Makefile b/Makefile index bb3e0f7..1b4ecff 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,10 @@ endif build: ##@ Build the python package python setup.py sdist +clean: ##@ Clean up build + rm -rf dist/* + rm -rf redfish_utilities.egg-info + install: ##@ Install with pip pip install dist/redfish_utilities-${VERSION}.tar.gz @@ -53,4 +57,5 @@ install-uv: ##@ Install with uv uv pip install dist/redfish_utilities-${VERSION}.tar.gz lint: ##@ Run linting - black . \ No newline at end of file + black . + ruff check . \ No newline at end of file diff --git a/redfish_utilities/arguments.py b/redfish_utilities/arguments.py new file mode 100644 index 0000000..c31e90f --- /dev/null +++ b/redfish_utilities/arguments.py @@ -0,0 +1,43 @@ +import argparse + + +def validate_auth(args): + if (args.user and args.password and not args.session_token) or ( + args.session_token and not (args.user or args.password) + ): + return + else: + print("You must specify both --user and --password or --session-token") + quit(1) + + +def create_parent_parser(description: str = "", auth: bool = False, rhost: bool = False): + parent_parser = argparse.ArgumentParser(description=description, add_help=False) + + parent_parser.add_argument( + "--log-level", + choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + default="INFO", + help="Creates debug file showing HTTP traces and exceptions", + ) + parent_parser.add_argument("--log-to-console", action="store_true", help="Enable logging to console") + parent_parser.add_argument("--log-to-file", action="store_true", help="Enable logging to a file") + parent_parser.add_argument( + "--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions" + ) + + if auth: + parent_parser.add_argument("--user", "-u", type=str, help="The user name for authentication") + parent_parser.add_argument("--password", "-p", type=str, help="The password for authentication") + parent_parser.add_argument("--session-token", "-t", type=str, help="The session token for authentication") + + if rhost: + parent_parser.add_argument( + "--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)" + ) + + return parent_parser + + +def validate_args(args): + validate_auth(args) diff --git a/redfish_utilities/logger.py b/redfish_utilities/logger.py new file mode 100644 index 0000000..1f9ca89 --- /dev/null +++ b/redfish_utilities/logger.py @@ -0,0 +1,42 @@ +import logging +import datetime +import redfish +import os + + +def get_debug_level(level): + if level == "DEBUG": + return logging.DEBUG + elif level == "INFO": + return logging.INFO + elif level == "WARNING": + return logging.WARNING + elif level == "ERROR": + return logging.ERROR + elif level == "CRITICAL": + return logging.CRITICAL + else: + raise ValueError(f"Invalid debug level: {level}") + + +def setup_logger( + file_log: bool = False, stream_log: bool = True, log_level: str = "INFO", file_name: str = "redfish_utils" +): + log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + log_level = get_debug_level(log_level) + logger = logging.getLogger(__name__) + + if file_log: + file_name = os.path.basename(file_name) + timestamp = datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S") + log_file = f"{file_name}-{timestamp}.log".format() + logger = redfish.redfish_logger(log_file, log_format, log_level) + + if stream_log: + formatter = logging.Formatter(log_format) + sh = logging.StreamHandler() + sh.setFormatter(formatter) + logger.addHandler(sh) + logger.setLevel(log_level) + + return logger diff --git a/scripts/rf_diagnostic_data.py b/scripts/rf_diagnostic_data.py index 80fc191..2ac68c3 100644 --- a/scripts/rf_diagnostic_data.py +++ b/scripts/rf_diagnostic_data.py @@ -12,22 +12,22 @@ """ import argparse -import datetime -import logging + +# import logging import os import redfish import redfish_utilities import traceback import sys from redfish.messages import RedfishPasswordChangeRequiredError +from redfish_utilities.arguments import create_parent_parser, validate_args +from redfish_utilities.logger import setup_logger # Get the input arguments -argget = argparse.ArgumentParser( - description="A tool to collect diagnostic data from a log service on a Redfish service" -) -argget.add_argument("--user", "-u", type=str, required=True, help="The user name for authentication") -argget.add_argument("--password", "-p", type=str, required=True, help="The password for authentication") -argget.add_argument("--rhost", "-r", type=str, required=True, help="The address of the Redfish service (with scheme)") +description = "A tool to collect diagnostic data from a log service on a Redfish service" +parent_parser = create_parent_parser(description=description, auth=True, rhost=True) +argget = argparse.ArgumentParser(parents=[parent_parser]) + argget.add_argument( "--manager", "-m", type=str, nargs="?", default=False, help="The ID of the manager containing the log service" ) @@ -38,6 +38,7 @@ "--chassis", "-c", type=str, nargs="?", default=False, help="The ID of the chassis containing the log service" ) argget.add_argument("--log", "-l", type=str, help="The ID of the log service") + argget.add_argument( "--type", "-type", @@ -59,8 +60,13 @@ help="The directory to save the diagnostic data; defaults to the current directory if not specified", default=".", ) -argget.add_argument("--debug", action="store_true", help="Creates debug file showing HTTP traces and exceptions") + args = argget.parse_args() +validate_args(args) +logger = setup_logger( + file_log=args.log_to_file, stream_log=args.log_to_console, log_level=args.log_level, file_name=__file__ +) + # Determine the target log service based on the inputs # Effectively if the user gives multiple targets, some will be ignored @@ -76,19 +82,18 @@ container_type = redfish_utilities.log_container.CHASSIS container_id = args.chassis -if args.debug: - log_file = "rf_diagnostic_data-{}.log".format(datetime.datetime.now().strftime("%Y-%m-%d-%H%M%S")) - log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - logger = redfish.redfish_logger(log_file, log_format, logging.DEBUG) - logger.info("rf_diagnostic_data Trace") - # Set up the Redfish object redfish_obj = None try: - redfish_obj = redfish.redfish_client( - base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 - ) - redfish_obj.login(auth="session") + if args.session_token: + sessionkey = str.encode(args.session_token) + redfish_obj = redfish.redfish_client(base_url=args.rhost, sessionkey=sessionkey, timeout=15, max_retry=3) + else: + redfish_obj = redfish.redfish_client( + base_url=args.rhost, username=args.user, password=args.password, timeout=15, max_retry=3 + ) + # Don't need to login if we're using a session key + redfish_obj.login(auth="session") except RedfishPasswordChangeRequiredError: redfish_utilities.print_password_change_required_and_logout(redfish_obj, args) sys.exit(1) @@ -97,7 +102,7 @@ exit_code = 0 try: - print("Collecting diagnostic data...") + logger.info("Collecting diagnostic data...") response = redfish_utilities.collect_diagnostic_data( redfish_obj, container_type, container_id, args.log, args.type, args.oemtype ) @@ -119,13 +124,13 @@ path = os.path.join(args.directory, filename) with open(path, "wb") as file: file.write(data) - print("Saved diagnostic data to '{}'".format(path)) + logger.info("Saved diagnostic data to '{}'".format(path)) except Exception as e: - if args.debug: - logger.error("Caught exception:\n\n{}\n".format(traceback.format_exc())) + logger.debug("Caught exception:\n\n{}\n".format(traceback.format_exc())) exit_code = 1 - print(e) + logger.info(e) finally: # Log out - redfish_utilities.logout(redfish_obj) + if not args.session_token: + redfish_utilities.logout(redfish_obj) sys.exit(exit_code)