update
This commit is contained in:
308
backend/services/drm_catalog_sync.py
Normal file
308
backend/services/drm_catalog_sync.py
Normal file
@@ -0,0 +1,308 @@
|
||||
"""
|
||||
Dell Repository Manager (DRM) 연동 모듈
|
||||
backend/services/drm_catalog_sync.py
|
||||
|
||||
Dell Repository Manager에서 생성한 로컬 리포지토리 카탈로그를 파싱하여
|
||||
펌웨어 정보를 가져오는 기능
|
||||
"""
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Optional
|
||||
from backend.models.firmware_version import FirmwareVersion, db
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class DRMCatalogSync:
|
||||
"""Dell Repository Manager 카탈로그 동기화"""
|
||||
|
||||
def __init__(self, drm_repository_path: str = None):
|
||||
"""
|
||||
Args:
|
||||
drm_repository_path: DRM 리포지토리 경로
|
||||
예: C:/Dell/Repository 또는 네트워크 경로
|
||||
"""
|
||||
self.repository_path = drm_repository_path
|
||||
self.catalog_file = None
|
||||
|
||||
if drm_repository_path:
|
||||
# DRM은 보통 catalog 폴더에 XML 파일 생성
|
||||
possible_catalogs = [
|
||||
os.path.join(drm_repository_path, 'catalog', 'Catalog.xml'),
|
||||
os.path.join(drm_repository_path, 'Catalog.xml'),
|
||||
os.path.join(drm_repository_path, 'catalog.xml'),
|
||||
]
|
||||
|
||||
for catalog_path in possible_catalogs:
|
||||
if os.path.exists(catalog_path):
|
||||
self.catalog_file = catalog_path
|
||||
print(f"[INFO] DRM 카탈로그 파일 발견: {catalog_path}")
|
||||
break
|
||||
|
||||
def sync_from_drm_repository(self, model: str = "PowerEdge R750") -> int:
|
||||
"""
|
||||
DRM 리포지토리에서 펌웨어 정보 동기화
|
||||
|
||||
Args:
|
||||
model: 서버 모델명
|
||||
|
||||
Returns:
|
||||
동기화된 펌웨어 개수
|
||||
"""
|
||||
if not self.catalog_file or not os.path.exists(self.catalog_file):
|
||||
print(f"[오류] DRM 카탈로그 파일을 찾을 수 없습니다: {self.repository_path}")
|
||||
return 0
|
||||
|
||||
try:
|
||||
print(f"[INFO] DRM 카탈로그 파싱 중: {self.catalog_file}")
|
||||
|
||||
# XML 파싱
|
||||
tree = ET.parse(self.catalog_file)
|
||||
root = tree.getroot()
|
||||
|
||||
count = 0
|
||||
|
||||
# DRM 카탈로그 구조 파싱
|
||||
for pkg in root.findall(".//SoftwareComponent"):
|
||||
# 모델 필터링
|
||||
supported_models = self._get_supported_models(pkg)
|
||||
|
||||
if model and model not in supported_models:
|
||||
continue
|
||||
|
||||
# 펌웨어 정보 추출
|
||||
firmware_info = self._extract_firmware_info(pkg, model)
|
||||
|
||||
if firmware_info:
|
||||
# DB에 저장
|
||||
if self._save_firmware_to_db(firmware_info):
|
||||
count += 1
|
||||
|
||||
print(f"✓ DRM에서 {model} 관련 펌웨어 {count}개 동기화 완료")
|
||||
return count
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] DRM 카탈로그 동기화 실패: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return 0
|
||||
|
||||
def _get_supported_models(self, pkg_element) -> List[str]:
|
||||
"""패키지가 지원하는 서버 모델 목록 추출"""
|
||||
models = []
|
||||
|
||||
# Dell 카탈로그 XML 구조
|
||||
for display in pkg_element.findall(".//SupportedSystems/Brand/Model/Display"):
|
||||
if display.text:
|
||||
models.append(display.text.strip())
|
||||
|
||||
return models
|
||||
|
||||
def _extract_firmware_info(self, pkg_element, model: str) -> Optional[Dict]:
|
||||
"""패키지에서 펌웨어 정보 추출"""
|
||||
try:
|
||||
name = pkg_element.findtext("Name")
|
||||
version = pkg_element.findtext("vendorVersion") # DRM은 vendorVersion 사용
|
||||
if not version:
|
||||
version = pkg_element.findtext("dellVersion") # 또는 dellVersion
|
||||
|
||||
release_date = pkg_element.findtext("dateTime")
|
||||
path = pkg_element.findtext("path")
|
||||
category = pkg_element.findtext("Category")
|
||||
|
||||
# 중요도 판단
|
||||
importance = pkg_element.findtext("ImportanceDisplay")
|
||||
is_critical = importance and "Critical" in importance
|
||||
|
||||
# 파일 정보
|
||||
file_name = None
|
||||
file_size_mb = None
|
||||
|
||||
if path:
|
||||
file_name = os.path.basename(path)
|
||||
# 실제 파일이 있으면 크기 확인
|
||||
if self.repository_path:
|
||||
full_path = os.path.join(self.repository_path, path)
|
||||
if os.path.exists(full_path):
|
||||
file_size_mb = os.path.getsize(full_path) // (1024 * 1024)
|
||||
|
||||
if not name or not version:
|
||||
return None
|
||||
|
||||
return {
|
||||
'component_name': name,
|
||||
'latest_version': version,
|
||||
'server_model': model,
|
||||
'vendor': 'Dell',
|
||||
'release_date': self._parse_date(release_date),
|
||||
'download_url': f"https://downloads.dell.com/{path}" if path else None,
|
||||
'file_name': file_name,
|
||||
'file_size_mb': file_size_mb,
|
||||
'component_type': category,
|
||||
'is_critical': is_critical,
|
||||
'notes': f"DRM에서 동기화 - {category}" if category else "DRM에서 동기화"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f"[경고] 펌웨어 정보 추출 실패: {str(e)}")
|
||||
return None
|
||||
|
||||
def _parse_date(self, date_str: str) -> Optional[str]:
|
||||
"""날짜 문자열 파싱"""
|
||||
if not date_str:
|
||||
return None
|
||||
|
||||
try:
|
||||
# DRM 날짜 형식: 2024-03-15T10:30:00
|
||||
dt = datetime.fromisoformat(date_str.replace('Z', '+00:00'))
|
||||
return dt.strftime('%Y-%m-%d')
|
||||
except:
|
||||
return date_str[:10] if len(date_str) >= 10 else None
|
||||
|
||||
def _save_firmware_to_db(self, firmware_info: Dict) -> bool:
|
||||
"""펌웨어 정보를 DB에 저장"""
|
||||
try:
|
||||
# 중복 확인
|
||||
existing = FirmwareVersion.query.filter_by(
|
||||
component_name=firmware_info['component_name'],
|
||||
latest_version=firmware_info['latest_version'],
|
||||
server_model=firmware_info['server_model']
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
# 이미 존재하면 업데이트
|
||||
for key, value in firmware_info.items():
|
||||
if hasattr(existing, key) and value is not None:
|
||||
setattr(existing, key, value)
|
||||
db.session.commit()
|
||||
return False # 새로 추가된 것은 아님
|
||||
else:
|
||||
# 새로 추가
|
||||
version = FirmwareVersion(**firmware_info)
|
||||
db.session.add(version)
|
||||
db.session.commit()
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] DB 저장 실패: {str(e)}")
|
||||
db.session.rollback()
|
||||
return False
|
||||
|
||||
def list_available_models(self) -> List[str]:
|
||||
"""DRM 리포지토리에서 사용 가능한 모델 목록 조회"""
|
||||
if not self.catalog_file or not os.path.exists(self.catalog_file):
|
||||
return []
|
||||
|
||||
try:
|
||||
tree = ET.parse(self.catalog_file)
|
||||
root = tree.getroot()
|
||||
|
||||
models = set()
|
||||
|
||||
for display in root.findall(".//SupportedSystems/Brand/Model/Display"):
|
||||
if display.text:
|
||||
models.add(display.text.strip())
|
||||
|
||||
return sorted(list(models))
|
||||
|
||||
except Exception as e:
|
||||
print(f"[오류] 모델 목록 조회 실패: {str(e)}")
|
||||
return []
|
||||
|
||||
def get_repository_info(self) -> Dict:
|
||||
"""DRM 리포지토리 정보 조회"""
|
||||
if not self.catalog_file or not os.path.exists(self.catalog_file):
|
||||
return {
|
||||
'exists': False,
|
||||
'path': self.repository_path,
|
||||
'catalog_file': None
|
||||
}
|
||||
|
||||
try:
|
||||
tree = ET.parse(self.catalog_file)
|
||||
root = tree.getroot()
|
||||
|
||||
# 카탈로그 메타데이터
|
||||
base_location = root.findtext(".//BaseLocation")
|
||||
release_id = root.findtext(".//ReleaseID")
|
||||
|
||||
# 패키지 수 계산
|
||||
total_packages = len(root.findall(".//SoftwareComponent"))
|
||||
|
||||
return {
|
||||
'exists': True,
|
||||
'path': self.repository_path,
|
||||
'catalog_file': self.catalog_file,
|
||||
'base_location': base_location,
|
||||
'release_id': release_id,
|
||||
'total_packages': total_packages,
|
||||
'available_models': self.list_available_models()
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'exists': True,
|
||||
'path': self.repository_path,
|
||||
'catalog_file': self.catalog_file,
|
||||
'error': str(e)
|
||||
}
|
||||
|
||||
|
||||
# ========================================
|
||||
# 편의 함수
|
||||
# ========================================
|
||||
|
||||
def sync_from_drm(repository_path: str, model: str = "PowerEdge R750") -> Dict:
|
||||
"""
|
||||
DRM 리포지토리에서 펌웨어 동기화 (간편 함수)
|
||||
|
||||
Args:
|
||||
repository_path: DRM 리포지토리 경로
|
||||
model: 서버 모델명
|
||||
|
||||
Returns:
|
||||
{'success': bool, 'count': int, 'message': str}
|
||||
"""
|
||||
try:
|
||||
drm = DRMCatalogSync(repository_path)
|
||||
|
||||
# 리포지토리 확인
|
||||
info = drm.get_repository_info()
|
||||
if not info['exists']:
|
||||
return {
|
||||
'success': False,
|
||||
'count': 0,
|
||||
'message': f'DRM 리포지토리를 찾을 수 없습니다: {repository_path}'
|
||||
}
|
||||
|
||||
# 동기화
|
||||
count = drm.sync_from_drm_repository(model)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'count': count,
|
||||
'message': f'{model} 펌웨어 {count}개 동기화 완료',
|
||||
'repository_info': info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'count': 0,
|
||||
'message': f'DRM 동기화 실패: {str(e)}'
|
||||
}
|
||||
|
||||
|
||||
def check_drm_repository(repository_path: str) -> Dict:
|
||||
"""
|
||||
DRM 리포지토리 상태 확인
|
||||
|
||||
Args:
|
||||
repository_path: DRM 리포지토리 경로
|
||||
|
||||
Returns:
|
||||
리포지토리 정보
|
||||
"""
|
||||
drm = DRMCatalogSync(repository_path)
|
||||
return drm.get_repository_info()
|
||||
Reference in New Issue
Block a user