import subprocess import lib.color_log as color_log import shutil import os import shlex from services.odoo.connection import OdooConnection class SSHHandler: def __init__(self, config_path="config/settings.yaml"): self.config = OdooConnection(config_path) def _check_sshpass(): """Check if sshpass is installed in the system.""" return shutil.which("sshpass") is not None def _build_command(host, cmd, ssh_user="root", ssh_key_path=None, passphrase=None): """ Build an SSH command with the given parameters. Args: host (str): The target host cmd (str): The command to execute ssh_user (str): SSH user (default: root) ssh_key_path (str, optional): Path to SSH key file passphrase (str, optional): Passphrase for SSH key Returns: tuple: (args, env) where args is a list of command arguments, and env is a dictionary of environment variables """ # For local execution if host in ["localhost", "127.0.0.1"]: return ["bash", "-c", cmd], None # Build SSH command as a list of arguments args = [ "ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", "-t", ] if ssh_key_path: args.extend(["-i", ssh_key_path]) args.append(f"{ssh_user}@{host}") # Quote the command safely for remote execution remote_cmd = f"sudo -s bash -c {shlex.quote(cmd)}" args.append(remote_cmd) env = None # Handle passphrase securely with sshpass if passphrase: if not SSHHandler._check_sshpass(): raise RuntimeError( "sshpass is required for passphrase authentication but not installed" ) args = ["sshpass", "-e"] + args env = {"SSHPASS": passphrase} return args, env def execute_command(args, instance_name, capture_output=True, env=None): """ Execute a command and handle errors. Args: args (list): List of command arguments instance_name (str): Name of the instance (for logging) capture_output (bool): Whether to capture command output env (dict, optional): Environment variables to set for the command Returns: str: Command output if capture_output is True Raises: subprocess.CalledProcessError: If command execution fails """ try: # Log the command being executed color_log.Show("INFO", f"Executing command: {' '.join(args)}") # Create a copy of the current environment cmd_env = os.environ.copy() # Update with any additional environment variables if env: cmd_env.update(env) # Execute the command with subprocess result = subprocess.run( args, check=True, capture_output=capture_output, text=True, env=cmd_env ) # Handle output if captured if capture_output: if result.stdout: print(result.stdout.strip()) if result.stderr: print(result.stderr.strip()) color_log.Show("OK", f"Command executed successfully for {instance_name}") return result.stdout if capture_output else None except subprocess.CalledProcessError as e: # Handle errors and log output if capture_output: if e.stdout: print(e.stdout.strip()) if e.stderr: print(e.stderr.strip()) color_log.Show( "FAILED", f"Error executing command for {instance_name}: {e}", ) raise def run_ssh_command( host, cmd, instance_name, ssh_user="root", ssh_key_path=None, passphrase=None, capture_output=True, env=None, ): """ Execute a command on a remote host via SSH or locally if host is localhost. Args: host (str): The target host cmd (str): The command to execute instance_name (str): Name of the instance (for logging) ssh_user (str): SSH user (default: root) ssh_key_path (str, optional): Path to SSH key file passphrase (str, optional): Passphrase for SSH key capture_output (bool): Whether to capture command output env (dict, optional): Additional environment variables to set for the command Returns: str: Command output if capture_output is True """ # Build the command and get any required environment variables args, cmd_env = SSHHandler._build_command( host, cmd, ssh_user, ssh_key_path, passphrase ) # Merge command-specific env with user-provided env if cmd_env: if env is None: env = cmd_env else: env.update(cmd_env) # Execute the command return SSHHandler.execute_command(args, instance_name, capture_output, env)