refactor: improve config and ssh core and add iac integration tests
This commit is contained in:
parent
5d61ef8116
commit
2d9e1bc06e
3 changed files with 94 additions and 29 deletions
|
|
@ -9,17 +9,23 @@ class Config:
|
|||
self.data = self._load()
|
||||
|
||||
def _load(self):
|
||||
if not os.path.exists(self.path):
|
||||
# Fallback to local config if exists
|
||||
if os.path.exists("config.yaml"):
|
||||
self.path = os.path.abspath("config.yaml")
|
||||
else:
|
||||
raise FileNotFoundError(f"Config file not found at {self.path}. Please create it based on config.yaml.example")
|
||||
data = {}
|
||||
if os.path.exists(self.path):
|
||||
with open(self.path, 'r') as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
elif os.path.exists("config.yaml"):
|
||||
self.path = os.path.abspath("config.yaml")
|
||||
with open(self.path, 'r') as f:
|
||||
data = yaml.safe_load(f) or {}
|
||||
|
||||
with open(self.path, 'r') as f:
|
||||
return yaml.safe_load(f)
|
||||
return data
|
||||
|
||||
def get(self, key, default=None):
|
||||
# Support ENV overrides: INFRA_PROXMOX_USER -> proxmox.user
|
||||
env_key = "INFRA_" + key.upper().replace('.', '_')
|
||||
if env_key in os.environ:
|
||||
return os.environ[env_key]
|
||||
|
||||
parts = key.split('.')
|
||||
val = self.data
|
||||
for part in parts:
|
||||
|
|
@ -30,18 +36,7 @@ class Config:
|
|||
return val
|
||||
|
||||
def get_node(self, node_name):
|
||||
"""Helper to get proxmox node details by name or default to first if none provided"""
|
||||
nodes = self.get('proxmox.nodes', {})
|
||||
if not nodes:
|
||||
# Fallback for old single-host config if present
|
||||
host = self.get('proxmox.host')
|
||||
if host:
|
||||
return {"host": host, "pass": self.get('proxmox.password')}
|
||||
return None
|
||||
|
||||
if not node_name:
|
||||
# Default to first node found
|
||||
return next(iter(nodes.values()))
|
||||
|
||||
return nodes.get(node_name)
|
||||
|
||||
return nodes.get(node_name)
|
||||
|
|
@ -1,15 +1,23 @@
|
|||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
class SSHClient:
|
||||
def __init__(self, host, user="root", key_path=None, password=None):
|
||||
def __init__(self, host, user="root", key_path=None, password=None, timeout=30):
|
||||
self.host = host
|
||||
self.user = user
|
||||
self.key_path = os.path.expanduser(key_path) if key_path else None
|
||||
self.password = password
|
||||
self.timeout = timeout
|
||||
|
||||
def run(self, cmd, capture=True):
|
||||
ssh_cmd = ["ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"]
|
||||
ssh_cmd = [
|
||||
"ssh",
|
||||
"-o", "StrictHostKeyChecking=no",
|
||||
"-o", "UserKnownHostsFile=/dev/null",
|
||||
"-o", f"ConnectTimeout={self.timeout}",
|
||||
"-o", "BatchMode=yes" if not self.password else "BatchMode=no"
|
||||
]
|
||||
|
||||
if self.key_path:
|
||||
ssh_cmd += ["-i", self.key_path]
|
||||
|
|
@ -17,16 +25,26 @@ class SSHClient:
|
|||
target = f"{self.user}@{self.host}"
|
||||
|
||||
if self.password:
|
||||
# sshpass is required for password auth
|
||||
full_cmd = ["sshpass", "-p", self.password] + ssh_cmd + [target, cmd]
|
||||
else:
|
||||
full_cmd = ssh_cmd + [target, cmd]
|
||||
|
||||
result = subprocess.run(
|
||||
full_cmd,
|
||||
capture_output=capture,
|
||||
text=True
|
||||
)
|
||||
return result
|
||||
try:
|
||||
result = subprocess.run(
|
||||
full_cmd,
|
||||
capture_output=capture,
|
||||
text=True,
|
||||
timeout=self.timeout + 10
|
||||
)
|
||||
return result
|
||||
except subprocess.TimeoutExpired:
|
||||
print(f"Error: SSH command timed out after {self.timeout}s on {self.host}", file=sys.stderr)
|
||||
# Create a mock result for timeout
|
||||
return subprocess.CompletedProcess(full_cmd, 1, "", "Timeout expired")
|
||||
except Exception as e:
|
||||
print(f"Error: SSH execution failed on {self.host}: {e}", file=sys.stderr)
|
||||
return subprocess.CompletedProcess(full_cmd, 1, "", str(e))
|
||||
|
||||
def scp_to(self, local_path, remote_path):
|
||||
scp_cmd = ["scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null"]
|
||||
|
|
@ -40,4 +58,4 @@ class SSHClient:
|
|||
else:
|
||||
full_cmd = scp_cmd + [local_path, target]
|
||||
|
||||
return subprocess.run(full_cmd, capture_output=True)
|
||||
return subprocess.run(full_cmd, capture_output=True, timeout=self.timeout + 60)
|
||||
Loading…
Add table
Add a link
Reference in a new issue