From a7492ba39f33c4000ec9bc239c72fcb8787592f1 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Mon, 29 Apr 2024 13:45:10 +0100 Subject: [PATCH 01/13] #2447: cli commands for dev mode --- .gitignore | 1 + docs/source/getting_started.rst | 4 +- src/primaite/cli.py | 60 ++----- src/primaite/session/io.py | 12 +- .../setup/_package_data/primaite_config.yaml | 7 +- src/primaite/simulator/__init__.py | 21 ++- src/primaite/utils/cli/__init__.py | 0 src/primaite/utils/cli/dev_cli.py | 151 ++++++++++++++++++ .../utils/cli/primaite_config_utils.py | 26 +++ src/primaite/utils/primaite_config_utils.py | 11 -- 10 files changed, 226 insertions(+), 67 deletions(-) create mode 100644 src/primaite/utils/cli/__init__.py create mode 100644 src/primaite/utils/cli/dev_cli.py create mode 100644 src/primaite/utils/cli/primaite_config_utils.py delete mode 100644 src/primaite/utils/primaite_config_utils.py diff --git a/.gitignore b/.gitignore index b3d9682a..5cd39f24 100644 --- a/.gitignore +++ b/.gitignore @@ -82,6 +82,7 @@ target/ # Jupyter Notebook .ipynb_checkpoints +PPO_UC2/ # IPython profile_default/ diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index d88be5c9..dccfd8aa 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -161,9 +161,9 @@ To set PrimAITE to run in development mode: .. code-block:: bash :caption: Unix - primaite mode --dev + primaite dev-mode enable .. code-block:: powershell :caption: Windows (Powershell) - primaite mode --dev + primaite dev-mode enable diff --git a/src/primaite/cli.py b/src/primaite/cli.py index b65a6c97..54f29067 100644 --- a/src/primaite/cli.py +++ b/src/primaite/cli.py @@ -9,9 +9,12 @@ import typer import yaml from typing_extensions import Annotated -from primaite import PRIMAITE_PATHS +from primaite import _PRIMAITE_ROOT, PRIMAITE_PATHS +from primaite.utils.cli import dev_cli +from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, update_primaite_config app = typer.Typer(no_args_is_help=True) +app.add_typer(dev_cli.dev, name="dev-mode") @app.command() @@ -113,48 +116,15 @@ def setup(overwrite_existing: bool = True) -> None: _LOGGER.info("Rebuilding the example notebooks...") reset_example_configs.run(overwrite_existing=True) + _LOGGER.info("Setting default simulation output") + config_dict = get_primaite_config_dict() + + if config_dict is None: + return + + config_dict["developer_mode"]["output_dir"] = str(_PRIMAITE_ROOT.parent.parent / "simulation_output") + print(f"PrimAITE dev-mode config updated output_dir {config_dict['developer_mode']['output_dir']}") + # update application config + update_primaite_config(config_dict) + _LOGGER.info("PrimAITE setup complete!") - - -@app.command() -def mode( - dev: Annotated[bool, typer.Option("--dev", help="Activates PrimAITE developer mode")] = None, - prod: Annotated[bool, typer.Option("--prod", help="Activates PrimAITE production mode")] = None, -) -> None: - """ - Switch PrimAITE between developer mode and production mode. - - By default, PrimAITE will be in production mode. - - To view the current mode, use: primaite mode - - To set to development mode, use: primaite mode --dev - - To return to production mode, use: primaite mode --prod - """ - if PRIMAITE_PATHS.app_config_file_path.exists(): - with open(PRIMAITE_PATHS.app_config_file_path, "r") as file: - primaite_config = yaml.safe_load(file) - if dev and prod: - print("Unable to activate developer and production modes concurrently.") - return - - if (dev is None) and (prod is None): - is_dev_mode = primaite_config["developer_mode"] - - if is_dev_mode: - print("PrimAITE is running in developer mode.") - else: - print("PrimAITE is running in production mode.") - if dev: - # activate dev mode - primaite_config["developer_mode"] = True - with open(PRIMAITE_PATHS.app_config_file_path, "w") as file: - yaml.dump(primaite_config, file) - print("PrimAITE is running in developer mode.") - if prod: - # activate prod mode - primaite_config["developer_mode"] = False - with open(PRIMAITE_PATHS.app_config_file_path, "w") as file: - yaml.dump(primaite_config, file) - print("PrimAITE is running in production mode.") diff --git a/src/primaite/session/io.py b/src/primaite/session/io.py index 22001fd2..65e900a8 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -5,9 +5,9 @@ from typing import Dict, List, Optional from pydantic import BaseModel, ConfigDict -from primaite import getLogger, PRIMAITE_PATHS +from primaite import _PRIMAITE_ROOT, getLogger, PRIMAITE_PATHS from primaite.simulator import LogLevel, SIM_OUTPUT -from src.primaite.utils.primaite_config_utils import is_dev_mode +from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode _LOGGER = getLogger(__name__) @@ -64,8 +64,12 @@ class PrimaiteIO: # check if running in dev mode if is_dev_mode(): - # if dev mode, simulation output will be the current working directory - session_path = Path.cwd() / "simulation_output" / date_str / time_str + # if dev mode, simulation output will be the repository root or whichever path is configured + app_config = get_primaite_config_dict() + if app_config["developer_mode"]["output_dir"] is not None: + session_path = app_config["developer_mode"]["output_path"] + else: + session_path = _PRIMAITE_ROOT.parent.parent / "simulation_output" / date_str / time_str else: session_path = PRIMAITE_PATHS.user_sessions_path / date_str / time_str diff --git a/src/primaite/setup/_package_data/primaite_config.yaml b/src/primaite/setup/_package_data/primaite_config.yaml index f80f4d8a..84619f17 100644 --- a/src/primaite/setup/_package_data/primaite_config.yaml +++ b/src/primaite/setup/_package_data/primaite_config.yaml @@ -1,6 +1,11 @@ # The main PrimAITE application config file -developer_mode: False # false by default +developer_mode: + enabled: False # not enabled by default + output_sys_logs: False # system logs not output by default + output_pcap_logs: False # pcap logs not output by default + output_to_terminal: False # do not output to terminal by default + output_dir: null # none by default - none will print to repository root # Logging logging: diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index 3f371ee5..6758dab6 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -7,6 +7,8 @@ from primaite import _PRIMAITE_ROOT __all__ = ["SIM_OUTPUT"] +from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode + class LogLevel(IntEnum): """Enum containing all the available log levels for PrimAITE simulation output.""" @@ -24,18 +26,29 @@ class LogLevel(IntEnum): class _SimOutput: + _default_path = _PRIMAITE_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + def __init__(self): - self._path: Path = ( - _PRIMAITE_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - ) + self._path: Path = self._default_path self.save_pcap_logs: bool = False self.save_sys_logs: bool = False self.write_sys_log_to_terminal: bool = False self.sys_log_level: LogLevel = LogLevel.WARNING # default log level is at WARNING + if is_dev_mode(): + # if dev mode, override with the values configured via the primaite dev-mode command + dev_config = get_primaite_config_dict().get("developer_mode") + self.save_pcap_logs = dev_config["output_pcap_logs"] + self.save_sys_logs = dev_config["output_sys_logs"] + self.write_sys_log_to_terminal = dev_config["output_to_terminal"] + @property def path(self) -> Path: - return self._path + if not is_dev_mode(): + return self._path + if is_dev_mode(): + dev_config = get_primaite_config_dict().get("developer_mode") + return Path(dev_config["output_path"]) if dev_config["output_path"] else self._default_path @path.setter def path(self, new_path: Path) -> None: diff --git a/src/primaite/utils/cli/__init__.py b/src/primaite/utils/cli/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py new file mode 100644 index 00000000..a17efa46 --- /dev/null +++ b/src/primaite/utils/cli/dev_cli.py @@ -0,0 +1,151 @@ +import typer +from rich import print +from typing_extensions import Annotated + +from primaite import _PRIMAITE_ROOT +from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode, update_primaite_config + +dev = typer.Typer() + +PRODUCTION_MODE_MESSAGE = ( + "\n[green]:monkey_face::monkey_face::monkey_face: " + " PrimAITE is running in Production mode " + " :monkey_face::monkey_face::monkey_face: [/green]\n" +) + +DEVELOPMENT_MODE_MESSAGE = ( + "\n[yellow] :construction::construction::construction: " + " PrimAITE is running in Development mode " + " :construction::construction::construction: [/yellow]\n" +) + + +def dev_mode(): + """ + CLI commands relevant to the dev-mode for PrimAITE. + + The dev-mode contains tools that help with the ease of developing or debugging PrimAITE. + + By default, PrimAITE will be in production mode. + + To enable development mode, use `primaite dev-mode enable` + """ + + +@dev.command() +def show(): + """Show if PrimAITE is in development mode or production mode.""" + # print if dev mode is enabled + print(DEVELOPMENT_MODE_MESSAGE if is_dev_mode() else PRODUCTION_MODE_MESSAGE) + print("\nTo see available options, use [medium_turquoise]`primaite dev-mode --help`[/medium_turquoise]\n") + + +@dev.command() +def enable(): + """Enable the development mode for PrimAITE.""" + config_dict = get_primaite_config_dict() + + if config_dict is None: + return + + # enable dev mode + config_dict["developer_mode"]["enabled"] = True + update_primaite_config(config_dict) + print(DEVELOPMENT_MODE_MESSAGE) + + +@dev.command() +def disable(): + """Disable the development mode for PrimAITE.""" + config_dict = get_primaite_config_dict() + + if config_dict is None: + return + + # disable dev mode + config_dict["developer_mode"]["enabled"] = False + update_primaite_config(config_dict) + print(PRODUCTION_MODE_MESSAGE) + + +def config_callback( + ctx: typer.Context, + output_sys_logs: Annotated[ + bool, typer.Option("--output-sys-logs/--no-sys-logs", "-sys/-nsys", help="Output system logs to file.") + ] = None, + output_pcap_logs: Annotated[ + bool, + typer.Option( + "--output-pcap-logs/--no-pcap-logs", "-pcap/-npcap", help="Output network packet capture logs to file." + ), + ] = None, + output_to_terminal: Annotated[ + bool, typer.Option("--output-to_terminal/--no-terminal", "-t/-nt", help="Output system logs to terminal.") + ] = None, +): + """Configure the development tools and environment.""" + config_dict = get_primaite_config_dict() + + if config_dict is None: + return + + if output_sys_logs is not None: + config_dict["developer_mode"]["output_sys_logs"] = output_sys_logs + print(f"PrimAITE dev-mode config updated {output_sys_logs=}") + + if output_pcap_logs is not None: + config_dict["developer_mode"]["output_pcap_logs"] = output_pcap_logs + print(f"PrimAITE dev-mode config updated {output_pcap_logs=}") + + if output_to_terminal is not None: + config_dict["developer_mode"]["output_to_terminal"] = output_to_terminal + print(f"PrimAITE dev-mode config updated {output_to_terminal=}") + + # update application config + update_primaite_config(config_dict) + + +config_typer = typer.Typer(callback=config_callback, name="config", invoke_without_command=True) +dev.add_typer(config_typer) + + +@config_typer.command() +def path( + directory: Annotated[ + str, + typer.Argument( + help="Directory where the system logs and PCAP logs will be output. By default, this will be where the" + "root of the PrimAITE repository is located.", + show_default=False, + ), + ] = None, + default: Annotated[ + bool, + typer.Option( + "--default", + "-root", + help="Set PrimAITE to output system logs and pcap logs to the PrimAITE repository root.", + ), + ] = None, +): + """Set the output directory for the PrimAITE system and PCAP logs.""" + config_dict = get_primaite_config_dict() + + if config_dict is None: + return + + if default: + config_dict["developer_mode"]["output_dir"] = None + print( + f"PrimAITE dev-mode config updated output directory will be in " + f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" + ) + # update application config + update_primaite_config(config_dict) + return + + if directory: + config_dict["developer_mode"]["output_dir"] = directory + print(f"PrimAITE dev-mode config updated output_dir={directory}") + # update application config + update_primaite_config(config_dict) diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py new file mode 100644 index 00000000..e78a5f86 --- /dev/null +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -0,0 +1,26 @@ +from typing import Dict + +import yaml + +from primaite import PRIMAITE_PATHS + + +def get_primaite_config_dict() -> Dict: + """Returns a dict containing the PrimAITE application config.""" + if PRIMAITE_PATHS.app_config_file_path.exists(): + with open(PRIMAITE_PATHS.app_config_file_path, "r") as file: + return yaml.safe_load(file) + else: + print("PrimAITE application config was not found. Have you run [bold red]primaite setup[/bold red]?") + + +def is_dev_mode() -> bool: + """Returns True if PrimAITE is currently running in developer mode.""" + config = get_primaite_config_dict() + return config["developer_mode"]["enabled"] + + +def update_primaite_config(config: Dict) -> None: + """Update the PrimAITE application config file.""" + with open(PRIMAITE_PATHS.app_config_file_path, "w") as file: + yaml.dump(config, file) diff --git a/src/primaite/utils/primaite_config_utils.py b/src/primaite/utils/primaite_config_utils.py deleted file mode 100644 index 70a7e4ba..00000000 --- a/src/primaite/utils/primaite_config_utils.py +++ /dev/null @@ -1,11 +0,0 @@ -import yaml - -from primaite import PRIMAITE_PATHS - - -def is_dev_mode() -> bool: - """Returns True if PrimAITE is currently running in developer mode.""" - if PRIMAITE_PATHS.app_config_file_path.exists(): - with open(PRIMAITE_PATHS.app_config_file_path, "r") as file: - primaite_config = yaml.safe_load(file) - return primaite_config["developer_mode"] From 59990813f53bbbc6e2b8150909bebfac65cab217 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 30 Apr 2024 15:43:25 +0100 Subject: [PATCH 02/13] #2533: added tests + log level for output --- src/primaite/__init__.py | 5 +- src/primaite/cli.py | 14 +- .../setup/_package_data/primaite_config.yaml | 1 + src/primaite/simulator/__init__.py | 2 +- src/primaite/utils/cli/dev_cli.py | 108 ++++++---- .../utils/cli/primaite_config_utils.py | 25 ++- tests/integration_tests/cli/__init__.py | 11 ++ tests/integration_tests/cli/test_dev_cli.py | 186 ++++++++++++++++++ 8 files changed, 294 insertions(+), 58 deletions(-) create mode 100644 tests/integration_tests/cli/__init__.py create mode 100644 tests/integration_tests/cli/test_dev_cli.py diff --git a/src/primaite/__init__.py b/src/primaite/__init__.py index c58f0103..59725ac7 100644 --- a/src/primaite/__init__.py +++ b/src/primaite/__init__.py @@ -123,9 +123,8 @@ PRIMAITE_PATHS: Final[_PrimaitePaths] = _PrimaitePaths() def _host_primaite_config() -> None: - if not PRIMAITE_PATHS.app_config_file_path.exists(): - pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) - shutil.copy2(pkg_config_path, PRIMAITE_PATHS.app_config_file_path) + pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) + shutil.copy(pkg_config_path, PRIMAITE_PATHS.app_config_file_path) _host_primaite_config() diff --git a/src/primaite/cli.py b/src/primaite/cli.py index 54f29067..6ad019dd 100644 --- a/src/primaite/cli.py +++ b/src/primaite/cli.py @@ -9,9 +9,8 @@ import typer import yaml from typing_extensions import Annotated -from primaite import _PRIMAITE_ROOT, PRIMAITE_PATHS +from primaite import PRIMAITE_PATHS from primaite.utils.cli import dev_cli -from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, update_primaite_config app = typer.Typer(no_args_is_help=True) app.add_typer(dev_cli.dev, name="dev-mode") @@ -116,15 +115,4 @@ def setup(overwrite_existing: bool = True) -> None: _LOGGER.info("Rebuilding the example notebooks...") reset_example_configs.run(overwrite_existing=True) - _LOGGER.info("Setting default simulation output") - config_dict = get_primaite_config_dict() - - if config_dict is None: - return - - config_dict["developer_mode"]["output_dir"] = str(_PRIMAITE_ROOT.parent.parent / "simulation_output") - print(f"PrimAITE dev-mode config updated output_dir {config_dict['developer_mode']['output_dir']}") - # update application config - update_primaite_config(config_dict) - _LOGGER.info("PrimAITE setup complete!") diff --git a/src/primaite/setup/_package_data/primaite_config.yaml b/src/primaite/setup/_package_data/primaite_config.yaml index 84619f17..c1caf1f4 100644 --- a/src/primaite/setup/_package_data/primaite_config.yaml +++ b/src/primaite/setup/_package_data/primaite_config.yaml @@ -2,6 +2,7 @@ developer_mode: enabled: False # not enabled by default + sys_log_level: DEBUG # level of output for system logs, DEBUG by default output_sys_logs: False # system logs not output by default output_pcap_logs: False # pcap logs not output by default output_to_terminal: False # do not output to terminal by default diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index 6758dab6..985bb663 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -48,7 +48,7 @@ class _SimOutput: return self._path if is_dev_mode(): dev_config = get_primaite_config_dict().get("developer_mode") - return Path(dev_config["output_path"]) if dev_config["output_path"] else self._default_path + return Path(dev_config["output_dir"]) if dev_config["output_dir"] else self._default_path @path.setter def path(self, new_path: Path) -> None: diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index a17efa46..886f1bf1 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -1,8 +1,12 @@ +from typing import List + +import click import typer from rich import print from typing_extensions import Annotated from primaite import _PRIMAITE_ROOT +from primaite.simulator import LogLevel from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode, update_primaite_config dev = typer.Typer() @@ -69,19 +73,44 @@ def disable(): def config_callback( - ctx: typer.Context, - output_sys_logs: Annotated[ - bool, typer.Option("--output-sys-logs/--no-sys-logs", "-sys/-nsys", help="Output system logs to file.") - ] = None, - output_pcap_logs: Annotated[ - bool, - typer.Option( - "--output-pcap-logs/--no-pcap-logs", "-pcap/-npcap", help="Output network packet capture logs to file." - ), - ] = None, - output_to_terminal: Annotated[ - bool, typer.Option("--output-to_terminal/--no-terminal", "-t/-nt", help="Output system logs to terminal.") - ] = None, + ctx: typer.Context, + sys_log_level: Annotated[ + LogLevel, + typer.Option( + "--sys-log-level", + "-level", + click_type=click.Choice(LogLevel._member_names_, case_sensitive=False), + help="The level of system logs to output.", + show_default=False + ) + ] = None, + output_sys_logs: Annotated[ + bool, + typer.Option( + "--output-sys-logs/--no-sys-logs", + "-sys/-nsys", + help="Output system logs to file.", + show_default=False + ) + ] = None, + output_pcap_logs: Annotated[ + bool, + typer.Option( + "--output-pcap-logs/--no-pcap-logs", + "-pcap/-npcap", + help="Output network packet capture logs to file.", + show_default=False + ), + ] = None, + output_to_terminal: Annotated[ + bool, + typer.Option( + "--output-to-terminal/--no-terminal", + "-t/-nt", + help="Output system logs to terminal.", + show_default=False + ) + ] = None, ): """Configure the development tools and environment.""" config_dict = get_primaite_config_dict() @@ -89,6 +118,11 @@ def config_callback( if config_dict is None: return + if ctx.params.get("sys_log_level") is not None: + config_dict["developer_mode"]["sys_log_level"] = ctx.params.get("sys_log_level") + print(f"PrimAITE dev-mode config updated sys_log_level={ctx.params.get('sys_log_level')}") + + if output_sys_logs is not None: config_dict["developer_mode"]["output_sys_logs"] = output_sys_logs print(f"PrimAITE dev-mode config updated {output_sys_logs=}") @@ -105,28 +139,33 @@ def config_callback( update_primaite_config(config_dict) -config_typer = typer.Typer(callback=config_callback, name="config", invoke_without_command=True) +config_typer = typer.Typer( + callback=config_callback, + name="config", + no_args_is_help=True, + invoke_without_command=True, +) dev.add_typer(config_typer) @config_typer.command() def path( - directory: Annotated[ - str, - typer.Argument( - help="Directory where the system logs and PCAP logs will be output. By default, this will be where the" - "root of the PrimAITE repository is located.", - show_default=False, - ), - ] = None, - default: Annotated[ - bool, - typer.Option( - "--default", - "-root", - help="Set PrimAITE to output system logs and pcap logs to the PrimAITE repository root.", - ), - ] = None, + directory: Annotated[ + str, + typer.Argument( + help="Directory where the system logs and PCAP logs will be output. By default, this will be where the" + "root of the PrimAITE repository is located.", + show_default=False, + ), + ] = None, + default: Annotated[ + bool, + typer.Option( + "--default", + "-root", + help="Set PrimAITE to output system logs and pcap logs to the PrimAITE repository root.", + ), + ] = None, ): """Set the output directory for the PrimAITE system and PCAP logs.""" config_dict = get_primaite_config_dict() @@ -136,16 +175,15 @@ def path( if default: config_dict["developer_mode"]["output_dir"] = None - print( - f"PrimAITE dev-mode config updated output directory will be in " - f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" - ) # update application config update_primaite_config(config_dict) + print(f"PrimAITE dev-mode output_dir [medium_turquoise]" + f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" + f"[/medium_turquoise]") return if directory: config_dict["developer_mode"]["output_dir"] = directory - print(f"PrimAITE dev-mode config updated output_dir={directory}") # update application config update_primaite_config(config_dict) + print(f"PrimAITE dev-mode output_dir [medium_turquoise]{directory}[/medium_turquoise]") diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index e78a5f86..2eb590ad 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -1,17 +1,30 @@ -from typing import Dict +from pathlib import Path +from typing import Dict, Optional import yaml from primaite import PRIMAITE_PATHS -def get_primaite_config_dict() -> Dict: - """Returns a dict containing the PrimAITE application config.""" - if PRIMAITE_PATHS.app_config_file_path.exists(): - with open(PRIMAITE_PATHS.app_config_file_path, "r") as file: +def get_primaite_config_dict( + config_path: Optional[Path] = None +) -> Dict: + """ + Returns a dict containing the PrimAITE application config. + + :param: config_path: takes in a path object - leave empty to use the default app config path + """ + err_msg = "PrimAITE application config could not be loaded." + + if config_path is None: + config_path = PRIMAITE_PATHS.app_config_file_path + err_msg = "PrimAITE application config was not found. Have you run `primaite setup`?" + + if config_path.exists(): + with open(config_path, "r") as file: return yaml.safe_load(file) else: - print("PrimAITE application config was not found. Have you run [bold red]primaite setup[/bold red]?") + print(err_msg) def is_dev_mode() -> bool: diff --git a/tests/integration_tests/cli/__init__.py b/tests/integration_tests/cli/__init__.py new file mode 100644 index 00000000..07487650 --- /dev/null +++ b/tests/integration_tests/cli/__init__.py @@ -0,0 +1,11 @@ +from typing import List + +from typer.testing import CliRunner, Result + +from primaite.cli import app + + +def cli(args: List[str]) -> Result: + """Pass in a list of arguments and it will return the result.""" + runner = CliRunner() + return runner.invoke(app, args) diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py new file mode 100644 index 00000000..cd697711 --- /dev/null +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -0,0 +1,186 @@ +import os +import shutil +import tempfile +from pathlib import Path + +import pytest + +from primaite import PRIMAITE_PATHS, _PRIMAITE_ROOT +from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict +from tests.integration_tests.cli import cli + + +@pytest.fixture(autouse=True) +def test_setup(): + """ + Setup this test by copying the + """ + original_config_path = PRIMAITE_PATHS.app_config_file_path # keep track of app config before test + + temp_dir = tempfile.gettempdir() + temp_config = Path(temp_dir) / "primaite_config.yaml" + shutil.copyfile( + _PRIMAITE_ROOT / "setup" / "_package_data" / "primaite_config.yaml", temp_config + ) # copy the default primaite config to temp directory + PRIMAITE_PATHS.app_config_file_path = temp_config # use the copy for the test + yield # run test + os.remove(temp_config) # clean up temp file + PRIMAITE_PATHS.app_config_file_path = original_config_path # restore app conf because other devs will yell at me + + +def test_dev_mode_enable_disable(): + """Test dev mode enable and disable.""" + # check defaults + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["enabled"] is False # not enabled by default + + result = cli(["dev-mode", "show"]) + assert "Production" in result.output # should print that it is in Production mode by default + + result = cli(["dev-mode", "enable"]) + + assert "Development" in result.output # should print that it is in Development mode + + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["enabled"] # config should reflect that dev mode is enabled + + result = cli(["dev-mode", "show"]) + assert "Development" in result.output # should print that it is in Development mode + + result = cli(["dev-mode", "disable"]) + + assert "Production" in result.output # should print that it is in Production mode + + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["enabled"] is False # config should reflect that dev mode is disabled + + result = cli(["dev-mode", "show"]) + assert "Production" in result.output # should print that it is in Production mode + + +def test_dev_mode_config_sys_log_level(): + """Check that the system log level can be changed via CLI.""" + # check defaults + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["sys_log_level"] == "DEBUG" # DEBUG by default + + result = cli(["dev-mode", "config", "-level", "WARNING"]) + + assert "sys_log_level=WARNING" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that log level is WARNING + assert config_dict["developer_mode"]["sys_log_level"] == "WARNING" + + result = cli(["dev-mode", "config", "--sys-log-level", "INFO"]) + + assert "sys_log_level=INFO" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that log level is WARNING + assert config_dict["developer_mode"]["sys_log_level"] == "INFO" + + +def test_dev_mode_config_sys_logs_enable_disable(): + """Test that the system logs output can be enabled or disabled.""" + # check defaults + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["output_sys_logs"] is False # False by default + + result = cli(["dev-mode", "config", "--output-sys-logs"]) + assert "output_sys_logs=True" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_sys_logs is True + assert config_dict["developer_mode"]["output_sys_logs"] + + result = cli(["dev-mode", "config", "--no-sys-logs"]) + assert "output_sys_logs=False" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_sys_logs is True + assert config_dict["developer_mode"]["output_sys_logs"] is False + + result = cli(["dev-mode", "config", "-sys"]) + assert "output_sys_logs=True" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_sys_logs is True + assert config_dict["developer_mode"]["output_sys_logs"] + + result = cli(["dev-mode", "config", "-nsys"]) + assert "output_sys_logs=False" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_sys_logs is True + assert config_dict["developer_mode"]["output_sys_logs"] is False + + +def test_dev_mode_config_pcap_logs_enable_disable(): + """Test that the pcap logs output can be enabled or disabled.""" + # check defaults + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["output_pcap_logs"] is False # False by default + + result = cli(["dev-mode", "config", "--output-pcap-logs"]) + assert "output_pcap_logs=True" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_pcap_logs is True + assert config_dict["developer_mode"]["output_pcap_logs"] + + result = cli(["dev-mode", "config", "--no-pcap-logs"]) + assert "output_pcap_logs=False" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_pcap_logs is True + assert config_dict["developer_mode"]["output_pcap_logs"] is False + + result = cli(["dev-mode", "config", "-pcap"]) + assert "output_pcap_logs=True" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_pcap_logs is True + assert config_dict["developer_mode"]["output_pcap_logs"] + + result = cli(["dev-mode", "config", "-npcap"]) + assert "output_pcap_logs=False" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_pcap_logs is True + assert config_dict["developer_mode"]["output_pcap_logs"] is False + + +def test_dev_mode_config_output_to_terminal_enable_disable(): + """Test that the output to terminal can be enabled or disabled.""" + # check defaults + config_dict = get_primaite_config_dict() + assert config_dict["developer_mode"]["output_to_terminal"] is False # False by default + + result = cli(["dev-mode", "config", "--output-to-terminal"]) + assert "output_to_terminal=True" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_to_terminal is True + assert config_dict["developer_mode"]["output_to_terminal"] + + result = cli(["dev-mode", "config", "--no-terminal"]) + assert "output_to_terminal=False" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_to_terminal is True + assert config_dict["developer_mode"]["output_to_terminal"] is False + + result = cli(["dev-mode", "config", "-t"]) + assert "output_to_terminal=True" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_to_terminal is True + assert config_dict["developer_mode"]["output_to_terminal"] + + result = cli(["dev-mode", "config", "-nt"]) + assert "output_to_terminal=False" in result.output # should print correct value + + config_dict = get_primaite_config_dict() + # config should reflect that output_to_terminal is True + assert config_dict["developer_mode"]["output_to_terminal"] is False From ab3d23785dc4676f38d89ceebc12806f68fd00cc Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 30 Apr 2024 15:50:53 +0100 Subject: [PATCH 03/13] #2533: precommit --- src/primaite/utils/cli/dev_cli.py | 113 ++++++++---------- .../utils/cli/primaite_config_utils.py | 4 +- tests/integration_tests/cli/test_dev_cli.py | 2 +- 3 files changed, 55 insertions(+), 64 deletions(-) diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 886f1bf1..03567785 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -1,5 +1,3 @@ -from typing import List - import click import typer from rich import print @@ -73,44 +71,38 @@ def disable(): def config_callback( - ctx: typer.Context, - sys_log_level: Annotated[ - LogLevel, - typer.Option( - "--sys-log-level", - "-level", - click_type=click.Choice(LogLevel._member_names_, case_sensitive=False), - help="The level of system logs to output.", - show_default=False - ) - ] = None, - output_sys_logs: Annotated[ - bool, - typer.Option( - "--output-sys-logs/--no-sys-logs", - "-sys/-nsys", - help="Output system logs to file.", - show_default=False - ) - ] = None, - output_pcap_logs: Annotated[ - bool, - typer.Option( - "--output-pcap-logs/--no-pcap-logs", - "-pcap/-npcap", - help="Output network packet capture logs to file.", - show_default=False - ), - ] = None, - output_to_terminal: Annotated[ - bool, - typer.Option( - "--output-to-terminal/--no-terminal", - "-t/-nt", - help="Output system logs to terminal.", - show_default=False - ) - ] = None, + ctx: typer.Context, + sys_log_level: Annotated[ + LogLevel, + typer.Option( + "--sys-log-level", + "-level", + click_type=click.Choice(LogLevel._member_names_, case_sensitive=False), + help="The level of system logs to output.", + show_default=False, + ), + ] = None, + output_sys_logs: Annotated[ + bool, + typer.Option( + "--output-sys-logs/--no-sys-logs", "-sys/-nsys", help="Output system logs to file.", show_default=False + ), + ] = None, + output_pcap_logs: Annotated[ + bool, + typer.Option( + "--output-pcap-logs/--no-pcap-logs", + "-pcap/-npcap", + help="Output network packet capture logs to file.", + show_default=False, + ), + ] = None, + output_to_terminal: Annotated[ + bool, + typer.Option( + "--output-to-terminal/--no-terminal", "-t/-nt", help="Output system logs to terminal.", show_default=False + ), + ] = None, ): """Configure the development tools and environment.""" config_dict = get_primaite_config_dict() @@ -122,7 +114,6 @@ def config_callback( config_dict["developer_mode"]["sys_log_level"] = ctx.params.get("sys_log_level") print(f"PrimAITE dev-mode config updated sys_log_level={ctx.params.get('sys_log_level')}") - if output_sys_logs is not None: config_dict["developer_mode"]["output_sys_logs"] = output_sys_logs print(f"PrimAITE dev-mode config updated {output_sys_logs=}") @@ -150,22 +141,22 @@ dev.add_typer(config_typer) @config_typer.command() def path( - directory: Annotated[ - str, - typer.Argument( - help="Directory where the system logs and PCAP logs will be output. By default, this will be where the" - "root of the PrimAITE repository is located.", - show_default=False, - ), - ] = None, - default: Annotated[ - bool, - typer.Option( - "--default", - "-root", - help="Set PrimAITE to output system logs and pcap logs to the PrimAITE repository root.", - ), - ] = None, + directory: Annotated[ + str, + typer.Argument( + help="Directory where the system logs and PCAP logs will be output. By default, this will be where the" + "root of the PrimAITE repository is located.", + show_default=False, + ), + ] = None, + default: Annotated[ + bool, + typer.Option( + "--default", + "-root", + help="Set PrimAITE to output system logs and pcap logs to the PrimAITE repository root.", + ), + ] = None, ): """Set the output directory for the PrimAITE system and PCAP logs.""" config_dict = get_primaite_config_dict() @@ -177,9 +168,11 @@ def path( config_dict["developer_mode"]["output_dir"] = None # update application config update_primaite_config(config_dict) - print(f"PrimAITE dev-mode output_dir [medium_turquoise]" - f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" - f"[/medium_turquoise]") + print( + f"PrimAITE dev-mode output_dir [medium_turquoise]" + f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" + f"[/medium_turquoise]" + ) return if directory: diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index 2eb590ad..99e95bed 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -6,9 +6,7 @@ import yaml from primaite import PRIMAITE_PATHS -def get_primaite_config_dict( - config_path: Optional[Path] = None -) -> Dict: +def get_primaite_config_dict(config_path: Optional[Path] = None) -> Dict: """ Returns a dict containing the PrimAITE application config. diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py index cd697711..fb051085 100644 --- a/tests/integration_tests/cli/test_dev_cli.py +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -5,7 +5,7 @@ from pathlib import Path import pytest -from primaite import PRIMAITE_PATHS, _PRIMAITE_ROOT +from primaite import _PRIMAITE_ROOT, PRIMAITE_PATHS from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict from tests.integration_tests.cli import cli From 729f9c5064617027aaeb9224ed31799956dd85f0 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 30 Apr 2024 19:15:54 +0100 Subject: [PATCH 04/13] #2533: fix primaite config recreated when running setup --- src/primaite/__init__.py | 5 +- src/primaite/cli.py | 12 +++- src/primaite/session/io.py | 21 +++--- src/primaite/simulator/__init__.py | 67 +++++++++++++------ .../utils/cli/primaite_config_utils.py | 2 +- tests/integration_tests/cli/test_dev_cli.py | 6 +- 6 files changed, 74 insertions(+), 39 deletions(-) diff --git a/src/primaite/__init__.py b/src/primaite/__init__.py index 59725ac7..c58f0103 100644 --- a/src/primaite/__init__.py +++ b/src/primaite/__init__.py @@ -123,8 +123,9 @@ PRIMAITE_PATHS: Final[_PrimaitePaths] = _PrimaitePaths() def _host_primaite_config() -> None: - pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) - shutil.copy(pkg_config_path, PRIMAITE_PATHS.app_config_file_path) + if not PRIMAITE_PATHS.app_config_file_path.exists(): + pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) + shutil.copy2(pkg_config_path, PRIMAITE_PATHS.app_config_file_path) _host_primaite_config() diff --git a/src/primaite/cli.py b/src/primaite/cli.py index 6ad019dd..e2e5f8f6 100644 --- a/src/primaite/cli.py +++ b/src/primaite/cli.py @@ -2,9 +2,12 @@ """Provides a CLI using Typer as an entry point.""" import logging import os +import shutil from enum import Enum +from pathlib import Path from typing import Optional +import pkg_resources import typer import yaml from typing_extensions import Annotated @@ -91,7 +94,7 @@ def version() -> None: @app.command() -def setup(overwrite_existing: bool = True) -> None: +def setup(overwrite_existing: bool = False) -> None: """ Perform the PrimAITE first-time setup. @@ -104,11 +107,14 @@ def setup(overwrite_existing: bool = True) -> None: _LOGGER.info("Performing the PrimAITE first-time setup...") - _LOGGER.info("Building primaite_config.yaml...") - _LOGGER.info("Building the PrimAITE app directories...") PRIMAITE_PATHS.mkdirs() + _LOGGER.info("Building primaite_config.yaml...") + if overwrite_existing: + pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) + shutil.copy(pkg_config_path, PRIMAITE_PATHS.app_config_file_path) + _LOGGER.info("Rebuilding the demo notebooks...") reset_demo_notebooks.run(overwrite_existing=True) diff --git a/src/primaite/session/io.py b/src/primaite/session/io.py index 65e900a8..0e7b90c9 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -5,9 +5,8 @@ from typing import Dict, List, Optional from pydantic import BaseModel, ConfigDict -from primaite import _PRIMAITE_ROOT, getLogger, PRIMAITE_PATHS +from primaite import getLogger, PRIMAITE_PATHS from primaite.simulator import LogLevel, SIM_OUTPUT -from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode _LOGGER = getLogger(__name__) @@ -63,15 +62,15 @@ class PrimaiteIO: time_str = timestamp.strftime("%H-%M-%S") # check if running in dev mode - if is_dev_mode(): - # if dev mode, simulation output will be the repository root or whichever path is configured - app_config = get_primaite_config_dict() - if app_config["developer_mode"]["output_dir"] is not None: - session_path = app_config["developer_mode"]["output_path"] - else: - session_path = _PRIMAITE_ROOT.parent.parent / "simulation_output" / date_str / time_str - else: - session_path = PRIMAITE_PATHS.user_sessions_path / date_str / time_str + # if is_dev_mode(): + # # if dev mode, simulation output will be the repository root or whichever path is configured + # app_config = get_primaite_config_dict() + # if app_config["developer_mode"]["output_dir"] is not None: + # session_path = app_config["developer_mode"]["output_dir"] + # else: + # session_path = _PRIMAITE_ROOT.parent.parent / "simulation_output" / date_str / time_str + # else: + session_path = PRIMAITE_PATHS.user_sessions_path / date_str / time_str session_path.mkdir(exist_ok=True, parents=True) return session_path diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index 985bb663..cde7136c 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -26,34 +26,63 @@ class LogLevel(IntEnum): class _SimOutput: - _default_path = _PRIMAITE_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - def __init__(self): - self._path: Path = self._default_path - self.save_pcap_logs: bool = False - self.save_sys_logs: bool = False - self.write_sys_log_to_terminal: bool = False - self.sys_log_level: LogLevel = LogLevel.WARNING # default log level is at WARNING - - if is_dev_mode(): - # if dev mode, override with the values configured via the primaite dev-mode command - dev_config = get_primaite_config_dict().get("developer_mode") - self.save_pcap_logs = dev_config["output_pcap_logs"] - self.save_sys_logs = dev_config["output_sys_logs"] - self.write_sys_log_to_terminal = dev_config["output_to_terminal"] + self._path: Path = ( + _PRIMAITE_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ) + self._save_pcap_logs: bool = False + self._save_sys_logs: bool = False + self._write_sys_log_to_terminal: bool = False + self._sys_log_level: LogLevel = LogLevel.WARNING # default log level is at WARNING @property def path(self) -> Path: - if not is_dev_mode(): - return self._path - if is_dev_mode(): - dev_config = get_primaite_config_dict().get("developer_mode") - return Path(dev_config["output_dir"]) if dev_config["output_dir"] else self._default_path + return self._path @path.setter def path(self, new_path: Path) -> None: self._path = new_path self._path.mkdir(exist_ok=True, parents=True) + @property + def save_pcap_logs(self) -> bool: + if is_dev_mode(): + return get_primaite_config_dict().get("developer_mode").get("output_pcap_logs") + return self._save_pcap_logs + + @save_pcap_logs.setter + def save_pcap_logs(self, save_pcap_logs: bool) -> None: + self._save_pcap_logs = save_pcap_logs + + @property + def save_sys_logs(self) -> bool: + if is_dev_mode(): + return get_primaite_config_dict().get("developer_mode").get("output_sys_logs") + return self._save_sys_logs + + @save_sys_logs.setter + def save_sys_logs(self, save_sys_logs: bool) -> None: + self._save_sys_logs = save_sys_logs + + @property + def write_sys_log_to_terminal(self) -> bool: + if is_dev_mode(): + return get_primaite_config_dict().get("developer_mode").get("output_to_terminal") + return self._write_sys_log_to_terminal + + @write_sys_log_to_terminal.setter + def write_sys_log_to_terminal(self, write_sys_log_to_terminal: bool) -> None: + self._write_sys_log_to_terminal = write_sys_log_to_terminal + + @property + def sys_log_level(self) -> LogLevel: + if is_dev_mode(): + return LogLevel[get_primaite_config_dict().get("developer_mode").get("sys_log_level")] + return self._sys_log_level + + @sys_log_level.setter + def sys_log_level(self, sys_log_level: LogLevel) -> None: + self._sys_log_level = sys_log_level + SIM_OUTPUT = _SimOutput() diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index 99e95bed..a11c2ce3 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -28,7 +28,7 @@ def get_primaite_config_dict(config_path: Optional[Path] = None) -> Dict: def is_dev_mode() -> bool: """Returns True if PrimAITE is currently running in developer mode.""" config = get_primaite_config_dict() - return config["developer_mode"]["enabled"] + return config["developer_mode"]["enabled"] if config.get("developer_mode", {}).get("enabled") else False def update_primaite_config(config: Dict) -> None: diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py index fb051085..d650bc90 100644 --- a/tests/integration_tests/cli/test_dev_cli.py +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -3,6 +3,7 @@ import shutil import tempfile from pathlib import Path +import pkg_resources import pytest from primaite import _PRIMAITE_ROOT, PRIMAITE_PATHS @@ -19,9 +20,8 @@ def test_setup(): temp_dir = tempfile.gettempdir() temp_config = Path(temp_dir) / "primaite_config.yaml" - shutil.copyfile( - _PRIMAITE_ROOT / "setup" / "_package_data" / "primaite_config.yaml", temp_config - ) # copy the default primaite config to temp directory + pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) + shutil.copyfile(pkg_config_path, temp_config) # copy the default primaite config to temp directory PRIMAITE_PATHS.app_config_file_path = temp_config # use the copy for the test yield # run test os.remove(temp_config) # clean up temp file From 95643d3255bdedf618cad0da36364e9e4a49729b Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Tue, 30 Apr 2024 19:36:22 +0100 Subject: [PATCH 05/13] #2533: optimise so we are not reading from file all the time --- src/primaite/__init__.py | 34 +++++-------- src/primaite/simulator/__init__.py | 12 ++--- src/primaite/utils/cli/dev_cli.py | 50 ++++++------------- .../utils/cli/primaite_config_utils.py | 35 +++---------- 4 files changed, 42 insertions(+), 89 deletions(-) diff --git a/src/primaite/__init__.py b/src/primaite/__init__.py index c58f0103..2cd44755 100644 --- a/src/primaite/__init__.py +++ b/src/primaite/__init__.py @@ -134,23 +134,15 @@ _host_primaite_config() def _get_primaite_config() -> Dict: config_path = PRIMAITE_PATHS.app_config_file_path if not config_path.exists(): + # load from package if config does not exist config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) with open(config_path, "r") as file: + # load from config primaite_config = yaml.safe_load(file) - log_level_map = { - "NOTSET": logging.NOTSET, - "DEBUG": logging.DEBUG, - "INFO": logging.INFO, - "WARN": logging.WARN, - "WARNING": logging.WARN, - "ERROR": logging.ERROR, - "CRITICAL": logging.CRITICAL, - } - primaite_config["log_level"] = log_level_map[primaite_config["logging"]["log_level"]] - return primaite_config + return primaite_config -_PRIMAITE_CONFIG = _get_primaite_config() +PRIMAITE_CONFIG = _get_primaite_config() class _LevelFormatter(Formatter): @@ -177,11 +169,11 @@ class _LevelFormatter(Formatter): _LEVEL_FORMATTER: Final[_LevelFormatter] = _LevelFormatter( { - logging.DEBUG: _PRIMAITE_CONFIG["logging"]["logger_format"]["DEBUG"], - logging.INFO: _PRIMAITE_CONFIG["logging"]["logger_format"]["INFO"], - logging.WARNING: _PRIMAITE_CONFIG["logging"]["logger_format"]["WARNING"], - logging.ERROR: _PRIMAITE_CONFIG["logging"]["logger_format"]["ERROR"], - logging.CRITICAL: _PRIMAITE_CONFIG["logging"]["logger_format"]["CRITICAL"], + logging.DEBUG: PRIMAITE_CONFIG["logging"]["logger_format"]["DEBUG"], + logging.INFO: PRIMAITE_CONFIG["logging"]["logger_format"]["INFO"], + logging.WARNING: PRIMAITE_CONFIG["logging"]["logger_format"]["WARNING"], + logging.ERROR: PRIMAITE_CONFIG["logging"]["logger_format"]["ERROR"], + logging.CRITICAL: PRIMAITE_CONFIG["logging"]["logger_format"]["CRITICAL"], } ) @@ -193,10 +185,10 @@ _FILE_HANDLER: Final[RotatingFileHandler] = RotatingFileHandler( backupCount=9, # Max 100MB of logs encoding="utf8", ) -_STREAM_HANDLER.setLevel(_PRIMAITE_CONFIG["logging"]["log_level"]) -_FILE_HANDLER.setLevel(_PRIMAITE_CONFIG["logging"]["log_level"]) +_STREAM_HANDLER.setLevel(PRIMAITE_CONFIG["logging"]["log_level"]) +_FILE_HANDLER.setLevel(PRIMAITE_CONFIG["logging"]["log_level"]) -_LOG_FORMAT_STR: Final[str] = _PRIMAITE_CONFIG["logging"]["logger_format"] +_LOG_FORMAT_STR: Final[str] = PRIMAITE_CONFIG["logging"]["logger_format"] _STREAM_HANDLER.setFormatter(_LEVEL_FORMATTER) _FILE_HANDLER.setFormatter(_LEVEL_FORMATTER) @@ -215,6 +207,6 @@ def getLogger(name: str) -> Logger: # noqa logging config. """ logger = logging.getLogger(name) - logger.setLevel(_PRIMAITE_CONFIG["log_level"]) + logger.setLevel(PRIMAITE_CONFIG["logging"]["log_level"]) return logger diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index cde7136c..9f936249 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -3,11 +3,11 @@ from datetime import datetime from enum import IntEnum from pathlib import Path -from primaite import _PRIMAITE_ROOT +from primaite import _PRIMAITE_ROOT, PRIMAITE_CONFIG __all__ = ["SIM_OUTPUT"] -from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode +from primaite.utils.cli.primaite_config_utils import is_dev_mode class LogLevel(IntEnum): @@ -47,7 +47,7 @@ class _SimOutput: @property def save_pcap_logs(self) -> bool: if is_dev_mode(): - return get_primaite_config_dict().get("developer_mode").get("output_pcap_logs") + return PRIMAITE_CONFIG.get("developer_mode").get("output_pcap_logs") return self._save_pcap_logs @save_pcap_logs.setter @@ -57,7 +57,7 @@ class _SimOutput: @property def save_sys_logs(self) -> bool: if is_dev_mode(): - return get_primaite_config_dict().get("developer_mode").get("output_sys_logs") + return PRIMAITE_CONFIG.get("developer_mode").get("output_sys_logs") return self._save_sys_logs @save_sys_logs.setter @@ -67,7 +67,7 @@ class _SimOutput: @property def write_sys_log_to_terminal(self) -> bool: if is_dev_mode(): - return get_primaite_config_dict().get("developer_mode").get("output_to_terminal") + return PRIMAITE_CONFIG.get("developer_mode").get("output_to_terminal") return self._write_sys_log_to_terminal @write_sys_log_to_terminal.setter @@ -77,7 +77,7 @@ class _SimOutput: @property def sys_log_level(self) -> LogLevel: if is_dev_mode(): - return LogLevel[get_primaite_config_dict().get("developer_mode").get("sys_log_level")] + return LogLevel[PRIMAITE_CONFIG.get("developer_mode").get("sys_log_level")] return self._sys_log_level @sys_log_level.setter diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 03567785..8d426b2d 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -3,9 +3,9 @@ import typer from rich import print from typing_extensions import Annotated -from primaite import _PRIMAITE_ROOT +from primaite import _PRIMAITE_ROOT, PRIMAITE_CONFIG from primaite.simulator import LogLevel -from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict, is_dev_mode, update_primaite_config +from primaite.utils.cli.primaite_config_utils import is_dev_mode, update_primaite_application_config dev = typer.Typer() @@ -45,28 +45,18 @@ def show(): @dev.command() def enable(): """Enable the development mode for PrimAITE.""" - config_dict = get_primaite_config_dict() - - if config_dict is None: - return - # enable dev mode - config_dict["developer_mode"]["enabled"] = True - update_primaite_config(config_dict) + PRIMAITE_CONFIG["developer_mode"]["enabled"] = True + update_primaite_application_config() print(DEVELOPMENT_MODE_MESSAGE) @dev.command() def disable(): """Disable the development mode for PrimAITE.""" - config_dict = get_primaite_config_dict() - - if config_dict is None: - return - # disable dev mode - config_dict["developer_mode"]["enabled"] = False - update_primaite_config(config_dict) + PRIMAITE_CONFIG["developer_mode"]["enabled"] = False + update_primaite_application_config() print(PRODUCTION_MODE_MESSAGE) @@ -105,29 +95,24 @@ def config_callback( ] = None, ): """Configure the development tools and environment.""" - config_dict = get_primaite_config_dict() - - if config_dict is None: - return - if ctx.params.get("sys_log_level") is not None: - config_dict["developer_mode"]["sys_log_level"] = ctx.params.get("sys_log_level") + PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] = ctx.params.get("sys_log_level") print(f"PrimAITE dev-mode config updated sys_log_level={ctx.params.get('sys_log_level')}") if output_sys_logs is not None: - config_dict["developer_mode"]["output_sys_logs"] = output_sys_logs + PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] = output_sys_logs print(f"PrimAITE dev-mode config updated {output_sys_logs=}") if output_pcap_logs is not None: - config_dict["developer_mode"]["output_pcap_logs"] = output_pcap_logs + PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] = output_pcap_logs print(f"PrimAITE dev-mode config updated {output_pcap_logs=}") if output_to_terminal is not None: - config_dict["developer_mode"]["output_to_terminal"] = output_to_terminal + PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] = output_to_terminal print(f"PrimAITE dev-mode config updated {output_to_terminal=}") # update application config - update_primaite_config(config_dict) + update_primaite_application_config() config_typer = typer.Typer( @@ -159,15 +144,10 @@ def path( ] = None, ): """Set the output directory for the PrimAITE system and PCAP logs.""" - config_dict = get_primaite_config_dict() - - if config_dict is None: - return - if default: - config_dict["developer_mode"]["output_dir"] = None + PRIMAITE_CONFIG["developer_mode"]["output_dir"] = None # update application config - update_primaite_config(config_dict) + update_primaite_application_config() print( f"PrimAITE dev-mode output_dir [medium_turquoise]" f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" @@ -176,7 +156,7 @@ def path( return if directory: - config_dict["developer_mode"]["output_dir"] = directory + PRIMAITE_CONFIG["developer_mode"]["output_dir"] = directory # update application config - update_primaite_config(config_dict) + update_primaite_application_config() print(f"PrimAITE dev-mode output_dir [medium_turquoise]{directory}[/medium_turquoise]") diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index a11c2ce3..e0f6fe56 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -1,37 +1,18 @@ -from pathlib import Path -from typing import Dict, Optional - import yaml -from primaite import PRIMAITE_PATHS - - -def get_primaite_config_dict(config_path: Optional[Path] = None) -> Dict: - """ - Returns a dict containing the PrimAITE application config. - - :param: config_path: takes in a path object - leave empty to use the default app config path - """ - err_msg = "PrimAITE application config could not be loaded." - - if config_path is None: - config_path = PRIMAITE_PATHS.app_config_file_path - err_msg = "PrimAITE application config was not found. Have you run `primaite setup`?" - - if config_path.exists(): - with open(config_path, "r") as file: - return yaml.safe_load(file) - else: - print(err_msg) +from primaite import PRIMAITE_CONFIG, PRIMAITE_PATHS def is_dev_mode() -> bool: """Returns True if PrimAITE is currently running in developer mode.""" - config = get_primaite_config_dict() - return config["developer_mode"]["enabled"] if config.get("developer_mode", {}).get("enabled") else False + return ( + PRIMAITE_CONFIG["developer_mode"]["enabled"] + if (PRIMAITE_CONFIG.get("developer_mode", {}).get("enabled")) + else False + ) -def update_primaite_config(config: Dict) -> None: +def update_primaite_application_config() -> None: """Update the PrimAITE application config file.""" with open(PRIMAITE_PATHS.app_config_file_path, "w") as file: - yaml.dump(config, file) + yaml.dump(PRIMAITE_CONFIG, file) From a2fb04e6f6c88d2ee41e2d6b8c63dab5df00e576 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 14:02:40 +0100 Subject: [PATCH 06/13] #2533: set default dev output path + clean up --- .gitignore | 1 + src/primaite/__init__.py | 11 +-- src/primaite/session/io.py | 17 ++-- src/primaite/simulator/__init__.py | 16 ++- src/primaite/utils/cli/dev_cli.py | 6 +- .../utils/cli/primaite_config_utils.py | 6 +- tests/conftest.py | 15 --- tests/integration_tests/cli/test_dev_cli.py | 97 +++++++++---------- .../_simulator/_system/core/test_sys_log.py | 10 ++ 9 files changed, 84 insertions(+), 95 deletions(-) diff --git a/.gitignore b/.gitignore index 5cd39f24..b464566b 100644 --- a/.gitignore +++ b/.gitignore @@ -151,6 +151,7 @@ docs/source/primaite-dependencies.rst # outputs src/primaite/outputs/ simulation_output/ +sessions/ # benchmark session outputs benchmark/output diff --git a/src/primaite/__init__.py b/src/primaite/__init__.py index 2cd44755..98612040 100644 --- a/src/primaite/__init__.py +++ b/src/primaite/__init__.py @@ -122,20 +122,13 @@ class _PrimaitePaths: PRIMAITE_PATHS: Final[_PrimaitePaths] = _PrimaitePaths() -def _host_primaite_config() -> None: - if not PRIMAITE_PATHS.app_config_file_path.exists(): - pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) - shutil.copy2(pkg_config_path, PRIMAITE_PATHS.app_config_file_path) - - -_host_primaite_config() - - def _get_primaite_config() -> Dict: config_path = PRIMAITE_PATHS.app_config_file_path if not config_path.exists(): # load from package if config does not exist config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) + # generate app config + shutil.copy2(config_path, PRIMAITE_PATHS.app_config_file_path) with open(config_path, "r") as file: # load from config primaite_config = yaml.safe_load(file) diff --git a/src/primaite/session/io.py b/src/primaite/session/io.py index 0e7b90c9..9dcc529d 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -5,8 +5,9 @@ from typing import Dict, List, Optional from pydantic import BaseModel, ConfigDict -from primaite import getLogger, PRIMAITE_PATHS +from primaite import _PRIMAITE_ROOT, getLogger, PRIMAITE_PATHS from primaite.simulator import LogLevel, SIM_OUTPUT +from primaite.utils.cli.primaite_config_utils import is_dev_mode _LOGGER = getLogger(__name__) @@ -61,17 +62,13 @@ class PrimaiteIO: date_str = timestamp.strftime("%Y-%m-%d") time_str = timestamp.strftime("%H-%M-%S") - # check if running in dev mode - # if is_dev_mode(): - # # if dev mode, simulation output will be the repository root or whichever path is configured - # app_config = get_primaite_config_dict() - # if app_config["developer_mode"]["output_dir"] is not None: - # session_path = app_config["developer_mode"]["output_dir"] - # else: - # session_path = _PRIMAITE_ROOT.parent.parent / "simulation_output" / date_str / time_str - # else: session_path = PRIMAITE_PATHS.user_sessions_path / date_str / time_str + # check if running in dev mode + if is_dev_mode(): + # check if there is an output directory set in config + session_path = _PRIMAITE_ROOT.parent.parent / "sessions" / date_str / time_str + session_path.mkdir(exist_ok=True, parents=True) return session_path diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index 9f936249..ee165c9f 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -3,7 +3,7 @@ from datetime import datetime from enum import IntEnum from pathlib import Path -from primaite import _PRIMAITE_ROOT, PRIMAITE_CONFIG +from primaite import _PRIMAITE_ROOT, PRIMAITE_CONFIG, PRIMAITE_PATHS __all__ = ["SIM_OUTPUT"] @@ -27,9 +27,17 @@ class LogLevel(IntEnum): class _SimOutput: def __init__(self): - self._path: Path = ( - _PRIMAITE_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - ) + date_str = datetime.now().strftime("%Y-%m-%d") + time_str = datetime.now().strftime("%H-%M-%S") + + path = PRIMAITE_PATHS.user_sessions_path / date_str / time_str + + if is_dev_mode(): + # if dev mode is enabled, if output dir is not set, print to primaite repo root + path: Path = _PRIMAITE_ROOT.parent.parent / "sessions" / date_str / time_str / "simulation_output" + # otherwise print to output dir + + self._path = path self._save_pcap_logs: bool = False self._save_sys_logs: bool = False self._write_sys_log_to_terminal: bool = False diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 8d426b2d..09cafff0 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -15,7 +15,7 @@ PRODUCTION_MODE_MESSAGE = ( " :monkey_face::monkey_face::monkey_face: [/green]\n" ) -DEVELOPMENT_MODE_MESSAGE = ( +DEVELOPER_MODE_MESSAGE = ( "\n[yellow] :construction::construction::construction: " " PrimAITE is running in Development mode " " :construction::construction::construction: [/yellow]\n" @@ -38,7 +38,7 @@ def dev_mode(): def show(): """Show if PrimAITE is in development mode or production mode.""" # print if dev mode is enabled - print(DEVELOPMENT_MODE_MESSAGE if is_dev_mode() else PRODUCTION_MODE_MESSAGE) + print(DEVELOPER_MODE_MESSAGE if is_dev_mode() else PRODUCTION_MODE_MESSAGE) print("\nTo see available options, use [medium_turquoise]`primaite dev-mode --help`[/medium_turquoise]\n") @@ -48,7 +48,7 @@ def enable(): # enable dev mode PRIMAITE_CONFIG["developer_mode"]["enabled"] = True update_primaite_application_config() - print(DEVELOPMENT_MODE_MESSAGE) + print(DEVELOPER_MODE_MESSAGE) @dev.command() diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index e0f6fe56..2a94ece8 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -5,11 +5,7 @@ from primaite import PRIMAITE_CONFIG, PRIMAITE_PATHS def is_dev_mode() -> bool: """Returns True if PrimAITE is currently running in developer mode.""" - return ( - PRIMAITE_CONFIG["developer_mode"]["enabled"] - if (PRIMAITE_CONFIG.get("developer_mode", {}).get("enabled")) - else False - ) + return PRIMAITE_CONFIG["developer_mode"]["enabled"] def update_primaite_application_config() -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 7de2bfde..c7b6ac04 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -40,21 +40,6 @@ ACTION_SPACE_NODE_ACTION_VALUES = 1 _LOGGER = getLogger(__name__) -@pytest.fixture(scope="function", autouse=True) -def set_syslog_output_to_true(): - """Will be run before each test.""" - monkeypatch = MonkeyPatch() - monkeypatch.setattr( - SIM_OUTPUT, - "path", - Path(TEST_ASSETS_ROOT.parent.parent / "simulation_output" / datetime.now().strftime("%Y-%m-%d_%H-%M-%S")), - ) - monkeypatch.setattr(SIM_OUTPUT, "save_pcap_logs", False) - monkeypatch.setattr(SIM_OUTPUT, "save_sys_logs", False) - - yield - - class TestService(Service): """Test Service class""" diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py index d650bc90..acd086b9 100644 --- a/tests/integration_tests/cli/test_dev_cli.py +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -5,34 +5,47 @@ from pathlib import Path import pkg_resources import pytest +import yaml +from _pytest.monkeypatch import MonkeyPatch -from primaite import _PRIMAITE_ROOT, PRIMAITE_PATHS -from primaite.utils.cli.primaite_config_utils import get_primaite_config_dict +import primaite from tests.integration_tests.cli import cli @pytest.fixture(autouse=True) def test_setup(): """ - Setup this test by copying the + Setup this test by using the default primaite app config in package """ - original_config_path = PRIMAITE_PATHS.app_config_file_path # keep track of app config before test + current_config = primaite.PRIMAITE_CONFIG # store the config before test + original_config_path = primaite.PRIMAITE_PATHS.app_config_file_path # keep track of app config before test temp_dir = tempfile.gettempdir() temp_config = Path(temp_dir) / "primaite_config.yaml" pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) shutil.copyfile(pkg_config_path, temp_config) # copy the default primaite config to temp directory - PRIMAITE_PATHS.app_config_file_path = temp_config # use the copy for the test - yield # run test + primaite.PRIMAITE_PATHS.app_config_file_path = temp_config # use the copy for the test + + with open(pkg_config_path, "r") as file: + # load from config + config_dict = yaml.safe_load(file) + + primaite.PRIMAITE_CONFIG = config_dict + assert primaite.PRIMAITE_CONFIG == config_dict + + yield + os.remove(temp_config) # clean up temp file - PRIMAITE_PATHS.app_config_file_path = original_config_path # restore app conf because other devs will yell at me + primaite.PRIMAITE_CONFIG = current_config # restore config to prevent being yelled at + assert primaite.PRIMAITE_CONFIG == current_config + primaite.PRIMAITE_PATHS.app_config_file_path = original_config_path +@pytest.mark.skip(reason="borked") def test_dev_mode_enable_disable(): """Test dev mode enable and disable.""" # check defaults - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["enabled"] is False # not enabled by default + assert primaite.PRIMAITE_CONFIG["developer_mode"]["enabled"] is False # not enabled by default result = cli(["dev-mode", "show"]) assert "Production" in result.output # should print that it is in Production mode by default @@ -41,8 +54,7 @@ def test_dev_mode_enable_disable(): assert "Development" in result.output # should print that it is in Development mode - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["enabled"] # config should reflect that dev mode is enabled + assert primaite.PRIMAITE_CONFIG["developer_mode"]["enabled"] # config should reflect that dev mode is enabled result = cli(["dev-mode", "show"]) assert "Development" in result.output # should print that it is in Development mode @@ -51,136 +63,123 @@ def test_dev_mode_enable_disable(): assert "Production" in result.output # should print that it is in Production mode - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["enabled"] is False # config should reflect that dev mode is disabled + assert ( + primaite.PRIMAITE_CONFIG["developer_mode"]["enabled"] is False + ) # config should reflect that dev mode is disabled result = cli(["dev-mode", "show"]) assert "Production" in result.output # should print that it is in Production mode +@pytest.mark.skip(reason="borked") def test_dev_mode_config_sys_log_level(): """Check that the system log level can be changed via CLI.""" # check defaults - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["sys_log_level"] == "DEBUG" # DEBUG by default + assert primaite.PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "DEBUG" # DEBUG by default result = cli(["dev-mode", "config", "-level", "WARNING"]) assert "sys_log_level=WARNING" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that log level is WARNING - assert config_dict["developer_mode"]["sys_log_level"] == "WARNING" + assert primaite.PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "WARNING" result = cli(["dev-mode", "config", "--sys-log-level", "INFO"]) assert "sys_log_level=INFO" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that log level is WARNING - assert config_dict["developer_mode"]["sys_log_level"] == "INFO" + assert primaite.PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "INFO" +@pytest.mark.skip(reason="borked") def test_dev_mode_config_sys_logs_enable_disable(): """Test that the system logs output can be enabled or disabled.""" # check defaults - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["output_sys_logs"] is False # False by default + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False # False by default result = cli(["dev-mode", "config", "--output-sys-logs"]) assert "output_sys_logs=True" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_sys_logs is True - assert config_dict["developer_mode"]["output_sys_logs"] + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] result = cli(["dev-mode", "config", "--no-sys-logs"]) assert "output_sys_logs=False" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_sys_logs is True - assert config_dict["developer_mode"]["output_sys_logs"] is False + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False result = cli(["dev-mode", "config", "-sys"]) assert "output_sys_logs=True" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_sys_logs is True - assert config_dict["developer_mode"]["output_sys_logs"] + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] result = cli(["dev-mode", "config", "-nsys"]) assert "output_sys_logs=False" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_sys_logs is True - assert config_dict["developer_mode"]["output_sys_logs"] is False + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False +@pytest.mark.skip(reason="borked") def test_dev_mode_config_pcap_logs_enable_disable(): """Test that the pcap logs output can be enabled or disabled.""" # check defaults - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["output_pcap_logs"] is False # False by default + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False # False by default result = cli(["dev-mode", "config", "--output-pcap-logs"]) assert "output_pcap_logs=True" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_pcap_logs is True - assert config_dict["developer_mode"]["output_pcap_logs"] + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] result = cli(["dev-mode", "config", "--no-pcap-logs"]) assert "output_pcap_logs=False" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_pcap_logs is True - assert config_dict["developer_mode"]["output_pcap_logs"] is False + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False result = cli(["dev-mode", "config", "-pcap"]) assert "output_pcap_logs=True" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_pcap_logs is True - assert config_dict["developer_mode"]["output_pcap_logs"] + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] result = cli(["dev-mode", "config", "-npcap"]) assert "output_pcap_logs=False" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_pcap_logs is True - assert config_dict["developer_mode"]["output_pcap_logs"] is False + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False +@pytest.mark.skip(reason="borked") def test_dev_mode_config_output_to_terminal_enable_disable(): """Test that the output to terminal can be enabled or disabled.""" # check defaults - config_dict = get_primaite_config_dict() - assert config_dict["developer_mode"]["output_to_terminal"] is False # False by default + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False # False by default result = cli(["dev-mode", "config", "--output-to-terminal"]) assert "output_to_terminal=True" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_to_terminal is True - assert config_dict["developer_mode"]["output_to_terminal"] + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] result = cli(["dev-mode", "config", "--no-terminal"]) assert "output_to_terminal=False" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_to_terminal is True - assert config_dict["developer_mode"]["output_to_terminal"] is False + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False result = cli(["dev-mode", "config", "-t"]) assert "output_to_terminal=True" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_to_terminal is True - assert config_dict["developer_mode"]["output_to_terminal"] + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] result = cli(["dev-mode", "config", "-nt"]) assert "output_to_terminal=False" in result.output # should print correct value - config_dict = get_primaite_config_dict() # config should reflect that output_to_terminal is True - assert config_dict["developer_mode"]["output_to_terminal"] is False + assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False diff --git a/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py b/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py index 56b58d71..1009adc3 100644 --- a/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py +++ b/tests/unit_tests/_primaite/_simulator/_system/core/test_sys_log.py @@ -2,10 +2,20 @@ from uuid import uuid4 import pytest +from primaite import PRIMAITE_CONFIG from primaite.simulator import LogLevel, SIM_OUTPUT from primaite.simulator.system.core.sys_log import SysLog +@pytest.fixture(autouse=True) +def override_dev_mode_temporarily(): + """Temporarily turn off dev mode for this test.""" + primaite_dev_mode = PRIMAITE_CONFIG["developer_mode"]["enabled"] + PRIMAITE_CONFIG["developer_mode"]["enabled"] = False + yield # run tests + PRIMAITE_CONFIG["developer_mode"]["enabled"] = primaite_dev_mode + + @pytest.fixture(scope="function") def syslog() -> SysLog: return SysLog(hostname="test") From b6b5ce91c29b3069c29790ef91c5fd436f42e52b Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 14:33:33 +0100 Subject: [PATCH 07/13] #2533: clean up temp items in tests + fixing the CLI tests --- .../utils/cli/primaite_config_utils.py | 14 +++- tests/conftest.py | 10 +-- .../test_rllib_single_agent_environment.py | 2 + .../environments/test_sb3_environment.py | 1 + tests/integration_tests/cli/test_dev_cli.py | 70 ++++++++----------- 5 files changed, 44 insertions(+), 53 deletions(-) diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index 2a94ece8..966376d9 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -1,3 +1,5 @@ +from typing import Dict, Optional + import yaml from primaite import PRIMAITE_CONFIG, PRIMAITE_PATHS @@ -8,7 +10,13 @@ def is_dev_mode() -> bool: return PRIMAITE_CONFIG["developer_mode"]["enabled"] -def update_primaite_application_config() -> None: - """Update the PrimAITE application config file.""" +def update_primaite_application_config(config: Optional[Dict] = None) -> None: + """ + Update the PrimAITE application config file. + + :params: config: Leave empty so that PRIMAITE_CONFIG is used - otherwise provide the Dict + """ with open(PRIMAITE_PATHS.app_config_file_path, "w") as file: - yaml.dump(PRIMAITE_CONFIG, file) + if not config: + config = PRIMAITE_CONFIG + yaml.dump(config, file) diff --git a/tests/conftest.py b/tests/conftest.py index c7b6ac04..37bc9581 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,19 +1,14 @@ # © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK -from datetime import datetime -from pathlib import Path -from typing import Any, Dict, Optional, Tuple, Union +from typing import Any, Dict, Tuple import pytest -import yaml -from _pytest.monkeypatch import MonkeyPatch -from primaite import getLogger, PRIMAITE_PATHS +from primaite import getLogger from primaite.game.agent.actions import ActionManager from primaite.game.agent.interface import AbstractAgent from primaite.game.agent.observations.observation_manager import NestedObservation, ObservationManager from primaite.game.agent.rewards import RewardFunction from primaite.game.game import PrimaiteGame -from primaite.simulator import SIM_OUTPUT from primaite.simulator.file_system.file_system import FileSystem from primaite.simulator.network.container import Network from primaite.simulator.network.hardware.nodes.host.computer import Computer @@ -32,7 +27,6 @@ from primaite.simulator.system.services.dns.dns_server import DNSServer from primaite.simulator.system.services.service import Service from primaite.simulator.system.services.web_server.web_server import WebServer from tests import TEST_ASSETS_ROOT -from tests.mock_and_patch.get_session_path_mock import temp_user_sessions_path ACTION_SPACE_NODE_VALUES = 1 ACTION_SPACE_NODE_ACTION_VALUES = 1 diff --git a/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py b/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py index 4c4b8d8d..d9057fef 100644 --- a/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py +++ b/tests/e2e_integration_tests/environments/test_rllib_single_agent_environment.py @@ -38,3 +38,5 @@ def test_rllib_single_agent_compatibility(): save_file = Path(tempfile.gettempdir()) / "ray/" algo.save(save_file) assert save_file.exists() + + save_file.unlink() # clean up diff --git a/tests/e2e_integration_tests/environments/test_sb3_environment.py b/tests/e2e_integration_tests/environments/test_sb3_environment.py index f6ff595f..f654234b 100644 --- a/tests/e2e_integration_tests/environments/test_sb3_environment.py +++ b/tests/e2e_integration_tests/environments/test_sb3_environment.py @@ -25,3 +25,4 @@ def test_sb3_compatibility(): model.save(save_path) assert (save_path).exists() + save_path.unlink() # clean up diff --git a/tests/integration_tests/cli/test_dev_cli.py b/tests/integration_tests/cli/test_dev_cli.py index acd086b9..8f1bdec6 100644 --- a/tests/integration_tests/cli/test_dev_cli.py +++ b/tests/integration_tests/cli/test_dev_cli.py @@ -6,9 +6,9 @@ from pathlib import Path import pkg_resources import pytest import yaml -from _pytest.monkeypatch import MonkeyPatch -import primaite +from primaite import PRIMAITE_CONFIG +from primaite.utils.cli.primaite_config_utils import update_primaite_application_config from tests.integration_tests.cli import cli @@ -17,35 +17,27 @@ def test_setup(): """ Setup this test by using the default primaite app config in package """ - current_config = primaite.PRIMAITE_CONFIG # store the config before test - original_config_path = primaite.PRIMAITE_PATHS.app_config_file_path # keep track of app config before test + global PRIMAITE_CONFIG + current_config = PRIMAITE_CONFIG.copy() # store the config before test - temp_dir = tempfile.gettempdir() - temp_config = Path(temp_dir) / "primaite_config.yaml" pkg_config_path = Path(pkg_resources.resource_filename("primaite", "setup/_package_data/primaite_config.yaml")) - shutil.copyfile(pkg_config_path, temp_config) # copy the default primaite config to temp directory - primaite.PRIMAITE_PATHS.app_config_file_path = temp_config # use the copy for the test with open(pkg_config_path, "r") as file: # load from config config_dict = yaml.safe_load(file) - primaite.PRIMAITE_CONFIG = config_dict - assert primaite.PRIMAITE_CONFIG == config_dict + PRIMAITE_CONFIG["developer_mode"] = config_dict["developer_mode"] yield - os.remove(temp_config) # clean up temp file - primaite.PRIMAITE_CONFIG = current_config # restore config to prevent being yelled at - assert primaite.PRIMAITE_CONFIG == current_config - primaite.PRIMAITE_PATHS.app_config_file_path = original_config_path + PRIMAITE_CONFIG["developer_mode"] = current_config["developer_mode"] # restore config to prevent being yelled at + update_primaite_application_config(config=PRIMAITE_CONFIG) -@pytest.mark.skip(reason="borked") def test_dev_mode_enable_disable(): """Test dev mode enable and disable.""" # check defaults - assert primaite.PRIMAITE_CONFIG["developer_mode"]["enabled"] is False # not enabled by default + assert PRIMAITE_CONFIG["developer_mode"]["enabled"] is False # not enabled by default result = cli(["dev-mode", "show"]) assert "Production" in result.output # should print that it is in Production mode by default @@ -54,7 +46,7 @@ def test_dev_mode_enable_disable(): assert "Development" in result.output # should print that it is in Development mode - assert primaite.PRIMAITE_CONFIG["developer_mode"]["enabled"] # config should reflect that dev mode is enabled + assert PRIMAITE_CONFIG["developer_mode"]["enabled"] # config should reflect that dev mode is enabled result = cli(["dev-mode", "show"]) assert "Development" in result.output # should print that it is in Development mode @@ -63,123 +55,117 @@ def test_dev_mode_enable_disable(): assert "Production" in result.output # should print that it is in Production mode - assert ( - primaite.PRIMAITE_CONFIG["developer_mode"]["enabled"] is False - ) # config should reflect that dev mode is disabled + assert PRIMAITE_CONFIG["developer_mode"]["enabled"] is False # config should reflect that dev mode is disabled result = cli(["dev-mode", "show"]) assert "Production" in result.output # should print that it is in Production mode -@pytest.mark.skip(reason="borked") def test_dev_mode_config_sys_log_level(): """Check that the system log level can be changed via CLI.""" # check defaults - assert primaite.PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "DEBUG" # DEBUG by default + assert PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "DEBUG" # DEBUG by default result = cli(["dev-mode", "config", "-level", "WARNING"]) assert "sys_log_level=WARNING" in result.output # should print correct value # config should reflect that log level is WARNING - assert primaite.PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "WARNING" + assert PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "WARNING" result = cli(["dev-mode", "config", "--sys-log-level", "INFO"]) assert "sys_log_level=INFO" in result.output # should print correct value # config should reflect that log level is WARNING - assert primaite.PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "INFO" + assert PRIMAITE_CONFIG["developer_mode"]["sys_log_level"] == "INFO" -@pytest.mark.skip(reason="borked") def test_dev_mode_config_sys_logs_enable_disable(): """Test that the system logs output can be enabled or disabled.""" # check defaults - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False # False by default + assert PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False # False by default result = cli(["dev-mode", "config", "--output-sys-logs"]) assert "output_sys_logs=True" in result.output # should print correct value # config should reflect that output_sys_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] + assert PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] result = cli(["dev-mode", "config", "--no-sys-logs"]) assert "output_sys_logs=False" in result.output # should print correct value # config should reflect that output_sys_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False + assert PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False result = cli(["dev-mode", "config", "-sys"]) assert "output_sys_logs=True" in result.output # should print correct value # config should reflect that output_sys_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] + assert PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] result = cli(["dev-mode", "config", "-nsys"]) assert "output_sys_logs=False" in result.output # should print correct value # config should reflect that output_sys_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False + assert PRIMAITE_CONFIG["developer_mode"]["output_sys_logs"] is False -@pytest.mark.skip(reason="borked") def test_dev_mode_config_pcap_logs_enable_disable(): """Test that the pcap logs output can be enabled or disabled.""" # check defaults - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False # False by default + assert PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False # False by default result = cli(["dev-mode", "config", "--output-pcap-logs"]) assert "output_pcap_logs=True" in result.output # should print correct value # config should reflect that output_pcap_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] + assert PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] result = cli(["dev-mode", "config", "--no-pcap-logs"]) assert "output_pcap_logs=False" in result.output # should print correct value # config should reflect that output_pcap_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False + assert PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False result = cli(["dev-mode", "config", "-pcap"]) assert "output_pcap_logs=True" in result.output # should print correct value # config should reflect that output_pcap_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] + assert PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] result = cli(["dev-mode", "config", "-npcap"]) assert "output_pcap_logs=False" in result.output # should print correct value # config should reflect that output_pcap_logs is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False + assert PRIMAITE_CONFIG["developer_mode"]["output_pcap_logs"] is False -@pytest.mark.skip(reason="borked") def test_dev_mode_config_output_to_terminal_enable_disable(): """Test that the output to terminal can be enabled or disabled.""" # check defaults - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False # False by default + assert PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False # False by default result = cli(["dev-mode", "config", "--output-to-terminal"]) assert "output_to_terminal=True" in result.output # should print correct value # config should reflect that output_to_terminal is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] + assert PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] result = cli(["dev-mode", "config", "--no-terminal"]) assert "output_to_terminal=False" in result.output # should print correct value # config should reflect that output_to_terminal is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False + assert PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False result = cli(["dev-mode", "config", "-t"]) assert "output_to_terminal=True" in result.output # should print correct value # config should reflect that output_to_terminal is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] + assert PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] result = cli(["dev-mode", "config", "-nt"]) assert "output_to_terminal=False" in result.output # should print correct value # config should reflect that output_to_terminal is True - assert primaite.PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False + assert PRIMAITE_CONFIG["developer_mode"]["output_to_terminal"] is False From 5516fbc6fc709e3727fbcdee4b3400054928e88a Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 14:48:10 +0100 Subject: [PATCH 08/13] #2533: output directory for sessions --- src/primaite/simulator/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index ee165c9f..e9d44ec3 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -36,6 +36,14 @@ class _SimOutput: # if dev mode is enabled, if output dir is not set, print to primaite repo root path: Path = _PRIMAITE_ROOT.parent.parent / "sessions" / date_str / time_str / "simulation_output" # otherwise print to output dir + if PRIMAITE_CONFIG["developer_mode"]["output_dir"]: + path: Path = ( + Path(PRIMAITE_CONFIG["developer_mode"]["output_dir"]) + / "sessions" + / date_str + / time_str + / "simulation_output" + ) self._path = path self._save_pcap_logs: bool = False From 61e7a4e4394deeb583be936ff5f17da39be9401f Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 16:04:18 +0100 Subject: [PATCH 09/13] #2533: documentation on use of dev-mode --- docs/index.rst | 1 + docs/source/developer_tools.rst | 210 ++++++++++++++++++++++++++++++ docs/source/getting_started.rst | 2 + src/primaite/utils/cli/dev_cli.py | 1 + 4 files changed, 214 insertions(+) create mode 100644 docs/source/developer_tools.rst diff --git a/docs/index.rst b/docs/index.rst index 4cc81b13..52bc8b57 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -116,6 +116,7 @@ Head over to the :ref:`getting-started` page to install and setup PrimAITE! :caption: Developer information: :hidden: + source/developer_tools source/state_system source/request_system PrimAITE API diff --git a/docs/source/developer_tools.rst b/docs/source/developer_tools.rst new file mode 100644 index 00000000..fcb52443 --- /dev/null +++ b/docs/source/developer_tools.rst @@ -0,0 +1,210 @@ +.. only:: comment + + © Crown-owned copyright 2023, Defence Science and Technology Laboratory UK + +.. _Developer Tools: + +Developer Tools +*************** + +PrimAITE includes developer CLI tools that are intended to be used by developers. + +dev-mode +======== + +The dev-mode contains configuration which override any of the config files during runtime. + +This is intended to make debugging easier by removing the need to find the relevant configuration file/settings. + +Enabling dev-mode +----------------- + +The PrimAITE dev-mode can be enabled via the use of + +.. code-block:: + + primaite dev-mode enable + +Disabling dev-mode +------------------ + +The PrimAITE dev-mode can be disabled via the use of + +.. code-block:: + + primaite dev-mode disable + +Show current mode +----------------- + +To show if the dev-mode is enabled or not, use +The PrimAITE dev-mode can be disabled via the use of + +.. code-block:: + + primaite dev-mode show + +dev-mode configuration +====================== + +The following configures some specific items that the dev-mode overrides, if enabled. + +`--sys-log-level` or `-level` +---------------------------- + +The level of system logs can be overridden by dev-mode. + +By default, this is set to DEBUG + +The available options are [DEBUG|INFO|WARNING|ERROR|CRITICAL] + +.. code-block:: + + primaite dev-mode config -level INFO + +or + +.. code-block:: + + primaite dev-mode config --sys-log-level INFO + +`--output-sys-logs` or `-sys` +----------------------------- + +The outputting of system logs can be overridden by dev-mode. + +By default, this is set to False + +Enabling system logs +"""""""""""""""""""" + +To enable outputting of system logs + +.. code-block:: + + primaite dev-mode config --output-sys-logs + +or + +.. code-block:: + + primaite dev-mode config -sys + +Disabling system logs +""""""""""""""""""""" + +To disable outputting of system logs + +.. code-block:: + + primaite dev-mode config --no-sys-logs + +or + +.. code-block:: + + primaite dev-mode config -nsys + +`--output-pcap-logs` or `-pcap` +------------------------------- + +The outputting of packet capture logs can be overridden by dev-mode. + +By default, this is set to False + +Enabling PCAP logs +"""""""""""""""""" + +To enable outputting of packet capture logs + +.. code-block:: + + primaite dev-mode config --output-pcap-logs + +or + +.. code-block:: + + primaite dev-mode config -pcap + +Disabling PCAP logs +""""""""""""""""""" + +To disable outputting of packet capture logs + +.. code-block:: + + primaite dev-mode config --no-pcap-logs + +or + +.. code-block:: + + primaite dev-mode config -npcap + +`--output-to-terminal` or `-t` +------------------------------ + +The outputting of system logs to the terminal can be overridden by dev-mode. + +By default, this is set to False + +Enabling system log output to terminal +"""""""""""""""""""""""""""""""""""""" + +To enable outputting of system logs to terminal + +.. code-block:: + + primaite dev-mode config --output-to-terminal + +or + +.. code-block:: + + primaite dev-mode config -t + +Disabling system log output to terminal +""""""""""""""""""""""""""""""""""""""" + +To disable outputting of system logs to terminal + +.. code-block:: + + primaite dev-mode config --no-terminal + +or + +.. code-block:: + + primaite dev-mode config -nt + +path +---- + +PrimAITE dev-mode can override where sessions are output. + +By default, PrimAITE will output the sessions in USER_HOME/primaite/sessions + +With dev-mode enabled, by default, this will be changed to PRIMAITE_REPOSITORY_ROOT/sessions + +However, providing a path will let dev-mode output sessions to the given path e.g. + +.. code-block:: bash + :caption: Unix + + primaite dev-mode config path ~/output/path + +.. code-block:: powershell + :caption: Windows (Powershell) + + primaite dev-mode config path ~\output\path + +default path +"""""""""""" + +To reset the path to use the PRIMAITE_REPOSITORY_ROOT/sessions, run the command + +.. code-block:: + + primaite dev-mode config path --default diff --git a/docs/source/getting_started.rst b/docs/source/getting_started.rst index dccfd8aa..7c91498c 100644 --- a/docs/source/getting_started.rst +++ b/docs/source/getting_started.rst @@ -167,3 +167,5 @@ To set PrimAITE to run in development mode: :caption: Windows (Powershell) primaite dev-mode enable + +More information about :ref:`Developer Tools` diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 09cafff0..7437d2fc 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -39,6 +39,7 @@ def show(): """Show if PrimAITE is in development mode or production mode.""" # print if dev mode is enabled print(DEVELOPER_MODE_MESSAGE if is_dev_mode() else PRODUCTION_MODE_MESSAGE) + print(f"Current Settings: {PRIMAITE_CONFIG['developer_mode']}") print("\nTo see available options, use [medium_turquoise]`primaite dev-mode --help`[/medium_turquoise]\n") From d7572dc18b9ed43c59cac9d43588c068d944531c Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 16:10:27 +0100 Subject: [PATCH 10/13] #2533: changelog --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f79a1fd3..8426f5dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,13 +14,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added ability to define scenarios that change depending on the episode number. - Standardised Environment API by renaming the config parameter of `PrimaiteGymEnv` from `game_config` to `env_config` - Database Connection ID's are now created/issued by DatabaseService and not DatabaseClient -- added ability to set PrimAITE between development and production modes via PrimAITE CLI ``mode`` command - Updated DatabaseClient so that it can now have a single native DatabaseClientConnection along with a collection of DatabaseClientConnection's. - Implemented the uninstall functionality for DatabaseClient so that all connections are terminated at the DatabaseService. - Added the ability for a DatabaseService to terminate a connection. - Added active_connection to DatabaseClientConnection so that if the connection is terminated active_connection is set to False and the object can no longer be used. - Added additional show functions to enable connection inspection. - Updates to agent logging, to include the reward both per step and per episode. +- Introduced Developer CLI tools to assist with developing/debugging PrimAITE + - Can be enabled via `primaite dev-mode enable` + - Activating dev-mode will change the location where the sessions will be output - by default will output where the PrimAITE repository is located ## [Unreleased] From 952f6ee225a11bf843f355ae262b0fcaf678c41c Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 16:23:44 +0100 Subject: [PATCH 11/13] #2533: fix is_dev_mode for missing or outdated configs --- src/primaite/utils/cli/primaite_config_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primaite/utils/cli/primaite_config_utils.py b/src/primaite/utils/cli/primaite_config_utils.py index 966376d9..fa9b68ff 100644 --- a/src/primaite/utils/cli/primaite_config_utils.py +++ b/src/primaite/utils/cli/primaite_config_utils.py @@ -7,7 +7,7 @@ from primaite import PRIMAITE_CONFIG, PRIMAITE_PATHS def is_dev_mode() -> bool: """Returns True if PrimAITE is currently running in developer mode.""" - return PRIMAITE_CONFIG["developer_mode"]["enabled"] + return PRIMAITE_CONFIG.get("developer_mode", {}).get("enabled", False) def update_primaite_application_config(config: Optional[Dict] = None) -> None: From ec6d2bf6401eb9d5e3a43fd707a701573df2fb40 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Wed, 1 May 2024 16:53:37 +0100 Subject: [PATCH 12/13] #2533: fix missing implementation of output_dir override in io.py + moving the dev mode override in SIM_OUTPUT to prevent the override from being overridden --- src/primaite/session/io.py | 7 +++++-- src/primaite/simulator/__init__.py | 20 +++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/primaite/session/io.py b/src/primaite/session/io.py index 9dcc529d..8bbc1b07 100644 --- a/src/primaite/session/io.py +++ b/src/primaite/session/io.py @@ -5,7 +5,7 @@ from typing import Dict, List, Optional from pydantic import BaseModel, ConfigDict -from primaite import _PRIMAITE_ROOT, getLogger, PRIMAITE_PATHS +from primaite import _PRIMAITE_ROOT, getLogger, PRIMAITE_CONFIG, PRIMAITE_PATHS from primaite.simulator import LogLevel, SIM_OUTPUT from primaite.utils.cli.primaite_config_utils import is_dev_mode @@ -66,9 +66,12 @@ class PrimaiteIO: # check if running in dev mode if is_dev_mode(): - # check if there is an output directory set in config session_path = _PRIMAITE_ROOT.parent.parent / "sessions" / date_str / time_str + # check if there is an output directory set in config + if PRIMAITE_CONFIG["developer_mode"]["output_dir"]: + session_path = Path(PRIMAITE_CONFIG["developer_mode"]["output_dir"]) / "sessions" / date_str / time_str + session_path.mkdir(exist_ok=True, parents=True) return session_path diff --git a/src/primaite/simulator/__init__.py b/src/primaite/simulator/__init__.py index e9d44ec3..bbcf9af4 100644 --- a/src/primaite/simulator/__init__.py +++ b/src/primaite/simulator/__init__.py @@ -32,7 +32,17 @@ class _SimOutput: path = PRIMAITE_PATHS.user_sessions_path / date_str / time_str + self._path = path + self._save_pcap_logs: bool = False + self._save_sys_logs: bool = False + self._write_sys_log_to_terminal: bool = False + self._sys_log_level: LogLevel = LogLevel.WARNING # default log level is at WARNING + + @property + def path(self) -> Path: if is_dev_mode(): + date_str = datetime.now().strftime("%Y-%m-%d") + time_str = datetime.now().strftime("%H-%M-%S") # if dev mode is enabled, if output dir is not set, print to primaite repo root path: Path = _PRIMAITE_ROOT.parent.parent / "sessions" / date_str / time_str / "simulation_output" # otherwise print to output dir @@ -44,15 +54,7 @@ class _SimOutput: / time_str / "simulation_output" ) - - self._path = path - self._save_pcap_logs: bool = False - self._save_sys_logs: bool = False - self._write_sys_log_to_terminal: bool = False - self._sys_log_level: LogLevel = LogLevel.WARNING # default log level is at WARNING - - @property - def path(self) -> Path: + self._path = path return self._path @path.setter From e599e03c10bf491e5faffed52ca62e3d4550bfe5 Mon Sep 17 00:00:00 2001 From: Czar Echavez Date: Thu, 2 May 2024 12:21:24 +0100 Subject: [PATCH 13/13] #2533: cleaner output text for dev-mode show + consistent colors --- src/primaite/utils/cli/dev_cli.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/primaite/utils/cli/dev_cli.py b/src/primaite/utils/cli/dev_cli.py index 7437d2fc..d2c8e370 100644 --- a/src/primaite/utils/cli/dev_cli.py +++ b/src/primaite/utils/cli/dev_cli.py @@ -1,6 +1,7 @@ import click import typer from rich import print +from rich.table import Table from typing_extensions import Annotated from primaite import _PRIMAITE_ROOT, PRIMAITE_CONFIG @@ -10,9 +11,9 @@ from primaite.utils.cli.primaite_config_utils import is_dev_mode, update_primait dev = typer.Typer() PRODUCTION_MODE_MESSAGE = ( - "\n[green]:monkey_face::monkey_face::monkey_face: " + "\n[green]:rocket::rocket::rocket: " " PrimAITE is running in Production mode " - " :monkey_face::monkey_face::monkey_face: [/green]\n" + " :rocket::rocket::rocket: [/green]\n" ) DEVELOPER_MODE_MESSAGE = ( @@ -39,8 +40,15 @@ def show(): """Show if PrimAITE is in development mode or production mode.""" # print if dev mode is enabled print(DEVELOPER_MODE_MESSAGE if is_dev_mode() else PRODUCTION_MODE_MESSAGE) - print(f"Current Settings: {PRIMAITE_CONFIG['developer_mode']}") - print("\nTo see available options, use [medium_turquoise]`primaite dev-mode --help`[/medium_turquoise]\n") + + table = Table(title="Current Dev-Mode Settings") + table.add_column("Setting", style="cyan") + table.add_column("Value", style="default") + for setting, value in PRIMAITE_CONFIG["developer_mode"].items(): + table.add_row(setting, str(value)) + + print(table) + print("\nTo see available options, use [cyan]`primaite dev-mode --help`[/cyan]\n") @dev.command() @@ -150,9 +158,9 @@ def path( # update application config update_primaite_application_config() print( - f"PrimAITE dev-mode output_dir [medium_turquoise]" + f"PrimAITE dev-mode output_dir [cyan]" f"{str(_PRIMAITE_ROOT.parent.parent / 'simulation_output')}" - f"[/medium_turquoise]" + f"[/cyan]" ) return @@ -160,4 +168,4 @@ def path( PRIMAITE_CONFIG["developer_mode"]["output_dir"] = directory # update application config update_primaite_application_config() - print(f"PrimAITE dev-mode output_dir [medium_turquoise]{directory}[/medium_turquoise]") + print(f"PrimAITE dev-mode output_dir [cyan]{directory}[/cyan]")