]> git.ipfire.org Git - thirdparty/shadow.git/commitdiff
tests: implement login.defs configuration utility
authoraborah-sudo <aborah@redhat.com>
Mon, 20 Apr 2026 13:53:18 +0000 (19:23 +0530)
committerIker Pedrosa <ikerpedrosam@gmail.com>
Tue, 5 May 2026 07:24:31 +0000 (09:24 +0200)
Introduce LoginDefsConfig class for /etc/login.defs manipulation.
It supports getting, setting, and removing configuration options
with automatic backup and restoration.

tests/system/framework/hosts/shadow.py
tests/system/framework/roles/shadow.py

index 970c821f8d5cf6e87a0f9ad343bf6b9b25dbeaab..7dba991ec66649f4e125849195bbae4e67385cd5 100644 (file)
@@ -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)
index f1a57eaf31a92431cb30b93127ecd45c38107268..231768029219c4f3b2c7dd08899e11b302c768a2 100644 (file)
@@ -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:
         """