From: aborah-sudo Date: Mon, 20 Apr 2026 13:53:18 +0000 (+0530) Subject: tests: implement login.defs configuration utility X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=8408fd7e6ff5b04f75c7b96cdd35ba10cd9679a7;p=thirdparty%2Fshadow.git tests: implement login.defs configuration utility Introduce LoginDefsConfig class for /etc/login.defs manipulation. It supports getting, setting, and removing configuration options with automatic backup and restoration. --- diff --git a/tests/system/framework/hosts/shadow.py b/tests/system/framework/hosts/shadow.py index 970c821f8..7dba991ec 100644 --- a/tests/system/framework/hosts/shadow.py +++ b/tests/system/framework/hosts/shadow.py @@ -5,6 +5,7 @@ from __future__ import annotations from pathlib import PurePosixPath from typing import Any +from pytest_mh import MultihostUtility from pytest_mh.conn import ProcessLogLevel from .base import BaseHost, BaseLinuxHost @@ -272,3 +273,106 @@ class ShadowHost(BaseHost, BaseLinuxHost): if x["origin"] == origin: self._verify_files.remove(x) break + + +class LoginDefsConfig(MultihostUtility[ShadowHost]): + """ + Manage /etc/login.defs configuration. + + Usage: + # Set a value + shadow.login_defs["CREATE_HOME"] = "no" + + # Get a value + value = shadow.login_defs["CREATE_HOME"] + + # Remove an option + del shadow.login_defs["CREATE_HOME"] + """ + + def __init__(self, host: ShadowHost) -> None: + super().__init__(host) + self._backup_path: str | None = None + + def setup(self) -> None: + """ + Backup the original /etc/login.defs file. + Called automatically by the framework. + """ + if self.host.fs.exists("/etc/login.defs"): + result = self.host.conn.run( + """ + set -ex + backup_path=`mktemp` + cp /etc/login.defs $backup_path + echo $backup_path + """, + log_level=ProcessLogLevel.Error, + ) + self._backup_path = result.stdout_lines[-1].strip() + + def teardown(self) -> None: + """ + Restore the original /etc/login.defs file. + Called automatically by the framework. + """ + if self._backup_path and self.host.fs.exists(self._backup_path): + self.host.conn.run(f"mv {self._backup_path} /etc/login.defs") + + def __getitem__(self, key: str) -> str: + """ + Get a value from /etc/login.defs. + + :param key: Option name (e.g., 'CREATE_HOME') + :type key: str + :return: Current value + :rtype: str + """ + result = self.host.conn.run( + f"grep -E '^{key}\\s+' /etc/login.defs | tail -1 | awk '{{print $2}}'", raise_on_error=False + ) + if result.rc != 0 or not result.stdout.strip(): + return "" + return result.stdout.strip() + + def __setitem__(self, key: str, value: str) -> None: + """ + Set a value in /etc/login.defs. + + :param key: Option name (e.g., 'CREATE_HOME') + :type key: str + :param value: Value to set (can contain special characters like /, &, etc.) + :type value: str + """ + self.logger.info(f"Setting {key}={value} in /etc/login.defs on {self.host.hostname}") + + # Escape special characters for awk + escaped_value = value.replace("/", "\\/") + + self.host.conn.run( + f""" + if grep -q '^{key}\\s' /etc/login.defs; then + awk -v key="{key}" -v val="{escaped_value}" \\ + '{{if ($0 ~ "^" key "\\\\s") print key " " val; else print $0}}' \\ + /etc/login.defs > /etc/login.defs.tmp && mv /etc/login.defs.tmp /etc/login.defs + elif grep -q '^#\\s*{key}\\s' /etc/login.defs; then + awk -v key="{key}" -v val="{escaped_value}" \\ + '{{if ($0 ~ "^#\\\\s*" key "\\\\s") print key " " val; else print $0}}' \\ + /etc/login.defs > /etc/login.defs.tmp && mv /etc/login.defs.tmp /etc/login.defs + else + echo '{key} {value}' >> /etc/login.defs + fi + """, + log_level=ProcessLogLevel.Error, + ) + + def __delitem__(self, key: str) -> None: + """ + Remove an option from /etc/login.defs (comment it out). + + :param key: Option name to remove + :type key: str + """ + self.logger.info(f"Removing {key} from /etc/login.defs on {self.host.hostname}") + + self.host.conn.run(f"sed -i 's/^{key}\\s.*/#&/' /etc/login.defs", log_level=ProcessLogLevel.Error) diff --git a/tests/system/framework/roles/shadow.py b/tests/system/framework/roles/shadow.py index f1a57eaf3..231768029 100644 --- a/tests/system/framework/roles/shadow.py +++ b/tests/system/framework/roles/shadow.py @@ -7,7 +7,7 @@ from typing import Dict, Tuple from pytest_mh.conn import ProcessLogLevel, ProcessResult -from ..hosts.shadow import ShadowHost +from ..hosts.shadow import LoginDefsConfig, ShadowHost from ..misc.errors import ExpectScriptError from .base import BaseLinuxRole @@ -31,6 +31,7 @@ class Shadow(BaseLinuxRole[ShadowHost]): Set up the environment. """ super().__init__(*args, **kwargs) + self.login_defs: LoginDefsConfig = LoginDefsConfig(self.host).postpone_setup() def teardown(self) -> None: """