#!/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! |
||