sdputils.py #8

  • //
  • guest/
  • russell_jackson/
  • sdp/
  • Maintenance/
  • sdputils.py
  • View
  • Commits
  • Open Download .zip Download (4 KB)
#!/usr/bin/env python3
"""
Utility classes and helpers for interacting with the SDP (Server
Deployment Package) configuration and Perforce command-line tools.

"""

from __future__ import annotations

import os
import platform
import subprocess
from configparser import RawConfigParser
from pathlib import Path
from typing import Iterable, Optional


# Users that should never be deleted by maintenance scripts.
SKIP_USERS = {"p4admin", "perforce", "swarm"}


class SDPUtils:
    """Convenience wrapper around SDP configuration and ``p4`` commands."""

    def __init__(self, sdp_instance: str) -> None:
        self.sdp_instance = str(sdp_instance)
        # Load configuration from maintenance.cfg
        config = RawConfigParser()
        config.read("maintenance.cfg")
        self._config = config
        # Determine server port and user from the environment, falling back to config if needed.
        self.server = os.environ.get("P4PORT") or config.get(self.sdp_instance, "server", fallback="")
        self.p4user = os.environ.get("P4USER") or config.get(self.sdp_instance, "p4user", fallback="")
        # Determine the path to the p4 executable.  On Windows use p4.exe; otherwise use the SDP path.
        if platform.system() == "Windows":
            # ``p4.exe`` will be found on PATH or can be specified via the P4BIN environment variable.
            self.p4binary = os.environ.get("P4BIN", "p4.exe")
        else:
            self.p4binary = f"/p4/{self.sdp_instance}/bin/p4_{self.sdp_instance}"

    @property
    def p4(self) -> str:
        """Alias for ``p4binary`` for backward compatibility."""
        return self.p4binary

    def get(self, varname: str, fallback: Optional[str] = None) -> str:
        """
        Retrieve a configuration value from the ``maintenance.cfg`` file for the
        current instance.

        :param varname: Name of the configuration variable.
        :param fallback: Value to return if the variable is not found.
        :returns: The configuration value.
        """
        return self._config.get(self.sdp_instance, varname, fallback=fallback)

    def run_p4(
        self,
        args: Iterable[str],
        *,
        port: Optional[str] = None,
        user: Optional[str] = None,
        input_data: Optional[str] = None,
        check: bool = False,
        capture_output: bool = False,
    ) -> subprocess.CompletedProcess[str]:
        """
        Run a Perforce command using ``subprocess.run`` with the configured
        port and user by default.

        :param args: Iterable of command arguments (e.g. ["info"]).
        :param port: Optional override for the server port; defaults to the
            instance's configured ``server``.
        :param user: Optional override for the Perforce user; defaults to
            the instance's configured ``p4user``.
        :param input_data: Optional string to pass to the process's stdin.
        :param check: If True, raise CalledProcessError on non‑zero exit.
        :param capture_output: If True, capture stdout/stderr and return
            them in the CompletedProcess.
        :returns: A ``CompletedProcess`` instance.
        """
        cmd = [self.p4binary]
        # Supply port and user if provided; otherwise use defaults.
        p4port = port or self.server
        p4user = user or self.p4user
        if p4port:
            cmd.extend(["-p", p4port])
        if p4user:
            cmd.extend(["-u", p4user])
        cmd.extend(args)
        return subprocess.run(
            cmd,
            input=input_data,
            text=True,
            capture_output=capture_output,
            check=check,
        )

    def login(self, port: Optional[str] = None) -> None:
        """
        Perform a ``p4 login -a`` using the admin password file associated with
        this SDP instance.  By default it uses the instance's configured
        server port, but you can provide a different port.

        The password file is read and its contents passed to the ``p4`` command
        via stdin instead of relying on shell redirection.  This method raises
        ``CalledProcessError`` if the login fails.
        """
        passwd_path = Path(f"/p4/common/config/.p4passwd.p4_{self.sdp_instance}.admin")
        try:
            with passwd_path.open() as pf:
                passwd = pf.read().strip() + "\n"
        except FileNotFoundError as exc:
            raise FileNotFoundError(f"Password file {passwd_path} not found: {exc}")
        self.run_p4(["login", "-a"], port=port, input_data=passwd, check=True)
# Change User Description Committed
#9 32424 Russell C. Jackson (Rusty) Remove unnecessary 'from __future__ import annotations' from sdputils.py
#8 32423 Russell C. Jackson (Rusty) Modernize SDP maintenance scripts: security, correctness, and Python 3

- Replace all os.system() and os.popen() calls with subprocess.run() using
  argument lists to eliminate shell injection vulnerabilities
- Fix critical bugs: broken indentation in convert_label_to_autoreload.py,
  malformed print() in p4lock/p4unlock, wrong variable in isitalabel,
  format string typo in maintain_user_from_groups
- Add p4 property alias and shared SKIP_USERS constant to sdputils.py
- Add try/finally with p4.disconnect() to all P4Python scripts
- Replace bare except: with specific exception types throughout
- Update all shebangs to python3, remove unnecessary __future__ imports
- Use context managers for all file handle operations
- Replace from subprocess import * with explicit imports
#7 32388 Russell C. Jackson (Rusty) Updates using Claude.ai to clean up the code, reduce duplication, enhanace security, and use current standards.
#6 31825 Russell C. Jackson (Rusty) Converted to use P4 api and to use only python 3.
#5 25025 Russell C. Jackson (Rusty) Fixed typo in sdputils.py and forced cfgweeks to be an int.
#4 24964 Russell C. Jackson (Rusty) Corrected type and ordering issue.
#3 24675 Russell C. Jackson (Rusty) Fixed bugs in sdputils.py and scripts using it.
Converted to standard 2 space spacing, removed copyright stuff.
#2 24648 Russell C. Jackson (Rusty) Updated p4deleteuser to support deleting workspaces off edge servers.
Added -a to login in sdputils.
#1 22693 Russell C. Jackson (Rusty) Branched a Unix only version of the SDP.
Removed extra items to create a cleaner tree.
Moved a few items around to make more sense without Windows in the mix.
//guest/perforce_software/sdp/dev/Maintenance/sdputils.py
#2 22337 Russell C. Jackson (Rusty) Added +x to file types.
#1 16638 C. Thomas Tyler Routine merge down to dev from main using:
p4 merge -b perforce_software-sdp-dev
//guest/perforce_software/sdp/main/Maintenance/sdputils.py
#1 16581 Robert Cowham Standardised processing and formatting.
Made python 2/3 compatible.
These need automated testing!