Skip to content

munet: Configurable environment variables within Linux Namespace nodes #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions munet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,7 @@
new_window=False,
tmux_target=None,
ns_only=False,
env_vars=None,
):
"""Run a command in a new window (TMUX, Screen or XTerm).

Expand All @@ -1217,6 +1218,7 @@
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`)
Expand All @@ -1228,6 +1230,9 @@
channel = "{}-wait-{}".format(our_pid, Commander.tmux_wait_gen)
Commander.tmux_wait_gen += 1

if not isinstance(env_vars, dict):
env_vars = {}

Check warning on line 1234 in munet/base.py

View check run for this annotation

Codecov / codecov/patch

munet/base.py#L1233-L1234

Added lines #L1233 - L1234 were not covered by tests

if forcex or ("TMUX" not in os.environ and "STY" not in os.environ):
root_level = False
else:
Expand All @@ -1249,8 +1254,11 @@
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}")

Check warning on line 1261 in munet/base.py

View check run for this annotation

Codecov / codecov/patch

munet/base.py#L1259-L1261

Added lines #L1259 - L1261 were not covered by tests
if "MUNET_PID" in os.environ:
cmd.append(f"MUNET_PID={os.environ.get('MUNET_PID')}")
cmd += cmd
Expand All @@ -1269,8 +1277,12 @@
]
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)}'

Check warning on line 1283 in munet/base.py

View check run for this annotation

Codecov / codecov/patch

munet/base.py#L1281-L1283

Added lines #L1281 - L1283 were not covered by tests
# 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}"

Check warning on line 1285 in munet/base.py

View check run for this annotation

Codecov / codecov/patch

munet/base.py#L1285

Added line #L1285 was not covered by tests
if hasattr(self, "rundir"):
envvars += f" RUNDIR={self.rundir}"
if "MUNET_PID" in os.environ:
Expand Down
62 changes: 60 additions & 2 deletions munet/native.py
Original file line number Diff line number Diff line change
Expand Up @@ -1231,16 +1231,74 @@
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"]

Check warning on line 1257 in munet/native.py

View check run for this annotation

Codecov / codecov/patch

munet/native.py#L1255-L1257

Added lines #L1255 - L1257 were not covered by tests

# All values in defaults should NOT be overwritten by config_env
config_env.update(defaults["env"])
defaults["env"] = config_env

Check warning on line 1261 in munet/native.py

View check run for this annotation

Codecov / codecov/patch

munet/native.py#L1260-L1261

Added lines #L1260 - L1261 were not covered by tests

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"]

Check warning on line 1282 in munet/native.py

View check run for this annotation

Codecov / codecov/patch

munet/native.py#L1279-L1282

Added lines #L1279 - L1282 were not covered by tests

if isinstance(env_vars, dict):

Check warning on line 1284 in munet/native.py

View check run for this annotation

Codecov / codecov/patch

munet/native.py#L1284

Added line #L1284 was not covered by tests
# All values in defaults should NOT be overwritten by config_env
config_env.update(env_vars)
env_vars=config_env

Check warning on line 1287 in munet/native.py

View check run for this annotation

Codecov / codecov/patch

munet/native.py#L1286-L1287

Added lines #L1286 - L1287 were not covered by tests

super().run_in_window(

Check warning on line 1289 in munet/native.py

View check run for this annotation

Codecov / codecov/patch

munet/native.py#L1289

Added line #L1289 was not covered by tests
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()
Expand Down
11 changes: 11 additions & 0 deletions tests/mutest_after/env_vars/munet.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

topology:
nodes:
- name: r1
cmd: |
echo $foo0
env:
- name: foo0
value: bar
- name: foo1
value: 'bar ''" baz'
42 changes: 42 additions & 0 deletions tests/mutest_after/env_vars/mutest_config_env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# -*- coding: utf-8 eval: (blacken-mode 1) -*-
# SPDX-License-Identifier: GPL-2.0-or-later
#
# April 18 2025, Liam Brady <[email protected]>
#
# 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,
)