diff --git a/munet/base.py b/munet/base.py index e9410d4..04c824e 100644 --- a/munet/base.py +++ b/munet/base.py @@ -1204,6 +1204,7 @@ def run_in_window( # pylint: disable=too-many-positional-arguments new_window=False, tmux_target=None, ns_only=False, + env_vars=None, ): """Run a command in a new window (TMUX, Screen or XTerm). @@ -1217,6 +1218,7 @@ def run_in_window( # pylint: disable=too-many-positional-arguments forcex: Force use of X11. new_window: Open new window (instead of pane) in TMUX tmux_target: Target for tmux pane. + env_vars: Dict of extra env variables to set in the window Returns: the pane/window identifier from TMUX (depends on `new_window`) @@ -1228,6 +1230,9 @@ def run_in_window( # pylint: disable=too-many-positional-arguments channel = "{}-wait-{}".format(our_pid, Commander.tmux_wait_gen) Commander.tmux_wait_gen += 1 + if not isinstance(env_vars, dict): + env_vars = {} + if forcex or ("TMUX" not in os.environ and "STY" not in os.environ): root_level = False else: @@ -1249,8 +1254,11 @@ def run_in_window( # pylint: disable=too-many-positional-arguments cmd = shlex.split(cmd) cmd = [ "/usr/bin/env", - f"MUNET_NODENAME={self.name}", ] + # Set extra env variables first so that they will be overwritted if needed + for key, value in env_vars: + cmd.append(f"{key}={shell_quote(value)}") + cmd.append(f"MUNET_NODENAME={self.name}") if "MUNET_PID" in os.environ: cmd.append(f"MUNET_PID={os.environ.get('MUNET_PID')}") cmd += cmd @@ -1269,8 +1277,12 @@ def run_in_window( # pylint: disable=too-many-positional-arguments ] else: # This is the command to execute to be inside the namespace. + # Set extra env variables first so that they will be overwritted if needed + envvars = "" + for key, value in env_vars.items(): + envvars += f' {key}={shell_quote(value)}' # We are getting into trouble with quoting. - envvars = f"MUNET_NODENAME={self.name} NODENAME={self.name}" + envvars += f" MUNET_NODENAME={self.name} NODENAME={self.name}" if hasattr(self, "rundir"): envvars += f" RUNDIR={self.rundir}" if "MUNET_PID" in os.environ: diff --git a/munet/native.py b/munet/native.py index 4e29fe9..5aa9406 100644 --- a/munet/native.py +++ b/munet/native.py @@ -1231,16 +1231,74 @@ async def _async_delete(self): class L3NamespaceNode(L3NodeMixin, LinuxNamespace): """A namespace L3 node.""" - def __init__(self, name, pid=True, **kwargs): + def __init__(self, name, pid=True, config=None, **kwargs): # logging.warning( # "L3NamespaceNode: name %s MRO: %s kwargs %s", # name, # L3NamespaceNode.mro(), # kwargs, # ) - super().__init__(name, pid=pid, **kwargs) + self.config = config if config else {} + super().__init__(name, pid=pid, config=config, **kwargs) super().pytest_hook_open_shell() + def _get_sub_args(self, cmd_list, defaults, use_pty=False, ns_only=False, **kwargs): + """Returns pre-command, cmd, and default keyword args. + + Overrides Commander in order to insert configured enviornmental variables + """ + pre_cmd_list, cmd_list, defaults = super()._get_sub_args( + cmd_list, defaults, use_pty, ns_only, **kwargs + ) + + if self.config and "env" in self.config and self.config["env"]: + config_env = {} + for env_var in self.config["env"].items(): + config_env[env_var[0]] = env_var[1]["value"] + + # All values in defaults should NOT be overwritten by config_env + config_env.update(defaults["env"]) + defaults["env"] = config_env + + return pre_cmd_list, cmd_list, defaults + + def run_in_window( # pylint: disable=too-many-positional-arguments + self, + cmd, + wait_for=False, + background=False, + name=None, + title=None, + forcex=False, + new_window=False, + tmux_target=None, + ns_only=False, + env_vars=None, + ): + + if self.config and "env" in self.config and self.config["env"]: + config_env = {} + for env_var in self.config["env"].items(): + config_env[env_var[0]] = env_var[1]["value"] + + if isinstance(env_vars, dict): + # All values in defaults should NOT be overwritten by config_env + config_env.update(env_vars) + env_vars=config_env + + super().run_in_window( + cmd, + wait_for, + background, + name, + title, + forcex, + new_window, + tmux_target, + ns_only, + env_vars, + ) + async def _async_delete(self): self.logger.debug("%s: deleting", self) await super()._async_delete() diff --git a/tests/mutest_after/env_vars/munet.yaml b/tests/mutest_after/env_vars/munet.yaml new file mode 100644 index 0000000..908a0a1 --- /dev/null +++ b/tests/mutest_after/env_vars/munet.yaml @@ -0,0 +1,11 @@ + +topology: + nodes: + - name: r1 + cmd: | + echo $foo0 + env: + - name: foo0 + value: bar + - name: foo1 + value: 'bar ''" baz' diff --git a/tests/mutest_after/env_vars/mutest_config_env.py b/tests/mutest_after/env_vars/mutest_config_env.py new file mode 100644 index 0000000..c85fed0 --- /dev/null +++ b/tests/mutest_after/env_vars/mutest_config_env.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# April 18 2025, Liam Brady +# +# Copyright (c) 2022, LabN Consulting, L.L.C. +# +"""Test for config of env variables. + +This test is for ensuring that configured environment variables properly appear +within run commands. +""" +import munet +from munet.mutest.userapi import match_step +from munet.mutest.userapi import section + +# Configured environment variables also should be set within +# new munet windows, however, this is not tested here. + +section("Test config for env variables") + +match_step( + "r1", + "cat cmd.out", + "bar", + "Check for env variable in cmd.out", + exact_match=True, +) +match_step( + "r1", + "echo $foo0", + "bar", + "Check for env variable in Linux namespace", + exact_match=True, +) +match_step( + "r1", + "echo $foo1", + "bar '\" baz", + "Check env variable for proper quoting", + exact_match=True, +)