import subprocess
from functools import lru_cache
from typing import Any, Dict, List, Optional
@lru_cache(maxsize=16)
def cli_available(binary: str) -> bool:
"""Check if a CLI binary is available (cached)."""
try:
result = subprocess.run(
[binary, "version"],
capture_output=True,
timeout=10
)
return result.returncode == 0
except Exception:
return False
@lru_cache(maxsize=16)
def get_cli_version(binary: str) -> Optional[str]:
"""Get CLI binary version string (cached)."""
try:
result = subprocess.run(
[binary, "version"],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
return result.stdout.strip()
return None
except Exception:
return None
def run_cli(
binary: str,
args: List[str],
timeout: int = 300,
capture_output: bool = True
) -> Dict[str, Any]:
"""Run a CLI command and return a standardized result dict.
Args:
binary: CLI binary name (e.g., "kind", "vcluster", "helm")
args: Command arguments (without the binary prefix)
timeout: Command timeout in seconds
capture_output: Whether to capture stdout/stderr
Returns:
Dict with success status and output/error
"""
cmd = [binary, *args]
try:
result = subprocess.run(
cmd,
capture_output=capture_output,
text=True,
timeout=timeout
)
if result.returncode == 0:
output = result.stdout.strip() if capture_output else ""
return {"success": True, "output": output}
return {
"success": False,
"error": result.stderr.strip() if capture_output else f"Command failed with exit code {result.returncode}"
}
except subprocess.TimeoutExpired:
return {"success": False, "error": f"Command timed out after {timeout} seconds"}
except FileNotFoundError:
return {"success": False, "error": f"{binary} CLI not available"}
except Exception as e:
return {"success": False, "error": str(e)}