import sys import os import shutil import tempfile import time import logging import subprocess from pathlib import Path # ───────────────────────────────────────────── # 설정 (필요하면 .env 등에서 읽어와도 됨) IDRAC_USER = os.getenv("IDRAC_USER", "root") IDRAC_PASS = os.getenv("IDRAC_PASS", "calvin") RACADM = os.getenv("RACADM_PATH", "racadm") # PATH에 있으면 'racadm' # 로깅 logging.basicConfig( level=logging.INFO, format='%(asctime)s [INFO] root: %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) def read_ip_list(ip_file: Path): ips = [] for line in ip_file.read_text(encoding="utf-8").splitlines(): s = line.strip() if s: ips.append(s) return ips def preflight(ip: str) -> tuple[bool, str]: """접속/인증/권한 간단 점검: getsysinfo 호출.""" cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "getsysinfo"] try: p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=30) if p.returncode != 0: return False, p.stderr.strip() or p.stdout.strip() return True, "" except Exception as e: return False, str(e) def safe_xml_path(src: Path) -> Path: """ racadm이 경로의 공백/한글을 싫어하는 문제 회피: 임시 폴더에 ASCII 파일명으로 복사해서 사용. """ tmp_dir = Path(tempfile.gettempdir()) / "idrac_xml" tmp_dir.mkdir(parents=True, exist_ok=True) # ASCII, 공백 제거 파일명 dst_name = "config_" + str(int(time.time())) + ".xml" dst = tmp_dir / dst_name shutil.copy2(src, dst) return dst def apply_xml(ip: str, xml_path: Path) -> tuple[bool, str]: """ racadm XML 적용. 벤더/세대에 따라 'config -f' 또는 'set -t xml -f' 를 씁니다. 일반적으로 최신 iDRAC은 config -f 가 잘 동작합니다. """ # 1) 공백/한글 제거된 임시 경로 준비 safe_path = safe_xml_path(xml_path) # 2) 명령 조립(리스트 인자, shell=False) # 필요 시 아래 둘 중 하나만 사용하세요. cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "set", "-t", "xml", "-f", str(safe_path)] # 대안: # cmd = [RACADM, "-r", ip, "-u", IDRAC_USER, "-p", IDRAC_PASS, "set", "-t", "xml", "-f", str(safe_path)] logging.info(f"실행 명령(리스트) → {' '.join(cmd)}") try: p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", shell=False, timeout=180) stdout = (p.stdout or "").strip() stderr = (p.stderr or "").strip() if p.returncode != 0: msg = f"racadm 실패 (rc={p.returncode})\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}" return False, msg # 일부 버전은 성공해도 stdout만 출력하고 rc=0 return True, stdout or "성공" except Exception as e: return False, f"예외: {e}" finally: # 임시 XML 제거(필요 시 보관하려면 주석처리) try: safe_path.unlink(missing_ok=True) except Exception: pass def main(): if len(sys.argv) != 3: logging.error("Usage: python 02-set_config.py ") sys.exit(1) ip_file = Path(sys.argv[1]) xml_file = Path(sys.argv[2]) if not ip_file.is_file(): logging.error("IP 파일을 찾을 수 없습니다: %s", ip_file) sys.exit(2) if not xml_file.is_file(): logging.error("XML 파일을 찾을 수 없습니다: %s", xml_file) sys.exit(3) ips = read_ip_list(ip_file) if not ips: logging.error("IP 목록이 비어있습니다.") sys.exit(4) start = time.time() for ip in ips: logging.info("%s에 XML 파일 '%s' 설정 적용 중...", ip, xml_file) ok, why = preflight(ip) if not ok: logging.error("%s 사전 점검(getsysinfo) 실패: %s", ip, why) continue ok, msg = apply_xml(ip, xml_file) if ok: logging.info("%s 설정 성공: %s", ip, msg) else: logging.error("%s 설정 실패\n%s", ip, msg) el = int(time.time() - start) logging.info("전체 설정 소요 시간: %d 시간, %d 분, %d 초.", el // 3600, (el % 3600) // 60, el % 60) if __name__ == "__main__": main()