refactor: remove SSH handling from GitHandler and update cmd service usage
This commit is contained in:
parent
49a722e0a1
commit
6a8c514817
@ -6,7 +6,7 @@ import sys
|
|||||||
import fcntl
|
import fcntl
|
||||||
import select
|
import select
|
||||||
from ..services import config as Config
|
from ..services import config as Config
|
||||||
from ..lib import color_log
|
from ..common import color_log
|
||||||
|
|
||||||
|
|
||||||
def set_nonblocking(fd):
|
def set_nonblocking(fd):
|
||||||
|
0
services/cmd/__init__.py
Normal file
0
services/cmd/__init__.py
Normal file
@ -10,146 +10,47 @@ class SSHHandler:
|
|||||||
def __init__(self, config_path="config/settings.yaml"):
|
def __init__(self, config_path="config/settings.yaml"):
|
||||||
self.config = OdooConnection(config_path)
|
self.config = OdooConnection(config_path)
|
||||||
|
|
||||||
def _check_sshpass():
|
def _get_remote_command(self, instance, cmd):
|
||||||
"""Check if sshpass is installed in the system."""
|
"""Generate SSH command for remote execution."""
|
||||||
return shutil.which("sshpass") is not None
|
host = instance["host"]
|
||||||
|
ssh_settings = instance.get("ssh", {})
|
||||||
|
ssh_user = ssh_settings.get("user", "root")
|
||||||
|
ssh_key_path = ssh_settings.get("key_path")
|
||||||
|
|
||||||
def _build_command(host, cmd, ssh_user="root", ssh_key_path=None, passphrase=None):
|
local_host = host in ["localhost", "127.0.0.1"]
|
||||||
"""
|
|
||||||
Build an SSH command with the given parameters.
|
|
||||||
|
|
||||||
Args:
|
if not local_host:
|
||||||
host (str): The target host
|
if not ssh_key_path:
|
||||||
cmd (str): The command to execute
|
return f"ssh -t {ssh_user}@{host} 'sudo -s bash -c \"{cmd}\"'"
|
||||||
ssh_user (str): SSH user (default: root)
|
else:
|
||||||
ssh_key_path (str, optional): Path to SSH key file
|
return f"ssh -i {ssh_key_path} {ssh_user}@{host} 'sudo -s bash -c \"{cmd}\"'"
|
||||||
passphrase (str, optional): Passphrase for SSH key
|
return cmd
|
||||||
|
|
||||||
Returns:
|
def _execute_command(self, cmd, instance_name):
|
||||||
tuple: (args, env) where args is a list of command arguments, and env is a dictionary of environment variables
|
"""Execute a shell command and handle errors."""
|
||||||
"""
|
|
||||||
# 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:
|
try:
|
||||||
# Log the command being executed
|
color_log.Show("INFO", f"Executing command: {cmd}")
|
||||||
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(
|
result = subprocess.run(
|
||||||
args, check=True, capture_output=capture_output, text=True, env=cmd_env
|
cmd, shell=True, check=True, capture_output=True, text=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Handle output if captured
|
# Print the output if there is any
|
||||||
if capture_output:
|
if result.stdout:
|
||||||
if result.stdout:
|
print(result.stdout.strip())
|
||||||
print(result.stdout.strip())
|
if result.stderr:
|
||||||
if result.stderr:
|
print(result.stderr.strip())
|
||||||
print(result.stderr.strip())
|
|
||||||
|
|
||||||
color_log.Show("OK", f"Command executed successfully for {instance_name}")
|
color_log.Show("OK", f"Command executed successfully for {instance_name}")
|
||||||
return result.stdout if capture_output else None
|
return result.stdout
|
||||||
|
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
# Handle errors and log output
|
# Print the error output
|
||||||
if capture_output:
|
if e.stdout:
|
||||||
if e.stdout:
|
print(e.stdout.strip())
|
||||||
print(e.stdout.strip())
|
if e.stderr:
|
||||||
if e.stderr:
|
print(e.stderr.strip())
|
||||||
print(e.stderr.strip())
|
|
||||||
|
|
||||||
color_log.Show(
|
color_log.Show(
|
||||||
"FAILED",
|
"FAILED",
|
||||||
f"Error executing command for {instance_name}: {e}",
|
f"Error performing git operation for {instance_name}: {e}",
|
||||||
)
|
)
|
||||||
raise
|
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)
|
|
||||||
|
@ -5,41 +5,14 @@ from services.odoo.connection import OdooConnection
|
|||||||
import common.color_log as color_log
|
import common.color_log as color_log
|
||||||
import subprocess
|
import subprocess
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
from services.cmd.ssh import SSHHandler
|
||||||
|
|
||||||
|
|
||||||
class GitHandler:
|
class GitHandler:
|
||||||
def __init__(self, config_path="config/settings.yaml"):
|
def __init__(self, config_path="config/settings.yaml"):
|
||||||
self.config = OdooConnection(config_path)
|
self.config = OdooConnection(config_path)
|
||||||
self.local_path = None # Will be set based on instance configuration
|
self.local_path = None # Will be set based on instance configuration
|
||||||
|
self.ssh_handler = SSHHandler(config_path)
|
||||||
def _execute_command(self, cmd, instance_name):
|
|
||||||
"""Execute a shell command and handle errors."""
|
|
||||||
try:
|
|
||||||
color_log.Show("INFO", f"Executing command: {cmd}")
|
|
||||||
result = subprocess.run(
|
|
||||||
cmd, shell=True, check=True, capture_output=True, text=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Print the output if there is any
|
|
||||||
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
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
# Print the error output
|
|
||||||
if e.stdout:
|
|
||||||
print(e.stdout.strip())
|
|
||||||
if e.stderr:
|
|
||||||
print(e.stderr.strip())
|
|
||||||
|
|
||||||
color_log.Show(
|
|
||||||
"FAILED",
|
|
||||||
f"Error performing git operation for {instance_name}: {e}",
|
|
||||||
)
|
|
||||||
raise
|
|
||||||
|
|
||||||
def _get_auth_url(self, repo_url, instance):
|
def _get_auth_url(self, repo_url, instance):
|
||||||
"""Add authentication to repository URL if credentials are provided."""
|
"""Add authentication to repository URL if credentials are provided."""
|
||||||
@ -57,22 +30,6 @@ class GitHandler:
|
|||||||
return auth_url
|
return auth_url
|
||||||
return repo_url
|
return repo_url
|
||||||
|
|
||||||
def _get_remote_command(self, instance, cmd):
|
|
||||||
"""Generate SSH command for remote execution."""
|
|
||||||
host = instance["host"]
|
|
||||||
ssh_settings = instance.get("ssh", {})
|
|
||||||
ssh_user = ssh_settings.get("user", "root")
|
|
||||||
ssh_key_path = ssh_settings.get("key_path")
|
|
||||||
|
|
||||||
local_host = host in ["localhost", "127.0.0.1"]
|
|
||||||
|
|
||||||
if not local_host:
|
|
||||||
if not ssh_key_path:
|
|
||||||
return f"ssh -t {ssh_user}@{host} 'sudo -s bash -c \"{cmd}\"'"
|
|
||||||
else:
|
|
||||||
return f"ssh -i {ssh_key_path} {ssh_user}@{host} 'sudo -s bash -c \"{cmd}\"'"
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def clone_or_open_repo(self, instance_name=None, repo_url=None, branch=None):
|
def clone_or_open_repo(self, instance_name=None, repo_url=None, branch=None):
|
||||||
"""Clone or open repository with SSH support"""
|
"""Clone or open repository with SSH support"""
|
||||||
try:
|
try:
|
||||||
@ -112,16 +69,18 @@ class GitHandler:
|
|||||||
check_cmd = (
|
check_cmd = (
|
||||||
f"test -d {self.local_path}/.git && echo 'exists' || echo 'not exists'"
|
f"test -d {self.local_path}/.git && echo 'exists' || echo 'not exists'"
|
||||||
)
|
)
|
||||||
remote_check_cmd = self._get_remote_command(instance, check_cmd)
|
remote_check_cmd = self.ssh_handler._get_remote_command(instance, check_cmd)
|
||||||
result = self._execute_command(remote_check_cmd, instance_name)
|
result = self.ssh_handler._execute_command(remote_check_cmd, instance_name)
|
||||||
|
|
||||||
if "not exists" in result:
|
if "not exists" in result:
|
||||||
# Clone repository with authentication
|
# Clone repository with authentication
|
||||||
clone_cmd = (
|
clone_cmd = (
|
||||||
f"git clone -b {branch or 'main'} {auth_url} {self.local_path}"
|
f"git clone -b {branch or 'main'} {auth_url} {self.local_path}"
|
||||||
)
|
)
|
||||||
remote_clone_cmd = self._get_remote_command(instance, clone_cmd)
|
remote_clone_cmd = self.ssh_handler._get_remote_command(
|
||||||
self._execute_command(remote_clone_cmd, instance_name)
|
instance, clone_cmd
|
||||||
|
)
|
||||||
|
self.ssh_handler._execute_command(remote_clone_cmd, instance_name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -183,8 +142,8 @@ class GitHandler:
|
|||||||
|
|
||||||
# Combine commands and execute remotely
|
# Combine commands and execute remotely
|
||||||
combined_cmd = " && ".join(git_commands)
|
combined_cmd = " && ".join(git_commands)
|
||||||
remote_cmd = self._get_remote_command(instance, combined_cmd)
|
remote_cmd = self.ssh_handler._get_remote_command(instance, combined_cmd)
|
||||||
self._execute_command(remote_cmd, instance_name)
|
self.ssh_handler._execute_command(remote_cmd, instance_name)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -194,64 +153,27 @@ class GitHandler:
|
|||||||
def get_current_commit(self):
|
def get_current_commit(self):
|
||||||
return self.repo.head.commit.hexsha if self.repo else None
|
return self.repo.head.commit.hexsha if self.repo else None
|
||||||
|
|
||||||
def _get_command(self, instance, action, repo_url=None, branch=None):
|
|
||||||
"""
|
|
||||||
Generate the appropriate git command based on instance type and action.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
instance (dict): Instance configuration
|
|
||||||
action (str): Git action (clone/pull)
|
|
||||||
repo_url (str, optional): Repository URL for clone operation
|
|
||||||
branch (str, optional): Branch name
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
str: Generated git command
|
|
||||||
"""
|
|
||||||
host = instance["host"]
|
|
||||||
ssh_settings = instance.get("ssh", {})
|
|
||||||
ssh_user = ssh_settings.get("user", "root")
|
|
||||||
ssh_key_path = ssh_settings.get("key_path")
|
|
||||||
|
|
||||||
local_host = host in ["localhost", "127.0.0.1"]
|
|
||||||
|
|
||||||
# Base git command
|
|
||||||
if action == "clone":
|
|
||||||
if not repo_url:
|
|
||||||
raise ValueError("Repository URL is required for clone operation")
|
|
||||||
cmd = f"git clone -b {branch or 'main'} {repo_url} {self.local_path}"
|
|
||||||
elif action == "pull":
|
|
||||||
cmd = f"git --git-dir={self.local_path}/.git --work-tree={self.local_path} pull origin {branch or 'main'}"
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unsupported git action: {action}")
|
|
||||||
|
|
||||||
# Wrap with SSH if remote host
|
|
||||||
if not local_host:
|
|
||||||
if not ssh_key_path:
|
|
||||||
cmd = f"ssh -t {ssh_user}@{host} 'sudo {cmd}'"
|
|
||||||
else:
|
|
||||||
cmd = f"ssh -i {ssh_key_path} {ssh_user}@{host} 'sudo {cmd}'"
|
|
||||||
|
|
||||||
return cmd
|
|
||||||
|
|
||||||
def get_submodule_paths(project_path):
|
def get_submodule_paths(project_path):
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['git', 'submodule', '--quiet', 'foreach', 'echo $path'],
|
["git", "submodule", "--quiet", "foreach", "echo $path"],
|
||||||
cwd=project_path,
|
cwd=project_path,
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True
|
text=True,
|
||||||
)
|
)
|
||||||
paths = result.stdout.strip().split('\n') if result.returncode == 0 else []
|
paths = result.stdout.strip().split("\n") if result.returncode == 0 else []
|
||||||
return [os.path.join(project_path, p) for p in paths if p]
|
return [os.path.join(project_path, p) for p in paths if p]
|
||||||
|
|
||||||
def get_git_diff(project_path, summary=True):
|
def get_git_diff(project_path, summary=True):
|
||||||
"""Run git diff and return the output."""
|
"""Run git diff and return the output."""
|
||||||
flag = "--compact-summary" if summary else ""
|
flag = "--compact-summary" if summary else ""
|
||||||
result = os.popen(f'cd "{project_path}" && git diff {flag}').read()
|
result = os.popen(f'cd "{project_path}" && git diff {flag}').read()
|
||||||
staged_changes = os.popen(f'cd "{project_path}" && git diff --cached {flag}').read()
|
staged_changes = os.popen(
|
||||||
|
f'cd "{project_path}" && git diff --cached {flag}'
|
||||||
|
).read()
|
||||||
|
|
||||||
# Combine both staged and unstaged changes
|
# Combine both staged and unstaged changes
|
||||||
all_changes = result.strip() + "\n" + staged_changes.strip()
|
all_changes = result.strip() + "\n" + staged_changes.strip()
|
||||||
return all_changes.strip() if all_changes.strip() else "✓ No changes"
|
return all_changes.strip() if all_changes.strip() else "✓ No changes"
|
||||||
|
|
||||||
async def has_submodules(self):
|
async def has_submodules(self):
|
||||||
"""Check if the repository has any submodules."""
|
"""Check if the repository has any submodules."""
|
||||||
@ -296,4 +218,4 @@ class GitHandler:
|
|||||||
path = parts[1]
|
path = parts[1]
|
||||||
abs_path = os.path.abspath(path)
|
abs_path = os.path.abspath(path)
|
||||||
submodule_info[abs_path] = name
|
submodule_info[abs_path] = name
|
||||||
return submodule_info
|
return submodule_info
|
||||||
|
Loading…
Reference in New Issue
Block a user